func (s *supportedSeriesSuite) TestOSSupportedSeries(c *gc.C) { version.SetSeriesVersions(map[string]string{ "trusty": "14.04", "utopic": "14.10", "win7": "win7", "win81": "win81", }) series := version.OSSupportedSeries(version.Ubuntu) c.Assert(series, jc.SameContents, []string{"trusty", "utopic"}) series = version.OSSupportedSeries(version.Windows) c.Assert(series, jc.SameContents, []string{"win7", "win81"}) }
func (s *toolsSuite) TestUploadSeriesExpanded(c *gc.C) { // Make some fake tools. expectedTools, vers, toolPath := s.setupToolsForUpload(c) // Now try uploading them. The "series" parameter is accepted // but ignored; the API server will expand the tools for all // supported series. params := "?binaryVersion=" + vers.String() + "&series=nonsense" resp, err := s.uploadRequest(c, s.toolsURI(c, params), true, toolPath) c.Assert(err, gc.IsNil) // Check the response. stor := s.Environ.Storage() toolsURL, err := stor.URL(tools.StorageName(vers)) c.Assert(err, gc.IsNil) expectedTools[0].URL = toolsURL s.assertUploadResponse(c, resp, expectedTools[0]) // Check the contents. for _, series := range version.OSSupportedSeries(version.Ubuntu) { toolsVersion := vers toolsVersion.Series = series r, err := stor.Get(tools.StorageName(toolsVersion)) c.Assert(err, gc.IsNil) uploadedData, err := ioutil.ReadAll(r) c.Assert(err, gc.IsNil) expectedData, err := ioutil.ReadFile(toolPath) c.Assert(err, gc.IsNil) c.Assert(uploadedData, gc.DeepEquals, expectedData) } }
// populateTools stores uploaded tools in provider storage // and updates the tools metadata. // // TODO(axw) store tools in gridfs, catalogue in state. func (c *BootstrapCommand) populateTools(env environs.Environ) error { agentConfig := c.CurrentConfig() dataDir := agentConfig.DataDir() tools, err := agenttools.ReadTools(dataDir, version.Current) if err != nil { return err } if !strings.HasPrefix(tools.URL, "file://") { // Nothing to do since the tools were not uploaded. return nil } // This is a hack: providers using localstorage (local, manual) // can't use storage during bootstrap as the localstorage worker // isn't running. Use filestorage instead. var stor storage.Storage storageDir := agentConfig.Value(agent.StorageDir) if storageDir != "" { stor, err = filestorage.NewFileStorageWriter(storageDir) if err != nil { return err } } else { stor = env.Storage() } // Create a temporary directory to contain source and cloned tools. tempDir, err := ioutil.TempDir("", "juju-sync-tools") if err != nil { return err } defer os.RemoveAll(tempDir) destTools := filepath.Join(tempDir, filepath.FromSlash(envtools.StorageName(tools.Version))) if err := os.MkdirAll(filepath.Dir(destTools), 0700); err != nil { return err } srcTools := filepath.Join( agenttools.SharedToolsDir(dataDir, version.Current), "tools.tar.gz", ) if err := utils.CopyFile(destTools, srcTools); err != nil { return err } // Until we catalogue tools in state, we clone the tools // for each of the supported series of the same OS. otherSeries := version.OSSupportedSeries(version.Current.OS) _, err = sync.SyncBuiltTools(stor, &sync.BuiltTools{ Version: tools.Version, Dir: tempDir, StorageName: envtools.StorageName(tools.Version), Sha256Hash: tools.SHA256, Size: tools.Size, }, otherSeries...) return err }
// 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 }
// uploadToStorage uploads the tools from the specified directory to environment storage. func (h *toolsUploadHandler) uploadToStorage(uploadedTools *tools.Tools, toolsDir, toolsFilename string) (*tools.Tools, bool, error) { // SyncTools requires simplestreams metadata to find the tools to upload. stor, err := filestorage.NewFileStorageWriter(toolsDir) if err != nil { return nil, false, errors.Annotate(err, "cannot create metadata storage") } // Generate metadata for each series of the same OS as the uploaded tools. // The URL for each fake series record points to the same tools tarball. allToolsMetadata := []*tools.Tools{uploadedTools} osSeries := version.OSSupportedSeries(uploadedTools.Version.OS) for _, series := range osSeries { vers := uploadedTools.Version vers.Series = series allToolsMetadata = append(allToolsMetadata, &tools.Tools{ Version: vers, URL: uploadedTools.URL, Size: uploadedTools.Size, SHA256: uploadedTools.SHA256, }) } err = envtools.MergeAndWriteMetadata(stor, allToolsMetadata, false) if err != nil { return nil, false, errors.Annotate(err, "cannot get environment config") } // Create the environment so we can get the storage to which we upload the tools. envConfig, err := h.state.EnvironConfig() if err != nil { return nil, false, errors.Annotate(err, "cannot get environment config") } env, err := environs.New(envConfig) if err != nil { return nil, false, errors.Annotate(err, "cannot access environment") } // Now perform the upload. builtTools := &sync.BuiltTools{ Version: uploadedTools.Version, Dir: toolsDir, StorageName: toolsFilename, Size: uploadedTools.Size, Sha256Hash: uploadedTools.SHA256, } uploadedTools, err = sync.SyncBuiltTools(env.Storage(), builtTools, osSeries...) if err != nil { return nil, false, err } return uploadedTools, !envConfig.SSLHostnameVerification(), nil }
// populateTools stores uploaded tools in provider storage // and updates the tools metadata. func (c *BootstrapCommand) populateTools(st *state.State, env environs.Environ) error { agentConfig := c.CurrentConfig() dataDir := agentConfig.DataDir() tools, err := agenttools.ReadTools(dataDir, version.Current) if err != nil { return err } data, err := ioutil.ReadFile(filepath.Join( agenttools.SharedToolsDir(dataDir, version.Current), "tools.tar.gz", )) if err != nil { return err } storage, err := st.ToolsStorage() if err != nil { return err } defer storage.Close() var toolsVersions []version.Binary if strings.HasPrefix(tools.URL, "file://") { // Tools were uploaded: clone for each series of the same OS. osSeries := version.OSSupportedSeries(tools.Version.OS) for _, series := range osSeries { toolsVersion := tools.Version toolsVersion.Series = series toolsVersions = append(toolsVersions, toolsVersion) } } else { // Tools were downloaded from an external source: don't clone. toolsVersions = []version.Binary{tools.Version} } for _, toolsVersion := range toolsVersions { metadata := toolstorage.Metadata{ Version: toolsVersion, Size: tools.Size, SHA256: tools.SHA256, } logger.Debugf("Adding tools: %v", toolsVersion) if err := storage.AddTools(bytes.NewReader(data), metadata); err != nil { return err } } return nil }
// uploadTools compiles jujud from $GOPATH and uploads it into the supplied // storage. If no version has been explicitly chosen, the version number // reported by the built tools will be based on the client version number. // In any case, the version number reported will have a build component higher // than that of any otherwise-matching available envtools. // uploadTools resets the chosen version and replaces the available tools // with the ones just uploaded. func (context *upgradeContext) uploadTools() (err error) { // TODO(fwereade): this is kinda crack: we should not assume that // version.Current matches whatever source happens to be built. The // ideal would be: // 1) compile jujud from $GOPATH into some build dir // 2) get actual version with `jujud version` // 3) check actual version for compatibility with CLI tools // 4) generate unique build version with reference to available tools // 5) force-version that unique version into the dir directly // 6) archive and upload the build dir // ...but there's no way we have time for that now. In the meantime, // considering the use cases, this should work well enough; but it // won't detect an incompatible major-version change, which is a shame. if context.chosen == version.Zero { context.chosen = context.client } context.chosen = uploadVersion(context.chosen, context.tools) builtTools, err := sync.BuildToolsTarball(&context.chosen, "upgrade") if err != nil { return err } defer os.RemoveAll(builtTools.Dir) var uploaded *coretools.Tools toolsPath := path.Join(builtTools.Dir, builtTools.StorageName) logger.Infof("uploading tools %v (%dkB) to Juju state server", builtTools.Version, (builtTools.Size+512)/1024) f, err := os.Open(toolsPath) if err != nil { return err } defer f.Close() additionalSeries := version.OSSupportedSeries(builtTools.Version.OS) uploaded, err = context.apiClient.UploadTools(f, builtTools.Version, additionalSeries...) if err != nil { return err } context.tools = coretools.List{uploaded} return nil }