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 }