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 }