github.com/rigado/snapd@v2.42.5-go-mod+incompatible/daemon/response.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2015-2018 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/snapstate" 39 "github.com/snapcore/snapd/snap" 40 "github.com/snapcore/snapd/store" 41 "github.com/snapcore/snapd/systemd" 42 ) 43 44 // ResponseType is the response type 45 type ResponseType string 46 47 // "there are three standard return types: Standard return value, 48 // Background operation, Error", each returning a JSON object with the 49 // following "type" field: 50 const ( 51 ResponseTypeSync ResponseType = "sync" 52 ResponseTypeAsync ResponseType = "async" 53 ResponseTypeError ResponseType = "error" 54 ) 55 56 // Response knows how to serve itself, and how to find itself 57 type Response interface { 58 ServeHTTP(w http.ResponseWriter, r *http.Request) 59 } 60 61 type resp struct { 62 Status int `json:"status-code"` 63 Type ResponseType `json:"type"` 64 Result interface{} `json:"result,omitempty"` 65 *Meta 66 Maintenance *errorResult `json:"maintenance,omitempty"` 67 } 68 69 func (r *resp) transmitMaintenance(kind errorKind, message string) { 70 r.Maintenance = &errorResult{ 71 Kind: kind, 72 Message: message, 73 } 74 } 75 76 func (r *resp) addWarningsToMeta(count int, stamp time.Time) { 77 if r.Meta != nil && r.Meta.WarningCount != 0 { 78 return 79 } 80 if count == 0 { 81 return 82 } 83 if r.Meta == nil { 84 r.Meta = &Meta{} 85 } 86 r.Meta.WarningCount = count 87 r.Meta.WarningTimestamp = &stamp 88 } 89 90 // TODO This is being done in a rush to get the proper external 91 // JSON representation in the API in time for the release. 92 // The right code style takes a bit more work and unifies 93 // these fields inside resp. 94 // Increment the counter if you read this: 42 95 type Meta struct { 96 Sources []string `json:"sources,omitempty"` 97 SuggestedCurrency string `json:"suggested-currency,omitempty"` 98 Change string `json:"change,omitempty"` 99 WarningTimestamp *time.Time `json:"warning-timestamp,omitempty"` 100 WarningCount int `json:"warning-count,omitempty"` 101 } 102 103 type respJSON struct { 104 Type ResponseType `json:"type"` 105 Status int `json:"status-code"` 106 StatusText string `json:"status"` 107 Result interface{} `json:"result"` 108 *Meta 109 Maintenance *errorResult `json:"maintenance,omitempty"` 110 } 111 112 func (r *resp) MarshalJSON() ([]byte, error) { 113 return json.Marshal(respJSON{ 114 Type: r.Type, 115 Status: r.Status, 116 StatusText: http.StatusText(r.Status), 117 Result: r.Result, 118 Meta: r.Meta, 119 Maintenance: r.Maintenance, 120 }) 121 } 122 123 func (r *resp) ServeHTTP(w http.ResponseWriter, _ *http.Request) { 124 status := r.Status 125 bs, err := r.MarshalJSON() 126 if err != nil { 127 logger.Noticef("cannot marshal %#v to JSON: %v", *r, err) 128 bs = nil 129 status = 500 130 } 131 132 hdr := w.Header() 133 if r.Status == 202 || r.Status == 201 { 134 if m, ok := r.Result.(map[string]interface{}); ok { 135 if location, ok := m["resource"]; ok { 136 if location, ok := location.(string); ok && location != "" { 137 hdr.Set("Location", location) 138 } 139 } 140 } 141 } 142 143 hdr.Set("Content-Type", "application/json") 144 w.WriteHeader(status) 145 w.Write(bs) 146 } 147 148 type errorKind string 149 150 const ( 151 errorKindTwoFactorRequired = errorKind("two-factor-required") 152 errorKindTwoFactorFailed = errorKind("two-factor-failed") 153 errorKindLoginRequired = errorKind("login-required") 154 errorKindInvalidAuthData = errorKind("invalid-auth-data") 155 errorKindAuthCancelled = errorKind("auth-cancelled") 156 errorKindTermsNotAccepted = errorKind("terms-not-accepted") 157 errorKindNoPaymentMethods = errorKind("no-payment-methods") 158 errorKindPaymentDeclined = errorKind("payment-declined") 159 errorKindPasswordPolicy = errorKind("password-policy") 160 161 errorKindSnapAlreadyInstalled = errorKind("snap-already-installed") 162 errorKindSnapNotInstalled = errorKind("snap-not-installed") 163 errorKindSnapNotFound = errorKind("snap-not-found") 164 errorKindAppNotFound = errorKind("app-not-found") 165 errorKindSnapLocal = errorKind("snap-local") 166 errorKindSnapNoUpdateAvailable = errorKind("snap-no-update-available") 167 168 errorKindSnapRevisionNotAvailable = errorKind("snap-revision-not-available") 169 errorKindSnapChannelNotAvailable = errorKind("snap-channel-not-available") 170 errorKindSnapArchitectureNotAvailable = errorKind("snap-architecture-not-available") 171 172 errorKindSnapChangeConflict = errorKind("snap-change-conflict") 173 174 errorKindNotSnap = errorKind("snap-not-a-snap") 175 176 errorKindSnapNeedsDevMode = errorKind("snap-needs-devmode") 177 errorKindSnapNeedsClassic = errorKind("snap-needs-classic") 178 errorKindSnapNeedsClassicSystem = errorKind("snap-needs-classic-system") 179 errorKindSnapNotClassic = errorKind("snap-not-classic") 180 181 errorKindBadQuery = errorKind("bad-query") 182 183 errorKindNetworkTimeout = errorKind("network-timeout") 184 errorKindDNSFailure = errorKind("dns-failure") 185 errorKindInterfacesUnchanged = errorKind("interfaces-unchanged") 186 187 errorKindConfigNoSuchOption = errorKind("option-not-found") 188 189 errorKindDaemonRestart = errorKind("daemon-restart") 190 errorKindSystemRestart = errorKind("system-restart") 191 192 errorKindAssertionNotFound = errorKind("assertion-not-found") 193 ) 194 195 type errorValue interface{} 196 197 type errorResult struct { 198 Message string `json:"message"` // note no omitempty 199 Kind errorKind `json:"kind,omitempty"` 200 Value errorValue `json:"value,omitempty"` 201 } 202 203 // SyncResponse builds a "sync" response from the given result. 204 func SyncResponse(result interface{}, meta *Meta) Response { 205 if err, ok := result.(error); ok { 206 return InternalError("internal error: %v", err) 207 } 208 209 if rsp, ok := result.(Response); ok { 210 return rsp 211 } 212 213 return &resp{ 214 Type: ResponseTypeSync, 215 Status: 200, 216 Result: result, 217 Meta: meta, 218 } 219 } 220 221 // AsyncResponse builds an "async" response from the given *Task 222 func AsyncResponse(result map[string]interface{}, meta *Meta) Response { 223 return &resp{ 224 Type: ResponseTypeAsync, 225 Status: 202, 226 Result: result, 227 Meta: meta, 228 } 229 } 230 231 // makeErrorResponder builds an errorResponder from the given error status. 232 func makeErrorResponder(status int) errorResponder { 233 return func(format string, v ...interface{}) Response { 234 res := &errorResult{} 235 if len(v) == 0 { 236 res.Message = format 237 } else { 238 res.Message = fmt.Sprintf(format, v...) 239 } 240 if status == 401 { 241 res.Kind = errorKindLoginRequired 242 } 243 return &resp{ 244 Type: ResponseTypeError, 245 Result: res, 246 Status: status, 247 } 248 } 249 } 250 251 // A FileStream ServeHTTP method streams the snap 252 type fileStream struct { 253 SnapName string 254 Info snap.DownloadInfo 255 stream io.ReadCloser 256 } 257 258 // ServeHTTP from the Response interface 259 func (s fileStream) ServeHTTP(w http.ResponseWriter, _ *http.Request) { 260 hdr := w.Header() 261 hdr.Set("Content-Type", "application/octet-stream") 262 snapname := fmt.Sprintf("attachment; filename=%s", s.SnapName) 263 hdr.Set("Content-Disposition", snapname) 264 265 size := fmt.Sprintf("%d", s.Info.Size) 266 hdr.Set("Content-Length", size) 267 268 defer s.stream.Close() 269 bytesCopied, err := io.Copy(w, s.stream) 270 if err != nil { 271 logger.Noticef("cannot copy snap %s (%#v) to the stream: %v", s.SnapName, s.Info, err) 272 http.Error(w, err.Error(), 500) 273 } 274 if bytesCopied != s.Info.Size { 275 logger.Noticef("cannot copy snap %s (%#v) to the stream: bytes copied=%d, expected=%d", s.SnapName, s.Info, bytesCopied, s.Info.Size) 276 http.Error(w, io.EOF.Error(), 502) 277 } 278 } 279 280 // A fileResponse 's ServeHTTP method serves the file 281 type fileResponse string 282 283 // ServeHTTP from the Response interface 284 func (f fileResponse) ServeHTTP(w http.ResponseWriter, r *http.Request) { 285 filename := fmt.Sprintf("attachment; filename=%s", filepath.Base(string(f))) 286 w.Header().Add("Content-Disposition", filename) 287 http.ServeFile(w, r, string(f)) 288 } 289 290 // A journalLineReaderSeqResponse's ServeHTTP method reads lines (presumed to 291 // be, each one on its own, a JSON dump of a systemd.Log, as output by 292 // journalctl -o json) from an io.ReadCloser, loads that into a client.Log, and 293 // outputs the json dump of that, padded with RS and LF to make it a valid 294 // json-seq response. 295 // 296 // The reader is always closed when done (this is important for 297 // osutil.WatingStdoutPipe). 298 // 299 // Tip: “jq” knows how to read this; “jq --seq” both reads and writes this. 300 type journalLineReaderSeqResponse struct { 301 io.ReadCloser 302 follow bool 303 } 304 305 func (rr *journalLineReaderSeqResponse) ServeHTTP(w http.ResponseWriter, r *http.Request) { 306 w.Header().Set("Content-Type", "application/json-seq") 307 308 flusher, hasFlusher := w.(http.Flusher) 309 310 var err error 311 dec := json.NewDecoder(rr) 312 writer := bufio.NewWriter(w) 313 enc := json.NewEncoder(writer) 314 for { 315 var log systemd.Log 316 if err = dec.Decode(&log); err != nil { 317 break 318 } 319 320 writer.WriteByte(0x1E) // RS -- see ascii(7), and RFC7464 321 322 // ignore the error... 323 t, _ := log.Time() 324 if err = enc.Encode(client.Log{ 325 Timestamp: t, 326 Message: log.Message(), 327 SID: log.SID(), 328 PID: log.PID(), 329 }); err != nil { 330 break 331 } 332 333 if rr.follow { 334 if e := writer.Flush(); e != nil { 335 break 336 } 337 if hasFlusher { 338 flusher.Flush() 339 } 340 } 341 } 342 if err != nil && err != io.EOF { 343 fmt.Fprintf(writer, `\x1E{"error": %q}\n`, err) 344 logger.Noticef("cannot stream response; problem reading: %v", err) 345 } 346 if err := writer.Flush(); err != nil { 347 logger.Noticef("cannot stream response; problem writing: %v", err) 348 } 349 rr.Close() 350 } 351 352 type assertResponse struct { 353 assertions []asserts.Assertion 354 bundle bool 355 } 356 357 // AssertResponse builds a response whose ServerHTTP method serves one or a bundle of assertions. 358 func AssertResponse(asserts []asserts.Assertion, bundle bool) Response { 359 if len(asserts) > 1 { 360 bundle = true 361 } 362 return &assertResponse{assertions: asserts, bundle: bundle} 363 } 364 365 func (ar assertResponse) ServeHTTP(w http.ResponseWriter, r *http.Request) { 366 t := asserts.MediaType 367 if ar.bundle { 368 t = mime.FormatMediaType(t, map[string]string{"bundle": "y"}) 369 } 370 w.Header().Set("Content-Type", t) 371 w.Header().Set("X-Ubuntu-Assertions-Count", strconv.Itoa(len(ar.assertions))) 372 w.WriteHeader(200) 373 enc := asserts.NewEncoder(w) 374 for _, a := range ar.assertions { 375 err := enc.Encode(a) 376 if err != nil { 377 logger.Noticef("cannot write encoded assertion into response: %v", err) 378 break 379 380 } 381 } 382 } 383 384 // errorResponder is a callable that produces an error Response. 385 // e.g., InternalError("something broke: %v", err), etc. 386 type errorResponder func(string, ...interface{}) Response 387 388 // standard error responses 389 var ( 390 Unauthorized = makeErrorResponder(401) 391 NotFound = makeErrorResponder(404) 392 BadRequest = makeErrorResponder(400) 393 MethodNotAllowed = makeErrorResponder(405) 394 InternalError = makeErrorResponder(500) 395 NotImplemented = makeErrorResponder(501) 396 Forbidden = makeErrorResponder(403) 397 Conflict = makeErrorResponder(409) 398 ) 399 400 // SnapNotFound is an error responder used when an operation is 401 // requested on a snap that doesn't exist. 402 func SnapNotFound(snapName string, err error) Response { 403 return &resp{ 404 Type: ResponseTypeError, 405 Result: &errorResult{ 406 Message: err.Error(), 407 Kind: errorKindSnapNotFound, 408 Value: snapName, 409 }, 410 Status: 404, 411 } 412 } 413 414 // SnapRevisionNotAvailable is an error responder used when an 415 // operation is requested for which no revivision can be found 416 // in the given context (e.g. request an install from a stable 417 // channel when this channel is empty). 418 func SnapRevisionNotAvailable(snapName string, rnaErr *store.RevisionNotAvailableError) Response { 419 var value interface{} = snapName 420 kind := errorKindSnapRevisionNotAvailable 421 msg := rnaErr.Error() 422 if len(rnaErr.Releases) != 0 && rnaErr.Channel != "" { 423 thisArch := arch.DpkgArchitecture() 424 values := map[string]interface{}{ 425 "snap-name": snapName, 426 "action": rnaErr.Action, 427 "channel": rnaErr.Channel, 428 "architecture": thisArch, 429 } 430 archOK := false 431 releases := make([]map[string]interface{}, 0, len(rnaErr.Releases)) 432 for _, c := range rnaErr.Releases { 433 if c.Architecture == thisArch { 434 archOK = true 435 } 436 releases = append(releases, map[string]interface{}{ 437 "architecture": c.Architecture, 438 "channel": c.Name, 439 }) 440 } 441 // we return all available releases (arch x channel) 442 // as reported in the store error, but we hint with 443 // the error kind whether there was anything at all 444 // available for this architecture 445 if archOK { 446 kind = errorKindSnapChannelNotAvailable 447 msg = "no snap revision on specified channel" 448 } else { 449 kind = errorKindSnapArchitectureNotAvailable 450 msg = "no snap revision on specified architecture" 451 } 452 values["releases"] = releases 453 value = values 454 } 455 return &resp{ 456 Type: ResponseTypeError, 457 Result: &errorResult{ 458 Message: msg, 459 Kind: kind, 460 Value: value, 461 }, 462 Status: 404, 463 } 464 } 465 466 // SnapChangeConflict is an error responder used when an operation is 467 // conflicts with another change. 468 func SnapChangeConflict(cce *snapstate.ChangeConflictError) Response { 469 value := map[string]interface{}{} 470 if cce.Snap != "" { 471 value["snap-name"] = cce.Snap 472 } 473 if cce.ChangeKind != "" { 474 value["change-kind"] = cce.ChangeKind 475 } 476 477 return &resp{ 478 Type: ResponseTypeError, 479 Result: &errorResult{ 480 Message: cce.Error(), 481 Kind: errorKindSnapChangeConflict, 482 Value: value, 483 }, 484 Status: 409, 485 } 486 } 487 488 // AppNotFound is an error responder used when an operation is 489 // requested on a app that doesn't exist. 490 func AppNotFound(format string, v ...interface{}) Response { 491 res := &errorResult{ 492 Message: fmt.Sprintf(format, v...), 493 Kind: errorKindAppNotFound, 494 } 495 return &resp{ 496 Type: ResponseTypeError, 497 Result: res, 498 Status: 404, 499 } 500 } 501 502 // AuthCancelled is an error responder used when a user cancelled 503 // the auth process. 504 func AuthCancelled(format string, v ...interface{}) Response { 505 res := &errorResult{ 506 Message: fmt.Sprintf(format, v...), 507 Kind: errorKindAuthCancelled, 508 } 509 return &resp{ 510 Type: ResponseTypeError, 511 Result: res, 512 Status: 403, 513 } 514 } 515 516 // InterfacesUnchanged is an error responder used when an operation 517 // that would normally change interfaces finds it has nothing to do 518 func InterfacesUnchanged(format string, v ...interface{}) Response { 519 res := &errorResult{ 520 Message: fmt.Sprintf(format, v...), 521 Kind: errorKindInterfacesUnchanged, 522 } 523 return &resp{ 524 Type: ResponseTypeError, 525 Result: res, 526 Status: 400, 527 } 528 } 529 530 func errToResponse(err error, snaps []string, fallback func(format string, v ...interface{}) Response, format string, v ...interface{}) Response { 531 var kind errorKind 532 var snapName string 533 534 switch err { 535 case store.ErrSnapNotFound: 536 switch len(snaps) { 537 case 1: 538 return SnapNotFound(snaps[0], err) 539 // store.ErrSnapNotFound should only be returned for individual 540 // snap queries; in all other cases something's wrong 541 case 0: 542 return InternalError("store.SnapNotFound with no snap given") 543 default: 544 return InternalError("store.SnapNotFound with %d snaps", len(snaps)) 545 } 546 case store.ErrNoUpdateAvailable: 547 kind = errorKindSnapNoUpdateAvailable 548 case store.ErrLocalSnap: 549 kind = errorKindSnapLocal 550 default: 551 handled := true 552 switch err := err.(type) { 553 case *store.RevisionNotAvailableError: 554 // store.ErrRevisionNotAvailable should only be returned for 555 // individual snap queries; in all other cases something's wrong 556 switch len(snaps) { 557 case 1: 558 return SnapRevisionNotAvailable(snaps[0], err) 559 case 0: 560 return InternalError("store.RevisionNotAvailable with no snap given") 561 default: 562 return InternalError("store.RevisionNotAvailable with %d snaps", len(snaps)) 563 } 564 case *snap.AlreadyInstalledError: 565 kind = errorKindSnapAlreadyInstalled 566 snapName = err.Snap 567 case *snap.NotInstalledError: 568 kind = errorKindSnapNotInstalled 569 snapName = err.Snap 570 case *snapstate.ChangeConflictError: 571 return SnapChangeConflict(err) 572 case *snapstate.SnapNeedsDevModeError: 573 kind = errorKindSnapNeedsDevMode 574 snapName = err.Snap 575 case *snapstate.SnapNeedsClassicError: 576 kind = errorKindSnapNeedsClassic 577 snapName = err.Snap 578 case *snapstate.SnapNeedsClassicSystemError: 579 kind = errorKindSnapNeedsClassicSystem 580 snapName = err.Snap 581 case *snapstate.SnapNotClassicError: 582 kind = errorKindSnapNotClassic 583 snapName = err.Snap 584 case net.Error: 585 if err.Timeout() { 586 kind = errorKindNetworkTimeout 587 } else { 588 handled = false 589 } 590 default: 591 handled = false 592 } 593 594 if !handled { 595 v = append(v, err) 596 return fallback(format, v...) 597 } 598 } 599 600 return SyncResponse(&resp{ 601 Type: ResponseTypeError, 602 Result: &errorResult{Message: err.Error(), Kind: kind, Value: snapName}, 603 Status: 400, 604 }, nil) 605 }