14
14
"github.com/juju/errors"
15
15
"github.com/juju/utils"
16
16
"github.com/juju/utils/featureflag"
17
"github.com/juju/version"
17
18
"gopkg.in/juju/charm.v6-unstable"
18
19
"launchpad.net/gnuflag"
20
21
apiblock "github.com/juju/juju/api/block"
21
"github.com/juju/juju/apiserver"
22
"github.com/juju/juju/apiserver/params"
22
23
jujucloud "github.com/juju/juju/cloud"
23
24
"github.com/juju/juju/cmd/juju/block"
25
"github.com/juju/juju/cmd/juju/common"
24
26
"github.com/juju/juju/cmd/modelcmd"
25
27
"github.com/juju/juju/constraints"
26
28
"github.com/juju/juju/environs"
27
29
"github.com/juju/juju/environs/bootstrap"
28
30
"github.com/juju/juju/environs/config"
29
"github.com/juju/juju/environs/configstore"
30
31
"github.com/juju/juju/feature"
31
32
"github.com/juju/juju/instance"
32
33
"github.com/juju/juju/juju"
33
34
"github.com/juju/juju/juju/osenv"
34
35
"github.com/juju/juju/jujuclient"
35
36
"github.com/juju/juju/network"
36
"github.com/juju/juju/version"
37
jujuversion "github.com/juju/juju/version"
39
40
// provisionalProviders is the names of providers that are hidden behind
51
52
The controller will be setup with an intial controller model called "admin" as well
52
53
as a hosted model which can be used to run workloads.
54
If boostrap-constraints are specified in the bootstrap command,
55
they will apply to the machine provisioned for the juju controller,
55
If boostrap-constraints are specified in the bootstrap command,
56
they will apply to the machine provisioned for the juju controller,
56
57
and any future controllers provisioned for HA.
58
If constraints are specified, they will be set as the default constraints
59
on the model for all future workload machines,
59
If constraints are specified, they will be set as the default constraints
60
on the model for all future workload machines,
60
61
exactly as if the constraints were set with juju set-constraints.
62
63
It is possible to override constraints and the automatic machine selection
99
100
juju help placement
103
// defaultHostedModelName is the name of the hosted model created in each
104
// controller for deploying workloads to, in addition to the "admin" model.
105
const defaultHostedModelName = "default"
102
107
func newBootstrapCommand() cmd.Command {
103
return modelcmd.Wrap(&bootstrapCommand{
104
CredentialStore: jujuclient.NewFileCredentialStore(),
108
return modelcmd.Wrap(
110
modelcmd.ModelSkipFlags, modelcmd.ModelSkipDefault,
108
114
// bootstrapCommand is responsible for launching the first machine in a juju
109
115
// environment, and setting up everything necessary to continue working.
110
116
type bootstrapCommand struct {
111
117
modelcmd.ModelCommandBase
112
CredentialStore jujuclient.CredentialStore
114
119
Constraints constraints.Value
115
120
BootstrapConstraints constraints.Value
154
160
f.StringVar(&c.AgentVersionParam, "agent-version", "", "the version of tools to use for Juju agents")
155
161
f.StringVar(&c.CredentialName, "credential", "", "the credentials to use when bootstrapping")
156
162
f.Var(&c.config, "config", "specify a controller config file, or one or more controller configuration options (--config config.yaml [--config k=v ...])")
163
f.StringVar(&c.hostedModelName, "d", defaultHostedModelName, "the name of the default hosted model for the controller")
164
f.StringVar(&c.hostedModelName, "default-model", defaultHostedModelName, "the name of the default hosted model for the controller")
159
167
func (c *bootstrapCommand) Init(args []string) (err error) {
297
305
// Get the credentials and region name.
298
credential, regionName, err := c.getCredentials(ctx, c.Cloud, cloud)
306
store := c.ClientStore()
307
credential, credentialName, regionName, err := modelcmd.GetCredentials(
308
store, c.Region, c.CredentialName, c.Cloud, cloud.Type,
299
310
if errors.IsNotFound(err) && c.CredentialName == "" {
300
311
// No credential was explicitly specified, and no credential
301
312
// was found in credentials.yaml; have the provider detect
302
313
// credentials from the environment.
303
314
ctx.Verbosef("no credentials found, checking environment")
304
provider, err := environs.Provider(cloud.Type)
306
return errors.Trace(err)
308
detected, err := provider.DetectCredentials()
310
return errors.Annotatef(err, "detecting credentials for %q cloud provider", c.Cloud)
312
logger.Tracef("provider detected credentials: %v", detected)
313
if len(detected.AuthCredentials) == 0 {
314
return errors.NotFoundf("credentials for cloud %q", c.Cloud)
316
if len(detected.AuthCredentials) > 1 {
315
detected, err := modelcmd.DetectCredential(c.Cloud, cloud.Type)
316
if errors.Cause(err) == modelcmd.ErrMultipleCredentials {
317
317
return ambiguousCredentialError
318
} else if err != nil {
319
return errors.Trace(err)
319
321
// We have one credential so extract it from the map.
320
322
var oneCredential jujucloud.Credential
335
337
return errors.Trace(err)
338
// Create an environment config from the cloud and credentials. The
339
// controller's model should be called "admin".
340
hostedModelUUID, err := utils.NewUUID()
342
return errors.Trace(err)
344
controllerUUID, err := utils.NewUUID()
346
return errors.Trace(err)
349
// Create an environment config from the cloud and credentials.
340
350
configAttrs := map[string]interface{}{
342
// TODO(axw) for now we call the initial model the same as the
343
// controller, without the "local." prefix. This is necessary
344
// to make CI happy. Once CI is updated, we'll switch over to
346
"name": configstore.AdminModelName(c.controllerName),
352
"name": environs.ControllerModelName,
353
config.UUIDKey: controllerUUID.String(),
354
config.ControllerUUIDKey: controllerUUID.String(),
348
356
userConfigAttrs, err := c.config.ReadAttrs(ctx)
353
361
configAttrs[k] = v
355
363
logger.Debugf("preparing controller with config: %v", configAttrs)
356
cfg, err := config.New(config.UseDefaults, configAttrs)
358
return errors.Annotate(err, "creating environment configuration")
360
store, err := configstore.Default()
362
return errors.Trace(err)
364
controllerStore := c.ClientStore()
365
// Read existing current controller, account, model so we can clean up on error.
366
var oldCurrentController string
367
oldCurrentController, err = modelcmd.ReadCurrentController()
369
return errors.Annotate(err, "error reading current controller")
373
if resultErr == nil || errors.IsAlreadyExists(resultErr) {
376
if oldCurrentController != "" {
377
if err := modelcmd.WriteCurrentController(oldCurrentController); err != nil {
379
"cannot reset current controller to %q: %v",
380
oldCurrentController, err,
384
if err := store.RemoveController(c.controllerName); err != nil {
386
"cannot destroy newly created controller %q details: %v",
387
c.controllerName, err,
365
392
environ, err := environsPrepare(
366
modelcmd.BootstrapContext(ctx), store, controllerStore, c.controllerName,
367
environs.PrepareForBootstrapParams{
369
Credentials: *credential,
393
modelcmd.BootstrapContext(ctx), store,
394
environs.PrepareParams{
395
BaseConfig: configAttrs,
396
ControllerName: c.controllerName,
370
398
CloudRegion: region.Name,
371
399
CloudEndpoint: region.Endpoint,
372
400
CloudStorageEndpoint: region.StorageEndpoint,
401
Credential: *credential,
402
CredentialName: credentialName,
376
406
return errors.Trace(err)
409
// Set the current model to the initial hosted model.
410
accountName, err := store.CurrentAccount(c.controllerName)
412
return errors.Trace(err)
414
if err := store.UpdateModel(c.controllerName, accountName, c.hostedModelName, jujuclient.ModelDetails{
415
hostedModelUUID.String(),
417
return errors.Trace(err)
419
if err := store.SetCurrentModel(c.controllerName, accountName, c.hostedModelName); err != nil {
420
return errors.Trace(err)
379
423
// Set the current controller so "juju status" can be run while
380
424
// bootstrapping is underway.
381
425
if err := modelcmd.WriteCurrentController(c.controllerName); err != nil {
442
486
logger.Infof("combined bootstrap constraints: %v", bootstrapConstraints)
488
hostedModelConfig := map[string]interface{}{
489
"name": c.hostedModelName,
490
config.UUIDKey: hostedModelUUID.String(),
493
// We copy across any user supplied attributes to the hosted model config.
494
// But only if the attributes have not been removed from the controller
495
// model config as part of preparing the controller model.
496
controllerConfigAttrs := environ.Config().AllAttrs()
497
for k, v := range userConfigAttrs {
498
if _, ok := controllerConfigAttrs[k]; ok {
499
hostedModelConfig[k] = v
502
// Ensure that certain config attributes are not included in the hosted
503
// model config. These attributes may be modified during bootstrap; by
504
// removing them from this map, we ensure the modified values are
506
delete(hostedModelConfig, config.AuthKeysConfig)
507
delete(hostedModelConfig, config.AgentVersionKey)
444
509
err = bootstrapFuncs.Bootstrap(modelcmd.BootstrapContext(ctx), environ, bootstrap.BootstrapParams{
445
EnvironConstraints: c.Constraints,
510
ModelConstraints: c.Constraints,
446
511
BootstrapConstraints: bootstrapConstraints,
447
512
BootstrapSeries: c.BootstrapSeries,
448
513
BootstrapImage: c.BootstrapImage,
450
515
UploadTools: c.UploadTools,
451
516
AgentVersion: c.AgentVersion,
452
517
MetadataDir: metadataDir,
518
HostedModelConfig: hostedModelConfig,
455
521
return errors.Annotate(err, "failed to bootstrap model")
458
if err := c.SetModelName(cfg.Name()); err != nil {
524
if err := c.SetModelName(c.hostedModelName); err != nil {
459
525
return errors.Trace(err)
462
err = c.setBootstrapEndpointAddress(store, environ)
528
err = c.setBootstrapEndpointAddress(environ)
464
530
return errors.Annotate(err, "saving bootstrap endpoint address")
470
536
return c.waitForAgentInitialisation(ctx)
473
// credentialByName returns the credential and default region to use for the
474
// specified cloud, optionally specifying a credential name. If no credential
475
// name is specified, then use the default credential for the cloud if one has
476
// been specified. The credential name is returned also, in case the default
477
// credential is used. If there is only one credential, it is implicitly the
480
// If there exists no matching credentials, an error satisfying
481
// errors.IsNotFound will be returned.
482
func credentialByName(
483
store jujuclient.CredentialGetter, cloudName, credentialName string,
484
) (_ *jujucloud.Credential, credentialNameUsed string, defaultRegion string, _ error) {
486
cloudCredentials, err := store.CredentialForCloud(cloudName)
488
return nil, "", "", errors.Annotate(err, "loading credentials")
490
if credentialName == "" {
491
// No credential specified, so use the default for the cloud.
492
credentialName = cloudCredentials.DefaultCredential
493
if credentialName == "" && len(cloudCredentials.AuthCredentials) == 1 {
494
for credentialName = range cloudCredentials.AuthCredentials {
498
credential, ok := cloudCredentials.AuthCredentials[credentialName]
500
return nil, "", "", errors.NotFoundf(
501
"%q credential for cloud %q", credentialName, cloudName,
504
return &credential, credentialName, cloudCredentials.DefaultRegion, nil
507
func (c *bootstrapCommand) getCredentials(
510
cloud *jujucloud.Cloud,
511
) (_ *jujucloud.Credential, region string, _ error) {
513
credential, credentialName, defaultRegion, err := credentialByName(
514
c.CredentialStore, cloudName, c.CredentialName,
517
return nil, "", errors.Trace(err)
520
regionName := c.Region
521
if regionName == "" {
522
regionName = defaultRegion
525
readFile := func(f string) ([]byte, error) {
526
f, err := utils.NormalizePath(f)
528
return nil, errors.Trace(err)
530
return ioutil.ReadFile(ctx.AbsPath(f))
533
// Finalize credential against schemas supported by the provider.
534
provider, err := environs.Provider(cloud.Type)
536
return nil, "", errors.Trace(err)
538
credential, err = jujucloud.FinalizeCredential(
539
*credential, provider.CredentialSchemas(), readFile,
542
return nil, "", errors.Annotatef(
543
err, "validating %q credential for cloud %q",
544
credentialName, cloudName,
547
return credential, regionName, nil
550
539
// getRegion returns the cloud.Region to use, based on the specified
551
540
// region name, and the region name selected if none was specified.
610
599
// waitForAgentInitialisation polls the bootstrapped controller with a read-only
611
600
// command which will fail until the controller is fully initialised.
612
601
// TODO(wallyworld) - add a bespoke command to maybe the admin facade for this purpose.
613
func (c *bootstrapCommand) waitForAgentInitialisation(ctx *cmd.Context) (err error) {
602
func (c *bootstrapCommand) waitForAgentInitialisation(ctx *cmd.Context) error {
614
603
attempts := utils.AttemptStrategy{
615
604
Min: bootstrapReadyPollCount,
616
605
Delay: bootstrapReadyPollDelay,
618
var client block.BlockListAPI
619
for attempt := attempts.Start(); attempt.Next(); {
609
client block.BlockListAPI
613
for attempt := attempts.Start(); attempt.Next(); retries++ {
620
614
client, err = blockAPI(&c.ModelCommandBase)
622
616
// Logins are prevented whilst space discovery is ongoing.
623
errorMessage := err.Error()
617
errorMessage := errors.Cause(err).Error()
624
618
if strings.Contains(errorMessage, "space discovery still in progress") {
629
623
_, err = client.List()
632
626
ctx.Infof("Bootstrap complete, %s now available.", c.controllerName)
635
629
// As the API server is coming up, it goes through a number of steps.
636
630
// Initially the upgrade steps run, but the api server allows some
640
634
// lead to EOF or "connection is shut down" error messages. We skip
641
635
// these too, hoping that things come back up before the end of the
642
636
// retry poll count.
643
errorMessage := err.Error()
644
if strings.Contains(errorMessage, apiserver.UpgradeInProgressError.Error()) ||
645
strings.HasSuffix(errorMessage, "EOF") ||
646
strings.HasSuffix(errorMessage, "connection is shut down") {
638
case errors.Cause(err) == io.EOF,
639
strings.HasSuffix(errors.Cause(err).Error(), "connection is shut down"):
647
640
ctx.Infof("Waiting for API to become available")
642
case params.ErrCode(err) == params.CodeUpgradeInProgress:
643
ctx.Infof("Waiting for API to become available: %v", err)
648
return errors.Annotatef(err, "unable to contact api server after %d attempts", retries)
655
651
// checkProviderType ensures the provider type is okay.