Skip to content

Commit 8fc1dca

Browse files
Refactor functions and add error handling
- run_cmd: remove verbose, add shell arg; call methods directly w/stdin and shell added - tfa: remove non-existent flag - get_ip: call subprocess popen and run directly for custom parsing of tf state - add_ip / rm_ip: inline edit via `re.sub` method - main: remove annotation, remove prompt in rm_ip() call; added newline re: pep8 before dunder method call
1 parent 2486610 commit 8fc1dca

1 file changed

Lines changed: 53 additions & 40 deletions

File tree

main.py

Lines changed: 53 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@
66
import typer
77
from decouple import config
88
from pathlib import Path
9-
from sh import jq
10-
# from sh import terraform
9+
from subprocess import Popen, PIPE, STDOUT
1110
from typing import List, Optional
1211
from typing_extensions import Annotated
1312

@@ -19,7 +18,8 @@
1918
tenant_id = config('TENANT_ID')
2019

2120
tf = "terraform"
22-
tf_dir = str(Path("terraform").resolve())
21+
tld = "terraform"
22+
tf_dir = str(Path(f"{tld}/linux").resolve())
2323
ch_dir = f"-chdir={tf_dir}"
2424

2525
ans_dir = str(Path("ansible").resolve())
@@ -29,34 +29,30 @@
2929
)
3030

3131

32-
def run_cmd(args, verbose=True):
32+
def run_cmd(args, shell=False):
3333
"""Run command, transfer stdout/stderr back into this process's stdout/stderr"""
3434
print("Running command: %s" % " ".join(args))
35-
if verbose:
36-
p = subprocess.Popen(
37-
args,
38-
stdout=subprocess.PIPE,
39-
stderr=subprocess.STDOUT)
40-
out = []
41-
with p.stdout:
42-
for line in iter(p.stdout.readline, b''):
43-
out += [line.decode('utf-8').rstrip()]
44-
p.wait()
45-
return "\n".join([str(i) for i in out])
46-
else:
47-
p = subprocess.Popen(
48-
args,
49-
stdout=subprocess.DEVNULL,
50-
stderr=subprocess.STDOUT)
51-
p.wait()
52-
return p.returncode
35+
p = Popen(
36+
args,
37+
shell=shell,
38+
stdin=PIPE,
39+
stdout=PIPE,
40+
stderr=STDOUT)
41+
out = []
42+
with p.stdout:
43+
for line in iter(p.stdout.readline, b''):
44+
out += [line.decode('utf-8').rstrip()]
45+
p.wait()
46+
stdout = "\n".join([str(i) for i in out])
47+
print(stdout)
48+
return stdout
5349

5450

5551
@app.command("hello")
5652
def hello_world():
5753
"""Debugging command"""
5854
cmd = ["echo", "hello world"]
59-
return print(f"Command exited with code: {run_cmd(cmd, verbose=False)}")
55+
return run_cmd(cmd)
6056

6157

6258
@app.command()
@@ -79,7 +75,7 @@ def tfa():
7975
if not Path(f"{tf_dir}/tfplan").exists():
8076
print("tfplan does not exist, running plan first")
8177
tfp()
82-
cmd = [tf, ch_dir, "apply", "tfplan", "-auto-approve"]
78+
cmd = [tf, ch_dir, "apply", "tfplan"]
8379
return run_cmd(cmd)
8480

8581

@@ -93,49 +89,67 @@ def tfs():
9389
@app.command()
9490
def get_ip():
9591
"""Get Azure instance IP"""
96-
# tf_state = terraform.show("-json", _cwd=tf_dir)
97-
tf_state = tfs()
98-
az_ip = jq(
99-
"-r",
100-
'.values.root_module.resources[] | select(.address == "azurerm_linux_virtual_machine.my_terraform_vm").values.public_ip_address',
101-
_in=tf_state,
92+
tf_state = Popen(
93+
[tf, ch_dir, "show", "-json"],
94+
stdin=PIPE,
95+
stdout=PIPE,
10296
)
97+
98+
out = []
99+
with tf_state.stdout:
100+
for line in iter(tf_state.stdout.readline, b''):
101+
out += [line.decode('utf-8').rstrip()]
102+
103+
az_ip = subprocess.run(
104+
["jq", "-r", '.values.root_module.resources[] | select(.address == "azurerm_linux_virtual_machine.my_terraform_vm").values.public_ip_address'],
105+
input="\n".join([str(i) for i in out]),
106+
capture_output=True,
107+
text=True,
108+
).stdout.strip()
109+
103110
print(az_ip)
104-
return az_ip.strip()
111+
return az_ip
105112

106113

107-
# TODO: debug args + match statement in main
108114
@app.command()
109-
def add_ip(*args: List[str]):
115+
def add_ip(*args):
110116
"""Add Azure instance IP to Ansible inventory"""
111117
if args:
112118
az_ip = args[0]
113119
else:
114120
az_ip = get_ip()
115121
hosts = Path(f"{ans_dir}/hosts").read_text()
116122
if az_ip not in hosts:
117-
hosts = hosts.replace("[azure]", f"[azure]\n{az_ip}")
123+
hosts = re.sub(r"\[azure\]\n", f"[azure]\n{az_ip}\n", hosts)
118124
Path(f"{ans_dir}/hosts").write_text(hosts)
119125
return print(Path(f"{ans_dir}/hosts").read_text())
120126
else:
121127
print(f"{az_ip} already in hosts file")
122128

123129

124130
@app.command()
125-
def rm_ip(ip_address: str):
131+
def rm_ip(*args):
126132
"""Remove Azure instance IP from Ansible inventory"""
127133
hosts = Path(f"{ans_dir}/hosts").read_text()
134+
if not args:
135+
ip_address = get_ip()
136+
elif args:
137+
ip_address = args[0]
138+
else:
139+
print("No IP address specified")
140+
raise typer.Abort()
141+
128142
if ip_address in hosts:
129-
# delete line with list comprehension
130-
hosts = "\n".join([i for i in hosts.split("\n") if i != ip_address])
143+
hosts = re.sub(rf"{ip_address}\n", "", hosts)
131144
Path(f"{ans_dir}/hosts").write_text(hosts)
132145
return print(Path(f"{ans_dir}/hosts").read_text())
133146
else:
134147
print(f"{ip_address} not in hosts file")
135148

136149

150+
# TODO: match multiple cases (case guard?)
137151
@app.callback(invoke_without_command=True)
138-
def main(command: Annotated[Optional[str], "command to run"] = typer.Argument(None)):
152+
def main(command: Optional[str] = typer.Argument(None)):
139153
match command:
140154
case "tfi":
141155
tfi()
@@ -150,8 +164,7 @@ def main(command: Annotated[Optional[str], "command to run"] = typer.Argument(No
150164
case "add-ip":
151165
add_ip()
152166
case "rm-ip":
153-
ip = typer.prompt("IP address to remove")
154-
rm_ip(ip)
167+
rm_ip()
155168
case "hello":
156169
hello_world()
157170
case _:

0 commit comments

Comments
 (0)