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 }