diff --git a/CHANGELOG.md b/CHANGELOG.md index 3890c8c..ed19ad0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and **Merged pull requests**. Critical items to know are: The versions coincide with releases on pip. Only major versions will be released as tags on Github. ## [0.0.x](https://github.com/converged-computing/cloud-select/tree/main) (0.0.x) + - bugfix for sorting by prices with select (0.0.18) - selector class (with interactive or automated select) (0.0.17) - support for `--efa` flag to find AWS instances that support it (0.0.16) - exponential backoff added for using aws products/prices api (0.0.15) diff --git a/cloud_select/client/cache.py b/cloud_select/client/cache.py index 95a2179..fc80e6f 100644 --- a/cloud_select/client/cache.py +++ b/cloud_select/client/cache.py @@ -8,7 +8,6 @@ def main(args, parser, extra, subparser): - utils.ensure_no_extra(extra) cli = Client( diff --git a/cloud_select/client/config.py b/cloud_select/client/config.py index 25ad153..4fdcdc4 100644 --- a/cloud_select/client/config.py +++ b/cloud_select/client/config.py @@ -12,7 +12,6 @@ def main(args, parser, extra, subparser): - cloud_select.utils.ensure_no_extra(extra) # If nothing provided, show help diff --git a/cloud_select/client/dbshell.py b/cloud_select/client/dbshell.py index 702b0a6..2c13c1c 100644 --- a/cloud_select/client/dbshell.py +++ b/cloud_select/client/dbshell.py @@ -10,7 +10,6 @@ def main(args, parser, extra, subparser): - cloud_select.utils.ensure_no_extra(extra) lookup = {"ipython": ipython, "python": python, "bpython": bpython} shells = ["ipython", "python", "bpython"] diff --git a/cloud_select/client/instance.py b/cloud_select/client/instance.py index 0ecffdc..26002f5 100644 --- a/cloud_select/client/instance.py +++ b/cloud_select/client/instance.py @@ -12,7 +12,6 @@ def main(args, parser, extra, subparser): - utils.ensure_no_extra(extra) cli = Client( diff --git a/cloud_select/client/shell.py b/cloud_select/client/shell.py index c65e12d..c7c977f 100644 --- a/cloud_select/client/shell.py +++ b/cloud_select/client/shell.py @@ -8,7 +8,6 @@ def main(args, parser, extra, subparser): - cloud_select.utils.ensure_no_extra(extra) lookup = {"ipython": ipython, "python": python, "bpython": bpython} shells = ["ipython", "python", "bpython"] @@ -32,7 +31,6 @@ def main(args, parser, extra, subparser): def create_client(args): - cli = Client( quiet=args.quiet, settings_file=args.settings_file, diff --git a/cloud_select/logger.py b/cloud_select/logger.py index d2bc7f4..2876fb2 100644 --- a/cloud_select/logger.py +++ b/cloud_select/logger.py @@ -38,7 +38,6 @@ def add_prefix(msg, char=">>"): class ColorizingStreamHandler(_logging.StreamHandler): - BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8) RESET_SEQ = LogColors.ENDC COLOR_SEQ = "\033[%dm" diff --git a/cloud_select/main/client.py b/cloud_select/main/client.py index e61d544..7ff73a8 100644 --- a/cloud_select/main/client.py +++ b/cloud_select/main/client.py @@ -72,7 +72,6 @@ def get_clouds(self, force=False, lookup=False): # We should always be able to get cloud classes, even without auth # The class knows how to parse the data types into a standard space for cloud_name, CloudClass in self._cloudclass.items(): - # Regions default to settings then defaults cloud_settings = getattr(self.settings, cloud_name) self._clouds[cloud_name] = CloudClass( @@ -102,7 +101,6 @@ def load_cache(self, key): """ items = {} for cloud in self.get_clouds(): - # Assume we don't find data data = None @@ -140,7 +138,6 @@ def update_from_cache(self, items, datatype): """ # For every cloud class we have... for cloud in self.get_clouds(): - # We have the data and it's expired OR we don't have it - update it if cloud.name not in items or self.cache.is_expired(cloud.name, datatype): func = getattr(cloud, datatype, None) @@ -195,7 +192,6 @@ def prepare_database(self, **kwargs): # 1. write mapping of common features into functions # 2. filter down to desired set based on these common functions for cloud_name, instance_group in instances.items(): - # Give a warning about properties that aren't supported instance_group.Instance.check_attributes( properties, self.settings.allow_missing_attributes diff --git a/cloud_select/main/cloud/aws/instance.py b/cloud_select/main/cloud/aws/instance.py index afe67e5..9bb212f 100644 --- a/cloud_select/main/cloud/aws/instance.py +++ b/cloud_select/main/cloud/aws/instance.py @@ -227,10 +227,8 @@ def add_instance_prices(self, prices): # Add prices to instances we have prices for for instance in self.data: - # Set to unreasonably high so it's not a choice if instance["InstanceType"] in lookup: - # Make a list of prices that matches regions region_prices = {} for region in instance["Regions"]: diff --git a/cloud_select/main/cloud/base.py b/cloud_select/main/cloud/base.py index 7229f1a..fe6c41c 100644 --- a/cloud_select/main/cloud/base.py +++ b/cloud_select/main/cloud/base.py @@ -18,7 +18,6 @@ class CloudProvider: name = "cloud" def __init__(self): - # If we weren't created with settings, add empty if not hasattr(self, "settings"): from cloud_select.main.settings import Settings diff --git a/cloud_select/main/cloud/google/instance.py b/cloud_select/main/cloud/google/instance.py index 8f85460..c30df3a 100644 --- a/cloud_select/main/cloud/google/instance.py +++ b/cloud_select/main/cloud/google/instance.py @@ -133,7 +133,7 @@ def add_instance_prices(self, prices): return logger.warning( - "Google Cloud instance prices derived from the web are limited to Iowa (us-centra-1)" + "Google Cloud instance prices derived from the web are limited to Iowa (us-central1)" ) # Get actual machine types and convert web listing to types @@ -148,7 +148,6 @@ def add_instance_prices(self, prices): if not row: continue if row[0] in actual_types: - # Find price index idx = [ i diff --git a/cloud_select/main/oras.py b/cloud_select/main/oras.py index 37282a5..09d6f44 100644 --- a/cloud_select/main/oras.py +++ b/cloud_select/main/oras.py @@ -52,7 +52,6 @@ def download_layer(self, cloud_name, datatype, manifest, root, package): # Find the layer of interest! Currently we look for presence of the string # e.g., "prices" can come from "prices" or "prices-web" for layer in manifest.get("layers", []): - # E.g., google.prices or google.prices-web or aws.prices contents = layer["mediaType"].split("cloud-select.")[-1] cloud_found, data_found = contents.split(".") @@ -61,7 +60,6 @@ def download_layer(self, cloud_name, datatype, manifest, root, package): # This gives flexibility to support different variances of prices, etc. if datatype in data_found: - logger.debug(f"Downloading data file for {datatype} from ORAS cache...") artifact = layer["annotations"]["org.opencontainers.image.title"] @@ -82,7 +80,6 @@ def push(self, container, archives: list): # Upload files as blobs for item in archives: - blob = item.get("path") media_type = item.get("media_type") or defaults.default_media_type annots = item.get("annotations", {}) diff --git a/cloud_select/main/selectors.py b/cloud_select/main/selectors.py index 75d0c9b..785f256 100644 --- a/cloud_select/main/selectors.py +++ b/cloud_select/main/selectors.py @@ -64,6 +64,14 @@ def select_instance( instances = self.cli.instance_select(**resources) subset = [x for x in instances if x.get(sort_by) not in [None, ""]] + + # If sort by price, need to set price to be float + if sort_by == "price": + for item in subset: + if sort_by not in item: + continue + item[sort_by] = float(item[sort_by]) + selection = sorted(subset, key=lambda x: x[sort_by], reverse=not ascending) # If not interactive, just return selection @@ -90,13 +98,25 @@ def select_instance( return instance - def get_options(self, selection, default=None, allow_exit=True): + def get_options( + self, + selection, + default=None, + allow_exit=True, + ): """ Get options for cloud select """ options = [] if default: options = [f" Use default {default}"] + + for instance in selection: + name = instance["name"].rjust(15) + description = instance["description"].ljust(5) + option = f"{name} {description} at {instance['price']} $/hour" + options.append(option) + options += [ f"{x['name'].rjust(15)} {x['description']} at {x['price']} $/hour" for x in selection diff --git a/cloud_select/main/table.py b/cloud_select/main/table.py index 19a390b..86a210a 100644 --- a/cloud_select/main/table.py +++ b/cloud_select/main/table.py @@ -71,7 +71,6 @@ def ensure_complete_list(self): # Ensure fields are present for entry in self.data: for field in fields: - if field not in entry: entry[field] = "" @@ -104,7 +103,6 @@ def table_rows(self, columns, limit=25): # All keys are lowercase column_width = self.available_width(columns) for i, row in enumerate(self.data): - # have we gone over the limit? if limit and i > limit: return diff --git a/cloud_select/tests/test_instance.py b/cloud_select/tests/test_instance.py index 1ad9aa6..23ee202 100644 --- a/cloud_select/tests/test_instance.py +++ b/cloud_select/tests/test_instance.py @@ -120,7 +120,6 @@ def test_instance_filters(tmp_path, cloud, prices_file, instances_file, region): # Set cache data to memory cache (a copy so we can edit it) def reset_data(): - client.cache.memory_set(cloud, copy.deepcopy(prices_sample), "prices") client.cache.memory_set(cloud, copy.deepcopy(instances_sample), "instances") @@ -169,7 +168,6 @@ def reset_data(): # Test args for each property - not min and max (will be tested separately) for prop, _ in schemas.instance_properties.items(): - # Skip min and max for now - will be tested separately if "min" in prop or "max" in prop or prop in skips: continue diff --git a/cloud_select/tests/test_utils.py b/cloud_select/tests/test_utils.py index d18f18f..b5a02d7 100644 --- a/cloud_select/tests/test_utils.py +++ b/cloud_select/tests/test_utils.py @@ -40,7 +40,6 @@ def test_write_bad_json(tmp_path): def test_write_json(tmp_path): - good_json = {"Wakkawakkawakka": [True, "2", 3]} tmpfile = str(tmp_path / "good_json_file.txt") diff --git a/cloud_select/version.py b/cloud_select/version.py index 67f7f3d..ab56ee6 100644 --- a/cloud_select/version.py +++ b/cloud_select/version.py @@ -3,7 +3,7 @@ # # SPDX-License-Identifier: (MIT) -__version__ = "0.0.17" +__version__ = "0.0.18" AUTHOR = "Vanessa Sochat" EMAIL = "vsoch@users.noreply.github.com" NAME = "cloud-select-tool" diff --git a/setup.py b/setup.py index 3531f6d..29c4b3b 100644 --- a/setup.py +++ b/setup.py @@ -68,7 +68,6 @@ def get_reqs(lookup=None, key="INSTALL_REQUIRES"): ################################################################################ if __name__ == "__main__": - INSTALL_REQUIRES = get_reqs(lookup) TESTS_REQUIRES = get_reqs(lookup, "TESTS_REQUIRES") INSTALL_REQUIRES_ALL = get_reqs(lookup, "INSTALL_REQUIRES_ALL")