func (*suite) TestParseBinary(c *gc.C) { parseBinaryTests := []struct { v string err string expect version.Binary }{{ v: "1.2.3-a-b", expect: binaryVersion(1, 2, 3, 0, "", "a", "b"), }, { v: "1.2.3.4-a-b", expect: binaryVersion(1, 2, 3, 4, "", "a", "b"), }, { v: "1.2-alpha3-a-b", expect: binaryVersion(1, 2, 3, 0, "alpha", "a", "b"), }, { v: "1.2-alpha3.4-a-b", expect: binaryVersion(1, 2, 3, 4, "alpha", "a", "b"), }, { v: "1.2.3", err: "invalid binary version.*", }, { v: "1.2-beta1", err: "invalid binary version.*", }, { v: "1.2.3--b", err: "invalid binary version.*", }, { v: "1.2.3-a-", err: "invalid binary version.*", }} for i, test := range parseBinaryTests { c.Logf("test 1: %d", i) got, err := version.ParseBinary(test.v) if test.err != "" { c.Assert(err, gc.ErrorMatches, test.err) } else { c.Assert(err, gc.IsNil) c.Assert(got, gc.Equals, test.expect) } } for i, test := range parseTests { c.Logf("test 2: %d", i) v := test.v + "-a-b" got, err := version.ParseBinary(v) expect := version.Binary{ Number: test.expect, Series: "a", Arch: "b", } if test.err != "" { c.Assert(err, gc.ErrorMatches, strings.Replace(test.err, "version", "binary version", 1)) } else { c.Assert(err, gc.IsNil) c.Assert(got, gc.Equals, expect) c.Check(got.IsDev(), gc.Equals, test.dev) } } }
// 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.Tools(version) if errors.IsNotFound(err) { // Tools could not be found in toolstorage, // so look for them in simplestreams, fetch // them and cache in toolstorage. 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 (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 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) return toolsList }
func (s *UpgradeJujuSuite) setUpEnvAndTools(c *gc.C, currentVersion string, agentVersion string, tools []string) { current := version.MustParseBinary(currentVersion) s.PatchValue(&version.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.UpdateEnvironConfig(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...) } }
// processGet handles a tools GET request. func (h *toolsDownloadHandler) processGet(r *http.Request) (*tools.Tools, utils.SSLHostnameVerification, error) { version, err := version.ParseBinary(r.URL.Query().Get(":version")) if err != nil { return nil, false, err } cfg, err := h.state.EnvironConfig() if err != nil { return nil, false, err } env, err := environs.New(cfg) if err != nil { return nil, false, err } filter := tools.Filter{ Number: version.Number, Series: version.Series, Arch: version.Arch, } tools, err := envtools.FindTools(env, version.Major, version.Minor, filter, false) if err != nil { return nil, false, errors.Annotate(err, "failed to find tools") } verify := utils.SSLHostnameVerification(cfg.SSLHostnameVerification()) return tools[0], verify, nil }
func (f mockToolsFinder) FindTools(number version.Number, series string, arch *string) (coretools.List, error) { v, err := version.ParseBinary(fmt.Sprintf("%s-%s-%s", number, series, version.Current.Arch)) if err != nil { return nil, err } if arch != nil { v.Arch = *arch } return coretools.List{&coretools.Tools{Version: v}}, nil }
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 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 // Major version must match specified value. if 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 (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 := version.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 != version.Current.Major || c.AgentVersion.Minor != version.Current.Minor) { return fmt.Errorf("requested agent version major.minor mismatch") } return cmd.CheckEmpty(args) }
// ReadList returns a List of the tools in store with the given major.minor version. // If minorVersion = -1, then only majorVersion is considered. // If store contains no such tools, it returns ErrNoMatches. func ReadList(stor storage.StorageReader, 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) } names, err := storage.List(stor, toolPrefix) if err != nil { return nil, err } var list coretools.List var foundAnyTools bool for _, name := range names { if !strings.HasPrefix(name, toolPrefix) || !strings.HasSuffix(name, toolSuffix) { continue } var t coretools.Tools vers := name[len(toolPrefix) : 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 // Major version must match specified value. if 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) } if len(list) == 0 { if foundAnyTools { return nil, coretools.ErrNoMatches } return nil, ErrNoTools } return list, nil }
// processPost handles a tools upload POST request after authentication. func (h *toolsUploadHandler) processPost(r *http.Request) (*tools.Tools, bool, error) { query := r.URL.Query() binaryVersionParam := query.Get("binaryVersion") if binaryVersionParam == "" { return nil, false, errors.New("expected binaryVersion argument") } toolsVersion, err := version.ParseBinary(binaryVersionParam) if err != nil { return nil, false, errors.Annotatef(err, "invalid tools version %q", binaryVersionParam) } logger.Debugf("request to upload tools %s", toolsVersion) // Make sure the content type is x-tar-gz. contentType := r.Header.Get("Content-Type") if contentType != "application/x-tar-gz" { return nil, false, errors.Errorf("expected Content-Type: application/x-tar-gz, got: %v", contentType) } return h.handleUpload(r.Body, toolsVersion) }
func (c *BootstrapCommand) Init(args []string) (err error) { if len(c.Series) > 0 && !c.UploadTools { return fmt.Errorf("--upload-series requires --upload-tools") } if len(c.seriesOld) > 0 && !c.UploadTools { return fmt.Errorf("--series requires --upload-tools") } if len(c.Series) > 0 && len(c.seriesOld) > 0 { return fmt.Errorf("--upload-series and --series can't be used together") } if c.AgentVersionParam != "" && c.UploadTools { return fmt.Errorf("--agent-version and --upload-tools can't be used together") } if c.AgentVersionParam != "" && c.NoAutoUpgrade { return fmt.Errorf("--agent-version and --no-auto-upgrade can't be used together") } // 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.NoAutoUpgrade { vers := version.Current.Number c.AgentVersion = &vers } else 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 != version.Current.Major || c.AgentVersion.Minor != version.Current.Minor) { return fmt.Errorf("requested agent version major.minor mismatch") } return cmd.CheckEmpty(args) }
// 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) }
// processPost handles a charm upload POST request after authentication. func (h *toolsHandler) processPost(r *http.Request) (*tools.Tools, bool, error) { query := r.URL.Query() binaryVersionParam := query.Get("binaryVersion") if binaryVersionParam == "" { return nil, false, fmt.Errorf("expected binaryVersion argument") } toolsVersion, err := version.ParseBinary(binaryVersionParam) if err != nil { return nil, false, fmt.Errorf("invalid tools version %q: %v", binaryVersionParam, err) } var fakeSeries []string seriesParam := query.Get("series") if seriesParam != "" { fakeSeries = strings.Split(seriesParam, ",") } logger.Debugf("request to upload tools %s for series %q", toolsVersion, seriesParam) // Make sure the content type is x-tar-gz. contentType := r.Header.Get("Content-Type") if contentType != "application/x-tar-gz" { return nil, false, fmt.Errorf("expected Content-Type: application/x-tar-gz, got: %v", contentType) } return h.handleUpload(r.Body, toolsVersion, fakeSeries...) }
// 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 }
func (s *UpgradeJujuSuite) TestUpgradeDryRun(c *gc.C) { tests := []DryRunTest{ { about: "dry run outputs and doesn't change anything when uploading tools", cmdArgs: []string{"--upload-tools", "--dry-run"}, tools: []string{"2.1.0-quantal-amd64", "2.1.2-quantal-i386", "2.1.3-quantal-amd64", "2.1-dev1-quantal-amd64", "2.2.3-quantal-amd64"}, currentVersion: "2.0.0-quantal-amd64", agentVersion: "2.0.0", expectedCmdOutput: `available tools: 2.1-dev1-quantal-amd64 2.1.0-quantal-amd64 2.1.2-quantal-i386 2.1.3-quantal-amd64 2.2.3-quantal-amd64 best version: 2.1.3 upgrade to this version by running juju upgrade-juju --version="2.1.3" `, }, { about: "dry run outputs and doesn't change anything", cmdArgs: []string{"--dry-run"}, tools: []string{"2.1.0-quantal-amd64", "2.1.2-quantal-i386", "2.1.3-quantal-amd64", "2.1-dev1-quantal-amd64", "2.2.3-quantal-amd64"}, currentVersion: "2.0.0-quantal-amd64", agentVersion: "2.0.0", expectedCmdOutput: `available tools: 2.1-dev1-quantal-amd64 2.1.0-quantal-amd64 2.1.2-quantal-i386 2.1.3-quantal-amd64 2.2.3-quantal-amd64 best version: 2.1.3 upgrade to this version by running juju upgrade-juju --version="2.1.3" `, }, { about: "dry run ignores unknown series", cmdArgs: []string{"--dry-run"}, tools: []string{"2.1.0-quantal-amd64", "2.1.2-quantal-i386", "2.1.3-quantal-amd64", "1.2.3-myawesomeseries-amd64"}, currentVersion: "2.0.0-quantal-amd64", agentVersion: "2.0.0", expectedCmdOutput: `available tools: 2.1.0-quantal-amd64 2.1.2-quantal-i386 2.1.3-quantal-amd64 best version: 2.1.3 upgrade to this version by running juju upgrade-juju --version="2.1.3" `, }, } for i, test := range tests { c.Logf("\ntest %d: %s", i, test.about) s.Reset(c) tools.DefaultBaseURL = "" s.PatchValue(&version.Current, version.MustParseBinary(test.currentVersion)) com := &UpgradeJujuCommand{} err := coretesting.InitCommand(envcmd.Wrap(com), test.cmdArgs) c.Assert(err, jc.ErrorIsNil) toolsDir := c.MkDir() updateAttrs := map[string]interface{}{ "agent-version": test.agentVersion, "agent-metadata-url": "file://" + toolsDir + "/tools", } err = s.State.UpdateEnvironConfig(updateAttrs, nil, nil) c.Assert(err, jc.ErrorIsNil) versions := make([]version.Binary, len(test.tools)) for i, v := range test.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...) } ctx := coretesting.Context(c) err = com.Run(ctx) c.Assert(err, jc.ErrorIsNil) // Check agent version doesn't change cfg, err := s.State.EnvironConfig() c.Assert(err, jc.ErrorIsNil) agentVer, ok := cfg.AgentVersion() c.Assert(ok, jc.IsTrue) c.Assert(agentVer, gc.Equals, version.MustParse(test.agentVersion)) output := coretesting.Stderr(ctx) c.Assert(output, gc.Equals, test.expectedCmdOutput) } }