cluster-service/update_instance_types.py (68 lines of code) (raw):

#!/usr/bin/python3 import argparse import json import os import yaml from yaml.resolver import BaseResolver # This script updates the available SKUs (instance-types) for node pools in the configmap used by ClustersService. # It requires an active az session, pointing to the right subscription by default. # # Examples: # $ python update_instance_types.py # $ python update_instance_types.py --help # $ python update_instance_types.py --region eastus --output cloud-resources-config.yaml parser = argparse.ArgumentParser(description='Generates a list of available instance types in the given region. ' 'Requires an active az session to run "az vm list-sizes" on the shell.') parser.add_argument('--region', default="westus3", help='the azure region, by default westus3') parser.add_argument('--input', default="deploy/templates/cloud-resources-config.configmap.yaml", help='the input configmap, by default the cloud resources-config in the helm template directory') parser.add_argument('--output', default="deploy/templates/cloud-resources-config.configmap.yaml", help='the output configmap, by default the cloud resources-config in the helm template directory') args = parser.parse_args() class AsLiteral(str): pass def represent_literal(dumper, data): return dumper.represent_scalar(BaseResolver.DEFAULT_SCALAR_TAG, data, style="|") yaml.add_representer(AsLiteral, represent_literal) if not os.path.exists(args.input): raise FileNotFoundError(f"Input file not found: {args.input}") with open(args.input) as f: try: cm_content = yaml.safe_load(f) except yaml.YAMLError as e: raise RuntimeError(f"Error parsing input YAML file {args.input}: {e}") # shelling out here to avoid importing all kinds of Azure libraries via pip json_out = os.popen(f"az vm list-sizes --location \"{args.region}\"").read() if not json_out.strip(): raise RuntimeError("Unable to list vm sizes. Please check that an azure session is active and is able to run az commands.") types = json.loads(json_out) instance_types_set = {} instance_types_list = [] for i in range(len(types)): vm_type = types[i] type_name = vm_type['name'] print(f"processing {vm_type['name']}") instance_type = { "cloud_provider_id": "azure", "id": type_name, "name": type_name, "generic_name": type_name.lower(), "cpu_cores": int(vm_type["numberOfCores"]), "memory": int(vm_type["memoryInMB"]) * 1024 * 1024, } if "L" in type_name: instance_type["category"] = "storage_optimized" elif "N" in type_name: instance_type["category"] = "gpu_workload" elif "E" in type_name: instance_type["category"] = "memory_optimized" elif "F" in type_name: instance_type["category"] = "compute_optimized" elif "D" in type_name: instance_type["category"] = "general_purpose" else: print(f"skipping unknown {type_name}") continue if "Promo" in type_name: print(f"skipping Promo type: {type_name}") continue if "p" in type_name: instance_type["architecture"] = "arm64" if type_name not in instance_types_set: instance_types_list.append(instance_type) instance_types_set[type_name] = True # we need to double-yaml-dump because the configmap stores the file as a yaml inside the yaml cm_content["data"]["instance-types.yaml"] = AsLiteral(yaml.dump({"instance_types": instance_types_list})) cm_content["data"]["cloud-regions.yaml"] = AsLiteral(cm_content["data"]["cloud-regions.yaml"]) with open(args.output, "w") as f: yaml.dump(cm_content, f)