func (*suite) TestParseBinary(c *gc.C) { parseBinaryTests := []struct { v string err string expect version.Binary }{{ v: "1.2.3-trusty-amd64", expect: binaryVersion(1, 2, 3, 0, "", "trusty", "amd64"), }, { v: "1.2.3.4-trusty-amd64", expect: binaryVersion(1, 2, 3, 4, "", "trusty", "amd64"), }, { v: "1.2-alpha3-trusty-amd64", expect: binaryVersion(1, 2, 3, 0, "alpha", "trusty", "amd64"), }, { v: "1.2-alpha3.4-trusty-amd64", expect: binaryVersion(1, 2, 3, 4, "alpha", "trusty", "amd64"), }, { v: "1.2.3", err: "invalid binary version.*", }, { v: "1.2-beta1", err: "invalid binary version.*", }, { v: "1.2.3--amd64", err: "invalid binary version.*", }, { v: "1.2.3-trusty-", err: "invalid binary version.*", }} for i, test := range parseBinaryTests { c.Logf("first test, %d: %q", i, test.v) got, err := version.ParseBinary(test.v) if test.err != "" { c.Assert(err, gc.ErrorMatches, test.err) } else { c.Assert(err, jc.ErrorIsNil) c.Assert(got, gc.Equals, test.expect) } } for i, test := range parseTests { c.Logf("second test, %d: %q", i, test.v) v := test.v + "-trusty-amd64" got, err := version.ParseBinary(v) expect := version.Binary{ Number: test.expect, Series: "trusty", Arch: "amd64", } if test.err != "" { c.Assert(err, gc.ErrorMatches, strings.Replace(test.err, "version", "binary version", 1)) } else { c.Assert(err, jc.ErrorIsNil) c.Assert(got, gc.Equals, expect) } } }
func (c *bootstrapCommand) Init(args []string) (err error) { if c.showClouds && c.showRegionsForCloud != "" { return errors.New("--clouds and --regions can't be used together") } if c.showClouds { return cmd.CheckEmpty(args) } if c.showRegionsForCloud != "" { return cmd.CheckEmpty(args) } if c.AgentVersionParam != "" && c.BuildAgent { return errors.New("--agent-version and --build-agent can't be used together") } if c.BootstrapSeries != "" && !charm.IsValidSeries(c.BootstrapSeries) { return errors.NotValidf("series %q", c.BootstrapSeries) } // Parse the placement directive. Bootstrap currently only // supports provider-specific placement directives. if c.Placement != "" { _, err = instance.ParsePlacement(c.Placement) if err != instance.ErrPlacementScopeMissing { // We only support unscoped placement directives for bootstrap. return errors.Errorf("unsupported bootstrap placement directive %q", c.Placement) } } if !c.AutoUpgrade { // With no auto upgrade chosen, we default to the version matching the bootstrap client. vers := jujuversion.Current c.AgentVersion = &vers } if c.AgentVersionParam != "" { if vers, err := version.ParseBinary(c.AgentVersionParam); err == nil { c.AgentVersion = &vers.Number } else if vers, err := version.Parse(c.AgentVersionParam); err == nil { c.AgentVersion = &vers } else { return err } } if c.AgentVersion != nil && (c.AgentVersion.Major != jujuversion.Current.Major || c.AgentVersion.Minor != jujuversion.Current.Minor) { return errors.New("requested agent version major.minor mismatch") } switch len(args) { case 0: // no args or flags, go interactive. c.interactive = true return nil } c.Cloud = args[0] if i := strings.IndexRune(c.Cloud, '/'); i > 0 { c.Cloud, c.Region = c.Cloud[:i], c.Cloud[i+1:] } if len(args) > 1 { c.controllerName = args[1] return cmd.CheckEmpty(args[2:]) } return nil }
func (s *simplestreamsSuite) TestWriteMetadataNoFetch(c *gc.C) { toolsList := coretools.List{ { Version: version.MustParseBinary("1.2.3-precise-amd64"), Size: 123, SHA256: "abcd", }, { Version: version.MustParseBinary("2.0.1-raring-amd64"), Size: 456, SHA256: "xyz", }, } expected := toolsList // Add tools with an unknown series. Do not add an entry in the // expected list as these tools should be ignored. vers, err := version.ParseBinary("3.2.1-xuanhuaceratops-amd64") c.Assert(err, jc.Satisfies, series.IsUnknownOSForSeriesError) toolsList = append(toolsList, &coretools.Tools{ Version: vers, Size: 456, SHA256: "wqe", }) dir := c.MkDir() writer, err := filestorage.NewFileStorageWriter(dir) c.Assert(err, jc.ErrorIsNil) err = tools.MergeAndWriteMetadata(writer, "proposed", "proposed", toolsList, tools.DoNotWriteMirrors) c.Assert(err, jc.ErrorIsNil) metadata := toolstesting.ParseMetadataFromDir(c, dir, "proposed", false) assertMetadataMatches(c, dir, "proposed", expected, metadata) }
func (s *UpgradeJujuSuite) setUpEnvAndTools(c *gc.C, currentVersion string, agentVersion string, tools []string) { current := version.MustParseBinary(currentVersion) s.PatchValue(&jujuversion.Current, current.Number) s.PatchValue(&arch.HostArch, func() string { return current.Arch }) s.PatchValue(&series.HostSeries, func() string { return current.Series }) toolsDir := c.MkDir() updateAttrs := map[string]interface{}{ "agent-version": agentVersion, "agent-metadata-url": "file://" + toolsDir + "/tools", } err := s.State.UpdateModelConfig(updateAttrs, nil, nil) c.Assert(err, jc.ErrorIsNil) versions := make([]version.Binary, len(tools)) for i, v := range tools { versions[i], err = version.ParseBinary(v) if err != nil { c.Assert(err, jc.Satisfies, series.IsUnknownOSForSeriesError) } } if len(versions) > 0 { stor, err := filestorage.NewFileStorageWriter(toolsDir) c.Assert(err, jc.ErrorIsNil) envtesting.MustUploadFakeToolsVersions(stor, s.Environ.Config().AgentStream(), versions...) } }
func importAgentToolsV1(source map[string]interface{}) (*agentTools, error) { fields := schema.Fields{ "tools-version": schema.String(), "url": schema.String(), "sha256": schema.String(), "size": schema.Int(), } checker := schema.FieldMap(fields, nil) // no defaults coerced, err := checker.Coerce(source, nil) if err != nil { return nil, errors.Annotatef(err, "agentTools v1 schema check failed") } valid := coerced.(map[string]interface{}) // From here we know that the map returned from the schema coercion // contains fields of the right type. verString := valid["tools-version"].(string) toolsVersion, err := version.ParseBinary(verString) if err != nil { return nil, errors.Annotatef(err, "agentTools tools-version") } return &agentTools{ Version_: 1, ToolsVersion_: toolsVersion, URL_: valid["url"].(string), SHA256_: valid["sha256"].(string), Size_: valid["size"].(int64), }, nil }
func makeTools(c *gc.C, metadataDir, stream string, versionStrings []string, withCheckSum bool) coretools.List { toolsDir := filepath.Join(metadataDir, storage.BaseToolsPath, stream) c.Assert(os.MkdirAll(toolsDir, 0755), gc.IsNil) var toolsList coretools.List for _, versionString := range versionStrings { binary, err := version.ParseBinary(versionString) if err != nil { c.Assert(err, jc.Satisfies, series.IsUnknownOSForSeriesError) } path := filepath.Join(toolsDir, fmt.Sprintf("juju-%s.tgz", binary)) data := binary.String() err = ioutil.WriteFile(path, []byte(data), 0644) c.Assert(err, jc.ErrorIsNil) tool := &coretools.Tools{ Version: binary, URL: path, } if withCheckSum { tool.Size, tool.SHA256 = SHA256sum(c, path) } toolsList = append(toolsList, tool) } // Write the tools metadata. stor, err := filestorage.NewFileStorageWriter(metadataDir) c.Assert(err, jc.ErrorIsNil) err = tools.MergeAndWriteMetadata(stor, stream, stream, toolsList, false) c.Assert(err, jc.ErrorIsNil) // Sign metadata err = envtesting.SignTestTools(stor) c.Assert(err, jc.ErrorIsNil) return toolsList }
// processGet handles a tools GET request. func (h *toolsDownloadHandler) processGet(r *http.Request, st *state.State) ([]byte, error) { version, err := version.ParseBinary(r.URL.Query().Get(":version")) if err != nil { return nil, errors.Annotate(err, "error parsing version") } storage, err := st.ToolsStorage() if err != nil { return nil, errors.Annotate(err, "error getting tools storage") } defer storage.Close() _, reader, err := storage.Open(version.String()) if errors.IsNotFound(err) { // Tools could not be found in tools storage, // so look for them in simplestreams, fetch // them and cache in tools storage. logger.Infof("%v tools not found locally, fetching", version) reader, err = h.fetchAndCacheTools(version, storage, st) if err != nil { err = errors.Annotate(err, "error fetching tools") } } if err != nil { return nil, err } defer reader.Close() data, err := ioutil.ReadAll(reader) if err != nil { return nil, errors.Annotate(err, "failed to read tools tarball") } return data, nil }
func (c *bootstrapCommand) Init(args []string) (err error) { if c.AgentVersionParam != "" && c.UploadTools { return fmt.Errorf("--agent-version and --upload-tools can't be used together") } if c.BootstrapSeries != "" && !charm.IsValidSeries(c.BootstrapSeries) { return errors.NotValidf("series %q", c.BootstrapSeries) } if c.BootstrapImage != "" { if c.BootstrapSeries == "" { return errors.Errorf("--bootstrap-image must be used with --bootstrap-series") } cons, err := constraints.Merge(c.Constraints, c.BootstrapConstraints) if err != nil { return errors.Trace(err) } if !cons.HasArch() { return errors.Errorf("--bootstrap-image must be used with --bootstrap-constraints, specifying architecture") } } // Parse the placement directive. Bootstrap currently only // supports provider-specific placement directives. if c.Placement != "" { _, err = instance.ParsePlacement(c.Placement) if err != instance.ErrPlacementScopeMissing { // We only support unscoped placement directives for bootstrap. return fmt.Errorf("unsupported bootstrap placement directive %q", c.Placement) } } if !c.AutoUpgrade { // With no auto upgrade chosen, we default to the version matching the bootstrap client. vers := jujuversion.Current c.AgentVersion = &vers } if c.AgentVersionParam != "" { if vers, err := version.ParseBinary(c.AgentVersionParam); err == nil { c.AgentVersion = &vers.Number } else if vers, err := version.Parse(c.AgentVersionParam); err == nil { c.AgentVersion = &vers } else { return err } } if c.AgentVersion != nil && (c.AgentVersion.Major != jujuversion.Current.Major || c.AgentVersion.Minor != jujuversion.Current.Minor) { return fmt.Errorf("requested agent version major.minor mismatch") } // The user must specify two positional arguments: the controller name, // and the cloud name (optionally with region specified). if len(args) < 2 { return errors.New("controller name and cloud name are required") } c.controllerName = bootstrappedControllerName(args[0]) c.Cloud = args[1] if i := strings.IndexRune(c.Cloud, '/'); i > 0 { c.Cloud, c.Region = c.Cloud[:i], c.Cloud[i+1:] } return cmd.CheckEmpty(args[2:]) }
func (f mockToolsFinder) FindTools(number version.Number, series string, a string) (coretools.List, error) { v, err := version.ParseBinary(fmt.Sprintf("%s-%s-%s", number, series, arch.HostArch())) if err != nil { return nil, err } if a != "" { v.Arch = a } return coretools.List{&coretools.Tools{Version: v}}, nil }
// ReadList returns a List of the tools in store with the given major.minor version. // If minorVersion = -1, then only majorVersion is considered. // If majorVersion is -1, then all tools tarballs are used. // If store contains no such tools, it returns ErrNoMatches. func ReadList(stor storage.StorageReader, toolsDir string, majorVersion, minorVersion int) (coretools.List, error) { if minorVersion >= 0 { logger.Debugf("reading v%d.%d tools", majorVersion, minorVersion) } else { logger.Debugf("reading v%d.* tools", majorVersion) } storagePrefix := storagePrefix(toolsDir) names, err := storage.List(stor, storagePrefix) if err != nil { return nil, err } var list coretools.List var foundAnyTools bool for _, name := range names { name = filepath.ToSlash(name) if !strings.HasPrefix(name, storagePrefix) || !strings.HasSuffix(name, toolSuffix) { continue } var t coretools.Tools vers := name[len(storagePrefix) : len(name)-len(toolSuffix)] if t.Version, err = version.ParseBinary(vers); err != nil { logger.Debugf("failed to parse version %q: %v", vers, err) continue } foundAnyTools = true // If specified major version value supplied, major version must match. if majorVersion >= 0 && t.Version.Major != majorVersion { continue } // If specified minor version value supplied, minor version must match. if minorVersion >= 0 && t.Version.Minor != minorVersion { continue } logger.Debugf("found %s", vers) if t.URL, err = stor.URL(name); err != nil { return nil, err } list = append(list, &t) // Older versions of Juju only know about ppc64, so add metadata for that arch. if t.Version.Arch == arch.PPC64EL { legacyPPC64Tools := t legacyPPC64Tools.Version.Arch = arch.LEGACY_PPC64 list = append(list, &legacyPPC64Tools) } } if len(list) == 0 { if foundAnyTools { return nil, coretools.ErrNoMatches } return nil, ErrNoTools } return list, nil }
func (m main) juju1xVersion() (ver string, exists bool) { out, err := m.execCommand(juju1xCmdName, "version").Output() if err == exec.ErrNotFound { return "", false } ver = "1.x" if err == nil { v := strings.TrimSpace(string(out)) // parse so we can drop the series and arch bin, err := version.ParseBinary(v) if err == nil { ver = bin.Number.String() } } return ver, true }
func warnJuju1x() { ver := "1.x" out, err := execCommand(juju1xCmdName, "version").Output() if err == nil { v := strings.TrimSpace(string(out)) // parse so we can drop the series and arch bin, err := version.ParseBinary(v) if err == nil { ver = bin.Number.String() } } fmt.Fprintf(os.Stderr, ` Welcome to Juju %s. If you meant to use Juju %s you can continue using it with the command %s e.g. '%s switch'. See https://jujucharms.com/docs/stable/introducing-2 for more details. `[1:], jujuversion.Current, ver, juju1xCmdName, juju1xCmdName) }
func getVersionFromJujud(dir string) (version.Binary, error) { path := filepath.Join(dir, names.Jujud) cmd := execCommand(path, "version") var stdout, stderr bytes.Buffer cmd.Stdout = &stdout cmd.Stderr = &stderr if err := cmd.Run(); err != nil { return version.Binary{}, errors.Errorf("cannot get version from %q: %v; %s", path, err, stderr.String()+stdout.String()) } tvs := strings.TrimSpace(stdout.String()) tvers, err := version.ParseBinary(tvs) if err != nil { return version.Binary{}, errors.Errorf("invalid version %q printed by jujud", tvs) } return tvers, nil }
// processPost handles a tools upload POST request after authentication. func (h *toolsUploadHandler) processPost(r *http.Request, st *state.State) (*tools.Tools, error) { query := r.URL.Query() binaryVersionParam := query.Get("binaryVersion") if binaryVersionParam == "" { return nil, errors.BadRequestf("expected binaryVersion argument") } toolsVersion, err := version.ParseBinary(binaryVersionParam) if err != nil { return nil, errors.NewBadRequest(err, fmt.Sprintf("invalid tools version %q", binaryVersionParam)) } // Make sure the content type is x-tar-gz. contentType := r.Header.Get("Content-Type") if contentType != "application/x-tar-gz" { return nil, errors.BadRequestf("expected Content-Type: application/x-tar-gz, got: %v", contentType) } // Get the server root, so we know how to form the URL in the Tools returned. serverRoot, err := h.getServerRoot(r, query, st) if err != nil { return nil, errors.NewBadRequest(err, "cannot to determine server root") } // We'll clone the tools for each additional series specified. var cloneSeries []string if seriesParam := query.Get("series"); seriesParam != "" { cloneSeries = strings.Split(seriesParam, ",") } logger.Debugf("request to upload tools: %s", toolsVersion) logger.Debugf("additional series: %s", cloneSeries) toolsVersions := []version.Binary{toolsVersion} for _, series := range cloneSeries { if series != toolsVersion.Series { v := toolsVersion v.Series = series toolsVersions = append(toolsVersions, v) } } return h.handleUpload(r.Body, toolsVersions, serverRoot, st) }
// matchingStorageTools returns a coretools.List, with an entry for each // metadata entry in the tools storage that matches the given parameters. func (f *ToolsFinder) matchingStorageTools(args params.FindToolsParams) (coretools.List, error) { storage, err := f.toolsStorageGetter.ToolsStorage() if err != nil { return nil, err } defer storage.Close() allMetadata, err := storage.AllMetadata() if err != nil { return nil, err } list := make(coretools.List, len(allMetadata)) for i, m := range allMetadata { vers, err := version.ParseBinary(m.Version) if err != nil { return nil, errors.Annotatef(err, "unexpectedly bad version %q in tools storage", m.Version) } list[i] = &coretools.Tools{ Version: vers, Size: m.Size, SHA256: m.SHA256, } } list, err = list.Match(toolsFilter(args)) if err != nil { return nil, err } var matching coretools.List for _, tools := range list { if args.MajorVersion != -1 && tools.Version.Major != args.MajorVersion { continue } if args.MinorVersion != -1 && tools.Version.Minor != args.MinorVersion { continue } matching = append(matching, tools) } if len(matching) == 0 { return nil, coretools.ErrNoMatches } return matching, nil }
// Export returns a serialized representation of the model associated // with the API connection. The charms used by the model are also // returned. func (c *Client) Export() (migration.SerializedModel, error) { var serialized params.SerializedModel err := c.caller.FacadeCall("Export", nil, &serialized) if err != nil { return migration.SerializedModel{}, err } // Convert tools info to output map. tools := make(map[version.Binary]string) for _, toolsInfo := range serialized.Tools { v, err := version.ParseBinary(toolsInfo.Version) if err != nil { return migration.SerializedModel{}, errors.Annotate(err, "error parsing tools version") } tools[v] = toolsInfo.URI } return migration.SerializedModel{ Bytes: serialized.Bytes, Charms: serialized.Charms, Tools: tools, }, nil }
// BundleTools bundles all the current juju tools in gzipped tar // format to the given writer. // If forceVersion is not nil, a FORCE-VERSION file is included in // the tools bundle so it will lie about its current version number. func bundleTools(w io.Writer, forceVersion *version.Number) (tvers version.Binary, sha256Hash string, err error) { dir, err := ioutil.TempDir("", "juju-tools") if err != nil { return version.Binary{}, "", err } defer os.RemoveAll(dir) if err := copyExistingJujud(dir); err != nil { logger.Debugf("copy existing failed: %v", err) if err := buildJujud(dir); err != nil { return version.Binary{}, "", err } } if forceVersion != nil { logger.Debugf("forcing version to %s", forceVersion) if err := ioutil.WriteFile(filepath.Join(dir, "FORCE-VERSION"), []byte(forceVersion.String()), 0666); err != nil { return version.Binary{}, "", err } } cmd := exec.Command(filepath.Join(dir, names.Jujud), "version") out, err := cmd.CombinedOutput() if err != nil { return version.Binary{}, "", fmt.Errorf("cannot get version from %q: %v; %s", cmd.Args[0], err, out) } tvs := strings.TrimSpace(string(out)) tvers, err = version.ParseBinary(tvs) if err != nil { return version.Binary{}, "", fmt.Errorf("invalid version %q printed by jujud", tvs) } sha256hash, err := archiveAndSHA256(w, dir) if err != nil { return version.Binary{}, "", err } return tvers, sha256hash, err }
// Run implements Command.Run. func (c *uploadToolsCommand) Run(ctx *cmd.Context) error { conn, err := c.NewAPIRoot() if err != nil { return errors.Annotate(err, "connecting to Juju") } defer conn.Close() client := conn.Client() versions := make([]version.Binary, len(c.archives)) for i, archive := range c.archives { basename := filepath.Base(archive) if !strings.HasPrefix(basename, toolsPrefix) || !strings.HasSuffix(basename, toolsSuffix) { return errors.NotValidf("tools archive %q", archive) } versionString := basename[len(toolsPrefix) : len(basename)-len(toolsSuffix)] binary, err := version.ParseBinary(versionString) if err != nil { return errors.NotValidf("tools archive %q", archive) } versions[i] = binary } for i, archive := range c.archives { ctx.Infof("uploading %q", archive) r, err := os.Open(archive) if err != nil { return err } _, err = client.UploadTools(r, versions[i]) r.Close() if err != nil { return errors.Annotate(err, "uploading tools") } } return nil }