github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/cmd/juju/deploy.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 "github.com/juju/cmd" 12 "github.com/juju/errors" 13 "github.com/juju/names" 14 "github.com/juju/utils/featureflag" 15 "gopkg.in/juju/charm.v4" 16 "launchpad.net/gnuflag" 17 18 "github.com/juju/juju/api" 19 "github.com/juju/juju/apiserver/params" 20 "github.com/juju/juju/cmd/envcmd" 21 "github.com/juju/juju/cmd/juju/block" 22 "github.com/juju/juju/constraints" 23 "github.com/juju/juju/environs/config" 24 "github.com/juju/juju/juju/osenv" 25 "github.com/juju/juju/storage" 26 ) 27 28 type DeployCommand struct { 29 envcmd.EnvCommandBase 30 UnitCommandBase 31 CharmName string 32 ServiceName string 33 Config cmd.FileVar 34 Constraints constraints.Value 35 Networks string 36 BumpRevision bool // Remove this once the 1.16 support is dropped. 37 RepoPath string // defaults to JUJU_REPOSITORY 38 39 // TODO(axw) move this to UnitCommandBase once we support --storage 40 // on add-unit too. 41 // 42 // Storage is a map of storage constraints, keyed on the storage name 43 // defined in charm storage metadata. 44 Storage map[string]storage.Constraints 45 } 46 47 const deployDoc = ` 48 <charm name> can be a charm URL, or an unambiguously condensed form of it; 49 assuming a current series of "precise", the following forms will be accepted: 50 51 For cs:precise/mysql 52 mysql 53 precise/mysql 54 55 For cs:~user/precise/mysql 56 cs:~user/mysql 57 58 The current series is determined first by the default-series environment 59 setting, followed by the preferred series for the charm in the charm store. 60 61 In these cases, a versioned charm URL will be expanded as expected (for example, 62 mysql-33 becomes cs:precise/mysql-33). 63 64 However, for local charms, when the default-series is not specified in the 65 environment, one must specify the series. For example: 66 local:precise/mysql 67 68 <service name>, if omitted, will be derived from <charm name>. 69 70 Constraints can be specified when using deploy by specifying the --constraints 71 flag. When used with deploy, service-specific constraints are set so that later 72 machines provisioned with add-unit will use the same constraints (unless changed 73 by set-constraints). 74 75 Charms can be deployed to a specific machine using the --to argument. 76 If the destination is an LXC container the default is to use lxc-clone 77 to create the container where possible. For Ubuntu deployments, lxc-clone 78 is supported for the trusty OS series and later. A 'template' container is 79 created with the name 80 juju-<series>-template 81 where <series> is the OS series, for example 'juju-trusty-template'. 82 83 You can override the use of clone by changing the provider configuration: 84 lxc-clone: false 85 86 If you have the main container directory mounted on a btrfs partition, 87 then the clone will be using btrfs snapshots to create the containers. 88 This means that clones use up much less disk space. If you do not have btrfs, 89 lxc will attempt to use aufs (an overlay type filesystem). You can 90 explicitly ask Juju to create full containers and not overlays by specifying 91 the following in the provider configuration: 92 lxc-clone-aufs: false 93 94 Examples: 95 juju deploy mysql --to 23 (deploy to machine 23) 96 juju deploy mysql --to 24/lxc/3 (deploy to lxc container 3 on host machine 24) 97 juju deploy mysql --to lxc:25 (deploy to a new lxc container on host machine 25) 98 99 juju deploy mysql -n 5 --constraints mem=8G 100 (deploy 5 instances of mysql with at least 8 GB of RAM each) 101 102 juju deploy mysql --networks=storage,mynet --constraints networks=^logging,db 103 (deploy mysql on machines with "storage", "mynet" and "db" networks, 104 but not on machines with "logging" network, also configure "storage" and 105 "mynet" networks) 106 107 Like constraints, service-specific network requirements can be 108 specified with the --networks argument, which takes a comma-delimited 109 list of juju-specific network names. Networks can also be specified with 110 constraints, but they only define what machine to pick, not what networks 111 to configure on it. The --networks argument instructs juju to add all the 112 networks specified with it to all new machines deployed to host units of 113 the service. Not supported on all providers. 114 115 See Also: 116 juju help constraints 117 juju help set-constraints 118 juju help get-constraints 119 ` 120 121 func (c *DeployCommand) Info() *cmd.Info { 122 return &cmd.Info{ 123 Name: "deploy", 124 Args: "<charm name> [<service name>]", 125 Purpose: "deploy a new service", 126 Doc: deployDoc, 127 } 128 } 129 130 func (c *DeployCommand) SetFlags(f *gnuflag.FlagSet) { 131 c.UnitCommandBase.SetFlags(f) 132 f.IntVar(&c.NumUnits, "n", 1, "number of service units to deploy for principal charms") 133 f.BoolVar(&c.BumpRevision, "u", false, "increment local charm directory revision (DEPRECATED)") 134 f.BoolVar(&c.BumpRevision, "upgrade", false, "") 135 f.Var(&c.Config, "config", "path to yaml-formatted service config") 136 f.Var(constraints.ConstraintsValue{Target: &c.Constraints}, "constraints", "set service constraints") 137 f.StringVar(&c.Networks, "networks", "", "bind the service to specific networks") 138 f.StringVar(&c.RepoPath, "repository", os.Getenv(osenv.JujuRepositoryEnvKey), "local charm repository") 139 if featureflag.Enabled(storage.FeatureFlag) { 140 // NOTE: if/when the feature flag is removed, bump the client 141 // facade and check that the ServiceDeployWithNetworks facade 142 // version supports storage, and error if it doesn't. 143 f.Var(storageFlag{&c.Storage}, "storage", "charm storage constraints") 144 } 145 } 146 147 func (c *DeployCommand) Init(args []string) error { 148 switch len(args) { 149 case 2: 150 if !names.IsValidService(args[1]) { 151 return fmt.Errorf("invalid service name %q", args[1]) 152 } 153 c.ServiceName = args[1] 154 fallthrough 155 case 1: 156 if _, err := charm.InferURL(args[0], "fake"); err != nil { 157 return fmt.Errorf("invalid charm name %q", args[0]) 158 } 159 c.CharmName = args[0] 160 case 0: 161 return errors.New("no charm specified") 162 default: 163 return cmd.CheckEmpty(args[2:]) 164 } 165 return c.UnitCommandBase.Init(args) 166 } 167 168 func (c *DeployCommand) Run(ctx *cmd.Context) error { 169 client, err := c.NewAPIClient() 170 if err != nil { 171 return err 172 } 173 defer client.Close() 174 175 conf, err := getClientConfig(client) 176 if err != nil { 177 return err 178 } 179 180 if err := c.checkProvider(conf); err != nil { 181 return err 182 } 183 184 curl, err := resolveCharmURL(c.CharmName, client, conf) 185 if err != nil { 186 return err 187 } 188 189 repo, err := charm.InferRepository(curl.Reference(), ctx.AbsPath(c.RepoPath)) 190 if err != nil { 191 return err 192 } 193 194 config.SpecializeCharmRepo(repo, conf) 195 196 curl, err = addCharmViaAPI(client, ctx, curl, repo) 197 if err != nil { 198 return block.ProcessBlockedError(err, block.BlockChange) 199 } 200 201 if c.BumpRevision { 202 ctx.Infof("--upgrade (or -u) is deprecated and ignored; charms are always deployed with a unique revision.") 203 } 204 205 requestedNetworks, err := networkNamesToTags(parseNetworks(c.Networks)) 206 if err != nil { 207 return err 208 } 209 // We need to ensure network names are valid below, but we don't need them here. 210 _, err = networkNamesToTags(c.Constraints.IncludeNetworks()) 211 if err != nil { 212 return err 213 } 214 _, err = networkNamesToTags(c.Constraints.ExcludeNetworks()) 215 if err != nil { 216 return err 217 } 218 haveNetworks := len(requestedNetworks) > 0 || c.Constraints.HaveNetworks() 219 220 charmInfo, err := client.CharmInfo(curl.String()) 221 if err != nil { 222 return err 223 } 224 225 numUnits := c.NumUnits 226 if charmInfo.Meta.Subordinate { 227 if !constraints.IsEmpty(&c.Constraints) { 228 return errors.New("cannot use --constraints with subordinate service") 229 } 230 if numUnits == 1 && c.ToMachineSpec == "" { 231 numUnits = 0 232 } else { 233 return errors.New("cannot use --num-units or --to with subordinate service") 234 } 235 } 236 serviceName := c.ServiceName 237 if serviceName == "" { 238 serviceName = charmInfo.Meta.Name 239 } 240 241 var configYAML []byte 242 if c.Config.Path != "" { 243 configYAML, err = c.Config.Read(ctx) 244 if err != nil { 245 return err 246 } 247 } 248 // TODO(axw) rename ServiceDeployWithNetworks to ServiceDeploy, 249 // and ServiceDeploy to ServiceDeployLegacy or some such. 250 err = client.ServiceDeployWithNetworks( 251 curl.String(), 252 serviceName, 253 numUnits, 254 string(configYAML), 255 c.Constraints, 256 c.ToMachineSpec, 257 requestedNetworks, 258 c.Storage, 259 ) 260 if params.IsCodeNotImplemented(err) { 261 if haveNetworks { 262 return errors.New("cannot use --networks/--constraints networks=...: not supported by the API server") 263 } 264 err = client.ServiceDeploy( 265 curl.String(), 266 serviceName, 267 numUnits, 268 string(configYAML), 269 c.Constraints, 270 c.ToMachineSpec) 271 } 272 return block.ProcessBlockedError(err, block.BlockChange) 273 } 274 275 // addCharmViaAPI calls the appropriate client API calls to add the 276 // given charm URL to state. Also displays the charm URL of the added 277 // charm on stdout. 278 func addCharmViaAPI(client *api.Client, ctx *cmd.Context, curl *charm.URL, repo charm.Repository) (*charm.URL, error) { 279 if curl.Revision < 0 { 280 latest, err := charm.Latest(repo, curl) 281 if err != nil { 282 return nil, err 283 } 284 curl = curl.WithRevision(latest) 285 } 286 switch curl.Schema { 287 case "local": 288 ch, err := repo.Get(curl) 289 if err != nil { 290 return nil, err 291 } 292 stateCurl, err := client.AddLocalCharm(curl, ch) 293 if err != nil { 294 return nil, err 295 } 296 curl = stateCurl 297 case "cs": 298 err := client.AddCharm(curl) 299 if err != nil { 300 return nil, err 301 } 302 default: 303 return nil, fmt.Errorf("unsupported charm URL schema: %q", curl.Schema) 304 } 305 ctx.Infof("Added charm %q to the environment.", curl) 306 return curl, nil 307 } 308 309 // parseNetworks returns a list of network names by parsing the 310 // comma-delimited string value of --networks argument. 311 func parseNetworks(networksValue string) []string { 312 parts := strings.Split(networksValue, ",") 313 var networks []string 314 for _, part := range parts { 315 network := strings.TrimSpace(part) 316 if network != "" { 317 networks = append(networks, network) 318 } 319 } 320 return networks 321 } 322 323 // networkNamesToTags returns the given network names converted to 324 // tags, or an error. 325 func networkNamesToTags(networks []string) ([]string, error) { 326 var tags []string 327 for _, network := range networks { 328 if !names.IsValidNetwork(network) { 329 return nil, fmt.Errorf("%q is not a valid network name", network) 330 } 331 tags = append(tags, names.NewNetworkTag(network).String()) 332 } 333 return tags, nil 334 }