1010from cfengine_cli .version import cfengine_cli_version_string
1111from cfengine_cli import commands
1212from cfengine_cli .utils import UserError
13+ from cf_remote .commands import spawn , list_boxes , list_platforms , init_cloud_config
14+ from cf_remote .spawn import CFRUserError , Providers
1315from cfbs .utils import CFBSProgrammerError
1416
1517
@@ -93,6 +95,66 @@ def _get_arg_parser():
9395 "If omitted and multiple installations are found, you'll be prompted." ,
9496 )
9597
98+ sp = subp .add_parser ("spawn" , help = "Spawn hosts in the clouds" )
99+ sp .add_argument (
100+ "--list-platforms" , help = "List supported platforms" , action = "store_true"
101+ )
102+ sp .add_argument (
103+ "--list-boxes" , help = "List installed vagrant boxes" , action = "store_true"
104+ )
105+ sp .add_argument (
106+ "--init-config" ,
107+ help = "Initialize configuration file for spawn functionality" ,
108+ action = "store_true" ,
109+ )
110+ sp .add_argument ("--platform" , help = "Platform or vagrant box to use" , type = str )
111+ sp .add_argument ("--count" , default = 1 , help = "How many hosts to spawn" , type = int )
112+ sp .add_argument (
113+ "--role" , help = "Role of the hosts" , choices = ["hub" , "hubs" , "client" , "clients" ]
114+ )
115+ sp .add_argument (
116+ "--name" , help = "Name of the group of hosts (can be used in other commands)"
117+ )
118+ sp .add_argument (
119+ "--append" ,
120+ help = "Append the new VMs to a pre-existing group" ,
121+ action = "store_true" ,
122+ )
123+ sp .add_argument (
124+ "--provider" ,
125+ help = "VM provider" ,
126+ type = str ,
127+ default = "aws" ,
128+ choices = ["aws" , "gcp" , "vagrant" ],
129+ )
130+ sp .add_argument ("--cpus" , help = "Number of CPUs of the vagrant instances" , type = int )
131+ sp .add_argument (
132+ "--sync-folder" ,
133+ help = "Root folder of synchronized folders of vagrant instance" ,
134+ type = str ,
135+ )
136+ sp .add_argument (
137+ "--provision" ,
138+ help = "full path to provision shell script for Vagrant VM" ,
139+ type = str ,
140+ )
141+ sp .add_argument ("--size" , help = "Size/type of the instances" , type = str )
142+ sp .add_argument (
143+ "--network" , help = "network/subnet to assign the VMs to (GCP only)" , type = str
144+ )
145+ sp .add_argument (
146+ "--no-public-ip" ,
147+ help = "No public IP needed (GCP only; WARNING: The VMs will only be accessible"
148+ + " from some other VM in the same cloud/network!)" ,
149+ action = "store_true" ,
150+ )
151+
152+ dp = subp .add_parser ("destroy" , help = "Destroy hosts spawned in the clouds" )
153+ dp .add_argument (
154+ "--all" , help = "Destroy all hosts spawned in the clouds" , action = "store_true"
155+ )
156+ dp .add_argument ("name" , help = "Name of the group of hosts to destroy" , nargs = "?" )
157+
96158 profile_parser = subp .add_parser (
97159 "profile" , help = "Parse CFEngine profiling output (cf-agent -Kp)"
98160 )
@@ -222,6 +284,60 @@ def run_command_with_args(args) -> int:
222284 return cfengine_commands .report (target = args .host )
223285 if args .command == "run" :
224286 return cfengine_commands .run (* args .run_args , target = args .host )
287+ if args .command == "spawn" :
288+ if args .list_platforms :
289+ return list_platforms ()
290+ if args .list_boxes :
291+ return list_boxes ()
292+ if args .init_config :
293+ return init_cloud_config ()
294+ if args .name and "," in args .name :
295+ UserError ("Group --name may not contain commas" )
296+ return - 1
297+ if args .role and args .role .endswith ("s" ):
298+ # role should be singular
299+ args .role = args .role [:- 1 ]
300+ if args .provider == "gcp" :
301+ provider = Providers .GCP
302+ elif args .provider == "aws" :
303+ provider = Providers .AWS
304+ if args .network :
305+ raise UserError ("--network not supported for AWS" )
306+ if args .no_public_ip :
307+ raise UserError ("--no-public-ip not supported for AWS" )
308+ else :
309+ assert args .provider == "vagrant"
310+ provider = Providers .VAGRANT
311+
312+ if provider != Providers .VAGRANT :
313+ if args .cpus :
314+ raise UserError (f"--cpus not supported for { args .provider } " )
315+ if args .sync_folder :
316+ raise UserError (f"--sync-folder not supported for { args .provider } " )
317+ if args .provision :
318+ raise UserError (f"--provision not supported for { args .provider } " )
319+
320+ if args .network and (args .network .count ("/" ) != 1 ):
321+ raise UserError (
322+ "Invalid network specified, needs to be in the network/subnet format"
323+ )
324+
325+ return spawn (
326+ args .platform ,
327+ args .count ,
328+ args .role ,
329+ args .name ,
330+ provider = provider ,
331+ size = args .size ,
332+ network = args .network ,
333+ public_ip = not args .no_public_ip ,
334+ extend_group = args .append ,
335+ vagrant_cpus = args .cpus ,
336+ vagrant_sync_folder = args .sync_folder ,
337+ vagrant_provision = args .provision ,
338+ )
339+ if args .command == "destroy" :
340+ return cfengine_commands .destroy (args .name , del_all = args .all )
225341 if args .command == "dev" :
226342 return commands .dev (args .dev_command , args )
227343 if args .command == "profile" :
@@ -234,6 +350,30 @@ def run_command_with_args(args) -> int:
234350def validate_args (args ):
235351 if args .command == "dev" and args .dev_command is None :
236352 raise UserError ("Missing subcommand - cfengine dev <subcommand>" )
353+ if (
354+ args .command == "spawn"
355+ and not args .list_platforms
356+ and not args .init_config
357+ and not args .list_boxes
358+ ):
359+ # The above options don't require any other options/arguments (TODO:
360+ # --provider), but otherwise all have to be given
361+ if not args .platform :
362+ raise UserError ("--platform needs to be specified" )
363+ if not args .count :
364+ raise UserError ("--count needs to be specified" )
365+ if not args .role :
366+ raise UserError ("--role needs to be specified" )
367+ if not args .name :
368+ raise UserError ("--name needs to be specified" )
369+
370+ if args .command == "destroy" :
371+ if not args .all and not args .name :
372+ raise UserError ("Either '--all' or 'NAME' must be specified for destroy" )
373+ if args .all and args .name :
374+ raise UserError (
375+ "Only one of '--all' or 'NAME' may be specified for destruction"
376+ )
237377
238378
239379def _main ():
@@ -252,7 +392,7 @@ def main():
252392 exit_code = _main ()
253393 assert type (exit_code ) is int
254394 sys .exit (exit_code )
255- except UserError as e :
395+ except ( UserError , CFRUserError ) as e :
256396 print (str (e ))
257397 sys .exit (- 1 )
258398 # Exceptions below are not expected, print extra info:
0 commit comments