func (em *EnvironmentManagerAPI) checkVersion(cfg map[string]interface{}) error { // If there is no agent-version specified, use the current version. // otherwise we need to check for tools value, found := cfg["agent-version"] if !found { cfg["agent-version"] = version.Current.Number.String() return nil } valuestr, ok := value.(string) if !ok { return errors.Errorf("agent-version must be a string but has type '%T'", value) } num, err := version.Parse(valuestr) if err != nil { return errors.Trace(err) } if comp := num.Compare(version.Current.Number); comp > 0 { return errors.Errorf("agent-version cannot be greater than the server: %s", version.Current.Number) } else if comp < 0 { // Look to see if we have tools available for that version. // Obviously if the version is the same, we have the tools available. list, err := em.toolsFinder.FindTools(params.FindToolsParams{ Number: num, }) if err != nil { return errors.Trace(err) } logger.Tracef("found tools: %#v", list) if len(list.List) == 0 { return errors.Errorf("no tools found for version %s", num) } } return nil }
func (c *UpgradeJujuCommand) Init(args []string) error { if c.vers != "" { vers, err := version.Parse(c.vers) if err != nil { return err } if vers.Major != version.Current.Major { return fmt.Errorf("cannot upgrade to version incompatible with CLI") } if c.UploadTools && vers.Build != 0 { // TODO(fwereade): when we start taking versions from actual built // code, we should disable --version when used with --upload-tools. // For now, it's the only way to experiment with version upgrade // behaviour live, so the only restriction is that Build cannot // be used (because its value needs to be chosen internally so as // not to collide with existing tools). return fmt.Errorf("cannot specify build number when uploading tools") } c.Version = vers } if len(c.Series) > 0 && !c.UploadTools { return fmt.Errorf("--series requires --upload-tools") } return cmd.CheckEmpty(args) }
func (*suite) TestCompare(c *gc.C) { cmpTests := []struct { v1, v2 string compare int }{ {"1.0.0", "1.0.0", 0}, {"01.0.0", "1.0.0", 0}, {"10.0.0", "9.0.0", 1}, {"1.0.0", "1.0.1", -1}, {"1.0.1", "1.0.0", 1}, {"1.0.0", "1.1.0", -1}, {"1.1.0", "1.0.0", 1}, {"1.0.0", "2.0.0", -1}, {"1.2-alpha1", "1.2.0", -1}, {"1.2-alpha2", "1.2-alpha1", 1}, {"1.2-alpha2.1", "1.2-alpha2", 1}, {"1.2-alpha2.2", "1.2-alpha2.1", 1}, {"1.2-beta1", "1.2-alpha1", 1}, {"1.2-beta1", "1.2-alpha2.1", 1}, {"1.2-beta1", "1.2.0", -1}, {"1.2.1", "1.2.0", 1}, {"2.0.0", "1.0.0", 1}, {"2.0.0.0", "2.0.0", 0}, {"2.0.0.0", "2.0.0.0", 0}, {"2.0.0.1", "2.0.0.0", 1}, {"2.0.1.10", "2.0.0.0", 1}, {"2.0-_0", "2.0-00", 1}, {"2.0-_0", "2.0.0", -1}, {"2.0-_0", "2.0-alpha1.0", -1}, {"2.0-_0", "1.999.0", 1}, } for i, test := range cmpTests { c.Logf("test %d", i) v1, err := version.Parse(test.v1) c.Assert(err, jc.ErrorIsNil) v2, err := version.Parse(test.v2) c.Assert(err, jc.ErrorIsNil) compare := v1.Compare(v2) c.Check(compare, gc.Equals, test.compare) // Check that reversing the operands has // the expected result. compare = v2.Compare(v1) c.Check(compare, gc.Equals, -test.compare) } }
// AgentVersion returns the proposed version number for the agent tools, // and whether it has been set. Once an environment is bootstrapped, this // must always be valid. func (c *Config) AgentVersion() (version.Number, bool) { if v, ok := c.defined["agent-version"].(string); ok { n, err := version.Parse(v) if err != nil { panic(err) // We should have checked it earlier. } return n, true } return version.Zero, false }
// ValidateToolsMetadata attempts to load tools metadata for the specified cloud attributes and returns // any tools versions found, or an error if the metadata could not be loaded. func ValidateToolsMetadata(params *ToolsMetadataLookupParams) ([]string, *simplestreams.ResolveInfo, error) { if len(params.Architectures) == 0 { return nil, nil, fmt.Errorf("required parameter arches not specified") } if len(params.Sources) == 0 { return nil, nil, fmt.Errorf("required parameter sources not specified") } if params.Version == "" && params.Major == 0 { params.Version = version.Current.Number.String() } var toolsConstraint *ToolsConstraint if params.Version == "" { toolsConstraint = NewGeneralToolsConstraint(params.Major, params.Minor, simplestreams.LookupParams{ CloudSpec: simplestreams.CloudSpec{ Region: params.Region, Endpoint: params.Endpoint, }, Stream: params.Stream, Series: []string{params.Series}, Arches: params.Architectures, }) } else { versNum, err := version.Parse(params.Version) if err != nil { return nil, nil, err } toolsConstraint = NewVersionedToolsConstraint(versNum, simplestreams.LookupParams{ CloudSpec: simplestreams.CloudSpec{ Region: params.Region, Endpoint: params.Endpoint, }, Stream: params.Stream, Series: []string{params.Series}, Arches: params.Architectures, }) } matchingTools, resolveInfo, err := Fetch(params.Sources, toolsConstraint, false) if err != nil { return nil, resolveInfo, err } if len(matchingTools) == 0 { return nil, resolveInfo, fmt.Errorf("no matching tools found for constraint %+v", toolsConstraint) } versions := make([]string, len(matchingTools)) for i, tm := range matchingTools { vers := version.Binary{ Number: version.MustParse(tm.Version), Series: tm.Release, Arch: tm.Arch, } versions[i] = vers.String() } return versions, resolveInfo, nil }
// binary returns the tools metadata's binary version, which may be used for // map lookup. func (t *ToolsMetadata) binary() (version.Binary, error) { num, err := version.Parse(t.Version) if err != nil { return version.Binary{}, errors.Trace(err) } return version.Binary{ Number: num, Series: t.Release, Arch: t.Arch, }, nil }
func (*suite) TestParse(c *gc.C) { for i, test := range parseTests { c.Logf("test %d", i) got, err := version.Parse(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) c.Check(got.IsDev(), gc.Equals, test.dev) c.Check(got.String(), gc.Equals, test.v) } } }
// LatestToolsVersion returns the newest version found in the last // check in the streams. // Bear in mind that the check was performed filtering only // new patches for the current major.minor. (major.minor.patch) func (e *Environment) LatestToolsVersion() version.Number { ver := e.doc.LatestAvailableTools if ver == "" { return version.Zero } v, err := version.Parse(ver) if err != nil { // This is being stored from a valid version but // in case this data would beacame corrupt It is not // worth to fail because of it. return version.Zero } return v }
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) }
// binary returns the tools metadata's binary version, which may be used for // map lookup. It is possible for a binary to have an unkown OS. func (t *ToolsMetadata) binary() (version.Binary, error) { num, err := version.Parse(t.Version) if err != nil { return version.Binary{}, errors.Trace(err) } toolsOS, err := version.GetOSFromSeries(t.Release) if err != nil && !version.IsUnknownOSForSeriesError(err) { return version.Binary{}, errors.Trace(err) } return version.Binary{ Number: num, Series: t.Release, Arch: t.Arch, OS: toolsOS, }, nil }
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) }
// GetEnvironmentVersion retrieves the environment's agent-version // value from an API client. func GetEnvironmentVersion(client ModelGetter) (version.Number, error) { noVersion := version.Number{} attrs, err := client.ModelGet() if err != nil { return noVersion, errors.Annotate(err, "unable to retrieve model config") } vi, found := attrs["agent-version"] if !found { return noVersion, errors.New("version not found in model config") } vs, ok := vi.(string) if !ok { return noVersion, errors.New("invalid model version type in config") } v, err := version.Parse(vs) if err != nil { return noVersion, errors.Annotate(err, "unable to parse model version") } return v, nil }
func (st *State) loginV2(tag, password, nonce string) error { var result params.LoginResultV1 request := ¶ms.LoginRequest{ AuthTag: tag, Credentials: password, Nonce: nonce, } err := st.APICall("Admin", 2, "", "Login", request, &result) if err != nil { // If the server complains about an empty tag it may be that we are // talking to an older server version that does not understand facades and // expects a params.Creds request instead of a params.LoginRequest. We // return a CodNotImplemented error to force login down to V1, which // supports older server logins. This may mask an actual empty tag in // params.LoginRequest, but that would be picked up in loginV1. V1 will // also produce a warning that we are ignoring an invalid API, so we do not // need to add one here. if err.Error() == `"" is not a valid tag` { return ¶ms.Error{ Message: err.Error(), Code: params.CodeNotImplemented, } } return errors.Trace(err) } servers := params.NetworkHostsPorts(result.Servers) err = st.setLoginResult(tag, result.EnvironTag, result.ServerTag, servers, result.Facades) if err != nil { return errors.Trace(err) } st.serverVersion, err = version.Parse(result.ServerVersion) if err != nil { return errors.Trace(err) } return nil }
// Validate ensures that config is a valid configuration. If old is not nil, // it holds the previous environment configuration for consideration when // validating changes. func Validate(cfg, old *Config) error { // Check that we don't have any disallowed fields. for _, attr := range allowedWithDefaultsOnly { if _, ok := cfg.defined[attr]; ok { return fmt.Errorf("attribute %q is not allowed in configuration", attr) } } // Check that mandatory fields are specified. for _, attr := range mandatoryWithoutDefaults { if _, ok := cfg.defined[attr]; !ok { return fmt.Errorf("%s missing from environment configuration", attr) } } // Check that all other fields that have been specified are non-empty, // unless they're allowed to be empty for backward compatibility, for attr, val := range cfg.defined { if !isEmpty(val) { continue } if !allowEmpty(attr) { return fmt.Errorf("empty %s in environment configuration", attr) } } if strings.ContainsAny(cfg.mustString("name"), "/\\") { return fmt.Errorf("environment name contains unsafe characters") } // Check that the agent version parses ok if set explicitly; otherwise leave // it alone. if v, ok := cfg.defined["agent-version"].(string); ok { if _, err := version.Parse(v); err != nil { return fmt.Errorf("invalid agent version in environment configuration: %q", v) } } // If the logging config is set, make sure it is valid. if v, ok := cfg.defined["logging-config"].(string); ok { if _, err := loggo.ParseConfigurationString(v); err != nil { return err } } // Check firewall mode. if mode := cfg.FirewallMode(); mode != FwInstance && mode != FwGlobal { return fmt.Errorf("invalid firewall mode in environment configuration: %q", mode) } caCert, caCertOK := cfg.CACert() caKey, caKeyOK := cfg.CAPrivateKey() if caCertOK || caKeyOK { if err := verifyKeyPair(caCert, caKey); err != nil { return errors.Annotate(err, "bad CA certificate/key in configuration") } } // Ensure that the auth token is a set of key=value pairs. authToken, _ := cfg.CharmStoreAuth() validAuthToken := regexp.MustCompile(`^([^\s=]+=[^\s=]+(,\s*)?)*$`) if !validAuthToken.MatchString(authToken) { return fmt.Errorf("charm store auth token needs to be a set"+ " of key-value pairs, not %q", authToken) } // Check the immutable config values. These can't change if old != nil { for _, attr := range immutableAttributes { if newv, oldv := cfg.defined[attr], old.defined[attr]; newv != oldv { return fmt.Errorf("cannot change %s from %#v to %#v", attr, oldv, newv) } } if _, oldFound := old.AgentVersion(); oldFound { if _, newFound := cfg.AgentVersion(); !newFound { return fmt.Errorf("cannot clear agent-version") } } } cfg.processDeprecatedAttributes() return nil }
func (st *state) loginV2(tag names.Tag, password, nonce string) error { var result params.LoginResultV1 request := ¶ms.LoginRequest{ AuthTag: tagToString(tag), Credentials: password, Nonce: nonce, } if tag == nil { // Add any macaroons that might work for authenticating the login request. request.Macaroons = httpbakery.MacaroonsForURL(st.bakeryClient.Client.Jar, st.cookieURL) } err := st.APICall("Admin", 2, "", "Login", request, &result) if err != nil { // If the server complains about an empty tag it may be that we are // talking to an older server version that does not understand facades and // expects a params.Creds request instead of a params.LoginRequest. We // return a CodNotImplemented error to force login down to V1, which // supports older server logins. This may mask an actual empty tag in // params.LoginRequest, but that would be picked up in loginV1. V1 will // also produce a warning that we are ignoring an invalid API, so we do not // need to add one here. if err.Error() == `"" is not a valid tag` { return ¶ms.Error{ Message: err.Error(), Code: params.CodeNotImplemented, } } return errors.Trace(err) } if result.DischargeRequired != nil { // The result contains a discharge-required // macaroon. We discharge it and retry // the login request with the original macaroon // and its discharges. if result.DischargeRequiredReason == "" { result.DischargeRequiredReason = "no reason given for discharge requirement" } if err := st.bakeryClient.HandleError(st.cookieURL, &httpbakery.Error{ Message: result.DischargeRequiredReason, Code: httpbakery.ErrDischargeRequired, Info: &httpbakery.ErrorInfo{ Macaroon: result.DischargeRequired, MacaroonPath: "/", }, }); err != nil { return errors.Trace(err) } // Add the macaroons that have been saved by HandleError to our login request. request.Macaroons = httpbakery.MacaroonsForURL(st.bakeryClient.Client.Jar, st.cookieURL) result = params.LoginResultV1{} // zero result err = st.APICall("Admin", 2, "", "Login", request, &result) if err != nil { return errors.Trace(err) } if result.DischargeRequired != nil { return errors.Errorf("login with discharged macaroons failed: %s", result.DischargeRequiredReason) } } servers := params.NetworkHostsPorts(result.Servers) err = st.setLoginResult(tag, result.EnvironTag, result.ServerTag, servers, result.Facades) if err != nil { return errors.Trace(err) } st.serverVersion, err = version.Parse(result.ServerVersion) if err != nil { return errors.Trace(err) } return nil }