func abortChange(c *Command, r *http.Request) 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) } chg.Abort() return SyncResponse(change2changeInfo(chg), nil) }
func (inst *snapInstruction) update() (*state.Change, error) { flags := snappy.DoInstallGC if inst.LeaveOld { flags = 0 } state := inst.overlord.State() state.Lock() msg := fmt.Sprintf(i18n.G("Refresh %q snap"), inst.pkg) if inst.Channel != "stable" && inst.Channel != "" { msg = fmt.Sprintf(i18n.G("Refresh %q snap from %q channel"), inst.pkg, inst.Channel) } chg := state.NewChange("refresh-snap", msg) ts, err := snapstateUpdate(state, inst.pkg, inst.Channel, inst.userID, flags) if err == nil { chg.AddAll(ts) } state.Unlock() if err != nil { return nil, err } state.EnsureBefore(0) return chg, nil }
func getChanges(c *Command, r *http.Request) 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") } 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) }
// 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) 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 change *state.Change 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) change = state.NewChange("connect-snap", summary) 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) change = state.NewChange("disconnect-snap", summary) taskset, err = ifacestate.Disconnect(state, a.Plugs[0].Snap, a.Plugs[0].Name, a.Slots[0].Snap, a.Slots[0].Name) } if err == nil { change.AddAll(taskset) } if err != nil { return BadRequest("%v", err) } state.EnsureBefore(0) return AsyncResponse(nil, &Meta{Change: change.ID()}) }
func getChange(c *Command, r *http.Request) 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 loginUser(c *Command, r *http.Request) 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) } macaroon, err := store.RequestPackageAccessMacaroon() if err != nil { return InternalError(err.Error()) } discharge, err := store.DischargeAuthCaveat(loginData.Username, loginData.Password, macaroon, loginData.Otp) if err == store.ErrAuthenticationNeeds2fa { twofactorRequiredResponse := &resp{ Type: ResponseTypeError, Result: &errorResult{ Kind: errorKindTwoFactorRequired, Message: store.ErrAuthenticationNeeds2fa.Error(), }, Status: http.StatusUnauthorized, } return SyncResponse(twofactorRequiredResponse, nil) } if err != nil { return Unauthorized(err.Error()) } 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 logoutUser(c *Command, r *http.Request) Response { state := c.d.overlord.State() state.Lock() defer state.Unlock() user, err := UserFromRequest(state, r) if err != nil { return BadRequest("not logged in") } err = auth.RemoveUser(state, user.ID) if err != nil { return InternalError(err.Error()) } return SyncResponse(nil, nil) }
func (ovs *overlordSuite) TestNewWithGoodState(c *C) { fakeState := []byte(`{"data":{"some":"data"},"changes":null,"tasks":null,"last-change-id":0,"last-task-id":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() d, err := state.MarshalJSON() c.Assert(err, IsNil) c.Assert(string(d), DeepEquals, string(fakeState)) }
func (inst *snapInstruction) deactivate() (*state.Change, error) { state := inst.overlord.State() state.Lock() msg := fmt.Sprintf(i18n.G("Deactivate %q snap"), inst.pkg) chg := state.NewChange("deactivate-snap", msg) ts, err := snapstate.Deactivate(state, inst.pkg) if err == nil { chg.AddAll(ts) } state.Unlock() if err != nil { return nil, err } state.EnsureBefore(0) return chg, nil }
func (inst *snapInstruction) rollback() (*state.Change, error) { state := inst.overlord.State() state.Lock() msg := fmt.Sprintf(i18n.G("Rollback %q snap"), inst.pkg) chg := state.NewChange("rollback-snap", msg) // use previous version ver := "" ts, err := snapstate.Rollback(state, inst.pkg, ver) if err == nil { chg.AddAll(ts) } state.Unlock() if err != nil { return nil, err } state.EnsureBefore(0) return chg, nil }
func postSnap(c *Command, r *http.Request) Response { route := c.d.router.Get(stateChangeCmd.Path) if route == nil { return InternalError("router can't find route for change") } decoder := json.NewDecoder(r.Body) var inst snapInstruction if err := decoder.Decode(&inst); err != nil { return BadRequest("can't decode request body into snap instruction: %v", err) } state := c.d.overlord.State() state.Lock() user, err := UserFromRequest(state, r) state.Unlock() if err == nil { inst.userID = user.ID } else if err != auth.ErrInvalidAuth { return InternalError("%v", err) } vars := muxVars(r) inst.pkg = vars["name"] inst.overlord = c.d.overlord f := pkgActionDispatch(&inst) if f == nil { return BadRequest("unknown action %s", inst.Action) } chg, err := f() if err != nil { return InternalError("can't %s %q: %v", inst.Action, inst.pkg, err) } return AsyncResponse(nil, &Meta{Change: chg.ID()}) }
func (inst *snapInstruction) remove() (*state.Change, error) { flags := snappy.DoRemoveGC if inst.LeaveOld { flags = 0 } state := inst.overlord.State() state.Lock() msg := fmt.Sprintf(i18n.G("Remove %q snap"), inst.pkg) chg := state.NewChange("remove-snap", msg) ts, err := snapstate.Remove(state, inst.pkg, flags) if err == nil { chg.AddAll(ts) } state.Unlock() if err != nil { return nil, err } state.EnsureBefore(0) return chg, nil }
func sideloadSnap(c *Command, r *http.Request) Response { route := c.d.router.Get(stateChangeCmd.Path) if route == nil { return InternalError("cannot find route for change") } body := r.Body contentType := r.Header.Get("Content-Type") if !strings.HasPrefix(contentType, "multipart/") { return BadRequest("unknown content type: %s", contentType) } // 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) } var flags snappy.InstallFlags if len(form.Value["devmode"]) > 0 && form.Value["devmode"][0] == "true" { flags |= snappy.DeveloperMode } // form.File is a map of arrays of *FileHeader things // we just allow one (for now at least) out: for _, v := range form.File { for i := range v { body, err = v[i].Open() if err != nil { return BadRequest("cannot open POST form file: %v", err) } defer body.Close() break out } } defer form.RemoveAll() tmpf, err := ioutil.TempFile("", "snapd-sideload-pkg-") if err != nil { return InternalError("cannot create temporary file: %v", err) } if _, err := io.Copy(tmpf, body); err != nil { os.Remove(tmpf.Name()) return InternalError("cannot copy request into temporary file: %v", err) } snap := tmpf.Name() state := c.d.overlord.State() state.Lock() defer state.Unlock() msg := fmt.Sprintf(i18n.G("Install %q snap file"), snap) chg := state.NewChange("install-snap", msg) var userID int user, err := UserFromRequest(state, r) if err == nil { userID = user.ID } else if err != auth.ErrInvalidAuth { return InternalError("%v", err) } err = ensureUbuntuCore(chg, userID) if err == nil { ts, err := snapstateInstallPath(state, snap, "", flags) if err == nil { chg.AddAll(ts) } } go func() { // XXX this needs to be a task in the manager; this is a hack to keep this branch smaller <-chg.Ready() os.Remove(snap) }() if err != nil { return InternalError("cannot install snap file: %v", err) } state.EnsureBefore(0) return AsyncResponse(nil, &Meta{Change: chg.ID()}) }