Skip to content

Commit dead85a

Browse files
authored
Merge pull request #62 from DataKitchen/access-info
feat: Adding command to show access instructions and credentials
2 parents 1f4c762 + 7e3baa3 commit dead85a

1 file changed

Lines changed: 88 additions & 69 deletions

File tree

dk-installer.py

Lines changed: 88 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -134,17 +134,6 @@ def generate_password():
134134
return password
135135

136136

137-
def write_credentials_file(folder: pathlib.Path, product, lines):
138-
file_path = folder.joinpath(CREDENTIALS_FILE.format(product))
139-
try:
140-
with open(file_path, "w") as file:
141-
file.writelines([f"{text}\n" for text in lines])
142-
except Exception:
143-
pass
144-
else:
145-
CONSOLE.msg(f"(Credentials also written to {file_path.name} file)")
146-
147-
148137
def delete_file(file_path):
149138
LOG.debug("Deleting [%s]", file_path.name)
150139
file_path.unlink(missing_ok=True)
@@ -245,22 +234,6 @@ def msg(self, text, skip_logging=False):
245234
print(text)
246235
self._last_is_space = False
247236

248-
def __enter__(self):
249-
print(self.MARGIN, end="")
250-
return self
251-
252-
def send(self, text):
253-
print(text, end="")
254-
sys.stdout.flush()
255-
self._partial_msg += text
256-
257-
def __exit__(self, exc_type, exc_val, exc_tb):
258-
print("")
259-
LOG.info("Console message: [%s]", self._partial_msg)
260-
self._partial_msg = ""
261-
self._last_is_space = False
262-
return False
263-
264237
def print_log(self, log_path: pathlib.Path) -> None:
265238
with log_path.open() as log_file:
266239
print("")
@@ -271,6 +244,43 @@ def print_log(self, log_path: pathlib.Path) -> None:
271244
print("")
272245
self._last_is_space = True
273246

247+
@contextlib.contextmanager
248+
def partial(self):
249+
print(self.MARGIN, end="")
250+
251+
def console_partial(text):
252+
print(text, end="")
253+
sys.stdout.flush()
254+
self._partial_msg += text
255+
256+
try:
257+
yield console_partial
258+
finally:
259+
print("")
260+
LOG.info("Console message: [%s]", self._partial_msg)
261+
self._partial_msg = ""
262+
self._last_is_space = False
263+
264+
@contextlib.contextmanager
265+
def tee(self, file_path, append=False):
266+
tee_lines = ["" if append else None]
267+
268+
def console_tee(text, skip_logging=False):
269+
tee_lines.append(text)
270+
return self.msg(text, skip_logging=skip_logging)
271+
272+
self.space()
273+
274+
try:
275+
yield console_tee
276+
finally:
277+
self.space()
278+
try:
279+
with open(file_path, "a" if append else "w") as file:
280+
file.writelines([f"{text}\n" for text in tee_lines if text is not None])
281+
except Exception:
282+
LOG.exception("Error tee'ing content to %s", file_path)
283+
274284

275285
CONSOLE = Console()
276286

@@ -779,25 +789,25 @@ def execute(self, args):
779789
action_fail_step = None
780790
for step in action_steps:
781791
executed_steps.append(step)
782-
with CONSOLE:
783-
CONSOLE.send(f"{step.label}... ")
792+
with CONSOLE.partial() as partial:
793+
partial(f"{step.label}... ")
784794
try:
785795
if action_fail_exception:
786796
raise SkipStep
787797
LOG.debug("Executing step [%s]", step)
788798
step.execute(self, args)
789799
except SkipStep:
790-
CONSOLE.send("SKIPPED")
800+
partial("SKIPPED")
791801
continue
792802
except Exception as e:
793-
CONSOLE.send("FAILED")
803+
partial("FAILED")
794804
if step.required:
795805
action_fail_exception = e
796806
action_fail_step = step
797807
else:
798808
LOG.warning("Non-required step [%s] failed with: %s", step, e)
799809
else:
800-
CONSOLE.send("OK")
810+
partial("OK")
801811

802812
if action_fail_exception:
803813
CONSOLE.title(f"{self.label} FAILED")
@@ -1247,11 +1257,12 @@ def on_action_success(self, action, args):
12471257
if args.namespace != NAMESPACE:
12481258
cmd_args.append(f"--namespace={args.namespace}")
12491259

1250-
CONSOLE.space()
1251-
CONSOLE.msg("Because you are using the docker driver on a Mac or Windows, you have to run")
1252-
CONSOLE.msg("the following command in order to be able to access the platform.")
1253-
CONSOLE.space()
1254-
CONSOLE.msg(f"python3 {INSTALLER_NAME} {args.prod} expose {' '.join(cmd_args)}")
1260+
cred_file_path = action.data_folder.joinpath(CREDENTIALS_FILE.format(args.prod))
1261+
with CONSOLE.tee(cred_file_path, append=True) as console_tee:
1262+
console_tee("Because you are using the docker driver on a Mac or Windows, you have to run")
1263+
console_tee("the following command in order to be able to access the platform.")
1264+
console_tee("")
1265+
console_tee(f"python3 {INSTALLER_NAME} {args.prod} expose {' '.join(cmd_args)}")
12551266

12561267
self._collect_images_sha(action, args)
12571268

@@ -1309,25 +1320,17 @@ def execute(self, action, args):
13091320
)
13101321

13111322
def on_action_success(self, action, args):
1312-
info_lines = []
1313-
if url := action.ctx.get("base_url"):
1314-
for service, label in SERVICES_LABELS.items():
1315-
info_lines.append(f"{label:>20}: {SERVICES_URLS[service].format(url)}")
1316-
info_lines.append("")
1317-
1318-
info_lines.extend(
1319-
[
1320-
f"Username: {self._user_data['username']}",
1321-
f"Password: {self._user_data['password']}",
1322-
"",
1323-
]
1324-
)
1323+
cred_file_path = action.data_folder.joinpath(CREDENTIALS_FILE.format(args.prod))
1324+
with CONSOLE.tee(cred_file_path) as console_tee:
1325+
if url := action.ctx.get("base_url"):
1326+
for service, label in SERVICES_LABELS.items():
1327+
console_tee(f"{label:>20}: {SERVICES_URLS[service].format(url)}")
1328+
console_tee("")
13251329

1326-
CONSOLE.space()
1327-
for line in info_lines:
1328-
CONSOLE.msg(line, skip_logging="Password" in line) if line else CONSOLE.space()
1330+
console_tee(f"Username: {self._user_data['username']}")
1331+
console_tee(f"Password: {self._user_data['password']}", skip_logging=True)
13291332

1330-
write_credentials_file(action.data_folder, args.prod, info_lines)
1333+
CONSOLE.msg(f"(Credentials also written to {cred_file_path.name} file)")
13311334

13321335

13331336
class ObsGenerateDemoConfigStep(Step):
@@ -1690,7 +1693,7 @@ def execute(self, action, args):
16901693

16911694
contents = action.docker_compose_file_path.read_text()
16921695
if self.update_version:
1693-
contents = re.sub(r"(image:\s*datakitchen.+:).+\n", fr"\1{TESTGEN_LATEST_TAG}\n", contents)
1696+
contents = re.sub(r"(image:\s*datakitchen.+:).+\n", rf"\1{TESTGEN_LATEST_TAG}\n", contents)
16941697

16951698
if self.update_analytics:
16961699
if args.send_analytics_data:
@@ -1713,7 +1716,7 @@ def execute(self, action, args):
17131716
if self.update_token:
17141717
match = re.search(r"^([ \t]+)TG_METADATA_DB_HOST:.*$", contents, flags=re.M)
17151718
var = f"\n{match.group(1)}TG_JWT_HASHING_KEY: {str(base64.b64encode(random.randbytes(32)), 'ascii')}"
1716-
contents = contents[0:match.end()] + match.group(1) + var + contents[match.end():]
1719+
contents = contents[0 : match.end()] + match.group(1) + var + contents[match.end() :]
17171720

17181721
action.docker_compose_file_path.write_text(contents)
17191722

@@ -1773,17 +1776,15 @@ def on_action_success(self, action, args):
17731776
CONSOLE.msg(f"Created new {DOCKER_COMPOSE_FILE} file using image {args.image}")
17741777

17751778
protocol = "https" if args.ssl_cert_file and args.ssl_key_file else "http"
1776-
info_lines = [
1777-
f"User Interface: {protocol}://localhost:{args.port}",
1778-
"CLI Access: docker compose exec engine bash",
1779-
"",
1780-
f"Username: {self.username}",
1781-
f"Password: {self.password}",
1782-
]
1783-
CONSOLE.space()
1784-
for line in info_lines:
1785-
CONSOLE.msg(line, skip_logging="Password" in line)
1786-
write_credentials_file(action.data_folder, args.prod, info_lines)
1779+
cred_file_path = action.data_folder.joinpath(CREDENTIALS_FILE.format(args.prod))
1780+
with CONSOLE.tee(cred_file_path) as console_tee:
1781+
console_tee(f"User Interface: {protocol}://localhost:{args.port}")
1782+
console_tee("CLI Access: docker compose exec engine bash")
1783+
console_tee("")
1784+
console_tee(f"Username: {self.username}")
1785+
console_tee(f"Password: {self.password}")
1786+
1787+
CONSOLE.msg(f"(Credentials also written to {cred_file_path.name} file)")
17871788

17881789
def on_action_fail(self, action, args):
17891790
# We keep the file around for inspection when in debug mode
@@ -1834,7 +1835,7 @@ def create_compose_file(self, action, args, username, password, ssl_cert_file, s
18341835
TESTGEN_PASSWORD: {password}
18351836
TG_DECRYPT_SALT: {generate_password()}
18361837
TG_DECRYPT_PASSWORD: {generate_password()}
1837-
TG_JWT_HASHING_KEY: {str(base64.b64encode(random.randbytes(32)), 'ascii')}
1838+
TG_JWT_HASHING_KEY: {str(base64.b64encode(random.randbytes(32)), "ascii")}
18381839
TG_METADATA_DB_HOST: postgres
18391840
TG_TARGET_DB_TRUST_SERVER_CERTIFICATE: yes
18401841
TG_EXPORT_TO_OBSERVABILITY_VERIFY_SSL: no
@@ -2378,6 +2379,19 @@ def execute(self, args):
23782379
raise AbortAction
23792380

23802381

2382+
class AccessInstructionsAction(Action):
2383+
args_cmd = "access-info"
2384+
2385+
def execute(self, args):
2386+
try:
2387+
info = self.data_folder.joinpath(CREDENTIALS_FILE.format(args.prod)).read_text()
2388+
except Exception:
2389+
CONSOLE.msg("No Access Information found. Is the platform installed?")
2390+
else:
2391+
for line in info.splitlines():
2392+
CONSOLE.msg(line)
2393+
2394+
23812395
#
23822396
# Entrypoint
23832397
#
@@ -2401,6 +2415,7 @@ def run_installer(args):
24012415
tg_menu = Menu(run_installer, "TestGen")
24022416
tg_menu.add_option("Install TestGen", ["tg", "install"])
24032417
tg_menu.add_option("Upgrade TestGen", ["tg", "upgrade"])
2418+
tg_menu.add_option("Access Instructions", ["tg", "access-info"])
24042419
tg_menu.add_option("Install TestGen demo data", ["tg", "run-demo"])
24052420
tg_menu.add_option(
24062421
"Install TestGen demo data with Observability export",
@@ -2412,10 +2427,12 @@ def run_installer(args):
24122427
obs_menu = Menu(run_installer, "Observability")
24132428
obs_menu.add_option("Install Observability", ["obs", "install"])
24142429
obs_menu.add_option("Upgrade Observability", ["obs", "upgrade"])
2430+
obs_menu.add_option("Access Instructions", ["obs", "access-info"])
2431+
obs_menu.add_option("Expose web access", ["obs", "expose"])
24152432
obs_menu.add_option("Install Observability demo data", ["obs", "run-demo"])
24162433
obs_menu.add_option("Delete Observability demo data", ["obs", "delete-demo"])
24172434
obs_menu.add_option("Run heartbeat demo", ["obs", "run-heartbeat-demo"])
2418-
obs_menu.add_option("Delete Observability", ["obs", "delete"])
2435+
obs_menu.add_option("Uninstall Observability", ["obs", "delete"])
24192436

24202437
cfg_menu = Menu(add_config, "Configuration")
24212438
cfg_menu.add_option(
@@ -2445,6 +2462,7 @@ def get_installer_instance():
24452462
[
24462463
ObsInstallAction(),
24472464
ObsExposeAction(),
2465+
AccessInstructionsAction(),
24482466
ObsDeleteAction(),
24492467
ObsRunDemoAction(),
24502468
ObsDeleteDemoAction(),
@@ -2457,6 +2475,7 @@ def get_installer_instance():
24572475
[
24582476
TestgenInstallAction(),
24592477
TestgenUpgradeAction(),
2478+
AccessInstructionsAction(),
24602479
TestgenDeleteAction(),
24612480
TestgenRunDemoAction(),
24622481
TestgenDeleteDemoAction(),

0 commit comments

Comments
 (0)