func postSnap(c *Command, r *http.Request) Response { route := c.d.router.Get(operationCmd.Path) if route == nil { return InternalError("router can't find route for operation") } 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) } vars := muxVars(r) inst.pkg = vars["name"] + "." + vars["origin"] f := pkgActionDispatch(&inst) if f == nil { return BadRequest("unknown action %s", inst.Action) } return AsyncResponse(c.d.AddTask(func() interface{} { lock, err := lockfile.Lock(dirs.SnapLockFile, true) if err != nil { return err } defer lock.Unlock() return f() }).Map(route)) }
func getLogs(c *Command, r *http.Request) Response { vars := muxVars(r) name := vars["name"] appName := vars["service"] lock, err := lockfile.Lock(dirs.SnapLockFile, true) if err != nil { return InternalError("unable to acquire lock: %v", err) } defer lock.Unlock() actor, err := findServices(name, appName, &progress.NullProgress{}) if err != nil { return NotFound("no services found for %q: %v", name, err) } rawlogs, err := actor.Logs() if err != nil { return InternalError("unable to get logs for %q: %v", name, err) } logs := make([]map[string]interface{}, len(rawlogs)) for i := range rawlogs { logs[i] = map[string]interface{}{ "timestamp": rawlogs[i].Timestamp(), "message": rawlogs[i].Message(), "raw": rawlogs[i], } } return SyncResponse(logs) }
func appIconGet(c *Command, r *http.Request) Response { vars := muxVars(r) name := vars["name"] origin := vars["origin"] lock, err := lockfile.Lock(dirs.SnapLockFile, true) if err != nil { return InternalError(err, "Unable to acquire lock") } defer lock.Unlock() bag := lightweight.PartBagByName(name, origin) if bag == nil || len(bag.Versions) == 0 { return NotFound } part := bag.LoadBest() if part == nil { return NotFound } path := filepath.Clean(part.Icon()) if !strings.HasPrefix(path, dirs.SnapAppsDir) && !strings.HasPrefix(path, dirs.SnapOemDir) { return BadRequest } return FileResponse(path) }
func iconGet(name, origin string) Response { lock, err := lockfile.Lock(dirs.SnapLockFile, true) if err != nil { return InternalError("unable to acquire lock: %v", err) } defer lock.Unlock() bag := lightweight.PartBagByName(name, origin) if bag == nil || len(bag.Versions) == 0 { return NotFound("unable to find snap with name %q and origin %q", name, origin) } part := bag.LoadBest() if part == nil { return NotFound("unable to load snap with name %q and origin %q", name, origin) } path := filepath.Clean(part.Icon()) if !strings.HasPrefix(path, dirs.SnapSnapsDir) { // XXX: how could this happen? return BadRequest("requested icon is not in snap path") } return FileResponse(path) }
func (ts *FileLockTestSuite) TestFileLockLocks(c *C) { path := filepath.Join(c.MkDir(), "lock") ch1 := make(chan bool) ch2 := make(chan bool) go func() { ch1 <- true lock, err := lockfile.Lock(path, true) c.Assert(err, IsNil) ch1 <- true ch1 <- true ch2 <- true c.Check(lock.Unlock(), IsNil) }() go func() { <-ch1 <-ch1 lock, err := lockfile.Lock(path, false) c.Assert(err, Equals, lockfile.ErrAlreadyLocked) <-ch1 lock, err = lockfile.Lock(path, true) c.Assert(err, IsNil) ch2 <- false c.Check(lock.Unlock(), IsNil) }() var bs []bool for { select { case b := <-ch2: bs = append(bs, b) if len(bs) == 2 { c.Check(bs, DeepEquals, []bool{true, false}) c.SucceedNow() } case <-time.After(time.Second): c.Fatal("timeout") } } }
func configMulti(c *Command, r *http.Request) Response { route := c.d.router.Get(operationCmd.Path) if route == nil { return InternalError(nil, "router can't find route for operation") } decoder := json.NewDecoder(r.Body) var pkgmap map[string]string if err := decoder.Decode(&pkgmap); err != nil { return BadRequest(err, "can't decode request body into map[string]string: %v", err) } return AsyncResponse(c.d.AddTask(func() interface{} { lock, err := lockfile.Lock(dirs.SnapLockFile, true) if err != nil { return err } defer lock.Unlock() rspmap := make(map[string]*configSubtask, len(pkgmap)) bags := lightweight.AllPartBags() for pkg, cfg := range pkgmap { out := errorResult{} sub := configSubtask{Status: TaskFailed, Output: &out} rspmap[pkg] = &sub bag, ok := bags[pkg] if !ok { out.Str = snappy.ErrPackageNotFound.Error() out.Obj = snappy.ErrPackageNotFound continue } part, _ := bag.Load(bag.ActiveIndex()) if part == nil { out.Str = snappy.ErrSnapNotActive.Error() out.Obj = snappy.ErrSnapNotActive continue } config, err := part.Config([]byte(cfg)) if err != nil { out.Msg = "Config failed" out.Str = err.Error() out.Obj = err continue } sub.Status = TaskSucceeded sub.Output = config } return rspmap }).Map(route)) }
func (ts *FileLockTestSuite) TestFileLock(c *C) { path := filepath.Join(c.MkDir(), "lock") c.Assert(helpers.FileExists(path), Equals, false) lock, err := lockfile.Lock(path, false) c.Assert(err, IsNil) c.Check(lock > 0, Equals, true) c.Assert(helpers.FileExists(path), Equals, true) err = lock.Unlock() c.Assert(err, IsNil) }
func snapConfig(c *Command, r *http.Request) Response { vars := muxVars(r) name := vars["name"] origin := vars["origin"] if name == "" || origin == "" { return BadRequest("missing name or origin") } pkgName := name + "." + origin lock, err := lockfile.Lock(dirs.SnapLockFile, true) if err != nil { return InternalError("unable to acquire lock: %v", err) } defer lock.Unlock() bag := lightweight.PartBagByName(name, origin) if bag == nil { return NotFound("no snap found with name %q and origin %q", name, origin) } idx := bag.ActiveIndex() if idx < 0 { return BadRequest("unable to configure non-active snap") } part, err := bag.Load(idx) if err != nil { return InternalError("unable to load active snap: %v", err) } bs, err := ioutil.ReadAll(r.Body) if err != nil { return BadRequest("reading config request body gave %v", err) } overlord := getConfigurator() config, err := overlord.Configure(part.(*snappy.SnapPart), bs) if err != nil { return InternalError("unable to retrieve config for %s: %v", pkgName, err) } return SyncResponse(config) }
func (ts *FileLockTestSuite) TestLockReuseAverted(c *C) { dir := c.MkDir() path := filepath.Join(dir, "lock") lock, err := lockfile.Lock(path, true) fd := uintptr(lock) // a copy! c.Assert(err, IsNil) c.Check(lock, Not(Equals), lockfile.LockedFile(0)) c.Assert(lock.Unlock(), IsNil) c.Check(lock, Equals, lockfile.LockedFile(0)) f, err := os.Create(filepath.Join(dir, "file")) c.Assert(err, IsNil) // why os.File.Fd returns an uintptr is a mystery to me c.Check(f.Fd(), Equals, fd) c.Check(lock.Unlock(), Equals, sys.EBADFD) c.Check(f.Sync(), IsNil) }
func packageConfig(c *Command, r *http.Request) Response { vars := muxVars(r) name := vars["name"] origin := vars["origin"] if name == "" || origin == "" { return BadRequest(nil, "missing name or origin") } pkgName := name + "." + origin lock, err := lockfile.Lock(dirs.SnapLockFile, true) if err != nil { return InternalError(err, "Unable to acquire lock") } defer lock.Unlock() bag := lightweight.PartBagByName(name, origin) if bag == nil { return NotFound } idx := bag.ActiveIndex() if idx < 0 { return BadRequest } part, err := bag.Load(idx) if err != nil { return InternalError(err, "unable to get load active package: %v", err) } bs, err := ioutil.ReadAll(r.Body) if err != nil { return BadRequest(err, "reading config request body gave %v", err) } config, err := part.Config(bs) if err != nil { return InternalError(err, "unable to retrieve config for %s: %v", pkgName, err) } return SyncResponse(config) }
func sysInfo(c *Command, r *http.Request) Response { lock, err := lockfile.Lock(dirs.SnapLockFile, true) if err != nil { return InternalError("unable to acquire lock: %v", err) } defer lock.Unlock() rel := release.Get() m := map[string]string{ "flavor": rel.Flavor, "release": rel.Series, "default_channel": rel.Channel, "api_compat": apiCompatLevel, } if store := snappy.StoreID(); store != "" { m["store"] = store } return SyncResponse(m) }
func getSnapInfo(c *Command, r *http.Request) Response { vars := muxVars(r) name := vars["name"] origin := vars["origin"] lock, err := lockfile.Lock(dirs.SnapLockFile, true) if err != nil { return InternalError("unable to acquire lock: %v", err) } defer lock.Unlock() repo := newRemoteRepo() var part snappy.Part if parts, _ := repo.Details(name, origin); len(parts) > 0 { part = parts[0] } bag := lightweight.PartBagByName(name, origin) if bag == nil && part == nil { return NotFound("unable to find snap with name %q and origin %q", name, origin) } route := c.d.router.Get(c.Path) if route == nil { return InternalError("router can't find route for snap %s.%s", name, origin) } url, err := route.URL("name", name, "origin", origin) if err != nil { return InternalError("route can't build URL for snap %s.%s: %v", name, origin, err) } result := webify(bag.Map(part), url.String()) return SyncResponse(result) }
func sideloadSnap(c *Command, r *http.Request) Response { route := c.d.router.Get(operationCmd.Path) if route == nil { return InternalError("router can't find route for operation") } body := r.Body unsignedOk := false contentType := r.Header.Get("Content-Type") if strings.HasPrefix(contentType, "multipart/") { // spec says POSTs to sideload snaps should be “a multipart file upload” _, params, err := mime.ParseMediaType(contentType) if err != nil { return BadRequest("unable to parse POST body: %v", err) } form, err := multipart.NewReader(r.Body, params["boundary"]).ReadForm(maxReadBuflen) if err != nil { return BadRequest("unable to read POST form: %v", err) } // if allow-unsigned is present in the form, unsigned is OK _, unsignedOk = form.Value["allow-unsigned"] // 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("unable to open POST form file: %v", err) } defer body.Close() break out } } defer form.RemoveAll() } else { // Looks like user didn't understand that multipart thing. // Maybe they just POSTed the snap at us (quite handy to do with e.g. curl). // So we try that. // If x-allow-unsigned is present, unsigned is OK _, unsignedOk = r.Header["X-Allow-Unsigned"] } tmpf, err := ioutil.TempFile("", "snapd-sideload-pkg-") if err != nil { return InternalError("can't create tempfile: %v", err) } if _, err := io.Copy(tmpf, body); err != nil { os.Remove(tmpf.Name()) return InternalError("can't copy request into tempfile: %v", err) } return AsyncResponse(c.d.AddTask(func() interface{} { defer os.Remove(tmpf.Name()) _, err := newSnap(tmpf.Name(), snappy.SideloadedOrigin, unsignedOk) if err != nil { return err } lock, err := lockfile.Lock(dirs.SnapLockFile, true) if err != nil { return err } defer lock.Unlock() var flags snappy.InstallFlags if unsignedOk { flags |= snappy.AllowUnauthenticated } overlord := &snappy.Overlord{} name, err := overlord.Install(tmpf.Name(), snappy.SideloadedOrigin, flags, &progress.NullProgress{}) if err != nil { return err } return name }).Map(route)) }
// plural! func getPackagesInfo(c *Command, r *http.Request) Response { route := c.d.router.Get(packageCmd.Path) if route == nil { return InternalError(nil, "router can't find route for packages") } lock, err := lockfile.Lock(dirs.SnapLockFile, true) if err != nil { return InternalError(err, "Unable to acquire lock") } defer lock.Unlock() sources := make([]string, 1, 3) sources[0] = "local" // we're not worried if the remote repos error out found, _ := newRemoteRepo().All() if len(found) > 0 { sources = append(sources, "store") } upd, _ := newSystemRepo().Updates() if len(upd) > 0 { sources = append(sources, "system-image") } found = append(found, upd...) sort.Sort(byQN(found)) bags := lightweight.AllPartBags() results := make(map[string]map[string]string) for _, part := range found { name := part.Name() origin := part.Origin() url, err := route.URL("name", name, "origin", origin) if err != nil { return InternalError(err, "can't get route to details for %s.%s: %v", name, origin, err) } fullname := name + "." + origin qn := snappy.QualifiedName(part) results[fullname] = webify(bags[qn].Map(part), url.String()) delete(bags, qn) } for _, v := range bags { m := v.Map(nil) name := m["name"] origin := m["origin"] resource := "no resource URL for this resource" url, _ := route.URL("name", name, "origin", origin) if url != nil { resource = url.String() } results[name+"."+origin] = webify(m, resource) } return SyncResponse(map[string]interface{}{ "packages": results, "sources": sources, "paging": map[string]interface{}{ "pages": 1, "page": 1, "count": len(results), }, }) }
func snapService(c *Command, r *http.Request) Response { route := c.d.router.Get(operationCmd.Path) if route == nil { return InternalError("router can't find route for operation") } vars := muxVars(r) name := vars["name"] origin := vars["origin"] if name == "" || origin == "" { return BadRequest("missing name or origin") } appName := vars["service"] pkgName := name + "." + origin action := "status" if r.Method != "GET" { decoder := json.NewDecoder(r.Body) var cmd map[string]string if err := decoder.Decode(&cmd); err != nil { return BadRequest("can't decode request body into service command: %v", err) } action = cmd["action"] } var lock lockfile.LockedFile reachedAsync := false switch action { case "status", "start", "stop", "restart", "enable", "disable": var err error lock, err = lockfile.Lock(dirs.SnapLockFile, true) if err != nil { return InternalError("unable to acquire lock: %v", err) } defer func() { if !reachedAsync { lock.Unlock() } }() default: return BadRequest("unknown action %s", action) } bag := lightweight.PartBagByName(name, origin) idx := bag.ActiveIndex() if idx < 0 { return NotFound("unable to find snap with name %q and origin %q", name, origin) } ipart, err := bag.Load(idx) if err != nil { return InternalError("unable to load active snap: %v", err) } part, ok := ipart.(*snappy.SnapPart) if !ok { return InternalError("active snap is not a *snappy.SnapPart: %T", ipart) } apps := part.Apps() if len(apps) == 0 { return NotFound("snap %q has no services", pkgName) } appmap := make(map[string]*appDesc, len(apps)) for i := range apps { if apps[i].Daemon == "" { continue } appmap[apps[i].Name] = &appDesc{Spec: apps[i], Op: action} } if appName != "" && appmap[appName] == nil { return NotFound("snap %q has no service %q", pkgName, appName) } // note findServices takes the *bare* name actor, err := findServices(name, appName, &progress.NullProgress{}) if err != nil { return InternalError("no services for %q [%q] found: %v", pkgName, appName, err) } f := func() interface{} { status, err := actor.ServiceStatus() if err != nil { logger.Noticef("unable to get status for %q [%q]: %v", pkgName, appName, err) return err } for i := range status { if desc, ok := appmap[status[i].ServiceName]; ok { desc.Status = status[i] } else { // shouldn't really happen, but can't hurt appmap[status[i].ServiceName] = &appDesc{Status: status[i]} } } if appName == "" { return appmap } return appmap[appName] } if action == "status" { return SyncResponse(f()) } reachedAsync = true return AsyncResponse(c.d.AddTask(func() interface{} { defer lock.Unlock() switch action { case "start": err = actor.Start() case "stop": err = actor.Stop() case "enable": err = actor.Enable() case "disable": err = actor.Disable() case "restart": err = actor.Restart() } if err != nil { logger.Noticef("unable to %s %q [%q]: %v\n", action, pkgName, appName, err) return err } return f() }).Map(route)) }
// plural! func getSnapsInfo(c *Command, r *http.Request) Response { route := c.d.router.Get(snapCmd.Path) if route == nil { return InternalError("router can't find route for snaps") } lock, err := lockfile.Lock(dirs.SnapLockFile, true) if err != nil { return InternalError("unable to acquire lock: %v", err) } defer lock.Unlock() // TODO: Marshal incrementally leveraging json.RawMessage. results := make(map[string]map[string]interface{}) sources := make([]string, 0, 2) query := r.URL.Query() var includeStore, includeLocal bool if len(query["sources"]) > 0 { for _, v := range strings.Split(query["sources"][0], ",") { if v == "store" { includeStore = true } else if v == "local" { includeLocal = true } } } else { includeStore = true includeLocal = true } searchTerm := query.Get("q") var includeTypes []string if len(query["types"]) > 0 { includeTypes = strings.Split(query["types"][0], ",") } var bags map[string]*lightweight.PartBag if includeLocal { sources = append(sources, "local") bags = lightweight.AllPartBags() for _, v := range bags { m := v.Map(nil) name, _ := m["name"].(string) origin, _ := m["origin"].(string) resource := "no resource URL for this resource" url, err := route.URL("name", name, "origin", origin) if err == nil { resource = url.String() } fullname := name + "." + origin // strings.Contains(fullname, "") is true if !strings.Contains(fullname, searchTerm) { continue } results[fullname] = webify(m, resource) } } if includeStore { repo := newRemoteRepo() var found []snappy.Part // repo.Find("") finds all // // TODO: Instead of ignoring the error from Find: // * if there are no results, return an error response. // * If there are results at all (perhaps local), include a // warning in the response found, _ = repo.Find(searchTerm) sources = append(sources, "store") sort.Sort(byQN(found)) for _, part := range found { name := part.Name() origin := part.Origin() url, err := route.URL("name", name, "origin", origin) if err != nil { return InternalError("can't get route to details for %s.%s: %v", name, origin, err) } fullname := name + "." + origin qn := snappy.QualifiedName(part) results[fullname] = webify(bags[qn].Map(part), url.String()) } } // TODO: it should be possible to search on the "content" field on the store // with multiple values, see: // https://wiki.ubuntu.com/AppStore/Interfaces/ClickPackageIndex#Search if len(includeTypes) > 0 { for name, result := range results { if !resultHasType(result, includeTypes) { delete(results, name) } } } return SyncResponse(map[string]interface{}{ "snaps": results, "sources": sources, "paging": map[string]interface{}{ "pages": 1, "page": 1, "count": len(results), }, }) }
// plural! func getSnapsInfo(c *Command, r *http.Request) Response { route := c.d.router.Get(snapCmd.Path) if route == nil { return InternalError("router can't find route for snaps") } lock, err := lockfile.Lock(dirs.SnapLockFile, true) if err != nil { return InternalError("unable to acquire lock: %v", err) } defer lock.Unlock() // TODO: Marshal incrementally leveraging json.RawMessage. results := make(map[string]map[string]interface{}) sources := make([]string, 0, 2) query := r.URL.Query() var includeStore, includeLocal bool if len(query["sources"]) > 0 { for _, v := range strings.Split(query["sources"][0], ",") { if v == "store" { includeStore = true } else if v == "local" { includeLocal = true } } } else { includeStore = true includeLocal = true } var bags map[string]*lightweight.PartBag if includeLocal { sources = append(sources, "local") bags = lightweight.AllPartBags() for _, v := range bags { m := v.Map(nil) name, _ := m["name"].(string) origin, _ := m["origin"].(string) resource := "no resource URL for this resource" url, err := route.URL("name", name, "origin", origin) if err == nil { resource = url.String() } results[name+"."+origin] = webify(m, resource) } } if includeStore { // TODO: If there are no results (local or remote), report the error. If // there are results at all, inform that the result is partial. found, _ := newRemoteRepo().All() if len(found) > 0 { sources = append(sources, "store") } sort.Sort(byQN(found)) for _, part := range found { name := part.Name() origin := part.Origin() url, err := route.URL("name", name, "origin", origin) if err != nil { return InternalError("can't get route to details for %s.%s: %v", name, origin, err) } fullname := name + "." + origin qn := snappy.QualifiedName(part) results[fullname] = webify(bags[qn].Map(part), url.String()) } } return SyncResponse(map[string]interface{}{ "snaps": results, "sources": sources, "paging": map[string]interface{}{ "pages": 1, "page": 1, "count": len(results), }, }) }