Skip to content

Commit dbbc06f

Browse files
ENT-14118: Added cf-remotes spawn & destroy functionality to cfengine-cli
Ticket: ENT-14118 Signed-off-by: Simon Halvorsen <simon.halvorsen@northern.tech>
1 parent 26b8a56 commit dbbc06f

2 files changed

Lines changed: 145 additions & 8 deletions

File tree

src/cfengine_cli/cfengine_wrapper/cfengine_commands.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from cfbs.commands import build_command
22
from cf_remote.commands import deploy as deploy_command
33
from cf_remote.commands import install as install_command
4-
from cf_remote.commands import spawn as spawn_command
54
from cf_remote.commands import destroy as destroy_command
65

76
from cfengine_cli.cfengine_wrapper.cfengine_utils import (
@@ -31,12 +30,10 @@ def install() -> int: # TODO ENT-14117
3130
return install_command(None, None)
3231

3332

34-
def spawn() -> int: # TODO ENT-14118
35-
return spawn_command(None, None, None, None)
36-
37-
38-
def destroy() -> int: # TODO ENT-14118
39-
return destroy_command(None)
33+
def destroy(groupname, del_all=False) -> int:
34+
if del_all:
35+
return destroy_command(None)
36+
return destroy_command(groupname)
4037

4138

4239
def build() -> int: # TODO ENT-14119

src/cfengine_cli/main.py

Lines changed: 141 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
from cfengine_cli.version import cfengine_cli_version_string
1111
from cfengine_cli import commands
1212
from 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
1315
from 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:
234350
def 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

239379
def _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

Comments
 (0)