func (c *ImageMetadataCommand) Run(context *cmd.Context) error { if err := c.setParams(context); err != nil { return err } out := context.Stdout im := &imagemetadata.ImageMetadata{ Id: c.ImageId, Arch: c.Arch, } cloudSpec := simplestreams.CloudSpec{ Region: c.Region, Endpoint: c.Endpoint, } targetStorage, err := filestorage.NewFileStorageWriter(c.Dir) if err != nil { return err } err = imagemetadata.MergeAndWriteMetadata(c.Series, []*imagemetadata.ImageMetadata{im}, &cloudSpec, targetStorage) if err != nil { return fmt.Errorf("image metadata files could not be created: %v", err) } dir := context.AbsPath(c.Dir) dest := filepath.Join(dir, storage.BaseImagesPath, "streams", "v1") fmt.Fprintf(out, fmt.Sprintf(helpDoc, dest, dir, dir)) return nil }
func writeServerFile(endpointProvider EndpointProvider, ctx *cmd.Context, username, password, outPath string) error { outPath = ctx.AbsPath(outPath) endpoint, err := endpointProvider.ConnectionEndpoint() if err != nil { return errors.Trace(err) } if !names.IsValidUser(username) { return errors.Errorf("%q is not a valid username", username) } outputInfo := modelcmd.ServerFile{ Username: username, Password: password, Addresses: endpoint.Addresses, CACert: endpoint.CACert, } yaml, err := cmd.FormatYaml(outputInfo) if err != nil { return errors.Trace(err) } if err := ioutil.WriteFile(outPath, yaml, 0644); err != nil { return errors.Trace(err) } serverFileNotify(outPath) ctx.Infof("server file written to %s", outPath) return nil }
// Run connects to the specified environment and starts the charm // upgrade process. func (c *upgradeCharmCommand) Run(ctx *cmd.Context) error { client, err := c.NewAPIClient() if err != nil { return err } defer client.Close() oldURL, err := client.ServiceGetCharmURL(c.ServiceName) if err != nil { return err } conf, err := service.GetClientConfig(client) if err != nil { return errors.Trace(err) } var newRef *charm.Reference if c.SwitchURL != "" { newRef, err = charm.ParseReference(c.SwitchURL) if err != nil { return err } } else { // No new URL specified, but revision might have been. newRef = oldURL.WithRevision(c.Revision).Reference() } httpClient, err := c.HTTPClient() if err != nil { return errors.Trace(err) } csClient := newCharmStoreClient(httpClient) newURL, repo, err := resolveCharmStoreEntityURL(newRef.String(), csClient.params, ctx.AbsPath(c.RepoPath), conf) if err != nil { return errors.Trace(err) } // If no explicit revision was set with either SwitchURL // or Revision flags, discover the latest. if *newURL == *oldURL { if newRef.Revision != -1 { return fmt.Errorf("already running specified charm %q", newURL) } if newURL.Schema == "cs" { // No point in trying to upgrade a charm store charm when // we just determined that's the latest revision // available. return fmt.Errorf("already running latest charm %q", newURL) } } addedURL, err := addCharmViaAPI(client, newURL, repo, csClient) if err != nil { return block.ProcessBlockedError(err, block.BlockChange) } ctx.Infof("Added charm %q to the environment.", addedURL) return block.ProcessBlockedError(client.ServiceSetCharm(c.ServiceName, addedURL.String(), c.Force), block.BlockChange) }
// Run connects to the specified environment and starts the charm // upgrade process. func (c *upgradeCharmCommand) Run(ctx *cmd.Context) error { client, err := c.NewAPIClient() if err != nil { return err } defer client.Close() serviceClient, err := c.newServiceAPIClient() if err != nil { return err } oldURL, err := serviceClient.GetCharmURL(c.ServiceName) if err != nil { return err } newRef := c.SwitchURL if newRef == "" { newRef = c.CharmPath } if c.SwitchURL == "" && c.CharmPath == "" { // No new URL specified, but revision might have been. newRef = oldURL.WithRevision(c.Revision).String() } bakeryClient, err := c.BakeryClient() if err != nil { return errors.Trace(err) } csClient := newCharmStoreClient(bakeryClient).WithChannel(c.Channel) conf, err := getClientConfig(client) if err != nil { return errors.Trace(err) } resolver := newCharmURLResolver(conf, csClient, ctx.AbsPath(c.RepoPath)) chID, csMac, err := c.addCharm(oldURL, newRef, client, resolver) if err != nil { return block.ProcessBlockedError(err, block.BlockChange) } ctx.Infof("Added charm %q to the model.", chID.URL) ids, err := c.upgradeResources(client, chID, csMac) if err != nil { return errors.Trace(err) } cfg := apiservice.SetCharmConfig{ ServiceName: c.ServiceName, CharmID: chID, ForceSeries: c.ForceSeries, ForceUnits: c.ForceUnits, ResourceIDs: ids, } return block.ProcessBlockedError(serviceClient.SetCharm(cfg), block.BlockChange) }
func (c *signMetadataCommand) Run(context *cmd.Context) error { loggo.RegisterWriter("signmetadata", cmd.NewCommandLogWriter("juju.plugins.metadata", context.Stdout, context.Stderr), loggo.INFO) defer loggo.RemoveWriter("signmetadata") keyData, err := ioutil.ReadFile(c.keyFile) if err != nil { return err } dir := context.AbsPath(c.dir) return process(dir, string(keyData), c.passphrase) }
func (c *RpcCommand) Run(ctx *cmd.Context) error { if c.Value == "error" { return errors.New("blam") } if c.Slow { time.Sleep(testing.ShortWait) return nil } ctx.Stdout.Write([]byte("eye of newt\n")) ctx.Stderr.Write([]byte("toe of frog\n")) return ioutil.WriteFile(ctx.AbsPath("local"), []byte(c.Value), 0644) }
func (c *toolsMetadataCommand) Run(context *cmd.Context) error { loggo.RegisterWriter("toolsmetadata", cmd.NewCommandLogWriter("juju.environs.tools", context.Stdout, context.Stderr), loggo.INFO) defer loggo.RemoveWriter("toolsmetadata") if c.metadataDir == "" { c.metadataDir = osenv.JujuHome() } else { c.metadataDir = context.AbsPath(c.metadataDir) } sourceStorage, err := filestorage.NewFileStorageReader(c.metadataDir) if err != nil { return err } // We now store the tools in a directory named after their stream, but the // legacy behaviour is to store all tools in a single "releases" directory. toolsDir := c.stream if c.stream == "" { fmt.Fprintf(context.Stdout, "No stream specified, defaulting to released tools in the releases directory.\n") c.stream = envtools.ReleasedStream toolsDir = envtools.LegacyReleaseDirectory } fmt.Fprintf(context.Stdout, "Finding tools in %s for stream %s.\n", c.metadataDir, c.stream) const minorVersion = -1 toolsList, err := envtools.ReadList(sourceStorage, toolsDir, version.Current.Major, minorVersion) if err == envtools.ErrNoTools { var source string source, err = envtools.ToolsURL(envtools.DefaultBaseURL) if err != nil { return err } sourceDataSource := simplestreams.NewURLDataSource("local source", source, utils.VerifySSLHostnames) toolsList, err = envtools.FindToolsForCloud( []simplestreams.DataSource{sourceDataSource}, simplestreams.CloudSpec{}, c.stream, version.Current.Major, minorVersion, coretools.Filter{}) } if err != nil { return err } targetStorage, err := filestorage.NewFileStorageWriter(c.metadataDir) if err != nil { return err } writeMirrors := envtools.DoNotWriteMirrors if c.public { writeMirrors = envtools.WriteMirrors } return mergeAndWriteMetadata(targetStorage, toolsDir, c.stream, c.clean, toolsList, writeMirrors) }
func (c *toolsMetadataCommand) Run(context *cmd.Context) error { writer := loggo.NewMinimumLevelWriter( cmd.NewCommandLogWriter("juju.environs.tools", context.Stdout, context.Stderr), loggo.INFO) loggo.RegisterWriter("toolsmetadata", writer) defer loggo.RemoveWriter("toolsmetadata") if c.metadataDir == "" { c.metadataDir = osenv.JujuXDGDataHomeDir() } else { c.metadataDir = context.AbsPath(c.metadataDir) } sourceStorage, err := filestorage.NewFileStorageReader(c.metadataDir) if err != nil { return err } // We now store the tools in a directory named after their stream, but the // legacy behaviour is to store all tools in a single "releases" directory. toolsDir := c.stream if c.stream == "" { fmt.Fprintln(context.Stdout, "No stream specified, defaulting to released tools in the releases directory.") c.stream = envtools.ReleasedStream toolsDir = envtools.LegacyReleaseDirectory } fmt.Fprintf(context.Stdout, "Finding tools in %s for stream %s.\n", c.metadataDir, c.stream) toolsList, err := envtools.ReadList(sourceStorage, toolsDir, -1, -1) if err == envtools.ErrNoTools { var source string source, err = envtools.ToolsURL(envtools.DefaultBaseURL) if err != nil { return err } toolsList, err = envtools.FindToolsForCloud(toolsDataSources(source), simplestreams.CloudSpec{}, c.stream, -1, -1, coretools.Filter{}) } if err != nil { return err } targetStorage, err := filestorage.NewFileStorageWriter(c.metadataDir) if err != nil { return err } writeMirrors := envtools.DoNotWriteMirrors if c.public { writeMirrors = envtools.WriteMirrors } return mergeAndWriteMetadata(targetStorage, toolsDir, c.stream, c.clean, toolsList, writeMirrors) }
// readValue reads the value of an option out of the named file. // An empty content is valid, like in parsing the options. The upper // size is 5M. func readValue(ctx *cmd.Context, filename string) (string, error) { absFilename := ctx.AbsPath(filename) fi, err := os.Stat(absFilename) if err != nil { return "", fmt.Errorf("cannot read option from file %q: %v", filename, err) } if fi.Size() > maxValueSize { return "", fmt.Errorf("size of option file is larger than 5M") } content, err := ioutil.ReadFile(ctx.AbsPath(filename)) if err != nil { return "", fmt.Errorf("cannot read option from file %q: %v", filename, err) } return string(content), nil }
// ReadAttrs reads attributes from the specified files, and then overlays // the results with the k=v attributes. func (f *ConfigFlag) ReadAttrs(ctx *cmd.Context) (map[string]interface{}, error) { attrs := make(map[string]interface{}) for _, f := range f.files { path, err := utils.NormalizePath(f) if err != nil { return nil, errors.Trace(err) } data, err := ioutil.ReadFile(ctx.AbsPath(path)) if err != nil { return nil, errors.Trace(err) } if err := yaml.Unmarshal(data, &attrs); err != nil { return nil, err } } for k, v := range f.attrs { attrs[k] = v } return attrs, nil }
func (c *toolsMetadataCommand) Run(context *cmd.Context) error { writer := loggo.NewMinimumLevelWriter( cmd.NewCommandLogWriter("juju.environs.tools", context.Stdout, context.Stderr), loggo.INFO) loggo.RegisterWriter("toolsmetadata", writer) defer loggo.RemoveWriter("toolsmetadata") if c.metadataDir == "" { c.metadataDir = osenv.JujuXDGDataHomeDir() } else { c.metadataDir = context.AbsPath(c.metadataDir) } sourceStorage, err := filestorage.NewFileStorageReader(c.metadataDir) if err != nil { return errors.Trace(err) } fmt.Fprintf(context.Stdout, "Finding tools in %s for stream %s.\n", c.metadataDir, c.stream) toolsList, err := envtools.ReadList(sourceStorage, c.stream, -1, -1) if err == envtools.ErrNoTools { var source string source, err = envtools.ToolsURL(envtools.DefaultBaseURL) if err != nil { return errors.Trace(err) } toolsList, err = envtools.FindToolsForCloud(toolsDataSources(source), simplestreams.CloudSpec{}, c.stream, -1, -1, coretools.Filter{}) } if err != nil { return errors.Trace(err) } targetStorage, err := filestorage.NewFileStorageWriter(c.metadataDir) if err != nil { return errors.Trace(err) } writeMirrors := envtools.DoNotWriteMirrors if c.public { writeMirrors = envtools.WriteMirrors } return errors.Trace(mergeAndWriteMetadata(targetStorage, c.stream, c.stream, c.clean, toolsList, writeMirrors)) }
func (c *ToolsMetadataCommand) Run(context *cmd.Context) error { loggo.RegisterWriter("toolsmetadata", cmd.NewCommandLogWriter("juju.environs.tools", context.Stdout, context.Stderr), loggo.INFO) defer loggo.RemoveWriter("toolsmetadata") if c.metadataDir == "" { c.metadataDir = osenv.JujuHome() } else { c.metadataDir = context.AbsPath(c.metadataDir) } sourceStorage, err := filestorage.NewFileStorageReader(c.metadataDir) if err != nil { return err } fmt.Fprintf(context.Stdout, "Finding tools in %s\n", c.metadataDir) const minorVersion = -1 toolsList, err := envtools.ReadList(sourceStorage, version.Current.Major, minorVersion) if err == envtools.ErrNoTools { var source string source, err = envtools.ToolsURL(envtools.DefaultBaseURL) if err != nil { return err } sourceDataSource := simplestreams.NewURLDataSource("local source", source, utils.VerifySSLHostnames) toolsList, err = envtools.FindToolsForCloud( []simplestreams.DataSource{sourceDataSource}, simplestreams.CloudSpec{}, version.Current.Major, minorVersion, coretools.Filter{}) } if err != nil { return err } targetStorage, err := filestorage.NewFileStorageWriter(c.metadataDir) if err != nil { return err } writeMirrors := envtools.DoNotWriteMirrors if c.public { writeMirrors = envtools.WriteMirrors } return mergeAndWriteMetadata(targetStorage, toolsList, writeMirrors) }
func (c *dumpLogsCommand) dumpLogsForEnv(ctx *cmd.Context, st0 *state.State, tag names.ModelTag) error { st, err := st0.ForModel(tag) if err != nil { return errors.Annotate(err, "failed open model") } defer st.Close() logName := ctx.AbsPath(filepath.Join(c.outDir, fmt.Sprintf("%s.log", tag.Id()))) ctx.Infof("writing to %s", logName) file, err := os.Create(logName) if err != nil { return errors.Annotate(err, "failed to open output file") } defer file.Close() writer := bufio.NewWriter(file) defer writer.Flush() tailer, err := state.NewLogTailer(st, &state.LogTailerParams{NoTail: true}) if err != nil { return errors.Annotate(err, "failed to create a log tailer") } logs := tailer.Logs() for { rec, ok := <-logs if !ok { break } writer.WriteString(c.format( rec.Time, rec.Level, rec.Entity.String(), rec.Module, rec.Message, ) + "\n") } return nil }
func (c *DeployCommand) maybeReadLocalBundleData(ctx *cmd.Context) ( _ *charm.BundleData, bundleFile string, bundleFilePath string, _ error, ) { bundleFile = c.CharmOrBundle bundleData, err := charmrepo.ReadBundleFile(bundleFile) if err == nil { // For local bundles, we extract the local path of // the bundle directory. bundleFilePath = filepath.Dir(ctx.AbsPath(bundleFile)) } else { // We may have been given a local bundle archive or exploded directory. if bundle, burl, pathErr := charmrepo.NewBundleAtPath(bundleFile); pathErr == nil { bundleData = bundle.Data() bundleFile = burl.String() if info, err := os.Stat(bundleFile); err == nil && info.IsDir() { bundleFilePath = bundleFile } err = nil } else { err = pathErr } } return bundleData, bundleFile, bundleFilePath, err }
// Run connects to the environment specified on the command line and bootstraps // a juju in that environment if none already exists. If there is as yet no environments.yaml file, // the user is informed how to create one. func (c *BootstrapCommand) Run(ctx *cmd.Context) (resultErr error) { bootstrapFuncs := getBootstrapFuncs() if len(c.seriesOld) > 0 { fmt.Fprintln(ctx.Stderr, "Use of --series is obsolete. --upload-tools now expands to all supported series of the same operating system.") } if len(c.Series) > 0 { fmt.Fprintln(ctx.Stderr, "Use of --upload-series is obsolete. --upload-tools now expands to all supported series of the same operating system.") } envName := getEnvName(c) if envName == "" { return errors.Errorf("the name of the environment must be specified") } if err := checkProviderType(envName); errors.IsNotFound(err) { // This error will get handled later. } else if err != nil { return errors.Trace(err) } environ, cleanup, err := environFromName( ctx, envName, "Bootstrap", bootstrapFuncs.EnsureNotBootstrapped, ) // If we error out for any reason, clean up the environment. defer func() { if resultErr != nil && cleanup != nil { if c.KeepBrokenEnvironment { logger.Warningf("bootstrap failed but --keep-broken was specified so environment is not being destroyed.\n" + "When you are finished diagnosing the problem, remember to run juju destroy-environment --force\n" + "to clean up the environment.") } else { handleBootstrapError(ctx, resultErr, cleanup) } } }() // Handle any errors from environFromName(...). if err != nil { return errors.Annotatef(err, "there was an issue examining the environment") } // Check to see if this environment is already bootstrapped. If it // is, we inform the user and exit early. If an error is returned // but it is not that the environment is already bootstrapped, // then we're in an unknown state. if err := bootstrapFuncs.EnsureNotBootstrapped(environ); nil != err { if environs.ErrAlreadyBootstrapped == err { logger.Warningf("This juju environment is already bootstrapped. If you want to start a new Juju\nenvironment, first run juju destroy-environment to clean up, or switch to an\nalternative environment.") return err } return errors.Annotatef(err, "cannot determine if environment is already bootstrapped.") } // Block interruption during bootstrap. Providers may also // register for interrupt notification so they can exit early. interrupted := make(chan os.Signal, 1) defer close(interrupted) ctx.InterruptNotify(interrupted) defer ctx.StopInterruptNotify(interrupted) go func() { for _ = range interrupted { ctx.Infof("Interrupt signalled: waiting for bootstrap to exit") } }() // If --metadata-source is specified, override the default tools metadata source so // SyncTools can use it, and also upload any image metadata. var metadataDir string if c.MetadataSource != "" { metadataDir = ctx.AbsPath(c.MetadataSource) } // TODO (wallyworld): 2013-09-20 bug 1227931 // We can set a custom tools data source instead of doing an // unnecessary upload. if environ.Config().Type() == provider.Local { c.UploadTools = true } err = bootstrapFuncs.Bootstrap(envcmd.BootstrapContext(ctx), environ, bootstrap.BootstrapParams{ Constraints: c.Constraints, Placement: c.Placement, UploadTools: c.UploadTools, AgentVersion: c.AgentVersion, MetadataDir: metadataDir, }) if err != nil { return errors.Annotate(err, "failed to bootstrap environment") } err = c.SetBootstrapEndpointAddress(environ) if err != nil { return errors.Annotate(err, "saving bootstrap endpoint address") } // To avoid race conditions when running scripted bootstraps, wait // for the state server's machine agent to be ready to accept commands // before exiting this bootstrap command. return c.waitForAgentInitialisation(ctx) }
func (c *DeployCommand) Run(ctx *cmd.Context) error { client, err := juju.NewAPIClientFromName(c.EnvName) if err != nil { return err } defer client.Close() attrs, err := client.EnvironmentGet() if err != nil { return err } conf, err := config.New(config.NoDefaults, attrs) if err != nil { return err } curl, err := resolveCharmURL(c.CharmName, client, conf) if err != nil { return err } repo, err := charm.InferRepository(curl.Reference, ctx.AbsPath(c.RepoPath)) if err != nil { return err } repo = config.SpecializeCharmRepo(repo, conf) curl, err = addCharmViaAPI(client, ctx, curl, repo) if err != nil { return err } if c.BumpRevision { ctx.Infof("--upgrade (or -u) is deprecated and ignored; charms are always deployed with a unique revision.") } requestedNetworks, err := networkNamesToTags(parseNetworks(c.Networks)) if err != nil { return err } // We need to ensure network names are valid below, but we don't need them here. _, err = networkNamesToTags(c.Constraints.IncludeNetworks()) if err != nil { return err } _, err = networkNamesToTags(c.Constraints.ExcludeNetworks()) if err != nil { return err } haveNetworks := len(requestedNetworks) > 0 || c.Constraints.HaveNetworks() charmInfo, err := client.CharmInfo(curl.String()) if err != nil { return err } numUnits := c.NumUnits if charmInfo.Meta.Subordinate { if !constraints.IsEmpty(&c.Constraints) { return errors.New("cannot use --constraints with subordinate service") } if numUnits == 1 && c.ToMachineSpec == "" { numUnits = 0 } else { return errors.New("cannot use --num-units or --to with subordinate service") } } serviceName := c.ServiceName if serviceName == "" { serviceName = charmInfo.Meta.Name } var configYAML []byte if c.Config.Path != "" { configYAML, err = c.Config.Read(ctx) if err != nil { return err } } err = client.ServiceDeployWithNetworks( curl.String(), serviceName, numUnits, string(configYAML), c.Constraints, c.ToMachineSpec, requestedNetworks, ) if params.IsCodeNotImplemented(err) { if haveNetworks { return errors.New("cannot use --networks/--constraints networks=...: not supported by the API server") } err = client.ServiceDeploy( curl.String(), serviceName, numUnits, string(configYAML), c.Constraints, c.ToMachineSpec) } return err }
func NormaliseJenvPath(ctx *cmd.Context, outPath string) string { if !strings.HasSuffix(outPath, ".jenv") { outPath = outPath + ".jenv" } return ctx.AbsPath(outPath) }
// Run connects to the environment specified on the command line and bootstraps // a juju in that environment if none already exists. If there is as yet no environments.yaml file, // the user is informed how to create one. func (c *bootstrapCommand) Run(ctx *cmd.Context) (resultErr error) { if err := c.parseConstraints(ctx); err != nil { return err } 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") } } if c.interactive { if err := c.runInteractive(ctx); err != nil { return errors.Trace(err) } // now run normal bootstrap using info gained above. } if c.showClouds { return printClouds(ctx, c.ClientStore()) } if c.showRegionsForCloud != "" { return printCloudRegions(ctx, c.showRegionsForCloud) } bootstrapFuncs := getBootstrapFuncs() // Get the cloud definition identified by c.Cloud. If c.Cloud does not // identify a cloud in clouds.yaml, but is the name of a provider, and // that provider implements environs.CloudRegionDetector, we'll // synthesise a Cloud structure with the detected regions and no auth- // types. cloud, err := jujucloud.CloudByName(c.Cloud) if errors.IsNotFound(err) { ctx.Verbosef("cloud %q not found, trying as a provider name", c.Cloud) provider, err := environs.Provider(c.Cloud) if errors.IsNotFound(err) { return errors.NewNotFound(nil, fmt.Sprintf("unknown cloud %q, please try %q", c.Cloud, "juju update-clouds")) } else if err != nil { return errors.Trace(err) } detector, ok := bootstrapFuncs.CloudRegionDetector(provider) if !ok { ctx.Verbosef( "provider %q does not support detecting regions", c.Cloud, ) return errors.NewNotFound(nil, fmt.Sprintf("unknown cloud %q, please try %q", c.Cloud, "juju update-clouds")) } var cloudEndpoint string regions, err := detector.DetectRegions() if errors.IsNotFound(err) { // It's not an error to have no regions. If the // provider does not support regions, then we // reinterpret the supplied region name as the // cloud's endpoint. This enables the user to // supply, for example, maas/<IP> or manual/<IP>. if c.Region != "" { ctx.Verbosef("interpreting %q as the cloud endpoint", c.Region) cloudEndpoint = c.Region c.Region = "" } } else if err != nil { return errors.Annotatef(err, "detecting regions for %q cloud provider", c.Cloud, ) } schemas := provider.CredentialSchemas() authTypes := make([]jujucloud.AuthType, 0, len(schemas)) for authType := range schemas { authTypes = append(authTypes, authType) } // Since we are iterating over a map, lets sort the authTypes so // they are always in a consistent order. sort.Sort(jujucloud.AuthTypes(authTypes)) cloud = &jujucloud.Cloud{ Type: c.Cloud, AuthTypes: authTypes, Endpoint: cloudEndpoint, Regions: regions, } } else if err != nil { return errors.Trace(err) } if err := checkProviderType(cloud.Type); errors.IsNotFound(err) { // This error will get handled later. } else if err != nil { return errors.Trace(err) } provider, err := environs.Provider(cloud.Type) if err != nil { return errors.Trace(err) } // Custom clouds may not have explicitly declared support for any auth- // types, in which case we'll assume that they support everything that // the provider supports. if len(cloud.AuthTypes) == 0 { for authType := range provider.CredentialSchemas() { cloud.AuthTypes = append(cloud.AuthTypes, authType) } } // Get the credentials and region name. store := c.ClientStore() var detectedCredentialName string credential, credentialName, regionName, err := modelcmd.GetCredentials( ctx, store, modelcmd.GetCredentialsParams{ Cloud: *cloud, CloudName: c.Cloud, CloudRegion: c.Region, CredentialName: c.CredentialName, }, ) if errors.Cause(err) == modelcmd.ErrMultipleCredentials { return ambiguousCredentialError } if errors.IsNotFound(err) && c.CredentialName == "" { // No credential was explicitly specified, and no credential // was found in credentials.yaml; have the provider detect // credentials from the environment. ctx.Verbosef("no credentials found, checking environment") detected, err := modelcmd.DetectCredential(c.Cloud, cloud.Type) if errors.Cause(err) == modelcmd.ErrMultipleCredentials { return ambiguousDetectedCredentialError } else if err != nil { return errors.Trace(err) } // We have one credential so extract it from the map. var oneCredential jujucloud.Credential for detectedCredentialName, oneCredential = range detected.AuthCredentials { } credential = &oneCredential regionName = c.Region if regionName == "" { regionName = detected.DefaultRegion } logger.Debugf( "authenticating with region %q and credential %q (%v)", regionName, detectedCredentialName, credential.Label, ) logger.Tracef("credential: %v", credential) } else if err != nil { return errors.Trace(err) } region, err := getRegion(cloud, c.Cloud, regionName) if err != nil { fmt.Fprintf(ctx.GetStderr(), "%s\n\nSpecify an alternative region, or try %q.", err, "juju update-clouds", ) return cmd.ErrSilent } controllerModelUUID, err := utils.NewUUID() if err != nil { return errors.Trace(err) } hostedModelUUID, err := utils.NewUUID() if err != nil { return errors.Trace(err) } controllerUUID, err := utils.NewUUID() if err != nil { return errors.Trace(err) } // Create a model config, and split out any controller // and bootstrap config attributes. modelConfigAttrs := map[string]interface{}{ "type": cloud.Type, "name": bootstrap.ControllerModelName, config.UUIDKey: controllerModelUUID.String(), } userConfigAttrs, err := c.config.ReadAttrs(ctx) if err != nil { return errors.Trace(err) } // The provider may define some custom attributes specific // to the provider. These will be added to the model config. providerAttrs := make(map[string]interface{}) if ps, ok := provider.(config.ConfigSchemaSource); ok { for attr := range ps.ConfigSchema() { if v, ok := userConfigAttrs[attr]; ok { providerAttrs[attr] = v } } fields := schema.FieldMap(ps.ConfigSchema(), ps.ConfigDefaults()) if coercedAttrs, err := fields.Coerce(providerAttrs, nil); err != nil { return errors.Annotatef(err, "invalid attribute value(s) for %v cloud", cloud.Type) } else { providerAttrs = coercedAttrs.(map[string]interface{}) } } logger.Debugf("provider attrs: %v", providerAttrs) for k, v := range userConfigAttrs { modelConfigAttrs[k] = v } // Provider specific attributes are either already specified in model // config (but may have been coerced), or were not present. Either way, // copy them in. for k, v := range providerAttrs { modelConfigAttrs[k] = v } bootstrapConfigAttrs := make(map[string]interface{}) controllerConfigAttrs := make(map[string]interface{}) // Based on the attribute names in clouds.yaml, create // a map of shared config for all models on this cloud. inheritedControllerAttrs := make(map[string]interface{}) for k, v := range cloud.Config { switch { case bootstrap.IsBootstrapAttribute(k): bootstrapConfigAttrs[k] = v continue case controller.ControllerOnlyAttribute(k): controllerConfigAttrs[k] = v continue } inheritedControllerAttrs[k] = v } for k, v := range modelConfigAttrs { switch { case bootstrap.IsBootstrapAttribute(k): bootstrapConfigAttrs[k] = v delete(modelConfigAttrs, k) case controller.ControllerOnlyAttribute(k): controllerConfigAttrs[k] = v delete(modelConfigAttrs, k) } } bootstrapConfig, err := bootstrap.NewConfig(bootstrapConfigAttrs) if err != nil { return errors.Annotate(err, "constructing bootstrap config") } controllerConfig, err := controller.NewConfig( controllerUUID.String(), bootstrapConfig.CACert, controllerConfigAttrs, ) if err != nil { return errors.Annotate(err, "constructing controller config") } if err := common.FinalizeAuthorizedKeys(ctx, modelConfigAttrs); err != nil { return errors.Annotate(err, "finalizing authorized-keys") } logger.Debugf("preparing controller with config: %v", modelConfigAttrs) // Read existing current controller so we can clean up on error. var oldCurrentController string oldCurrentController, err = store.CurrentController() if errors.IsNotFound(err) { oldCurrentController = "" } else if err != nil { return errors.Annotate(err, "error reading current controller") } defer func() { if resultErr == nil || errors.IsAlreadyExists(resultErr) { return } if oldCurrentController != "" { if err := store.SetCurrentController(oldCurrentController); err != nil { logger.Errorf( "cannot reset current controller to %q: %v", oldCurrentController, err, ) } } if err := store.RemoveController(c.controllerName); err != nil { logger.Errorf( "cannot destroy newly created controller %q details: %v", c.controllerName, err, ) } }() bootstrapModelConfig := make(map[string]interface{}) for k, v := range inheritedControllerAttrs { bootstrapModelConfig[k] = v } for k, v := range modelConfigAttrs { bootstrapModelConfig[k] = v } // Add in any default attribute values if not already // specified, making the recorded bootstrap config // immutable to changes in Juju. for k, v := range config.ConfigDefaults() { if _, ok := bootstrapModelConfig[k]; !ok { bootstrapModelConfig[k] = v } } environ, err := bootstrapPrepare( modelcmd.BootstrapContext(ctx), store, bootstrap.PrepareParams{ ModelConfig: bootstrapModelConfig, ControllerConfig: controllerConfig, ControllerName: c.controllerName, Cloud: environs.CloudSpec{ Type: cloud.Type, Name: c.Cloud, Region: region.Name, Endpoint: region.Endpoint, IdentityEndpoint: region.IdentityEndpoint, StorageEndpoint: region.StorageEndpoint, Credential: credential, }, CredentialName: credentialName, AdminSecret: bootstrapConfig.AdminSecret, }, ) if err != nil { return errors.Trace(err) } // Set the current model to the initial hosted model. if err := store.UpdateModel(c.controllerName, c.hostedModelName, jujuclient.ModelDetails{ hostedModelUUID.String(), }); err != nil { return errors.Trace(err) } if err := store.SetCurrentModel(c.controllerName, c.hostedModelName); err != nil { return errors.Trace(err) } // Set the current controller so "juju status" can be run while // bootstrapping is underway. if err := store.SetCurrentController(c.controllerName); err != nil { return errors.Trace(err) } cloudRegion := c.Cloud if region.Name != "" { cloudRegion = fmt.Sprintf("%s/%s", cloudRegion, region.Name) } ctx.Infof( "Creating Juju controller %q on %s", c.controllerName, cloudRegion, ) // If we error out for any reason, clean up the environment. defer func() { if resultErr != nil { if c.KeepBrokenEnvironment { ctx.Infof(` bootstrap failed but --keep-broken was specified so resources are not being destroyed. When you have finished diagnosing the problem, remember to clean up the failed controller. See `[1:] + "`juju kill-controller`" + `.`) } else { handleBootstrapError(ctx, resultErr, func() error { return environsDestroy( c.controllerName, environ, store, ) }) } } }() // Block interruption during bootstrap. Providers may also // register for interrupt notification so they can exit early. interrupted := make(chan os.Signal, 1) defer close(interrupted) ctx.InterruptNotify(interrupted) defer ctx.StopInterruptNotify(interrupted) go func() { for _ = range interrupted { ctx.Infof("Interrupt signalled: waiting for bootstrap to exit") } }() // If --metadata-source is specified, override the default tools metadata source so // SyncTools can use it, and also upload any image metadata. var metadataDir string if c.MetadataSource != "" { metadataDir = ctx.AbsPath(c.MetadataSource) } // Merge environ and bootstrap-specific constraints. constraintsValidator, err := environ.ConstraintsValidator() if err != nil { return errors.Trace(err) } bootstrapConstraints, err := constraintsValidator.Merge( c.Constraints, c.BootstrapConstraints, ) if err != nil { return errors.Trace(err) } logger.Infof("combined bootstrap constraints: %v", bootstrapConstraints) hostedModelConfig := map[string]interface{}{ "name": c.hostedModelName, config.UUIDKey: hostedModelUUID.String(), } for k, v := range inheritedControllerAttrs { hostedModelConfig[k] = v } // We copy across any user supplied attributes to the hosted model config. // But only if the attributes have not been removed from the controller // model config as part of preparing the controller model. controllerModelConfigAttrs := environ.Config().AllAttrs() for k, v := range userConfigAttrs { if _, ok := controllerModelConfigAttrs[k]; ok { hostedModelConfig[k] = v } } // Ensure that certain config attributes are not included in the hosted // model config. These attributes may be modified during bootstrap; by // removing them from this map, we ensure the modified values are // inherited. delete(hostedModelConfig, config.AuthorizedKeysKey) delete(hostedModelConfig, config.AgentVersionKey) // Check whether the Juju GUI must be installed in the controller. // Leaving this value empty means no GUI will be installed. var guiDataSourceBaseURL string if !c.noGUI { guiDataSourceBaseURL = common.GUIDataSourceBaseURL() } if credentialName == "" { // credentialName will be empty if the credential was detected. // We must supply a name for the credential in the database, // so choose one. credentialName = detectedCredentialName } err = bootstrapFuncs.Bootstrap(modelcmd.BootstrapContext(ctx), environ, bootstrap.BootstrapParams{ ModelConstraints: c.Constraints, BootstrapConstraints: bootstrapConstraints, BootstrapSeries: c.BootstrapSeries, BootstrapImage: c.BootstrapImage, Placement: c.Placement, BuildAgent: c.BuildAgent, BuildAgentTarball: sync.BuildAgentTarball, AgentVersion: c.AgentVersion, MetadataDir: metadataDir, Cloud: *cloud, CloudName: c.Cloud, CloudRegion: region.Name, CloudCredential: credential, CloudCredentialName: credentialName, ControllerConfig: controllerConfig, ControllerInheritedConfig: inheritedControllerAttrs, RegionInheritedConfig: cloud.RegionConfig, HostedModelConfig: hostedModelConfig, GUIDataSourceBaseURL: guiDataSourceBaseURL, AdminSecret: bootstrapConfig.AdminSecret, CAPrivateKey: bootstrapConfig.CAPrivateKey, DialOpts: environs.BootstrapDialOpts{ Timeout: bootstrapConfig.BootstrapTimeout, RetryDelay: bootstrapConfig.BootstrapRetryDelay, AddressesDelay: bootstrapConfig.BootstrapAddressesDelay, }, }) if err != nil { return errors.Annotate(err, "failed to bootstrap model") } if err := c.SetModelName(modelcmd.JoinModelName(c.controllerName, c.hostedModelName)); err != nil { return errors.Trace(err) } agentVersion := jujuversion.Current if c.AgentVersion != nil { agentVersion = *c.AgentVersion } err = common.SetBootstrapEndpointAddress(c.ClientStore(), c.controllerName, agentVersion, controllerConfig.APIPort(), environ) if err != nil { return errors.Annotate(err, "saving bootstrap endpoint address") } // To avoid race conditions when running scripted bootstraps, wait // for the controller's machine agent to be ready to accept commands // before exiting this bootstrap command. return waitForAgentInitialisation(ctx, &c.ModelCommandBase, c.controllerName, c.hostedModelName) }
func (c *DeployCommand) deployCharmOrBundle(ctx *cmd.Context, client *api.Client) error { deployer := serviceDeployer{ctx, c.newServiceAPIClient, c.newAnnotationsAPIClient} // We may have been given a local bundle file. bundlePath := c.CharmOrBundle bundleData, err := charmrepo.ReadBundleFile(bundlePath) if err != nil { // We may have been given a local bundle archive or exploded directory. if bundle, burl, pathErr := charmrepo.NewBundleAtPath(bundlePath); err == nil { bundleData = bundle.Data() bundlePath = burl.String() err = pathErr } } // If not a bundle then maybe a local charm. if err != nil { // Charm may have been supplied via a path reference. ch, curl, charmErr := charmrepo.NewCharmAtPathForceSeries(c.CharmOrBundle, c.Series, c.Force) if charmErr == nil { if curl, charmErr = client.AddLocalCharm(curl, ch); charmErr != nil { return charmErr } return c.deployCharm(curl, curl.Series, ctx, client, &deployer) } // We check for several types of known error which indicate // that the supplied reference was indeed a path but there was // an issue reading the charm located there. if charm.IsMissingSeriesError(charmErr) { return charmErr } if charm.IsUnsupportedSeriesError(charmErr) { return errors.Errorf("%v. Use --force to deploy the charm anyway.", charmErr) } err = charmErr } if _, ok := err.(*charmrepo.NotFoundError); ok { return errors.Errorf("no charm or bundle found at %q", c.CharmOrBundle) } // If we get a "not exists" error then we attempt to interpret the supplied // charm or bundle reference as a URL below, otherwise we return the error. if err != nil && err != os.ErrNotExist { return err } repoPath := ctx.AbsPath(c.RepoPath) conf, err := getClientConfig(client) if err != nil { return err } httpClient, err := c.HTTPClient() if err != nil { return errors.Trace(err) } csClient := newCharmStoreClient(httpClient) var charmOrBundleURL *charm.URL var repo charmrepo.Interface var supportedSeries []string // If we don't already have a bundle loaded, we try the charm store for a charm or bundle. if bundleData == nil { // Charm or bundle has been supplied as a URL so we resolve and deploy using the store. charmOrBundleURL, supportedSeries, repo, err = resolveCharmStoreEntityURL(resolveCharmStoreEntityParams{ urlStr: c.CharmOrBundle, requestedSeries: c.Series, forceSeries: c.Force, csParams: csClient.params, repoPath: repoPath, conf: conf, }) if charm.IsUnsupportedSeriesError(err) { return errors.Errorf("%v. Use --force to deploy the charm anyway.", err) } if err != nil { return errors.Trace(err) } if charmOrBundleURL.Series == "bundle" { // Load the bundle entity. bundle, err := repo.GetBundle(charmOrBundleURL) if err != nil { return errors.Trace(err) } bundleData = bundle.Data() bundlePath = charmOrBundleURL.String() } } // Handle a bundle. if bundleData != nil { if flags := getFlags(c.flagSet, charmOnlyFlags); len(flags) > 0 { return errors.Errorf("Flags provided but not supported when deploying a bundle: %s.", strings.Join(flags, ", ")) } if err := deployBundle( bundleData, client, &deployer, csClient, repoPath, conf, ctx, c.BundleStorage, ); err != nil { return errors.Trace(err) } ctx.Infof("deployment of bundle %q completed", bundlePath) return nil } // Handle a charm. if flags := getFlags(c.flagSet, bundleOnlyFlags); len(flags) > 0 { return errors.Errorf("Flags provided but not supported when deploying a charm: %s.", strings.Join(flags, ", ")) } // Get the series to use. series, message, err := charmSeries(c.Series, charmOrBundleURL.Series, supportedSeries, c.Force, conf) if charm.IsUnsupportedSeriesError(err) { return errors.Errorf("%v. Use --force to deploy the charm anyway.", err) } // Store the charm in state. curl, err := addCharmFromURL(client, charmOrBundleURL, repo, csClient) if err != nil { if err1, ok := errors.Cause(err).(*termsRequiredError); ok { terms := strings.Join(err1.Terms, " ") return errors.Errorf(`Declined: please agree to the following terms %s. Try: "juju agree %s"`, terms, terms) } return errors.Annotatef(err, "storing charm for URL %q", charmOrBundleURL) } ctx.Infof("Added charm %q to the model.", curl) ctx.Infof("Deploying charm %q %v.", curl, fmt.Sprintf(message, series)) return c.deployCharm(curl, series, ctx, client, &deployer) }
func (c *DeployCommand) Run(ctx *cmd.Context) error { client, err := c.NewAPIClient() if err != nil { return err } defer client.Close() conf, err := service.GetClientConfig(client) if err != nil { return err } if err := c.CheckProvider(conf); err != nil { return err } csClient, err := newCharmStoreClient() if err != nil { return errors.Trace(err) } defer csClient.jar.Save() curl, repo, err := resolveCharmURL(c.CharmName, csClient.params, ctx.AbsPath(c.RepoPath), conf) if err != nil { return errors.Trace(err) } curl, err = addCharmViaAPI(client, ctx, curl, repo, csClient) if err != nil { return block.ProcessBlockedError(err, block.BlockChange) } if c.BumpRevision { ctx.Infof("--upgrade (or -u) is deprecated and ignored; charms are always deployed with a unique revision.") } charmInfo, err := client.CharmInfo(curl.String()) if err != nil { return err } numUnits := c.NumUnits if charmInfo.Meta.Subordinate { if !constraints.IsEmpty(&c.Constraints) { return errors.New("cannot use --constraints with subordinate service") } if numUnits == 1 && c.PlacementSpec == "" { numUnits = 0 } else { return errors.New("cannot use --num-units or --to with subordinate service") } } serviceName := c.ServiceName if serviceName == "" { serviceName = charmInfo.Meta.Name } var configYAML []byte if c.Config.Path != "" { configYAML, err = c.Config.Read(ctx) if err != nil { return err } } // If storage or placement is specified, we attempt to use a new API on the service facade. if len(c.Storage) > 0 || len(c.Placement) > 0 { notSupported := errors.New("cannot deploy charms with storage or placement: not supported by the API server") serviceClient, err := c.newServiceAPIClient() if err != nil { return notSupported } defer serviceClient.Close() for i, p := range c.Placement { if p.Scope == "env-uuid" { p.Scope = serviceClient.EnvironmentUUID() } c.Placement[i] = p } err = serviceClient.ServiceDeploy( curl.String(), serviceName, numUnits, string(configYAML), c.Constraints, c.PlacementSpec, c.Placement, []string{}, c.Storage, ) if params.IsCodeNotImplemented(err) { return notSupported } return block.ProcessBlockedError(err, block.BlockChange) } if len(c.Networks) > 0 { ctx.Infof("use of --networks is deprecated and is ignored. Please use spaces to manage placement within networks") } err = client.ServiceDeploy( curl.String(), serviceName, numUnits, string(configYAML), c.Constraints, c.PlacementSpec) if err != nil { return block.ProcessBlockedError(err, block.BlockChange) } state, err := c.NewAPIRoot() if err != nil { return err } err = registerMeteredCharm(c.RegisterURL, state, csClient.jar, curl.String(), serviceName, client.EnvironmentUUID()) if params.IsCodeNotImplemented(err) { // The state server is too old to support metering. Warn // the user, but don't return an error. logger.Warningf("current state server version does not support charm metering") return nil } return block.ProcessBlockedError(err, block.BlockChange) }
// Run connects to the environment specified on the command line and bootstraps // a juju in that environment if none already exists. If there is as yet no environments.yaml file, // the user is informed how to create one. func (c *BootstrapCommand) Run(ctx *cmd.Context) (resultErr error) { bootstrapFuncs := getBootstrapFuncs() if len(c.seriesOld) > 0 { fmt.Fprintln(ctx.Stderr, "Use of --series is deprecated. Please use --upload-series instead.") } if c.ConnectionName() == "" { return fmt.Errorf("the name of the environment must be specified") } environ, cleanup, err := environFromName(ctx, c.ConnectionName(), "Bootstrap") if err != nil { return errors.Annotatef(err, "there was an issue examining the environment") } // If we error out for any reason, clean up the environment. defer func() { if resultErr != nil { cleanup() } }() // We want to validate constraints early. However, if a custom image metadata // source is specified, we can't validate the arch because that depends on what // images metadata is to be uploaded. So we validate here if no custom metadata // source is specified, and defer till later if not. if c.MetadataSource == "" { if err := validateConstraints(c.Constraints, environ); err != nil { return err } } // Check to see if this environment is already bootstrapped. If it // is, we inform the user and exit early. If an error is returned // but it is not that the environment is already bootstrapped, // then we're in an unknown state. if err := bootstrapFuncs.EnsureNotBootstrapped(environ); nil != err { if environs.ErrAlreadyBootstrapped == err { logger.Warningf("This juju environment is already bootstrapped. If you want to start a new Juju\nenvironment, first run juju destroy-environment to clean up, or switch to an\nalternative environment.") return err } return errors.Annotatef(err, "cannot determine if environment is already bootstrapped.") } // Block interruption during bootstrap. Providers may also // register for interrupt notification so they can exit early. interrupted := make(chan os.Signal, 1) defer close(interrupted) ctx.InterruptNotify(interrupted) defer ctx.StopInterruptNotify(interrupted) go func() { for _ = range interrupted { ctx.Infof("Interrupt signalled: waiting for bootstrap to exit") } }() // If --metadata-source is specified, override the default tools metadata source so // SyncTools can use it, and also upload any image metadata. if c.MetadataSource != "" { metadataDir := ctx.AbsPath(c.MetadataSource) if err := uploadCustomMetadata(metadataDir, environ); err != nil { return err } if err := validateConstraints(c.Constraints, environ); err != nil { return err } } // TODO (wallyworld): 2013-09-20 bug 1227931 // We can set a custom tools data source instead of doing an // unnecessary upload. if environ.Config().Type() == provider.Local { c.UploadTools = true } if c.UploadTools { err = bootstrapFuncs.UploadTools(ctx, environ, c.Constraints.Arch, true, c.Series...) if err != nil { return err } } return bootstrapFuncs.Bootstrap(ctx, environ, environs.BootstrapParams{ Constraints: c.Constraints, Placement: c.Placement, }) }
// Run connects to the environment specified on the command line and bootstraps // a juju in that environment if none already exists. If there is as yet no environments.yaml file, // the user is informed how to create one. func (c *bootstrapCommand) Run(ctx *cmd.Context) (resultErr error) { bootstrapFuncs := getBootstrapFuncs() envName := getModelName(c) if envName == "" { return errors.Errorf("the name of the model must be specified") } if err := checkProviderType(envName); errors.IsNotFound(err) { // This error will get handled later. } else if err != nil { return errors.Trace(err) } environ, cleanup, err := environFromName( ctx, envName, "Bootstrap", bootstrapFuncs.EnsureNotBootstrapped, ) // If we error out for any reason, clean up the environment. defer func() { if resultErr != nil && cleanup != nil { if c.KeepBrokenEnvironment { logger.Warningf("bootstrap failed but --keep-broken was specified so model is not being destroyed.\n" + "When you are finished diagnosing the problem, remember to run juju destroy-model --force\n" + "to clean up the model.") } else { handleBootstrapError(ctx, resultErr, cleanup) } } }() // Handle any errors from environFromName(...). if err != nil { return errors.Annotatef(err, "there was an issue examining the model") } // Check to see if this environment is already bootstrapped. If it // is, we inform the user and exit early. If an error is returned // but it is not that the environment is already bootstrapped, // then we're in an unknown state. if err := bootstrapFuncs.EnsureNotBootstrapped(environ); nil != err { if environs.ErrAlreadyBootstrapped == err { logger.Warningf("This juju model is already bootstrapped. If you want to start a new Juju\nmodel, first run juju destroy-model to clean up, or switch to an\nalternative model.") return err } return errors.Annotatef(err, "cannot determine if model is already bootstrapped.") } // Block interruption during bootstrap. Providers may also // register for interrupt notification so they can exit early. interrupted := make(chan os.Signal, 1) defer close(interrupted) ctx.InterruptNotify(interrupted) defer ctx.StopInterruptNotify(interrupted) go func() { for _ = range interrupted { ctx.Infof("Interrupt signalled: waiting for bootstrap to exit") } }() // If --metadata-source is specified, override the default tools metadata source so // SyncTools can use it, and also upload any image metadata. var metadataDir string if c.MetadataSource != "" { metadataDir = ctx.AbsPath(c.MetadataSource) } // Merge environ and bootstrap-specific constraints. constraintsValidator, err := environ.ConstraintsValidator() if err != nil { return errors.Trace(err) } bootstrapConstraints, err := constraintsValidator.Merge( c.Constraints, c.BootstrapConstraints, ) if err != nil { return errors.Trace(err) } logger.Infof("combined bootstrap constraints: %v", bootstrapConstraints) err = bootstrapFuncs.Bootstrap(modelcmd.BootstrapContext(ctx), environ, bootstrap.BootstrapParams{ EnvironConstraints: c.Constraints, BootstrapConstraints: bootstrapConstraints, BootstrapSeries: c.BootstrapSeries, BootstrapImage: c.BootstrapImage, Placement: c.Placement, UploadTools: c.UploadTools, AgentVersion: c.AgentVersion, MetadataDir: metadataDir, }) if err != nil { return errors.Annotate(err, "failed to bootstrap model") } err = c.SetBootstrapEndpointAddress(environ) if err != nil { return errors.Annotate(err, "saving bootstrap endpoint address") } err = modelcmd.SetCurrentModel(ctx, envName) if err != nil { return errors.Trace(err) } // To avoid race conditions when running scripted bootstraps, wait // for the controller's machine agent to be ready to accept commands // before exiting this bootstrap command. return c.waitForAgentInitialisation(ctx) }
func (c *ConfigCommand) ReadConfig(ctx *cmd.Context) (err error) { c.Config, err = charmstore.ReadConfig(ctx.AbsPath(c.ConfigPath)) return err }
// addCharm interprets the new charmRef and adds the specified charm if the new charm is different // to what's already deployed as specified by oldURL. func (c *upgradeCharmCommand) addCharm(oldURL *charm.URL, charmRef string, ctx *cmd.Context, client *api.Client, csClient *csClient, ) (*charm.URL, error) { // Charm may have been supplied via a path reference. ch, newURL, err := charmrepo.NewCharmAtPathForceSeries(charmRef, oldURL.Series, c.ForceSeries) if err == nil { _, newName := filepath.Split(charmRef) if newName != oldURL.Name { return nil, fmt.Errorf("cannot upgrade %q to %q", oldURL.Name, newName) } return client.AddLocalCharm(newURL, ch) } if _, ok := err.(*charmrepo.NotFoundError); ok { return nil, errors.Errorf("no charm found at %q", charmRef) } // If we get a "not exists" or invalid path error then we attempt to interpret // the supplied charm reference as a URL below, otherwise we return the error. if err != os.ErrNotExist && !charmrepo.IsInvalidPathError(err) { return nil, err } // Charm has been supplied as a URL so we resolve and deploy using the store. conf, err := getClientConfig(client) if err != nil { return nil, err } newURL, supportedSeries, repo, err := resolveCharmStoreEntityURL(resolveCharmStoreEntityParams{ urlStr: charmRef, requestedSeries: oldURL.Series, forceSeries: c.ForceSeries, csParams: csClient.params, repoPath: ctx.AbsPath(c.RepoPath), conf: conf, }) if err != nil { return nil, errors.Trace(err) } if !c.ForceSeries && oldURL.Series != "" && newURL.Series == "" && !isSeriesSupported(oldURL.Series, supportedSeries) { series := []string{"no series"} if len(supportedSeries) > 0 { series = supportedSeries } return nil, errors.Errorf( "cannot upgrade from single series %q charm to a charm supporting %q. Use --force-series to override.", oldURL.Series, series, ) } // If no explicit revision was set with either SwitchURL // or Revision flags, discover the latest. if *newURL == *oldURL { newRef, _ := charm.ParseURL(charmRef) if newRef.Revision != -1 { return nil, fmt.Errorf("already running specified charm %q", newURL) } if newURL.Schema == "cs" { // No point in trying to upgrade a charm store charm when // we just determined that's the latest revision // available. return nil, fmt.Errorf("already running latest charm %q", newURL) } } addedURL, err := addCharmFromURL(client, newURL, repo, csClient) if err != nil { return nil, err } ctx.Infof("Added charm %q to the model.", addedURL) return addedURL, nil }
func (c *publishCommand) Run(ctx *cmd.Context) (err error) { branch := bzr.New(ctx.AbsPath(c.CharmPath)) if _, err := os.Stat(branch.Join(".bzr")); err != nil { return fmt.Errorf("not a charm branch: %s", branch.Location()) } if err := branch.CheckClean(); err != nil { return err } var curl *charm.URL if c.URL == "" { if err == nil { loc, err := branch.PushLocation() if err != nil { return fmt.Errorf("no charm URL provided and cannot infer from current directory (no push location)") } curl, err = charmrepo.LegacyStore.CharmURL(loc) if err != nil { return fmt.Errorf("cannot infer charm URL from branch location: %q", loc) } } } else { ref, err := charm.ParseReference(c.URL) if err != nil { return err } curl, err = ref.URL("") if err != nil { return err } } pushLocation := charmrepo.LegacyStore.BranchLocation(curl) if c.changePushLocation != nil { pushLocation = c.changePushLocation(pushLocation) } repo, err := charmrepo.LegacyInferRepository(curl.Reference(), "/not/important") if err != nil { return err } if repo != charmrepo.LegacyStore { return fmt.Errorf("charm URL must reference the juju charm store") } localDigest, err := branch.RevisionId() if err != nil { return fmt.Errorf("cannot obtain local digest: %v", err) } logger.Infof("local digest is %s", localDigest) ch, err := charm.ReadCharmDir(branch.Location()) if err != nil { return err } if ch.Meta().Name != curl.Name { return fmt.Errorf("charm name in metadata must match name in URL: %q != %q", ch.Meta().Name, curl.Name) } oldEvent, err := charmrepo.LegacyStore.Event(curl, localDigest) if _, ok := err.(*charmrepo.NotFoundError); ok { oldEvent, err = charmrepo.LegacyStore.Event(curl, "") if _, ok := err.(*charmrepo.NotFoundError); ok { logger.Infof("charm %s is not yet in the store", curl) err = nil } } if err != nil { return fmt.Errorf("cannot obtain event details from the store: %s", err) } if oldEvent != nil && oldEvent.Digest == localDigest { return handleEvent(ctx, curl, oldEvent) } logger.Infof("sending charm to the charm store...") err = branch.Push(&bzr.PushAttr{Location: pushLocation, Remember: true}) if err != nil { return err } logger.Infof("charm sent; waiting for it to be published...") for { time.Sleep(c.pollDelay) newEvent, err := charmrepo.LegacyStore.Event(curl, "") if _, ok := err.(*charmrepo.NotFoundError); ok { continue } if err != nil { return fmt.Errorf("cannot obtain event details from the store: %s", err) } if oldEvent != nil && oldEvent.Digest == newEvent.Digest { continue } if newEvent.Digest != localDigest { // TODO Check if the published digest is in the local history. return fmt.Errorf("charm changed but not to local charm digest; publishing race?") } return handleEvent(ctx, curl, newEvent) } }
func (c *deployCommand) Run(ctx *cmd.Context) error { client, err := c.NewAPIClient() if err != nil { return err } defer client.Close() conf, err := service.GetClientConfig(client) if err != nil { return err } if err := c.CheckProvider(conf); err != nil { return err } httpClient, err := c.HTTPClient() if err != nil { return errors.Trace(err) } csClient := newCharmStoreClient(httpClient) repoPath := ctx.AbsPath(c.RepoPath) // Handle local bundle paths. f, err := os.Open(c.CharmOrBundle) if err == nil { defer f.Close() info, err := f.Stat() if err != nil { return block.ProcessBlockedError(err, block.BlockChange) } if info.IsDir() { return errors.New("deployment of bundle directories not yet supported") } bundleData, err := charm.ReadBundleData(f) if err != nil { return block.ProcessBlockedError(err, block.BlockChange) } if err := deployBundle(bundleData, client, csClient, repoPath, conf, ctx); err != nil { return block.ProcessBlockedError(err, block.BlockChange) } ctx.Infof("deployment of bundle %q completed", f.Name()) return nil } else if !os.IsNotExist(err) { logger.Warningf("cannot open %q: %v; falling back to using charm repository", c.CharmOrBundle, err) } curl, repo, err := resolveCharmStoreEntityURL(c.CharmOrBundle, csClient.params, repoPath, conf) if err != nil { return errors.Trace(err) } // Handle bundle URLs. if curl.Series == "bundle" { // Deploy a bundle entity. bundle, err := repo.GetBundle(curl) if err != nil { return block.ProcessBlockedError(err, block.BlockChange) } if err := deployBundle(bundle.Data(), client, csClient, repoPath, conf, ctx); err != nil { return block.ProcessBlockedError(err, block.BlockChange) } ctx.Infof("deployment of bundle %q completed", curl) return nil } curl, err = addCharmViaAPI(client, curl, repo, csClient) if err != nil { return block.ProcessBlockedError(err, block.BlockChange) } ctx.Infof("Added charm %q to the environment.", curl) if c.BumpRevision { ctx.Infof("--upgrade (or -u) is deprecated and ignored; charms are always deployed with a unique revision.") } charmInfo, err := client.CharmInfo(curl.String()) if err != nil { return err } numUnits := c.NumUnits if charmInfo.Meta.Subordinate { if !constraints.IsEmpty(&c.Constraints) { return errors.New("cannot use --constraints with subordinate service") } if numUnits == 1 && c.PlacementSpec == "" { numUnits = 0 } else { return errors.New("cannot use --num-units or --to with subordinate service") } } serviceName := c.ServiceName if serviceName == "" { serviceName = charmInfo.Meta.Name } var configYAML []byte if c.Config.Path != "" { configYAML, err = c.Config.Read(ctx) if err != nil { return err } } // If storage or placement is specified, we attempt to use a new API on the service facade. if len(c.Storage) > 0 || len(c.Placement) > 0 { notSupported := errors.New("cannot deploy charms with storage or placement: not supported by the API server") serviceClient, err := c.newServiceAPIClient() if err != nil { return notSupported } defer serviceClient.Close() for i, p := range c.Placement { if p.Scope == "env-uuid" { p.Scope = serviceClient.EnvironmentUUID() } c.Placement[i] = p } err = serviceClient.ServiceDeploy( curl.String(), serviceName, numUnits, string(configYAML), c.Constraints, c.PlacementSpec, c.Placement, []string{}, c.Storage, ) if params.IsCodeNotImplemented(err) { return notSupported } return block.ProcessBlockedError(err, block.BlockChange) } if len(c.Networks) > 0 { ctx.Infof("use of --networks is deprecated and is ignored. Please use spaces to manage placement within networks") } err = client.ServiceDeploy( curl.String(), serviceName, numUnits, string(configYAML), c.Constraints, c.PlacementSpec) if err != nil { return block.ProcessBlockedError(err, block.BlockChange) } state, err := c.NewAPIRoot() if err != nil { return err } err = registerMeteredCharm(c.RegisterURL, state, httpClient, curl.String(), serviceName, client.EnvironmentUUID()) if params.IsCodeNotImplemented(err) { // The state server is too old to support metering. Warn // the user, but don't return an error. logger.Warningf("current state server version does not support charm metering") return nil } return block.ProcessBlockedError(err, block.BlockChange) }
// Run connects to the specified environment and starts the charm // upgrade process. func (c *UpgradeCharmCommand) Run(ctx *cmd.Context) error { client, err := c.NewAPIClient() if err != nil { return err } defer client.Close() oldURL, err := client.ServiceGetCharmURL(c.ServiceName) if err != nil { return err } attrs, err := client.EnvironmentGet() if err != nil { return err } conf, err := config.New(config.NoDefaults, attrs) if err != nil { return err } var newURL *charm.URL if c.SwitchURL != "" { newURL, err = resolveCharmURL(c.SwitchURL, client, conf) if err != nil { return err } } else { // No new URL specified, but revision might have been. newURL = oldURL.WithRevision(c.Revision) } repo, err := charm.InferRepository(newURL.Reference(), ctx.AbsPath(c.RepoPath)) if err != nil { return err } repo = config.SpecializeCharmRepo(repo, conf) // If no explicit revision was set with either SwitchURL // or Revision flags, discover the latest. explicitRevision := true if newURL.Revision == -1 { explicitRevision = false latest, err := charm.Latest(repo, newURL) if err != nil { return err } newURL = newURL.WithRevision(latest) } if *newURL == *oldURL { if explicitRevision { return fmt.Errorf("already running specified charm %q", newURL) } else if newURL.Schema == "cs" { // No point in trying to upgrade a charm store charm when // we just determined that's the latest revision // available. return fmt.Errorf("already running latest charm %q", newURL) } } addedURL, err := addCharmViaAPI(client, ctx, newURL, repo) if err != nil { return err } return client.ServiceSetCharm(c.ServiceName, addedURL.String(), c.Force) }
// Run connects to the environment specified on the command line and bootstraps // a juju in that environment if none already exists. If there is as yet no environments.yaml file, // the user is informed how to create one. func (c *bootstrapCommand) Run(ctx *cmd.Context) (resultErr error) { bootstrapFuncs := getBootstrapFuncs() // Get the cloud definition identified by c.Cloud. If c.Cloud does not // identify a cloud in clouds.yaml, but is the name of a provider, and // that provider implements environs.CloudRegionDetector, we'll // synthesise a Cloud structure with the detected regions and no auth- // types. cloud, err := jujucloud.CloudByName(c.Cloud) if errors.IsNotFound(err) { ctx.Verbosef("cloud %q not found, trying as a provider name", c.Cloud) provider, err := environs.Provider(c.Cloud) if errors.IsNotFound(err) { return errors.NewNotFound(nil, fmt.Sprintf("unknown cloud %q, please try %q", c.Cloud, "juju update-clouds")) } else if err != nil { return errors.Trace(err) } detector, ok := provider.(environs.CloudRegionDetector) if !ok { ctx.Verbosef( "provider %q does not support detecting regions", c.Cloud, ) return errors.NewNotFound(nil, fmt.Sprintf("unknown cloud %q, please try %q", c.Cloud, "juju update-clouds")) } regions, err := detector.DetectRegions() if err != nil && !errors.IsNotFound(err) { // It's not an error to have no regions. return errors.Annotatef(err, "detecting regions for %q cloud provider", c.Cloud, ) } cloud = &jujucloud.Cloud{ Type: c.Cloud, Regions: regions, } } else if err != nil { return errors.Trace(err) } if err := checkProviderType(cloud.Type); errors.IsNotFound(err) { // This error will get handled later. } else if err != nil { return errors.Trace(err) } // Get the credentials and region name. store := c.ClientStore() credential, credentialName, regionName, err := modelcmd.GetCredentials( store, c.Region, c.CredentialName, c.Cloud, cloud.Type, ) if errors.IsNotFound(err) && c.CredentialName == "" { // No credential was explicitly specified, and no credential // was found in credentials.yaml; have the provider detect // credentials from the environment. ctx.Verbosef("no credentials found, checking environment") detected, err := modelcmd.DetectCredential(c.Cloud, cloud.Type) if errors.Cause(err) == modelcmd.ErrMultipleCredentials { return ambiguousCredentialError } else if err != nil { return errors.Trace(err) } // We have one credential so extract it from the map. var oneCredential jujucloud.Credential for _, oneCredential = range detected.AuthCredentials { } credential = &oneCredential regionName = c.Region if regionName == "" { regionName = detected.DefaultRegion } logger.Tracef("authenticating with region %q and %v", regionName, credential) } else if err != nil { return errors.Trace(err) } region, err := getRegion(cloud, c.Cloud, regionName) if err != nil { return errors.Trace(err) } hostedModelUUID, err := utils.NewUUID() if err != nil { return errors.Trace(err) } controllerUUID, err := utils.NewUUID() if err != nil { return errors.Trace(err) } // Create an environment config from the cloud and credentials. configAttrs := map[string]interface{}{ "type": cloud.Type, "name": environs.ControllerModelName, config.UUIDKey: controllerUUID.String(), config.ControllerUUIDKey: controllerUUID.String(), } userConfigAttrs, err := c.config.ReadAttrs(ctx) if err != nil { return errors.Trace(err) } for k, v := range userConfigAttrs { configAttrs[k] = v } logger.Debugf("preparing controller with config: %v", configAttrs) // Read existing current controller, account, model so we can clean up on error. var oldCurrentController string oldCurrentController, err = modelcmd.ReadCurrentController() if err != nil { return errors.Annotate(err, "error reading current controller") } defer func() { if resultErr == nil || errors.IsAlreadyExists(resultErr) { return } if oldCurrentController != "" { if err := modelcmd.WriteCurrentController(oldCurrentController); err != nil { logger.Warningf( "cannot reset current controller to %q: %v", oldCurrentController, err, ) } } if err := store.RemoveController(c.controllerName); err != nil { logger.Warningf( "cannot destroy newly created controller %q details: %v", c.controllerName, err, ) } }() environ, err := environsPrepare( modelcmd.BootstrapContext(ctx), store, environs.PrepareParams{ BaseConfig: configAttrs, ControllerName: c.controllerName, CloudName: c.Cloud, CloudRegion: region.Name, CloudEndpoint: region.Endpoint, CloudStorageEndpoint: region.StorageEndpoint, Credential: *credential, CredentialName: credentialName, }, ) if err != nil { return errors.Trace(err) } // Set the current model to the initial hosted model. accountName, err := store.CurrentAccount(c.controllerName) if err != nil { return errors.Trace(err) } if err := store.UpdateModel(c.controllerName, accountName, c.hostedModelName, jujuclient.ModelDetails{ hostedModelUUID.String(), }); err != nil { return errors.Trace(err) } if err := store.SetCurrentModel(c.controllerName, accountName, c.hostedModelName); err != nil { return errors.Trace(err) } // Set the current controller so "juju status" can be run while // bootstrapping is underway. if err := modelcmd.WriteCurrentController(c.controllerName); err != nil { return errors.Trace(err) } cloudRegion := c.Cloud if region.Name != "" { cloudRegion = fmt.Sprintf("%s/%s", cloudRegion, region.Name) } ctx.Infof( "Creating Juju controller %q on %s", c.controllerName, cloudRegion, ) // If we error out for any reason, clean up the environment. defer func() { if resultErr != nil { if c.KeepBrokenEnvironment { logger.Warningf(` bootstrap failed but --keep-broken was specified so model is not being destroyed. When you are finished diagnosing the problem, remember to run juju destroy-model --force to clean up the model.`[1:]) } else { handleBootstrapError(ctx, resultErr, func() error { return environsDestroy( c.controllerName, environ, store, ) }) } } }() // Block interruption during bootstrap. Providers may also // register for interrupt notification so they can exit early. interrupted := make(chan os.Signal, 1) defer close(interrupted) ctx.InterruptNotify(interrupted) defer ctx.StopInterruptNotify(interrupted) go func() { for _ = range interrupted { ctx.Infof("Interrupt signalled: waiting for bootstrap to exit") } }() // If --metadata-source is specified, override the default tools metadata source so // SyncTools can use it, and also upload any image metadata. var metadataDir string if c.MetadataSource != "" { metadataDir = ctx.AbsPath(c.MetadataSource) } // Merge environ and bootstrap-specific constraints. constraintsValidator, err := environ.ConstraintsValidator() if err != nil { return errors.Trace(err) } bootstrapConstraints, err := constraintsValidator.Merge( c.Constraints, c.BootstrapConstraints, ) if err != nil { return errors.Trace(err) } logger.Infof("combined bootstrap constraints: %v", bootstrapConstraints) hostedModelConfig := map[string]interface{}{ "name": c.hostedModelName, config.UUIDKey: hostedModelUUID.String(), } // We copy across any user supplied attributes to the hosted model config. // But only if the attributes have not been removed from the controller // model config as part of preparing the controller model. controllerConfigAttrs := environ.Config().AllAttrs() for k, v := range userConfigAttrs { if _, ok := controllerConfigAttrs[k]; ok { hostedModelConfig[k] = v } } // Ensure that certain config attributes are not included in the hosted // model config. These attributes may be modified during bootstrap; by // removing them from this map, we ensure the modified values are // inherited. delete(hostedModelConfig, config.AuthKeysConfig) delete(hostedModelConfig, config.AgentVersionKey) // Check whether the Juju GUI must be installed in the controller. // Leaving this value empty means no GUI will be installed. var guiDataSourceBaseURL string if !c.noGUI { guiDataSourceBaseURL = common.GUIDataSourceBaseURL() } err = bootstrapFuncs.Bootstrap(modelcmd.BootstrapContext(ctx), environ, bootstrap.BootstrapParams{ ModelConstraints: c.Constraints, BootstrapConstraints: bootstrapConstraints, BootstrapSeries: c.BootstrapSeries, BootstrapImage: c.BootstrapImage, Placement: c.Placement, UploadTools: c.UploadTools, AgentVersion: c.AgentVersion, MetadataDir: metadataDir, HostedModelConfig: hostedModelConfig, GUIDataSourceBaseURL: guiDataSourceBaseURL, }) if err != nil { return errors.Annotate(err, "failed to bootstrap model") } if err := c.SetModelName(c.hostedModelName); err != nil { return errors.Trace(err) } err = c.setBootstrapEndpointAddress(environ) if err != nil { return errors.Annotate(err, "saving bootstrap endpoint address") } // To avoid race conditions when running scripted bootstraps, wait // for the controller's machine agent to be ready to accept commands // before exiting this bootstrap command. return c.waitForAgentInitialisation(ctx) }
func (c *DeployCommand) Run(ctx *cmd.Context) error { client, err := c.NewAPIClient() if err != nil { return err } defer client.Close() conf, err := service.GetClientConfig(client) if err != nil { return err } if err := c.CheckProvider(conf); err != nil { return err } csClient, err := newCharmStoreClient() if err != nil { return errors.Trace(err) } defer csClient.jar.Save() curl, repo, err := resolveCharmURL(c.CharmName, csClient.params, ctx.AbsPath(c.RepoPath), conf) if err != nil { return errors.Trace(err) } curl, err = addCharmViaAPI(client, ctx, curl, repo, csClient) if err != nil { return block.ProcessBlockedError(err, block.BlockChange) } if c.BumpRevision { ctx.Infof("--upgrade (or -u) is deprecated and ignored; charms are always deployed with a unique revision.") } requestedNetworks, err := networkNamesToTags(parseNetworks(c.Networks)) if err != nil { return err } // We need to ensure network names are valid below, but we don't need them here. _, err = networkNamesToTags(c.Constraints.IncludeNetworks()) if err != nil { return err } _, err = networkNamesToTags(c.Constraints.ExcludeNetworks()) if err != nil { return err } haveNetworks := len(requestedNetworks) > 0 || c.Constraints.HaveNetworks() charmInfo, err := client.CharmInfo(curl.String()) if err != nil { return err } numUnits := c.NumUnits if charmInfo.Meta.Subordinate { if !constraints.IsEmpty(&c.Constraints) { return errors.New("cannot use --constraints with subordinate service") } if numUnits == 1 && c.ToMachineSpec == "" { numUnits = 0 } else { return errors.New("cannot use --num-units or --to with subordinate service") } } serviceName := c.ServiceName if serviceName == "" { serviceName = charmInfo.Meta.Name } var configYAML []byte if c.Config.Path != "" { configYAML, err = c.Config.Read(ctx) if err != nil { return err } } // If storage is specified, we attempt to use a new API on the service facade. if len(c.Storage) > 0 { notSupported := errors.New("cannot deploy charms with storage: not supported by the API server") serviceClient, err := c.newServiceAPIClient() if err != nil { return notSupported } defer serviceClient.Close() err = serviceClient.ServiceDeploy( curl.String(), serviceName, numUnits, string(configYAML), c.Constraints, c.ToMachineSpec, requestedNetworks, c.Storage, ) if params.IsCodeNotImplemented(err) { return notSupported } return block.ProcessBlockedError(err, block.BlockChange) } err = client.ServiceDeployWithNetworks( curl.String(), serviceName, numUnits, string(configYAML), c.Constraints, c.ToMachineSpec, requestedNetworks, ) if params.IsCodeNotImplemented(err) { if haveNetworks { return errors.New("cannot use --networks/--constraints networks=...: not supported by the API server") } err = client.ServiceDeploy( curl.String(), serviceName, numUnits, string(configYAML), c.Constraints, c.ToMachineSpec) } if err != nil { return block.ProcessBlockedError(err, block.BlockChange) } state, err := c.NewAPIRoot() if err != nil { return err } err = registerMeteredCharm(c.RegisterURL, state, csClient.jar, curl.String(), serviceName, client.EnvironmentUUID()) if err != nil { return err } return block.ProcessBlockedError(err, block.BlockChange) }
func (c *DeployCommand) deployCharmOrBundle(ctx *cmd.Context, client *api.Client) error { deployer := serviceDeployer{ctx, c} // We may have been given a local bundle file. bundlePath := c.CharmOrBundle bundleData, err := charmrepo.ReadBundleFile(bundlePath) if err != nil { // We may have been given a local bundle archive or exploded directory. if bundle, burl, pathErr := charmrepo.NewBundleAtPath(bundlePath); pathErr == nil { bundleData = bundle.Data() bundlePath = burl.String() err = pathErr } } // If not a bundle then maybe a local charm. if err != nil { // Charm may have been supplied via a path reference. ch, curl, charmErr := charmrepo.NewCharmAtPathForceSeries(c.CharmOrBundle, c.Series, c.Force) if charmErr == nil { if curl, charmErr = client.AddLocalCharm(curl, ch); charmErr != nil { return charmErr } id := charmstore.CharmID{ URL: curl, // Local charms don't need a channel. } var csMac *macaroon.Macaroon // local charms don't need one. return c.deployCharm(deployCharmArgs{ id: id, csMac: csMac, series: curl.Series, ctx: ctx, client: client, deployer: &deployer, }) } // We check for several types of known error which indicate // that the supplied reference was indeed a path but there was // an issue reading the charm located there. if charm.IsMissingSeriesError(charmErr) { return charmErr } if charm.IsUnsupportedSeriesError(charmErr) { return errors.Errorf("%v. Use --force to deploy the charm anyway.", charmErr) } if errors.Cause(charmErr) == zip.ErrFormat { return errors.Errorf("invalid charm or bundle provided at %q", c.CharmOrBundle) } err = charmErr } if err != nil { if _, ok := errors.Cause(err).(*charmrepo.NotFoundError); ok { return errors.Errorf("no charm or bundle found at %q: %v", c.CharmOrBundle, err) } if errors.Cause(err) != os.ErrNotExist { return err } // We've got a "not exists" error. Attempt to interpret the supplied // charm or bundle reference as a URL. } repoPath := ctx.AbsPath(c.RepoPath) conf, err := getClientConfig(client) if err != nil { return err } bakeryClient, err := c.BakeryClient() if err != nil { return errors.Trace(err) } csClient := newCharmStoreClient(bakeryClient).WithChannel(c.Channel) resolver := newCharmURLResolver(conf, csClient, repoPath) var charmOrBundleURL *charm.URL var repo charmrepo.Interface var supportedSeries []string // If we don't already have a bundle loaded, we try the charm store for a charm or bundle. if bundleData == nil { // Charm or bundle has been supplied as a URL so we resolve and deploy using the store. charmOrBundleURL, c.Channel, supportedSeries, repo, err = resolver.resolve(c.CharmOrBundle) if charm.IsUnsupportedSeriesError(err) { return errors.Errorf("%v. Use --force to deploy the charm anyway.", err) } if err != nil { return errors.Trace(err) } if charmOrBundleURL.Series == "bundle" { // Load the bundle entity. bundle, err := repo.GetBundle(charmOrBundleURL) if err != nil { return errors.Trace(err) } bundleData = bundle.Data() bundlePath = charmOrBundleURL.String() } } // Handle a bundle. if bundleData != nil { if flags := getFlags(c.flagSet, charmOnlyFlags); len(flags) > 0 { return errors.Errorf("Flags provided but not supported when deploying a bundle: %s.", strings.Join(flags, ", ")) } // TODO(ericsnow) Do something with the CS macaroons that were returned? if _, err := deployBundle( bundleData, c.Channel, client, &deployer, resolver, ctx, c.BundleStorage, ); err != nil { return errors.Trace(err) } ctx.Infof("deployment of bundle %q completed", bundlePath) return nil } // Handle a charm. if flags := getFlags(c.flagSet, bundleOnlyFlags); len(flags) > 0 { return errors.Errorf("Flags provided but not supported when deploying a charm: %s.", strings.Join(flags, ", ")) } // Get the series to use. series, message, err := charmSeries(c.Series, charmOrBundleURL.Series, supportedSeries, c.Force, conf) if charm.IsUnsupportedSeriesError(err) { return errors.Errorf("%v. Use --force to deploy the charm anyway.", err) } // Store the charm in state. curl, csMac, err := addCharmFromURL(client, charmOrBundleURL, c.Channel, repo) if err != nil { if err1, ok := errors.Cause(err).(*termsRequiredError); ok { terms := strings.Join(err1.Terms, " ") return errors.Errorf(`Declined: please agree to the following terms %s. Try: "juju agree %s"`, terms, terms) } return errors.Annotatef(err, "storing charm for URL %q", charmOrBundleURL) } ctx.Infof("Added charm %q to the model.", curl) ctx.Infof("Deploying charm %q %v.", curl, fmt.Sprintf(message, series)) id := charmstore.CharmID{ URL: curl, Channel: c.Channel, } return c.deployCharm(deployCharmArgs{ id: id, csMac: csMac, series: series, ctx: ctx, client: client, deployer: &deployer, }) }