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

     1  // Copyright 2011-2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package bootstrap
     5  
     6  import (
     7  	"github.com/juju/errors"
     8  	"github.com/juju/featureflag"
     9  	"github.com/juju/names/v5"
    10  
    11  	"github.com/juju/juju/caas"
    12  	"github.com/juju/juju/controller"
    13  	"github.com/juju/juju/core/model"
    14  	"github.com/juju/juju/core/permission"
    15  	"github.com/juju/juju/environs"
    16  	environscloudspec "github.com/juju/juju/environs/cloudspec"
    17  	"github.com/juju/juju/environs/config"
    18  	"github.com/juju/juju/feature"
    19  	"github.com/juju/juju/jujuclient"
    20  )
    21  
    22  const (
    23  	// ControllerModelName is the name of the admin model in each controller.
    24  	ControllerModelName = "controller"
    25  
    26  	// ControllerCharmName is the name of the controller charm.
    27  	ControllerCharmName = "juju-controller"
    28  
    29  	// ControllerApplicationName is the name of the controller application.
    30  	ControllerApplicationName = "controller"
    31  
    32  	// ControllerCharmArchive is the name of the controller charm archive.
    33  	ControllerCharmArchive = "controller.charm"
    34  )
    35  
    36  // PrepareParams contains the parameters for preparing a controller Environ
    37  // for bootstrapping.
    38  type PrepareParams struct {
    39  	// ModelConfig contains the base configuration for the controller model.
    40  	//
    41  	// This includes the model name, cloud type, any user-supplied
    42  	// configuration, config inherited from controller, and any defaults.
    43  	ModelConfig map[string]interface{}
    44  
    45  	// ControllerConfig is the configuration of the controller being prepared.
    46  	ControllerConfig controller.Config
    47  
    48  	// ControllerName is the name of the controller being prepared.
    49  	ControllerName string
    50  
    51  	// Cloud is the specification of the cloud that the controller is
    52  	// being prepared for.
    53  	Cloud environscloudspec.CloudSpec
    54  
    55  	// CredentialName is the name of the credential to use to bootstrap.
    56  	// This will be empty for auto-detected credentials.
    57  	CredentialName string
    58  
    59  	// AdminSecret contains the password for the admin user.
    60  	AdminSecret string
    61  }
    62  
    63  // Validate validates the PrepareParams.
    64  func (p PrepareParams) Validate() error {
    65  	if err := p.ControllerConfig.Validate(); err != nil {
    66  		return errors.Annotate(err, "validating controller config")
    67  	}
    68  	if p.ControllerName == "" {
    69  		return errors.NotValidf("empty controller name")
    70  	}
    71  	if p.Cloud.Name == "" {
    72  		return errors.NotValidf("empty cloud name")
    73  	}
    74  	if p.AdminSecret == "" {
    75  		return errors.NotValidf("empty admin-secret")
    76  	}
    77  	return nil
    78  }
    79  
    80  // PrepareController prepares a new controller based on the provided configuration.
    81  // It is an error to prepare a controller if there already exists an
    82  // entry in the client store with the same name.
    83  //
    84  // Upon success, Prepare will update the ClientStore with the details of
    85  // the controller, admin account, and admin model.
    86  func PrepareController(
    87  	isCAASController bool,
    88  	ctx environs.BootstrapContext,
    89  	store jujuclient.ClientStore,
    90  	args PrepareParams,
    91  ) (environs.BootstrapEnviron, error) {
    92  
    93  	if err := args.Validate(); err != nil {
    94  		return nil, errors.Trace(err)
    95  	}
    96  
    97  	_, err := store.ControllerByName(args.ControllerName)
    98  	if err == nil {
    99  		return nil, errors.AlreadyExistsf("controller %q", args.ControllerName)
   100  	} else if !errors.IsNotFound(err) {
   101  		return nil, errors.Annotatef(err, "error reading controller %q info", args.ControllerName)
   102  	}
   103  
   104  	cloudType, ok := args.ModelConfig["type"].(string)
   105  	if !ok {
   106  		return nil, errors.NotFoundf("cloud type in base configuration")
   107  	}
   108  
   109  	p, err := environs.Provider(cloudType)
   110  	if err != nil {
   111  		return nil, errors.Trace(err)
   112  	}
   113  
   114  	cfg, details, err := prepare(ctx, p, args)
   115  	if err != nil {
   116  		return nil, errors.Trace(err)
   117  	}
   118  
   119  	do := func() error {
   120  		if err := decorateAndWriteInfo(
   121  			store, details, args.ControllerName, cfg.Name(),
   122  		); err != nil {
   123  			return errors.Annotatef(err, "cannot create controller %q info", args.ControllerName)
   124  		}
   125  		return nil
   126  	}
   127  
   128  	var env environs.BootstrapEnviron
   129  	openParams := environs.OpenParams{
   130  		ControllerUUID: args.ControllerConfig.ControllerUUID(),
   131  		Cloud:          args.Cloud,
   132  		Config:         cfg,
   133  	}
   134  	if isCAASController {
   135  		details.ModelType = model.CAAS
   136  		env, err = caas.Open(ctx.Context(), p, openParams)
   137  	} else {
   138  		details.ModelType = model.IAAS
   139  		env, err = environs.Open(ctx.Context(), p, openParams)
   140  	}
   141  	if err != nil {
   142  		return nil, errors.Trace(err)
   143  	}
   144  	if err := env.PrepareForBootstrap(ctx, args.ControllerName); err != nil {
   145  		return nil, errors.Trace(err)
   146  	}
   147  	if err := do(); err != nil {
   148  		return nil, errors.Trace(err)
   149  	}
   150  	return env, nil
   151  }
   152  
   153  // decorateAndWriteInfo decorates the info struct with information
   154  // from the given cfg, and the writes that out to the filesystem.
   155  func decorateAndWriteInfo(
   156  	store jujuclient.ClientStore,
   157  	details prepareDetails,
   158  	controllerName, modelName string,
   159  ) error {
   160  	qualifiedModelName := jujuclient.JoinOwnerModelName(
   161  		names.NewUserTag(details.AccountDetails.User),
   162  		modelName,
   163  	)
   164  	if err := store.AddController(controllerName, details.ControllerDetails); err != nil {
   165  		return errors.Trace(err)
   166  	}
   167  	if err := store.UpdateBootstrapConfig(controllerName, details.BootstrapConfig); err != nil {
   168  		return errors.Trace(err)
   169  	}
   170  	if err := store.UpdateAccount(controllerName, details.AccountDetails); err != nil {
   171  		return errors.Trace(err)
   172  	}
   173  	if err := store.UpdateModel(controllerName, qualifiedModelName, details.ModelDetails); err != nil {
   174  		return errors.Trace(err)
   175  	}
   176  	if err := store.SetCurrentModel(controllerName, qualifiedModelName); err != nil {
   177  		return errors.Trace(err)
   178  	}
   179  	return nil
   180  }
   181  
   182  func prepare(
   183  	ctx environs.BootstrapContext,
   184  	p environs.EnvironProvider,
   185  	args PrepareParams,
   186  ) (*config.Config, prepareDetails, error) {
   187  	var details prepareDetails
   188  
   189  	cfg, err := config.New(config.NoDefaults, args.ModelConfig)
   190  	if err != nil {
   191  		return cfg, details, errors.Trace(err)
   192  	}
   193  
   194  	cfg, err = p.PrepareConfig(environs.PrepareConfigParams{Cloud: args.Cloud, Config: cfg})
   195  	if err != nil {
   196  		return cfg, details, errors.Trace(err)
   197  	}
   198  
   199  	// We store the base configuration only; we don't want the
   200  	// default attributes, generated secrets/certificates, or
   201  	// UUIDs stored in the bootstrap config. Make a copy, so
   202  	// we don't disturb the caller's config map.
   203  	details.Config = make(map[string]interface{})
   204  	for k, v := range args.ModelConfig {
   205  		details.Config[k] = v
   206  	}
   207  	delete(details.Config, config.UUIDKey)
   208  
   209  	// TODO(axw) change signature of CACert() to not return a bool.
   210  	// It's no longer possible to have a controller config without
   211  	// a CA certificate.
   212  	caCert, ok := args.ControllerConfig.CACert()
   213  	if !ok {
   214  		return cfg, details, errors.New("controller config is missing CA certificate")
   215  	}
   216  
   217  	// We want to store attributes describing how a controller has been configured.
   218  	// These do not include the CACert or UUID since they will be replaced with new
   219  	// values when/if we need to use this configuration.
   220  	details.ControllerConfig = make(controller.Config)
   221  	for k, v := range args.ControllerConfig {
   222  		if k == controller.CACertKey || k == controller.ControllerUUIDKey {
   223  			continue
   224  		}
   225  		details.ControllerConfig[k] = v
   226  	}
   227  	for k, v := range args.ControllerConfig {
   228  		if k == controller.CACertKey || k == controller.ControllerUUIDKey {
   229  			continue
   230  		}
   231  		details.ControllerConfig[k] = v
   232  	}
   233  	details.CACert = caCert
   234  	details.ControllerUUID = args.ControllerConfig.ControllerUUID()
   235  	details.ControllerModelUUID = args.ModelConfig[config.UUIDKey].(string)
   236  	details.User = environs.AdminUser
   237  	details.Password = args.AdminSecret
   238  	details.LastKnownAccess = string(permission.SuperuserAccess)
   239  	details.ModelUUID = cfg.UUID()
   240  	if featureflag.Enabled(feature.Branches) || featureflag.Enabled(feature.Generations) {
   241  		details.ActiveBranch = model.GenerationMaster
   242  	}
   243  	details.ControllerDetails.Cloud = args.Cloud.Name
   244  	details.ControllerDetails.CloudRegion = args.Cloud.Region
   245  	details.ControllerDetails.CloudType = args.Cloud.Type
   246  	details.BootstrapConfig.CloudType = args.Cloud.Type
   247  	details.BootstrapConfig.Cloud = args.Cloud.Name
   248  	details.BootstrapConfig.CloudRegion = args.Cloud.Region
   249  	details.BootstrapConfig.CloudCACertificates = args.Cloud.CACertificates
   250  	details.BootstrapConfig.SkipTLSVerify = args.Cloud.SkipTLSVerify
   251  	details.CloudEndpoint = args.Cloud.Endpoint
   252  	details.CloudIdentityEndpoint = args.Cloud.IdentityEndpoint
   253  	details.CloudStorageEndpoint = args.Cloud.StorageEndpoint
   254  	details.Credential = args.CredentialName
   255  
   256  	if args.Cloud.SkipTLSVerify {
   257  		if len(args.Cloud.CACertificates) > 0 && args.Cloud.CACertificates[0] != "" {
   258  			return cfg, details, errors.NotValidf("cloud with both skip-TLS-verify=true and CA certificates")
   259  		}
   260  		logger.Warningf("controller %v is configured to skip validity checks on the server's certificate", args.ControllerName)
   261  	}
   262  
   263  	return cfg, details, nil
   264  }
   265  
   266  type prepareDetails struct {
   267  	jujuclient.ControllerDetails
   268  	jujuclient.BootstrapConfig
   269  	jujuclient.AccountDetails
   270  	jujuclient.ModelDetails
   271  }