github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/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 10 "github.com/juju/cmd" 11 "github.com/juju/errors" 12 "github.com/juju/names" 13 "gopkg.in/juju/charm.v6-unstable" 14 "launchpad.net/gnuflag" 15 16 "github.com/juju/juju/api" 17 apiservice "github.com/juju/juju/api/service" 18 "github.com/juju/juju/apiserver/params" 19 "github.com/juju/juju/cmd/envcmd" 20 "github.com/juju/juju/cmd/juju/block" 21 "github.com/juju/juju/cmd/juju/service" 22 "github.com/juju/juju/constraints" 23 "github.com/juju/juju/juju/osenv" 24 "github.com/juju/juju/storage" 25 ) 26 27 type DeployCommand struct { 28 envcmd.EnvCommandBase 29 service.UnitCommandBase 30 CharmName string 31 ServiceName string 32 Config cmd.FileVar 33 Constraints constraints.Value 34 Networks string // TODO(dimitern): Drop this in a follow-up and fix docs. 35 BumpRevision bool // Remove this once the 1.16 support is dropped. 36 RepoPath string // defaults to JUJU_REPOSITORY 37 RegisterURL string 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 See Also: 103 juju help constraints 104 juju help set-constraints 105 juju help get-constraints 106 ` 107 108 func (c *DeployCommand) Info() *cmd.Info { 109 return &cmd.Info{ 110 Name: "deploy", 111 Args: "<charm name> [<service name>]", 112 Purpose: "deploy a new service", 113 Doc: deployDoc, 114 } 115 } 116 117 func (c *DeployCommand) SetFlags(f *gnuflag.FlagSet) { 118 c.UnitCommandBase.SetFlags(f) 119 f.IntVar(&c.NumUnits, "n", 1, "number of service units to deploy for principal charms") 120 f.BoolVar(&c.BumpRevision, "u", false, "increment local charm directory revision (DEPRECATED)") 121 f.BoolVar(&c.BumpRevision, "upgrade", false, "") 122 f.Var(&c.Config, "config", "path to yaml-formatted service config") 123 f.Var(constraints.ConstraintsValue{Target: &c.Constraints}, "constraints", "set service constraints") 124 f.StringVar(&c.Networks, "networks", "", "deprecated and ignored: use space constraints instead.") 125 f.StringVar(&c.RepoPath, "repository", os.Getenv(osenv.JujuRepositoryEnvKey), "local charm repository") 126 f.Var(storageFlag{&c.Storage}, "storage", "charm storage constraints") 127 } 128 129 func (c *DeployCommand) Init(args []string) error { 130 switch len(args) { 131 case 2: 132 if !names.IsValidService(args[1]) { 133 return fmt.Errorf("invalid service name %q", args[1]) 134 } 135 c.ServiceName = args[1] 136 fallthrough 137 case 1: 138 if _, err := charm.InferURL(args[0], "fake"); err != nil { 139 return fmt.Errorf("invalid charm name %q", args[0]) 140 } 141 c.CharmName = args[0] 142 case 0: 143 return errors.New("no charm specified") 144 default: 145 return cmd.CheckEmpty(args[2:]) 146 } 147 return c.UnitCommandBase.Init(args) 148 } 149 150 func (c *DeployCommand) newServiceAPIClient() (*apiservice.Client, error) { 151 root, err := c.NewAPIRoot() 152 if err != nil { 153 return nil, errors.Trace(err) 154 } 155 return apiservice.NewClient(root), nil 156 } 157 158 func (c *DeployCommand) Run(ctx *cmd.Context) error { 159 client, err := c.NewAPIClient() 160 if err != nil { 161 return err 162 } 163 defer client.Close() 164 165 conf, err := service.GetClientConfig(client) 166 if err != nil { 167 return err 168 } 169 170 if err := c.CheckProvider(conf); err != nil { 171 return err 172 } 173 174 csClient, err := newCharmStoreClient() 175 if err != nil { 176 return errors.Trace(err) 177 } 178 defer csClient.jar.Save() 179 curl, repo, err := resolveCharmURL(c.CharmName, csClient.params, ctx.AbsPath(c.RepoPath), conf) 180 if err != nil { 181 return errors.Trace(err) 182 } 183 184 curl, err = addCharmViaAPI(client, ctx, curl, repo, csClient) 185 if err != nil { 186 return block.ProcessBlockedError(err, block.BlockChange) 187 } 188 189 if c.BumpRevision { 190 ctx.Infof("--upgrade (or -u) is deprecated and ignored; charms are always deployed with a unique revision.") 191 } 192 193 charmInfo, err := client.CharmInfo(curl.String()) 194 if err != nil { 195 return err 196 } 197 198 numUnits := c.NumUnits 199 if charmInfo.Meta.Subordinate { 200 if !constraints.IsEmpty(&c.Constraints) { 201 return errors.New("cannot use --constraints with subordinate service") 202 } 203 if numUnits == 1 && c.PlacementSpec == "" { 204 numUnits = 0 205 } else { 206 return errors.New("cannot use --num-units or --to with subordinate service") 207 } 208 } 209 serviceName := c.ServiceName 210 if serviceName == "" { 211 serviceName = charmInfo.Meta.Name 212 } 213 214 var configYAML []byte 215 if c.Config.Path != "" { 216 configYAML, err = c.Config.Read(ctx) 217 if err != nil { 218 return err 219 } 220 } 221 222 // If storage or placement is specified, we attempt to use a new API on the service facade. 223 if len(c.Storage) > 0 || len(c.Placement) > 0 { 224 notSupported := errors.New("cannot deploy charms with storage or placement: not supported by the API server") 225 serviceClient, err := c.newServiceAPIClient() 226 if err != nil { 227 return notSupported 228 } 229 defer serviceClient.Close() 230 for i, p := range c.Placement { 231 if p.Scope == "env-uuid" { 232 p.Scope = serviceClient.EnvironmentUUID() 233 } 234 c.Placement[i] = p 235 } 236 err = serviceClient.ServiceDeploy( 237 curl.String(), 238 serviceName, 239 numUnits, 240 string(configYAML), 241 c.Constraints, 242 c.PlacementSpec, 243 c.Placement, 244 []string{}, 245 c.Storage, 246 ) 247 if params.IsCodeNotImplemented(err) { 248 return notSupported 249 } 250 return block.ProcessBlockedError(err, block.BlockChange) 251 } 252 253 if len(c.Networks) > 0 { 254 ctx.Infof("use of --networks is deprecated and is ignored. Please use spaces to manage placement within networks") 255 } 256 257 err = client.ServiceDeploy( 258 curl.String(), 259 serviceName, 260 numUnits, 261 string(configYAML), 262 c.Constraints, 263 c.PlacementSpec) 264 265 if err != nil { 266 return block.ProcessBlockedError(err, block.BlockChange) 267 } 268 269 state, err := c.NewAPIRoot() 270 if err != nil { 271 return err 272 } 273 err = registerMeteredCharm(c.RegisterURL, state, csClient.jar, curl.String(), serviceName, client.EnvironmentUUID()) 274 if params.IsCodeNotImplemented(err) { 275 // The state server is too old to support metering. Warn 276 // the user, but don't return an error. 277 logger.Warningf("current state server version does not support charm metering") 278 return nil 279 } 280 281 return block.ProcessBlockedError(err, block.BlockChange) 282 } 283 284 type metricCredentialsAPI interface { 285 SetMetricCredentials(string, []byte) error 286 Close() error 287 } 288 289 type metricsCredentialsAPIImpl struct { 290 api *apiservice.Client 291 state api.Connection 292 } 293 294 // SetMetricCredentials sets the credentials on the service. 295 func (s *metricsCredentialsAPIImpl) SetMetricCredentials(serviceName string, data []byte) error { 296 return s.api.SetMetricCredentials(serviceName, data) 297 } 298 299 // Close closes the api connection 300 func (s *metricsCredentialsAPIImpl) Close() error { 301 err := s.api.Close() 302 if err != nil { 303 return errors.Trace(err) 304 } 305 err = s.state.Close() 306 if err != nil { 307 return errors.Trace(err) 308 } 309 return nil 310 } 311 312 var getMetricCredentialsAPI = func(state api.Connection) (metricCredentialsAPI, error) { 313 return &metricsCredentialsAPIImpl{api: apiservice.NewClient(state), state: state}, nil 314 }