func ConfigureMachine(ctx environs.BootstrapContext, client ssh.Client, host string, machineConfig *cloudinit.MachineConfig) error { // Bootstrap is synchronous, and will spawn a subprocess // to complete the procedure. If the user hits Ctrl-C, // SIGINT is sent to the foreground process attached to // the terminal, which will be the ssh subprocess at this // point. For that reason, we do not call StopInterruptNotify // until this function completes. cloudcfg := coreCloudinit.New() cloudcfg.SetAptUpdate(machineConfig.EnableOSRefreshUpdate) cloudcfg.SetAptUpgrade(machineConfig.EnableOSUpgrade) udata, err := cloudinit.NewUserdataConfig(machineConfig, cloudcfg) if err != nil { return err } if err := udata.ConfigureJuju(); err != nil { return err } configScript, err := sshinit.ConfigureScript(cloudcfg) if err != nil { return err } script := shell.DumpFileOnErrorScript(machineConfig.CloudInitOutputLog) + configScript return sshinit.RunConfigureScript(script, sshinit.ConfigureParams{ Host: "ubuntu@" + host, Client: client, Config: cloudcfg, ProgressWriter: ctx.GetStderr(), }) }
// PrepareForBootstrap is specified in the EnvironProvider interface. func (prov *azureEnvironProvider) PrepareForBootstrap(ctx environs.BootstrapContext, cfg *config.Config) (environs.Environ, error) { // Ensure that internal configuration is not specified, and then set // what we can now. We only need to do this during bootstrap. Validate // will check for changes later. unknownAttrs := cfg.UnknownAttrs() for _, key := range internalConfigAttributes { if _, ok := unknownAttrs[key]; ok { return nil, errors.Errorf(`internal config %q must not be specified`, key) } } // Record the UUID that will be used for the controller environment. cfg, err := cfg.Apply(map[string]interface{}{ configAttrControllerResourceGroup: resourceGroupName(cfg), }) if err != nil { return nil, errors.Annotate(err, "recording controller-resource-group") } env, err := prov.Open(cfg) if err != nil { return nil, errors.Trace(err) } if ctx.ShouldVerifyCredentials() { if err := verifyCredentials(env.(*azureEnviron)); err != nil { return nil, errors.Trace(err) } } return env, nil }
// Bootstrap is a common implementation of the Bootstrap method defined on // environs.Environ; we strongly recommend that this implementation be used // when writing a new provider. func Bootstrap(ctx environs.BootstrapContext, env environs.Environ, args environs.BootstrapParams) (arch, series string, _ environs.BootstrapFinalizer, err error) { // TODO make safe in the case of racing Bootstraps // If two Bootstraps are called concurrently, there's // no way to make sure that only one succeeds. var inst instance.Instance defer func() { handleBootstrapError(err, ctx, inst, env) }() // First thing, ensure we have tools otherwise there's no point. series = config.PreferredSeries(env.Config()) availableTools, err := args.AvailableTools.Match(coretools.Filter{Series: series}) if err != nil { return "", "", nil, err } // Get the bootstrap SSH client. Do this early, so we know // not to bother with any of the below if we can't finish the job. client := ssh.DefaultClient if client == nil { // This should never happen: if we don't have OpenSSH, then // go.crypto/ssh should be used with an auto-generated key. return "", "", nil, fmt.Errorf("no SSH client available") } machineConfig, err := environs.NewBootstrapMachineConfig(args.Constraints, series) if err != nil { return "", "", nil, err } machineConfig.EnableOSRefreshUpdate = env.Config().EnableOSRefreshUpdate() machineConfig.EnableOSUpgrade = env.Config().EnableOSUpgrade() fmt.Fprintln(ctx.GetStderr(), "Launching instance") inst, hw, _, err := env.StartInstance(environs.StartInstanceParams{ Constraints: args.Constraints, Tools: availableTools, MachineConfig: machineConfig, Placement: args.Placement, }) if err != nil { return "", "", nil, fmt.Errorf("cannot start bootstrap instance: %v", err) } fmt.Fprintf(ctx.GetStderr(), " - %s\n", inst.Id()) err = SaveState(env.Storage(), &BootstrapState{ StateInstances: []instance.Id{inst.Id()}, }) if err != nil { return "", "", nil, fmt.Errorf("cannot save state: %v", err) } finalize := func(ctx environs.BootstrapContext, mcfg *cloudinit.MachineConfig) error { mcfg.InstanceId = inst.Id() mcfg.HardwareCharacteristics = hw if err := environs.FinishMachineConfig(mcfg, env.Config()); err != nil { return err } return FinishBootstrap(ctx, client, inst, mcfg) } return *hw.Arch, series, finalize, nil }
// PrepareForBootstrap implements environs.Environ. func (env *environ) PrepareForBootstrap(ctx environs.BootstrapContext) error { if ctx.ShouldVerifyCredentials() { if err := env.gce.VerifyCredentials(); err != nil { return errors.Trace(err) } } return nil }
// waitSSH waits for the instance to be assigned a routable // address, then waits until we can connect to it via SSH. // // waitSSH attempts on all addresses returned by the instance // in parallel; the first succeeding one wins. We ensure that // private addresses are for the correct machine by checking // the presence of a file on the machine that contains the // machine's nonce. The "checkHostScript" is a bash script // that performs this file check. func waitSSH(ctx environs.BootstrapContext, interrupted <-chan os.Signal, client ssh.Client, checkHostScript string, inst addresser, timeout config.SSHTimeoutOpts) (addr string, err error) { globalTimeout := time.After(timeout.Timeout) pollAddresses := time.NewTimer(0) // checker checks each address in a loop, in parallel, // until one succeeds, the global timeout is reached, // or the tomb is killed. checker := parallelHostChecker{ Try: parallel.NewTry(0, nil), client: client, stderr: ctx.GetStderr(), active: make(map[network.Address]chan struct{}), checkDelay: timeout.RetryDelay, checkHostScript: checkHostScript, } defer checker.wg.Wait() defer checker.Kill() fmt.Fprintln(ctx.GetStderr(), "Waiting for address") for { select { case <-pollAddresses.C: pollAddresses.Reset(timeout.AddressesDelay) if err := inst.Refresh(); err != nil { return "", fmt.Errorf("refreshing addresses: %v", err) } addresses, err := inst.Addresses() if err != nil { return "", fmt.Errorf("getting addresses: %v", err) } checker.UpdateAddresses(addresses) case <-globalTimeout: checker.Close() lastErr := checker.Wait() format := "waited for %v " args := []interface{}{timeout.Timeout} if len(checker.active) == 0 { format += "without getting any addresses" } else { format += "without being able to connect" } if lastErr != nil && lastErr != parallel.ErrStopped { format += ": %v" args = append(args, lastErr) } return "", fmt.Errorf(format, args...) case <-interrupted: return "", fmt.Errorf("interrupted") case <-checker.Dead(): result, err := checker.Result() if err != nil { return "", err } return result.(*hostChecker).addr.Value, nil } } }
// Bootstrap is a common implementation of the Bootstrap method defined on // environs.Environ; we strongly recommend that this implementation be used // when writing a new provider. func Bootstrap(ctx environs.BootstrapContext, env environs.Environ, args environs.BootstrapParams) (err error) { // TODO make safe in the case of racing Bootstraps // If two Bootstraps are called concurrently, there's // no way to make sure that only one succeeds. var inst instance.Instance defer func() { handleBootstrapError(err, ctx, inst, env) }() network.InitializeFromConfig(env.Config()) // First thing, ensure we have tools otherwise there's no point. selectedTools, err := EnsureBootstrapTools(ctx, env, config.PreferredSeries(env.Config()), args.Constraints.Arch) if err != nil { return err } // Get the bootstrap SSH client. Do this early, so we know // not to bother with any of the below if we can't finish the job. client := ssh.DefaultClient if client == nil { // This should never happen: if we don't have OpenSSH, then // go.crypto/ssh should be used with an auto-generated key. return fmt.Errorf("no SSH client available") } privateKey, err := GenerateSystemSSHKey(env) if err != nil { return err } machineConfig := environs.NewBootstrapMachineConfig(privateKey) fmt.Fprintln(ctx.GetStderr(), "Launching instance") inst, hw, _, err := env.StartInstance(environs.StartInstanceParams{ Constraints: args.Constraints, Tools: selectedTools, MachineConfig: machineConfig, Placement: args.Placement, }) if err != nil { return fmt.Errorf("cannot start bootstrap instance: %v", err) } fmt.Fprintf(ctx.GetStderr(), " - %s\n", inst.Id()) machineConfig.InstanceId = inst.Id() machineConfig.HardwareCharacteristics = hw err = bootstrap.SaveState( env.Storage(), &bootstrap.BootstrapState{ StateInstances: []instance.Id{inst.Id()}, }) if err != nil { return fmt.Errorf("cannot save state: %v", err) } return FinishBootstrap(ctx, client, inst, machineConfig) }
// PrepareForBootstrap is specified in the EnvironProvider interface. func (p maasEnvironProvider) PrepareForBootstrap(ctx environs.BootstrapContext, cfg *config.Config) (environs.Environ, error) { env, err := p.Open(cfg) if err != nil { return nil, err } if ctx.ShouldVerifyCredentials() { if err := verifyCredentials(env.(*maasEnviron)); err != nil { return nil, err } } return env, nil }
// PrepareForBootstrap is specified in the EnvironProvider interface. func (p joyentProvider) PrepareForBootstrap(ctx environs.BootstrapContext, cfg *config.Config) (environs.Environ, error) { e, err := p.Open(cfg) if err != nil { return nil, errors.Trace(err) } if ctx.ShouldVerifyCredentials() { if err := verifyCredentials(e.(*joyentEnviron)); err != nil { return nil, errors.Trace(err) } } return e, nil }
// handleBootstrapError cleans up after a failed bootstrap. func handleBootstrapError(err error, ctx environs.BootstrapContext, inst instance.Instance, env environs.Environ) { if err == nil { return } logger.Errorf("bootstrap failed: %v", err) ch := make(chan os.Signal, 1) ctx.InterruptNotify(ch) defer ctx.StopInterruptNotify(ch) defer close(ch) go func() { for _ = range ch { fmt.Fprintln(ctx.GetStderr(), "Cleaning up failed bootstrap") } }() if inst != nil { fmt.Fprintln(ctx.GetStderr(), "Stopping instance...") if stoperr := env.StopInstances(inst.Id()); stoperr != nil { logger.Errorf("cannot stop failed bootstrap instance %q: %v", inst.Id(), stoperr) } else { // set to nil so we know we can safely delete the state file inst = nil } } // We only delete the bootstrap state file if either we didn't // start an instance, or we managed to cleanly stop it. if inst == nil { if rmerr := bootstrap.DeleteStateFile(env.Storage()); rmerr != nil { logger.Errorf("cannot delete bootstrap state file: %v", rmerr) } } }
// PrepareForBootstrap implements environs.EnvironProvider. func (p environProvider) PrepareForBootstrap(ctx environs.BootstrapContext, cfg *config.Config) (environs.Environ, error) { // TODO(ericsnow) Do some of what happens in local provider's // PrepareForBootstrap()? Only if "remote" is local host? env, err := newEnviron(cfg, newRawProvider) if err != nil { return nil, errors.Trace(err) } if ctx.ShouldVerifyCredentials() { if err := env.verifyCredentials(); err != nil { return nil, errors.Trace(err) } } return env, nil }
func (p environProvider) PrepareForBootstrap(ctx environs.BootstrapContext, cfg *config.Config) (environs.Environ, error) { cfg, err := p.PrepareForCreateEnvironment(cfg) if err != nil { return nil, err } e, err := p.Open(cfg) if err != nil { return nil, err } if ctx.ShouldVerifyCredentials() { if err := verifyCredentials(e.(*environ)); err != nil { return nil, err } } return e, nil }
// PrepareForBootstrap is specified in the EnvironProvider interface. func (prov azureEnvironProvider) PrepareForBootstrap(ctx environs.BootstrapContext, cfg *config.Config) (environs.Environ, error) { cfg, err := prov.PrepareForCreateEnvironment(cfg) if err != nil { return nil, errors.Trace(err) } env, err := prov.Open(cfg) if err != nil { return nil, errors.Trace(err) } if ctx.ShouldVerifyCredentials() { if err := verifyCredentials(env.(*azureEnviron)); err != nil { return nil, errors.Trace(err) } } return env, nil }
func validateBootstrapVPC(apiClient vpcAPIClient, region, vpcID string, forceVPCID bool, ctx environs.BootstrapContext) error { if vpcID == vpcIDNone { ctx.Infof("Using EC2-classic features or default VPC in region %q", region) } if !isVPCIDSet(vpcID) { return nil } err := validateVPC(apiClient, vpcID) switch { case isVPCNotUsableError(err): // VPC missing or has no subnets at all. return errors.Annotate(err, vpcNotUsableForBootstrapErrorPrefix) case isVPCNotRecommendedError(err): // VPC does not meet minumum validation criteria. if !forceVPCID { return errors.Annotatef(err, vpcNotRecommendedErrorPrefix, vpcID) } ctx.Infof(vpcNotRecommendedButForcedWarning) case err != nil: // Anything else unexpected while validating the VPC. return errors.Annotate(err, cannotValidateVPCErrorPrefix) } ctx.Infof("Using VPC %q in region %q", vpcID, region) return nil }
// UploadTools uploads tools for the specified series and any other relevant series to // the environment storage, after which it sets the agent-version. If forceVersion is true, // we allow uploading even when the agent-version is already set in the environment. func UploadTools(ctx environs.BootstrapContext, env environs.Environ, toolsArch *string, forceVersion bool, bootstrapSeries ...string) error { logger.Infof("checking that upload is possible") // Check the series are valid. for _, series := range bootstrapSeries { if _, err := ubuntu.SeriesVersion(series); err != nil { return err } } // See that we are allowed to upload the tools. if err := validateUploadAllowed(env, toolsArch, forceVersion); err != nil { return err } // Make storage interruptible. interrupted := make(chan os.Signal, 1) interruptStorage := make(chan struct{}) ctx.InterruptNotify(interrupted) defer ctx.StopInterruptNotify(interrupted) defer close(interrupted) go func() { defer close(interruptStorage) // closing interrupts all uploads if _, ok := <-interrupted; ok { ctx.Infof("cancelling tools upload") } }() stor := newInterruptibleStorage(env.Storage(), interruptStorage) cfg := env.Config() explicitVersion := uploadVersion(version.Current.Number, nil) uploadSeries := SeriesToUpload(cfg, bootstrapSeries) ctx.Infof("uploading tools for series %s", uploadSeries) tools, err := sync.Upload(stor, &explicitVersion, uploadSeries...) if err != nil { return err } cfg, err = cfg.Apply(map[string]interface{}{ "agent-version": tools.Version.Number.String(), }) if err == nil { err = env.SetConfig(cfg) } if err != nil { return fmt.Errorf("failed to update environment configuration: %v", err) } return nil }
// Bootstrap bootstraps the given environment. The supplied constraints are // used to provision the instance, and are also set within the bootstrapped // environment. func Bootstrap(ctx environs.BootstrapContext, environ environs.Environ, args BootstrapParams) error { cfg := environ.Config() network.InitializeFromConfig(cfg) if secret := cfg.AdminSecret(); secret == "" { return errors.Errorf("environment configuration has no admin-secret") } if authKeys := ssh.SplitAuthorisedKeys(cfg.AuthorizedKeys()); len(authKeys) == 0 { // Apparently this can never happen, so it's not tested. But, one day, // Config will act differently (it's pretty crazy that, AFAICT, the // authorized-keys are optional config settings... but it's impossible // to actually *create* a config without them)... and when it does, // we'll be here to catch this problem early. return errors.Errorf("environment configuration has no authorized-keys") } if _, hasCACert := cfg.CACert(); !hasCACert { return errors.Errorf("environment configuration has no ca-cert") } if _, hasCAKey := cfg.CAPrivateKey(); !hasCAKey { return errors.Errorf("environment configuration has no ca-private-key") } // Set default tools metadata source, add image metadata source, // then verify constraints. Providers may rely on image metadata // for constraint validation. var imageMetadata []*imagemetadata.ImageMetadata if args.MetadataDir != "" { var err error imageMetadata, err = setPrivateMetadataSources(environ, args.MetadataDir) if err != nil { return err } } if err := validateConstraints(environ, args.Constraints); err != nil { return err } _, supportsNetworking := environs.SupportsNetworking(environ) ctx.Infof("Bootstrapping environment %q", cfg.Name()) logger.Debugf("environment %q supports service/machine networks: %v", cfg.Name(), supportsNetworking) disableNetworkManagement, _ := cfg.DisableNetworkManagement() logger.Debugf("network management by juju enabled: %v", !disableNetworkManagement) availableTools, err := findAvailableTools(environ, args.AgentVersion, args.Constraints.Arch, args.UploadTools) if errors.IsNotFound(err) { return errors.New(noToolsMessage) } else if err != nil { return err } if lxcMTU, ok := cfg.LXCDefaultMTU(); ok { logger.Debugf("using MTU %v for all created LXC containers' network interfaces", lxcMTU) } // If we're uploading, we must override agent-version; // if we're not uploading, we want to ensure we have an // agent-version set anyway, to appease FinishInstanceConfig. // In the latter case, setBootstrapTools will later set // agent-version to the correct thing. agentVersion := version.Current if args.AgentVersion != nil { agentVersion = *args.AgentVersion } if cfg, err = cfg.Apply(map[string]interface{}{ "agent-version": agentVersion.String(), }); err != nil { return err } if err = environ.SetConfig(cfg); err != nil { return err } ctx.Infof("Starting new instance for initial state server") arch, series, finalizer, err := environ.Bootstrap(ctx, environs.BootstrapParams{ Constraints: args.Constraints, Placement: args.Placement, AvailableTools: availableTools, }) if err != nil { return err } matchingTools, err := availableTools.Match(coretools.Filter{ Arch: arch, Series: series, }) if err != nil { return err } selectedTools, err := setBootstrapTools(environ, matchingTools) if err != nil { return err } if selectedTools.URL == "" { if !args.UploadTools { logger.Warningf("no prepackaged tools available") } ctx.Infof("Building tools to upload (%s)", selectedTools.Version) builtTools, err := sync.BuildToolsTarball(&selectedTools.Version.Number, cfg.AgentStream()) if err != nil { return errors.Annotate(err, "cannot upload bootstrap tools") } defer os.RemoveAll(builtTools.Dir) filename := filepath.Join(builtTools.Dir, builtTools.StorageName) selectedTools.URL = fmt.Sprintf("file://%s", filename) selectedTools.Size = builtTools.Size selectedTools.SHA256 = builtTools.Sha256Hash } ctx.Infof("Installing Juju agent on bootstrap instance") instanceConfig, err := instancecfg.NewBootstrapInstanceConfig(args.Constraints, series) if err != nil { return err } instanceConfig.Tools = selectedTools instanceConfig.CustomImageMetadata = imageMetadata if err := finalizer(ctx, instanceConfig); err != nil { return err } ctx.Infof("Bootstrap agent installed") return nil }
// BootstrapInstance creates a new instance with the series of its choice, // constrained to those of the available tools, and // returns the instance result, series, and a function that // must be called to finalize the bootstrap process by transferring // the tools and installing the initial Juju controller. // This method is called by Bootstrap above, which implements environs.Bootstrap, but // is also exported so that providers can manipulate the started instance. func BootstrapInstance(ctx environs.BootstrapContext, env environs.Environ, args environs.BootstrapParams, ) (_ *environs.StartInstanceResult, selectedSeries string, _ environs.BootstrapFinalizer, err error) { // TODO make safe in the case of racing Bootstraps // If two Bootstraps are called concurrently, there's // no way to make sure that only one succeeds. // First thing, ensure we have tools otherwise there's no point. if args.BootstrapSeries != "" { selectedSeries = args.BootstrapSeries } else { selectedSeries = config.PreferredSeries(env.Config()) } availableTools, err := args.AvailableTools.Match(coretools.Filter{ Series: selectedSeries, }) if err != nil { return nil, "", nil, err } // Filter image metadata to the selected series. var imageMetadata []*imagemetadata.ImageMetadata seriesVersion, err := series.SeriesVersion(selectedSeries) if err != nil { return nil, "", nil, errors.Trace(err) } for _, m := range args.ImageMetadata { if m.Version != seriesVersion { continue } imageMetadata = append(imageMetadata, m) } // Get the bootstrap SSH client. Do this early, so we know // not to bother with any of the below if we can't finish the job. client := ssh.DefaultClient if client == nil { // This should never happen: if we don't have OpenSSH, then // go.crypto/ssh should be used with an auto-generated key. return nil, "", nil, fmt.Errorf("no SSH client available") } publicKey, err := simplestreams.UserPublicSigningKey() if err != nil { return nil, "", nil, err } envCfg := env.Config() instanceConfig, err := instancecfg.NewBootstrapInstanceConfig( args.ControllerConfig, args.BootstrapConstraints, args.ModelConstraints, selectedSeries, publicKey, ) if err != nil { return nil, "", nil, err } instanceConfig.EnableOSRefreshUpdate = env.Config().EnableOSRefreshUpdate() instanceConfig.EnableOSUpgrade = env.Config().EnableOSUpgrade() instanceConfig.Tags = instancecfg.InstanceTags(envCfg.UUID(), args.ControllerConfig.ControllerUUID(), envCfg, instanceConfig.Jobs) maybeSetBridge := func(icfg *instancecfg.InstanceConfig) { // If we need to override the default bridge name, do it now. When // args.ContainerBridgeName is empty, the default names for LXC // (lxcbr0) and KVM (virbr0) will be used. if args.ContainerBridgeName != "" { logger.Debugf("using %q as network bridge for all container types", args.ContainerBridgeName) if icfg.AgentEnvironment == nil { icfg.AgentEnvironment = make(map[string]string) } icfg.AgentEnvironment[agent.LxcBridge] = args.ContainerBridgeName } } maybeSetBridge(instanceConfig) cloudRegion := args.CloudName if args.CloudRegion != "" { cloudRegion += "/" + args.CloudRegion } fmt.Fprintf(ctx.GetStderr(), "Launching controller instance(s) on %s...\n", cloudRegion) // Print instance status reports status changes during provisioning. // Note the carriage returns, meaning subsequent prints are to the same // line of stderr, not a new line. instanceStatus := func(settableStatus status.Status, info string, data map[string]interface{}) error { // The data arg is not expected to be used in this case, but // print it, rather than ignore it, if we get something. dataString := "" if len(data) > 0 { dataString = fmt.Sprintf(" %v", data) } fmt.Fprintf(ctx.GetStderr(), " - %s%s\r", info, dataString) return nil } // Likely used after the final instanceStatus call to white-out the // current stderr line before the next use, removing any residual status // reporting output. statusCleanup := func(info string) error { // The leading spaces account for the leading characters // emitted by instanceStatus above. fmt.Fprintf(ctx.GetStderr(), " %s\r", info) return nil } result, err := env.StartInstance(environs.StartInstanceParams{ ControllerUUID: args.ControllerConfig.ControllerUUID(), Constraints: args.BootstrapConstraints, Tools: availableTools, InstanceConfig: instanceConfig, Placement: args.Placement, ImageMetadata: imageMetadata, StatusCallback: instanceStatus, CleanupCallback: statusCleanup, }) if err != nil { return nil, "", nil, errors.Annotate(err, "cannot start bootstrap instance") } // We need some padding below to overwrite any previous messages. We'll use a width of 40. msg := fmt.Sprintf(" - %s", result.Instance.Id()) if len(msg) < 40 { padding := make([]string, 40-len(msg)) msg += strings.Join(padding, " ") } fmt.Fprintln(ctx.GetStderr(), msg) finalize := func(ctx environs.BootstrapContext, icfg *instancecfg.InstanceConfig, opts environs.BootstrapDialOpts) error { icfg.Bootstrap.BootstrapMachineInstanceId = result.Instance.Id() icfg.Bootstrap.BootstrapMachineHardwareCharacteristics = result.Hardware envConfig := env.Config() if result.Config != nil { updated, err := envConfig.Apply(result.Config.UnknownAttrs()) if err != nil { return errors.Trace(err) } envConfig = updated } if err := instancecfg.FinishInstanceConfig(icfg, envConfig); err != nil { return err } maybeSetBridge(icfg) return FinishBootstrap(ctx, client, env, result.Instance, icfg, opts) } return result, selectedSeries, finalize, nil }
// Bootstrap bootstraps the given environment. The supplied constraints are // used to provision the instance, and are also set within the bootstrapped // environment. func Bootstrap(ctx environs.BootstrapContext, environ environs.Environ, args BootstrapParams) error { if err := args.Validate(); err != nil { return errors.Annotate(err, "validating bootstrap parameters") } cfg := environ.Config() if authKeys := ssh.SplitAuthorisedKeys(cfg.AuthorizedKeys()); len(authKeys) == 0 { // Apparently this can never happen, so it's not tested. But, one day, // Config will act differently (it's pretty crazy that, AFAICT, the // authorized-keys are optional config settings... but it's impossible // to actually *create* a config without them)... and when it does, // we'll be here to catch this problem early. return errors.Errorf("model configuration has no authorized-keys") } _, supportsNetworking := environs.SupportsNetworking(environ) logger.Debugf("model %q supports service/machine networks: %v", cfg.Name(), supportsNetworking) disableNetworkManagement, _ := cfg.DisableNetworkManagement() logger.Debugf("network management by juju enabled: %v", !disableNetworkManagement) // Set default tools metadata source, add image metadata source, // then verify constraints. Providers may rely on image metadata // for constraint validation. var customImageMetadata []*imagemetadata.ImageMetadata if args.MetadataDir != "" { var err error customImageMetadata, err = setPrivateMetadataSources(args.MetadataDir) if err != nil { return err } } var bootstrapSeries *string if args.BootstrapSeries != "" { bootstrapSeries = &args.BootstrapSeries } var bootstrapArchForImageSearch string if args.BootstrapConstraints.Arch != nil { bootstrapArchForImageSearch = *args.BootstrapConstraints.Arch } else if args.ModelConstraints.Arch != nil { bootstrapArchForImageSearch = *args.ModelConstraints.Arch } else { bootstrapArchForImageSearch = arch.HostArch() // We no longer support i386. if bootstrapArchForImageSearch == arch.I386 { bootstrapArchForImageSearch = arch.AMD64 } } ctx.Verbosef("Loading image metadata") imageMetadata, err := bootstrapImageMetadata(environ, bootstrapSeries, bootstrapArchForImageSearch, args.BootstrapImage, &customImageMetadata, ) if err != nil { return errors.Trace(err) } // We want to determine a list of valid architectures for which to pick tools and images. // This includes architectures from custom and other available image metadata. architectures := set.NewStrings() if len(customImageMetadata) > 0 { for _, customMetadata := range customImageMetadata { architectures.Add(customMetadata.Arch) } } if len(imageMetadata) > 0 { for _, iMetadata := range imageMetadata { architectures.Add(iMetadata.Arch) } } constraintsValidator, err := environ.ConstraintsValidator() if err != nil { return err } constraintsValidator.UpdateVocabulary(constraints.Arch, architectures.SortedValues()) bootstrapConstraints, err := constraintsValidator.Merge( args.ModelConstraints, args.BootstrapConstraints, ) if err != nil { return errors.Trace(err) } // The arch we use to find tools isn't the boostrapConstraints arch. // We copy the constraints arch to a separate variable and // update it from the host arch if not specified. // (axw) This is still not quite right: // For e.g. if there is a MAAS with only ARM64 machines, // on an AMD64 client, we're going to look for only AMD64 tools, // limiting what the provider can bootstrap anyway. var bootstrapArch string if bootstrapConstraints.Arch != nil { bootstrapArch = *bootstrapConstraints.Arch } else { // If no arch is specified as a constraint, we'll bootstrap // on the same arch as the client used to bootstrap. bootstrapArch = arch.HostArch() // We no longer support controllers on i386. // If we are bootstrapping from an i386 client, // we'll look for amd64 tools. if bootstrapArch == arch.I386 { bootstrapArch = arch.AMD64 } } var availableTools coretools.List if !args.BuildAgent { ctx.Infof("Looking for packaged Juju agent version %s for %s", args.AgentVersion, bootstrapArch) availableTools, err = findPackagedTools(environ, args.AgentVersion, &bootstrapArch, bootstrapSeries) if err != nil && !errors.IsNotFound(err) { return err } } // If there are no prepackaged tools and a specific version has not been // requested, look for or build a local binary. var builtTools *sync.BuiltAgent if len(availableTools) == 0 && (args.AgentVersion == nil || isCompatibleVersion(*args.AgentVersion, jujuversion.Current)) { if args.BuildAgentTarball == nil { return errors.New("cannot build agent binary to upload") } if err := validateUploadAllowed(environ, &bootstrapArch, bootstrapSeries, constraintsValidator); err != nil { return err } if args.BuildAgent { ctx.Infof("Building local Juju agent binary version %s for %s", args.AgentVersion, bootstrapArch) } else { ctx.Infof("No packaged binary found, preparing local Juju agent binary") } var forceVersion version.Number availableTools, forceVersion = locallyBuildableTools(bootstrapSeries) builtTools, err = args.BuildAgentTarball(args.BuildAgent, &forceVersion, cfg.AgentStream()) if err != nil { return errors.Annotate(err, "cannot package bootstrap agent binary") } defer os.RemoveAll(builtTools.Dir) for i, tool := range availableTools { if tool.URL != "" { continue } filename := filepath.Join(builtTools.Dir, builtTools.StorageName) tool.URL = fmt.Sprintf("file://%s", filename) tool.Size = builtTools.Size tool.SHA256 = builtTools.Sha256Hash availableTools[i] = tool } } if len(availableTools) == 0 { return errors.New(noToolsMessage) } // If we're uploading, we must override agent-version; // if we're not uploading, we want to ensure we have an // agent-version set anyway, to appease FinishInstanceConfig. // In the latter case, setBootstrapTools will later set // agent-version to the correct thing. agentVersion := jujuversion.Current if args.AgentVersion != nil { agentVersion = *args.AgentVersion } if cfg, err = cfg.Apply(map[string]interface{}{ "agent-version": agentVersion.String(), }); err != nil { return err } if err = environ.SetConfig(cfg); err != nil { return err } ctx.Verbosef("Starting new instance for initial controller") result, err := environ.Bootstrap(ctx, environs.BootstrapParams{ CloudName: args.CloudName, CloudRegion: args.CloudRegion, ControllerConfig: args.ControllerConfig, ModelConstraints: args.ModelConstraints, BootstrapConstraints: bootstrapConstraints, BootstrapSeries: args.BootstrapSeries, Placement: args.Placement, AvailableTools: availableTools, ImageMetadata: imageMetadata, }) if err != nil { return err } matchingTools, err := availableTools.Match(coretools.Filter{ Arch: result.Arch, Series: result.Series, }) if err != nil { return err } selectedToolsList, err := getBootstrapToolsVersion(matchingTools) if err != nil { return err } // We set agent-version to the newest version, so the agent will immediately upgrade itself. // Note that this only is relevant if a specific agent version has not been requested, since // in that case the specific version will be the only version available. newestVersion, _ := matchingTools.Newest() if err := setBootstrapToolsVersion(environ, newestVersion); err != nil { return err } logger.Infof("Installing Juju agent on bootstrap instance") publicKey, err := userPublicSigningKey() if err != nil { return err } instanceConfig, err := instancecfg.NewBootstrapInstanceConfig( args.ControllerConfig, bootstrapConstraints, args.ModelConstraints, result.Series, publicKey, ) if err != nil { return err } if err := instanceConfig.SetTools(selectedToolsList); err != nil { return errors.Trace(err) } // Make sure we have the most recent environ config as the specified // tools version has been updated there. cfg = environ.Config() if err := finalizeInstanceBootstrapConfig(ctx, instanceConfig, args, cfg, customImageMetadata); err != nil { return errors.Annotate(err, "finalizing bootstrap instance config") } if err := result.Finalize(ctx, instanceConfig, args.DialOpts); err != nil { return err } ctx.Infof("Bootstrap agent now started") return nil }
// Bootstrap bootstraps the given environment. The supplied constraints are // used to provision the instance, and are also set within the bootstrapped // environment. func Bootstrap(ctx environs.BootstrapContext, environ environs.Environ, args BootstrapParams) error { cfg := environ.Config() network.SetPreferIPv6(cfg.PreferIPv6()) if secret := cfg.AdminSecret(); secret == "" { return errors.Errorf("model configuration has no admin-secret") } if authKeys := ssh.SplitAuthorisedKeys(cfg.AuthorizedKeys()); len(authKeys) == 0 { // Apparently this can never happen, so it's not tested. But, one day, // Config will act differently (it's pretty crazy that, AFAICT, the // authorized-keys are optional config settings... but it's impossible // to actually *create* a config without them)... and when it does, // we'll be here to catch this problem early. return errors.Errorf("model configuration has no authorized-keys") } if _, hasCACert := cfg.CACert(); !hasCACert { return errors.Errorf("model configuration has no ca-cert") } if _, hasCAKey := cfg.CAPrivateKey(); !hasCAKey { return errors.Errorf("model configuration has no ca-private-key") } // Set default tools metadata source, add image metadata source, // then verify constraints. Providers may rely on image metadata // for constraint validation. var customImageMetadata []*imagemetadata.ImageMetadata if args.MetadataDir != "" { var err error customImageMetadata, err = setPrivateMetadataSources(environ, args.MetadataDir) if err != nil { return err } } if err := validateConstraints(environ, args.ModelConstraints); err != nil { return err } if err := validateConstraints(environ, args.BootstrapConstraints); err != nil { return err } constraintsValidator, err := environ.ConstraintsValidator() if err != nil { return err } bootstrapConstraints, err := constraintsValidator.Merge( args.ModelConstraints, args.BootstrapConstraints, ) if err != nil { return err } _, supportsNetworking := environs.SupportsNetworking(environ) var bootstrapSeries *string if args.BootstrapSeries != "" { bootstrapSeries = &args.BootstrapSeries } ctx.Infof("Bootstrapping model %q", cfg.Name()) logger.Debugf("model %q supports service/machine networks: %v", cfg.Name(), supportsNetworking) disableNetworkManagement, _ := cfg.DisableNetworkManagement() logger.Debugf("network management by juju enabled: %v", !disableNetworkManagement) availableTools, err := findAvailableTools( environ, args.AgentVersion, bootstrapConstraints.Arch, bootstrapSeries, args.UploadTools, args.BuildToolsTarball != nil, ) if errors.IsNotFound(err) { return errors.New(noToolsMessage) } else if err != nil { return err } if lxcMTU, ok := cfg.LXCDefaultMTU(); ok { logger.Debugf("using MTU %v for all created LXC containers' network interfaces", lxcMTU) } imageMetadata, err := bootstrapImageMetadata( environ, availableTools, args.BootstrapImage, &customImageMetadata, ) if err != nil { return errors.Trace(err) } // If we're uploading, we must override agent-version; // if we're not uploading, we want to ensure we have an // agent-version set anyway, to appease FinishInstanceConfig. // In the latter case, setBootstrapTools will later set // agent-version to the correct thing. agentVersion := jujuversion.Current if args.AgentVersion != nil { agentVersion = *args.AgentVersion } if cfg, err = cfg.Apply(map[string]interface{}{ "agent-version": agentVersion.String(), }); err != nil { return err } if err = environ.SetConfig(cfg); err != nil { return err } ctx.Infof("Starting new instance for initial controller") result, err := environ.Bootstrap(ctx, environs.BootstrapParams{ ModelConstraints: args.ModelConstraints, BootstrapConstraints: args.BootstrapConstraints, BootstrapSeries: args.BootstrapSeries, Placement: args.Placement, AvailableTools: availableTools, ImageMetadata: imageMetadata, }) if err != nil { return err } matchingTools, err := availableTools.Match(coretools.Filter{ Arch: result.Arch, Series: result.Series, }) if err != nil { return err } selectedToolsList, err := setBootstrapTools(environ, matchingTools) if err != nil { return err } havePrepackaged := false for i, selectedTools := range selectedToolsList { if selectedTools.URL != "" { havePrepackaged = true continue } ctx.Infof("Building tools to upload (%s)", selectedTools.Version) builtTools, err := args.BuildToolsTarball(&selectedTools.Version.Number, cfg.AgentStream()) if err != nil { return errors.Annotate(err, "cannot upload bootstrap tools") } defer os.RemoveAll(builtTools.Dir) filename := filepath.Join(builtTools.Dir, builtTools.StorageName) selectedTools.URL = fmt.Sprintf("file://%s", filename) selectedTools.Size = builtTools.Size selectedTools.SHA256 = builtTools.Sha256Hash selectedToolsList[i] = selectedTools } if !havePrepackaged && !args.UploadTools { // There are no prepackaged agents, so we must upload // even though the user didn't ask for it. We only do // this when the image-stream is not "released" and // the agent version hasn't been specified. logger.Warningf("no prepackaged tools available") } ctx.Infof("Installing Juju agent on bootstrap instance") publicKey, err := userPublicSigningKey() if err != nil { return err } instanceConfig, err := instancecfg.NewBootstrapInstanceConfig( args.BootstrapConstraints, args.ModelConstraints, result.Series, publicKey, ) if err != nil { return err } if err := instanceConfig.SetTools(selectedToolsList); err != nil { return errors.Trace(err) } instanceConfig.CustomImageMetadata = customImageMetadata instanceConfig.HostedModelConfig = args.HostedModelConfig instanceConfig.GUI = guiArchive(args.GUIDataSourceBaseURL, func(msg string) { ctx.Infof(msg) }) if err := result.Finalize(ctx, instanceConfig); err != nil { return err } ctx.Infof("Bootstrap agent installed") return nil }
func finalizeInstanceBootstrapConfig( ctx environs.BootstrapContext, icfg *instancecfg.InstanceConfig, args BootstrapParams, cfg *config.Config, customImageMetadata []*imagemetadata.ImageMetadata, ) error { if icfg.APIInfo != nil || icfg.Controller.MongoInfo != nil { return errors.New("machine configuration already has api/state info") } controllerCfg := icfg.Controller.Config caCert, hasCACert := controllerCfg.CACert() if !hasCACert { return errors.New("controller configuration has no ca-cert") } icfg.APIInfo = &api.Info{ Password: args.AdminSecret, CACert: caCert, ModelTag: names.NewModelTag(cfg.UUID()), } icfg.Controller.MongoInfo = &mongo.MongoInfo{ Password: args.AdminSecret, Info: mongo.Info{CACert: caCert}, } // These really are directly relevant to running a controller. // Initially, generate a controller certificate with no host IP // addresses in the SAN field. Once the controller is up and the // NIC addresses become known, the certificate can be regenerated. cert, key, err := controller.GenerateControllerCertAndKey(caCert, args.CAPrivateKey, nil) if err != nil { return errors.Annotate(err, "cannot generate controller certificate") } icfg.Bootstrap.StateServingInfo = params.StateServingInfo{ StatePort: controllerCfg.StatePort(), APIPort: controllerCfg.APIPort(), Cert: string(cert), PrivateKey: string(key), CAPrivateKey: args.CAPrivateKey, } if _, ok := cfg.AgentVersion(); !ok { return errors.New("controller model configuration has no agent-version") } icfg.Bootstrap.ControllerModelConfig = cfg icfg.Bootstrap.CustomImageMetadata = customImageMetadata icfg.Bootstrap.ControllerCloudName = args.CloudName icfg.Bootstrap.ControllerCloud = args.Cloud icfg.Bootstrap.ControllerCloudRegion = args.CloudRegion icfg.Bootstrap.ControllerCloudCredential = args.CloudCredential icfg.Bootstrap.ControllerCloudCredentialName = args.CloudCredentialName icfg.Bootstrap.ControllerConfig = args.ControllerConfig icfg.Bootstrap.ControllerInheritedConfig = args.ControllerInheritedConfig icfg.Bootstrap.RegionInheritedConfig = args.Cloud.RegionConfig icfg.Bootstrap.HostedModelConfig = args.HostedModelConfig icfg.Bootstrap.Timeout = args.DialOpts.Timeout icfg.Bootstrap.GUI = guiArchive(args.GUIDataSourceBaseURL, func(msg string) { ctx.Infof(msg) }) return nil }
// BootstrapInstance creates a new instance with the series and architecture // of its choice, constrained to those of the available tools, and // returns the instance result, series, and a function that // must be called to finalize the bootstrap process by transferring // the tools and installing the initial Juju state server. // This method is called by Bootstrap above, which implements environs.Bootstrap, but // is also exported so that providers can manipulate the started instance. func BootstrapInstance(ctx environs.BootstrapContext, env environs.Environ, args environs.BootstrapParams, ) (_ *environs.StartInstanceResult, series string, _ environs.BootstrapFinalizer, err error) { // TODO make safe in the case of racing Bootstraps // If two Bootstraps are called concurrently, there's // no way to make sure that only one succeeds. // First thing, ensure we have tools otherwise there's no point. series = config.PreferredSeries(env.Config()) availableTools, err := args.AvailableTools.Match(coretools.Filter{Series: series}) if err != nil { return nil, "", nil, err } // Get the bootstrap SSH client. Do this early, so we know // not to bother with any of the below if we can't finish the job. client := ssh.DefaultClient if client == nil { // This should never happen: if we don't have OpenSSH, then // go.crypto/ssh should be used with an auto-generated key. return nil, "", nil, fmt.Errorf("no SSH client available") } instanceConfig, err := instancecfg.NewBootstrapInstanceConfig(args.Constraints, series) if err != nil { return nil, "", nil, err } instanceConfig.EnableOSRefreshUpdate = env.Config().EnableOSRefreshUpdate() instanceConfig.EnableOSUpgrade = env.Config().EnableOSUpgrade() instanceConfig.Tags = instancecfg.InstanceTags(env.Config(), instanceConfig.Jobs) maybeSetBridge := func(icfg *instancecfg.InstanceConfig) { // If we need to override the default bridge name, do it now. When // args.ContainerBridgeName is empty, the default names for LXC // (lxcbr0) and KVM (virbr0) will be used. if args.ContainerBridgeName != "" { logger.Debugf("using %q as network bridge for all container types", args.ContainerBridgeName) if icfg.AgentEnvironment == nil { icfg.AgentEnvironment = make(map[string]string) } icfg.AgentEnvironment[agent.LxcBridge] = args.ContainerBridgeName } } maybeSetBridge(instanceConfig) fmt.Fprintln(ctx.GetStderr(), "Launching instance") result, err := env.StartInstance(environs.StartInstanceParams{ Constraints: args.Constraints, Tools: availableTools, InstanceConfig: instanceConfig, Placement: args.Placement, }) if err != nil { return nil, "", nil, errors.Annotate(err, "cannot start bootstrap instance") } fmt.Fprintf(ctx.GetStderr(), " - %s\n", result.Instance.Id()) finalize := func(ctx environs.BootstrapContext, icfg *instancecfg.InstanceConfig) error { icfg.InstanceId = result.Instance.Id() icfg.HardwareCharacteristics = result.Hardware if err := instancecfg.FinishInstanceConfig(icfg, env.Config()); err != nil { return err } maybeSetBridge(icfg) return FinishBootstrap(ctx, client, result.Instance, icfg) } return result, series, finalize, nil }
// BootstrapInstance creates a new instance with the series and architecture // of its choice, constrained to those of the available tools, and // returns the instance result, series, and a function that // must be called to finalize the bootstrap process by transferring // the tools and installing the initial Juju controller. // This method is called by Bootstrap above, which implements environs.Bootstrap, but // is also exported so that providers can manipulate the started instance. func BootstrapInstance(ctx environs.BootstrapContext, env environs.Environ, args environs.BootstrapParams, ) (_ *environs.StartInstanceResult, selectedSeries string, _ environs.BootstrapFinalizer, err error) { // TODO make safe in the case of racing Bootstraps // If two Bootstraps are called concurrently, there's // no way to make sure that only one succeeds. // First thing, ensure we have tools otherwise there's no point. if args.BootstrapSeries != "" { selectedSeries = args.BootstrapSeries } else { selectedSeries = config.PreferredSeries(env.Config()) } availableTools, err := args.AvailableTools.Match(coretools.Filter{ Series: selectedSeries, }) if err != nil { return nil, "", nil, err } // Filter image metadata to the selected series. var imageMetadata []*imagemetadata.ImageMetadata seriesVersion, err := series.SeriesVersion(selectedSeries) if err != nil { return nil, "", nil, errors.Trace(err) } for _, m := range args.ImageMetadata { if m.Version != seriesVersion { continue } imageMetadata = append(imageMetadata, m) } // Get the bootstrap SSH client. Do this early, so we know // not to bother with any of the below if we can't finish the job. client := ssh.DefaultClient if client == nil { // This should never happen: if we don't have OpenSSH, then // go.crypto/ssh should be used with an auto-generated key. return nil, "", nil, fmt.Errorf("no SSH client available") } publicKey, err := simplestreams.UserPublicSigningKey() if err != nil { return nil, "", nil, err } instanceConfig, err := instancecfg.NewBootstrapInstanceConfig( args.BootstrapConstraints, args.ModelConstraints, selectedSeries, publicKey, ) if err != nil { return nil, "", nil, err } instanceConfig.EnableOSRefreshUpdate = env.Config().EnableOSRefreshUpdate() instanceConfig.EnableOSUpgrade = env.Config().EnableOSUpgrade() instanceConfig.Tags = instancecfg.InstanceTags(env.Config(), instanceConfig.Jobs) maybeSetBridge := func(icfg *instancecfg.InstanceConfig) { // If we need to override the default bridge name, do it now. When // args.ContainerBridgeName is empty, the default names for LXC // (lxcbr0) and KVM (virbr0) will be used. if args.ContainerBridgeName != "" { logger.Debugf("using %q as network bridge for all container types", args.ContainerBridgeName) if icfg.AgentEnvironment == nil { icfg.AgentEnvironment = make(map[string]string) } icfg.AgentEnvironment[agent.LxcBridge] = args.ContainerBridgeName } } maybeSetBridge(instanceConfig) fmt.Fprintln(ctx.GetStderr(), "Launching instance") instanceStatus := func(settableStatus status.Status, info string, data map[string]interface{}) error { fmt.Fprintf(ctx.GetStderr(), "%s \r", info) return nil } result, err := env.StartInstance(environs.StartInstanceParams{ Constraints: args.BootstrapConstraints, Tools: availableTools, InstanceConfig: instanceConfig, Placement: args.Placement, ImageMetadata: imageMetadata, StatusCallback: instanceStatus, }) if err != nil { return nil, "", nil, errors.Annotate(err, "cannot start bootstrap instance") } fmt.Fprintf(ctx.GetStderr(), " - %s\n", result.Instance.Id()) finalize := func(ctx environs.BootstrapContext, icfg *instancecfg.InstanceConfig) error { icfg.InstanceId = result.Instance.Id() icfg.HardwareCharacteristics = result.Hardware envConfig := env.Config() if result.Config != nil { updated, err := envConfig.Apply(result.Config.UnknownAttrs()) if err != nil { return errors.Trace(err) } envConfig = updated } if err := instancecfg.FinishInstanceConfig(icfg, envConfig); err != nil { return err } maybeSetBridge(icfg) return FinishBootstrap(ctx, client, env, result.Instance, icfg) } return result, selectedSeries, finalize, nil }
// Bootstrap bootstraps the given environment. The supplied constraints are // used to provision the instance, and are also set within the bootstrapped // environment. func Bootstrap(ctx environs.BootstrapContext, environ environs.Environ, args BootstrapParams) error { cfg := environ.Config() network.InitializeFromConfig(cfg) if secret := cfg.AdminSecret(); secret == "" { return errors.Errorf("environment configuration has no admin-secret") } if authKeys := ssh.SplitAuthorisedKeys(cfg.AuthorizedKeys()); len(authKeys) == 0 { // Apparently this can never happen, so it's not tested. But, one day, // Config will act differently (it's pretty crazy that, AFAICT, the // authorized-keys are optional config settings... but it's impossible // to actually *create* a config without them)... and when it does, // we'll be here to catch this problem early. return errors.Errorf("environment configuration has no authorized-keys") } if _, hasCACert := cfg.CACert(); !hasCACert { return errors.Errorf("environment configuration has no ca-cert") } if _, hasCAKey := cfg.CAPrivateKey(); !hasCAKey { return errors.Errorf("environment configuration has no ca-private-key") } // Write out the bootstrap-init file, and confirm storage is writeable. if err := environsVerifyStorage(environ.Storage()); err != nil { return err } ctx.Infof("Bootstrapping environment %q", cfg.Name()) logger.Debugf("environment %q supports service/machine networks: %v", cfg.Name(), environ.SupportNetworks()) disableNetworkManagement, _ := cfg.DisableNetworkManagement() logger.Debugf("network management by juju enabled: %v", disableNetworkManagement) availableTools, err := findAvailableTools(environ, args.Constraints.Arch, args.UploadTools) if errors.IsNotFound(err) { return errors.New(noToolsMessage) } else if err != nil { return err } // If we're uploading, we must override agent-version; // if we're not uploading, we want to ensure we have an // agent-version set anyway, to appease FinishMachineConfig. // In the latter case, setBootstrapTools will later set // agent-version to the correct thing. if cfg, err = cfg.Apply(map[string]interface{}{ "agent-version": version.Current.Number.String(), }); err != nil { return err } if err = environ.SetConfig(cfg); err != nil { return err } ctx.Infof("Starting new instance for initial state server") arch, series, finalizer, err := environ.Bootstrap(ctx, environs.BootstrapParams{ Constraints: args.Constraints, Placement: args.Placement, AvailableTools: availableTools, }) if err != nil { return err } matchingTools, err := availableTools.Match(coretools.Filter{ Arch: arch, Series: series, }) if err != nil { return err } selectedTools, err := setBootstrapTools(environ, matchingTools) if err != nil { return err } if selectedTools.URL == "" { if !args.UploadTools { logger.Warningf("no prepackaged tools available") } ctx.Infof("Building tools to upload (%s)", selectedTools.Version) builtTools, err := sync.BuildToolsTarball(&selectedTools.Version.Number) if err != nil { return errors.Annotate(err, "cannot upload bootstrap tools") } filename := filepath.Join(builtTools.Dir, builtTools.StorageName) selectedTools.URL = fmt.Sprintf("file://%s", filename) selectedTools.Size = builtTools.Size selectedTools.SHA256 = builtTools.Sha256Hash } ctx.Infof("Installing Juju agent on bootstrap instance") machineConfig, err := environs.NewBootstrapMachineConfig(args.Constraints, series) if err != nil { return err } machineConfig.Tools = selectedTools if err := finalizer(ctx, machineConfig); err != nil { return err } ctx.Infof("Bootstrap complete") return nil }
func ensureBootstrapUbuntuUser(ctx environs.BootstrapContext, cfg *environConfig) error { err := initUbuntuUser(cfg.bootstrapHost(), cfg.bootstrapUser(), cfg.AuthorizedKeys(), ctx.GetStdin(), ctx.GetStdout()) if err != nil { logger.Errorf("initializing ubuntu user: %v", err) return err } logger.Infof("initialized ubuntu user") return nil }