func validateConstraints(env environs.Environ, cons constraints.Value) error { validator, err := env.ConstraintsValidator() if err != nil { return err } unsupported, err := validator.Validate(cons) if len(unsupported) > 0 { logger.Warningf("unsupported constraints: %v", unsupported) } return err }
// 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 }
// 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 }