Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fixing sort of prices for select #23

Merged
merged 3 commits into from
Feb 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 0 additions & 1 deletion cloud_select/client/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@


def main(args, parser, extra, subparser):

utils.ensure_no_extra(extra)

cli = Client(
Expand Down
1 change: 0 additions & 1 deletion cloud_select/client/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@


def main(args, parser, extra, subparser):

cloud_select.utils.ensure_no_extra(extra)

# If nothing provided, show help
Expand Down
1 change: 0 additions & 1 deletion cloud_select/client/dbshell.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
1 change: 0 additions & 1 deletion cloud_select/client/instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@


def main(args, parser, extra, subparser):

utils.ensure_no_extra(extra)

cli = Client(
Expand Down
2 changes: 0 additions & 2 deletions cloud_select/client/shell.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand All @@ -32,7 +31,6 @@ def main(args, parser, extra, subparser):


def create_client(args):

cli = Client(
quiet=args.quiet,
settings_file=args.settings_file,
Expand Down
1 change: 0 additions & 1 deletion cloud_select/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
4 changes: 0 additions & 4 deletions cloud_select/main/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -102,7 +101,6 @@ def load_cache(self, key):
"""
items = {}
for cloud in self.get_clouds():

# Assume we don't find data
data = None

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
2 changes: 0 additions & 2 deletions cloud_select/main/cloud/aws/instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]:
Expand Down
1 change: 0 additions & 1 deletion cloud_select/main/cloud/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 1 addition & 2 deletions cloud_select/main/cloud/google/instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
3 changes: 0 additions & 3 deletions cloud_select/main/oras.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(".")
Expand All @@ -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"]

Expand All @@ -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", {})
Expand Down
22 changes: 21 additions & 1 deletion cloud_select/main/selectors.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 0 additions & 2 deletions cloud_select/main/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -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] = ""

Expand Down Expand Up @@ -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
Expand Down
2 changes: 0 additions & 2 deletions cloud_select/tests/test_instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion cloud_select/tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down
2 changes: 1 addition & 1 deletion cloud_select/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 0 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down