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  }