// 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 (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 (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 (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()}) }