func (ovs *overlordSuite) TestNewWithPatches(c *C) { p := func(s *state.State) error { s.Set("patched", true) return nil } patch.Mock(1, map[int]func(*state.State) error{1: p}) fakeState := []byte(fmt.Sprintf(`{"data":{"patch-level":0}}`)) err := ioutil.WriteFile(dirs.SnapStateFile, fakeState, 0600) c.Assert(err, IsNil) o, err := overlord.New() c.Assert(err, IsNil) state := o.State() c.Assert(err, IsNil) state.Lock() defer state.Unlock() var level int err = state.Get("patch-level", &level) c.Assert(err, IsNil) c.Check(level, Equals, 1) var b bool err = state.Get("patched", &b) c.Assert(err, IsNil) c.Check(b, Equals, true) }
func (s *repositorySuite) TestAddHandlerGenerator(c *C) { repository := newRepository() var calledContext *Context mockHandlerGenerator := func(context *Context) Handler { calledContext = context return hooktest.NewMockHandler() } // Verify that a handler generator can be added to the repository repository.addHandlerGenerator(regexp.MustCompile("test-hook"), mockHandlerGenerator) state := state.New(nil) state.Lock() task := state.NewTask("test-task", "my test task") setup := &HookSetup{Snap: "test-snap", Revision: snap.R(1), Hook: "test-hook"} context := &Context{task: task, setup: setup} state.Unlock() c.Assert(context, NotNil) // Verify that the handler can be generated handlers := repository.generateHandlers(context) c.Check(handlers, HasLen, 1) c.Check(calledContext, DeepEquals, context) // Add another handler repository.addHandlerGenerator(regexp.MustCompile(".*-hook"), mockHandlerGenerator) // Verify that two handlers are generated for the test-hook, now handlers = repository.generateHandlers(context) c.Check(handlers, HasLen, 2) c.Check(calledContext, DeepEquals, context) }
func assertsFindMany(c *Command, r *http.Request, user *auth.UserState) Response { assertTypeName := muxVars(r)["assertType"] assertType := asserts.Type(assertTypeName) if assertType == nil { return BadRequest("invalid assert type: %q", assertTypeName) } headers := map[string]string{} q := r.URL.Query() for k := range q { headers[k] = q.Get(k) } state := c.d.overlord.State() state.Lock() db := assertstate.DB(state) state.Unlock() assertions, err := db.FindMany(assertType, headers) if err == asserts.ErrNotFound { return AssertResponse(nil, true) } else if err != nil { return InternalError("searching assertions failed: %v", err) } return AssertResponse(assertions, true) }
func abortChange(c *Command, r *http.Request, user *auth.UserState) Response { chID := muxVars(r)["id"] state := c.d.overlord.State() state.Lock() defer state.Unlock() chg := state.Change(chID) if chg == nil { return NotFound("cannot find change with id %q", chID) } var reqData struct { Action string `json:"action"` } decoder := json.NewDecoder(r.Body) if err := decoder.Decode(&reqData); err != nil { return BadRequest("cannot decode data from request body: %v", err) } if reqData.Action != "abort" { return BadRequest("change action %q is unsupported", reqData.Action) } if chg.Status().Ready() { return BadRequest("cannot abort change %s with nothing pending", chID) } // flag the change chg.Abort() // actually ask to proceed with the abort ensureStateSoon(state) return SyncResponse(change2changeInfo(chg), nil) }
func (s *getSuite) TestGetTests(c *C) { for _, test := range getTests { c.Logf("Test: %s", test.args) mockHandler := hooktest.NewMockHandler() state := state.New(nil) state.Lock() task := state.NewTask("test-task", "my test task") setup := &hookstate.HookSetup{Snap: "test-snap", Revision: snap.R(1), Hook: "test-hook"} var err error mockContext, err := hookstate.NewContext(task, setup, mockHandler) c.Check(err, IsNil) // Initialize configuration t := configstate.NewTransaction(state) t.Set("test-snap", "test-key1", "test-value1") t.Set("test-snap", "test-key2", 2) t.Commit() state.Unlock() stdout, stderr, err := ctlcmd.Run(mockContext, strings.Fields(test.args)) if test.error != "" { c.Check(err, ErrorMatches, test.error) } else { c.Check(err, IsNil) c.Check(string(stderr), Equals, "") c.Check(string(stdout), Equals, test.stdout) } } }
func (c *Command) ServeHTTP(w http.ResponseWriter, r *http.Request) { state := c.d.overlord.State() state.Lock() // TODO Look at the error and fail if there's an attempt to authenticate with invalid data. user, _ := UserFromRequest(state, r) state.Unlock() if !c.canAccess(r, user) { Unauthorized("access denied").ServeHTTP(w, r) return } var rspf ResponseFunc var rsp = BadMethod("method %q not allowed", r.Method) switch r.Method { case "GET": rspf = c.GET case "PUT": rspf = c.PUT case "POST": rspf = c.POST case "DELETE": rspf = c.DELETE } if rspf != nil { rsp = rspf(c, r, user) } rsp.ServeHTTP(w, r) }
func (s *contextSuite) SetUpTest(c *C) { state := state.New(nil) state.Lock() defer state.Unlock() s.task = state.NewTask("test-task", "my test task") s.setup = &HookSetup{Snap: "test-snap", Revision: snap.R(1), Hook: "test-hook"} var err error s.context, err = NewContext(s.task, s.setup, nil) c.Check(err, IsNil) }
func getChanges(c *Command, r *http.Request, user *auth.UserState) Response { query := r.URL.Query() qselect := query.Get("select") if qselect == "" { qselect = "in-progress" } var filter func(*state.Change) bool switch qselect { case "all": filter = func(*state.Change) bool { return true } case "in-progress": filter = func(chg *state.Change) bool { return !chg.Status().Ready() } case "ready": filter = func(chg *state.Change) bool { return chg.Status().Ready() } default: return BadRequest("select should be one of: all,in-progress,ready") } if wantedName := query.Get("for"); wantedName != "" { outerFilter := filter filter = func(chg *state.Change) bool { if !outerFilter(chg) { return false } var snapNames []string if err := chg.Get("snap-names", &snapNames); err != nil { logger.Noticef("Cannot get snap-name for change %v", chg.ID()) return false } for _, snapName := range snapNames { if snapName == wantedName { return true } } return false } } state := c.d.overlord.State() state.Lock() defer state.Unlock() chgs := state.Changes() chgInfos := make([]*changeInfo, 0, len(chgs)) for _, chg := range chgs { if !filter(chg) { continue } chgInfos = append(chgInfos, change2changeInfo(chg)) } return SyncResponse(chgInfos, nil) }
func getChange(c *Command, r *http.Request, user *auth.UserState) Response { chID := muxVars(r)["id"] state := c.d.overlord.State() state.Lock() defer state.Unlock() chg := state.Change(chID) if chg == nil { return NotFound("cannot find change with id %q", chID) } return SyncResponse(change2changeInfo(chg), nil) }
func (s *setSuite) SetUpTest(c *C) { s.mockHandler = hooktest.NewMockHandler() state := state.New(nil) state.Lock() defer state.Unlock() task := state.NewTask("test-task", "my test task") setup := &hookstate.HookSetup{Snap: "test-snap", Revision: snap.R(1), Hook: "test-hook"} var err error s.mockContext, err = hookstate.NewContext(task, setup, s.mockHandler) c.Assert(err, IsNil) }
// changeInterfaces controls the interfaces system. // Plugs can be connected to and disconnected from slots. // When enableInternalInterfaceActions is true plugs and slots can also be // explicitly added and removed. func changeInterfaces(c *Command, r *http.Request, user *auth.UserState) Response { var a interfaceAction decoder := json.NewDecoder(r.Body) if err := decoder.Decode(&a); err != nil { return BadRequest("cannot decode request body into an interface action: %v", err) } if a.Action == "" { return BadRequest("interface action not specified") } if !c.d.enableInternalInterfaceActions && a.Action != "connect" && a.Action != "disconnect" { return BadRequest("internal interface actions are disabled") } if len(a.Plugs) > 1 || len(a.Slots) > 1 { return NotImplemented("many-to-many operations are not implemented") } if a.Action != "connect" && a.Action != "disconnect" { return BadRequest("unsupported interface action: %q", a.Action) } if len(a.Plugs) == 0 || len(a.Slots) == 0 { return BadRequest("at least one plug and slot is required") } var summary string var taskset *state.TaskSet var err error state := c.d.overlord.State() state.Lock() defer state.Unlock() switch a.Action { case "connect": summary = fmt.Sprintf("Connect %s:%s to %s:%s", a.Plugs[0].Snap, a.Plugs[0].Name, a.Slots[0].Snap, a.Slots[0].Name) taskset, err = ifacestate.Connect(state, a.Plugs[0].Snap, a.Plugs[0].Name, a.Slots[0].Snap, a.Slots[0].Name) case "disconnect": summary = fmt.Sprintf("Disconnect %s:%s from %s:%s", a.Plugs[0].Snap, a.Plugs[0].Name, a.Slots[0].Snap, a.Slots[0].Name) taskset, err = ifacestate.Disconnect(state, a.Plugs[0].Snap, a.Plugs[0].Name, a.Slots[0].Snap, a.Slots[0].Name) } if err != nil { return BadRequest("%v", err) } change := state.NewChange(a.Action+"-snap", summary) change.Set("snap-names", []string{a.Plugs[0].Snap, a.Slots[0].Snap}) change.AddAll(taskset) state.EnsureBefore(0) return AsyncResponse(nil, &Meta{Change: change.ID()}) }
func logoutUser(c *Command, r *http.Request, user *auth.UserState) Response { state := c.d.overlord.State() state.Lock() defer state.Unlock() if user == nil { return BadRequest("not logged in") } err := auth.RemoveUser(state, user.ID) if err != nil { return InternalError(err.Error()) } return SyncResponse(nil, nil) }
func storeUpdates(c *Command, r *http.Request, user *auth.UserState) Response { route := c.d.router.Get(snapCmd.Path) if route == nil { return InternalError("cannot find route for snaps") } state := c.d.overlord.State() state.Lock() updates, err := snapstateRefreshCandidates(state, user) state.Unlock() if err != nil { return InternalError("cannot list updates: %v", err) } return sendStorePackages(route, nil, updates) }
func (s *getSuite) SetUpTest(c *C) { s.mockHandler = hooktest.NewMockHandler() state := state.New(nil) state.Lock() defer state.Unlock() task := state.NewTask("test-task", "my test task") setup := &hookstate.HookSetup{Snap: "test-snap", Revision: snap.R(1), Hook: "test-hook"} var err error s.mockContext, err = hookstate.NewContext(task, setup, s.mockHandler) c.Assert(err, IsNil) // Initialize configuration transaction := configstate.NewTransaction(state) transaction.Set("test-snap", "initial-key", "initial-value") transaction.Commit() }
func doAssert(c *Command, r *http.Request, user *auth.UserState) Response { batch := assertstate.NewBatch() _, err := batch.AddStream(r.Body) if err != nil { return BadRequest("cannot decode request body into assertions: %v", err) } state := c.d.overlord.State() state.Lock() defer state.Unlock() if err := batch.Commit(state); err != nil { return BadRequest("assert failed: %v", err) } // TODO: what more info do we want to return on success? return &resp{ Type: ResponseTypeSync, Status: http.StatusOK, } }
func postSnap(c *Command, r *http.Request, user *auth.UserState) Response { route := c.d.router.Get(stateChangeCmd.Path) if route == nil { return InternalError("cannot find route for change") } decoder := json.NewDecoder(r.Body) var inst snapInstruction if err := decoder.Decode(&inst); err != nil { return BadRequest("cannot decode request body into snap instruction: %v", err) } state := c.d.overlord.State() state.Lock() defer state.Unlock() if user != nil { inst.userID = user.ID } vars := muxVars(r) inst.Snaps = []string{vars["name"]} impl := inst.dispatch() if impl == nil { return BadRequest("unknown action %s", inst.Action) } msg, tsets, err := impl(&inst, state) if err != nil { return BadRequest("cannot %s %q: %v", inst.Action, inst.Snaps[0], err) } chg := newChange(state, inst.Action+"-snap", msg, tsets, inst.Snaps) ensureStateSoon(state) return AsyncResponse(nil, &Meta{Change: chg.ID()}) }
func (ovs *overlordSuite) TestNewWithGoodState(c *C) { fakeState := []byte(fmt.Sprintf(`{"data":{"patch-level":%d,"some":"data"},"changes":null,"tasks":null,"last-change-id":0,"last-task-id":0}`, patch.Level)) err := ioutil.WriteFile(dirs.SnapStateFile, fakeState, 0600) c.Assert(err, IsNil) o, err := overlord.New() c.Assert(err, IsNil) state := o.State() c.Assert(err, IsNil) state.Lock() defer state.Unlock() d, err := state.MarshalJSON() c.Assert(err, IsNil) var got, expected map[string]interface{} err = json.Unmarshal(d, &got) c.Assert(err, IsNil) err = json.Unmarshal(fakeState, &expected) c.Assert(err, IsNil) c.Check(got, DeepEquals, expected) }
func (s *FirstBootTestSuite) TestPopulateFromSeedHappy(c *C) { // put a firstboot snap into the SnapBlobDir snapYaml := `name: foo version: 1.0` mockSnapFile := snaptest.MakeTestSnapWithFiles(c, snapYaml, nil) targetSnapFile := filepath.Join(dirs.SnapSeedDir, "snaps", filepath.Base(mockSnapFile)) err := os.Rename(mockSnapFile, targetSnapFile) c.Assert(err, IsNil) // put a firstboot local snap into the SnapBlobDir snapYaml = `name: local version: 1.0` mockSnapFile = snaptest.MakeTestSnapWithFiles(c, snapYaml, nil) targetSnapFile2 := filepath.Join(dirs.SnapSeedDir, "snaps", filepath.Base(mockSnapFile)) err = os.Rename(mockSnapFile, targetSnapFile2) c.Assert(err, IsNil) devAcct := assertstest.NewAccount(s.storeSigning, "developer", map[string]interface{}{ "account-id": "developerid", }, "") devAcctFn := filepath.Join(dirs.SnapSeedDir, "assertions", "developer.account") err = ioutil.WriteFile(devAcctFn, asserts.Encode(devAcct), 0644) c.Assert(err, IsNil) snapDecl, err := s.storeSigning.Sign(asserts.SnapDeclarationType, map[string]interface{}{ "series": "16", "snap-id": "snapidsnapid", "publisher-id": "developerid", "snap-name": "foo", "timestamp": time.Now().UTC().Format(time.RFC3339), }, nil, "") c.Assert(err, IsNil) declFn := filepath.Join(dirs.SnapSeedDir, "assertions", "foo.snap-declaration") err = ioutil.WriteFile(declFn, asserts.Encode(snapDecl), 0644) c.Assert(err, IsNil) sha3_384, size, err := asserts.SnapFileSHA3_384(targetSnapFile) c.Assert(err, IsNil) snapRev, err := s.storeSigning.Sign(asserts.SnapRevisionType, map[string]interface{}{ "snap-sha3-384": sha3_384, "snap-size": fmt.Sprintf("%d", size), "snap-id": "snapidsnapid", "developer-id": "developerid", "snap-revision": "128", "timestamp": time.Now().UTC().Format(time.RFC3339), }, nil, "") c.Assert(err, IsNil) revFn := filepath.Join(dirs.SnapSeedDir, "assertions", "foo.snap-revision") err = ioutil.WriteFile(revFn, asserts.Encode(snapRev), 0644) c.Assert(err, IsNil) // add a model assertion and its chain assertsChain := s.makeModelAssertionChain(c) for i, as := range assertsChain { fn := filepath.Join(dirs.SnapSeedDir, "assertions", strconv.Itoa(i)) err := ioutil.WriteFile(fn, asserts.Encode(as), 0644) c.Assert(err, IsNil) } // create a seed.yaml content := []byte(fmt.Sprintf(` snaps: - name: foo file: %s devmode: true - name: local unasserted: true file: %s `, filepath.Base(targetSnapFile), filepath.Base(targetSnapFile2))) err = ioutil.WriteFile(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), content, 0644) c.Assert(err, IsNil) // run the firstboot stuff err = boot.PopulateStateFromSeed() c.Assert(err, IsNil) // and check the snap got correctly installed c.Check(osutil.FileExists(filepath.Join(dirs.SnapMountDir, "foo", "128", "meta", "snap.yaml")), Equals, true) c.Check(osutil.FileExists(filepath.Join(dirs.SnapMountDir, "local", "x1", "meta", "snap.yaml")), Equals, true) // verify r, err := os.Open(dirs.SnapStateFile) c.Assert(err, IsNil) state, err := state.ReadState(nil, r) c.Assert(err, IsNil) state.Lock() defer state.Unlock() // check foo info, err := snapstate.CurrentInfo(state, "foo") c.Assert(err, IsNil) c.Assert(info.SnapID, Equals, "snapidsnapid") c.Assert(info.Revision, Equals, snap.R(128)) c.Assert(info.DeveloperID, Equals, "developerid") var snapst snapstate.SnapState err = snapstate.Get(state, "foo", &snapst) c.Assert(err, IsNil) c.Assert(snapst.DevMode(), Equals, true) // check local info, err = snapstate.CurrentInfo(state, "local") c.Assert(err, IsNil) c.Assert(info.SnapID, Equals, "") c.Assert(info.Revision, Equals, snap.R("x1")) c.Assert(info.DeveloperID, Equals, "") }
func loginUser(c *Command, r *http.Request, user *auth.UserState) Response { var loginData struct { Username string `json:"username"` Password string `json:"password"` Otp string `json:"otp"` } decoder := json.NewDecoder(r.Body) if err := decoder.Decode(&loginData); err != nil { return BadRequest("cannot decode login data from request body: %v", err) } // the "username" needs to look a lot like an email address if !isEmailish(loginData.Username) { return SyncResponse(&resp{ Type: ResponseTypeError, Result: &errorResult{ Message: "please use a valid email address.", Kind: errorKindInvalidAuthData, Value: map[string][]string{"email": {"invalid"}}, }, Status: http.StatusBadRequest, }, nil) } macaroon, discharge, err := store.LoginUser(loginData.Username, loginData.Password, loginData.Otp) switch err { case store.ErrAuthenticationNeeds2fa: return SyncResponse(&resp{ Type: ResponseTypeError, Result: &errorResult{ Kind: errorKindTwoFactorRequired, Message: err.Error(), }, Status: http.StatusUnauthorized, }, nil) case store.Err2faFailed: return SyncResponse(&resp{ Type: ResponseTypeError, Result: &errorResult{ Kind: errorKindTwoFactorFailed, Message: err.Error(), }, Status: http.StatusUnauthorized, }, nil) default: if err, ok := err.(store.ErrInvalidAuthData); ok { return SyncResponse(&resp{ Type: ResponseTypeError, Result: &errorResult{ Message: err.Error(), Kind: errorKindInvalidAuthData, Value: err, }, Status: http.StatusBadRequest, }, nil) } return Unauthorized(err.Error()) case nil: // continue } overlord := c.d.overlord state := overlord.State() state.Lock() _, err = auth.NewUser(state, loginData.Username, macaroon, []string{discharge}) state.Unlock() if err != nil { return InternalError("cannot persist authentication details: %v", err) } result := loginResponseData{ Macaroon: macaroon, Discharges: []string{discharge}, } return SyncResponse(result, nil) }
func (s *FirstBootTestSuite) TestPopulateFromSeedHappyMultiAssertsFiles(c *C) { // put a firstboot snap into the SnapBlobDir snapYaml := `name: foo version: 1.0` mockSnapFile := snaptest.MakeTestSnapWithFiles(c, snapYaml, nil) fooSnapFile := filepath.Join(dirs.SnapSeedDir, "snaps", filepath.Base(mockSnapFile)) err := os.Rename(mockSnapFile, fooSnapFile) c.Assert(err, IsNil) // put a 2nd firstboot snap into the SnapBlobDir snapYaml = `name: bar version: 1.0` mockSnapFile = snaptest.MakeTestSnapWithFiles(c, snapYaml, nil) barSnapFile := filepath.Join(dirs.SnapSeedDir, "snaps", filepath.Base(mockSnapFile)) err = os.Rename(mockSnapFile, barSnapFile) c.Assert(err, IsNil) devAcct := assertstest.NewAccount(s.storeSigning, "developer", map[string]interface{}{ "account-id": "developerid", }, "") snapDeclFoo, err := s.storeSigning.Sign(asserts.SnapDeclarationType, map[string]interface{}{ "series": "16", "snap-id": "foosnapidsnapid", "publisher-id": "developerid", "snap-name": "foo", "timestamp": time.Now().UTC().Format(time.RFC3339), }, nil, "") c.Assert(err, IsNil) sha3_384, size, err := asserts.SnapFileSHA3_384(fooSnapFile) c.Assert(err, IsNil) snapRevFoo, err := s.storeSigning.Sign(asserts.SnapRevisionType, map[string]interface{}{ "snap-sha3-384": sha3_384, "snap-size": fmt.Sprintf("%d", size), "snap-id": "foosnapidsnapid", "developer-id": "developerid", "snap-revision": "128", "timestamp": time.Now().UTC().Format(time.RFC3339), }, nil, "") c.Assert(err, IsNil) writeAssertionsToFile("foo.asserts", []asserts.Assertion{devAcct, snapRevFoo, snapDeclFoo}) snapDeclBar, err := s.storeSigning.Sign(asserts.SnapDeclarationType, map[string]interface{}{ "series": "16", "snap-id": "barsnapidsnapid", "publisher-id": "developerid", "snap-name": "bar", "timestamp": time.Now().UTC().Format(time.RFC3339), }, nil, "") c.Assert(err, IsNil) sha3_384, size, err = asserts.SnapFileSHA3_384(barSnapFile) c.Assert(err, IsNil) snapRevBar, err := s.storeSigning.Sign(asserts.SnapRevisionType, map[string]interface{}{ "snap-sha3-384": sha3_384, "snap-size": fmt.Sprintf("%d", size), "snap-id": "barsnapidsnapid", "developer-id": "developerid", "snap-revision": "65", "timestamp": time.Now().UTC().Format(time.RFC3339), }, nil, "") c.Assert(err, IsNil) writeAssertionsToFile("bar.asserts", []asserts.Assertion{devAcct, snapDeclBar, snapRevBar}) // add a model assertion and its chain assertsChain := s.makeModelAssertionChain(c) writeAssertionsToFile("model.asserts", assertsChain) // create a seed.yaml content := []byte(fmt.Sprintf(` snaps: - name: foo file: %s - name: bar file: %s `, filepath.Base(fooSnapFile), filepath.Base(barSnapFile))) err = ioutil.WriteFile(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), content, 0644) c.Assert(err, IsNil) // run the firstboot stuff st := s.overlord.State() st.Lock() defer st.Unlock() tsAll, err := devicestate.PopulateStateFromSeedImpl(st) c.Assert(err, IsNil) chg := st.NewChange("run-it", "run the populate from seed changes") for _, ts := range tsAll { chg.AddAll(ts) } c.Assert(st.Changes(), HasLen, 1) st.Unlock() s.overlord.Settle() st.Lock() c.Assert(chg.Err(), IsNil) // and check the snap got correctly installed c.Check(osutil.FileExists(filepath.Join(dirs.SnapMountDir, "foo", "128", "meta", "snap.yaml")), Equals, true) // and check the snap got correctly installed c.Check(osutil.FileExists(filepath.Join(dirs.SnapMountDir, "bar", "65", "meta", "snap.yaml")), Equals, true) // verify r, err := os.Open(dirs.SnapStateFile) c.Assert(err, IsNil) state, err := state.ReadState(nil, r) c.Assert(err, IsNil) state.Lock() defer state.Unlock() // check foo info, err := snapstate.CurrentInfo(state, "foo") c.Assert(err, IsNil) c.Assert(info.SnapID, Equals, "foosnapidsnapid") c.Assert(info.Revision, Equals, snap.R(128)) c.Assert(info.DeveloperID, Equals, "developerid") // check bar info, err = snapstate.CurrentInfo(state, "bar") c.Assert(err, IsNil) c.Assert(info.SnapID, Equals, "barsnapidsnapid") c.Assert(info.Revision, Equals, snap.R(65)) c.Assert(info.DeveloperID, Equals, "developerid") }
func (s *FirstBootTestSuite) TestPopulateFromSeedHappy(c *C) { // put a firstboot snap into the SnapBlobDir snapYaml := `name: foo version: 1.0` mockSnapFile := snaptest.MakeTestSnapWithFiles(c, snapYaml, nil) targetSnapFile := filepath.Join(dirs.SnapSeedDir, "snaps", filepath.Base(mockSnapFile)) err := os.Rename(mockSnapFile, targetSnapFile) c.Assert(err, IsNil) // put a firstboot local snap into the SnapBlobDir snapYaml = `name: local version: 1.0` mockSnapFile = snaptest.MakeTestSnapWithFiles(c, snapYaml, nil) targetSnapFile2 := filepath.Join(dirs.SnapSeedDir, "snaps", filepath.Base(mockSnapFile)) err = os.Rename(mockSnapFile, targetSnapFile2) c.Assert(err, IsNil) devAcct := assertstest.NewAccount(s.storeSigning, "developer", map[string]interface{}{ "account-id": "developerid", }, "") devAcctFn := filepath.Join(dirs.SnapSeedDir, "assertions", "developer.account") err = ioutil.WriteFile(devAcctFn, asserts.Encode(devAcct), 0644) c.Assert(err, IsNil) snapDecl, err := s.storeSigning.Sign(asserts.SnapDeclarationType, map[string]interface{}{ "series": "16", "snap-id": "snapidsnapid", "publisher-id": "developerid", "snap-name": "foo", "timestamp": time.Now().UTC().Format(time.RFC3339), }, nil, "") c.Assert(err, IsNil) declFn := filepath.Join(dirs.SnapSeedDir, "assertions", "foo.snap-declaration") err = ioutil.WriteFile(declFn, asserts.Encode(snapDecl), 0644) c.Assert(err, IsNil) sha3_384, size, err := asserts.SnapFileSHA3_384(targetSnapFile) c.Assert(err, IsNil) snapRev, err := s.storeSigning.Sign(asserts.SnapRevisionType, map[string]interface{}{ "snap-sha3-384": sha3_384, "snap-size": fmt.Sprintf("%d", size), "snap-id": "snapidsnapid", "developer-id": "developerid", "snap-revision": "128", "timestamp": time.Now().UTC().Format(time.RFC3339), }, nil, "") c.Assert(err, IsNil) revFn := filepath.Join(dirs.SnapSeedDir, "assertions", "foo.snap-revision") err = ioutil.WriteFile(revFn, asserts.Encode(snapRev), 0644) c.Assert(err, IsNil) // add a model assertion and its chain assertsChain := s.makeModelAssertionChain(c) for i, as := range assertsChain { fn := filepath.Join(dirs.SnapSeedDir, "assertions", strconv.Itoa(i)) err := ioutil.WriteFile(fn, asserts.Encode(as), 0644) c.Assert(err, IsNil) } // create a seed.yaml content := []byte(fmt.Sprintf(` snaps: - name: foo file: %s devmode: true - name: local unasserted: true file: %s `, filepath.Base(targetSnapFile), filepath.Base(targetSnapFile2))) err = ioutil.WriteFile(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), content, 0644) c.Assert(err, IsNil) // run the firstboot stuff st := s.overlord.State() st.Lock() defer st.Unlock() tsAll, err := devicestate.PopulateStateFromSeedImpl(st) c.Assert(err, IsNil) // the last task of the last taskset must be mark-seeded markSeededTask := tsAll[len(tsAll)-1].Tasks()[0] c.Check(markSeededTask.Kind(), Equals, "mark-seeded") // and the markSeededTask must wait for the other tasks prevTasks := tsAll[len(tsAll)-2].Tasks() otherTask := prevTasks[len(prevTasks)-1] c.Check(markSeededTask.WaitTasks(), testutil.Contains, otherTask) // now run the change and check the result chg := st.NewChange("run-it", "run the populate from seed changes") for _, ts := range tsAll { chg.AddAll(ts) } c.Assert(st.Changes(), HasLen, 1) st.Unlock() s.overlord.Settle() st.Lock() c.Assert(chg.Err(), IsNil) // and check the snap got correctly installed c.Check(osutil.FileExists(filepath.Join(dirs.SnapMountDir, "foo", "128", "meta", "snap.yaml")), Equals, true) c.Check(osutil.FileExists(filepath.Join(dirs.SnapMountDir, "local", "x1", "meta", "snap.yaml")), Equals, true) // verify r, err := os.Open(dirs.SnapStateFile) c.Assert(err, IsNil) state, err := state.ReadState(nil, r) c.Assert(err, IsNil) state.Lock() defer state.Unlock() // check foo info, err := snapstate.CurrentInfo(state, "foo") c.Assert(err, IsNil) c.Assert(info.SnapID, Equals, "snapidsnapid") c.Assert(info.Revision, Equals, snap.R(128)) c.Assert(info.DeveloperID, Equals, "developerid") var snapst snapstate.SnapState err = snapstate.Get(state, "foo", &snapst) c.Assert(err, IsNil) c.Assert(snapst.DevMode, Equals, true) // check local info, err = snapstate.CurrentInfo(state, "local") c.Assert(err, IsNil) c.Assert(info.SnapID, Equals, "") c.Assert(info.Revision, Equals, snap.R("x1")) c.Assert(info.DeveloperID, Equals, "") // and ensure state is now considered seeded var seeded bool err = state.Get("seeded", &seeded) c.Assert(err, IsNil) c.Check(seeded, Equals, true) }