github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/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 10 "launchpad.net/gnuflag" 11 12 "github.com/juju/juju/charm" 13 "github.com/juju/juju/cmd" 14 "github.com/juju/juju/cmd/envcmd" 15 "github.com/juju/juju/constraints" 16 "github.com/juju/juju/environs" 17 "github.com/juju/juju/environs/bootstrap" 18 "github.com/juju/juju/environs/imagemetadata" 19 "github.com/juju/juju/environs/tools" 20 "github.com/juju/juju/instance" 21 "github.com/juju/juju/provider" 22 ) 23 24 const bootstrapDoc = ` 25 bootstrap starts a new environment of the current type (it will return an error 26 if the environment has already been bootstrapped). Bootstrapping an environment 27 will provision a new machine in the environment and run the juju state server on 28 that machine. 29 30 If constraints are specified in the bootstrap command, they will apply to the 31 machine provisioned for the juju state server. They will also be set as default 32 constraints on the environment for all future machines, exactly as if the 33 constraints were set with juju set-constraints. 34 35 Bootstrap initializes the cloud environment synchronously and displays information 36 about the current installation steps. The time for bootstrap to complete varies 37 across cloud providers from a few seconds to several minutes. Once bootstrap has 38 completed, you can run other juju commands against your environment. You can change 39 the default timeout and retry delays used during the bootstrap by changing the 40 following settings in your environments.yaml (all values represent number of seconds): 41 42 # How long to wait for a connection to the state server. 43 bootstrap-timeout: 600 # default: 10 minutes 44 # How long to wait between connection attempts to a state server address. 45 bootstrap-retry-delay: 5 # default: 5 seconds 46 # How often to refresh state server addresses from the API server. 47 bootstrap-addresses-delay: 10 # default: 10 seconds 48 49 Private clouds may need to specify their own custom image metadata, and possibly upload 50 Juju tools to cloud storage if no outgoing Internet access is available. In this case, 51 use the --metadata-source paramater to tell bootstrap a local directory from which to 52 upload tools and/or image metadata. 53 54 See Also: 55 juju help switch 56 juju help constraints 57 juju help set-constraints 58 ` 59 60 // BootstrapCommand is responsible for launching the first machine in a juju 61 // environment, and setting up everything necessary to continue working. 62 type BootstrapCommand struct { 63 envcmd.EnvCommandBase 64 Constraints constraints.Value 65 UploadTools bool 66 Series []string 67 seriesOld []string 68 MetadataSource string 69 Placement string 70 } 71 72 func (c *BootstrapCommand) Info() *cmd.Info { 73 return &cmd.Info{ 74 Name: "bootstrap", 75 Purpose: "start up an environment from scratch", 76 Doc: bootstrapDoc, 77 } 78 } 79 80 func (c *BootstrapCommand) SetFlags(f *gnuflag.FlagSet) { 81 f.Var(constraints.ConstraintsValue{Target: &c.Constraints}, "constraints", "set environment constraints") 82 f.BoolVar(&c.UploadTools, "upload-tools", false, "upload local version of tools before bootstrapping") 83 f.Var(newSeriesValue(nil, &c.Series), "upload-series", "upload tools for supplied comma-separated series list") 84 f.Var(newSeriesValue(nil, &c.seriesOld), "series", "upload tools for supplied comma-separated series list (DEPRECATED, see --upload-series)") 85 f.StringVar(&c.MetadataSource, "metadata-source", "", "local path to use as tools and/or metadata source") 86 f.StringVar(&c.Placement, "to", "", "a placement directive indicating an instance to bootstrap") 87 } 88 89 func (c *BootstrapCommand) Init(args []string) (err error) { 90 if len(c.Series) > 0 && !c.UploadTools { 91 return fmt.Errorf("--upload-series requires --upload-tools") 92 } 93 if len(c.seriesOld) > 0 && !c.UploadTools { 94 return fmt.Errorf("--series requires --upload-tools") 95 } 96 if len(c.Series) > 0 && len(c.seriesOld) > 0 { 97 return fmt.Errorf("--upload-series and --series can't be used together") 98 } 99 if len(c.seriesOld) > 0 { 100 c.Series = c.seriesOld 101 } 102 103 // Parse the placement directive. Bootstrap currently only 104 // supports provider-specific placement directives. 105 if c.Placement != "" { 106 _, err = instance.ParsePlacement(c.Placement) 107 if err != instance.ErrPlacementScopeMissing { 108 // We only support unscoped placement directives for bootstrap. 109 return fmt.Errorf("unsupported bootstrap placement directive %q", c.Placement) 110 } 111 } 112 return cmd.CheckEmpty(args) 113 } 114 115 type seriesValue struct { 116 *cmd.StringsValue 117 } 118 119 // newSeriesValue is used to create the type passed into the gnuflag.FlagSet Var function. 120 func newSeriesValue(defaultValue []string, target *[]string) *seriesValue { 121 v := seriesValue{(*cmd.StringsValue)(target)} 122 *(v.StringsValue) = defaultValue 123 return &v 124 } 125 126 // Implements gnuflag.Value Set. 127 func (v *seriesValue) Set(s string) error { 128 if err := v.StringsValue.Set(s); err != nil { 129 return err 130 } 131 for _, name := range *(v.StringsValue) { 132 if !charm.IsValidSeries(name) { 133 v.StringsValue = nil 134 return fmt.Errorf("invalid series name %q", name) 135 } 136 } 137 return nil 138 } 139 140 // bootstrap functionality that Run calls to support cleaner testing 141 type BootstrapInterface interface { 142 EnsureNotBootstrapped(env environs.Environ) error 143 UploadTools(environs.BootstrapContext, environs.Environ, *string, bool, ...string) error 144 Bootstrap(ctx environs.BootstrapContext, environ environs.Environ, args environs.BootstrapParams) error 145 } 146 147 type bootstrapFuncs struct{} 148 149 func (b bootstrapFuncs) EnsureNotBootstrapped(env environs.Environ) error { 150 return bootstrap.EnsureNotBootstrapped(env) 151 } 152 153 func (b bootstrapFuncs) UploadTools(ctx environs.BootstrapContext, env environs.Environ, toolsArch *string, forceVersion bool, bootstrapSeries ...string) error { 154 return bootstrap.UploadTools(ctx, env, toolsArch, forceVersion, bootstrapSeries...) 155 } 156 157 func (b bootstrapFuncs) Bootstrap(ctx environs.BootstrapContext, env environs.Environ, args environs.BootstrapParams) error { 158 return bootstrap.Bootstrap(ctx, env, args) 159 } 160 161 var getBootstrapFuncs = func() BootstrapInterface { 162 return &bootstrapFuncs{} 163 } 164 165 // Run connects to the environment specified on the command line and bootstraps 166 // a juju in that environment if none already exists. If there is as yet no environments.yaml file, 167 // the user is informed how to create one. 168 func (c *BootstrapCommand) Run(ctx *cmd.Context) (resultErr error) { 169 bootstrapFuncs := getBootstrapFuncs() 170 171 if len(c.seriesOld) > 0 { 172 fmt.Fprintln(ctx.Stderr, "Use of --series is deprecated. Please use --upload-series instead.") 173 } 174 175 environ, cleanup, err := environFromName(ctx, c.EnvName, &resultErr, "Bootstrap") 176 if err != nil { 177 return err 178 } 179 // We want to validate constraints early. However, if a custom image metadata 180 // source is specified, we can't validate the arch because that depends on what 181 // images metadata is to be uploaded. So we validate here if no custom metadata 182 // source is specified, and defer till later if not. 183 if c.MetadataSource == "" { 184 if err := validateConstraints(c.Constraints, environ); err != nil { 185 return err 186 } 187 } 188 189 defer cleanup() 190 if err := bootstrapFuncs.EnsureNotBootstrapped(environ); err != nil { 191 return err 192 } 193 194 // Block interruption during bootstrap. Providers may also 195 // register for interrupt notification so they can exit early. 196 interrupted := make(chan os.Signal, 1) 197 defer close(interrupted) 198 ctx.InterruptNotify(interrupted) 199 defer ctx.StopInterruptNotify(interrupted) 200 go func() { 201 for _ = range interrupted { 202 ctx.Infof("Interrupt signalled: waiting for bootstrap to exit") 203 } 204 }() 205 206 // If --metadata-source is specified, override the default tools metadata source so 207 // SyncTools can use it, and also upload any image metadata. 208 if c.MetadataSource != "" { 209 metadataDir := ctx.AbsPath(c.MetadataSource) 210 if err := uploadCustomMetadata(metadataDir, environ); err != nil { 211 return err 212 } 213 if err := validateConstraints(c.Constraints, environ); err != nil { 214 return err 215 } 216 } 217 // TODO (wallyworld): 2013-09-20 bug 1227931 218 // We can set a custom tools data source instead of doing an 219 // unnecessary upload. 220 if environ.Config().Type() == provider.Local { 221 c.UploadTools = true 222 } 223 if c.UploadTools { 224 err = bootstrapFuncs.UploadTools(ctx, environ, c.Constraints.Arch, true, c.Series...) 225 if err != nil { 226 return err 227 } 228 } 229 return bootstrapFuncs.Bootstrap(ctx, environ, environs.BootstrapParams{ 230 Constraints: c.Constraints, 231 Placement: c.Placement, 232 }) 233 } 234 235 var uploadCustomMetadata = func(metadataDir string, env environs.Environ) error { 236 logger.Infof("Setting default tools and image metadata sources: %s", metadataDir) 237 tools.DefaultBaseURL = metadataDir 238 if err := imagemetadata.UploadImageMetadata(env.Storage(), metadataDir); err != nil { 239 // Do not error if image metadata directory doesn't exist. 240 if !os.IsNotExist(err) { 241 return fmt.Errorf("uploading image metadata: %v", err) 242 } 243 } else { 244 logger.Infof("custom image metadata uploaded") 245 } 246 return nil 247 } 248 249 var validateConstraints = func(cons constraints.Value, env environs.Environ) error { 250 validator, err := env.ConstraintsValidator() 251 if err != nil { 252 return err 253 } 254 unsupported, err := validator.Validate(cons) 255 if len(unsupported) > 0 { 256 logger.Warningf("unsupported constraints: %v", err) 257 } else if err != nil { 258 return err 259 } 260 return nil 261 }