From bd19421744f845a09e916bc54783f3579c619815 Mon Sep 17 00:00:00 2001 From: Jadon Naas Date: Tue, 4 Apr 2023 14:27:09 -0400 Subject: [PATCH 01/15] Working basic implementation of launch command --- requirements.txt | 4 + sunbeam/commands/launch.py | 170 +++++++++++++++++++++++++++++++++++++ sunbeam/main.py | 2 + 3 files changed, 176 insertions(+) create mode 100644 sunbeam/commands/launch.py diff --git a/requirements.txt b/requirements.txt index 4a4153c..a1b50dc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,6 +6,10 @@ rich # MIT # Set upper bound to match Juju 3.1.x series target juju<3.2 # Apache 2 +# Used in the launch command to launch an instance +openstacksdk==0.61.* +petname + # Used for communication with snapd socket requests # Apache 2 requests-unixsocket # Apache 2 diff --git a/sunbeam/commands/launch.py b/sunbeam/commands/launch.py new file mode 100644 index 0000000..a9a49f3 --- /dev/null +++ b/sunbeam/commands/launch.py @@ -0,0 +1,170 @@ +# Copyright (c) 2023 Canonical Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import asyncio +import logging +import os +import subprocess + +import click +import openstack +import petname + +from typing import List + +from rich.console import Console +from snaphelpers import Snap + +from sunbeam.commands import juju + +LOG = logging.getLogger(__name__) +console = Console() +snap = Snap() + + +def check_output(*args: List[str]) -> str: + """Execute a shell command, returning the output of the command. + + :param args: strings to be composed into the bash call. + + Include our env; pass in any extra keyword args. + """ + return subprocess.check_output(args, universal_newlines=True, + env=os.environ).strip() + +def check(*args: List[str]) -> int: + """Execute a shell command, raising an error on failed excution. + + :param args: strings to be composed into the bash call. + + """ + return subprocess.check_call(args, env=os.environ) + +def check_keypair(openstack_conn: openstack.connection.Connection): + """ + Check for the sunbeam keypair's existence, creating it if it doesn't. + + """ + key_path = f"~/.ssh/sunbeam" + LOG.debug(f"check_keypair Key Path is: {key_path}") + if os.path.exists(key_path): + return key_path + console.print('Creating local "sunbeam" ssh key at {}'.format(key_path)) + # TODO: make sure that we get rid of this path on snap + # uninstall. If we don't, check to make sure that Sunbeam + # has a sunbeam ssh key, in addition to checking for the + # existence of the file. + key_dir = os.sep.join(key_path.split(os.sep)[:-1]) + check('mkdir', '-p', key_dir) + check('chmod', '700', key_dir) + + id_ = openstack_conn.compute.create_keypair(name="sunbeam") + + with open(key_path, 'w') as file_: + file_.write(id_.private_key) + check('chmod', '600', key_path) + + return key_path + +@click.command() +@click.option("-k", "--key", default="sunbeam", help="The SSH key to use for the instance") +def launch( + key: str = "sunbeam" +) -> None: + """ + Launch an OpenStack instance + """ + LOG.debug(f"The supplied key name is {key}.") + model = snap.config.get("control-plane.model") + jhelper = juju.JujuHelper() + server_id = "" + keypath = "" + console.print("Launching an OpenStack instance ... ") + with console.status("Getting Keystone admin information ... "): + app = "keystone" + action_cmd = "get-admin-account" + action_result = asyncio.get_event_loop().run_until_complete( + jhelper.run_action(model, app, action_cmd) + ) + + if action_result.get("return-code", 0) > 1: + _message = "Unable to retrieve OpenStack credentials from Keystone service" + raise click.ClickException(_message) + else: + LOG.debug("Successfully retrieved admin info from Keystone") + + os_username = action_result.get("username") + os_password = action_result.get("password") + os_auth_url = action_result.get("public-endpoint") + os_user_domain_name = action_result.get("user-domain-name") + os_project_domain_name = action_result.get("project-domain-name") + os_project_name = action_result.get("project-name") + os_auth_version = action_result.get("api-version") + os_identity_api_version = action_result.get("api_version") + + conn = openstack.connect( + os_auth_url=os_auth_url, + project_name=os_project_name, + username=os_username, + password=os_password, + user_domain_name=os_user_domain_name, + project_domain_name=os_project_domain_name, + ) + + with console.status("Checking for SSH key pair ... "): + if key == "sunbeam": + # Make sure that we have a default ssh key to hand off to the + # instance. + key_path = check_keypair(conn) + else: + # We've been passed an ssh key with an unknown path. Drop in + # some placeholder text for the message at the end of this + # routine, but don't worry about verifying it. We trust the + # caller to have created it! + key_path = '/path/to/ssh/key' + + with console.status("Creating the OpenStack instance ... "): + instance_name = petname.Generate() + image = conn.compute.find_image("ubuntu-jammy") + flavor = conn.compute.find_flavor("m1.tiny") + network = conn.network.find_network("demo-network") + keypair = conn.compute.find_keypair(key) + LOG.debug( + """ +Creating an instance with this configuration: +name = %s, +image = %s, +flavor = %s, +network = %s, +key_name = %s + """ % (instance_name, image.id, flavor.id, network.id, keypair.name) + ) + server = conn.compute.create_server( + name=instance_name, + image_id=image.id, + flavor_id=flavor.id, + networks=[{"uuid": network.id}], + key_name=keypair.name + ) + + server = conn.compute.wait_for_server(server) + server_id = server.id + + with console.status("Allocating IP address to instance ... "): + external_network = conn.network.find_network("external-network") + ip = conn.network.create_ip(floating_network_id=external_network.id) + conn.compute.add_floating_ip_to_server(server_id, ip.floating_ip_address) + + console.print("Access the instance with `ssh -i {key_path} ubuntu@{ip.floating_ip_address}") \ No newline at end of file diff --git a/sunbeam/main.py b/sunbeam/main.py index 933299e..d61e200 100644 --- a/sunbeam/main.py +++ b/sunbeam/main.py @@ -22,6 +22,7 @@ from sunbeam.commands import configure as configure_cmds from sunbeam.commands import inspect as inspect_cmds from sunbeam.commands import install_script as install_script_cmds +from sunbeam.commands import launch as launch_cmds from sunbeam.commands import openrc as openrc_cmds from sunbeam.commands import reset as reset_cmds from sunbeam.commands import status as status_cmds @@ -55,6 +56,7 @@ def main(): cli.add_command(configure_cmds.configure) cli.add_command(inspect_cmds.inspect) cli.add_command(install_script_cmds.install_script) + cli.add_command(launch_cmds.launch) cli() From ada550f808545f13ee2f5b8a202638d48c516f00 Mon Sep 17 00:00:00 2001 From: Jadon Naas Date: Wed, 5 Apr 2023 12:31:35 -0400 Subject: [PATCH 02/15] Add output for clouds.yaml --- sunbeam/commands/configure.py | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/sunbeam/commands/configure.py b/sunbeam/commands/configure.py index f88d7c3..ccae02a 100644 --- a/sunbeam/commands/configure.py +++ b/sunbeam/commands/configure.py @@ -222,11 +222,12 @@ def _retrieve_admin_credentials(jhelper: JujuHelper, model: str) -> dict: class UserOpenRCStep(BaseStep): """Generate openrc for created cloud user.""" - def __init__(self, auth_url: str, auth_version: str, openrc: str): + def __init__(self, auth_url: str, auth_version: str, openrc: str, clouds: str): super().__init__("Generate user openrc", "Generating openrc for cloud usage") self.auth_url = auth_url self.auth_version = auth_version self.openrc = openrc + self.clouds = clouds def is_skip(self, status: Optional["Status"] = None): """Determines if the step should be skipped or not. @@ -253,6 +254,7 @@ def run(self, status: Optional[Status]) -> Result: ) tf_output = json.loads(process.stdout) self._print_openrc(tf_output) + self._print_clouds_yaml(tf_output) return Result(ResultType.COMPLETED) except subprocess.CalledProcessError as e: LOG.exception("Error initializing Terraform") @@ -279,6 +281,28 @@ def _print_openrc(self, tf_output: dict) -> None: else: console.print(_openrc) + def _print_clouds_yaml(self, tf_output: dict) -> None: + """Print a clouds.yaml file and save to disk using provided information""" + _cloudsyaml = f""" +clouds: + sunbeam: + auth: + auth_url: {self.auth_url} + project_name: {tf_output["OS_PROJECT_NAME"]["value"]} + username: {tf_output["OS_USERNAME"]["value"]} + password: {tf_output["OS_PASSWORD"]["value"]} + user_domain_name: {tf_output["OS_USER_DOMAIN_NAME"]["value"]} + project_domain_name: {tf_output["OS_PROJECT_DOMAIN_NAME"]["value"]} + identity_api_version: {self.auth_version} + """ + if self.clouds: + message = f"Writing clouds.yaml to {self.clouds} ... " + console.status(message) + with open(self.clouds, "w") as f_clouds: + os.fchmod(f_clouds.fileno(), mode=0o640) + f_clouds.write(_cloudsyaml) + else: + console.print(_cloudsyaml) class ConfigureCloudStep(BaseStep): """Default cloud configuration for all-in-one install.""" @@ -415,8 +439,9 @@ def run(self, status: Optional[Status]) -> Result: @click.option("-a", "--accept-defaults", help="Accept all defaults.", is_flag=True) @click.option("-p", "--preseed", help="Preseed file.") @click.option("-o", "--openrc", help="Output file for cloud access details.") +@click.option("-c", "--clouds", help="Output file for clouds.yaml") def configure( - openrc: str = None, preseed: str = None, accept_defaults: bool = False + openrc: str = None, preseed: str = None, accept_defaults: bool = False, clouds: str = None ) -> None: """Configure cloud with some sane defaults.""" snap = utils.get_snap() @@ -452,6 +477,7 @@ def configure( auth_url=admin_credentials["OS_AUTH_URL"], auth_version=admin_credentials["OS_AUTH_VERSION"], openrc=openrc, + clouds=clouds ), UpdateExternalNetworkConfigStep(ext_network=ext_network_file), ] From eb129e8e7e408f2ed8ad75aef75340899d96102a Mon Sep 17 00:00:00 2001 From: Jadon Naas Date: Thu, 6 Apr 2023 15:33:51 -0400 Subject: [PATCH 03/15] Use clouds.yaml instead of Juju admin creds --- sunbeam/commands/launch.py | 30 ++---------------------------- 1 file changed, 2 insertions(+), 28 deletions(-) diff --git a/sunbeam/commands/launch.py b/sunbeam/commands/launch.py index a9a49f3..8f4f339 100644 --- a/sunbeam/commands/launch.py +++ b/sunbeam/commands/launch.py @@ -92,35 +92,9 @@ def launch( server_id = "" keypath = "" console.print("Launching an OpenStack instance ... ") - with console.status("Getting Keystone admin information ... "): - app = "keystone" - action_cmd = "get-admin-account" - action_result = asyncio.get_event_loop().run_until_complete( - jhelper.run_action(model, app, action_cmd) - ) - - if action_result.get("return-code", 0) > 1: - _message = "Unable to retrieve OpenStack credentials from Keystone service" - raise click.ClickException(_message) - else: - LOG.debug("Successfully retrieved admin info from Keystone") - - os_username = action_result.get("username") - os_password = action_result.get("password") - os_auth_url = action_result.get("public-endpoint") - os_user_domain_name = action_result.get("user-domain-name") - os_project_domain_name = action_result.get("project-domain-name") - os_project_name = action_result.get("project-name") - os_auth_version = action_result.get("api-version") - os_identity_api_version = action_result.get("api_version") - + conn = openstack.connect( - os_auth_url=os_auth_url, - project_name=os_project_name, - username=os_username, - password=os_password, - user_domain_name=os_user_domain_name, - project_domain_name=os_project_domain_name, + cloud="sunbeam" ) with console.status("Checking for SSH key pair ... "): From 99e5af025fb36e7061d4dd00cd3cb67d2df38403 Mon Sep 17 00:00:00 2001 From: Jadon Naas Date: Thu, 13 Apr 2023 21:01:45 -0400 Subject: [PATCH 04/15] Better error handling for when you can't connect Catch errors when connecting to OpenStack initially. --- sunbeam/commands/launch.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/sunbeam/commands/launch.py b/sunbeam/commands/launch.py index 8f4f339..918c351 100644 --- a/sunbeam/commands/launch.py +++ b/sunbeam/commands/launch.py @@ -71,7 +71,6 @@ def check_keypair(openstack_conn: openstack.connection.Connection): check('chmod', '700', key_dir) id_ = openstack_conn.compute.create_keypair(name="sunbeam") - with open(key_path, 'w') as file_: file_.write(id_.private_key) check('chmod', '600', key_path) @@ -92,10 +91,13 @@ def launch( server_id = "" keypath = "" console.print("Launching an OpenStack instance ... ") - - conn = openstack.connect( - cloud="sunbeam" - ) + try: + conn = openstack.connect( + cloud="sunbeam" + ) + except: + console.print(f"Unable to connect to OpenStack. Is OpenStack running? Have you run the configure command? Do you have a clouds.yaml file?") + return with console.status("Checking for SSH key pair ... "): if key == "sunbeam": From af74af3bb28144e1dbb404d5e3e5daa1638dcd5d Mon Sep 17 00:00:00 2001 From: Jadon Naas Date: Thu, 13 Apr 2023 21:20:15 -0400 Subject: [PATCH 05/15] Better sunbeam key generation This checks for the key in OpenStack first, then generates a key --- sunbeam/commands/launch.py | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/sunbeam/commands/launch.py b/sunbeam/commands/launch.py index 918c351..fb91dd0 100644 --- a/sunbeam/commands/launch.py +++ b/sunbeam/commands/launch.py @@ -57,24 +57,18 @@ def check_keypair(openstack_conn: openstack.connection.Connection): Check for the sunbeam keypair's existence, creating it if it doesn't. """ - key_path = f"~/.ssh/sunbeam" - LOG.debug(f"check_keypair Key Path is: {key_path}") - if os.path.exists(key_path): - return key_path - console.print('Creating local "sunbeam" ssh key at {}'.format(key_path)) - # TODO: make sure that we get rid of this path on snap - # uninstall. If we don't, check to make sure that Sunbeam - # has a sunbeam ssh key, in addition to checking for the - # existence of the file. - key_dir = os.sep.join(key_path.split(os.sep)[:-1]) - check('mkdir', '-p', key_dir) - check('chmod', '700', key_dir) - - id_ = openstack_conn.compute.create_keypair(name="sunbeam") - with open(key_path, 'w') as file_: - file_.write(id_.private_key) - check('chmod', '600', key_path) - + console.print("Checking for sunbeam key in OpenStack ... ") + home = os.environ.get("SNAP_REAL_HOME") + key_path = f"{home}/sunbeam" + try: + keypair = openstack_conn.compute.get_keypair("sunbeam") + console.print("Found sunbeam key!") + except openstack.exceptions.ResourceNotFound: + console.print(f"No sunbeam key found. Creating sunbeam SSH key at {key_path}/sunbeam") + id_ = openstack_conn.compute.create_keypair(name="sunbeam") + with open(key_path, 'w') as file_: + file_.write(id_.private_key) + check('chmod', '600', key_path) return key_path @click.command() From 8cbd15d2adc15f95f3ebb89a5037257b78bd32fc Mon Sep 17 00:00:00 2001 From: Jadon Naas Date: Thu, 13 Apr 2023 21:20:47 -0400 Subject: [PATCH 06/15] Forgot to make this an f string --- sunbeam/commands/launch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sunbeam/commands/launch.py b/sunbeam/commands/launch.py index fb91dd0..2025d86 100644 --- a/sunbeam/commands/launch.py +++ b/sunbeam/commands/launch.py @@ -137,4 +137,4 @@ def launch( ip = conn.network.create_ip(floating_network_id=external_network.id) conn.compute.add_floating_ip_to_server(server_id, ip.floating_ip_address) - console.print("Access the instance with `ssh -i {key_path} ubuntu@{ip.floating_ip_address}") \ No newline at end of file + console.print(f"Access the instance with `ssh -i {key_path} ubuntu@{ip.floating_ip_address}") \ No newline at end of file From ee4cb70275e436976cfee2f99371ecde28598d21 Mon Sep 17 00:00:00 2001 From: Jadon Naas Date: Mon, 17 Apr 2023 11:02:38 -0400 Subject: [PATCH 07/15] Removed extra unused code --- sunbeam/commands/launch.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/sunbeam/commands/launch.py b/sunbeam/commands/launch.py index 2025d86..63e9434 100644 --- a/sunbeam/commands/launch.py +++ b/sunbeam/commands/launch.py @@ -79,11 +79,6 @@ def launch( """ Launch an OpenStack instance """ - LOG.debug(f"The supplied key name is {key}.") - model = snap.config.get("control-plane.model") - jhelper = juju.JujuHelper() - server_id = "" - keypath = "" console.print("Launching an OpenStack instance ... ") try: conn = openstack.connect( From 01592daec86ceb24de5fc664c0b347e080fa7804 Mon Sep 17 00:00:00 2001 From: Jadon Naas Date: Mon, 17 Apr 2023 11:10:09 -0400 Subject: [PATCH 08/15] Change key path placeholder text --- sunbeam/commands/launch.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sunbeam/commands/launch.py b/sunbeam/commands/launch.py index 63e9434..657b77f 100644 --- a/sunbeam/commands/launch.py +++ b/sunbeam/commands/launch.py @@ -98,7 +98,8 @@ def launch( # some placeholder text for the message at the end of this # routine, but don't worry about verifying it. We trust the # caller to have created it! - key_path = '/path/to/ssh/key' + home = os.environ.get("SNAP_REAL_HOME") + key_path = f"{home}/.ssh/{key}" with console.status("Creating the OpenStack instance ... "): instance_name = petname.Generate() From 5666ba413525dfc227aabb9e6e40f9f60dc9ecdf Mon Sep 17 00:00:00 2001 From: Jadon Naas Date: Mon, 17 Apr 2023 11:29:39 -0400 Subject: [PATCH 09/15] Pep8 fixes --- sunbeam/commands/launch.py | 47 +++++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/sunbeam/commands/launch.py b/sunbeam/commands/launch.py index 657b77f..2678883 100644 --- a/sunbeam/commands/launch.py +++ b/sunbeam/commands/launch.py @@ -44,6 +44,7 @@ def check_output(*args: List[str]) -> str: return subprocess.check_output(args, universal_newlines=True, env=os.environ).strip() + def check(*args: List[str]) -> int: """Execute a shell command, raising an error on failed excution. @@ -52,6 +53,7 @@ def check(*args: List[str]) -> int: """ return subprocess.check_call(args, env=os.environ) + def check_keypair(openstack_conn: openstack.connection.Connection): """ Check for the sunbeam keypair's existence, creating it if it doesn't. @@ -64,15 +66,23 @@ def check_keypair(openstack_conn: openstack.connection.Connection): keypair = openstack_conn.compute.get_keypair("sunbeam") console.print("Found sunbeam key!") except openstack.exceptions.ResourceNotFound: - console.print(f"No sunbeam key found. Creating sunbeam SSH key at {key_path}/sunbeam") + console.print( + f"No sunbeam key found. Creating SSH key at {key_path}/sunbeam" + ) id_ = openstack_conn.compute.create_keypair(name="sunbeam") with open(key_path, 'w') as file_: file_.write(id_.private_key) check('chmod', '600', key_path) return key_path + @click.command() -@click.option("-k", "--key", default="sunbeam", help="The SSH key to use for the instance") +@click.option( + "-k", + "--key", + default="sunbeam", + help="The SSH key to use for the instance" +) def launch( key: str = "sunbeam" ) -> None: @@ -84,8 +94,15 @@ def launch( conn = openstack.connect( cloud="sunbeam" ) - except: - console.print(f"Unable to connect to OpenStack. Is OpenStack running? Have you run the configure command? Do you have a clouds.yaml file?") + except Exception: + console.print( + ( + f"Unable to connect to OpenStack.", + f" Is OpenStack running?", + f" Have you run the configure command?", + f" Do you have a clouds.yaml file?" + ) + ) return with console.status("Checking for SSH key pair ... "): @@ -107,16 +124,6 @@ def launch( flavor = conn.compute.find_flavor("m1.tiny") network = conn.network.find_network("demo-network") keypair = conn.compute.find_keypair(key) - LOG.debug( - """ -Creating an instance with this configuration: -name = %s, -image = %s, -flavor = %s, -network = %s, -key_name = %s - """ % (instance_name, image.id, flavor.id, network.id, keypair.name) - ) server = conn.compute.create_server( name=instance_name, image_id=image.id, @@ -131,6 +138,14 @@ def launch( with console.status("Allocating IP address to instance ... "): external_network = conn.network.find_network("external-network") ip = conn.network.create_ip(floating_network_id=external_network.id) - conn.compute.add_floating_ip_to_server(server_id, ip.floating_ip_address) + conn.compute.add_floating_ip_to_server( + server_id, + ip.floating_ip_address + ) - console.print(f"Access the instance with `ssh -i {key_path} ubuntu@{ip.floating_ip_address}") \ No newline at end of file + console.print( + ( + f"Access instance with", + f"`ssh -i {key_path} ubuntu@{ip.floating_ip_address}" + ) + ) From f5eb71d8f1d398692de13bc736138b090ce647b1 Mon Sep 17 00:00:00 2001 From: Jadon Naas Date: Mon, 17 Apr 2023 11:30:09 -0400 Subject: [PATCH 10/15] Remove unused imports --- sunbeam/commands/launch.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/sunbeam/commands/launch.py b/sunbeam/commands/launch.py index 2678883..bbf2b3b 100644 --- a/sunbeam/commands/launch.py +++ b/sunbeam/commands/launch.py @@ -13,7 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -import asyncio import logging import os import subprocess @@ -27,8 +26,6 @@ from rich.console import Console from snaphelpers import Snap -from sunbeam.commands import juju - LOG = logging.getLogger(__name__) console = Console() snap = Snap() From d306e12bf8f9b33084c8219dd6939f1d23e6e4ab Mon Sep 17 00:00:00 2001 From: Jadon Naas Date: Mon, 17 Apr 2023 12:56:04 -0400 Subject: [PATCH 11/15] I implemented the multi-line string wrong --- sunbeam/commands/launch.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/sunbeam/commands/launch.py b/sunbeam/commands/launch.py index bbf2b3b..c4c8c45 100644 --- a/sunbeam/commands/launch.py +++ b/sunbeam/commands/launch.py @@ -17,11 +17,12 @@ import os import subprocess +from typing import List + import click import openstack import petname -from typing import List from rich.console import Console from snaphelpers import Snap @@ -60,14 +61,14 @@ def check_keypair(openstack_conn: openstack.connection.Connection): home = os.environ.get("SNAP_REAL_HOME") key_path = f"{home}/sunbeam" try: - keypair = openstack_conn.compute.get_keypair("sunbeam") + openstack_conn.compute.get_keypair("sunbeam") console.print("Found sunbeam key!") except openstack.exceptions.ResourceNotFound: console.print( f"No sunbeam key found. Creating SSH key at {key_path}/sunbeam" ) id_ = openstack_conn.compute.create_keypair(name="sunbeam") - with open(key_path, 'w') as file_: + with open(key_path, 'w', encoding="utf-8") as file_: file_.write(id_.private_key) check('chmod', '600', key_path) return key_path @@ -93,12 +94,10 @@ def launch( ) except Exception: console.print( - ( - f"Unable to connect to OpenStack.", - f" Is OpenStack running?", - f" Have you run the configure command?", - f" Do you have a clouds.yaml file?" - ) + "Unable to connect to OpenStack.", + " Is OpenStack running?", + " Have you run the configure command?", + " Do you have a clouds.yaml file?" ) return @@ -134,15 +133,15 @@ def launch( with console.status("Allocating IP address to instance ... "): external_network = conn.network.find_network("external-network") - ip = conn.network.create_ip(floating_network_id=external_network.id) + ip_ = conn.network.create_ip(floating_network_id=external_network.id) conn.compute.add_floating_ip_to_server( server_id, - ip.floating_ip_address + ip_.floating_ip_address ) console.print( ( - f"Access instance with", - f"`ssh -i {key_path} ubuntu@{ip.floating_ip_address}" + "Access instance with", + f"`ssh -i {key_path} ubuntu@{ip_.floating_ip_address}" ) ) From 777bee357477543e89d0a14703ef05cb44bef8f1 Mon Sep 17 00:00:00 2001 From: Jadon Naas Date: Mon, 17 Apr 2023 12:57:14 -0400 Subject: [PATCH 12/15] Print multi-line string better --- sunbeam/commands/launch.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/sunbeam/commands/launch.py b/sunbeam/commands/launch.py index c4c8c45..24b0477 100644 --- a/sunbeam/commands/launch.py +++ b/sunbeam/commands/launch.py @@ -140,8 +140,6 @@ def launch( ) console.print( - ( "Access instance with", f"`ssh -i {key_path} ubuntu@{ip_.floating_ip_address}" - ) ) From 46894f9963a48b1323b1b02a4159926cedf6c665 Mon Sep 17 00:00:00 2001 From: Jadon Naas Date: Mon, 17 Apr 2023 13:03:32 -0400 Subject: [PATCH 13/15] key_path should indicate it is not a real string --- sunbeam/commands/launch.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sunbeam/commands/launch.py b/sunbeam/commands/launch.py index 24b0477..9ea8078 100644 --- a/sunbeam/commands/launch.py +++ b/sunbeam/commands/launch.py @@ -111,8 +111,7 @@ def launch( # some placeholder text for the message at the end of this # routine, but don't worry about verifying it. We trust the # caller to have created it! - home = os.environ.get("SNAP_REAL_HOME") - key_path = f"{home}/.ssh/{key}" + key_path = "/path/to/your/key" with console.status("Creating the OpenStack instance ... "): instance_name = petname.Generate() From 921f57c2108a768ab5800dabe8b0fd22bf06fc52 Mon Sep 17 00:00:00 2001 From: Jadon Naas Date: Mon, 17 Apr 2023 13:07:48 -0400 Subject: [PATCH 14/15] More fine-grained exception to catch --- sunbeam/commands/launch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sunbeam/commands/launch.py b/sunbeam/commands/launch.py index 9ea8078..a8e70d9 100644 --- a/sunbeam/commands/launch.py +++ b/sunbeam/commands/launch.py @@ -92,7 +92,7 @@ def launch( conn = openstack.connect( cloud="sunbeam" ) - except Exception: + except openstack.exceptions.SDKException: console.print( "Unable to connect to OpenStack.", " Is OpenStack running?", From f33e59821b6797920f87bf64a35edb1ca62d5cdc Mon Sep 17 00:00:00 2001 From: Jadon Naas Date: Mon, 17 Apr 2023 13:11:42 -0400 Subject: [PATCH 15/15] Ran black to fix formatting and styling --- sunbeam/commands/launch.py | 44 ++++++++++++++------------------------ 1 file changed, 16 insertions(+), 28 deletions(-) diff --git a/sunbeam/commands/launch.py b/sunbeam/commands/launch.py index a8e70d9..6e614b4 100644 --- a/sunbeam/commands/launch.py +++ b/sunbeam/commands/launch.py @@ -39,8 +39,9 @@ def check_output(*args: List[str]) -> str: Include our env; pass in any extra keyword args. """ - return subprocess.check_output(args, universal_newlines=True, - env=os.environ).strip() + return subprocess.check_output( + args, universal_newlines=True, env=os.environ + ).strip() def check(*args: List[str]) -> int: @@ -64,40 +65,31 @@ def check_keypair(openstack_conn: openstack.connection.Connection): openstack_conn.compute.get_keypair("sunbeam") console.print("Found sunbeam key!") except openstack.exceptions.ResourceNotFound: - console.print( - f"No sunbeam key found. Creating SSH key at {key_path}/sunbeam" - ) + console.print(f"No sunbeam key found. Creating SSH key at {key_path}/sunbeam") id_ = openstack_conn.compute.create_keypair(name="sunbeam") - with open(key_path, 'w', encoding="utf-8") as file_: + with open(key_path, "w", encoding="utf-8") as file_: file_.write(id_.private_key) - check('chmod', '600', key_path) + check("chmod", "600", key_path) return key_path @click.command() @click.option( - "-k", - "--key", - default="sunbeam", - help="The SSH key to use for the instance" + "-k", "--key", default="sunbeam", help="The SSH key to use for the instance" ) -def launch( - key: str = "sunbeam" -) -> None: +def launch(key: str = "sunbeam") -> None: """ Launch an OpenStack instance """ console.print("Launching an OpenStack instance ... ") try: - conn = openstack.connect( - cloud="sunbeam" - ) + conn = openstack.connect(cloud="sunbeam") except openstack.exceptions.SDKException: console.print( - "Unable to connect to OpenStack.", - " Is OpenStack running?", - " Have you run the configure command?", - " Do you have a clouds.yaml file?" + "Unable to connect to OpenStack.", + " Is OpenStack running?", + " Have you run the configure command?", + " Do you have a clouds.yaml file?", ) return @@ -124,7 +116,7 @@ def launch( image_id=image.id, flavor_id=flavor.id, networks=[{"uuid": network.id}], - key_name=keypair.name + key_name=keypair.name, ) server = conn.compute.wait_for_server(server) @@ -133,12 +125,8 @@ def launch( with console.status("Allocating IP address to instance ... "): external_network = conn.network.find_network("external-network") ip_ = conn.network.create_ip(floating_network_id=external_network.id) - conn.compute.add_floating_ip_to_server( - server_id, - ip_.floating_ip_address - ) + conn.compute.add_floating_ip_to_server(server_id, ip_.floating_ip_address) console.print( - "Access instance with", - f"`ssh -i {key_path} ubuntu@{ip_.floating_ip_address}" + "Access instance with", f"`ssh -i {key_path} ubuntu@{ip_.floating_ip_address}" )