launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/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 11 "launchpad.net/gnuflag" 12 "launchpad.net/juju-core/charm" 13 "launchpad.net/juju-core/cmd" 14 "launchpad.net/juju-core/constraints" 15 "launchpad.net/juju-core/environs/config" 16 "launchpad.net/juju-core/juju" 17 "launchpad.net/juju-core/juju/osenv" 18 "launchpad.net/juju-core/names" 19 "launchpad.net/juju-core/state/api" 20 "launchpad.net/juju-core/state/api/params" 21 ) 22 23 type DeployCommand struct { 24 cmd.EnvCommandBase 25 UnitCommandBase 26 CharmName string 27 ServiceName string 28 Config cmd.FileVar 29 Constraints constraints.Value 30 BumpRevision bool // Remove this once the 1.16 support is dropped. 31 RepoPath string // defaults to JUJU_REPOSITORY 32 } 33 34 const deployDoc = ` 35 <charm name> can be a charm URL, or an unambiguously condensed form of it; 36 assuming a current default series of "precise", the following forms will be 37 accepted. 38 39 For cs:precise/mysql 40 mysql 41 precise/mysql 42 43 For cs:~user/precise/mysql 44 cs:~user/mysql 45 46 For local:precise/mysql 47 local:mysql 48 49 In all cases, a versioned charm URL will be expanded as expected (for example, 50 mysql-33 becomes cs:precise/mysql-33). 51 52 <service name>, if omitted, will be derived from <charm name>. 53 54 Constraints can be specified when using deploy by specifying the --constraints 55 flag. When used with deploy, service-specific constraints are set so that later 56 machines provisioned with add-unit will use the same constraints (unless changed 57 by set-constraints). 58 59 Charms can be deployed to a specific machine using the --to argument. 60 61 Examples: 62 juju deploy mysql --to 23 (Deploy to machine 23) 63 juju deploy mysql --to 24/lxc/3 (Deploy to lxc container 3 on host machine 24) 64 juju deploy mysql --to lxc:25 (Deploy to a new lxc container on host machine 25) 65 66 juju deploy mysql -n 5 --constraints mem=8G (deploy 5 instances of mysql with at least 8 GB of RAM each) 67 68 See Also: 69 juju help constraints 70 juju help set-constraints 71 juju help get-constraints 72 ` 73 74 func (c *DeployCommand) Info() *cmd.Info { 75 return &cmd.Info{ 76 Name: "deploy", 77 Args: "<charm name> [<service name>]", 78 Purpose: "deploy a new service", 79 Doc: deployDoc, 80 } 81 } 82 83 func (c *DeployCommand) SetFlags(f *gnuflag.FlagSet) { 84 c.EnvCommandBase.SetFlags(f) 85 c.UnitCommandBase.SetFlags(f) 86 f.IntVar(&c.NumUnits, "n", 1, "number of service units to deploy for principal charms") 87 f.BoolVar(&c.BumpRevision, "u", false, "increment local charm directory revision (DEPRECATED)") 88 f.BoolVar(&c.BumpRevision, "upgrade", false, "") 89 f.Var(&c.Config, "config", "path to yaml-formatted service config") 90 f.Var(constraints.ConstraintsValue{&c.Constraints}, "constraints", "set service constraints") 91 f.StringVar(&c.RepoPath, "repository", os.Getenv(osenv.JujuRepositoryEnvKey), "local charm repository") 92 } 93 94 func (c *DeployCommand) Init(args []string) error { 95 switch len(args) { 96 case 2: 97 if !names.IsService(args[1]) { 98 return fmt.Errorf("invalid service name %q", args[1]) 99 } 100 c.ServiceName = args[1] 101 fallthrough 102 case 1: 103 if _, err := charm.InferURL(args[0], "fake"); err != nil { 104 return fmt.Errorf("invalid charm name %q", args[0]) 105 } 106 c.CharmName = args[0] 107 case 0: 108 return errors.New("no charm specified") 109 default: 110 return cmd.CheckEmpty(args[2:]) 111 } 112 return c.UnitCommandBase.Init(args) 113 } 114 115 func (c *DeployCommand) Run(ctx *cmd.Context) error { 116 client, err := juju.NewAPIClientFromName(c.EnvName) 117 if err != nil { 118 return err 119 } 120 defer client.Close() 121 122 attrs, err := client.EnvironmentGet() 123 if params.IsCodeNotImplemented(err) { 124 logger.Infof("EnvironmentGet not supported by the API server, " + 125 "falling back to 1.16 compatibility mode (direct DB access)") 126 return c.run1dot16(ctx) 127 } 128 if err != nil { 129 return err 130 } 131 conf, err := config.New(config.NoDefaults, attrs) 132 if err != nil { 133 return err 134 } 135 curl, err := charm.InferURL(c.CharmName, conf.DefaultSeries()) 136 if err != nil { 137 return err 138 } 139 repo, err := charm.InferRepository(curl, ctx.AbsPath(c.RepoPath)) 140 if err != nil { 141 return err 142 } 143 144 repo = config.AuthorizeCharmRepo(repo, conf) 145 146 curl, err = addCharmViaAPI(client, ctx, curl, repo) 147 if err != nil { 148 return err 149 } 150 151 if c.BumpRevision { 152 ctx.Stdout.Write([]byte("--upgrade (or -u) is deprecated and ignored; charms are always deployed with a unique revision.\n")) 153 } 154 155 charmInfo, err := client.CharmInfo(curl.String()) 156 if err != nil { 157 return err 158 } 159 160 numUnits := c.NumUnits 161 if charmInfo.Meta.Subordinate { 162 if !constraints.IsEmpty(&c.Constraints) { 163 return errors.New("cannot use --constraints with subordinate service") 164 } 165 if numUnits == 1 && c.ToMachineSpec == "" { 166 numUnits = 0 167 } else { 168 return errors.New("cannot use --num-units or --to with subordinate service") 169 } 170 } 171 serviceName := c.ServiceName 172 if serviceName == "" { 173 serviceName = charmInfo.Meta.Name 174 } 175 176 var configYAML []byte 177 if c.Config.Path != "" { 178 configYAML, err = c.Config.Read(ctx) 179 if err != nil { 180 return err 181 } 182 } 183 return client.ServiceDeploy( 184 curl.String(), 185 serviceName, 186 numUnits, 187 string(configYAML), 188 c.Constraints, 189 c.ToMachineSpec, 190 ) 191 } 192 193 // run1dot16 implements the deploy command in 1.16 compatibility mode, 194 // with direct state access. Remove this when support for 1.16 is 195 // dropped. 196 func (c *DeployCommand) run1dot16(ctx *cmd.Context) error { 197 conn, err := juju.NewConnFromName(c.EnvName) 198 if err != nil { 199 return err 200 } 201 defer conn.Close() 202 conf, err := conn.State.EnvironConfig() 203 if err != nil { 204 return err 205 } 206 curl, err := charm.InferURL(c.CharmName, conf.DefaultSeries()) 207 if err != nil { 208 return err 209 } 210 repo, err := charm.InferRepository(curl, ctx.AbsPath(c.RepoPath)) 211 if err != nil { 212 return err 213 } 214 215 repo = config.AuthorizeCharmRepo(repo, conf) 216 217 // TODO(fwereade) it's annoying to roundtrip the bytes through the client 218 // here, but it's the original behaviour and not convenient to change. 219 // PutCharm will always be required in some form for local charms; and we 220 // will need an EnsureStoreCharm method somewhere that gets the state.Charm 221 // for use in the following checks. 222 ch, err := conn.PutCharm(curl, repo, c.BumpRevision) 223 if err != nil { 224 return err 225 } 226 numUnits := c.NumUnits 227 if ch.Meta().Subordinate { 228 if !constraints.IsEmpty(&c.Constraints) { 229 return errors.New("cannot use --constraints with subordinate service") 230 } 231 if numUnits == 1 && c.ToMachineSpec == "" { 232 numUnits = 0 233 } else { 234 return errors.New("cannot use --num-units or --to with subordinate service") 235 } 236 } 237 238 serviceName := c.ServiceName 239 if serviceName == "" { 240 serviceName = ch.Meta().Name 241 } 242 var settings charm.Settings 243 if c.Config.Path != "" { 244 configYAML, err := c.Config.Read(ctx) 245 if err != nil { 246 return err 247 } 248 settings, err = ch.Config().ParseSettingsYAML(configYAML, serviceName) 249 if err != nil { 250 return err 251 } 252 } 253 _, err = juju.DeployService(conn.State, 254 juju.DeployServiceParams{ 255 ServiceName: serviceName, 256 Charm: ch, 257 NumUnits: numUnits, 258 ConfigSettings: settings, 259 Constraints: c.Constraints, 260 ToMachineSpec: c.ToMachineSpec, 261 }) 262 return err 263 } 264 265 // addCharmViaAPI calls the appropriate client API calls to add the 266 // given charm URL to state. Also displays the charm URL of the added 267 // charm on stdout. 268 func addCharmViaAPI(client *api.Client, ctx *cmd.Context, curl *charm.URL, repo charm.Repository) (*charm.URL, error) { 269 if curl.Revision < 0 { 270 latest, err := charm.Latest(repo, curl) 271 if err != nil { 272 return nil, err 273 } 274 curl = curl.WithRevision(latest) 275 } 276 switch curl.Schema { 277 case "local": 278 ch, err := repo.Get(curl) 279 if err != nil { 280 return nil, err 281 } 282 stateCurl, err := client.AddLocalCharm(curl, ch) 283 if err != nil { 284 return nil, err 285 } 286 curl = stateCurl 287 case "cs": 288 err := client.AddCharm(curl) 289 if err != nil { 290 return nil, err 291 } 292 default: 293 return nil, fmt.Errorf("unsupported charm URL schema: %q", curl.Schema) 294 } 295 report := fmt.Sprintf("Added charm %q to the environment.\n", curl) 296 ctx.Stdout.Write([]byte(report)) 297 return curl, nil 298 }