def prune_func(args: Namespace): try: dstack_config = get_config() repo_url, _, _, _ = load_repo_data() # TODO: Support non-default profiles profile = dstack_config.get_profile("default") if args.force or confirm( f"WARNING! This will permanently delete all untagged finished runs.\n\n" f"Are you sure you want to continue?"): headers = {"Content-Type": f"application/json; charset=utf-8"} if profile.token is not None: headers["Authorization"] = f"Bearer {profile.token}" data = {"repo_url": repo_url} data_bytes = json.dumps(data).encode("utf-8") response = request(method="POST", url=f"{profile.server}/runs/prune", data=data_bytes, headers=headers, verify=profile.verify) if response.status_code == 200: print(f"{colorama.Fore.LIGHTBLACK_EX}OK{colorama.Fore.RESET}") elif response.status_code == 400: print(response.json()["message"]) else: response.raise_for_status() else: print(f"{colorama.Fore.RED}Cancelled{colorama.Fore.RESET}") except InvalidGitRepositoryError: sys.exit(f"{os.getcwd()} is not a Git repo") except ConfigurationError: sys.exit(f"Call 'dstack config' first")
def untag_func(args: Namespace): try: if args.force or confirm( f"Are you sure you want to remove the tag from the run?"): dstack_config = get_config() # TODO: Support non-default profiles profile = dstack_config.get_profile("default") headers = {"Content-Type": f"application/json; charset=utf-8"} if profile.token is not None: headers["Authorization"] = f"Bearer {profile.token}" data = {"run_name": args.run_name} response = requests.request(method="POST", url=f"{profile.server}/runs/untag", data=json.dumps(data).encode("utf-8"), headers=headers, verify=profile.verify) if response.status_code == 404: sys.exit(f"No run '{args.run_name_or_job_id}' is found") elif response.status_code != 200: response.raise_for_status() else: print(f"{colorama.Fore.LIGHTBLACK_EX}OK{colorama.Fore.RESET}") else: print(f"{colorama.Fore.RED}Cancelled{colorama.Fore.RESET}") except ConfigurationError: sys.exit(f"Call 'dstack config' first")
def config_func(args: Namespace): try: dstack_config = get_config() # TODO: Support non-default profiles profile = dstack_config.get_profile("default") user_info = get_user_info(profile) data = { "aws_access_key_id": get_or_ask(args, None, "aws_access_key_id", "AWS Access Key ID: ", secure=True), "aws_secret_access_key": get_or_ask(args, None, "aws_secret_access_key", "AWS Secret Access Key: ", secure=True), "aws_region": get_or_ask(args, None, "aws_region", f"Region name [{user_info['default_configuration']['aws_region']}]: ", secure=False, required=False), "artifacts_s3_bucket": get_or_ask(args, None, "artifacts_s3_bucket", "Artifacts S3 bucket [None]: ", secure=False, required=False) } response = do_post("users/aws/info") if response.status_code == 200: response_json = response.json() if response_json.get("aws_access_key_id") is None \ and response_json.get("aws_secret_access_key") is None \ and response_json.get("aws_region") is None \ and response_json.get("artifacts_s3_bucket") is None: send_aws_config_request(data) else: if args.force or confirm(f"Do you want to override the previous configuration?"): send_aws_config_request(data) else: print(f"{colorama.Fore.RED}Cancelled{colorama.Fore.RESET}") else: response.raise_for_status() except ConfigurationError: sys.exit(f"Call 'dstack config' first")
def remove_profile(args: Namespace): conf = from_yaml_file(_get_config_path(args.file)) if args.force or confirm( f"Do you want to delete profile '{args.profile}'"): conf.remove_profile(args.profile) conf.save()
def disable_func(args: Namespace): if args.force or confirm(f"Are you sure you want to disable on-demand runners?"): try: data = { "enabled": False } response = do_post("on-demand/settings/update", data) if response.status_code == 200: print(f"{colorama.Fore.LIGHTBLACK_EX}OK{colorama.Fore.RESET}") else: response.raise_for_status() except ConfigurationError: sys.exit(f"Call 'dstack config' first") else: print(f"{colorama.Fore.RED}Cancelled{colorama.Fore.RESET}")
def limit_func(args: Namespace): if args.delete: if args.force or confirm(f"Are you sure you want to delete the limit?"): try: data = { "region_name": args.region, "instance_type": args.instance_type, "purchase_type": "spot" if args.spot else "on-demand" } response = do_post("on-demand/limits/delete", data) if response.status_code == 200: print(f"{colorama.Fore.LIGHTBLACK_EX}OK{colorama.Fore.RESET}") if response.status_code == 400 and response.json().get("message") == "aws is not configured": sys.exit(f"Call 'dstack aws config' first") if response.status_code == 404 and response.json().get("message") == "limit not found": sys.exit(f"Limit doesn't exist") if response.status_code == 404 and response.json().get("message") == "region not found": sys.exit(f"Region is not supported") if response.status_code == 404 and response.json().get("message") == "instance type not found": sys.exit(f"Instance type is not supported") else: response.raise_for_status() except ConfigurationError: sys.exit(f"Call 'dstack config' first") else: print(f"{colorama.Fore.RED}Cancelled{colorama.Fore.RESET}") else: try: data = { "region_name": args.region, "instance_type": args.instance_type, "purchase_type": "spot" if args.spot else "on-demand", "maximum": args.max, } response = do_post("on-demand/limits/set", data) if response.status_code == 200: print(f"{colorama.Fore.LIGHTBLACK_EX}OK{colorama.Fore.RESET}") if response.status_code == 400 and response.json().get("message") == "aws is not configured": sys.exit(f"Call 'dstack aws config' first") if response.status_code == 404 and response.json().get("message") == "region not found": sys.exit(f"Region is not supported") if response.status_code == 404 and response.json().get("message") == "instance type not found": sys.exit(f"Instance type is not supported") else: response.raise_for_status() except ConfigurationError: sys.exit(f"Call 'dstack config' first")
def limits_func(args: Namespace): if args.delete_all: if args.force or confirm(f"Are you sure you want to delete all limits?"): try: response = do_post("on-demand/limits/clear") if response.status_code == 200: print(f"{colorama.Fore.LIGHTBLACK_EX}OK{colorama.Fore.RESET}") else: response.raise_for_status() except ConfigurationError: sys.exit(f"Call 'dstack config' first") else: print(f"{colorama.Fore.RED}Cancelled{colorama.Fore.RESET}") else: try: response = do_get("on-demand/limits/query") if response.status_code == 200: table_headers = [ f"{colorama.Fore.LIGHTMAGENTA_EX}REGION{colorama.Fore.RESET}", f"{colorama.Fore.LIGHTMAGENTA_EX}INSTANCE TYPE{colorama.Fore.RESET}", f"{colorama.Fore.LIGHTMAGENTA_EX}CPU{colorama.Fore.RESET}", f"{colorama.Fore.LIGHTMAGENTA_EX}MEMORY{colorama.Fore.RESET}", f"{colorama.Fore.LIGHTMAGENTA_EX}GPU{colorama.Fore.RESET}", f"{colorama.Fore.LIGHTMAGENTA_EX}PURCHASE TYPE{colorama.Fore.RESET}", f"{colorama.Fore.LIGHTMAGENTA_EX}MAXIMUM{colorama.Fore.RESET}", f"{colorama.Fore.LIGHTMAGENTA_EX}STATUS{colorama.Fore.RESET}" ] table_rows = [] for limit in response.json()["limits"]: availability_issues_at = pretty_date( round(limit["availability_issues_at"] / 1000)) if limit.get("availability_issues_at") else "" table_rows.append([ limit["region_name"], limit["instance_type"], limit["resources"]["cpu"]["count"] if limit.get("resources") else "", str(int(limit["resources"]["memory_mib"] / 1024)) + "GiB" if limit.get("resources") else "", __pretty_print_gpu_resources(limit["resources"]) if limit.get("resources") else "", limit["purchase_type"], limit["maximum"], f"{colorama.Fore.RED}{'No capacity ' + availability_issues_at}{colorama.Fore.RESET}" if limit.get( "availability_issues_message") else f"{colorama.Fore.GREEN}OK{colorama.Fore.RESET}" ]) print(tabulate(table_rows, headers=table_headers, tablefmt="plain")) else: response.raise_for_status() except ConfigurationError: sys.exit(f"Call 'dstack config' first")
def add_or_modify_profile(args: Namespace): file = Path(args.file) if args.file else None conf = from_yaml_file(_get_config_path(file)) profile = conf.get_profile(args.profile) token = get_or_ask(args, profile, "token", "Token: ", secure=True) if profile is None: profile = Profile(args.profile, token, args.server, not args.no_verify) elif args.force or (token != profile.token and confirm( f"Do you want to replace the token for the profile '{args.profile}'" )): profile.token = token profile.server = args.server profile.verify = not args.no_verify conf.add_or_replace_profile(profile) conf.save()
def clear_func(args: Namespace): try: response = do_post("users/aws/info") if response.status_code == 200: response_json = response.json() if response_json.get("aws_access_key_id") is None \ and response_json.get("aws_secret_access_key") is None \ and response_json.get("aws_region") is None \ and response_json.get("artifacts_s3_bucket") is None: do_clear_request() else: if args.force or confirm(f"Do you want to override the previous configuration?"): do_clear_request() else: print(f"{colorama.Fore.RED}Cancelled{colorama.Fore.RESET}") else: response.raise_for_status() except ConfigurationError: sys.exit(f"Call 'dstack config' first")