github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/daemon/response.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  	"bufio"
    24  	"encoding/json"
    25  	"fmt"
    26  	"io"
    27  	"mime"
    28  	"net"
    29  	"net/http"
    30  	"path/filepath"
    31  	"strconv"
    32  	"time"
    33  
    34  	"github.com/snapcore/snapd/arch"
    35  	"github.com/snapcore/snapd/asserts"
    36  	"github.com/snapcore/snapd/client"
    37  	"github.com/snapcore/snapd/logger"
    38  	"github.com/snapcore/snapd/overlord/snapshotstate"
    39  	"github.com/snapcore/snapd/overlord/snapstate"
    40  	"github.com/snapcore/snapd/snap"
    41  	"github.com/snapcore/snapd/store"
    42  	"github.com/snapcore/snapd/systemd"
    43  )
    44  
    45  // ResponseType is the response type
    46  type ResponseType string
    47  
    48  // "there are three standard return types: Standard return value,
    49  // Background operation, Error", each returning a JSON object with the
    50  // following "type" field:
    51  const (
    52  	ResponseTypeSync  ResponseType = "sync"
    53  	ResponseTypeAsync ResponseType = "async"
    54  	ResponseTypeError ResponseType = "error"
    55  )
    56  
    57  // Response knows how to serve itself, and how to find itself
    58  type Response interface {
    59  	ServeHTTP(w http.ResponseWriter, r *http.Request)
    60  }
    61  
    62  type resp struct {
    63  	Status int          `json:"status-code"`
    64  	Type   ResponseType `json:"type"`
    65  	Result interface{}  `json:"result,omitempty"`
    66  	*Meta
    67  	Maintenance *errorResult `json:"maintenance,omitempty"`
    68  }
    69  
    70  func (r *resp) transmitMaintenance(kind client.ErrorKind, message string) {
    71  	r.Maintenance = &errorResult{
    72  		Kind:    kind,
    73  		Message: message,
    74  	}
    75  }
    76  
    77  func (r *resp) addWarningsToMeta(count int, stamp time.Time) {
    78  	if r.Meta != nil && r.Meta.WarningCount != 0 {
    79  		return
    80  	}
    81  	if count == 0 {
    82  		return
    83  	}
    84  	if r.Meta == nil {
    85  		r.Meta = &Meta{}
    86  	}
    87  	r.Meta.WarningCount = count
    88  	r.Meta.WarningTimestamp = &stamp
    89  }
    90  
    91  // TODO This is being done in a rush to get the proper external
    92  //      JSON representation in the API in time for the release.
    93  //      The right code style takes a bit more work and unifies
    94  //      these fields inside resp.
    95  // Increment the counter if you read this: 42
    96  type Meta struct {
    97  	Sources           []string   `json:"sources,omitempty"`
    98  	SuggestedCurrency string     `json:"suggested-currency,omitempty"`
    99  	Change            string     `json:"change,omitempty"`
   100  	WarningTimestamp  *time.Time `json:"warning-timestamp,omitempty"`
   101  	WarningCount      int        `json:"warning-count,omitempty"`
   102  }
   103  
   104  type respJSON struct {
   105  	Type       ResponseType `json:"type"`
   106  	Status     int          `json:"status-code"`
   107  	StatusText string       `json:"status"`
   108  	Result     interface{}  `json:"result"`
   109  	*Meta
   110  	Maintenance *errorResult `json:"maintenance,omitempty"`
   111  }
   112  
   113  func (r *resp) MarshalJSON() ([]byte, error) {
   114  	return json.Marshal(respJSON{
   115  		Type:        r.Type,
   116  		Status:      r.Status,
   117  		StatusText:  http.StatusText(r.Status),
   118  		Result:      r.Result,
   119  		Meta:        r.Meta,
   120  		Maintenance: r.Maintenance,
   121  	})
   122  }
   123  
   124  func (r *resp) ServeHTTP(w http.ResponseWriter, _ *http.Request) {
   125  	status := r.Status
   126  	bs, err := r.MarshalJSON()
   127  	if err != nil {
   128  		logger.Noticef("cannot marshal %#v to JSON: %v", *r, err)
   129  		bs = nil
   130  		status = 500
   131  	}
   132  
   133  	hdr := w.Header()
   134  	if r.Status == 202 || r.Status == 201 {
   135  		if m, ok := r.Result.(map[string]interface{}); ok {
   136  			if location, ok := m["resource"]; ok {
   137  				if location, ok := location.(string); ok && location != "" {
   138  					hdr.Set("Location", location)
   139  				}
   140  			}
   141  		}
   142  	}
   143  
   144  	hdr.Set("Content-Type", "application/json")
   145  	w.WriteHeader(status)
   146  	w.Write(bs)
   147  }
   148  
   149  type errorValue interface{}
   150  
   151  type errorResult struct {
   152  	Message string `json:"message"` // note no omitempty
   153  	// Kind is the error kind. See client/errors.go
   154  	Kind  client.ErrorKind `json:"kind,omitempty"`
   155  	Value errorValue       `json:"value,omitempty"`
   156  }
   157  
   158  // SyncResponse builds a "sync" response from the given result.
   159  func SyncResponse(result interface{}, meta *Meta) Response {
   160  	if err, ok := result.(error); ok {
   161  		return InternalError("internal error: %v", err)
   162  	}
   163  
   164  	if rsp, ok := result.(Response); ok {
   165  		return rsp
   166  	}
   167  
   168  	return &resp{
   169  		Type:   ResponseTypeSync,
   170  		Status: 200,
   171  		Result: result,
   172  		Meta:   meta,
   173  	}
   174  }
   175  
   176  // AsyncResponse builds an "async" response from the given *Task
   177  func AsyncResponse(result map[string]interface{}, meta *Meta) Response {
   178  	return &resp{
   179  		Type:   ResponseTypeAsync,
   180  		Status: 202,
   181  		Result: result,
   182  		Meta:   meta,
   183  	}
   184  }
   185  
   186  // makeErrorResponder builds an errorResponder from the given error status.
   187  func makeErrorResponder(status int) errorResponder {
   188  	return func(format string, v ...interface{}) Response {
   189  		res := &errorResult{}
   190  		if len(v) == 0 {
   191  			res.Message = format
   192  		} else {
   193  			res.Message = fmt.Sprintf(format, v...)
   194  		}
   195  		if status == 401 {
   196  			res.Kind = client.ErrorKindLoginRequired
   197  		}
   198  		return &resp{
   199  			Type:   ResponseTypeError,
   200  			Result: res,
   201  			Status: status,
   202  		}
   203  	}
   204  }
   205  
   206  // A snapStream ServeHTTP method streams a snap
   207  type snapStream struct {
   208  	SnapName string
   209  	Filename string
   210  	Info     *snap.DownloadInfo
   211  	Token    string
   212  	stream   io.ReadCloser
   213  	resume   int64
   214  }
   215  
   216  // ServeHTTP from the Response interface
   217  func (s *snapStream) ServeHTTP(w http.ResponseWriter, _ *http.Request) {
   218  	hdr := w.Header()
   219  	hdr.Set("Content-Type", "application/octet-stream")
   220  	snapname := fmt.Sprintf("attachment; filename=%s", s.Filename)
   221  	hdr.Set("Content-Disposition", snapname)
   222  
   223  	hdr.Set("Snap-Sha3-384", s.Info.Sha3_384)
   224  	// can't set Content-Length when stream is nil as it breaks http clients
   225  	// setting it also when there is a stream, for consistency
   226  	hdr.Set("Snap-Length", strconv.FormatInt(s.Info.Size, 10))
   227  	if s.Token != "" {
   228  		hdr.Set("Snap-Download-Token", s.Token)
   229  	}
   230  
   231  	if s.stream == nil {
   232  		// nothing to actually stream
   233  		return
   234  	}
   235  	hdr.Set("Content-Length", strconv.FormatInt(s.Info.Size-s.resume, 10))
   236  
   237  	if s.resume > 0 {
   238  		hdr.Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", s.resume, s.Info.Size-1, s.Info.Size))
   239  		w.WriteHeader(206)
   240  	}
   241  
   242  	defer s.stream.Close()
   243  	bytesCopied, err := io.Copy(w, s.stream)
   244  	if err != nil {
   245  		logger.Noticef("cannot copy snap %s (%#v) to the stream: %v", s.SnapName, s.Info, err)
   246  		http.Error(w, err.Error(), 500)
   247  	}
   248  	if bytesCopied != s.Info.Size-s.resume {
   249  		logger.Noticef("cannot copy snap %s (%#v) to the stream: bytes copied=%d, expected=%d", s.SnapName, s.Info, bytesCopied, s.Info.Size)
   250  		http.Error(w, io.EOF.Error(), 502)
   251  	}
   252  }
   253  
   254  // A snapshotExportResponse 's ServeHTTP method serves a specific snapshot ID
   255  type snapshotExportResponse struct {
   256  	*snapshotstate.SnapshotExport
   257  }
   258  
   259  // ServeHTTP from the Response interface
   260  func (s snapshotExportResponse) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   261  	w.Header().Add("Content-Length", strconv.FormatInt(s.Size(), 10))
   262  	if err := s.StreamTo(w); err != nil {
   263  		logger.Debugf("cannot export snapshot: %v", err)
   264  	}
   265  	s.Close()
   266  }
   267  
   268  // A fileResponse 's ServeHTTP method serves the file
   269  type fileResponse string
   270  
   271  // ServeHTTP from the Response interface
   272  func (f fileResponse) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   273  	filename := fmt.Sprintf("attachment; filename=%s", filepath.Base(string(f)))
   274  	w.Header().Add("Content-Disposition", filename)
   275  	http.ServeFile(w, r, string(f))
   276  }
   277  
   278  // A journalLineReaderSeqResponse's ServeHTTP method reads lines (presumed to
   279  // be, each one on its own, a JSON dump of a systemd.Log, as output by
   280  // journalctl -o json) from an io.ReadCloser, loads that into a client.Log, and
   281  // outputs the json dump of that, padded with RS and LF to make it a valid
   282  // json-seq response.
   283  //
   284  // The reader is always closed when done (this is important for
   285  // osutil.WatingStdoutPipe).
   286  //
   287  // Tip: “jq” knows how to read this; “jq --seq” both reads and writes this.
   288  type journalLineReaderSeqResponse struct {
   289  	io.ReadCloser
   290  	follow bool
   291  }
   292  
   293  func (rr *journalLineReaderSeqResponse) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   294  	w.Header().Set("Content-Type", "application/json-seq")
   295  
   296  	flusher, hasFlusher := w.(http.Flusher)
   297  
   298  	var err error
   299  	dec := json.NewDecoder(rr)
   300  	writer := bufio.NewWriter(w)
   301  	enc := json.NewEncoder(writer)
   302  	for {
   303  		var log systemd.Log
   304  		if err = dec.Decode(&log); err != nil {
   305  			break
   306  		}
   307  
   308  		writer.WriteByte(0x1E) // RS -- see ascii(7), and RFC7464
   309  
   310  		// ignore the error...
   311  		t, _ := log.Time()
   312  		if err = enc.Encode(client.Log{
   313  			Timestamp: t,
   314  			Message:   log.Message(),
   315  			SID:       log.SID(),
   316  			PID:       log.PID(),
   317  		}); err != nil {
   318  			break
   319  		}
   320  
   321  		if rr.follow {
   322  			if e := writer.Flush(); e != nil {
   323  				break
   324  			}
   325  			if hasFlusher {
   326  				flusher.Flush()
   327  			}
   328  		}
   329  	}
   330  	if err != nil && err != io.EOF {
   331  		fmt.Fprintf(writer, `\x1E{"error": %q}\n`, err)
   332  		logger.Noticef("cannot stream response; problem reading: %v", err)
   333  	}
   334  	if err := writer.Flush(); err != nil {
   335  		logger.Noticef("cannot stream response; problem writing: %v", err)
   336  	}
   337  	rr.Close()
   338  }
   339  
   340  type assertResponse struct {
   341  	assertions []asserts.Assertion
   342  	bundle     bool
   343  }
   344  
   345  // AssertResponse builds a response whose ServerHTTP method serves one or a bundle of assertions.
   346  func AssertResponse(asserts []asserts.Assertion, bundle bool) Response {
   347  	if len(asserts) > 1 {
   348  		bundle = true
   349  	}
   350  	return &assertResponse{assertions: asserts, bundle: bundle}
   351  }
   352  
   353  func (ar assertResponse) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   354  	t := asserts.MediaType
   355  	if ar.bundle {
   356  		t = mime.FormatMediaType(t, map[string]string{"bundle": "y"})
   357  	}
   358  	w.Header().Set("Content-Type", t)
   359  	w.Header().Set("X-Ubuntu-Assertions-Count", strconv.Itoa(len(ar.assertions)))
   360  	w.WriteHeader(200)
   361  	enc := asserts.NewEncoder(w)
   362  	for _, a := range ar.assertions {
   363  		err := enc.Encode(a)
   364  		if err != nil {
   365  			logger.Noticef("cannot write encoded assertion into response: %v", err)
   366  			break
   367  
   368  		}
   369  	}
   370  }
   371  
   372  // errorResponder is a callable that produces an error Response.
   373  // e.g., InternalError("something broke: %v", err), etc.
   374  type errorResponder func(string, ...interface{}) Response
   375  
   376  // standard error responses
   377  var (
   378  	Unauthorized     = makeErrorResponder(401)
   379  	NotFound         = makeErrorResponder(404)
   380  	BadRequest       = makeErrorResponder(400)
   381  	MethodNotAllowed = makeErrorResponder(405)
   382  	InternalError    = makeErrorResponder(500)
   383  	NotImplemented   = makeErrorResponder(501)
   384  	Forbidden        = makeErrorResponder(403)
   385  	Conflict         = makeErrorResponder(409)
   386  )
   387  
   388  // SnapNotFound is an error responder used when an operation is
   389  // requested on a snap that doesn't exist.
   390  func SnapNotFound(snapName string, err error) Response {
   391  	return &resp{
   392  		Type: ResponseTypeError,
   393  		Result: &errorResult{
   394  			Message: err.Error(),
   395  			Kind:    client.ErrorKindSnapNotFound,
   396  			Value:   snapName,
   397  		},
   398  		Status: 404,
   399  	}
   400  }
   401  
   402  // SnapRevisionNotAvailable is an error responder used when an
   403  // operation is requested for which no revivision can be found
   404  // in the given context (e.g. request an install from a stable
   405  // channel when this channel is empty).
   406  func SnapRevisionNotAvailable(snapName string, rnaErr *store.RevisionNotAvailableError) Response {
   407  	var value interface{} = snapName
   408  	kind := client.ErrorKindSnapRevisionNotAvailable
   409  	msg := rnaErr.Error()
   410  	if len(rnaErr.Releases) != 0 && rnaErr.Channel != "" {
   411  		thisArch := arch.DpkgArchitecture()
   412  		values := map[string]interface{}{
   413  			"snap-name":    snapName,
   414  			"action":       rnaErr.Action,
   415  			"channel":      rnaErr.Channel,
   416  			"architecture": thisArch,
   417  		}
   418  		archOK := false
   419  		releases := make([]map[string]interface{}, 0, len(rnaErr.Releases))
   420  		for _, c := range rnaErr.Releases {
   421  			if c.Architecture == thisArch {
   422  				archOK = true
   423  			}
   424  			releases = append(releases, map[string]interface{}{
   425  				"architecture": c.Architecture,
   426  				"channel":      c.Name,
   427  			})
   428  		}
   429  		// we return all available releases (arch x channel)
   430  		// as reported in the store error, but we hint with
   431  		// the error kind whether there was anything at all
   432  		// available for this architecture
   433  		if archOK {
   434  			kind = client.ErrorKindSnapChannelNotAvailable
   435  			msg = "no snap revision on specified channel"
   436  		} else {
   437  			kind = client.ErrorKindSnapArchitectureNotAvailable
   438  			msg = "no snap revision on specified architecture"
   439  		}
   440  		values["releases"] = releases
   441  		value = values
   442  	}
   443  	return &resp{
   444  		Type: ResponseTypeError,
   445  		Result: &errorResult{
   446  			Message: msg,
   447  			Kind:    kind,
   448  			Value:   value,
   449  		},
   450  		Status: 404,
   451  	}
   452  }
   453  
   454  // SnapChangeConflict is an error responder used when an operation is
   455  // conflicts with another change.
   456  func SnapChangeConflict(cce *snapstate.ChangeConflictError) Response {
   457  	value := map[string]interface{}{}
   458  	if cce.Snap != "" {
   459  		value["snap-name"] = cce.Snap
   460  	}
   461  	if cce.ChangeKind != "" {
   462  		value["change-kind"] = cce.ChangeKind
   463  	}
   464  
   465  	return &resp{
   466  		Type: ResponseTypeError,
   467  		Result: &errorResult{
   468  			Message: cce.Error(),
   469  			Kind:    client.ErrorKindSnapChangeConflict,
   470  			Value:   value,
   471  		},
   472  		Status: 409,
   473  	}
   474  }
   475  
   476  // InsufficientSpace is an error responder used when an operation cannot
   477  // be performed due to low disk space.
   478  func InsufficientSpace(dserr *snapstate.InsufficientSpaceError) Response {
   479  	value := map[string]interface{}{}
   480  	if len(dserr.Snaps) > 0 {
   481  		value["snap-names"] = dserr.Snaps
   482  	}
   483  	if dserr.ChangeKind != "" {
   484  		value["change-kind"] = dserr.ChangeKind
   485  	}
   486  	return &resp{
   487  		Type: ResponseTypeError,
   488  		Result: &errorResult{
   489  			Message: dserr.Error(),
   490  			Kind:    client.ErrorKindInsufficientDiskSpace,
   491  			Value:   value,
   492  		},
   493  		Status: 507,
   494  	}
   495  }
   496  
   497  // AppNotFound is an error responder used when an operation is
   498  // requested on a app that doesn't exist.
   499  func AppNotFound(format string, v ...interface{}) Response {
   500  	res := &errorResult{
   501  		Message: fmt.Sprintf(format, v...),
   502  		Kind:    client.ErrorKindAppNotFound,
   503  	}
   504  	return &resp{
   505  		Type:   ResponseTypeError,
   506  		Result: res,
   507  		Status: 404,
   508  	}
   509  }
   510  
   511  // AuthCancelled is an error responder used when a user cancelled
   512  // the auth process.
   513  func AuthCancelled(format string, v ...interface{}) Response {
   514  	res := &errorResult{
   515  		Message: fmt.Sprintf(format, v...),
   516  		Kind:    client.ErrorKindAuthCancelled,
   517  	}
   518  	return &resp{
   519  		Type:   ResponseTypeError,
   520  		Result: res,
   521  		Status: 403,
   522  	}
   523  }
   524  
   525  // InterfacesUnchanged is an error responder used when an operation
   526  // that would normally change interfaces finds it has nothing to do
   527  func InterfacesUnchanged(format string, v ...interface{}) Response {
   528  	res := &errorResult{
   529  		Message: fmt.Sprintf(format, v...),
   530  		Kind:    client.ErrorKindInterfacesUnchanged,
   531  	}
   532  	return &resp{
   533  		Type:   ResponseTypeError,
   534  		Result: res,
   535  		Status: 400,
   536  	}
   537  }
   538  
   539  func errToResponse(err error, snaps []string, fallback func(format string, v ...interface{}) Response, format string, v ...interface{}) Response {
   540  	var kind client.ErrorKind
   541  	var snapName string
   542  
   543  	switch err {
   544  	case store.ErrSnapNotFound:
   545  		switch len(snaps) {
   546  		case 1:
   547  			return SnapNotFound(snaps[0], err)
   548  		// store.ErrSnapNotFound should only be returned for individual
   549  		// snap queries; in all other cases something's wrong
   550  		case 0:
   551  			return InternalError("store.SnapNotFound with no snap given")
   552  		default:
   553  			return InternalError("store.SnapNotFound with %d snaps", len(snaps))
   554  		}
   555  	case store.ErrNoUpdateAvailable:
   556  		kind = client.ErrorKindSnapNoUpdateAvailable
   557  	case store.ErrLocalSnap:
   558  		kind = client.ErrorKindSnapLocal
   559  	default:
   560  		handled := true
   561  		switch err := err.(type) {
   562  		case *store.RevisionNotAvailableError:
   563  			// store.ErrRevisionNotAvailable should only be returned for
   564  			// individual snap queries; in all other cases something's wrong
   565  			switch len(snaps) {
   566  			case 1:
   567  				return SnapRevisionNotAvailable(snaps[0], err)
   568  			case 0:
   569  				return InternalError("store.RevisionNotAvailable with no snap given")
   570  			default:
   571  				return InternalError("store.RevisionNotAvailable with %d snaps", len(snaps))
   572  			}
   573  		case *snap.AlreadyInstalledError:
   574  			kind = client.ErrorKindSnapAlreadyInstalled
   575  			snapName = err.Snap
   576  		case *snap.NotInstalledError:
   577  			kind = client.ErrorKindSnapNotInstalled
   578  			snapName = err.Snap
   579  		case *snapstate.ChangeConflictError:
   580  			return SnapChangeConflict(err)
   581  		case *snapstate.SnapNeedsDevModeError:
   582  			kind = client.ErrorKindSnapNeedsDevMode
   583  			snapName = err.Snap
   584  		case *snapstate.SnapNeedsClassicError:
   585  			kind = client.ErrorKindSnapNeedsClassic
   586  			snapName = err.Snap
   587  		case *snapstate.SnapNeedsClassicSystemError:
   588  			kind = client.ErrorKindSnapNeedsClassicSystem
   589  			snapName = err.Snap
   590  		case *snapstate.SnapNotClassicError:
   591  			kind = client.ErrorKindSnapNotClassic
   592  			snapName = err.Snap
   593  		case *snapstate.InsufficientSpaceError:
   594  			return InsufficientSpace(err)
   595  		case net.Error:
   596  			if err.Timeout() {
   597  				kind = client.ErrorKindNetworkTimeout
   598  			} else {
   599  				handled = false
   600  			}
   601  		case *store.SnapActionError:
   602  			// we only handle a few specific cases
   603  			_, _, e := err.SingleOpError()
   604  			if e != nil {
   605  				// 👉😎👉
   606  				return errToResponse(e, snaps, fallback, format)
   607  			}
   608  			handled = false
   609  		default:
   610  			handled = false
   611  		}
   612  
   613  		if !handled {
   614  			v = append(v, err)
   615  			return fallback(format, v...)
   616  		}
   617  	}
   618  
   619  	return SyncResponse(&resp{
   620  		Type:   ResponseTypeError,
   621  		Result: &errorResult{Message: err.Error(), Kind: kind, Value: snapName},
   622  		Status: 400,
   623  	}, nil)
   624  }