github.com/rigado/snapd@v2.42.5-go-mod+incompatible/daemon/response.go (about)

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