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 }