Пример #1
0
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)
}
Пример #2
0
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
}
Пример #3
0
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)
}
Пример #4
0
// 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()})
}
Пример #5
0
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)
}
Пример #6
0
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)
}
Пример #7
0
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)
}
Пример #8
0
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))
}
Пример #9
0
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
}
Пример #10
0
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
}
Пример #11
0
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()})
}
Пример #12
0
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
}
Пример #13
0
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()})
}