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