// 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 environs.BootstrapParams) error { cfg := environ.Config() if secret := cfg.AdminSecret(); secret == "" { return fmt.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 fmt.Errorf("environment configuration has no authorized-keys") } if _, hasCACert := cfg.CACert(); !hasCACert { return fmt.Errorf("environment configuration has no ca-cert") } if _, hasCAKey := cfg.CAPrivateKey(); !hasCAKey { return fmt.Errorf("environment configuration has no ca-private-key") } // Write out the bootstrap-init file, and confirm storage is writeable. if err := environs.VerifyStorage(environ.Storage()); err != nil { return err } logger.Debugf("environment %q supports service/machine networks: %v", environ.Name(), environ.SupportNetworks()) logger.Infof("bootstrapping environment %q", environ.Name()) return environ.Bootstrap(ctx, args) }
// 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([]instance.Instance{inst}); 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) } } }
// StateInfo is a reusable implementation of Environ.StateInfo, available to // providers that also use the other functionality from this file. func StateInfo(env environs.Environ) (*state.Info, *api.Info, error) { st, err := bootstrap.LoadState(env.Storage()) if err != nil { return nil, nil, err } config := env.Config() if _, hasCert := config.CACert(); !hasCert { return nil, nil, fmt.Errorf("no CA certificate in environment configuration") } // Wait for the DNS names of any of the instances // to become available. logger.Debugf("waiting for DNS name(s) of state server instances %v", st.StateInstances) var hostnames []string for a := LongAttempt.Start(); len(hostnames) == 0 && a.Next(); { insts, err := env.Instances(st.StateInstances) if err != nil && err != environs.ErrPartialInstances { logger.Debugf("error getting state instances: %v", err.Error()) return nil, nil, err } hostnames = getDNSNames(insts) } if len(hostnames) == 0 { return nil, nil, fmt.Errorf("timed out waiting for mgo address from %v", st.StateInstances) } stateInfo, apiInfo := getStateInfo(config, hostnames) return stateInfo, apiInfo, nil }
func makeTestMetadata(c *gc.C, env environs.Environ, series, location string, im []*imagemetadata.ImageMetadata) { cloudSpec := simplestreams.CloudSpec{ Region: location, Endpoint: "https://management.core.windows.net/", } err := imagemetadata.MergeAndWriteMetadata(series, im, &cloudSpec, env.Storage()) c.Assert(err, gc.IsNil) }
// 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) }() // 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) }
// EnsureNotBootstrapped returns nil if the environment is not // bootstrapped, and an error if it is or if the function was not able // to tell. func EnsureNotBootstrapped(env environs.Environ) error { _, err := LoadState(env.Storage()) // If there is no error loading the bootstrap state, then we are // bootstrapped. if err == nil { return fmt.Errorf("environment is already bootstrapped") } if err == environs.ErrNotBootstrapped { return nil } return err }
// Destroy is a common implementation of the Destroy method defined on // environs.Environ; we strongly recommend that this implementation be // used when writing a new provider. func Destroy(env environs.Environ) error { logger.Infof("destroying environment %q", env.Name()) instances, err := env.AllInstances() switch err { case nil: if err := env.StopInstances(instances); err != nil { return err } fallthrough case environs.ErrNoInstances: return env.Storage().RemoveAll() } return err }
// 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 }
// RemoveAllTools deletes all tools from the supplied environment. func RemoveAllTools(c *gc.C, env environs.Environ) { c.Logf("clearing private storage") RemoveTools(c, env.Storage()) }
func uploadTools(c *gc.C, env environs.Environ) { usefulVersion := version.Current usefulVersion.Series = config.PreferredSeries(env.Config()) envtesting.AssertUploadFakeToolsVersions(c, env.Storage(), usefulVersion) }