github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/environs/bootstrap/bootstrap.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package bootstrap
     5  
     6  import (
     7  	stdcontext "context"
     8  	"fmt"
     9  	"os"
    10  	"path/filepath"
    11  
    12  	"github.com/juju/charm/v12"
    13  	"github.com/juju/collections/set"
    14  	"github.com/juju/errors"
    15  	"github.com/juju/loggo"
    16  	"github.com/juju/names/v5"
    17  	"github.com/juju/utils/v3"
    18  	"github.com/juju/utils/v3/ssh"
    19  	"github.com/juju/version/v2"
    20  
    21  	"github.com/juju/juju/api"
    22  	"github.com/juju/juju/cloud"
    23  	"github.com/juju/juju/cloudconfig/instancecfg"
    24  	"github.com/juju/juju/cloudconfig/podcfg"
    25  	"github.com/juju/juju/controller"
    26  	"github.com/juju/juju/core/arch"
    27  	corebase "github.com/juju/juju/core/base"
    28  	"github.com/juju/juju/core/constraints"
    29  	corecontext "github.com/juju/juju/core/context"
    30  	"github.com/juju/juju/environs"
    31  	"github.com/juju/juju/environs/config"
    32  	"github.com/juju/juju/environs/context"
    33  	"github.com/juju/juju/environs/imagemetadata"
    34  	"github.com/juju/juju/environs/simplestreams"
    35  	"github.com/juju/juju/environs/storage"
    36  	"github.com/juju/juju/environs/sync"
    37  	"github.com/juju/juju/environs/tools"
    38  	"github.com/juju/juju/pki"
    39  	corestorage "github.com/juju/juju/storage"
    40  	coretools "github.com/juju/juju/tools"
    41  	jujuversion "github.com/juju/juju/version"
    42  )
    43  
    44  const (
    45  	// SimplestreamsFetcherContextKey defines a way to change the simplestreams
    46  	// fetcher within a context.
    47  	SimplestreamsFetcherContextKey corecontext.ContextKey = "simplestreams-fetcher"
    48  )
    49  
    50  const noToolsMessage = `Juju cannot bootstrap because no agent binaries are available for your model.
    51  You may want to use the 'agent-metadata-url' configuration setting to specify the binaries' location.
    52  `
    53  
    54  var (
    55  	logger = loggo.GetLogger("juju.environs.bootstrap")
    56  
    57  	errCancelled = errors.New("cancelled")
    58  )
    59  
    60  // BootstrapParams holds the parameters for bootstrapping an environment.
    61  type BootstrapParams struct {
    62  	// ModelConstraints are merged with the bootstrap constraints
    63  	// to choose the initial instance, and will be stored in the
    64  	// initial models' states.
    65  	ModelConstraints constraints.Value
    66  
    67  	// BootstrapConstraints are used to choose the initial instance.
    68  	// BootstrapConstraints does not affect the model constraints.
    69  	BootstrapConstraints constraints.Value
    70  
    71  	// ControllerName is the controller name.
    72  	ControllerName string
    73  
    74  	// BootstrapImage, if specified, is the image ID to use for the
    75  	// initial bootstrap machine.
    76  	BootstrapImage string
    77  
    78  	// Cloud contains the properties of the cloud that Juju will be
    79  	// bootstrapped in.
    80  	Cloud cloud.Cloud
    81  
    82  	// CloudRegion is the name of the cloud region that Juju will be bootstrapped in.
    83  	CloudRegion string
    84  
    85  	// CloudCredentialName is the name of the cloud credential that Juju will be
    86  	// bootstrapped with. This may be empty, for clouds that do not require
    87  	// credentials.
    88  	CloudCredentialName string
    89  
    90  	// CloudCredential contains the cloud credential that Juju will be
    91  	// bootstrapped with. This may be nil, for clouds that do not require
    92  	// credentialis.
    93  	CloudCredential *cloud.Credential
    94  
    95  	// ControllerConfig is the set of config attributes relevant
    96  	// to a controller.
    97  	ControllerConfig controller.Config
    98  
    99  	// ControllerInheritedConfig is the set of config attributes to be shared
   100  	// across all models in the same controller.
   101  	ControllerInheritedConfig map[string]interface{}
   102  
   103  	// RegionInheritedConfig holds region specific configuration attributes to
   104  	// be shared across all models in the same controller on a particular
   105  	// cloud.
   106  	RegionInheritedConfig cloud.RegionConfig
   107  
   108  	// InitialModelConfig is the set of config attributes to be overlaid
   109  	// on the controller config to construct the initial hosted model
   110  	// config.
   111  	InitialModelConfig map[string]interface{}
   112  
   113  	// Placement, if non-empty, holds an environment-specific placement
   114  	// directive used to choose the initial instance.
   115  	Placement string
   116  
   117  	// BuildAgent reports whether we should build and upload the local agent
   118  	// binary and override the environment's specified agent-version.
   119  	// It is an error to specify BuildAgent with a nil BuildAgentTarball.
   120  	BuildAgent bool
   121  
   122  	// BuildAgentTarball, if non-nil, is a function that may be used to
   123  	// build tools to upload. If this is nil, tools uploading will never
   124  	// take place.
   125  	BuildAgentTarball sync.BuildAgentTarballFunc
   126  
   127  	// MetadataDir is an optional path to a local directory containing
   128  	// tools and/or image metadata.
   129  	MetadataDir string
   130  
   131  	// AgentVersion, if set, determines the exact tools version that
   132  	// will be used to start the Juju agents.
   133  	AgentVersion *version.Number
   134  
   135  	// AdminSecret contains the administrator password.
   136  	AdminSecret string
   137  
   138  	// CAPrivateKey is the controller's CA certificate private key.
   139  	CAPrivateKey string
   140  
   141  	// ControllerServiceType is the service type of a k8s controller.
   142  	ControllerServiceType string
   143  
   144  	// ControllerExternalName is the external name of a k8s controller.
   145  	ControllerExternalName string
   146  
   147  	// ControllerExternalIPs is the list of external ips for a k8s controller.
   148  	ControllerExternalIPs []string
   149  
   150  	// DialOpts contains the bootstrap dial options.
   151  	DialOpts environs.BootstrapDialOpts
   152  
   153  	// JujuDbSnapPath is the path to a local .snap file that will be used
   154  	// to run the juju-db service.
   155  	JujuDbSnapPath string
   156  
   157  	// JujuDbSnapAssertionsPath is the path to a local .assertfile that
   158  	// will be used to test the contents of the .snap at JujuDbSnap.
   159  	JujuDbSnapAssertionsPath string
   160  
   161  	// StoragePools is one or more named storage pools to create
   162  	// in the controller model.
   163  	StoragePools map[string]corestorage.Attrs
   164  
   165  	// Force is used to allow a bootstrap to be run on unsupported series.
   166  	Force bool
   167  
   168  	// ControllerCharmPath is a local controller charm archive.
   169  	ControllerCharmPath string
   170  
   171  	// ControllerCharmChannel is used when fetching the charmhub controller charm.
   172  	ControllerCharmChannel charm.Channel
   173  
   174  	// ExtraAgentValuesForTesting are testing only values written to the agent config file.
   175  	ExtraAgentValuesForTesting map[string]string
   176  
   177  	// BootstrapBase, if specified, is the base to use for the
   178  	// initial bootstrap machine (deprecated use BootstrapBase).
   179  	BootstrapBase corebase.Base
   180  
   181  	// SupportedBootstrapBase is a supported set of bases to use for
   182  	// validating against the bootstrap base.
   183  	SupportedBootstrapBases []corebase.Base
   184  }
   185  
   186  // Validate validates the bootstrap parameters.
   187  func (p BootstrapParams) Validate() error {
   188  	if p.AdminSecret == "" {
   189  		return errors.New("admin-secret is empty")
   190  	}
   191  	if p.ControllerConfig.ControllerUUID() == "" {
   192  		return errors.New("controller configuration has no controller UUID")
   193  	}
   194  	if _, hasCACert := p.ControllerConfig.CACert(); !hasCACert {
   195  		return errors.New("controller configuration has no ca-cert")
   196  	}
   197  	if p.CAPrivateKey == "" {
   198  		return errors.New("empty ca-private-key")
   199  	}
   200  	if p.SupportedBootstrapBases == nil || len(p.SupportedBootstrapBases) == 0 {
   201  		return errors.NotValidf("supported bootstrap bases")
   202  	}
   203  
   204  	// TODO(axw) validate other things.
   205  	return nil
   206  }
   207  
   208  // withDefaultControllerConstraints returns the given constraints,
   209  // updated to choose a default instance type appropriate for a
   210  // controller machine. We use this only if the user does not specify
   211  // any constraints that would otherwise control the instance type
   212  // selection.
   213  func withDefaultControllerConstraints(cons constraints.Value) constraints.Value {
   214  	if !cons.HasInstanceType() && !cons.HasCpuCores() && !cons.HasCpuPower() && !cons.HasMem() {
   215  		// A default of 3.5GiB will result in machines with up to 4GiB of memory, eg
   216  		// - 3.75GiB on AWS, Google
   217  		// - 3.5GiB on Azure
   218  		var mem uint64 = 3.5 * 1024
   219  		cons.Mem = &mem
   220  	}
   221  	// If we're bootstrapping a controller on a lxd virtual machine, we want to
   222  	// ensure that it has at least 2 cores. Less than 2 cores can cause the
   223  	// controller to become unresponsive when installing.
   224  	if !cons.HasCpuCores() && cons.HasVirtType() && *cons.VirtType == "virtual-machine" {
   225  		var cores = uint64(2)
   226  		cons.CpuCores = &cores
   227  	}
   228  	return cons
   229  }
   230  
   231  // withDefaultCAASControllerConstraints returns the given constraints,
   232  // updated to choose a default instance type appropriate for a
   233  // controller machine. We use this only if the user does not specify
   234  // any constraints that would otherwise control the instance type
   235  // selection.
   236  func withDefaultCAASControllerConstraints(cons constraints.Value) constraints.Value {
   237  	if !cons.HasInstanceType() && !cons.HasCpuCores() && !cons.HasCpuPower() && !cons.HasMem() {
   238  		// TODO(caas): Set memory constraints for mongod and controller containers independently.
   239  		var mem uint64 = 1.5 * 1024
   240  		cons.Mem = &mem
   241  	}
   242  	return cons
   243  }
   244  
   245  func bootstrapCAAS(
   246  	ctx environs.BootstrapContext,
   247  	environ environs.BootstrapEnviron,
   248  	callCtx context.ProviderCallContext,
   249  	args BootstrapParams,
   250  	bootstrapParams environs.BootstrapParams,
   251  ) error {
   252  	if args.BuildAgent {
   253  		return errors.NotSupportedf("--build-agent when bootstrapping a k8s controller")
   254  	}
   255  	if args.BootstrapImage != "" {
   256  		return errors.NotSupportedf("--bootstrap-image when bootstrapping a k8s controller")
   257  	}
   258  	if !args.BootstrapBase.Empty() {
   259  		return errors.NotSupportedf("--bootstrap-series or --bootstrap-base when bootstrapping a k8s controller")
   260  	}
   261  
   262  	constraintsValidator, err := environ.ConstraintsValidator(callCtx)
   263  	if err != nil {
   264  		return err
   265  	}
   266  	bootstrapConstraints, err := constraintsValidator.Merge(
   267  		args.ModelConstraints, args.BootstrapConstraints,
   268  	)
   269  	if err != nil {
   270  		return errors.Trace(err)
   271  	}
   272  	bootstrapConstraints = withDefaultCAASControllerConstraints(bootstrapConstraints)
   273  	bootstrapParams.BootstrapConstraints = bootstrapConstraints
   274  
   275  	result, err := environ.Bootstrap(ctx, callCtx, bootstrapParams)
   276  	if err != nil {
   277  		return errors.Trace(err)
   278  	}
   279  
   280  	podConfig, err := podcfg.NewBootstrapControllerPodConfig(
   281  		args.ControllerConfig,
   282  		args.ControllerName,
   283  		result.Base.OS,
   284  		bootstrapConstraints,
   285  	)
   286  	if err != nil {
   287  		return errors.Trace(err)
   288  	}
   289  
   290  	jujuVersion := jujuversion.Current
   291  	if args.AgentVersion != nil {
   292  		jujuVersion = *args.AgentVersion
   293  	}
   294  	// set agent version before finalizing bootstrap config
   295  	if err := setBootstrapAgentVersion(environ, jujuVersion); err != nil {
   296  		return errors.Trace(err)
   297  	}
   298  	podConfig.JujuVersion = jujuVersion
   299  	if err := finalizePodBootstrapConfig(ctx, podConfig, args, environ.Config()); err != nil {
   300  		return errors.Annotate(err, "finalizing bootstrap instance config")
   301  	}
   302  	if err := result.CaasBootstrapFinalizer(ctx, podConfig, args.DialOpts); err != nil {
   303  		return errors.Trace(err)
   304  	}
   305  	return nil
   306  }
   307  
   308  func bootstrapIAAS(
   309  	ctx environs.BootstrapContext,
   310  	environ environs.BootstrapEnviron,
   311  	callCtx context.ProviderCallContext,
   312  	args BootstrapParams,
   313  	bootstrapParams environs.BootstrapParams,
   314  ) error {
   315  	cfg := environ.Config()
   316  	if authKeys := ssh.SplitAuthorisedKeys(cfg.AuthorizedKeys()); len(authKeys) == 0 {
   317  		// Apparently this can never happen, so it's not tested. But, one day,
   318  		// Config will act differently (it's pretty crazy that, AFAICT, the
   319  		// authorized-keys are optional config settings... but it's impossible
   320  		// to actually *create* a config without them)... and when it does,
   321  		// we'll be here to catch this problem early.
   322  		return errors.Errorf("model configuration has no authorized-keys")
   323  	}
   324  
   325  	_, supportsNetworking := environs.SupportsNetworking(environ)
   326  	logger.Debugf("model %q supports application/machine networks: %v", cfg.Name(), supportsNetworking)
   327  	disableNetworkManagement, _ := cfg.DisableNetworkManagement()
   328  	logger.Debugf("network management by juju enabled: %v", !disableNetworkManagement)
   329  
   330  	var ss *simplestreams.Simplestreams
   331  	if value := ctx.Context().Value(SimplestreamsFetcherContextKey); value == nil {
   332  		ss = simplestreams.NewSimpleStreams(simplestreams.DefaultDataSourceFactory())
   333  	} else if s, ok := value.(*simplestreams.Simplestreams); ok {
   334  		ss = s
   335  	} else {
   336  		return errors.Errorf("expected a valid simple streams type")
   337  	}
   338  
   339  	// Set default tools metadata source, add image metadata source,
   340  	// then verify constraints. Providers may rely on image metadata
   341  	// for constraint validation.
   342  	var customImageMetadata []*imagemetadata.ImageMetadata
   343  	if args.MetadataDir != "" {
   344  		var err error
   345  		customImageMetadata, err = setPrivateMetadataSources(ss, args.MetadataDir)
   346  		if err != nil {
   347  			return errors.Trace(err)
   348  		}
   349  	}
   350  
   351  	// If the provider allows advance discovery of the series and hw
   352  	// characteristics of the instance we are about to bootstrap, use this
   353  	// information to backfill in any missing series and/or arch contstraints.
   354  	if detector, supported := environ.(environs.HardwareCharacteristicsDetector); supported {
   355  		detectedBase, err := detector.DetectBase()
   356  		if err != nil {
   357  			return errors.Trace(err)
   358  		}
   359  		detectedHW, err := detector.DetectHardware()
   360  		if err != nil {
   361  			return errors.Trace(err)
   362  		}
   363  
   364  		if args.BootstrapBase.Empty() && !detectedBase.Empty() {
   365  			args.BootstrapBase = detectedBase
   366  			logger.Debugf("auto-selecting bootstrap series %q", args.BootstrapBase.String())
   367  		}
   368  		if args.BootstrapConstraints.Arch == nil &&
   369  			args.ModelConstraints.Arch == nil &&
   370  			detectedHW != nil &&
   371  			detectedHW.Arch != nil {
   372  			arch := *detectedHW.Arch
   373  			args.BootstrapConstraints.Arch = &arch
   374  			if detector.UpdateModelConstraints() {
   375  				args.ModelConstraints.Arch = &arch
   376  			}
   377  			logger.Debugf("auto-selecting bootstrap arch %q", arch)
   378  		}
   379  	}
   380  
   381  	requestedBootstrapBase, err := corebase.ValidateBase(
   382  		args.SupportedBootstrapBases,
   383  		args.BootstrapBase,
   384  		config.PreferredBase(cfg),
   385  	)
   386  	if !args.Force && err != nil {
   387  		// If the base isn't valid (i.e. non-ubuntu) then don't prompt users to use
   388  		// the --force flag.
   389  		if requestedBootstrapBase.OS != corebase.UbuntuOS {
   390  			return errors.NotValidf("non-ubuntu bootstrap base %q", requestedBootstrapBase.String())
   391  		}
   392  		return errors.Annotatef(err, "use --force to override")
   393  	}
   394  	bootstrapBase := requestedBootstrapBase
   395  
   396  	var bootstrapArchForImageSearch string
   397  	if args.BootstrapConstraints.Arch != nil {
   398  		bootstrapArchForImageSearch = *args.BootstrapConstraints.Arch
   399  	} else if args.ModelConstraints.Arch != nil {
   400  		bootstrapArchForImageSearch = *args.ModelConstraints.Arch
   401  	} else {
   402  		bootstrapArchForImageSearch = arch.HostArch()
   403  	}
   404  
   405  	ctx.Verbosef("Loading image metadata")
   406  	imageMetadata, err := bootstrapImageMetadata(environ,
   407  		ss,
   408  		&bootstrapBase,
   409  		bootstrapArchForImageSearch,
   410  		args.BootstrapImage,
   411  		&customImageMetadata,
   412  	)
   413  	if err != nil {
   414  		return errors.Trace(err)
   415  	}
   416  
   417  	// We want to determine a list of valid architectures for which to pick tools and images.
   418  	// This includes architectures from custom and other available image metadata.
   419  	architectures := set.NewStrings()
   420  	if len(customImageMetadata) > 0 {
   421  		for _, customMetadata := range customImageMetadata {
   422  			architectures.Add(customMetadata.Arch)
   423  		}
   424  	}
   425  	if len(imageMetadata) > 0 {
   426  		for _, iMetadata := range imageMetadata {
   427  			architectures.Add(iMetadata.Arch)
   428  		}
   429  	}
   430  	bootstrapParams.ImageMetadata = imageMetadata
   431  
   432  	constraintsValidator, err := environ.ConstraintsValidator(callCtx)
   433  	if err != nil {
   434  		return err
   435  	}
   436  	constraintsValidator.UpdateVocabulary(constraints.Arch, architectures.SortedValues())
   437  
   438  	bootstrapConstraints, err := constraintsValidator.Merge(
   439  		args.ModelConstraints, args.BootstrapConstraints,
   440  	)
   441  	if err != nil {
   442  		return errors.Trace(err)
   443  	}
   444  	// The follow is used to determine if we should apply the default
   445  	// constraints when we bootstrap. Generally speaking this should always be
   446  	// applied, but there are exceptions to the rule e.g. local LXD
   447  	if checker, ok := environ.(environs.DefaultConstraintsChecker); !ok || checker.ShouldApplyControllerConstraints(bootstrapConstraints) {
   448  		bootstrapConstraints = withDefaultControllerConstraints(bootstrapConstraints)
   449  	}
   450  	bootstrapParams.BootstrapConstraints = bootstrapConstraints
   451  
   452  	var bootstrapArch string
   453  	if bootstrapConstraints.Arch != nil {
   454  		bootstrapArch = *bootstrapConstraints.Arch
   455  	} else {
   456  		// If no arch is specified as a constraint and we couldn't
   457  		// auto-discover the arch from the provider, we'll fall back
   458  		// to bootstrapping on the same arch as the CLI client.
   459  		bootstrapArch = localToolsArch()
   460  	}
   461  
   462  	agentVersion := jujuversion.Current
   463  	var availableTools coretools.List
   464  	if !args.BuildAgent {
   465  		latestPatchTxt := ""
   466  		versionTxt := fmt.Sprintf("%v", args.AgentVersion)
   467  		if args.AgentVersion == nil {
   468  			latestPatchTxt = "latest patch of "
   469  			versionTxt = fmt.Sprintf("%v.%v", agentVersion.Major, agentVersion.Minor)
   470  		}
   471  		ctx.Infof("Looking for %vpackaged Juju agent version %s for %s", latestPatchTxt, versionTxt, bootstrapArch)
   472  
   473  		availableTools, err = findPackagedTools(environ, ss, args.AgentVersion, &bootstrapArch, &bootstrapBase)
   474  		if err != nil && !errors.IsNotFound(err) {
   475  			return err
   476  		}
   477  		if len(availableTools) != 0 {
   478  			if args.AgentVersion == nil {
   479  				// If agent version was not specified in the arguments,
   480  				// we always want the latest/newest available.
   481  				agentVersion, availableTools = availableTools.Newest()
   482  			}
   483  			for _, tool := range availableTools {
   484  				ctx.Infof("Located Juju agent version %s at %s", tool.Version, tool.URL)
   485  			}
   486  		}
   487  	}
   488  	// If there are no prepackaged tools and a specific version has not been
   489  	// requested, look for or build a local binary.
   490  	var builtTools *sync.BuiltAgent
   491  	if len(availableTools) == 0 && (args.AgentVersion == nil || isCompatibleVersion(*args.AgentVersion, jujuversion.Current)) {
   492  		if args.BuildAgentTarball == nil {
   493  			return errors.New("cannot build agent binary to upload")
   494  		}
   495  		if err = validateUploadAllowed(environ, &bootstrapArch, &bootstrapBase, constraintsValidator); err != nil {
   496  			return err
   497  		}
   498  		if args.BuildAgent {
   499  			ctx.Infof("Building local Juju agent binary version %s for %s", args.AgentVersion, bootstrapArch)
   500  		} else {
   501  			ctx.Infof("No packaged binary found, preparing local Juju agent binary")
   502  		}
   503  		var forceVersion version.Number
   504  		availableTools, forceVersion, err = locallyBuildableTools()
   505  		if err != nil {
   506  			return errors.Annotate(err, "cannot package bootstrap agent binary")
   507  		}
   508  		builtTools, err = args.BuildAgentTarball(
   509  			args.BuildAgent, cfg.AgentStream(),
   510  			func(version.Number) version.Number { return forceVersion },
   511  		)
   512  		if err != nil {
   513  			return errors.Annotate(err, "cannot package bootstrap agent binary")
   514  		}
   515  		defer os.RemoveAll(builtTools.Dir)
   516  		// Combine the built agent information with the list of
   517  		// available tools.
   518  		for i, tool := range availableTools {
   519  			if tool.URL != "" {
   520  				continue
   521  			}
   522  			filename := filepath.Join(builtTools.Dir, builtTools.StorageName)
   523  			tool.URL = fmt.Sprintf("file://%s", filename)
   524  			tool.Size = builtTools.Size
   525  			tool.SHA256 = builtTools.Sha256Hash
   526  
   527  			// Use the version from the built tools but with the
   528  			// corrected series and arch - this ensures the build
   529  			// number is right if we found a valid official build.
   530  			version := builtTools.Version
   531  			version.Release = tool.Version.Release
   532  			version.Arch = tool.Version.Arch
   533  			// But if not an official build, use the forced version.
   534  			if !builtTools.Official {
   535  				version.Number = forceVersion
   536  			}
   537  			tool.Version = version
   538  			availableTools[i] = tool
   539  		}
   540  	}
   541  	if len(availableTools) == 0 {
   542  		return errors.New(noToolsMessage)
   543  	}
   544  	bootstrapParams.AvailableTools = availableTools
   545  
   546  	// TODO (anastasiamac 2018-02-02) By this stage, we will have a list
   547  	// of available tools (agent binaries) but they should all be the same
   548  	// version. Need to do check here, otherwise the provider.Bootstrap call
   549  	// may fail. This also means that compatibility check, currently done
   550  	// after provider.Bootstrap call in getBootstrapToolsVersion,
   551  	// should be done here.
   552  
   553  	// If we're uploading, we must override agent-version;
   554  	// if we're not uploading, we want to ensure we have an
   555  	// agent-version set anyway, to appease FinishInstanceConfig.
   556  	// In the latter case, setBootstrapTools will later set
   557  	// agent-version to the correct thing.
   558  	if args.AgentVersion != nil {
   559  		agentVersion = *args.AgentVersion
   560  	}
   561  	if cfg, err = cfg.Apply(map[string]interface{}{
   562  		"agent-version": agentVersion.String(),
   563  	}); err != nil {
   564  		return errors.Trace(err)
   565  	}
   566  	if err = environ.SetConfig(cfg); err != nil {
   567  		return errors.Trace(err)
   568  	}
   569  
   570  	if bootstrapParams.BootstrapConstraints.HasInstanceRole() {
   571  		instanceRoleEnviron, ok := environ.(environs.InstanceRole)
   572  		if !ok || !instanceRoleEnviron.SupportsInstanceRoles(callCtx) {
   573  			return errors.NewNotSupported(nil, "instance role constraint for provider")
   574  		}
   575  
   576  		bootstrapParams, err = finaliseInstanceRole(callCtx, instanceRoleEnviron, bootstrapParams)
   577  		if err != nil {
   578  			return errors.Annotate(err, "finalising instance role for provider")
   579  		}
   580  	}
   581  
   582  	ctx.Verbosef("Starting new instance for initial controller")
   583  
   584  	result, err := environ.Bootstrap(ctx, callCtx, bootstrapParams)
   585  	if err != nil {
   586  		return errors.Trace(err)
   587  	}
   588  
   589  	publicKey, err := userPublicSigningKey()
   590  	if err != nil {
   591  		return errors.Trace(err)
   592  	}
   593  	instanceConfig, err := instancecfg.NewBootstrapInstanceConfig(
   594  		args.ControllerConfig,
   595  		bootstrapParams.BootstrapConstraints,
   596  		args.ModelConstraints,
   597  		result.Base,
   598  		publicKey,
   599  		args.ExtraAgentValuesForTesting,
   600  	)
   601  	if err != nil {
   602  		return errors.Trace(err)
   603  	}
   604  
   605  	matchingTools, err := bootstrapParams.AvailableTools.Match(coretools.Filter{
   606  		Arch:   result.Arch,
   607  		OSType: result.Base.OS,
   608  	})
   609  	if err != nil {
   610  		return errors.Annotatef(err, "expected tools for %q", result.Base.OS)
   611  	}
   612  	selectedToolsList, err := getBootstrapToolsVersion(matchingTools)
   613  	if err != nil {
   614  		return errors.Trace(err)
   615  	}
   616  	// We set agent-version to the newest version, so the agent will immediately upgrade itself.
   617  	// Note that this only is relevant if a specific agent version has not been requested, since
   618  	// in that case the specific version will be the only version available.
   619  	newestToolVersion, _ := matchingTools.Newest()
   620  	// set agent version before finalizing bootstrap config
   621  	if err := setBootstrapAgentVersion(environ, newestToolVersion); err != nil {
   622  		return errors.Trace(err)
   623  	}
   624  
   625  	ctx.Infof("Installing Juju agent on bootstrap instance")
   626  	if err := instanceConfig.SetTools(selectedToolsList); err != nil {
   627  		return errors.Trace(err)
   628  	}
   629  
   630  	if err := instanceConfig.SetSnapSource(args.JujuDbSnapPath, args.JujuDbSnapAssertionsPath); err != nil {
   631  		return errors.Trace(err)
   632  	}
   633  
   634  	if err := instanceConfig.SetControllerCharm(args.ControllerCharmPath); err != nil {
   635  		return errors.Trace(err)
   636  	}
   637  	instanceConfig.Bootstrap.ControllerCharmChannel = args.ControllerCharmChannel
   638  
   639  	var environVersion int
   640  	if e, ok := environ.(environs.Environ); ok {
   641  		environVersion = e.Provider().Version()
   642  	}
   643  
   644  	if finalizer, ok := environ.(environs.BootstrapCredentialsFinaliser); ok {
   645  		cred, err := finalizer.FinaliseBootstrapCredential(
   646  			ctx,
   647  			bootstrapParams,
   648  			args.CloudCredential)
   649  
   650  		if err != nil {
   651  			return errors.Annotate(err, "finalizing bootstrap credential")
   652  		}
   653  
   654  		args.CloudCredential = cred
   655  	}
   656  
   657  	// Make sure we have the most recent environ config as the specified
   658  	// tools version has been updated there.
   659  	if err := finalizeInstanceBootstrapConfig(
   660  		ctx, instanceConfig, args, environ.Config(), environVersion, customImageMetadata,
   661  	); err != nil {
   662  		return errors.Annotate(err, "finalizing bootstrap instance config")
   663  	}
   664  	if err := result.CloudBootstrapFinalizer(ctx, instanceConfig, args.DialOpts); err != nil {
   665  		return errors.Trace(err)
   666  	}
   667  	return nil
   668  }
   669  
   670  func finaliseInstanceRole(
   671  	ctx context.ProviderCallContext,
   672  	ir environs.InstanceRole,
   673  	args environs.BootstrapParams,
   674  ) (environs.BootstrapParams, error) {
   675  	if *args.BootstrapConstraints.InstanceRole !=
   676  		environs.InstanceProfileAutoCreate {
   677  		return args, nil
   678  	}
   679  	irName, err := ir.CreateAutoInstanceRole(ctx, args)
   680  	args.BootstrapConstraints.InstanceRole = &irName
   681  	return args, err
   682  }
   683  
   684  // Bootstrap bootstraps the given environment. The supplied constraints are
   685  // used to provision the instance, and are also set within the bootstrapped
   686  // environment.
   687  func Bootstrap(
   688  	ctx environs.BootstrapContext,
   689  	environ environs.BootstrapEnviron,
   690  	callCtx context.ProviderCallContext,
   691  	args BootstrapParams,
   692  ) error {
   693  	if err := args.Validate(); err != nil {
   694  		return errors.Annotate(err, "validating bootstrap parameters")
   695  	}
   696  
   697  	// TODO(stickupkid): Once environs doesn't have a dependency on series, we
   698  	// can remove this conversion.
   699  	var bootstrapSeries string
   700  	if !args.BootstrapBase.Empty() {
   701  		var err error
   702  		bootstrapSeries, err = corebase.GetSeriesFromBase(args.BootstrapBase)
   703  		if err != nil {
   704  			return errors.NotValidf("base %q", args.BootstrapBase)
   705  		}
   706  	}
   707  	supportedBootstrapSeries := set.NewStrings()
   708  	for _, base := range args.SupportedBootstrapBases {
   709  		s, err := corebase.GetSeriesFromBase(base)
   710  		if err != nil {
   711  			return errors.Trace(err)
   712  		}
   713  		supportedBootstrapSeries.Add(s)
   714  	}
   715  
   716  	bootstrapParams := environs.BootstrapParams{
   717  		CloudName:                  args.Cloud.Name,
   718  		CloudRegion:                args.CloudRegion,
   719  		ControllerConfig:           args.ControllerConfig,
   720  		ModelConstraints:           args.ModelConstraints,
   721  		StoragePools:               args.StoragePools,
   722  		BootstrapSeries:            bootstrapSeries,
   723  		SupportedBootstrapSeries:   supportedBootstrapSeries,
   724  		Placement:                  args.Placement,
   725  		Force:                      args.Force,
   726  		ExtraAgentValuesForTesting: args.ExtraAgentValuesForTesting,
   727  	}
   728  	doBootstrap := bootstrapIAAS
   729  	if cloud.CloudIsCAAS(args.Cloud) {
   730  		doBootstrap = bootstrapCAAS
   731  	}
   732  
   733  	if err := doBootstrap(ctx, environ, callCtx, args, bootstrapParams); err != nil {
   734  		return errors.Trace(err)
   735  	}
   736  	if IsContextDone(ctx.Context()) {
   737  		ctx.Infof("Bootstrap cancelled, you may need to manually remove the bootstrap instance")
   738  		return Cancelled()
   739  	}
   740  
   741  	ctx.Infof("Bootstrap agent now started")
   742  	return nil
   743  }
   744  
   745  func finalizeInstanceBootstrapConfig(
   746  	ctx environs.BootstrapContext,
   747  	icfg *instancecfg.InstanceConfig,
   748  	args BootstrapParams,
   749  	cfg *config.Config,
   750  	environVersion int,
   751  	customImageMetadata []*imagemetadata.ImageMetadata,
   752  ) error {
   753  	if icfg.APIInfo != nil {
   754  		return errors.New("machine configuration already has api info")
   755  	}
   756  	controllerCfg := icfg.ControllerConfig
   757  	caCert, hasCACert := controllerCfg.CACert()
   758  	if !hasCACert {
   759  		return errors.New("controller configuration has no ca-cert")
   760  	}
   761  	icfg.APIInfo = &api.Info{
   762  		Password: args.AdminSecret,
   763  		CACert:   caCert,
   764  		ModelTag: names.NewModelTag(cfg.UUID()),
   765  	}
   766  
   767  	authority, err := pki.NewDefaultAuthorityPemCAKey(
   768  		[]byte(caCert), []byte(args.CAPrivateKey))
   769  	if err != nil {
   770  		return errors.Annotate(err, "loading juju certificate authority")
   771  	}
   772  
   773  	leaf, err := authority.LeafRequestForGroup(pki.DefaultLeafGroup).
   774  		AddDNSNames(controller.DefaultDNSNames...).
   775  		Commit()
   776  
   777  	if err != nil {
   778  		return errors.Annotate(err, "make juju default controller cert")
   779  	}
   780  
   781  	cert, key, err := leaf.ToPemParts()
   782  	if err != nil {
   783  		return errors.Annotate(err, "encoding default controller cert to pem")
   784  	}
   785  
   786  	icfg.Bootstrap.StateServingInfo = controller.StateServingInfo{
   787  		StatePort:    controllerCfg.StatePort(),
   788  		APIPort:      controllerCfg.APIPort(),
   789  		Cert:         string(cert),
   790  		PrivateKey:   string(key),
   791  		CAPrivateKey: args.CAPrivateKey,
   792  	}
   793  	icfg.Bootstrap.ControllerModelConfig = cfg
   794  	icfg.Bootstrap.ControllerModelEnvironVersion = environVersion
   795  	icfg.Bootstrap.CustomImageMetadata = customImageMetadata
   796  	icfg.Bootstrap.ControllerCloud = args.Cloud
   797  	icfg.Bootstrap.ControllerCloudRegion = args.CloudRegion
   798  	icfg.Bootstrap.ControllerCloudCredential = args.CloudCredential
   799  	icfg.Bootstrap.ControllerCloudCredentialName = args.CloudCredentialName
   800  	icfg.Bootstrap.ControllerConfig = args.ControllerConfig
   801  	icfg.Bootstrap.ControllerInheritedConfig = args.ControllerInheritedConfig
   802  	icfg.Bootstrap.RegionInheritedConfig = args.Cloud.RegionConfig
   803  	icfg.Bootstrap.InitialModelConfig = args.InitialModelConfig
   804  	icfg.Bootstrap.StoragePools = args.StoragePools
   805  	icfg.Bootstrap.Timeout = args.DialOpts.Timeout
   806  	icfg.Bootstrap.JujuDbSnapPath = args.JujuDbSnapPath
   807  	icfg.Bootstrap.JujuDbSnapAssertionsPath = args.JujuDbSnapAssertionsPath
   808  	icfg.Bootstrap.ControllerCharm = args.ControllerCharmPath
   809  	icfg.Bootstrap.ControllerCharmChannel = args.ControllerCharmChannel
   810  	return nil
   811  }
   812  
   813  func finalizePodBootstrapConfig(
   814  	ctx environs.BootstrapContext,
   815  	pcfg *podcfg.ControllerPodConfig,
   816  	args BootstrapParams,
   817  	cfg *config.Config,
   818  ) error {
   819  	if pcfg.APIInfo != nil {
   820  		return errors.New("machine configuration already has api info")
   821  	}
   822  
   823  	controllerCfg := pcfg.Controller
   824  	caCert, hasCACert := controllerCfg.CACert()
   825  	if !hasCACert {
   826  		return errors.New("controller configuration has no ca-cert")
   827  	}
   828  	pcfg.APIInfo = &api.Info{
   829  		Password: args.AdminSecret,
   830  		CACert:   caCert,
   831  		ModelTag: names.NewModelTag(cfg.UUID()),
   832  	}
   833  
   834  	authority, err := pki.NewDefaultAuthorityPemCAKey(
   835  		[]byte(caCert), []byte(args.CAPrivateKey))
   836  	if err != nil {
   837  		return errors.Annotate(err, "loading juju certificate authority")
   838  	}
   839  
   840  	// We generate a controller certificate with a set of well known static dns
   841  	// names. IP addresses are left for other workers to make subsequent
   842  	// certificates.
   843  	leaf, err := authority.LeafRequestForGroup(pki.DefaultLeafGroup).
   844  		AddDNSNames(controller.DefaultDNSNames...).
   845  		Commit()
   846  
   847  	if err != nil {
   848  		return errors.Annotate(err, "make juju default controller cert")
   849  	}
   850  
   851  	cert, key, err := leaf.ToPemParts()
   852  	if err != nil {
   853  		return errors.Annotate(err, "encoding default controller cert to pem")
   854  	}
   855  
   856  	pcfg.Bootstrap.StateServingInfo = controller.StateServingInfo{
   857  		StatePort:    controllerCfg.StatePort(),
   858  		APIPort:      controllerCfg.APIPort(),
   859  		Cert:         string(cert),
   860  		PrivateKey:   string(key),
   861  		CAPrivateKey: args.CAPrivateKey,
   862  	}
   863  	if _, ok := cfg.AgentVersion(); !ok {
   864  		return errors.New("controller model configuration has no agent-version")
   865  	}
   866  
   867  	pcfg.AgentEnvironment = make(map[string]string)
   868  	for k, v := range args.ExtraAgentValuesForTesting {
   869  		pcfg.AgentEnvironment[k] = v
   870  	}
   871  
   872  	pcfg.Bootstrap.ControllerModelConfig = cfg
   873  	pcfg.Bootstrap.ControllerCloud = args.Cloud
   874  	pcfg.Bootstrap.ControllerCloudRegion = args.CloudRegion
   875  	pcfg.Bootstrap.ControllerCloudCredential = args.CloudCredential
   876  	pcfg.Bootstrap.ControllerCloudCredentialName = args.CloudCredentialName
   877  	pcfg.Bootstrap.ControllerConfig = args.ControllerConfig
   878  	pcfg.Bootstrap.ControllerInheritedConfig = args.ControllerInheritedConfig
   879  	pcfg.Bootstrap.InitialModelConfig = args.InitialModelConfig
   880  	pcfg.Bootstrap.StoragePools = args.StoragePools
   881  	pcfg.Bootstrap.Timeout = args.DialOpts.Timeout
   882  	pcfg.Bootstrap.ControllerServiceType = args.ControllerServiceType
   883  	pcfg.Bootstrap.ControllerExternalName = args.ControllerExternalName
   884  	pcfg.Bootstrap.ControllerExternalIPs = append([]string(nil), args.ControllerExternalIPs...)
   885  	pcfg.Bootstrap.ControllerCharmPath = args.ControllerCharmPath
   886  	pcfg.Bootstrap.ControllerCharmChannel = args.ControllerCharmChannel
   887  	return nil
   888  }
   889  
   890  func userPublicSigningKey() (string, error) {
   891  	signingKeyFile := os.Getenv("JUJU_STREAMS_PUBLICKEY_FILE")
   892  	signingKey := ""
   893  	if signingKeyFile != "" {
   894  		path, err := utils.NormalizePath(signingKeyFile)
   895  		if err != nil {
   896  			return "", errors.Annotatef(err, "cannot expand key file path: %s", signingKeyFile)
   897  		}
   898  		b, err := os.ReadFile(path)
   899  		if err != nil {
   900  			return "", errors.Annotatef(err, "invalid public key file: %s", path)
   901  		}
   902  		signingKey = string(b)
   903  	}
   904  	return signingKey, nil
   905  }
   906  
   907  // bootstrapImageMetadata returns the image metadata to use for bootstrapping
   908  // the given environment. If the environment provider does not make use of
   909  // simplestreams, no metadata will be returned.
   910  //
   911  // If a bootstrap image ID is specified, image metadata will be synthesised
   912  // using that image ID, and the architecture and series specified by the
   913  // initiator. In addition, the custom image metadata that is saved into the
   914  // state database will have the synthesised image metadata added to it.
   915  func bootstrapImageMetadata(
   916  	environ environs.BootstrapEnviron,
   917  	fetcher imagemetadata.SimplestreamsFetcher,
   918  	bootstrapBase *corebase.Base,
   919  	bootstrapArch string,
   920  	bootstrapImageId string,
   921  	customImageMetadata *[]*imagemetadata.ImageMetadata,
   922  ) ([]*imagemetadata.ImageMetadata, error) {
   923  
   924  	hasRegion, ok := environ.(simplestreams.HasRegion)
   925  	if !ok {
   926  		if bootstrapImageId != "" {
   927  			// We only support specifying image IDs for providers
   928  			// that use simplestreams for now.
   929  			return nil, errors.NotSupportedf(
   930  				"specifying bootstrap image for %q provider",
   931  				environ.Config().Type(),
   932  			)
   933  		}
   934  		// No region, no metadata.
   935  		return nil, nil
   936  	}
   937  	region, err := hasRegion.Region()
   938  	if err != nil {
   939  		return nil, errors.Trace(err)
   940  	}
   941  
   942  	if bootstrapImageId != "" {
   943  		if bootstrapBase == nil {
   944  			return nil, errors.NotValidf("no base specified with bootstrap image")
   945  		}
   946  		// The returned metadata does not have information about the
   947  		// storage or virtualisation type. Any provider that wants to
   948  		// filter on those properties should allow for empty values.
   949  		meta := &imagemetadata.ImageMetadata{
   950  			Id:         bootstrapImageId,
   951  			Arch:       bootstrapArch,
   952  			Version:    bootstrapBase.Channel.Track,
   953  			RegionName: region.Region,
   954  			Endpoint:   region.Endpoint,
   955  			Stream:     environ.Config().ImageStream(),
   956  		}
   957  		*customImageMetadata = append(*customImageMetadata, meta)
   958  		return []*imagemetadata.ImageMetadata{meta}, nil
   959  	}
   960  
   961  	// For providers that support making use of simplestreams
   962  	// image metadata, search public image metadata. We need
   963  	// to pass this onto Bootstrap for selecting images.
   964  	sources, err := environs.ImageMetadataSources(environ, fetcher)
   965  	if err != nil {
   966  		return nil, errors.Trace(err)
   967  	}
   968  	// This constraint will search image metadata for all supported architectures and series.
   969  	imageConstraint, err := imagemetadata.NewImageConstraint(simplestreams.LookupParams{
   970  		CloudSpec: region,
   971  		Stream:    environ.Config().ImageStream(),
   972  	})
   973  	if err != nil {
   974  		return nil, errors.Trace(err)
   975  	}
   976  	logger.Debugf("constraints for image metadata lookup %v", imageConstraint)
   977  
   978  	// Get image metadata from all data sources.
   979  	// Since order of data source matters, order of image metadata matters too. Append is important here.
   980  	var publicImageMetadata []*imagemetadata.ImageMetadata
   981  	for _, source := range sources {
   982  		sourceMetadata, _, err := imagemetadata.Fetch(fetcher, []simplestreams.DataSource{source}, imageConstraint)
   983  		if errors.Is(err, errors.NotFound) || errors.Is(err, errors.Unauthorized) {
   984  			logger.Debugf("ignoring image metadata in %s: %v", source.Description(), err)
   985  			// Just keep looking...
   986  			continue
   987  		} else if err != nil {
   988  			// When we get an actual protocol/unexpected error, we need to stop.
   989  			return nil, errors.Annotatef(err, "failed looking for image metadata in %s", source.Description())
   990  		}
   991  		logger.Debugf("found %d image metadata in %s", len(sourceMetadata), source.Description())
   992  		publicImageMetadata = append(publicImageMetadata, sourceMetadata...)
   993  	}
   994  
   995  	logger.Debugf("found %d image metadata from all image data sources", len(publicImageMetadata))
   996  	return publicImageMetadata, nil
   997  }
   998  
   999  // getBootstrapToolsVersion returns the newest tools from the given tools list.
  1000  func getBootstrapToolsVersion(possibleTools coretools.List) (coretools.List, error) {
  1001  	if len(possibleTools) == 0 {
  1002  		return nil, errors.New("no bootstrap agent binaries available")
  1003  	}
  1004  	var newVersion version.Number
  1005  	newVersion, toolsList := possibleTools.Newest()
  1006  	logger.Infof("newest version: %s", newVersion)
  1007  	bootstrapVersion := newVersion
  1008  	// We should only ever bootstrap the exact same version as the client,
  1009  	// or we risk bootstrap incompatibility.
  1010  	if !isCompatibleVersion(newVersion, jujuversion.Current) {
  1011  		compatibleVersion, compatibleTools := findCompatibleTools(possibleTools, jujuversion.Current)
  1012  		if len(compatibleTools) == 0 {
  1013  			logger.Infof(
  1014  				"failed to find %s agent binaries, will attempt to use %s",
  1015  				jujuversion.Current, newVersion,
  1016  			)
  1017  		} else {
  1018  			bootstrapVersion, toolsList = compatibleVersion, compatibleTools
  1019  		}
  1020  	}
  1021  	logger.Infof("picked bootstrap agent binary version: %s", bootstrapVersion)
  1022  	return toolsList, nil
  1023  }
  1024  
  1025  // setBootstrapAgentVersion updates the agent-version configuration attribute.
  1026  func setBootstrapAgentVersion(environ environs.Configer, toolsVersion version.Number) error {
  1027  	cfg := environ.Config()
  1028  	if agentVersion, _ := cfg.AgentVersion(); agentVersion != toolsVersion {
  1029  		cfg, err := cfg.Apply(map[string]interface{}{
  1030  			"agent-version": toolsVersion.String(),
  1031  		})
  1032  		if err == nil {
  1033  			err = environ.SetConfig(cfg)
  1034  		}
  1035  		if err != nil {
  1036  			return errors.Errorf("failed to update model configuration: %v", err)
  1037  		}
  1038  	}
  1039  	return nil
  1040  }
  1041  
  1042  // findCompatibleTools finds tools in the list that have the same major, minor
  1043  // and patch level as jujuversion.Current.
  1044  //
  1045  // Build number is not important to match; uploaded tools will have
  1046  // incremented build number, and we want to match them.
  1047  func findCompatibleTools(possibleTools coretools.List, version version.Number) (version.Number, coretools.List) {
  1048  	var compatibleTools coretools.List
  1049  	for _, tools := range possibleTools {
  1050  		if isCompatibleVersion(tools.Version.Number, version) {
  1051  			compatibleTools = append(compatibleTools, tools)
  1052  		}
  1053  	}
  1054  	return compatibleTools.Newest()
  1055  }
  1056  
  1057  func isCompatibleVersion(v1, v2 version.Number) bool {
  1058  	x := v1.ToPatch()
  1059  	y := v2.ToPatch()
  1060  	return x.Compare(y) == 0
  1061  }
  1062  
  1063  // setPrivateMetadataSources verifies the specified metadataDir exists,
  1064  // uses it to set the default agent metadata source for agent binaries,
  1065  // and adds an image metadata source after verifying the contents. If the
  1066  // directory ends in tools, only the default tools metadata source will be
  1067  // set. Same for images.
  1068  func setPrivateMetadataSources(fetcher imagemetadata.SimplestreamsFetcher, metadataDir string) ([]*imagemetadata.ImageMetadata, error) {
  1069  	if _, err := os.Stat(metadataDir); err != nil {
  1070  		if !os.IsNotExist(err) {
  1071  			return nil, errors.Annotate(err, "cannot access simplestreams metadata directory")
  1072  		}
  1073  		return nil, errors.NotFoundf("simplestreams metadata source: %s", metadataDir)
  1074  	}
  1075  
  1076  	agentBinaryMetadataDir := metadataDir
  1077  	ending := filepath.Base(agentBinaryMetadataDir)
  1078  	if ending != storage.BaseToolsPath {
  1079  		agentBinaryMetadataDir = filepath.Join(metadataDir, storage.BaseToolsPath)
  1080  	}
  1081  	if _, err := os.Stat(agentBinaryMetadataDir); err != nil {
  1082  		if !os.IsNotExist(err) {
  1083  			return nil, errors.Annotate(err, "cannot access agent metadata")
  1084  		}
  1085  		logger.Debugf("no agent directory found, using default agent metadata source: %s", tools.DefaultBaseURL)
  1086  	} else {
  1087  		if ending == storage.BaseToolsPath {
  1088  			// As the specified metadataDir ended in 'tools'
  1089  			// assume that is the only metadata to find and return.
  1090  			tools.DefaultBaseURL = filepath.Dir(metadataDir)
  1091  			logger.Debugf("setting default agent metadata source: %s", tools.DefaultBaseURL)
  1092  			return nil, nil
  1093  		} else {
  1094  			tools.DefaultBaseURL = metadataDir
  1095  			logger.Debugf("setting default agent metadata source: %s", tools.DefaultBaseURL)
  1096  		}
  1097  	}
  1098  
  1099  	imageMetadataDir := metadataDir
  1100  	ending = filepath.Base(imageMetadataDir)
  1101  	if ending != storage.BaseImagesPath {
  1102  		imageMetadataDir = filepath.Join(metadataDir, storage.BaseImagesPath)
  1103  	}
  1104  	if _, err := os.Stat(imageMetadataDir); err != nil {
  1105  		if !os.IsNotExist(err) {
  1106  			return nil, errors.Annotate(err, "cannot access image metadata")
  1107  		}
  1108  		return nil, nil
  1109  	} else {
  1110  		logger.Debugf("setting default image metadata source: %s", imageMetadataDir)
  1111  	}
  1112  
  1113  	baseURL := fmt.Sprintf("file://%s", filepath.ToSlash(imageMetadataDir))
  1114  	publicKey, err := simplestreams.UserPublicSigningKey()
  1115  	if err != nil {
  1116  		return nil, errors.Trace(err)
  1117  	}
  1118  	// TODO: (hml) 2020-01-08
  1119  	// Why ignore the the model-config "ssl-hostname-verification" value in
  1120  	// the config here? Its default value is true.
  1121  	dataSourceConfig := simplestreams.Config{
  1122  		Description:          "bootstrap metadata",
  1123  		BaseURL:              baseURL,
  1124  		PublicSigningKey:     publicKey,
  1125  		HostnameVerification: false,
  1126  		Priority:             simplestreams.CUSTOM_CLOUD_DATA,
  1127  	}
  1128  	if err := dataSourceConfig.Validate(); err != nil {
  1129  		return nil, errors.Annotate(err, "simplestreams config validation failed")
  1130  	}
  1131  	dataSource := fetcher.NewDataSource(dataSourceConfig)
  1132  
  1133  	// Read the image metadata, as we'll want to upload it to the environment.
  1134  	imageConstraint, err := imagemetadata.NewImageConstraint(simplestreams.LookupParams{})
  1135  	if err != nil {
  1136  		return nil, errors.Trace(err)
  1137  	}
  1138  	existingMetadata, _, err := imagemetadata.Fetch(fetcher, []simplestreams.DataSource{dataSource}, imageConstraint)
  1139  	if err != nil && !errors.IsNotFound(err) {
  1140  		return nil, errors.Annotatef(err, "cannot read image metadata in %s", dataSource.Description())
  1141  	}
  1142  
  1143  	// Add an image metadata datasource for constraint validation, etc.
  1144  	environs.RegisterUserImageDataSourceFunc("bootstrap metadata", func(environs.Environ) (simplestreams.DataSource, error) {
  1145  		return dataSource, nil
  1146  	})
  1147  	logger.Infof("custom image metadata added to search path")
  1148  	return existingMetadata, nil
  1149  }
  1150  
  1151  // Cancelled returns an error that satisfies IsCancelled.
  1152  func Cancelled() error {
  1153  	return errCancelled
  1154  }
  1155  
  1156  // IsContextDone returns true if the context is done.
  1157  func IsContextDone(ctx stdcontext.Context) bool {
  1158  	return ctx.Err() != nil
  1159  }