Example #1
0
func (s *httpSuite) TestNonValidatingClientGetter(c *gc.C) {
	client := utils.GetNonValidatingHTTPClient()
	resp, err := client.Get(s.Server.URL)
	c.Assert(err, gc.IsNil)
	resp.Body.Close()
	c.Assert(resp.StatusCode, gc.Equals, http.StatusOK)

	client1 := utils.GetNonValidatingHTTPClient()
	c.Assert(client1, gc.Not(gc.Equals), client)
}
Example #2
0
func (s *toolsWithMacaroonsSuite) TestCanPostWithLocalLogin(c *gc.C) {
	// Create a new user, and a local login macaroon for it.
	user := s.Factory.MakeUser(c, &factory.UserParams{Password: "******"})
	conn := s.OpenAPIAs(c, user.Tag(), "hunter2")
	defer conn.Close()
	mac, err := usermanager.NewClient(conn).CreateLocalLoginMacaroon(user.UserTag())
	c.Assert(err, jc.ErrorIsNil)

	checkCount := 0
	s.DischargerLogin = func() string {
		checkCount++
		return user.UserTag().Id()
	}
	do := func(req *http.Request) (*http.Response, error) {
		data, err := json.Marshal(macaroon.Slice{mac})
		if err != nil {
			return nil, err
		}
		req.Header.Add(httpbakery.MacaroonsHeader, base64.StdEncoding.EncodeToString(data))
		return utils.GetNonValidatingHTTPClient().Do(req)
	}
	// send without using bakeryDo, so we don't pass any macaroon cookies
	// along.
	resp := s.sendRequest(c, httpRequestParams{
		method:   "POST",
		url:      s.toolsURI(c, ""),
		tag:      user.UserTag().String(),
		password: "", // no password forces macaroon usage
		do:       do,
	})
	s.assertErrorResponse(c, resp, http.StatusBadRequest, "expected binaryVersion argument")
	c.Assert(checkCount, gc.Equals, 0)
}
Example #3
0
// UploadTools uploads tools at the specified location to the API server over HTTPS.
func (c *Client) UploadTools(r io.Reader, vers version.Binary, additionalSeries ...string) (*tools.Tools, error) {
	// Prepare the upload request.
	query := fmt.Sprintf("binaryVersion=%s&series=%s",
		vers,
		strings.Join(additionalSeries, ","),
	)

	endpoint, err := c.st.apiEndpoint("/tools", query)
	if err != nil {
		return nil, errors.Trace(err)
	}

	req, err := http.NewRequest("POST", endpoint.String(), r)
	if err != nil {
		return nil, errors.Annotate(err, "cannot create upload request")
	}
	req.SetBasicAuth(c.st.tag, c.st.password)
	req.Header.Set("Content-Type", "application/x-tar-gz")

	// Send the request.

	// BUG(dimitern) 2013-12-17 bug #1261780
	// Due to issues with go 1.1.2, fixed later, we cannot use a
	// regular TLS client with the CACert here, because we get "x509:
	// cannot validate certificate for 127.0.0.1 because it doesn't
	// contain any IP SANs". Once we use a later go version, this
	// should be changed to connect to the API server with a regular
	// HTTP+TLS enabled client, using the CACert (possily cached, like
	// the tag and password) passed in api.Open()'s info argument.
	resp, err := utils.GetNonValidatingHTTPClient().Do(req)
	if err != nil {
		return nil, errors.Annotate(err, "cannot upload tools")
	}
	defer resp.Body.Close()

	// Now parse the response & return.
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, errors.Annotate(err, "cannot read tools upload response")
	}
	if resp.StatusCode != http.StatusOK {
		// TODO (2015/09/15, bug #1499277) parse as JSON not as text.
		message := fmt.Sprintf("%s", bytes.TrimSpace(body))
		if resp.StatusCode == http.StatusBadRequest && strings.Contains(message, params.CodeOperationBlocked) {
			// Operation Blocked errors must contain correct error code and message.
			return nil, &params.Error{Code: params.CodeOperationBlocked, Message: message}
		}
		return nil, errors.Errorf("tools upload failed: %v (%s)", resp.StatusCode, message)
	}

	var jsonResponse params.ToolsResult
	if err := json.Unmarshal(body, &jsonResponse); err != nil {
		return nil, errors.Annotate(err, "cannot unmarshal upload response")
	}
	if err := jsonResponse.Error; err != nil {
		return nil, errors.Annotate(err, "error uploading tools")
	}
	return jsonResponse.Tools, nil
}
Example #4
0
func (c *registerCommand) secretKeyLogin(addrs []string, request params.SecretKeyLoginRequest) (*params.SecretKeyLoginResponse, error) {
	buf, err := json.Marshal(&request)
	if err != nil {
		return nil, errors.Annotate(err, "marshalling request")
	}
	r := bytes.NewReader(buf)

	// Determine which address to use by attempting to open an API
	// connection with each of the addresses. Note that we do not
	// know the CA certificate yet, so we do not want to send any
	// sensitive information. We make no attempt to log in until
	// we can verify the server's identity.
	opts := api.DefaultDialOpts()
	opts.InsecureSkipVerify = true
	conn, err := c.apiOpen(&api.Info{
		Addrs:     addrs,
		SkipLogin: true,
		// NOTE(axw) CACert is required, but ignored if
		// InsecureSkipVerify is set. We should try to
		// bring together CACert and InsecureSkipVerify
		// so they can be validated together.
		CACert: "ignored",
	}, opts)
	if err != nil {
		return nil, errors.Trace(err)
	}
	apiAddr := conn.Addr()
	if err := conn.Close(); err != nil {
		return nil, errors.Trace(err)
	}

	// Using the address we connected to above, perform the request.
	urlString := fmt.Sprintf("https://%s/register", apiAddr)
	httpReq, err := http.NewRequest("POST", urlString, r)
	if err != nil {
		return nil, errors.Trace(err)
	}
	httpReq.Header.Set("Content-Type", "application/json")
	httpClient := utils.GetNonValidatingHTTPClient()
	httpResp, err := httpClient.Do(httpReq)
	if err != nil {
		return nil, errors.Trace(err)
	}
	defer httpResp.Body.Close()

	if httpResp.StatusCode != http.StatusOK {
		var resp params.ErrorResult
		if err := json.NewDecoder(httpResp.Body).Decode(&resp); err != nil {
			return nil, errors.Trace(err)
		}
		return nil, resp.Error
	}

	var resp params.SecretKeyLoginResponse
	if err := json.NewDecoder(httpResp.Body).Decode(&resp); err != nil {
		return nil, errors.Trace(err)
	}
	return &resp, nil
}
Example #5
0
func (s *httpSuite) TestInsecureClientSucceeds(c *gc.C) {
	response, err := utils.GetNonValidatingHTTPClient().Get(s.Server.URL)
	c.Assert(err, gc.IsNil)
	defer response.Body.Close()
	body, err := ioutil.ReadAll(response.Body)
	c.Assert(err, gc.IsNil)
	c.Check(string(body), gc.Equals, "Greetings!\n")
}
Example #6
0
func (c *Client) UploadTools(
	toolsFilename string, vers version.Binary, fakeSeries ...string,
) (
	tools *tools.Tools, err error,
) {
	toolsTarball, err := os.Open(toolsFilename)
	if err != nil {
		return nil, err
	}
	defer toolsTarball.Close()

	// Prepare the upload request.
	url := fmt.Sprintf("%s/tools?binaryVersion=%s&series=%s", c.st.serverRoot, vers, strings.Join(fakeSeries, ","))
	req, err := http.NewRequest("POST", url, toolsTarball)
	if err != nil {
		return nil, fmt.Errorf("cannot create upload request: %v", err)
	}
	req.SetBasicAuth(c.st.tag, c.st.password)
	req.Header.Set("Content-Type", "application/x-tar-gz")

	// Send the request.

	// BUG(dimitern) 2013-12-17 bug #1261780
	// Due to issues with go 1.1.2, fixed later, we cannot use a
	// regular TLS client with the CACert here, because we get "x509:
	// cannot validate certificate for 127.0.0.1 because it doesn't
	// contain any IP SANs". Once we use a later go version, this
	// should be changed to connect to the API server with a regular
	// HTTP+TLS enabled client, using the CACert (possily cached, like
	// the tag and password) passed in api.Open()'s info argument.
	resp, err := utils.GetNonValidatingHTTPClient().Do(req)
	if err != nil {
		return nil, fmt.Errorf("cannot upload charm: %v", err)
	}
	if resp.StatusCode == http.StatusMethodNotAllowed {
		// API server is older than 1.17.5, so tools upload
		// is not supported; notify the client.
		return nil, &params.Error{
			Message: "tools upload is not supported by the API server",
			Code:    params.CodeNotImplemented,
		}
	}

	// Now parse the response & return.
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("cannot read tools upload response: %v", err)
	}
	defer resp.Body.Close()
	var jsonResponse params.ToolsResult
	if err := json.Unmarshal(body, &jsonResponse); err != nil {
		return nil, fmt.Errorf("cannot unmarshal upload response: %v", err)
	}
	if err := jsonResponse.Error; err != nil {
		return nil, fmt.Errorf("error uploading tools: %v", err)
	}
	return jsonResponse.Tools, nil
}
Example #7
0
func (s *authHttpSuite) sendRequest(c *gc.C, tag, password, method, uri, contentType string, body io.Reader) (*http.Response, error) {
	req, err := http.NewRequest(method, uri, body)
	c.Assert(err, gc.IsNil)
	if tag != "" && password != "" {
		req.SetBasicAuth(tag, password)
	}
	if contentType != "" {
		req.Header.Set("Content-Type", contentType)
	}
	return utils.GetNonValidatingHTTPClient().Do(req)
}
Example #8
0
func (s *registrationSuite) testInvalidRequest(c *gc.C, requestBody, errorMessage, errorCode string, statusCode int) {
	httptesting.AssertJSONCall(c, httptesting.JSONCallParams{
		Do:           utils.GetNonValidatingHTTPClient().Do,
		URL:          s.registrationURL(c),
		Method:       "POST",
		Body:         strings.NewReader(requestBody),
		ExpectStatus: statusCode,
		ExpectBody: &params.ErrorResult{
			Error: &params.Error{Message: errorMessage, Code: errorCode},
		},
	})
}
Example #9
0
func (s *registrationSuite) TestRegisterInvalidMethod(c *gc.C) {
	httptesting.AssertJSONCall(c, httptesting.JSONCallParams{
		Do:           utils.GetNonValidatingHTTPClient().Do,
		URL:          s.registrationURL(c),
		Method:       "GET",
		ExpectStatus: http.StatusMethodNotAllowed,
		ExpectBody: &params.ErrorResult{
			Error: &params.Error{
				Message: `unsupported method: "GET"`,
				Code:    params.CodeMethodNotAllowed,
			},
		},
	})
}
Example #10
0
// fetchAndCacheTools fetches tools with the specified version by searching for a URL
// in simplestreams and GETting it, caching the result in toolstorage before returning
// to the caller.
func (h *toolsDownloadHandler) fetchAndCacheTools(v version.Binary, stor toolstorage.Storage, st *state.State) (io.ReadCloser, error) {
	envcfg, err := st.EnvironConfig()
	if err != nil {
		return nil, err
	}
	env, err := environs.New(envcfg)
	if err != nil {
		return nil, err
	}
	tools, err := envtools.FindExactTools(env, v.Number, v.Series, v.Arch)
	if err != nil {
		return nil, err
	}

	// No need to verify the server's identity because we verify the SHA-256 hash.
	logger.Infof("fetching %v tools from %v", v, tools.URL)
	resp, err := utils.GetNonValidatingHTTPClient().Get(tools.URL)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		msg := fmt.Sprintf("bad HTTP response: %v", resp.Status)
		if body, err := ioutil.ReadAll(resp.Body); err == nil {
			msg += fmt.Sprintf(" (%s)", bytes.TrimSpace(body))
		}
		return nil, errors.New(msg)
	}
	data, sha256, err := readAndHash(resp.Body)
	if err != nil {
		return nil, err
	}
	if int64(len(data)) != tools.Size {
		return nil, errors.Errorf("size mismatch for %s", tools.URL)
	}
	if sha256 != tools.SHA256 {
		return nil, errors.Errorf("hash mismatch for %s", tools.URL)
	}

	// Cache tarball in toolstorage before returning.
	metadata := toolstorage.Metadata{
		Version: v,
		Size:    tools.Size,
		SHA256:  tools.SHA256,
	}
	if err := stor.AddTools(bytes.NewReader(data), metadata); err != nil {
		return nil, errors.Annotate(err, "error caching tools")
	}
	return ioutil.NopCloser(bytes.NewReader(data)), nil
}
Example #11
0
// UploadTools uploads tools at the specified location to the API server over HTTPS.
func (c *Client) UploadTools(r io.Reader, vers version.Binary) (*tools.Tools, error) {
	// Older versions of Juju expect to be told which series to expand
	// the uploaded tools to on the server-side. In new versions we
	// do this automatically, and the parameter will be ignored.
	fakeSeries := version.OSSupportedSeries(vers.OS)

	// Prepare the upload request.
	url := fmt.Sprintf("%s/tools?binaryVersion=%s&series=%s", c.st.serverRoot, vers, strings.Join(fakeSeries, ","))
	req, err := http.NewRequest("POST", url, r)
	if err != nil {
		return nil, errors.Annotate(err, "cannot create upload request")
	}
	req.SetBasicAuth(c.st.tag, c.st.password)
	req.Header.Set("Content-Type", "application/x-tar-gz")

	// Send the request.

	// BUG(dimitern) 2013-12-17 bug #1261780
	// Due to issues with go 1.1.2, fixed later, we cannot use a
	// regular TLS client with the CACert here, because we get "x509:
	// cannot validate certificate for 127.0.0.1 because it doesn't
	// contain any IP SANs". Once we use a later go version, this
	// should be changed to connect to the API server with a regular
	// HTTP+TLS enabled client, using the CACert (possily cached, like
	// the tag and password) passed in api.Open()'s info argument.
	resp, err := utils.GetNonValidatingHTTPClient().Do(req)
	if err != nil {
		return nil, errors.Annotate(err, "cannot upload charm")
	}
	defer resp.Body.Close()

	// Now parse the response & return.
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, errors.Annotate(err, "cannot read tools upload response")
	}
	if resp.StatusCode != http.StatusOK {
		return nil, errors.Errorf("tools upload failed: %v (%s)", resp.StatusCode, bytes.TrimSpace(body))
	}

	var jsonResponse params.ToolsResult
	if err := json.Unmarshal(body, &jsonResponse); err != nil {
		return nil, errors.Annotate(err, "cannot unmarshal upload response")
	}
	if err := jsonResponse.Error; err != nil {
		return nil, errors.Annotate(err, "error uploading tools")
	}
	return jsonResponse.Tools, nil
}
Example #12
0
func fetchCharmArchive(url *url.URL) ([]byte, error) {
	client := utils.GetNonValidatingHTTPClient()
	resp, err := client.Get(url.String())
	if err != nil {
		return nil, errors.Annotatef(err, "cannot get %q", url)
	}
	body, err := ioutil.ReadAll(resp.Body)
	resp.Body.Close()
	if err != nil {
		return nil, errors.Annotatef(err, "cannot read charm archive")
	}
	if resp.StatusCode != http.StatusOK {
		return nil, errors.Errorf("cannot get %q: %s %s", url, resp.Status, body)
	}
	return body, nil
}
Example #13
0
func (s *registrationSuite) TestRegister(c *gc.C) {
	// Ensure we cannot log in with the password yet.
	const password = "******"
	c.Assert(s.bob.PasswordValid(password), jc.IsFalse)

	validNonce := []byte(strings.Repeat("X", 24))
	secretKey := s.bob.SecretKey()
	ciphertext := s.sealBox(
		c, validNonce, secretKey, fmt.Sprintf(`{"password": "******"}`, password),
	)
	resp := httptesting.Do(c, httptesting.DoRequestParams{
		Do:     utils.GetNonValidatingHTTPClient().Do,
		URL:    s.registrationURL(c),
		Method: "POST",
		JSONBody: &params.SecretKeyLoginRequest{
			User:              "******",
			Nonce:             validNonce,
			PayloadCiphertext: ciphertext,
		},
	})
	c.Assert(resp.StatusCode, gc.Equals, http.StatusOK)
	defer resp.Body.Close()

	// It should be possible to log in as bob with the
	// password "hunter2" now, and there should be no
	// secret key any longer.
	err := s.bob.Refresh()
	c.Assert(err, jc.ErrorIsNil)
	c.Assert(s.bob.PasswordValid(password), jc.IsTrue)
	c.Assert(s.bob.SecretKey(), gc.IsNil)

	var response params.SecretKeyLoginResponse
	bodyData, err := ioutil.ReadAll(resp.Body)
	c.Assert(err, jc.ErrorIsNil)
	err = json.Unmarshal(bodyData, &response)
	c.Assert(err, jc.ErrorIsNil)
	c.Assert(response.Nonce, gc.HasLen, len(validNonce))
	plaintext := s.openBox(c, response.PayloadCiphertext, response.Nonce, secretKey)

	var responsePayload params.SecretKeyLoginResponsePayload
	err = json.Unmarshal(plaintext, &responsePayload)
	c.Assert(err, jc.ErrorIsNil)
	c.Assert(responsePayload.CACert, gc.Equals, s.BackingState.CACert())
	model, err := s.BackingState.Model()
	c.Assert(err, jc.ErrorIsNil)
	c.Assert(responsePayload.ControllerUUID, gc.Equals, model.ControllerUUID())
}
Example #14
0
func (s *toolsWithMacaroonsSuite) TestCanPostWithLocalLogin(c *gc.C) {
	// Create a new local user that we can log in as
	// using macaroon authentication.
	const password = "******"
	user := s.Factory.MakeUser(c, &factory.UserParams{Password: password})

	// Install a "web-page" visitor that deals with the interaction
	// method that Juju controllers support for authenticating local
	// users. Note: the use of httpbakery.NewMultiVisitor is necessary
	// to trigger httpbakery to query the authentication methods and
	// bypass browser authentication.
	var prompted bool
	jar := apitesting.NewClearableCookieJar()
	client := utils.GetNonValidatingHTTPClient()
	client.Jar = jar
	bakeryClient := httpbakery.NewClient()
	bakeryClient.Client = client
	bakeryClient.WebPageVisitor = httpbakery.NewMultiVisitor(apiauthentication.NewVisitor(
		user.UserTag().Id(),
		func(username string) (string, error) {
			c.Assert(username, gc.Equals, user.UserTag().Id())
			prompted = true
			return password, nil
		},
	))
	bakeryDo := func(req *http.Request) (*http.Response, error) {
		var body io.ReadSeeker
		if req.Body != nil {
			body = req.Body.(io.ReadSeeker)
			req.Body = nil
		}
		return bakeryClient.DoWithBodyAndCustomError(req, body, bakeryGetError)
	}

	resp := s.sendRequest(c, httpRequestParams{
		method:   "POST",
		url:      s.toolsURI(c, ""),
		tag:      user.UserTag().String(),
		password: "", // no password forces macaroon usage
		do:       bakeryDo,
	})
	s.assertErrorResponse(c, resp, http.StatusBadRequest, "expected binaryVersion argument")
	c.Assert(prompted, jc.IsTrue)
}
Example #15
0
func (u *Upgrader) ensureTools(agentTools *coretools.Tools) error {
	logger.Infof("fetching tools from %q", agentTools.URL)
	// The reader MUST verify the tools' hash, so there is no
	// need to validate the peer. We cannot anyway: see http://pad.lv/1261780.
	resp, err := utils.GetNonValidatingHTTPClient().Get(agentTools.URL)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("bad HTTP response: %v", resp.Status)
	}
	err = agenttools.UnpackTools(u.dataDir, agentTools, resp.Body)
	if err != nil {
		return fmt.Errorf("cannot unpack tools: %v", err)
	}
	logger.Infof("unpacked tools %s to %s", agentTools.Version, u.dataDir)
	return nil
}
Example #16
0
func (s *authHttpSuite) sendRequest(c *gc.C, p httpRequestParams) *http.Response {
	c.Logf("sendRequest: %s", p.url)
	hp := httptesting.DoRequestParams{
		Do:          p.do,
		Method:      p.method,
		URL:         p.url,
		Body:        p.body,
		JSONBody:    p.jsonBody,
		Header:      make(http.Header),
		Username:    p.tag,
		Password:    p.password,
		ExpectError: p.expectError,
	}
	if p.contentType != "" {
		hp.Header.Set("Content-Type", p.contentType)
	}
	if p.nonce != "" {
		hp.Header.Set(params.MachineNonceHeader, p.nonce)
	}
	if hp.Do == nil {
		hp.Do = utils.GetNonValidatingHTTPClient().Do
	}
	return httptesting.Do(c, hp)
}
Example #17
0
func (s *httpSuite) TestInsecureClientCached(c *gc.C) {
	client1 := utils.GetNonValidatingHTTPClient()
	client2 := utils.GetNonValidatingHTTPClient()
	c.Check(client1, gc.Equals, client2)
}
Example #18
0
func (s *httpSuite) TestNonValidatingClientGetter(c *gc.C) {
	client1 := utils.GetNonValidatingHTTPClient()
	client2 := utils.GetHTTPClient(utils.NoVerifySSLHostnames)
	c.Check(client1, gc.Equals, client2)
}
Example #19
0
func (s *dialSuite) TestInsecureClientNoAccess(c *gc.C) {
	s.PatchValue(&utils.OutgoingAccessAllowed, false)
	_, err := utils.GetNonValidatingHTTPClient().Get("http://10.0.0.1:1234")
	c.Assert(err, gc.ErrorMatches, `.*access to address "10.0.0.1:1234" not allowed`)
}
Example #20
0
func (s *httpDialSuite) TestInsecureClientAllowAccess(c *gc.C) {
	_, err := utils.GetNonValidatingHTTPClient().Get("http://0.1.2.3:1234")
	c.Assert(err, gc.ErrorMatches, `Get http://0.1.2.3:1234: dial tcp 0.1.2.3:1234: connect: .*`)
}
Example #21
0
// AddLocalCharm prepares the given charm with a local: schema in its
// URL, and uploads it via the API server, returning the assigned
// charm URL. If the API server does not support charm uploads, an
// error satisfying params.IsCodeNotImplemented() is returned.
func (c *Client) AddLocalCharm(curl *charm.URL, ch charm.Charm) (*charm.URL, error) {
	if curl.Schema != "local" {
		return nil, fmt.Errorf("expected charm URL with local: schema, got %q", curl.String())
	}
	// Package the charm for uploading.
	var archive *os.File
	switch ch := ch.(type) {
	case *charm.Dir:
		var err error
		if archive, err = ioutil.TempFile("", "charm"); err != nil {
			return nil, fmt.Errorf("cannot create temp file: %v", err)
		}
		defer os.Remove(archive.Name())
		defer archive.Close()
		if err := ch.BundleTo(archive); err != nil {
			return nil, fmt.Errorf("cannot repackage charm: %v", err)
		}
		if _, err := archive.Seek(0, 0); err != nil {
			return nil, fmt.Errorf("cannot rewind packaged charm: %v", err)
		}
	case *charm.Bundle:
		var err error
		if archive, err = os.Open(ch.Path); err != nil {
			return nil, fmt.Errorf("cannot read charm archive: %v", err)
		}
		defer archive.Close()
	default:
		return nil, fmt.Errorf("unknown charm type %T", ch)
	}

	// Prepare the upload request.
	url := fmt.Sprintf("%s/charms?series=%s", c.st.serverRoot, curl.Series)
	req, err := http.NewRequest("POST", url, archive)
	if err != nil {
		return nil, fmt.Errorf("cannot create upload request: %v", err)
	}
	req.SetBasicAuth(c.st.tag, c.st.password)
	req.Header.Set("Content-Type", "application/zip")

	// Send the request.

	// BUG(dimitern) 2013-12-17 bug #1261780
	// Due to issues with go 1.1.2, fixed later, we cannot use a
	// regular TLS client with the CACert here, because we get "x509:
	// cannot validate certificate for 127.0.0.1 because it doesn't
	// contain any IP SANs". Once we use a later go version, this
	// should be changed to connect to the API server with a regular
	// HTTP+TLS enabled client, using the CACert (possily cached, like
	// the tag and password) passed in api.Open()'s info argument.
	resp, err := utils.GetNonValidatingHTTPClient().Do(req)
	if err != nil {
		return nil, fmt.Errorf("cannot upload charm: %v", err)
	}
	if resp.StatusCode == http.StatusMethodNotAllowed {
		// API server is 1.16 or older, so charm upload
		// is not supported; notify the client.
		return nil, &params.Error{
			Message: "charm upload is not supported by the API server",
			Code:    params.CodeNotImplemented,
		}
	}

	// Now parse the response & return.
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("cannot read charm upload response: %v", err)
	}
	defer resp.Body.Close()
	var jsonResponse params.CharmsResponse
	if err := json.Unmarshal(body, &jsonResponse); err != nil {
		return nil, fmt.Errorf("cannot unmarshal upload response: %v", err)
	}
	if jsonResponse.Error != "" {
		return nil, fmt.Errorf("error uploading charm: %v", jsonResponse.Error)
	}
	return charm.MustParseURL(jsonResponse.CharmURL), nil
}
Example #22
0
// AddLocalCharm prepares the given charm with a local: schema in its
// URL, and uploads it via the API server, returning the assigned
// charm URL. If the API server does not support charm uploads, an
// error satisfying params.IsCodeNotImplemented() is returned.
func (c *Client) AddLocalCharm(curl *charm.URL, ch charm.Charm) (*charm.URL, error) {
	if curl.Schema != "local" {
		return nil, errors.Errorf("expected charm URL with local: schema, got %q", curl.String())
	}
	// Package the charm for uploading.
	var archive *os.File
	switch ch := ch.(type) {
	case *charm.CharmDir:
		var err error
		if archive, err = ioutil.TempFile("", "charm"); err != nil {
			return nil, errors.Annotate(err, "cannot create temp file")
		}
		defer os.Remove(archive.Name())
		defer archive.Close()
		if err := ch.ArchiveTo(archive); err != nil {
			return nil, errors.Annotate(err, "cannot repackage charm")
		}
		if _, err := archive.Seek(0, 0); err != nil {
			return nil, errors.Annotate(err, "cannot rewind packaged charm")
		}
	case *charm.CharmArchive:
		var err error
		if archive, err = os.Open(ch.Path); err != nil {
			return nil, errors.Annotate(err, "cannot read charm archive")
		}
		defer archive.Close()
	default:
		return nil, errors.Errorf("unknown charm type %T", ch)
	}

	endPoint, err := c.apiEndpoint("charms", "series="+curl.Series)
	if err != nil {
		return nil, errors.Trace(err)
	}

	// wrap archive in a noopCloser to prevent the underlying transport closing
	// the request body. This is neccessary to prevent a data race on the underlying
	// *os.File as the http transport _may_ issue Close once the body is sent, or it
	// may not if there is an error.
	noop := &noopCloser{archive}
	req, err := http.NewRequest("POST", endPoint, noop)
	if err != nil {
		return nil, errors.Annotate(err, "cannot create upload request")
	}
	req.SetBasicAuth(c.st.tag, c.st.password)
	req.Header.Set("Content-Type", "application/zip")

	// Send the request.

	// BUG(dimitern) 2013-12-17 bug #1261780
	// Due to issues with go 1.1.2, fixed later, we cannot use a
	// regular TLS client with the CACert here, because we get "x509:
	// cannot validate certificate for 127.0.0.1 because it doesn't
	// contain any IP SANs". Once we use a later go version, this
	// should be changed to connect to the API server with a regular
	// HTTP+TLS enabled client, using the CACert (possily cached, like
	// the tag and password) passed in api.Open()'s info argument.
	resp, err := utils.GetNonValidatingHTTPClient().Do(req)
	if err != nil {
		return nil, errors.Annotate(err, "cannot upload charm")
	}
	defer resp.Body.Close()

	// Now parse the response & return.
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, errors.Annotate(err, "cannot read charm upload response")
	}
	if resp.StatusCode != http.StatusOK {
		return nil, errors.Errorf("charm upload failed: %v (%s)", resp.StatusCode, bytes.TrimSpace(body))
	}

	var jsonResponse params.CharmsResponse
	if err := json.Unmarshal(body, &jsonResponse); err != nil {
		return nil, errors.Annotate(err, "cannot unmarshal upload response")
	}
	if jsonResponse.Error != "" {
		return nil, errors.Errorf("error uploading charm: %v", jsonResponse.Error)
	}
	return charm.MustParseURL(jsonResponse.CharmURL), nil
}
Example #23
0
func (s *CmdSuite) TestHttpTransport(c *gc.C) {
	transport := http.DefaultTransport.(*http.Transport)
	c.Assert(transport.DisableKeepAlives, jc.IsTrue)
	client := utils.GetNonValidatingHTTPClient()
	c.Assert(client.Transport.(*http.Transport).DisableKeepAlives, jc.IsTrue)
}