func (s *assertMgrSuite) TestBatchAddStreamReturnsEffectivelyAddedRefs(c *C) { s.state.Lock() defer s.state.Unlock() b := &bytes.Buffer{} enc := asserts.NewEncoder(b) // wrong order is ok err := enc.Encode(s.dev1Acct) c.Assert(err, IsNil) enc.Encode(s.storeSigning.StoreAccountKey("")) c.Assert(err, IsNil) batch := assertstate.NewBatch() err = batch.Add(s.storeSigning.StoreAccountKey("")) c.Assert(err, IsNil) refs, err := batch.AddStream(b) c.Assert(err, IsNil) c.Check(refs, DeepEquals, []*asserts.Ref{ {Type: asserts.AccountType, PrimaryKey: []string{s.dev1Acct.AccountID()}}, }) err = batch.Commit(s.state) c.Assert(err, IsNil) db := assertstate.DB(s.state) devAcct, err := db.Find(asserts.AccountType, map[string]string{ "account-id": s.dev1Acct.AccountID(), }) c.Assert(err, IsNil) c.Check(devAcct.(*asserts.Account).Username(), Equals, "developer1") }
func (s *assertMgrSuite) TestFetchIdempotent(c *C) { s.prereqSnapAssertions(c, 10, 11) s.state.Lock() defer s.state.Unlock() ref := &asserts.Ref{ Type: asserts.SnapRevisionType, PrimaryKey: []string{makeDigest(10)}, } fetching := func(f asserts.Fetcher) error { return f.Fetch(ref) } err := assertstate.DoFetch(s.state, 0, fetching) c.Assert(err, IsNil) ref = &asserts.Ref{ Type: asserts.SnapRevisionType, PrimaryKey: []string{makeDigest(11)}, } err = assertstate.DoFetch(s.state, 0, fetching) c.Assert(err, IsNil) snapRev, err := ref.Resolve(assertstate.DB(s.state).Find) c.Assert(err, IsNil) c.Check(snapRev.(*asserts.SnapRevision).SnapRevision(), Equals, 11) }
func (s *assertMgrSuite) TestDB(c *C) { s.state.Lock() defer s.state.Unlock() db := assertstate.DB(s.state) c.Check(db, FitsTypeOf, (*asserts.Database)(nil)) }
func importAssertionsFromSeed(st *state.State) error { device, err := auth.Device(st) if err != nil { return err } // set device,model from the model assertion assertSeedDir := filepath.Join(dirs.SnapSeedDir, "assertions") dc, err := ioutil.ReadDir(assertSeedDir) if err != nil { return fmt.Errorf("cannot read assert seed dir: %s", err) } // FIXME: remove this check once asserts are mandatory if len(dc) == 0 { return nil } // collect var modelRef *asserts.Ref batch := assertstate.NewBatch() for _, fi := range dc { fn := filepath.Join(assertSeedDir, fi.Name()) refs, err := readAsserts(fn, batch) if err != nil { return fmt.Errorf("cannot read assertions: %s", err) } for _, ref := range refs { if ref.Type == asserts.ModelType { if modelRef != nil && modelRef.Unique() != ref.Unique() { return fmt.Errorf("cannot add more than one model assertion") } modelRef = ref } } } // verify we have one model assertion if modelRef == nil { return fmt.Errorf("need a model assertion") } if err := batch.Commit(st); err != nil { return err } a, err := modelRef.Resolve(assertstate.DB(st).Find) if err != nil { return fmt.Errorf("internal error: cannot find just added assertion %v: %v", modelRef, err) } modelAssertion := a.(*asserts.Model) // set device,model from the model assertion device.Brand = modelAssertion.BrandID() device.Model = modelAssertion.Model() if err := auth.SetDevice(st, device); err != nil { return err } return nil }
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 (s *FirstBootTestSuite) TestImportAssertionsFromSeedHappy(c *C) { ovld, err := overlord.New() c.Assert(err, IsNil) st := ovld.State() // add a bunch of assert files 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) } // import them err = boot.ImportAssertionsFromSeed(st) c.Assert(err, IsNil) // verify that the model was added st.Lock() defer st.Unlock() db := assertstate.DB(st) as, err := db.Find(asserts.ModelType, map[string]string{ "series": "16", "brand-id": "my-brand", "model": "my-model", }) c.Assert(err, IsNil) _, ok := as.(*asserts.Model) c.Check(ok, Equals, true) ds, err := auth.Device(st) c.Assert(err, IsNil) c.Check(ds.Brand, Equals, "my-brand") c.Check(ds.Model, Equals, "my-model") }
func (s *assertMgrSuite) TestAdd(c *C) { s.state.Lock() defer s.state.Unlock() // prereq store key err := assertstate.Add(s.state, s.storeSigning.StoreAccountKey("")) c.Assert(err, IsNil) err = assertstate.Add(s.state, s.dev1Acct) c.Assert(err, IsNil) db := assertstate.DB(s.state) devAcct, err := db.Find(asserts.AccountType, map[string]string{ "account-id": s.dev1Acct.AccountID(), }) c.Assert(err, IsNil) c.Check(devAcct.(*asserts.Account).Username(), Equals, "developer1") }
// alreadyFirstbooted recovers already first booted devices with the old method appropriately func (m *DeviceManager) alreadyFirstbooted() error { device, err := auth.Device(m.state) if err != nil { return err } // recover key-id if device.Brand != "" && device.Model != "" { serials, err := assertstate.DB(m.state).FindMany(asserts.SerialType, map[string]string{ "brand-id": device.Brand, "model": device.Model, }) if err != nil && err != asserts.ErrNotFound { return err } if len(serials) == 1 { // we can recover the key id from the assertion serial := serials[0].(*asserts.Serial) keyID := serial.DeviceKey().ID() device.KeyID = keyID device.Serial = serial.Serial() err := auth.SetDevice(m.state, device) if err != nil { return err } // best effort to cleanup abandoned keys pat := filepath.Join(dirs.SnapDeviceDir, "private-keys-v1", "*") keyFns, err := filepath.Glob(pat) if err != nil { panic(fmt.Sprintf("invalid glob for device keys: %v", err)) } for _, keyFn := range keyFns { if filepath.Base(keyFn) == keyID { continue } os.Remove(keyFn) } } } m.state.Set("seeded", true) return nil }
func (s *assertMgrSuite) TestValidateSnap(c *C) { s.prereqSnapAssertions(c, 10) tempdir := c.MkDir() snapPath := filepath.Join(tempdir, "foo.snap") err := ioutil.WriteFile(snapPath, fakeSnap(10), 0644) c.Assert(err, IsNil) s.state.Lock() defer s.state.Unlock() chg := s.state.NewChange("install", "...") t := s.state.NewTask("validate-snap", "Fetch and check snap assertions") ss := snapstate.SnapSetup{ SnapPath: snapPath, UserID: 0, SideInfo: &snap.SideInfo{ RealName: "foo", SnapID: "snap-id-1", Revision: snap.R(10), }, } t.Set("snap-setup", ss) chg.AddTask(t) s.state.Unlock() defer s.mgr.Stop() s.settle() s.state.Lock() c.Assert(chg.Err(), IsNil) snapRev, err := assertstate.DB(s.state).Find(asserts.SnapRevisionType, map[string]string{ "snap-id": "snap-id-1", "snap-sha3-384": makeDigest(10), }) c.Assert(err, IsNil) c.Check(snapRev.(*asserts.SnapRevision).SnapRevision(), Equals, 10) }
// Serial returns the device serial assertion. func Serial(st *state.State) (*asserts.Serial, error) { device, err := auth.Device(st) if err != nil { return nil, err } if device.Serial == "" { return nil, state.ErrNoState } a, err := assertstate.DB(st).Find(asserts.SerialType, map[string]string{ "brand-id": device.Brand, "model": device.Model, "serial": device.Serial, }) if err == asserts.ErrNotFound { return nil, state.ErrNoState } if err != nil { return nil, err } return a.(*asserts.Serial), nil }
func (m *DeviceManager) doRequestSerial(t *state.Task, _ *tomb.Tomb) error { st := t.State() st.Lock() defer st.Unlock() cfg, err := getSerialRequestConfig(t) if err != nil { return err } device, err := auth.Device(st) if err != nil { return err } privKey, err := m.keyPair() if err == state.ErrNoState { return fmt.Errorf("internal error: cannot find device key pair") } if err != nil { return err } // make this idempotent, look if we have already a serial assertion // for privKey serials, err := assertstate.DB(st).FindMany(asserts.SerialType, map[string]string{ "brand-id": device.Brand, "model": device.Model, "device-key-sha3-384": privKey.PublicKey().ID(), }) if err != nil && err != asserts.ErrNotFound { return err } if len(serials) == 1 { // means we saved the assertion but didn't get to the end of the task device.Serial = serials[0].(*asserts.Serial).Serial() err := auth.SetDevice(st, device) if err != nil { return err } t.SetStatus(state.DoneStatus) return nil } if len(serials) > 1 { return fmt.Errorf("internal error: multiple serial assertions for the same device key") } serial, err := getSerial(t, privKey, device, cfg) if err == errPoll { t.Logf("Will poll for device serial assertion in 60 seconds") return &state.Retry{After: retryInterval} } if err != nil { // errors & retries return err } sto := snapstate.Store(st) // try to fetch the signing key of the serial st.Unlock() a, errAcctKey := sto.Assertion(asserts.AccountKeyType, []string{serial.SignKeyID()}, nil) st.Lock() if errAcctKey == nil { err := assertstate.Add(st, a) if err != nil { if !asserts.IsUnaccceptedUpdate(err) { return err } } } // add the serial assertion to the system assertion db err = assertstate.Add(st, serial) if err != nil { // if we had failed to fetch the signing key, retry in a bit if errAcctKey != nil { t.Errorf("cannot fetch signing key for the serial: %v", errAcctKey) return &state.Retry{After: retryInterval} } return err } if repeatRequestSerial == "after-add-serial" { // For testing purposes, ensure a crash in this state works. return &state.Retry{} } device.Serial = serial.Serial() err = auth.SetDevice(st, device) if err != nil { return err } t.SetStatus(state.DoneStatus) return nil }
func (s *assertMgrSuite) TestRefreshSnapDeclarations(c *C) { s.state.Lock() defer s.state.Unlock() snapDeclFoo := s.snapDecl(c, "foo", nil) snapDeclBar := s.snapDecl(c, "bar", nil) s.stateFromDecl(snapDeclFoo, snap.R(7)) s.stateFromDecl(snapDeclBar, snap.R(3)) snapstate.Set(s.state, "local", &snapstate.SnapState{ Active: false, Sequence: []*snap.SideInfo{ {RealName: "local", Revision: snap.R(-1)}, }, Current: snap.R(-1), }) // previous state err := assertstate.Add(s.state, s.storeSigning.StoreAccountKey("")) c.Assert(err, IsNil) err = assertstate.Add(s.state, s.dev1Acct) c.Assert(err, IsNil) err = assertstate.Add(s.state, snapDeclFoo) c.Assert(err, IsNil) err = assertstate.Add(s.state, snapDeclBar) c.Assert(err, IsNil) // one changed assertion headers := map[string]interface{}{ "series": "16", "snap-id": "foo-id", "snap-name": "fo-o", "publisher-id": s.dev1Acct.AccountID(), "timestamp": time.Now().Format(time.RFC3339), "revision": "1", } snapDeclFoo1, err := s.storeSigning.Sign(asserts.SnapDeclarationType, headers, nil, "") c.Assert(err, IsNil) err = s.storeSigning.Add(snapDeclFoo1) c.Assert(err, IsNil) err = assertstate.RefreshSnapDeclarations(s.state, 0) c.Assert(err, IsNil) a, err := assertstate.DB(s.state).Find(asserts.SnapDeclarationType, map[string]string{ "series": "16", "snap-id": "foo-id", }) c.Assert(err, IsNil) c.Check(a.(*asserts.SnapDeclaration).SnapName(), Equals, "fo-o") // another one // one changed assertion headers = s.dev1Acct.Headers() headers["display-name"] = "Dev 1 edited display-name" headers["revision"] = "1" dev1Acct1, err := s.storeSigning.Sign(asserts.AccountType, headers, nil, "") c.Assert(err, IsNil) err = s.storeSigning.Add(dev1Acct1) c.Assert(err, IsNil) err = assertstate.RefreshSnapDeclarations(s.state, 0) c.Assert(err, IsNil) a, err = assertstate.DB(s.state).Find(asserts.AccountType, map[string]string{ "account-id": s.dev1Acct.AccountID(), }) c.Assert(err, IsNil) c.Check(a.(*asserts.Account).DisplayName(), Equals, "Dev 1 edited display-name") // change snap decl to something that has a too new format (func() { restore := asserts.MockMaxSupportedFormat(asserts.SnapDeclarationType, 999) defer restore() headers := map[string]interface{}{ "format": "999", "series": "16", "snap-id": "foo-id", "snap-name": "foo", "publisher-id": s.dev1Acct.AccountID(), "timestamp": time.Now().Format(time.RFC3339), "revision": "2", } snapDeclFoo2, err := s.storeSigning.Sign(asserts.SnapDeclarationType, headers, nil, "") c.Assert(err, IsNil) err = s.storeSigning.Add(snapDeclFoo2) c.Assert(err, IsNil) })() // no error, kept the old one err = assertstate.RefreshSnapDeclarations(s.state, 0) c.Assert(err, IsNil) a, err = assertstate.DB(s.state).Find(asserts.SnapDeclarationType, map[string]string{ "series": "16", "snap-id": "foo-id", }) c.Assert(err, IsNil) c.Check(a.(*asserts.SnapDeclaration).SnapName(), Equals, "fo-o") c.Check(a.(*asserts.SnapDeclaration).Revision(), Equals, 1) }
func (ms *mgrsSuite) TestHappyRemoteInstallAndUpgradeSvc(c *C) { // test install through store and update, plus some mechanics // of update // TODO: ok to split if it gets too messy to maintain ms.prereqSnapAssertions(c) snapYamlContent := `name: foo version: @VERSION@ apps: bar: command: bin/bar svc: command: svc daemon: forking ` ver := "1.0" revno := "42" snapPath, digest := ms.makeStoreTestSnap(c, strings.Replace(snapYamlContent, "@VERSION@", ver, -1), revno) ms.serveSnap(snapPath, revno) mockServer := ms.mockStore(c) defer mockServer.Close() st := ms.o.State() st.Lock() defer st.Unlock() ts, err := snapstate.Install(st, "foo", "stable", snap.R(0), 0, snapstate.Flags{}) c.Assert(err, IsNil) chg := st.NewChange("install-snap", "...") chg.AddAll(ts) st.Unlock() err = ms.o.Settle() st.Lock() c.Assert(err, IsNil) c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("install-snap change failed with: %v", chg.Err())) info, err := snapstate.CurrentInfo(st, "foo") c.Assert(err, IsNil) c.Check(info.Revision, Equals, snap.R(42)) c.Check(info.SnapID, Equals, fooSnapID) c.Check(info.DeveloperID, Equals, "devdevdev") c.Check(info.Version, Equals, "1.0") c.Check(info.Summary(), Equals, "Foo") c.Check(info.Description(), Equals, "this is a description") c.Check(info.Developer, Equals, "bar") c.Assert(osutil.FileExists(info.MountFile()), Equals, true) snapRev42, err := assertstate.DB(st).Find(asserts.SnapRevisionType, map[string]string{ "snap-sha3-384": digest, }) c.Assert(err, IsNil) c.Check(snapRev42.(*asserts.SnapRevision).SnapID(), Equals, fooSnapID) c.Check(snapRev42.(*asserts.SnapRevision).SnapRevision(), Equals, 42) // check service was setup properly svcFile := filepath.Join(dirs.SnapServicesDir, "snap.foo.svc.service") c.Assert(osutil.FileExists(svcFile), Equals, true) stat, err := os.Stat(svcFile) c.Assert(err, IsNil) // should _not_ be executable c.Assert(stat.Mode().String(), Equals, "-rw-r--r--") // Refresh ver = "2.0" revno = "50" snapPath, digest = ms.makeStoreTestSnap(c, strings.Replace(snapYamlContent, "@VERSION@", ver, -1), revno) ms.serveSnap(snapPath, revno) ts, err = snapstate.Update(st, "foo", "stable", snap.R(0), 0, snapstate.Flags{}) c.Assert(err, IsNil) chg = st.NewChange("upgrade-snap", "...") chg.AddAll(ts) st.Unlock() err = ms.o.Settle() st.Lock() c.Assert(err, IsNil) c.Assert(chg.Err(), IsNil) c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("upgrade-snap change failed with: %v", chg.Err())) info, err = snapstate.CurrentInfo(st, "foo") c.Assert(err, IsNil) c.Check(info.Revision, Equals, snap.R(50)) c.Check(info.SnapID, Equals, fooSnapID) c.Check(info.DeveloperID, Equals, "devdevdev") c.Check(info.Version, Equals, "2.0") snapRev50, err := assertstate.DB(st).Find(asserts.SnapRevisionType, map[string]string{ "snap-sha3-384": digest, }) c.Assert(err, IsNil) c.Check(snapRev50.(*asserts.SnapRevision).SnapID(), Equals, fooSnapID) c.Check(snapRev50.(*asserts.SnapRevision).SnapRevision(), Equals, 50) // check updated wrapper symlinkTarget, err := os.Readlink(info.Apps["bar"].WrapperPath()) c.Assert(err, IsNil) c.Assert(symlinkTarget, Equals, "/usr/bin/snap") // check updated service file content, err := ioutil.ReadFile(svcFile) c.Assert(err, IsNil) c.Assert(strings.Contains(string(content), "/var/snap/foo/"+revno), Equals, true) }
func populateStateFromSeedImpl(st *state.State) ([]*state.TaskSet, error) { // check that the state is empty var seeded bool err := st.Get("seeded", &seeded) if err != nil && err != state.ErrNoState { return nil, err } if seeded { return nil, fmt.Errorf("cannot populate state: already seeded") } // ack all initial assertions if err := importAssertionsFromSeed(st); err != nil { return nil, err } seed, err := snap.ReadSeedYaml(filepath.Join(dirs.SnapSeedDir, "seed.yaml")) if err != nil { return nil, err } tsAll := []*state.TaskSet{} for i, sn := range seed.Snaps { var flags snapstate.Flags if sn.DevMode { flags.DevMode = true } path := filepath.Join(dirs.SnapSeedDir, "snaps", sn.File) var sideInfo snap.SideInfo if sn.Unasserted { sideInfo.RealName = sn.Name } else { si, err := snapasserts.DeriveSideInfo(path, assertstate.DB(st)) if err == asserts.ErrNotFound { return nil, fmt.Errorf("cannot find signatures with metadata for snap %q (%q)", sn.Name, path) } if err != nil { return nil, err } sideInfo = *si sideInfo.Private = sn.Private } ts, err := snapstate.InstallPath(st, &sideInfo, path, sn.Channel, flags) if i > 0 { ts.WaitAll(tsAll[i-1]) } if err != nil { return nil, err } tsAll = append(tsAll, ts) } if len(tsAll) == 0 { return nil, nil } ts := tsAll[len(tsAll)-1] markSeeded := st.NewTask("mark-seeded", i18n.G("Mark system seeded")) markSeeded.WaitAll(ts) tsAll = append(tsAll, state.NewTaskSet(markSeeded)) return tsAll, nil }
func populateStateFromSeed() error { if osutil.FileExists(dirs.SnapStateFile) { return fmt.Errorf("cannot create state: state %q already exists", dirs.SnapStateFile) } ovld, err := overlord.New() if err != nil { return err } st := ovld.State() // ack all initial assertions if err := importAssertionsFromSeed(st); err != nil { return err } seed, err := snap.ReadSeedYaml(filepath.Join(dirs.SnapSeedDir, "seed.yaml")) if err != nil { return err } tsAll := []*state.TaskSet{} for i, sn := range seed.Snaps { st.Lock() flags := snapstate.Flags(0) if sn.DevMode { flags |= snapstate.DevMode } path := filepath.Join(dirs.SnapSeedDir, "snaps", sn.File) var sideInfo snap.SideInfo if sn.Unasserted { sideInfo.RealName = sn.Name } else { si, err := snapasserts.DeriveSideInfo(path, assertstate.DB(st)) if err == asserts.ErrNotFound { st.Unlock() return fmt.Errorf("cannot find signatures with metadata for snap %q (%q)", sn.Name, path) } if err != nil { st.Unlock() return err } sideInfo = *si sideInfo.Private = sn.Private } ts, err := snapstate.InstallPath(st, &sideInfo, path, sn.Channel, flags) if i > 0 { ts.WaitAll(tsAll[i-1]) } st.Unlock() if err != nil { return err } tsAll = append(tsAll, ts) } if len(tsAll) == 0 { return nil } st.Lock() msg := fmt.Sprintf("First boot seeding") chg := st.NewChange("seed", msg) for _, ts := range tsAll { chg.AddAll(ts) } st.Unlock() // do it and wait for ready ovld.Loop() st.EnsureBefore(0) <-chg.Ready() st.Lock() status := chg.Status() err = chg.Err() st.Unlock() if status != state.DoneStatus { ovld.Stop() return fmt.Errorf("cannot run seed change: %s", err) } return ovld.Stop() }
func postSnaps(c *Command, r *http.Request, user *auth.UserState) Response { contentType := r.Header.Get("Content-Type") if contentType == "application/json" { return snapsOp(c, r, user) } if !strings.HasPrefix(contentType, "multipart/") { return BadRequest("unknown content type: %s", contentType) } route := c.d.router.Get(stateChangeCmd.Path) if route == nil { return InternalError("cannot find route for change") } // POSTs to sideload snaps must be a multipart/form-data file upload. _, params, err := mime.ParseMediaType(contentType) if err != nil { return BadRequest("cannot parse POST body: %v", err) } form, err := multipart.NewReader(r.Body, params["boundary"]).ReadForm(maxReadBuflen) if err != nil { return BadRequest("cannot read POST form: %v", err) } dangerousOK := isTrue(form, "dangerous") devmode := isTrue(form, "devmode") flags, err := modeFlags(devmode, isTrue(form, "jailmode")) if err != nil { return BadRequest(err.Error()) } if len(form.Value["action"]) > 0 && form.Value["action"][0] == "try" { if len(form.Value["snap-path"]) == 0 { return BadRequest("need 'snap-path' value in form") } return trySnap(c, r, user, form.Value["snap-path"][0], flags) } // find the file for the "snap" form field var snapBody multipart.File var origPath string out: for name, fheaders := range form.File { if name != "snap" { continue } for _, fheader := range fheaders { snapBody, err = fheader.Open() origPath = fheader.Filename if err != nil { return BadRequest(`cannot open uploaded "snap" file: %v`, err) } defer snapBody.Close() break out } } defer form.RemoveAll() if snapBody == nil { return BadRequest(`cannot find "snap" file field in provided multipart/form-data payload`) } tmpf, err := ioutil.TempFile("", "snapd-sideload-pkg-") if err != nil { return InternalError("cannot create temporary file: %v", err) } if _, err := io.Copy(tmpf, snapBody); err != nil { os.Remove(tmpf.Name()) return InternalError("cannot copy request into temporary file: %v", err) } tmpf.Sync() tempPath := tmpf.Name() if len(form.Value["snap-path"]) > 0 { origPath = form.Value["snap-path"][0] } st := c.d.overlord.State() st.Lock() defer st.Unlock() var snapName string var sideInfo *snap.SideInfo if !dangerousOK { si, err := snapasserts.DeriveSideInfo(tempPath, assertstate.DB(st)) switch err { case nil: snapName = si.RealName sideInfo = si case asserts.ErrNotFound: // with devmode we try to find assertions but it's ok // if they are not there (implies --dangerous) if !devmode { msg := "cannot find signatures with metadata for snap" if origPath != "" { msg = fmt.Sprintf("%s %q", msg, origPath) } return BadRequest(msg) } // TODO: set a warning if devmode default: return BadRequest(err.Error()) } } if snapName == "" { // potentially dangerous but dangerous or devmode params were set info, err := unsafeReadSnapInfo(tempPath) if err != nil { return InternalError("cannot read snap file: %v", err) } snapName = info.Name() sideInfo = &snap.SideInfo{RealName: snapName} } msg := fmt.Sprintf(i18n.G("Install %q snap from file"), snapName) if origPath != "" { msg = fmt.Sprintf(i18n.G("Install %q snap from file %q"), snapName, origPath) } var userID int if user != nil { userID = user.ID } tsets, err := withEnsureUbuntuCore(st, snapName, userID, func() (*state.TaskSet, error) { return snapstateInstallPath(st, sideInfo, tempPath, "", flags) }, ) if err != nil { return InternalError("cannot install snap file: %v", err) } chg := newChange(st, "install-snap", msg, tsets, []string{snapName}) chg.Set("api-data", map[string]string{"snap-name": snapName}) ensureStateSoon(st) return AsyncResponse(nil, &Meta{Change: chg.ID()}) }