Skip to content

chore: improving warnings on incorrect or absent configuration #193

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

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
19 changes: 16 additions & 3 deletions .envrc
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,22 @@ if [ -d ".venv" ]; then
fi
fi

python -m uv venv .venv --python $PYTHON_VERSION
python -m uv pip install -U pip uv
python -m uv pip install -e .
if [ ! -d ".venv" ]
then
# System python doesn't like us installing packages into it
# Test if uv is already installed (via brew or other package manager etc.)
# and use that if available. Otherwise fall back to previous behvaiour
if command -v uv
then
uv venv .venv --python $PYTHON_VERSION
uv pip install -U pip uv
uv pip install -e .
else
python -m uv venv .venv --python $PYTHON_VERSION
python -m uv pip install -U pip uv
python -m uv pip install -e .
fi
fi

source ./.venv/bin/activate

Expand Down
4 changes: 4 additions & 0 deletions cloudsmith_cli/cli/commands/repos.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ def repositories(ctx, opts): # pylink: disable=unused-argument
@decorators.common_cli_output_options
@decorators.common_api_auth_options
@decorators.initialise_api
@decorators.verify_authenticated
@click.argument(
"owner_repo",
metavar="OWNER/REPO",
Expand All @@ -101,6 +102,9 @@ def get(ctx, opts, owner_repo, page, page_size):

click.echo("Getting list of repositories ... ", nl=False, err=use_stderr)

repo = None
Copy link
Preview

Copilot AI Apr 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable 'repo' is assigned but never used. Consider removing it to clean up the code.

Copilot uses AI. Check for mistakes.

owner = None

if isinstance(owner_repo, list):
if len(owner_repo) == 1:
owner = owner_repo[0]
Expand Down
66 changes: 64 additions & 2 deletions cloudsmith_cli/cli/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ class ConfigReader(ConfigFileReader):
config_name = "standard"
config_searchpath = list(_CFG_SEARCH_PATHS)
config_section_schemas = [ConfigSchema.Default, ConfigSchema.Profile]
config_warning_issued = False

@classmethod
def select_config_schema_for(cls, section_name):
Expand Down Expand Up @@ -147,6 +148,19 @@ def has_default_file(cls):

return False

@classmethod
def config_already_warned(cls):
"""
Check if a configuration file warning has been issued.
This is required as configs are gathered at the root of the
command chain as well as for command verbs
"""
if cls.config_warning_issued:
return True

cls.config_warning_issued = True
return False

@classmethod
def load_config(cls, opts, path=None, profile=None):
"""Load a configuration file into an options object."""
Expand All @@ -161,8 +175,33 @@ def load_config(cls, opts, path=None, profile=None):
cls._load_values_into_opts(opts, values)

if profile and profile != "default":
values = config.get("profile:%s" % profile, {})
cls._load_values_into_opts(opts, values)
try:
values = config["profile:%s" % profile]
cls._load_values_into_opts(opts, values)
except KeyError:
if not cls.config_already_warned():
click.secho(
f"Warning: profile {profile} not found in config files {cls.config_files}",
fg="yellow",
)

existing_config_paths = {
path: os.path.exists(path) for path in cls.config_files
}
if not any(existing_config_paths.values()) and not cls.config_already_warned():
click.secho(
"Warning: No config files found in search paths. Tried the following:",
fg="yellow",
)
for tested_path, exists in existing_config_paths.items():
if exists:
click.secho(f"{tested_path} - file exists", fg="green")
else:
click.secho(f"{tested_path} - file does not exist", fg="yellow")
click.secho(
"You may need to run `cloudsmith login` to authenticate and create a config file.",
fg="yellow",
)

return values

Expand Down Expand Up @@ -206,6 +245,29 @@ class CredentialsReader(ConfigReader):
config_searchpath = list(_CFG_SEARCH_PATHS)
config_section_schemas = [CredentialsSchema.Default, CredentialsSchema.Profile]

@classmethod
def load_config(cls, opts, path=None, profile=None):
"""
Load a credentials configuration file into an options object.
We overload the load_config command in CredentialsReader as
credentials files have their own specific default functionality.
"""
if path and os.path.exists(path):
if os.path.isdir(path):
cls.config_searchpath.insert(0, path)
else:
cls.config_files.insert(0, path)

config = cls.read_config()
values = config.get("default", {})
cls._load_values_into_opts(opts, values)

if profile and profile != "default":
values = config.get("profile:%s" % profile, {})
cls._load_values_into_opts(opts, values)

return values


class Options:
"""Options object that holds config for the application."""
Expand Down
38 changes: 38 additions & 0 deletions cloudsmith_cli/cli/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import click

from ..core.api.init import initialise_api as _initialise_api
from ..core.api.user import get_user_brief
from . import config, utils, validators


Expand Down Expand Up @@ -309,3 +310,40 @@ def call_print_rate_limit_info_with_opts(rate_info):
return ctx.invoke(f, *args, **kwargs)

return wrapper


def verify_authenticated(f):
"""Verify that the user is authenticated and warn if not."""

@click.option(
"--no-warn",
default=False,
is_flag=True,
help="Don't warn on misconfiguration issues",
)
@click.pass_context
@functools.wraps(f)
def wrapper(ctx, *args, **kwargs):
cloudsmith_host = kwargs["opts"].opts["api_config"].host
print(kwargs["opts"].opts["api_config"].__dict__)
no_warn = kwargs.pop("no_warn")
is_auth, _, _, _ = get_user_brief()
if not is_auth and not no_warn:
click.secho(
"Warning: You are not authenticated with the API. "
"Please verify your config files, API key and "
"run `cloudsmith login` if necessary to authenticate.",
fg="yellow",
)
click.secho(
f"You're currently attempting to connect to Cloudsmith instance {cloudsmith_host}",
fg="yellow",
)
no_warn = True

opts = config.get_or_create_options(ctx)
opts.no_warn = no_warn
kwargs["opts"] = opts
return ctx.invoke(f, *args, **kwargs)

return wrapper