github.com/bugraaydogar/snapd@v0.0.0-20210315170335-8c70bb858939/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 setID uint64 271 st *state.State 272 } 273 274 // ServeHTTP from the Response interface 275 func (s snapshotExportResponse) ServeHTTP(w http.ResponseWriter, r *http.Request) { 276 w.Header().Add("Content-Length", strconv.FormatInt(s.Size(), 10)) 277 w.Header().Add("Content-Type", client.SnapshotExportMediaType) 278 if err := s.StreamTo(w); err != nil { 279 logger.Debugf("cannot export snapshot: %v", err) 280 } 281 s.Close() 282 s.st.Lock() 283 defer s.st.Unlock() 284 snapshotstate.UnsetSnapshotOpInProgress(s.st, s.setID) 285 } 286 287 // A fileResponse 's ServeHTTP method serves the file 288 type fileResponse string 289 290 // ServeHTTP from the Response interface 291 func (f fileResponse) ServeHTTP(w http.ResponseWriter, r *http.Request) { 292 filename := fmt.Sprintf("attachment; filename=%s", filepath.Base(string(f))) 293 w.Header().Add("Content-Disposition", filename) 294 http.ServeFile(w, r, string(f)) 295 } 296 297 // A journalLineReaderSeqResponse's ServeHTTP method reads lines (presumed to 298 // be, each one on its own, a JSON dump of a systemd.Log, as output by 299 // journalctl -o json) from an io.ReadCloser, loads that into a client.Log, and 300 // outputs the json dump of that, padded with RS and LF to make it a valid 301 // json-seq response. 302 // 303 // The reader is always closed when done (this is important for 304 // osutil.WatingStdoutPipe). 305 // 306 // Tip: “jq” knows how to read this; “jq --seq” both reads and writes this. 307 type journalLineReaderSeqResponse struct { 308 io.ReadCloser 309 follow bool 310 } 311 312 func (rr *journalLineReaderSeqResponse) ServeHTTP(w http.ResponseWriter, r *http.Request) { 313 w.Header().Set("Content-Type", "application/json-seq") 314 315 flusher, hasFlusher := w.(http.Flusher) 316 317 var err error 318 dec := json.NewDecoder(rr) 319 writer := bufio.NewWriter(w) 320 enc := json.NewEncoder(writer) 321 for { 322 var log systemd.Log 323 if err = dec.Decode(&log); err != nil { 324 break 325 } 326 327 writer.WriteByte(0x1E) // RS -- see ascii(7), and RFC7464 328 329 // ignore the error... 330 t, _ := log.Time() 331 if err = enc.Encode(client.Log{ 332 Timestamp: t, 333 Message: log.Message(), 334 SID: log.SID(), 335 PID: log.PID(), 336 }); err != nil { 337 break 338 } 339 340 if rr.follow { 341 if e := writer.Flush(); e != nil { 342 break 343 } 344 if hasFlusher { 345 flusher.Flush() 346 } 347 } 348 } 349 if err != nil && err != io.EOF { 350 fmt.Fprintf(writer, `\x1E{"error": %q}\n`, err) 351 logger.Noticef("cannot stream response; problem reading: %v", err) 352 } 353 if err := writer.Flush(); err != nil { 354 logger.Noticef("cannot stream response; problem writing: %v", err) 355 } 356 rr.Close() 357 } 358 359 type assertResponse struct { 360 assertions []asserts.Assertion 361 bundle bool 362 } 363 364 // AssertResponse builds a response whose ServerHTTP method serves one or a bundle of assertions. 365 func AssertResponse(asserts []asserts.Assertion, bundle bool) Response { 366 if len(asserts) > 1 { 367 bundle = true 368 } 369 return &assertResponse{assertions: asserts, bundle: bundle} 370 } 371 372 func (ar assertResponse) ServeHTTP(w http.ResponseWriter, r *http.Request) { 373 t := asserts.MediaType 374 if ar.bundle { 375 t = mime.FormatMediaType(t, map[string]string{"bundle": "y"}) 376 } 377 w.Header().Set("Content-Type", t) 378 w.Header().Set("X-Ubuntu-Assertions-Count", strconv.Itoa(len(ar.assertions))) 379 w.WriteHeader(200) 380 enc := asserts.NewEncoder(w) 381 for _, a := range ar.assertions { 382 err := enc.Encode(a) 383 if err != nil { 384 logger.Noticef("cannot write encoded assertion into response: %v", err) 385 break 386 387 } 388 } 389 } 390 391 // errorResponder is a callable that produces an error Response. 392 // e.g., InternalError("something broke: %v", err), etc. 393 type errorResponder func(string, ...interface{}) Response 394 395 // standard error responses 396 var ( 397 Unauthorized = makeErrorResponder(401) 398 NotFound = makeErrorResponder(404) 399 BadRequest = makeErrorResponder(400) 400 MethodNotAllowed = makeErrorResponder(405) 401 InternalError = makeErrorResponder(500) 402 NotImplemented = makeErrorResponder(501) 403 Forbidden = makeErrorResponder(403) 404 Conflict = makeErrorResponder(409) 405 ) 406 407 // SnapNotFound is an error responder used when an operation is 408 // requested on a snap that doesn't exist. 409 func SnapNotFound(snapName string, err error) Response { 410 return &resp{ 411 Type: ResponseTypeError, 412 Result: &errorResult{ 413 Message: err.Error(), 414 Kind: client.ErrorKindSnapNotFound, 415 Value: snapName, 416 }, 417 Status: 404, 418 } 419 } 420 421 // SnapRevisionNotAvailable is an error responder used when an 422 // operation is requested for which no revivision can be found 423 // in the given context (e.g. request an install from a stable 424 // channel when this channel is empty). 425 func SnapRevisionNotAvailable(snapName string, rnaErr *store.RevisionNotAvailableError) Response { 426 var value interface{} = snapName 427 kind := client.ErrorKindSnapRevisionNotAvailable 428 msg := rnaErr.Error() 429 if len(rnaErr.Releases) != 0 && rnaErr.Channel != "" { 430 thisArch := arch.DpkgArchitecture() 431 values := map[string]interface{}{ 432 "snap-name": snapName, 433 "action": rnaErr.Action, 434 "channel": rnaErr.Channel, 435 "architecture": thisArch, 436 } 437 archOK := false 438 releases := make([]map[string]interface{}, 0, len(rnaErr.Releases)) 439 for _, c := range rnaErr.Releases { 440 if c.Architecture == thisArch { 441 archOK = true 442 } 443 releases = append(releases, map[string]interface{}{ 444 "architecture": c.Architecture, 445 "channel": c.Name, 446 }) 447 } 448 // we return all available releases (arch x channel) 449 // as reported in the store error, but we hint with 450 // the error kind whether there was anything at all 451 // available for this architecture 452 if archOK { 453 kind = client.ErrorKindSnapChannelNotAvailable 454 msg = "no snap revision on specified channel" 455 } else { 456 kind = client.ErrorKindSnapArchitectureNotAvailable 457 msg = "no snap revision on specified architecture" 458 } 459 values["releases"] = releases 460 value = values 461 } 462 return &resp{ 463 Type: ResponseTypeError, 464 Result: &errorResult{ 465 Message: msg, 466 Kind: kind, 467 Value: value, 468 }, 469 Status: 404, 470 } 471 } 472 473 // SnapChangeConflict is an error responder used when an operation is 474 // conflicts with another change. 475 func SnapChangeConflict(cce *snapstate.ChangeConflictError) Response { 476 value := map[string]interface{}{} 477 if cce.Snap != "" { 478 value["snap-name"] = cce.Snap 479 } 480 if cce.ChangeKind != "" { 481 value["change-kind"] = cce.ChangeKind 482 } 483 484 return &resp{ 485 Type: ResponseTypeError, 486 Result: &errorResult{ 487 Message: cce.Error(), 488 Kind: client.ErrorKindSnapChangeConflict, 489 Value: value, 490 }, 491 Status: 409, 492 } 493 } 494 495 // InsufficientSpace is an error responder used when an operation cannot 496 // be performed due to low disk space. 497 func InsufficientSpace(dserr *snapstate.InsufficientSpaceError) Response { 498 value := map[string]interface{}{} 499 if len(dserr.Snaps) > 0 { 500 value["snap-names"] = dserr.Snaps 501 } 502 if dserr.ChangeKind != "" { 503 value["change-kind"] = dserr.ChangeKind 504 } 505 return &resp{ 506 Type: ResponseTypeError, 507 Result: &errorResult{ 508 Message: dserr.Error(), 509 Kind: client.ErrorKindInsufficientDiskSpace, 510 Value: value, 511 }, 512 Status: 507, 513 } 514 } 515 516 // AppNotFound is an error responder used when an operation is 517 // requested on a app that doesn't exist. 518 func AppNotFound(format string, v ...interface{}) Response { 519 res := &errorResult{ 520 Message: fmt.Sprintf(format, v...), 521 Kind: client.ErrorKindAppNotFound, 522 } 523 return &resp{ 524 Type: ResponseTypeError, 525 Result: res, 526 Status: 404, 527 } 528 } 529 530 // AuthCancelled is an error responder used when a user cancelled 531 // the auth process. 532 func AuthCancelled(format string, v ...interface{}) Response { 533 res := &errorResult{ 534 Message: fmt.Sprintf(format, v...), 535 Kind: client.ErrorKindAuthCancelled, 536 } 537 return &resp{ 538 Type: ResponseTypeError, 539 Result: res, 540 Status: 403, 541 } 542 } 543 544 // InterfacesUnchanged is an error responder used when an operation 545 // that would normally change interfaces finds it has nothing to do 546 func InterfacesUnchanged(format string, v ...interface{}) Response { 547 res := &errorResult{ 548 Message: fmt.Sprintf(format, v...), 549 Kind: client.ErrorKindInterfacesUnchanged, 550 } 551 return &resp{ 552 Type: ResponseTypeError, 553 Result: res, 554 Status: 400, 555 } 556 } 557 558 func errToResponse(err error, snaps []string, fallback func(format string, v ...interface{}) Response, format string, v ...interface{}) Response { 559 var kind client.ErrorKind 560 var snapName string 561 562 switch err { 563 case store.ErrSnapNotFound: 564 switch len(snaps) { 565 case 1: 566 return SnapNotFound(snaps[0], err) 567 // store.ErrSnapNotFound should only be returned for individual 568 // snap queries; in all other cases something's wrong 569 case 0: 570 return InternalError("store.SnapNotFound with no snap given") 571 default: 572 return InternalError("store.SnapNotFound with %d snaps", len(snaps)) 573 } 574 case store.ErrNoUpdateAvailable: 575 kind = client.ErrorKindSnapNoUpdateAvailable 576 case store.ErrLocalSnap: 577 kind = client.ErrorKindSnapLocal 578 default: 579 handled := true 580 switch err := err.(type) { 581 case *store.RevisionNotAvailableError: 582 // store.ErrRevisionNotAvailable should only be returned for 583 // individual snap queries; in all other cases something's wrong 584 switch len(snaps) { 585 case 1: 586 return SnapRevisionNotAvailable(snaps[0], err) 587 case 0: 588 return InternalError("store.RevisionNotAvailable with no snap given") 589 default: 590 return InternalError("store.RevisionNotAvailable with %d snaps", len(snaps)) 591 } 592 case *snap.AlreadyInstalledError: 593 kind = client.ErrorKindSnapAlreadyInstalled 594 snapName = err.Snap 595 case *snap.NotInstalledError: 596 kind = client.ErrorKindSnapNotInstalled 597 snapName = err.Snap 598 case *snapstate.ChangeConflictError: 599 return SnapChangeConflict(err) 600 case *snapstate.SnapNeedsDevModeError: 601 kind = client.ErrorKindSnapNeedsDevMode 602 snapName = err.Snap 603 case *snapstate.SnapNeedsClassicError: 604 kind = client.ErrorKindSnapNeedsClassic 605 snapName = err.Snap 606 case *snapstate.SnapNeedsClassicSystemError: 607 kind = client.ErrorKindSnapNeedsClassicSystem 608 snapName = err.Snap 609 case *snapstate.SnapNotClassicError: 610 kind = client.ErrorKindSnapNotClassic 611 snapName = err.Snap 612 case *snapstate.InsufficientSpaceError: 613 return InsufficientSpace(err) 614 case net.Error: 615 if err.Timeout() { 616 kind = client.ErrorKindNetworkTimeout 617 } else { 618 handled = false 619 } 620 case *store.SnapActionError: 621 // we only handle a few specific cases 622 _, _, e := err.SingleOpError() 623 if e != nil { 624 // 👉😎👉 625 return errToResponse(e, snaps, fallback, format) 626 } 627 handled = false 628 default: 629 handled = false 630 } 631 632 if !handled { 633 v = append(v, err) 634 return fallback(format, v...) 635 } 636 } 637 638 return SyncResponse(&resp{ 639 Type: ResponseTypeError, 640 Result: &errorResult{Message: err.Error(), Kind: kind, Value: snapName}, 641 Status: 400, 642 }, nil) 643 }