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