# Buggy code
def _bootstrap_config(
    config: Dict[str, Any], no_config_cache: bool = False
) -> Dict[str, Any]:
    config = prepare_config(config)

    hasher = hashlib.sha1()
    hasher.update(json.dumps([config], sort_keys=True).encode("utf-8"))
    cache_key = os.path.join(
        tempfile.gettempdir(), "ray-config-{}".format(hasher.hexdigest())
    )

    if os.path.exists(cache_key) and not no_config_cache:
        config_cache = json.loads(open(cache_key).read())
        if config_cache.get("_version", -1) == CONFIG_CACHE_VERSION:
            # todo: is it fine to re-resolve? afaik it should be.
            # we can have migrations otherwise or something
            # but this seems overcomplicated given that resolving is
            # relatively cheap
            try_reload_log_state(
                config_cache["config"]["provider"],
                config_cache.get("provider_log_info"),
            )

            if log_once("_printed_cached_config_warning"):
                cli_logger.verbose_warning(
                    "Loaded cached provider configuration from " + cf.bold("{}"),
                    cache_key,
                )
                if cli_logger.verbosity == 0:
                    cli_logger.warning("Loaded cached provider configuration")
                cli_logger.warning(
                    "If you experience issues with "
                    "the cloud provider, try re-running "
                    "the command with {}.",
                    cf.bold("--no-config-cache"),
                )

            return config_cache["config"]
        else:
            cli_logger.warning(
                "Found cached cluster config "
                "but the version " + cf.bold("{}") + " "
                "(expected " + cf.bold("{}") + ") does not match.\n"
                "This is normal if cluster launcher was updated.\n"
                "Config will be re-resolved.",
                config_cache.get("_version", "none"),
                CONFIG_CACHE_VERSION,
            )

    importer = _NODE_PROVIDERS.get(config["provider"]["type"])
    if not importer:
        raise NotImplementedError("Unsupported provider {}".format(config["provider"]))

    provider_cls = importer(config["provider"])

    cli_logger.print(
        "Checking {} environment settings",
        _PROVIDER_PRETTY_NAMES.get(config["provider"]["type"]),
    )

    config = provider_cls.fillout_available_node_types_resources(config)

    # NOTE: if `resources` field is missing, validate_config for non-AWS will
    # fail (the schema error will ask the user to manually fill the resources)
    # as we currently support autofilling resources for AWS instances only.
    validate_config(config)
    resolved_config = provider_cls.bootstrap_config(config)

    if not no_config_cache:
        with open(cache_key, "w") as f:
            config_cache = {
                "_version": CONFIG_CACHE_VERSION,
                "provider_log_info": try_get_log_state(config["provider"]),
                "config": resolved_config,
            }
            f.write(json.dumps(config_cache))
    return resolved_config


# Traceback (AttributeError)
(base) ➜  tune git:(fix-kubernetes-dep) ✗ ray up $CFG -y
Cluster: basic

Checking AWS environment settings
Traceback (most recent call last):
File "/Users/rliaw/miniconda3/bin/ray", line 8, in <module>
sys.exit(main())
File "/Users/rliaw/miniconda3/lib/python3.7/site-packages/ray/scripts/scripts.py", line 1471, in main
return cli()
File "/Users/rliaw/miniconda3/lib/python3.7/site-packages/click/core.py", line 829, in __call__
return self.main(*args, **kwargs)
File "/Users/rliaw/miniconda3/lib/python3.7/site-packages/click/core.py", line 782, in main
rv = self.invoke(ctx)
File "/Users/rliaw/miniconda3/lib/python3.7/site-packages/click/core.py", line 1259, in invoke
return _process_result(sub_ctx.command.invoke(sub_ctx))
File "/Users/rliaw/miniconda3/lib/python3.7/site-packages/click/core.py", line 1066, in invoke
return ctx.invoke(self.callback, **ctx.params)
File "/Users/rliaw/miniconda3/lib/python3.7/site-packages/click/core.py", line 610, in invoke
return callback(*args, **kwargs)
File "/Users/rliaw/miniconda3/lib/python3.7/site-packages/ray/scripts/scripts.py", line 860, in up
use_login_shells=use_login_shells)
File "/Users/rliaw/miniconda3/lib/python3.7/site-packages/ray/autoscaler/_private/commands.py", line 213, in create_or_update_cluster
config = _bootstrap_config(config, no_config_cache=no_config_cache)
File "/Users/rliaw/miniconda3/lib/python3.7/site-packages/ray/autoscaler/_private/commands.py", line 274, in _bootstrap_config
config = provider_cls.fillout_available_node_types_resources(config)
File "/Users/rliaw/miniconda3/lib/python3.7/site-packages/ray/autoscaler/_private/aws/node_provider.py", line 487, in fillout_available_node_types_resources
cluster_config["provider"].get("aws_credentials"))
File "/Users/rliaw/miniconda3/lib/python3.7/site-packages/ray/autoscaler/_private/aws/node_provider.py", line 76, in list_ec2_instances
instance_types = ec2.describe_instance_types()
File "/Users/rliaw/miniconda3/lib/python3.7/site-packages/botocore/client.py", line 565, in __getattr__
self.__class__.__name__, item)
AttributeError: 'EC2' object has no attribute 'describe_instance_types'

# Fixed code
def _bootstrap_config(
    config: Dict[str, Any], no_config_cache: bool = False
) -> Dict[str, Any]:
    config = prepare_config(config)

    hasher = hashlib.sha1()
    hasher.update(json.dumps([config], sort_keys=True).encode("utf-8"))
    cache_key = os.path.join(
        tempfile.gettempdir(), "ray-config-{}".format(hasher.hexdigest())
    )

    if os.path.exists(cache_key) and not no_config_cache:
        config_cache = json.loads(open(cache_key).read())
        if config_cache.get("_version", -1) == CONFIG_CACHE_VERSION:
            # todo: is it fine to re-resolve? afaik it should be.
            # we can have migrations otherwise or something
            # but this seems overcomplicated given that resolving is
            # relatively cheap
            try_reload_log_state(
                config_cache["config"]["provider"],
                config_cache.get("provider_log_info"),
            )

            if log_once("_printed_cached_config_warning"):
                cli_logger.verbose_warning(
                    "Loaded cached provider configuration from " + cf.bold("{}"),
                    cache_key,
                )
                if cli_logger.verbosity == 0:
                    cli_logger.warning("Loaded cached provider configuration")
                cli_logger.warning(
                    "If you experience issues with "
                    "the cloud provider, try re-running "
                    "the command with {}.",
                    cf.bold("--no-config-cache"),
                )

            return config_cache["config"]
        else:
            cli_logger.warning(
                "Found cached cluster config "
                "but the version " + cf.bold("{}") + " "
                "(expected " + cf.bold("{}") + ") does not match.\n"
                "This is normal if cluster launcher was updated.\n"
                "Config will be re-resolved.",
                config_cache.get("_version", "none"),
                CONFIG_CACHE_VERSION,
            )

    importer = _NODE_PROVIDERS.get(config["provider"]["type"])
    if not importer:
        raise NotImplementedError("Unsupported provider {}".format(config["provider"]))

    provider_cls = importer(config["provider"])

    cli_logger.print(
        "Checking {} environment settings",
        _PROVIDER_PRETTY_NAMES.get(config["provider"]["type"]),
    )
    try:
        config = provider_cls.fillout_available_node_types_resources(config)
    except Exception as exc:
        if cli_logger.verbosity > 2:
            logger.exception("Failed to autodetect node resources.")
        else:
            cli_logger.warning(
                f"Failed to autodetect node resources: {str(exc)}. "
                "You can see full stack trace with higher verbosity."
            )

    # NOTE: if `resources` field is missing, validate_config for non-AWS will
    # fail (the schema error will ask the user to manually fill the resources)
    # as we currently support autofilling resources for AWS instances only.
    validate_config(config)
    resolved_config = provider_cls.bootstrap_config(config)

    if not no_config_cache:
        with open(cache_key, "w") as f:
            config_cache = {
                "_version": CONFIG_CACHE_VERSION,
                "provider_log_info": try_get_log_state(config["provider"]),
                "config": resolved_config,
            }
            f.write(json.dumps(config_cache))
    return resolved_config