Skip to content

Commit 775b726

Browse files
authored
Merge pull request #183 from SimonThalvorsen/ENT-14119
Added wrappers for `cfbs build`, `cf-remote deploy`, `cf-remote install` and `cf-remote uninstall`
2 parents 938ea19 + 82254ae commit 775b726

4 files changed

Lines changed: 342 additions & 114 deletions

File tree

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
import argparse
2+
3+
4+
def parse_wrapper_args(subp: argparse._SubParsersAction):
5+
subp.add_parser("build", help="Build a policy set from a CFEngine Build project\n\
6+
A wrapper arount the cfbs `build`-function.")
7+
8+
sp = subp.add_parser("deploy", help="Deploy policy-set (masterfiles) to hub\n\
9+
A wrapper around the cf-remote `deploy`-function with some added niceties.")
10+
sp.add_argument("--hub", help="Hub(s) to deploy to", type=str)
11+
sp.add_argument(
12+
"masterfiles",
13+
help="Policy-set location (tarball URL or local path to tarball / directory)",
14+
type=str,
15+
nargs="?",
16+
)
17+
18+
install_parser = subp.add_parser(
19+
"install",
20+
help="Install CFEngine on the given hosts",
21+
description="A wrapper around the cf-remote `install` function",
22+
)
23+
install_parser.add_argument(
24+
"--version",
25+
"-V",
26+
help="Specify version",
27+
type=str,
28+
)
29+
# install_parser._option_string_actions.get("--version").help = "absdfsf"
30+
# TODO: Update cf-remote/cfbs to have more modular arg-parsing, then we can import
31+
# and override any differences? technically illegal since _option_string_actions,
32+
# but will save ~ 200-1000 loc depending on how much we import into cfengine-cli
33+
34+
install_parser.add_argument(
35+
"--edition",
36+
"-E",
37+
choices=["community", "enterprise"],
38+
help="Enterprise or community packages",
39+
type=str,
40+
)
41+
install_parser.add_argument(
42+
"--package", help="Local path to package or URL to download", type=str
43+
)
44+
install_parser.add_argument(
45+
"--hub-package",
46+
help="Local path to package or URL to download for --hub",
47+
type=str,
48+
)
49+
install_parser.add_argument(
50+
"--client-package",
51+
help="Local path to package or URL to download for --clients",
52+
type=str,
53+
)
54+
install_parser.add_argument(
55+
"--bootstrap", "-B", help="cf-agent --bootstrap argument", type=str
56+
)
57+
install_parser.add_argument(
58+
"--clients", "-c", help="Where to install client package", type=str
59+
)
60+
install_parser.add_argument("--hub", help="Where to install hub package", type=str)
61+
install_parser.add_argument(
62+
"--demo",
63+
help="Use defaults to make demos smoother (NOT secure)",
64+
action="store_true",
65+
)
66+
install_parser.add_argument(
67+
"--call-collect",
68+
help="Enable call collect in --demo def.json",
69+
action="store_true",
70+
)
71+
install_parser.add_argument(
72+
"--remote-download",
73+
help="Package will be downloaded directly to the target machine",
74+
action="store_true",
75+
)
76+
install_parser.add_argument(
77+
"--trust-keys",
78+
help="Comma-separated list of paths to keys hosts should trust"
79+
+ " (implies '--trust-server no' when boostraping)",
80+
type=str,
81+
)
82+
install_parser.add_argument(
83+
"--insecure",
84+
help="Ignore mismatching checksums when downloading urls",
85+
action="store_true",
86+
)
87+
88+
uninstall_parser = subp.add_parser(
89+
"uninstall",
90+
help="Uninstall CFEngine on the given hosts",
91+
description="A wrapper around the cf-remote `uninstall` function",
92+
)
93+
uninstall_parser.add_argument(
94+
"--purge", help="Complete uninstallation", action="store_true"
95+
)
96+
uninstall_parser.add_argument(
97+
"--clients", "-c", help="Where to uninstall", type=str
98+
)
99+
uninstall_parser.add_argument("--hub", help="Where to uninstall", type=str)
100+
uninstall_parser.add_argument("--hosts", "-H", help="Where to uninstall", type=str)
101+
102+
report_parser = subp.add_parser(
103+
"report",
104+
help="Run the agent and hub commands necessary to get new reporting data",
105+
)
106+
report_parser.add_argument(
107+
"--host",
108+
type=str,
109+
default=None,
110+
help="Select which installation to use by name/IP (e.g. 'local' or '192.168.56.90'). "
111+
"If omitted and multiple installations of cf-agent+cf-hub are found, you'll be prompted.",
112+
)
113+
114+
run_parser = subp.add_parser(
115+
"run",
116+
description="Run the CFEngine agent, fetching, evaluating, and enforcing policy.\n\
117+
A wrapper around the cf-remote `run`-function with some added niceties",
118+
epilog="""Examples:
119+
`cfengine run` defaults to use `cf-agent -KIf update.cf && cf-agent -KI`
120+
121+
Run can also be used directly on a specific file, e.g.
122+
'cfengine run /tmp/some_policy.cf' or 'cfengine run "-KIf /tmp/some_policy.cf"'
123+
If no flags are present in the command, then -KIf will be automatically prepended.
124+
125+
Multiple commands can also be run in sequence, such as:
126+
'cfengine run /tmp/some_policy.cf /tmp/some_other_policy.cf /tmp/and_another.cf'
127+
Where all three files will be run in sequence, exiting on first fail
128+
""",
129+
formatter_class=argparse.RawDescriptionHelpFormatter,
130+
)
131+
run_parser.add_argument(
132+
"run_args",
133+
nargs="*",
134+
help="Command(s) to run with cf-agent",
135+
)
136+
run_parser.add_argument(
137+
"--host",
138+
type=str,
139+
default=None,
140+
help="Select which installation of cf-agent to use by name/IP (e.g. 'local' or '192.168.56.90'). "
141+
"If omitted and multiple installations are found, you'll be prompted.",
142+
)
143+
144+
sp = subp.add_parser(
145+
"spawn",
146+
help="Spawn hosts in the clouds",
147+
description="A wrapper around the cf-remote `spawn`-function",
148+
)
149+
sp.add_argument(
150+
"--list-platforms", help="List supported platforms", action="store_true"
151+
)
152+
sp.add_argument(
153+
"--list-boxes", help="List installed vagrant boxes", action="store_true"
154+
)
155+
sp.add_argument(
156+
"--init-config",
157+
help="Initialize configuration file for spawn functionality",
158+
action="store_true",
159+
)
160+
sp.add_argument("--platform", help="Platform or vagrant box to use", type=str)
161+
sp.add_argument("--count", default=1, help="How many hosts to spawn", type=int)
162+
sp.add_argument(
163+
"--role", help="Role of the hosts", choices=["hub", "hubs", "client", "clients"]
164+
)
165+
sp.add_argument(
166+
"--name", help="Name of the group of hosts (can be used in other commands)"
167+
)
168+
sp.add_argument(
169+
"--append",
170+
help="Append the new VMs to a pre-existing group",
171+
action="store_true",
172+
)
173+
sp.add_argument(
174+
"--provider",
175+
help="VM provider",
176+
type=str,
177+
default="aws",
178+
choices=["aws", "gcp", "vagrant"],
179+
)
180+
sp.add_argument("--cpus", help="Number of CPUs of the vagrant instances", type=int)
181+
sp.add_argument(
182+
"--sync-folder",
183+
help="Root folder of synchronized folders of vagrant instance",
184+
type=str,
185+
)
186+
sp.add_argument(
187+
"--provision",
188+
help="full path to provision shell script for Vagrant VM",
189+
type=str,
190+
)
191+
sp.add_argument("--size", help="Size/type of the instances", type=str)
192+
sp.add_argument(
193+
"--network", help="network/subnet to assign the VMs to (GCP only)", type=str
194+
)
195+
sp.add_argument(
196+
"--no-public-ip",
197+
help="No public IP needed (GCP only; WARNING: The VMs will only be accessible"
198+
+ " from some other VM in the same cloud/network!)",
199+
action="store_true",
200+
)
201+
202+
dp = subp.add_parser(
203+
"destroy",
204+
help="Destroy hosts spawned in the clouds",
205+
description="A wrapper around the cf-remote `destroy`-function",
206+
)
207+
dp.add_argument(
208+
"--all", help="Destroy all hosts spawned in the clouds", action="store_true"
209+
)
210+
dp.add_argument("name", help="Name of the group of hosts to destroy", nargs="?")
211+
212+
profile_parser = subp.add_parser(
213+
"profile", help="Parse CFEngine profiling output (cf-agent -Kp)"
214+
)
215+
profile_parser.add_argument(
216+
"profiling_input", help="Path to the profiling input file"
217+
)
218+
profile_parser.add_argument("--top", type=int, default=10)
219+
profile_parser.add_argument("--bundles", action="store_true")
220+
profile_parser.add_argument("--promises", action="store_true")
221+
profile_parser.add_argument("--functions", action="store_true")
222+
profile_parser.add_argument(
223+
"--flamegraph", type=str, help="Generate input file for ./flamegraph.pl"
224+
)

src/cfengine_cli/cfengine_wrapper/cfengine_commands.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
from cfbs.commands import build_command
55
from cf_remote.commands import deploy as deploy_command
6-
from cf_remote.commands import install as install_command
76
from cf_remote.commands import destroy as destroy_command
87
from cf_remote.remote import run_command, transfer_file
98

@@ -15,6 +14,7 @@
1514
from cfengine_cli.cfengine_wrapper.cfengine_utils import (
1615
extract_agent_file,
1716
prompt_two_options,
17+
prompt_yes_no,
1818
require_executable,
1919
require_installation,
2020
)
@@ -149,19 +149,23 @@ def run(*args, target: str | None = None) -> int:
149149
return agent.run(*resolved)
150150

151151

152-
def install() -> int: # TODO ENT-14117
153-
return install_command(None, None)
154-
155-
156152
def destroy(groupname, del_all=False) -> int:
157153
if del_all:
158154
return destroy_command(None)
159155
return destroy_command(groupname)
160156

161157

162-
def build() -> int: # TODO ENT-14119
163-
return build_command()
158+
def build() -> int:
159+
rc = build_command()
160+
if rc != 0:
161+
return rc
162+
if prompt_yes_no("Deploy the built policy set now?", default=True):
163+
return deploy(None, None)
164+
return 0
164165

165166

166-
def deploy() -> int: # TODO ENT-14119
167-
return deploy_command(None, None)
167+
def deploy(target: str | list[str] | None, masterfiles: str | None = None) -> int:
168+
if isinstance(target, str):
169+
target = [target]
170+
hubs = [require_executable("cf-agent", h).location for h in (target or [])] or None
171+
return deploy_command(hubs, masterfiles)

src/cfengine_cli/cfengine_wrapper/cfengine_utils.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,16 @@
1212
from cf_remote.utils import read_json
1313

1414

15+
def prompt_yes_no(prompt: str, default: bool = True) -> bool:
16+
if not sys.stdin.isatty():
17+
raise UserError(f"{prompt} -- no terminal to confirm.")
18+
suffix = "[Y/n]" if default else "[y/N]"
19+
answer = input(f"{prompt} {suffix} ").strip().lower()
20+
if not answer:
21+
return default
22+
return answer in ("y", "yes")
23+
24+
1525
def prompt_two_options(header: str, option_a: str, option_b: str) -> str:
1626
print(header)
1727
print(f" 1) {option_a}")
@@ -130,7 +140,9 @@ def _find_all_paired() -> list[Installation]:
130140
if not data:
131141
continue
132142
agent_path = data.get("agent")
133-
hub_path = data.get("hub")
143+
# If role is hub, assume hub exists and path resolves correctly
144+
is_hub = data.get("role") == "hub"
145+
hub_path = "cf-hub" if is_hub else None
134146
if agent_path and hub_path:
135147
installations.append(
136148
Installation(

0 commit comments

Comments
 (0)