OLD | NEW |
1 // Copyright 2013 Canonical Ltd. | 1 // Copyright 2013 Canonical Ltd. |
2 // Licensed under the AGPLv3, see LICENCE file for details. | 2 // Licensed under the AGPLv3, see LICENCE file for details. |
3 | 3 |
4 package api | 4 package api |
5 | 5 |
6 import ( | 6 import ( |
| 7 "encoding/base64" |
7 "encoding/json" | 8 "encoding/json" |
8 "fmt" | 9 "fmt" |
9 "io/ioutil" | 10 "io/ioutil" |
10 "net/http" | 11 "net/http" |
| 12 "net/url" |
11 "os" | 13 "os" |
12 "time" | 14 "time" |
13 | 15 |
| 16 "code.google.com/p/go.net/websocket" |
| 17 |
14 "launchpad.net/juju-core/charm" | 18 "launchpad.net/juju-core/charm" |
15 "launchpad.net/juju-core/constraints" | 19 "launchpad.net/juju-core/constraints" |
16 "launchpad.net/juju-core/instance" | 20 "launchpad.net/juju-core/instance" |
17 "launchpad.net/juju-core/state/api/params" | 21 "launchpad.net/juju-core/state/api/params" |
18 "launchpad.net/juju-core/utils" | 22 "launchpad.net/juju-core/utils" |
19 "launchpad.net/juju-core/version" | 23 "launchpad.net/juju-core/version" |
20 ) | 24 ) |
21 | 25 |
22 // Client represents the client-accessible part of the state. | 26 // Client represents the client-accessible part of the state. |
23 type Client struct { | 27 type Client struct { |
(...skipping 428 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
452 var err error | 456 var err error |
453 if archive, err = os.Open(ch.Path); err != nil { | 457 if archive, err = os.Open(ch.Path); err != nil { |
454 return nil, fmt.Errorf("cannot read charm archive: %v",
err) | 458 return nil, fmt.Errorf("cannot read charm archive: %v",
err) |
455 } | 459 } |
456 defer archive.Close() | 460 defer archive.Close() |
457 default: | 461 default: |
458 return nil, fmt.Errorf("unknown charm type %T", ch) | 462 return nil, fmt.Errorf("unknown charm type %T", ch) |
459 } | 463 } |
460 | 464 |
461 // Prepare the upload request. | 465 // Prepare the upload request. |
462 » url := fmt.Sprintf("%s/charms?series=%s", c.st.serverRoot, curl.Series) | 466 » url := fmt.Sprintf("https://%s/charms?series=%s", c.st.serverHostPort, c
url.Series) |
463 req, err := http.NewRequest("POST", url, archive) | 467 req, err := http.NewRequest("POST", url, archive) |
464 if err != nil { | 468 if err != nil { |
465 return nil, fmt.Errorf("cannot create upload request: %v", err) | 469 return nil, fmt.Errorf("cannot create upload request: %v", err) |
466 } | 470 } |
467 req.SetBasicAuth(c.st.tag, c.st.password) | 471 req.SetBasicAuth(c.st.tag, c.st.password) |
468 req.Header.Set("Content-Type", "application/zip") | 472 req.Header.Set("Content-Type", "application/zip") |
469 | 473 |
470 // Send the request. | 474 // Send the request. |
471 | 475 |
472 // BUG(dimitern) 2013-12-17 bug #1261780 | 476 // BUG(dimitern) 2013-12-17 bug #1261780 |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
507 } | 511 } |
508 | 512 |
509 // AddCharm adds the given charm URL (which must include revision) to | 513 // AddCharm adds the given charm URL (which must include revision) to |
510 // the environment, if it does not exist yet. Local charms are not | 514 // the environment, if it does not exist yet. Local charms are not |
511 // supported, only charm store URLs. See also AddLocalCharm() in the | 515 // supported, only charm store URLs. See also AddLocalCharm() in the |
512 // client-side API. | 516 // client-side API. |
513 func (c *Client) AddCharm(curl *charm.URL) error { | 517 func (c *Client) AddCharm(curl *charm.URL) error { |
514 args := params.CharmURL{URL: curl.String()} | 518 args := params.CharmURL{URL: curl.String()} |
515 return c.st.Call("Client", "", "AddCharm", args, nil) | 519 return c.st.Call("Client", "", "AddCharm", args, nil) |
516 } | 520 } |
| 521 |
| 522 // WatchDebugLog returns a ClientDebugLog reading the debug log message. |
| 523 // The filter allows to grep wanted lines out of the output, e.g. |
| 524 // machines or units. The watching is started the given number of |
| 525 // matching lines back in history. |
| 526 func (c *Client) WatchDebugLog(lines int, filter string) (*ClientDebugLog, error
) { |
| 527 cfg := c.st.websocketConfig |
| 528 // Prepare URL. |
| 529 attrs := url.Values{ |
| 530 "lines": {fmt.Sprintf("%d", lines)}, |
| 531 "filter": {filter}, |
| 532 } |
| 533 cfg.Location = &url.URL{ |
| 534 Scheme: "wss", |
| 535 Host: c.st.serverHostPort, |
| 536 Path: "/log", |
| 537 RawQuery: attrs.Encode(), |
| 538 } |
| 539 cfg.Header = make(http.Header) |
| 540 setBasicAuth(cfg.Header, c.st.tag, c.st.password) |
| 541 |
| 542 wsConn, err := websocket.DialConfig(&cfg) |
| 543 if err != nil { |
| 544 return nil, err |
| 545 } |
| 546 return &ClientDebugLog{wsConn}, nil |
| 547 } |
| 548 |
| 549 // ClientDebugLog represents a stream of debug log messages. |
| 550 type ClientDebugLog struct { |
| 551 wsConn *websocket.Conn |
| 552 } |
| 553 |
| 554 // Close closes the log. |
| 555 func (c *ClientDebugLog) Close() error { |
| 556 return c.wsConn.Close() |
| 557 } |
| 558 |
| 559 // SetFilter sets the filter regular expression. This |
| 560 // setting will not take place immediately - messages |
| 561 // already in the pipeline will still be received. |
| 562 func (c *ClientDebugLog) SetFilter(filter string) error { |
| 563 req := params.DebugLogRequest{Filter: filter} |
| 564 if err := websocket.JSON.Send(c.wsConn, &req); err != nil { |
| 565 return err |
| 566 } |
| 567 return nil |
| 568 } |
| 569 |
| 570 // Read implements io.Reader.Read. |
| 571 func (c *ClientDebugLog) Read(buf []byte) (int, error) { |
| 572 return c.wsConn.Read(buf) |
| 573 } |
| 574 |
| 575 // setBasicAuth creates an Authorization header for HTTP Basic |
| 576 // Authentication, using the given username and password. |
| 577 func setBasicAuth(h http.Header, username, password string) { |
| 578 h.Set("Authorization", "Basic "+basicAuth(username, password)) |
| 579 } |
| 580 |
| 581 // basicAuth is copied from net/http. |
| 582 // See 2 (end of page 4) http://www.ietf.org/rfc/rfc2617.txt |
| 583 // "To receive authorization, the client sends the userid and password, |
| 584 // separated by a single colon (":") character, within a base64 |
| 585 // encoded string in the credentials." |
| 586 // It is not meant to be urlencoded. |
| 587 func basicAuth(username, password string) string { |
| 588 auth := username + ":" + password |
| 589 return base64.StdEncoding.EncodeToString([]byte(auth)) |
| 590 } |
OLD | NEW |