github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/cmd/juju/bootstrap.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package main
     5  
     6  import (
     7  	"fmt"
     8  	"os"
     9  	"strings"
    10  
    11  	"launchpad.net/gnuflag"
    12  
    13  	"launchpad.net/juju-core/charm"
    14  	"launchpad.net/juju-core/cmd"
    15  	"launchpad.net/juju-core/constraints"
    16  	"launchpad.net/juju-core/environs"
    17  	"launchpad.net/juju-core/environs/bootstrap"
    18  	"launchpad.net/juju-core/environs/config"
    19  	"launchpad.net/juju-core/environs/imagemetadata"
    20  	"launchpad.net/juju-core/environs/sync"
    21  	"launchpad.net/juju-core/environs/tools"
    22  	"launchpad.net/juju-core/provider"
    23  	"launchpad.net/juju-core/utils/set"
    24  	"launchpad.net/juju-core/version"
    25  )
    26  
    27  const bootstrapDoc = `
    28  bootstrap starts a new environment of the current type (it will return an error
    29  if the environment has already been bootstrapped).  Bootstrapping an environment
    30  will provision a new machine in the environment and run the juju state server on
    31  that machine.
    32  
    33  If constraints are specified in the bootstrap command, they will apply to the 
    34  machine provisioned for the juju state server.  They will also be set as default
    35  constraints on the environment for all future machines, exactly as if the
    36  constraints were set with juju set-constraints.
    37  
    38  Bootstrap initializes the cloud environment synchronously and displays information
    39  about the current installation steps.  The time for bootstrap to complete varies 
    40  across cloud providers from a few seconds to several minutes.  Once bootstrap has 
    41  completed, you can run other juju commands against your environment. You can change
    42  the default timeout and retry delays used during the bootstrap by changing the
    43  following settings in your environments.yaml (all values represent number of seconds):
    44  
    45      # How long to wait for a connection to the state server.
    46      bootstrap-timeout: 600 # default: 10 minutes
    47      # How long to wait between connection attempts to a state server address.
    48      bootstrap-retry-delay: 5 # default: 5 seconds
    49      # How often to refresh state server addresses from the API server.
    50      bootstrap-addresses-delay: 10 # default: 10 seconds
    51  
    52  Private clouds may need to specify their own custom image metadata, and possibly upload
    53  Juju tools to cloud storage if no outgoing Internet access is available. In this case,
    54  use the --metadata-source paramater to tell bootstrap a local directory from which to
    55  upload tools and/or image metadata.
    56  
    57  See Also:
    58     juju help switch
    59     juju help constraints
    60     juju help set-constraints
    61  `
    62  
    63  // BootstrapCommand is responsible for launching the first machine in a juju
    64  // environment, and setting up everything necessary to continue working.
    65  type BootstrapCommand struct {
    66  	cmd.EnvCommandBase
    67  	Constraints    constraints.Value
    68  	UploadTools    bool
    69  	Series         []string
    70  	MetadataSource string
    71  }
    72  
    73  func (c *BootstrapCommand) Info() *cmd.Info {
    74  	return &cmd.Info{
    75  		Name:    "bootstrap",
    76  		Purpose: "start up an environment from scratch",
    77  		Doc:     bootstrapDoc,
    78  	}
    79  }
    80  
    81  func (c *BootstrapCommand) SetFlags(f *gnuflag.FlagSet) {
    82  	c.EnvCommandBase.SetFlags(f)
    83  	f.Var(constraints.ConstraintsValue{&c.Constraints}, "constraints", "set environment constraints")
    84  	f.BoolVar(&c.UploadTools, "upload-tools", false, "upload local version of tools before bootstrapping")
    85  	f.Var(seriesVar{&c.Series}, "series", "upload tools for supplied comma-separated series list")
    86  	f.StringVar(&c.MetadataSource, "metadata-source", "", "local path to use as tools and/or metadata source")
    87  }
    88  
    89  func (c *BootstrapCommand) Init(args []string) (err error) {
    90  	if len(c.Series) > 0 && !c.UploadTools {
    91  		return fmt.Errorf("--series requires --upload-tools")
    92  	}
    93  	return cmd.CheckEmpty(args)
    94  }
    95  
    96  // Run connects to the environment specified on the command line and bootstraps
    97  // a juju in that environment if none already exists. If there is as yet no environments.yaml file,
    98  // the user is informed how to create one.
    99  func (c *BootstrapCommand) Run(ctx *cmd.Context) (resultErr error) {
   100  	environ, cleanup, err := environFromName(ctx, c.EnvName, &resultErr, "Bootstrap")
   101  	if err != nil {
   102  		return err
   103  	}
   104  	defer cleanup()
   105  	if err := bootstrap.EnsureNotBootstrapped(environ); err != nil {
   106  		return err
   107  	}
   108  	// If --metadata-source is specified, override the default tools metadata source so
   109  	// SyncTools can use it, and also upload any image metadata.
   110  	if c.MetadataSource != "" {
   111  		metadataDir := ctx.AbsPath(c.MetadataSource)
   112  		logger.Infof("Setting default tools and image metadata sources: %s", metadataDir)
   113  		tools.DefaultBaseURL = metadataDir
   114  		if err := imagemetadata.UploadImageMetadata(environ.Storage(), metadataDir); err != nil {
   115  			// Do not error if image metadata directory doesn't exist.
   116  			if !os.IsNotExist(err) {
   117  				return fmt.Errorf("uploading image metadata: %v", err)
   118  			}
   119  		} else {
   120  			logger.Infof("custom image metadata uploaded")
   121  		}
   122  	}
   123  	// TODO (wallyworld): 2013-09-20 bug 1227931
   124  	// We can set a custom tools data source instead of doing an
   125  	// unnecessary upload.
   126  	if environ.Config().Type() == provider.Local {
   127  		c.UploadTools = true
   128  	}
   129  	if c.UploadTools {
   130  		err = c.uploadTools(environ)
   131  		if err != nil {
   132  			return err
   133  		}
   134  	}
   135  	return bootstrap.Bootstrap(ctx, environ, c.Constraints)
   136  }
   137  
   138  func (c *BootstrapCommand) uploadTools(environ environs.Environ) error {
   139  	// Force version.Current, for consistency with subsequent upgrade-juju
   140  	// (see UpgradeJujuCommand).
   141  	forceVersion := uploadVersion(version.Current.Number, nil)
   142  	cfg := environ.Config()
   143  	series := getUploadSeries(cfg, c.Series)
   144  	agenttools, err := sync.Upload(environ.Storage(), &forceVersion, series...)
   145  	if err != nil {
   146  		return err
   147  	}
   148  	cfg, err = cfg.Apply(map[string]interface{}{
   149  		"agent-version": agenttools.Version.Number.String(),
   150  	})
   151  	if err == nil {
   152  		err = environ.SetConfig(cfg)
   153  	}
   154  	if err != nil {
   155  		return fmt.Errorf("failed to update environment configuration: %v", err)
   156  	}
   157  	return nil
   158  }
   159  
   160  type seriesVar struct {
   161  	target *[]string
   162  }
   163  
   164  func (v seriesVar) Set(value string) error {
   165  	names := strings.Split(value, ",")
   166  	for _, name := range names {
   167  		if !charm.IsValidSeries(name) {
   168  			return fmt.Errorf("invalid series name %q", name)
   169  		}
   170  	}
   171  	*v.target = names
   172  	return nil
   173  }
   174  
   175  func (v seriesVar) String() string {
   176  	return strings.Join(*v.target, ",")
   177  }
   178  
   179  // getUploadSeries returns the supplied series with duplicates removed if
   180  // non-empty; otherwise it returns a default list of series we should
   181  // probably upload, based on cfg.
   182  func getUploadSeries(cfg *config.Config, series []string) []string {
   183  	unique := set.NewStrings(series...)
   184  	if unique.IsEmpty() {
   185  		unique.Add(version.Current.Series)
   186  		unique.Add(config.DefaultSeries)
   187  		unique.Add(cfg.DefaultSeries())
   188  	}
   189  	return unique.Values()
   190  }