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