github.com/bugraaydogar/snapd@v0.0.0-20210315170335-8c70bb858939/daemon/api_apps.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2015-2020 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package daemon 21 22 import ( 23 "encoding/json" 24 "fmt" 25 "net/http" 26 "sort" 27 "strconv" 28 "strings" 29 30 "github.com/snapcore/snapd/client/clientutil" 31 "github.com/snapcore/snapd/overlord/auth" 32 "github.com/snapcore/snapd/overlord/servicestate" 33 "github.com/snapcore/snapd/overlord/state" 34 "github.com/snapcore/snapd/progress" 35 "github.com/snapcore/snapd/snap" 36 "github.com/snapcore/snapd/strutil" 37 "github.com/snapcore/snapd/systemd" 38 ) 39 40 var ( 41 appsCmd = &Command{ 42 Path: "/v2/apps", 43 UserOK: true, 44 GET: getAppsInfo, 45 POST: postApps, 46 } 47 48 logsCmd = &Command{ 49 Path: "/v2/logs", 50 PolkitOK: "io.snapcraft.snapd.manage", 51 GET: getLogs, 52 } 53 ) 54 55 func getAppsInfo(c *Command, r *http.Request, user *auth.UserState) Response { 56 query := r.URL.Query() 57 58 opts := appInfoOptions{} 59 switch sel := query.Get("select"); sel { 60 case "": 61 // nothing to do 62 case "service": 63 opts.service = true 64 default: 65 return BadRequest("invalid select parameter: %q", sel) 66 } 67 68 appInfos, rsp := appInfosFor(c.d.overlord.State(), strutil.CommaSeparatedList(query.Get("names")), opts) 69 if rsp != nil { 70 return rsp 71 } 72 73 sd := servicestate.NewStatusDecorator(progress.Null) 74 75 clientAppInfos, err := clientutil.ClientAppInfosFromSnapAppInfos(appInfos, sd) 76 if err != nil { 77 return InternalError("%v", err) 78 } 79 80 return SyncResponse(clientAppInfos, nil) 81 } 82 83 type appInfoOptions struct { 84 service bool 85 } 86 87 func (opts appInfoOptions) String() string { 88 if opts.service { 89 return "service" 90 } 91 92 return "app" 93 } 94 95 // appInfosFor returns a sorted list apps described by names. 96 // 97 // * If names is empty, returns all apps of the wanted kinds (which 98 // could be an empty list). 99 // * An element of names can be a snap name, in which case all apps 100 // from the snap of the wanted kind are included in the result (and 101 // it's an error if the snap has no apps of the wanted kind). 102 // * An element of names can instead be snap.app, in which case that app is 103 // included in the result (and it's an error if the snap and app don't 104 // both exist, or if the app is not a wanted kind) 105 // On error an appropriate error Response is returned; a nil Response means 106 // no error. 107 // 108 // It's a programming error to call this with wanted having neither 109 // services nor commands set. 110 func appInfosFor(st *state.State, names []string, opts appInfoOptions) ([]*snap.AppInfo, Response) { 111 snapNames := make(map[string]bool) 112 requested := make(map[string]bool) 113 for _, name := range names { 114 requested[name] = true 115 name, _ = splitAppName(name) 116 snapNames[name] = true 117 } 118 119 snaps, err := allLocalSnapInfos(st, false, snapNames) 120 if err != nil { 121 return nil, InternalError("cannot list local snaps! %v", err) 122 } 123 124 found := make(map[string]bool) 125 appInfos := make([]*snap.AppInfo, 0, len(requested)) 126 for _, snp := range snaps { 127 snapName := snp.info.InstanceName() 128 apps := make([]*snap.AppInfo, 0, len(snp.info.Apps)) 129 for _, app := range snp.info.Apps { 130 if !opts.service || app.IsService() { 131 apps = append(apps, app) 132 } 133 } 134 135 if len(apps) == 0 && requested[snapName] { 136 return nil, AppNotFound("snap %q has no %ss", snapName, opts) 137 } 138 139 includeAll := len(requested) == 0 || requested[snapName] 140 if includeAll { 141 // want all services in a snap 142 found[snapName] = true 143 } 144 145 for _, app := range apps { 146 appName := snapName + "." + app.Name 147 if includeAll || requested[appName] { 148 appInfos = append(appInfos, app) 149 found[appName] = true 150 } 151 } 152 } 153 154 for k := range requested { 155 if !found[k] { 156 if snapNames[k] { 157 return nil, SnapNotFound(k, fmt.Errorf("snap %q not found", k)) 158 } else { 159 snap, app := splitAppName(k) 160 return nil, AppNotFound("snap %q has no %s %q", snap, opts, app) 161 } 162 } 163 } 164 165 sort.Sort(snap.AppInfoBySnapApp(appInfos)) 166 167 return appInfos, nil 168 } 169 170 // this differs from snap.SplitSnapApp in the handling of the 171 // snap-only case: 172 // snap.SplitSnapApp("foo") is ("foo", "foo"), 173 // splitAppName("foo") is ("foo", ""). 174 func splitAppName(s string) (snap, app string) { 175 if idx := strings.IndexByte(s, '.'); idx > -1 { 176 return s[:idx], s[idx+1:] 177 } 178 179 return s, "" 180 } 181 182 func getLogs(c *Command, r *http.Request, user *auth.UserState) Response { 183 query := r.URL.Query() 184 n := 10 185 if s := query.Get("n"); s != "" { 186 m, err := strconv.ParseInt(s, 0, 32) 187 if err != nil { 188 return BadRequest(`invalid value for n: %q: %v`, s, err) 189 } 190 n = int(m) 191 } 192 follow := false 193 if s := query.Get("follow"); s != "" { 194 f, err := strconv.ParseBool(s) 195 if err != nil { 196 return BadRequest(`invalid value for follow: %q: %v`, s, err) 197 } 198 follow = f 199 } 200 201 // only services have logs for now 202 opts := appInfoOptions{service: true} 203 appInfos, rsp := appInfosFor(c.d.overlord.State(), strutil.CommaSeparatedList(query.Get("names")), opts) 204 if rsp != nil { 205 return rsp 206 } 207 if len(appInfos) == 0 { 208 return AppNotFound("no matching services") 209 } 210 211 serviceNames := make([]string, len(appInfos)) 212 for i, appInfo := range appInfos { 213 serviceNames[i] = appInfo.ServiceName() 214 } 215 216 sysd := systemd.New(systemd.SystemMode, progress.Null) 217 reader, err := sysd.LogReader(serviceNames, n, follow) 218 if err != nil { 219 return InternalError("cannot get logs: %v", err) 220 } 221 222 return &journalLineReaderSeqResponse{ 223 ReadCloser: reader, 224 follow: follow, 225 } 226 } 227 228 var servicestateControl = servicestate.Control 229 230 func postApps(c *Command, r *http.Request, user *auth.UserState) Response { 231 var inst servicestate.Instruction 232 decoder := json.NewDecoder(r.Body) 233 if err := decoder.Decode(&inst); err != nil { 234 return BadRequest("cannot decode request body into service operation: %v", err) 235 } 236 // XXX: decoder.More() 237 if len(inst.Names) == 0 { 238 // on POST, don't allow empty to mean all 239 return BadRequest("cannot perform operation on services without a list of services to operate on") 240 } 241 242 st := c.d.overlord.State() 243 appInfos, rsp := appInfosFor(st, inst.Names, appInfoOptions{service: true}) 244 if rsp != nil { 245 return rsp 246 } 247 if len(appInfos) == 0 { 248 // can't happen: appInfosFor with a non-empty list of services 249 // shouldn't ever return an empty appInfos with no error response 250 return InternalError("no services found") 251 } 252 253 // do not pass flags - only create service-control tasks, do not create 254 // exec-command tasks for old snapd. These are not needed since we are 255 // handling momentary snap service commands. 256 st.Lock() 257 defer st.Unlock() 258 tss, err := servicestateControl(st, appInfos, &inst, nil, nil) 259 if err != nil { 260 // TODO: use errToResponse here too and introduce a proper error kind ? 261 if _, ok := err.(servicestate.ServiceActionConflictError); ok { 262 return Conflict(err.Error()) 263 } 264 return BadRequest(err.Error()) 265 } 266 // names received in the request can be snap or snap.app, we need to 267 // extract the actual snap names before associating them with a change 268 chg := newChange(st, "service-control", fmt.Sprintf("Running service command"), tss, namesToSnapNames(&inst)) 269 st.EnsureBefore(0) 270 return AsyncResponse(nil, &Meta{Change: chg.ID()}) 271 } 272 273 func namesToSnapNames(inst *servicestate.Instruction) []string { 274 seen := make(map[string]struct{}, len(inst.Names)) 275 for _, snapOrSnapDotApp := range inst.Names { 276 snapName, _ := snap.SplitSnapApp(snapOrSnapDotApp) 277 seen[snapName] = struct{}{} 278 } 279 names := make([]string, 0, len(seen)) 280 for k := range seen { 281 names = append(names, k) 282 } 283 // keep stable ordering 284 sort.Strings(names) 285 return names 286 }