github.com/stulluk/snapd@v0.0.0-20210611110309-f6d5d5bd24b0/daemon/errors.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2015-2021 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  	"fmt"
    24  	"net"
    25  	"net/http"
    26  
    27  	"github.com/snapcore/snapd/arch"
    28  	"github.com/snapcore/snapd/client"
    29  	"github.com/snapcore/snapd/overlord/snapstate"
    30  	"github.com/snapcore/snapd/snap"
    31  	"github.com/snapcore/snapd/store"
    32  )
    33  
    34  // apiError represents an error meant for returning to the client.
    35  // It can serialize itself to our standard JSON response format.
    36  type apiError struct {
    37  	// Status is the error HTTP status code.
    38  	Status  int
    39  	Message string
    40  	// Kind is the error kind. See client/errors.go
    41  	Kind  client.ErrorKind
    42  	Value errorValue
    43  }
    44  
    45  func (ae *apiError) Error() string {
    46  	kindOrStatus := "api"
    47  	if ae.Kind != "" {
    48  		kindOrStatus = fmt.Sprintf("api: %s", ae.Kind)
    49  	} else if ae.Status != 400 {
    50  		kindOrStatus = fmt.Sprintf("api %d", ae.Status)
    51  	}
    52  	return fmt.Sprintf("%s (%s)", ae.Message, kindOrStatus)
    53  }
    54  
    55  func (ae *apiError) JSON() *respJSON {
    56  	return &respJSON{
    57  		Status: ae.Status,
    58  		Type:   ResponseTypeError,
    59  		Result: &errorResult{
    60  			Message: ae.Message,
    61  			Kind:    ae.Kind,
    62  			Value:   ae.Value,
    63  		},
    64  	}
    65  }
    66  
    67  func (ae *apiError) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    68  	ae.JSON().ServeHTTP(w, r)
    69  }
    70  
    71  // check it implements StructuredResponse
    72  var _ StructuredResponse = (*apiError)(nil)
    73  
    74  type errorValue interface{}
    75  
    76  type errorResult struct {
    77  	Message string `json:"message"` // note no omitempty
    78  	// Kind is the error kind. See client/errors.go
    79  	Kind  client.ErrorKind `json:"kind,omitempty"`
    80  	Value errorValue       `json:"value,omitempty"`
    81  }
    82  
    83  // errorResponder is a callable that produces an error Response.
    84  // e.g., InternalError("something broke: %v", err), etc.
    85  type errorResponder func(string, ...interface{}) *apiError
    86  
    87  // makeErrorResponder builds an errorResponder from the given error status.
    88  func makeErrorResponder(status int) errorResponder {
    89  	return func(format string, v ...interface{}) *apiError {
    90  		var msg string
    91  		if len(v) == 0 {
    92  			msg = format
    93  		} else {
    94  			msg = fmt.Sprintf(format, v...)
    95  		}
    96  		var kind client.ErrorKind
    97  		if status == 401 || status == 403 {
    98  			kind = client.ErrorKindLoginRequired
    99  		}
   100  		return &apiError{
   101  			Status:  status,
   102  			Message: msg,
   103  			Kind:    kind,
   104  		}
   105  	}
   106  }
   107  
   108  // standard error responses
   109  var (
   110  	Unauthorized     = makeErrorResponder(401)
   111  	NotFound         = makeErrorResponder(404)
   112  	BadRequest       = makeErrorResponder(400)
   113  	MethodNotAllowed = makeErrorResponder(405)
   114  	InternalError    = makeErrorResponder(500)
   115  	NotImplemented   = makeErrorResponder(501)
   116  	Forbidden        = makeErrorResponder(403)
   117  	Conflict         = makeErrorResponder(409)
   118  )
   119  
   120  // BadQuery is an error responder used when a bad query was
   121  // provided to the store.
   122  func BadQuery() *apiError {
   123  	return &apiError{
   124  		Status:  400,
   125  		Message: "bad query",
   126  		Kind:    client.ErrorKindBadQuery,
   127  	}
   128  }
   129  
   130  // SnapNotFound is an error responder used when an operation is
   131  // requested on a snap that doesn't exist.
   132  func SnapNotFound(snapName string, err error) *apiError {
   133  	return &apiError{
   134  		Status:  404,
   135  		Message: err.Error(),
   136  		Kind:    client.ErrorKindSnapNotFound,
   137  		Value:   snapName,
   138  	}
   139  }
   140  
   141  // SnapRevisionNotAvailable is an error responder used when an
   142  // operation is requested for which no revivision can be found
   143  // in the given context (e.g. request an install from a stable
   144  // channel when this channel is empty).
   145  func SnapRevisionNotAvailable(snapName string, rnaErr *store.RevisionNotAvailableError) *apiError {
   146  	var value interface{} = snapName
   147  	kind := client.ErrorKindSnapRevisionNotAvailable
   148  	msg := rnaErr.Error()
   149  	if len(rnaErr.Releases) != 0 && rnaErr.Channel != "" {
   150  		thisArch := arch.DpkgArchitecture()
   151  		values := map[string]interface{}{
   152  			"snap-name":    snapName,
   153  			"action":       rnaErr.Action,
   154  			"channel":      rnaErr.Channel,
   155  			"architecture": thisArch,
   156  		}
   157  		archOK := false
   158  		releases := make([]map[string]interface{}, 0, len(rnaErr.Releases))
   159  		for _, c := range rnaErr.Releases {
   160  			if c.Architecture == thisArch {
   161  				archOK = true
   162  			}
   163  			releases = append(releases, map[string]interface{}{
   164  				"architecture": c.Architecture,
   165  				"channel":      c.Name,
   166  			})
   167  		}
   168  		// we return all available releases (arch x channel)
   169  		// as reported in the store error, but we hint with
   170  		// the error kind whether there was anything at all
   171  		// available for this architecture
   172  		if archOK {
   173  			kind = client.ErrorKindSnapChannelNotAvailable
   174  			msg = "no snap revision on specified channel"
   175  		} else {
   176  			kind = client.ErrorKindSnapArchitectureNotAvailable
   177  			msg = "no snap revision on specified architecture"
   178  		}
   179  		values["releases"] = releases
   180  		value = values
   181  	}
   182  	return &apiError{
   183  		Status:  404,
   184  		Message: msg,
   185  		Kind:    kind,
   186  		Value:   value,
   187  	}
   188  }
   189  
   190  // SnapChangeConflict is an error responder used when an operation is
   191  // conflicts with another change.
   192  func SnapChangeConflict(cce *snapstate.ChangeConflictError) *apiError {
   193  	value := map[string]interface{}{}
   194  	if cce.Snap != "" {
   195  		value["snap-name"] = cce.Snap
   196  	}
   197  	if cce.ChangeKind != "" {
   198  		value["change-kind"] = cce.ChangeKind
   199  	}
   200  
   201  	return &apiError{
   202  		Status:  409,
   203  		Message: cce.Error(),
   204  		Kind:    client.ErrorKindSnapChangeConflict,
   205  		Value:   value,
   206  	}
   207  }
   208  
   209  // InsufficientSpace is an error responder used when an operation cannot
   210  // be performed due to low disk space.
   211  func InsufficientSpace(dserr *snapstate.InsufficientSpaceError) *apiError {
   212  	value := map[string]interface{}{}
   213  	if len(dserr.Snaps) > 0 {
   214  		value["snap-names"] = dserr.Snaps
   215  	}
   216  	if dserr.ChangeKind != "" {
   217  		value["change-kind"] = dserr.ChangeKind
   218  	}
   219  	return &apiError{
   220  		Status:  507,
   221  		Message: dserr.Error(),
   222  		Kind:    client.ErrorKindInsufficientDiskSpace,
   223  		Value:   value,
   224  	}
   225  }
   226  
   227  // AppNotFound is an error responder used when an operation is
   228  // requested on a app that doesn't exist.
   229  func AppNotFound(format string, v ...interface{}) *apiError {
   230  	return &apiError{
   231  		Status:  404,
   232  		Message: fmt.Sprintf(format, v...),
   233  		Kind:    client.ErrorKindAppNotFound,
   234  	}
   235  }
   236  
   237  // AuthCancelled is an error responder used when a user cancelled
   238  // the auth process.
   239  func AuthCancelled(format string, v ...interface{}) *apiError {
   240  	return &apiError{
   241  		Status:  403,
   242  		Message: fmt.Sprintf(format, v...),
   243  		Kind:    client.ErrorKindAuthCancelled,
   244  	}
   245  }
   246  
   247  // InterfacesUnchanged is an error responder used when an operation
   248  // that would normally change interfaces finds it has nothing to do
   249  func InterfacesUnchanged(format string, v ...interface{}) *apiError {
   250  	return &apiError{
   251  		Status:  400,
   252  		Message: fmt.Sprintf(format, v...),
   253  		Kind:    client.ErrorKindInterfacesUnchanged,
   254  	}
   255  }
   256  
   257  func errToResponse(err error, snaps []string, fallback errorResponder, format string, v ...interface{}) *apiError {
   258  	var kind client.ErrorKind
   259  	var snapName string
   260  
   261  	switch err {
   262  	case store.ErrSnapNotFound:
   263  		switch len(snaps) {
   264  		case 1:
   265  			return SnapNotFound(snaps[0], err)
   266  		// store.ErrSnapNotFound should only be returned for individual
   267  		// snap queries; in all other cases something's wrong
   268  		case 0:
   269  			return InternalError("store.SnapNotFound with no snap given")
   270  		default:
   271  			return InternalError("store.SnapNotFound with %d snaps", len(snaps))
   272  		}
   273  	case store.ErrNoUpdateAvailable:
   274  		kind = client.ErrorKindSnapNoUpdateAvailable
   275  	case store.ErrLocalSnap:
   276  		kind = client.ErrorKindSnapLocal
   277  	default:
   278  		handled := true
   279  		switch err := err.(type) {
   280  		case *store.RevisionNotAvailableError:
   281  			// store.ErrRevisionNotAvailable should only be returned for
   282  			// individual snap queries; in all other cases something's wrong
   283  			switch len(snaps) {
   284  			case 1:
   285  				return SnapRevisionNotAvailable(snaps[0], err)
   286  			case 0:
   287  				return InternalError("store.RevisionNotAvailable with no snap given")
   288  			default:
   289  				return InternalError("store.RevisionNotAvailable with %d snaps", len(snaps))
   290  			}
   291  		case *snap.AlreadyInstalledError:
   292  			kind = client.ErrorKindSnapAlreadyInstalled
   293  			snapName = err.Snap
   294  		case *snap.NotInstalledError:
   295  			kind = client.ErrorKindSnapNotInstalled
   296  			snapName = err.Snap
   297  		case *snapstate.ChangeConflictError:
   298  			return SnapChangeConflict(err)
   299  		case *snapstate.SnapNeedsDevModeError:
   300  			kind = client.ErrorKindSnapNeedsDevMode
   301  			snapName = err.Snap
   302  		case *snapstate.SnapNeedsClassicError:
   303  			kind = client.ErrorKindSnapNeedsClassic
   304  			snapName = err.Snap
   305  		case *snapstate.SnapNeedsClassicSystemError:
   306  			kind = client.ErrorKindSnapNeedsClassicSystem
   307  			snapName = err.Snap
   308  		case *snapstate.SnapNotClassicError:
   309  			kind = client.ErrorKindSnapNotClassic
   310  			snapName = err.Snap
   311  		case *snapstate.InsufficientSpaceError:
   312  			return InsufficientSpace(err)
   313  		case net.Error:
   314  			if err.Timeout() {
   315  				kind = client.ErrorKindNetworkTimeout
   316  			} else {
   317  				handled = false
   318  			}
   319  		case *store.SnapActionError:
   320  			// we only handle a few specific cases
   321  			_, _, e := err.SingleOpError()
   322  			if e != nil {
   323  				// 👉😎👉
   324  				return errToResponse(e, snaps, fallback, format)
   325  			}
   326  			handled = false
   327  		default:
   328  			handled = false
   329  		}
   330  
   331  		if !handled {
   332  			v = append(v, err)
   333  			return fallback(format, v...)
   334  		}
   335  	}
   336  
   337  	return &apiError{
   338  		Status:  400,
   339  		Message: err.Error(),
   340  		Kind:    kind,
   341  		Value:   snapName,
   342  	}
   343  }