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