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