Skip to content

Commit 3e1a697

Browse files
committed
first attempt, adding warnings in place
1 parent 47eefaa commit 3e1a697

File tree

5 files changed

+133
-18
lines changed

5 files changed

+133
-18
lines changed

.envrc

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,22 @@ if [ -d ".venv" ]; then
1818
fi
1919
fi
2020

21-
python -m uv venv .venv --python $PYTHON_VERSION
22-
python -m uv pip install -U pip uv
23-
python -m uv pip install -e .
21+
if [ ! -d ".venv" ]
22+
then
23+
# System python doesn't like us installing packages into it
24+
# Test if uv is already installed (via brew or other package manager etc.)
25+
# and use that if available. Otherwise fall back to previous behvaiour
26+
if command -v uv
27+
then
28+
uv venv .venv --python $PYTHON_VERSION
29+
uv pip install -U pip uv
30+
uv pip install -e .
31+
else
32+
python -m uv venv .venv --python $PYTHON_VERSION
33+
python -m uv pip install -U pip uv
34+
python -m uv pip install -e .
35+
fi
36+
fi
2437

2538
source ./.venv/bin/activate
2639

cloudsmith_cli/cli/commands/repos.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ def get(ctx, opts, owner_repo, page, page_size):
101101

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

104+
repo = None
105+
owner = None
106+
104107
if isinstance(owner_repo, list):
105108
if len(owner_repo) == 1:
106109
owner = owner_repo[0]

cloudsmith_cli/cli/config.py

Lines changed: 83 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ class ConfigReader(ConfigFileReader):
7777
config_name = "standard"
7878
config_searchpath = list(_CFG_SEARCH_PATHS)
7979
config_section_schemas = [ConfigSchema.Default, ConfigSchema.Profile]
80+
config_warning_issued = False
8081

8182
@classmethod
8283
def select_config_schema_for(cls, section_name):
@@ -148,7 +149,20 @@ def has_default_file(cls):
148149
return False
149150

150151
@classmethod
151-
def load_config(cls, opts, path=None, profile=None):
152+
def config_already_warned(cls):
153+
"""
154+
Check if a configuration file warning has been issued.
155+
This is required as configs are gathered at the root of the
156+
command chain as well as for command verbs
157+
"""
158+
if cls.config_warning_issued:
159+
return True
160+
161+
cls.config_warning_issued = True
162+
return False
163+
164+
@classmethod
165+
def load_config(cls, opts, path=None, profile=None, no_warn=False):
152166
"""Load a configuration file into an options object."""
153167
if path and os.path.exists(path):
154168
if os.path.isdir(path):
@@ -160,9 +174,36 @@ def load_config(cls, opts, path=None, profile=None):
160174
values = config.get("default", {})
161175
cls._load_values_into_opts(opts, values)
162176

163-
if profile and profile != "default":
164-
values = config.get("profile:%s" % profile, {})
165-
cls._load_values_into_opts(opts, values)
177+
warn = not no_warn and not cls.config_already_warned()
178+
179+
if profile and profile != "default" and warn:
180+
try:
181+
values = config["profile:%s" % profile]
182+
cls._load_values_into_opts(opts, values)
183+
except KeyError:
184+
if warn:
185+
click.secho(
186+
f"Warning: profile {profile} not found in config files {cls.config_files}",
187+
fg="yellow",
188+
)
189+
190+
existing_config_paths = {
191+
path: os.path.exists(path) for path in cls.config_files
192+
}
193+
if not any(existing_config_paths.values()) and warn:
194+
click.secho(
195+
"Warning: No config files found in search paths. Tried the following:",
196+
fg="yellow",
197+
)
198+
for tested_path, exists in existing_config_paths.items():
199+
if exists:
200+
click.secho(f"{tested_path} - file exists", fg="green")
201+
else:
202+
click.secho(f"{tested_path} - file does not exist", fg="yellow")
203+
click.secho(
204+
"You may need to run `cloudsmith login` to authenticate and create a config file.",
205+
fg="yellow",
206+
)
166207

167208
return values
168209

@@ -206,7 +247,31 @@ class CredentialsReader(ConfigReader):
206247
config_searchpath = list(_CFG_SEARCH_PATHS)
207248
config_section_schemas = [CredentialsSchema.Default, CredentialsSchema.Profile]
208249

250+
@classmethod
251+
def load_config(cls, opts, path=None, profile=None, no_warn=False):
252+
"""
253+
Load a credentials configuration file into an options object.
254+
We overload the load_config command in CredentialsReader as
255+
credentials files have their own specific default functionality.
256+
"""
257+
if path and os.path.exists(path):
258+
if os.path.isdir(path):
259+
cls.config_searchpath.insert(0, path)
260+
else:
261+
cls.config_files.insert(0, path)
262+
263+
config = cls.read_config()
264+
values = config.get("default", {})
265+
cls._load_values_into_opts(opts, values)
266+
267+
if profile and profile != "default":
268+
values = config.get("profile:%s" % profile, {})
269+
cls._load_values_into_opts(opts, values)
270+
271+
return values
272+
209273

274+
# pylint: disable=too-many-public-methods
210275
class Options:
211276
"""Options object that holds config for the application."""
212277

@@ -227,15 +292,15 @@ def get_creds_reader():
227292
"""Get the credentials config reader class."""
228293
return CredentialsReader
229294

230-
def load_config_file(self, path, profile=None):
295+
def load_config_file(self, path, profile=None, no_warn=False):
231296
"""Load the standard config file."""
232297
config_cls = self.get_config_reader()
233-
return config_cls.load_config(self, path, profile=profile)
298+
return config_cls.load_config(self, path, profile=profile, no_warn=no_warn)
234299

235-
def load_creds_file(self, path, profile=None):
300+
def load_creds_file(self, path, profile=None, no_warn=False):
236301
"""Load the credentials config file."""
237302
config_cls = self.get_creds_reader()
238-
return config_cls.load_config(self, path, profile=profile)
303+
return config_cls.load_config(self, path, profile=profile, no_warn=no_warn)
239304

240305
@property
241306
def api_config(self):
@@ -268,6 +333,16 @@ def api_host(self, value):
268333
"""Set value for API host."""
269334
self._set_option("api_host", value)
270335

336+
@property
337+
def no_warn(self):
338+
"""Get value for API host."""
339+
return self._get_option("no_warn")
340+
341+
@no_warn.setter
342+
def no_warn(self, value):
343+
"""Set value for API host."""
344+
self._set_option("no_warn", value)
345+
271346
@property
272347
def api_key(self):
273348
"""Get value for API key."""

cloudsmith_cli/cli/decorators.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import click
66

77
from ..core.api.init import initialise_api as _initialise_api
8+
from ..core.api.user import get_user_brief
89
from . import config, utils, validators
910

1011

@@ -86,6 +87,12 @@ def common_cli_config_options(f):
8687
envvar="CLOUDSMITH_PROFILE",
8788
help="The name of the profile to use for configuration.",
8889
)
90+
@click.option(
91+
"--no-warn",
92+
is_flag=True,
93+
default=None,
94+
help="Don't warn on misconfiguration issues",
95+
)
8996
@click.pass_context
9097
@functools.wraps(f)
9198
def wrapper(ctx, *args, **kwargs):
@@ -94,8 +101,11 @@ def wrapper(ctx, *args, **kwargs):
94101
profile = kwargs.pop("profile")
95102
config_file = kwargs.pop("config_file")
96103
creds_file = kwargs.pop("credentials_file")
97-
opts.load_config_file(path=config_file, profile=profile)
98-
opts.load_creds_file(path=creds_file, profile=profile)
104+
no_warn = kwargs.pop("no_warn")
105+
if no_warn:
106+
opts.no_warn = no_warn
107+
opts.load_config_file(path=config_file, profile=profile, no_warn=opts.no_warn)
108+
opts.load_creds_file(path=creds_file, profile=profile, no_warn=opts.no_warn)
99109
kwargs["opts"] = opts
100110
return ctx.invoke(f, *args, **kwargs)
101111

@@ -305,6 +315,22 @@ def call_print_rate_limit_info_with_opts(rate_info):
305315
error_retry_cb=opts.error_retry_cb,
306316
)
307317

318+
cloudsmith_host = kwargs["opts"].opts["api_config"].host
319+
no_warn = opts.no_warn
320+
is_auth, _, _, _ = get_user_brief()
321+
if not is_auth and not no_warn:
322+
click.secho(
323+
"Warning: You are not authenticated with the API. "
324+
"Please verify your config files, API key and "
325+
"run `cloudsmith login` if necessary to authenticate.",
326+
fg="yellow",
327+
)
328+
click.secho(
329+
f"You're currently attempting to connect to Cloudsmith instance {cloudsmith_host}",
330+
fg="yellow",
331+
)
332+
opts.no_warn = True
333+
308334
kwargs["opts"] = opts
309335
return ctx.invoke(f, *args, **kwargs)
310336

cloudsmith_cli/cli/tests/commands/test_main.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,9 @@ def test_main_version(self, runner, option):
1111
"""Test the output of `cloudsmith --version`."""
1212
result = runner.invoke(main, [option])
1313
assert result.exit_code == 0
14-
assert (
15-
result.output == "Versions:\n"
16-
"CLI Package Version: " + get_version() + "\n"
17-
"API Package Version: " + get_api_version() + "\n"
18-
)
14+
15+
assert "CLI Package Version: " + get_version() in result.output
16+
assert "API Package Version: " + get_api_version() in result.output
1917

2018
@pytest.mark.parametrize("option", ["-h", "--help"])
2119
def test_main_help(self, runner, option):

0 commit comments

Comments
 (0)