Skip to content

Commit 0e67c8b

Browse files
committed
fix: run MAAS commands on leader units
1 parent 2de5e50 commit 0e67c8b

File tree

2 files changed

+175
-70
lines changed

2 files changed

+175
-70
lines changed

anvil-python/anvil/commands/maas_region.py

Lines changed: 126 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,17 @@
1616
import logging
1717
from typing import Any, List
1818

19+
from rich.status import Status
1920
from sunbeam.clusterd.client import Client
2021
from sunbeam.commands.terraform import TerraformException, TerraformInitStep
2122
from sunbeam.jobs import questions
22-
from sunbeam.jobs.common import BaseStep, ResultType
23-
from sunbeam.jobs.juju import JujuHelper
23+
from sunbeam.jobs.common import BaseStep, Result, ResultType
24+
from sunbeam.jobs.juju import (
25+
ActionFailedException,
26+
JujuHelper,
27+
LeaderNotFoundException,
28+
run_sync,
29+
)
2430
from sunbeam.jobs.steps import (
2531
AddMachineUnitsStep,
2632
DeployMachineApplicationStep,
@@ -241,3 +247,121 @@ def maas_region_upgrade_steps(
241247
verb="Refresh",
242248
),
243249
]
250+
251+
252+
class MAASCreateAdminStep(BaseStep):
253+
"""Create MAAS admin user."""
254+
255+
def __init__(
256+
self,
257+
username: str,
258+
password: str,
259+
email: str,
260+
ssh_import: str | None,
261+
jhelper: JujuHelper,
262+
model: str,
263+
):
264+
super().__init__("Create MAAS admin user", "Creating MAAS admin user")
265+
self.username = username
266+
self.password = password
267+
self.email = email
268+
self.ssh_import = ssh_import
269+
self.app = APPLICATION
270+
self.jhelper = jhelper
271+
self.model = model
272+
273+
def run(self, status: Status | None = None) -> Result:
274+
"""Run maas-region create-admin action."""
275+
action_cmd = "create-admin"
276+
try:
277+
unit = run_sync(self.jhelper.get_leader_unit(self.app, self.model))
278+
except LeaderNotFoundException as e:
279+
LOG.debug(f"Unable to get {self.app} leader")
280+
return Result(ResultType.FAILED, str(e))
281+
282+
action_params = {
283+
"username": self.username,
284+
"password": self.password,
285+
"email": self.email,
286+
}
287+
if self.ssh_import:
288+
action_params["ssh-import"] = self.ssh_import
289+
290+
try:
291+
LOG.debug(
292+
f"Running action {action_cmd} with params {action_params}"
293+
)
294+
action_result = run_sync(
295+
self.jhelper.run_action(
296+
unit, self.model, action_cmd, action_params
297+
)
298+
)
299+
except ActionFailedException as e:
300+
LOG.debug(f"Running action {action_cmd} on {unit} failed")
301+
return Result(ResultType.FAILED, e.args[0].get("stderr"))
302+
303+
LOG.debug(f"Result from action {action_cmd}: {action_result}")
304+
if action_result.get("return-code", 0) > 1:
305+
return Result(
306+
ResultType.FAILED,
307+
f"Action {action_cmd} on {unit} returned error: {action_result.get('stderr')}",
308+
)
309+
310+
return Result(ResultType.COMPLETED)
311+
312+
313+
class MAASGetAPIKeyStep(BaseStep):
314+
"""Retrieve an API key for a MAAS user."""
315+
316+
def __init__(
317+
self,
318+
username: str,
319+
jhelper: JujuHelper,
320+
model: str,
321+
):
322+
super().__init__(
323+
"Retrieve the MAAS API key", "Retrieving the MAAS API key"
324+
)
325+
self.username = username
326+
self.app = APPLICATION
327+
self.jhelper = jhelper
328+
self.model = model
329+
330+
def run(self, status: Status | None = None) -> Result:
331+
"""Run maas-region get-api-key action."""
332+
action_cmd = "get-api-key"
333+
try:
334+
unit = run_sync(self.jhelper.get_leader_unit(self.app, self.model))
335+
except LeaderNotFoundException as e:
336+
LOG.debug(f"Unable to get {self.app} leader")
337+
return Result(ResultType.FAILED, str(e))
338+
339+
action_params = {
340+
"username": self.username,
341+
}
342+
343+
try:
344+
LOG.debug(
345+
f"Running action {action_cmd} with params {action_params}"
346+
)
347+
action_result = run_sync(
348+
self.jhelper.run_action(
349+
unit, self.model, action_cmd, action_params
350+
)
351+
)
352+
except ActionFailedException as e:
353+
LOG.debug(f"Running action {action_cmd} on {unit} failed")
354+
return Result(ResultType.FAILED, e.args[0].get("stderr"))
355+
356+
LOG.debug(f"Result from action {action_cmd}: {action_result}")
357+
if action_result.get("return-code", 0) > 1:
358+
return Result(
359+
ResultType.FAILED,
360+
f"Action {action_cmd} on {unit} returned error: {action_result.get('stderr')}",
361+
)
362+
if api_key := action_result.get("api-key"):
363+
return Result(ResultType.COMPLETED, message=api_key)
364+
return Result(
365+
ResultType.FAILED,
366+
f"Action {action_cmd} on {unit} returned unexpected results content",
367+
)

anvil-python/anvil/commands/utils.py

Lines changed: 49 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,8 @@
1313
# See the License for the specific language governing permissions and
1414
# limitations under the License.
1515

16-
import json
1716
import logging
1817
import re
19-
import subprocess
2018

2119
import click
2220
from rich.console import Console
@@ -25,11 +23,14 @@
2523
FORMAT_DEFAULT,
2624
FORMAT_VALUE,
2725
FORMAT_YAML,
26+
ResultType,
2827
run_plan,
2928
run_preflight_checks,
3029
)
30+
from sunbeam.jobs.juju import JujuHelper
3131
import yaml
3232

33+
from anvil.commands.maas_region import MAASCreateAdminStep, MAASGetAPIKeyStep
3334
from anvil.jobs.checks import VerifyBootstrappedCheck
3435
from anvil.provider.local.deployment import LocalDeployment
3536
from anvil.utils import FormatEpilogCommand
@@ -104,44 +105,31 @@ def validate_ssh_import(
104105
help="Import SSH keys from Launchpad (lp:user-id) or GitHub (gh:user-id)",
105106
callback=validate_ssh_import,
106107
)
108+
@click.pass_context
107109
def create_admin(
108-
username: str,
109-
password: str,
110-
email: str,
110+
ctx: click.Context,
111111
ssh_import: str | None,
112+
email: str,
113+
password: str,
114+
username: str,
112115
) -> None:
113116
"""Creates a MAAS admin account."""
114-
cmd = [
115-
"juju",
116-
"run",
117-
"maas-region/0",
118-
"create-admin",
119-
"--format=json",
120-
f"username={username}",
121-
f"password={password}",
122-
f"email={email}",
123-
]
124-
if ssh_import:
125-
cmd.append(f"ssh-import={ssh_import}")
126-
try:
127-
result = subprocess.run(cmd, check=True, capture_output=True)
128-
except subprocess.CalledProcessError as e:
129-
raise RuntimeError(
130-
f"Failed to create MAAS admin account: {e.stderr.decode()}"
131-
)
132-
try:
133-
result_dict = json.loads(result.stdout.decode())
134-
if result_dict["maas-region/0"]["status"] == "failed":
135-
raise RuntimeError(
136-
"Failed to create admin account, response from maas-region: "
137-
f"{result_dict['maas-region/0']['message']}: "
138-
f"{result_dict['maas-region/0']['results']['stderr']}"
117+
deployment: LocalDeployment = ctx.obj
118+
deployment.reload_juju_credentials()
119+
jhelper = JujuHelper(deployment.get_connected_controller())
120+
run_plan(
121+
[
122+
MAASCreateAdminStep(
123+
username,
124+
password,
125+
email,
126+
ssh_import,
127+
jhelper,
128+
deployment.infrastructure_model,
139129
)
140-
except (KeyError, json.JSONDecodeError):
141-
LOG.error(
142-
f"Unknown response from maas-region/0 when getting API key: {result.stdout.decode()}"
143-
)
144-
raise RuntimeError("Failed to parse response from maas-region/0")
130+
],
131+
console,
132+
)
145133
console.print("MAAS admin account has been successfully created.")
146134

147135

@@ -164,43 +152,36 @@ def create_admin(
164152
default=FORMAT_DEFAULT,
165153
help="Output format of the API key.",
166154
)
155+
@click.pass_context
167156
def get_api_key(
157+
ctx: click.Context,
168158
username: str,
169159
format: str,
170160
) -> None:
171161
"""Retrieves an API key for MAAS."""
172-
cmd = [
173-
"juju",
174-
"run",
175-
"maas-region/0",
176-
"get-api-key",
177-
f"username={username}",
178-
"--format=json",
179-
]
180-
try:
181-
result = subprocess.run(cmd, check=True, capture_output=True)
182-
except subprocess.CalledProcessError as e:
183-
raise RuntimeError(f"Failed to retrieve API key: {e.stderr.decode()}")
184-
try:
185-
result_dict = json.loads(result.stdout.decode())
186-
if result_dict["maas-region/0"]["status"] == "failed":
187-
raise RuntimeError(
188-
"Failed to get API key, response from maas-region: "
189-
f"{result_dict['maas-region/0']['message']}: "
190-
f"{result_dict['maas-region/0']['results']['stderr']}"
162+
deployment: LocalDeployment = ctx.obj
163+
deployment.reload_juju_credentials()
164+
jhelper = JujuHelper(deployment.get_connected_controller())
165+
166+
def _print_output(api_key: str) -> None:
167+
"""Helper for printing formatted output."""
168+
if format == FORMAT_DEFAULT:
169+
console.print(
170+
f"MAAS API key for user {username}: {api_key}", soft_wrap=True
191171
)
192-
api_key = json.loads(result.stdout.decode())["maas-region/0"][
193-
"results"
194-
]["api-key"]
195-
except (KeyError, json.JSONDecodeError):
196-
LOG.error(
197-
f"Unknown response from maas-region/0 when getting API key: {result.stdout.decode()}"
198-
)
199-
raise RuntimeError("Failed to parse response from maas-region/0")
172+
elif format == FORMAT_YAML:
173+
click.echo(yaml.dump({"api-key": api_key}))
174+
elif format == FORMAT_VALUE:
175+
click.echo(api_key)
200176

201-
if format == FORMAT_DEFAULT:
202-
console.print(f"API key: {api_key}", soft_wrap=True)
203-
elif format == FORMAT_YAML:
204-
click.echo(yaml.dump({"api-key": api_key}))
205-
elif format == FORMAT_VALUE:
206-
click.echo(api_key)
177+
plan_results = run_plan(
178+
[
179+
MAASGetAPIKeyStep(
180+
username, jhelper, deployment.infrastructure_model
181+
)
182+
],
183+
console,
184+
)
185+
get_api_key_step_result = plan_results.get("MAASGetAPIKeyStep")
186+
if get_api_key_step_result.result_type == ResultType.COMPLETED:
187+
_print_output(get_api_key_step_result.message)

0 commit comments

Comments
 (0)