cloudsmith-cli 1.20.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cloudsmith_cli/__init__.py +10 -0
- cloudsmith_cli/__main__.py +8 -0
- cloudsmith_cli/cli/__init__.py +1 -0
- cloudsmith_cli/cli/command.py +160 -0
- cloudsmith_cli/cli/commands/__init__.py +33 -0
- cloudsmith_cli/cli/commands/auth.py +173 -0
- cloudsmith_cli/cli/commands/check.py +129 -0
- cloudsmith_cli/cli/commands/copy.py +98 -0
- cloudsmith_cli/cli/commands/credential_helper/__init__.py +39 -0
- cloudsmith_cli/cli/commands/credential_helper/docker.py +66 -0
- cloudsmith_cli/cli/commands/credential_helper/manage.py +299 -0
- cloudsmith_cli/cli/commands/delete.py +67 -0
- cloudsmith_cli/cli/commands/dependencies.py +108 -0
- cloudsmith_cli/cli/commands/docs.py +16 -0
- cloudsmith_cli/cli/commands/download.py +620 -0
- cloudsmith_cli/cli/commands/entitlements.py +819 -0
- cloudsmith_cli/cli/commands/help_.py +12 -0
- cloudsmith_cli/cli/commands/list_.py +317 -0
- cloudsmith_cli/cli/commands/login.py +99 -0
- cloudsmith_cli/cli/commands/logout.py +151 -0
- cloudsmith_cli/cli/commands/main.py +73 -0
- cloudsmith_cli/cli/commands/mcp.py +523 -0
- cloudsmith_cli/cli/commands/metadata.py +503 -0
- cloudsmith_cli/cli/commands/metrics/__init__.py +2 -0
- cloudsmith_cli/cli/commands/metrics/command.py +20 -0
- cloudsmith_cli/cli/commands/metrics/entitlements.py +148 -0
- cloudsmith_cli/cli/commands/metrics/packages.py +134 -0
- cloudsmith_cli/cli/commands/move.py +111 -0
- cloudsmith_cli/cli/commands/policy/__init__.py +3 -0
- cloudsmith_cli/cli/commands/policy/command.py +20 -0
- cloudsmith_cli/cli/commands/policy/deny.py +248 -0
- cloudsmith_cli/cli/commands/policy/license.py +335 -0
- cloudsmith_cli/cli/commands/policy/vulnerability.py +322 -0
- cloudsmith_cli/cli/commands/push.py +1323 -0
- cloudsmith_cli/cli/commands/quarantine.py +148 -0
- cloudsmith_cli/cli/commands/quota/__init__.py +2 -0
- cloudsmith_cli/cli/commands/quota/command.py +20 -0
- cloudsmith_cli/cli/commands/quota/history.py +122 -0
- cloudsmith_cli/cli/commands/quota/quota.py +107 -0
- cloudsmith_cli/cli/commands/repos.py +330 -0
- cloudsmith_cli/cli/commands/resync.py +90 -0
- cloudsmith_cli/cli/commands/status.py +92 -0
- cloudsmith_cli/cli/commands/tags.py +375 -0
- cloudsmith_cli/cli/commands/tokens.py +318 -0
- cloudsmith_cli/cli/commands/upstream.py +479 -0
- cloudsmith_cli/cli/commands/vulnerabilities.py +141 -0
- cloudsmith_cli/cli/commands/whoami.py +188 -0
- cloudsmith_cli/cli/config.py +635 -0
- cloudsmith_cli/cli/decorators.py +624 -0
- cloudsmith_cli/cli/exceptions.py +215 -0
- cloudsmith_cli/cli/metadata_common.py +146 -0
- cloudsmith_cli/cli/saml.py +109 -0
- cloudsmith_cli/cli/table.py +59 -0
- cloudsmith_cli/cli/types.py +14 -0
- cloudsmith_cli/cli/utils.py +267 -0
- cloudsmith_cli/cli/validators.py +378 -0
- cloudsmith_cli/cli/webserver.py +263 -0
- cloudsmith_cli/core/__init__.py +1 -0
- cloudsmith_cli/core/api/__init__.py +1 -0
- cloudsmith_cli/core/api/distros.py +31 -0
- cloudsmith_cli/core/api/entitlements.py +130 -0
- cloudsmith_cli/core/api/exceptions.py +57 -0
- cloudsmith_cli/core/api/files.py +131 -0
- cloudsmith_cli/core/api/init.py +109 -0
- cloudsmith_cli/core/api/metadata.py +217 -0
- cloudsmith_cli/core/api/metrics.py +78 -0
- cloudsmith_cli/core/api/orgs.py +201 -0
- cloudsmith_cli/core/api/packages.py +309 -0
- cloudsmith_cli/core/api/quota.py +64 -0
- cloudsmith_cli/core/api/rates.py +28 -0
- cloudsmith_cli/core/api/repos.py +81 -0
- cloudsmith_cli/core/api/status.py +27 -0
- cloudsmith_cli/core/api/upstreams.py +72 -0
- cloudsmith_cli/core/api/user.py +109 -0
- cloudsmith_cli/core/api/version.py +15 -0
- cloudsmith_cli/core/api/vulnerabilities.py +230 -0
- cloudsmith_cli/core/cache_utils.py +160 -0
- cloudsmith_cli/core/config.py +140 -0
- cloudsmith_cli/core/credentials/__init__.py +0 -0
- cloudsmith_cli/core/credentials/chain.py +69 -0
- cloudsmith_cli/core/credentials/models.py +44 -0
- cloudsmith_cli/core/credentials/oidc/__init__.py +6 -0
- cloudsmith_cli/core/credentials/oidc/cache.py +220 -0
- cloudsmith_cli/core/credentials/oidc/detectors/__init__.py +122 -0
- cloudsmith_cli/core/credentials/oidc/detectors/aws.py +85 -0
- cloudsmith_cli/core/credentials/oidc/detectors/azure_devops.py +70 -0
- cloudsmith_cli/core/credentials/oidc/detectors/base.py +26 -0
- cloudsmith_cli/core/credentials/oidc/detectors/bitbucket_pipelines.py +35 -0
- cloudsmith_cli/core/credentials/oidc/detectors/circleci.py +40 -0
- cloudsmith_cli/core/credentials/oidc/detectors/generic.py +42 -0
- cloudsmith_cli/core/credentials/oidc/detectors/github_actions.py +64 -0
- cloudsmith_cli/core/credentials/oidc/detectors/gitlab_ci.py +49 -0
- cloudsmith_cli/core/credentials/oidc/exchange.py +87 -0
- cloudsmith_cli/core/credentials/provider.py +17 -0
- cloudsmith_cli/core/credentials/providers/__init__.py +15 -0
- cloudsmith_cli/core/credentials/providers/cli_flag.py +24 -0
- cloudsmith_cli/core/credentials/providers/credentials_file.py +24 -0
- cloudsmith_cli/core/credentials/providers/env_var.py +24 -0
- cloudsmith_cli/core/credentials/providers/keyring_provider.py +59 -0
- cloudsmith_cli/core/credentials/providers/oidc_provider.py +115 -0
- cloudsmith_cli/core/download.py +594 -0
- cloudsmith_cli/core/keyring.py +171 -0
- cloudsmith_cli/core/mcp/__init__.py +0 -0
- cloudsmith_cli/core/mcp/data.py +17 -0
- cloudsmith_cli/core/mcp/server.py +786 -0
- cloudsmith_cli/core/pagination.py +131 -0
- cloudsmith_cli/core/ratelimits.py +88 -0
- cloudsmith_cli/core/rest.py +255 -0
- cloudsmith_cli/core/utils.py +95 -0
- cloudsmith_cli/core/version.py +20 -0
- cloudsmith_cli/credential_helpers/__init__.py +7 -0
- cloudsmith_cli/credential_helpers/backends.py +41 -0
- cloudsmith_cli/credential_helpers/common.py +111 -0
- cloudsmith_cli/credential_helpers/custom_domains.py +281 -0
- cloudsmith_cli/credential_helpers/docker/__init__.py +4 -0
- cloudsmith_cli/credential_helpers/docker/installer.py +349 -0
- cloudsmith_cli/credential_helpers/docker/runtime.py +117 -0
- cloudsmith_cli/credential_helpers/launchers.py +175 -0
- cloudsmith_cli/data/VERSION +1 -0
- cloudsmith_cli/data/config.ini +23 -0
- cloudsmith_cli/data/credentials.ini +14 -0
- cloudsmith_cli/templates/__init__.py +3 -0
- cloudsmith_cli/templates/auth_error.html +45 -0
- cloudsmith_cli/templates/auth_success.html +37 -0
- cloudsmith_cli-1.20.0.dist-info/METADATA +610 -0
- cloudsmith_cli-1.20.0.dist-info/RECORD +130 -0
- cloudsmith_cli-1.20.0.dist-info/WHEEL +5 -0
- cloudsmith_cli-1.20.0.dist-info/entry_points.txt +2 -0
- cloudsmith_cli-1.20.0.dist-info/licenses/LICENSE +201 -0
- cloudsmith_cli-1.20.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,635 @@
|
|
|
1
|
+
"""CLI - Configuration."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
import threading
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
from click_configfile import ConfigFileReader, Param, SectionSchema, matches_section
|
|
9
|
+
|
|
10
|
+
from ..core.utils import get_data_path, read_file
|
|
11
|
+
from . import utils, validators
|
|
12
|
+
|
|
13
|
+
OPTIONS = threading.local()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ConfigParam(Param):
|
|
17
|
+
# For compatibility with click>=7.0
|
|
18
|
+
def __init__(self, *args, **kwargs):
|
|
19
|
+
super().__init__(*args, **kwargs)
|
|
20
|
+
self.ctx = None
|
|
21
|
+
|
|
22
|
+
def parse(self, text):
|
|
23
|
+
if text:
|
|
24
|
+
text = text.strip()
|
|
25
|
+
if self.type.name == "boolean":
|
|
26
|
+
if not text:
|
|
27
|
+
return None
|
|
28
|
+
return super().parse(text)
|
|
29
|
+
|
|
30
|
+
def get_error_hint(self, ctx):
|
|
31
|
+
if self.ctx:
|
|
32
|
+
files = []
|
|
33
|
+
for path in self.ctx.config_searchpath:
|
|
34
|
+
for filename in self.ctx.config_files:
|
|
35
|
+
files.append(os.path.join(path, filename))
|
|
36
|
+
files = " or ".join(files)
|
|
37
|
+
msg = f"{self.name} in {files}"
|
|
38
|
+
else:
|
|
39
|
+
msg = f"{self.name} in a config file"
|
|
40
|
+
return msg
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def get_default_config_path():
|
|
44
|
+
"""Get the default path to cloudsmith config files."""
|
|
45
|
+
return click.get_app_dir("cloudsmith") # only returns a single path
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
_CFG_SEARCH_PATHS = (
|
|
49
|
+
".",
|
|
50
|
+
get_default_config_path(),
|
|
51
|
+
os.path.expanduser("~/.cloudsmith"),
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class ConfigSchema:
|
|
56
|
+
"""Schema for standard configuration."""
|
|
57
|
+
|
|
58
|
+
@matches_section("default")
|
|
59
|
+
class Default(SectionSchema):
|
|
60
|
+
"""Default configuration schema."""
|
|
61
|
+
|
|
62
|
+
api_headers = ConfigParam(name="api_headers", type=str)
|
|
63
|
+
api_host = ConfigParam(name="api_host", type=str)
|
|
64
|
+
api_proxy = ConfigParam(name="api_proxy", type=str)
|
|
65
|
+
api_ssl_verify = ConfigParam(name="api_ssl_verify", type=bool, default=True)
|
|
66
|
+
api_user_agent = ConfigParam(name="api_user_agent", type=str)
|
|
67
|
+
mcp_allowed_tools = ConfigParam(name="mcp_allowed_tools", type=str)
|
|
68
|
+
mcp_allowed_tool_groups = ConfigParam(name="mcp_allowed_tool_groups", type=str)
|
|
69
|
+
oidc_audience = ConfigParam(name="oidc_audience", type=str)
|
|
70
|
+
oidc_org = ConfigParam(name="oidc_org", type=str)
|
|
71
|
+
oidc_service_slug = ConfigParam(name="oidc_service_slug", type=str)
|
|
72
|
+
oidc_detector_order = ConfigParam(name="oidc_detector_order", type=str)
|
|
73
|
+
oidc_disabled_detectors = ConfigParam(name="oidc_disabled_detectors", type=str)
|
|
74
|
+
metadata_failure_mode = ConfigParam(name="metadata_failure_mode", type=str)
|
|
75
|
+
|
|
76
|
+
@matches_section("profile:*")
|
|
77
|
+
class Profile(Default):
|
|
78
|
+
"""Profile-specific configuration schema."""
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class ConfigReader(ConfigFileReader):
|
|
82
|
+
"""Reader for standard configuration."""
|
|
83
|
+
|
|
84
|
+
config_files = ["config.ini"]
|
|
85
|
+
config_name = "standard"
|
|
86
|
+
config_searchpath = list(_CFG_SEARCH_PATHS)
|
|
87
|
+
config_section_schemas = [ConfigSchema.Default, ConfigSchema.Profile]
|
|
88
|
+
|
|
89
|
+
@classmethod
|
|
90
|
+
def select_config_schema_for(cls, section_name):
|
|
91
|
+
section_schema = super().select_config_schema_for(section_name)
|
|
92
|
+
for v in section_schema.__dict__.values():
|
|
93
|
+
if isinstance(v, ConfigParam):
|
|
94
|
+
v.ctx = cls
|
|
95
|
+
return section_schema
|
|
96
|
+
|
|
97
|
+
@classmethod
|
|
98
|
+
def get_storage_name_for(cls, section_name):
|
|
99
|
+
"""Get storage name for a configuration section."""
|
|
100
|
+
if not section_name or section_name == "default":
|
|
101
|
+
return "default"
|
|
102
|
+
return section_name
|
|
103
|
+
|
|
104
|
+
@classmethod
|
|
105
|
+
def get_default_filepath(cls):
|
|
106
|
+
"""Get the default filepath for the configuration file."""
|
|
107
|
+
if not cls.config_files:
|
|
108
|
+
return None
|
|
109
|
+
if not cls.config_searchpath:
|
|
110
|
+
return None
|
|
111
|
+
filename = cls.config_files[0]
|
|
112
|
+
filepath = cls.config_searchpath[0]
|
|
113
|
+
return os.path.join(filepath, filename)
|
|
114
|
+
|
|
115
|
+
@classmethod
|
|
116
|
+
def create_default_file(cls, data=None, mode=None):
|
|
117
|
+
"""Create a config file and override data if specified."""
|
|
118
|
+
filepath = cls.get_default_filepath()
|
|
119
|
+
if not filepath:
|
|
120
|
+
return False
|
|
121
|
+
|
|
122
|
+
filename = os.path.basename(filepath)
|
|
123
|
+
config = read_file(get_data_path(), filename)
|
|
124
|
+
|
|
125
|
+
# Find and replace data in default config
|
|
126
|
+
data = data or {}
|
|
127
|
+
for k, v in data.items():
|
|
128
|
+
v = v or ""
|
|
129
|
+
config = re.sub(
|
|
130
|
+
rf"^({k})\s*=\s*$",
|
|
131
|
+
f"{k} = {v}",
|
|
132
|
+
config,
|
|
133
|
+
flags=re.MULTILINE,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
dirpath = os.path.dirname(filepath)
|
|
137
|
+
if not os.path.exists(dirpath):
|
|
138
|
+
os.makedirs(dirpath)
|
|
139
|
+
with click.open_file(filepath, "w+") as f:
|
|
140
|
+
f.write(config)
|
|
141
|
+
|
|
142
|
+
if mode is not None:
|
|
143
|
+
os.chmod(filepath, mode)
|
|
144
|
+
|
|
145
|
+
return True
|
|
146
|
+
|
|
147
|
+
@classmethod
|
|
148
|
+
def has_default_file(cls):
|
|
149
|
+
"""Check if a configuration file exists."""
|
|
150
|
+
for filename in cls.config_files:
|
|
151
|
+
for searchpath in cls.config_searchpath:
|
|
152
|
+
path = os.path.join(searchpath, filename)
|
|
153
|
+
if os.path.exists(path):
|
|
154
|
+
return True
|
|
155
|
+
|
|
156
|
+
return False
|
|
157
|
+
|
|
158
|
+
@classmethod
|
|
159
|
+
def read_relative_config_value(cls, key, profile=None):
|
|
160
|
+
"""Read `key` as loaded from directory-relative (untrusted) configs only.
|
|
161
|
+
|
|
162
|
+
Loads via the normal machinery but through a reader narrowed to the
|
|
163
|
+
relative search paths/files (e.g. the current working directory);
|
|
164
|
+
absolute/user-level paths and any explicit ``--config-file`` are
|
|
165
|
+
excluded. The value is parsed and quote-stripped exactly as a real run
|
|
166
|
+
would. Returns None when the key is absent or empty.
|
|
167
|
+
"""
|
|
168
|
+
relative_searchpath = [p for p in cls.config_searchpath if not os.path.isabs(p)]
|
|
169
|
+
relative_files = [f for f in cls.config_files if not os.path.isabs(f)]
|
|
170
|
+
if not relative_searchpath or not relative_files:
|
|
171
|
+
return None
|
|
172
|
+
|
|
173
|
+
relative_reader = type(
|
|
174
|
+
"RelativeConfigReader",
|
|
175
|
+
(cls,),
|
|
176
|
+
{"config_searchpath": relative_searchpath, "config_files": relative_files},
|
|
177
|
+
)
|
|
178
|
+
probe = Options()
|
|
179
|
+
relative_reader.load_config(probe, profile=profile)
|
|
180
|
+
return probe.opts.get(key)
|
|
181
|
+
|
|
182
|
+
@classmethod
|
|
183
|
+
def load_config(cls, opts, path=None, profile=None):
|
|
184
|
+
"""Load a configuration file into an options object."""
|
|
185
|
+
if path and os.path.exists(path):
|
|
186
|
+
if os.path.isdir(path):
|
|
187
|
+
cls.config_searchpath.insert(0, path)
|
|
188
|
+
else:
|
|
189
|
+
cls.config_files.insert(0, path)
|
|
190
|
+
|
|
191
|
+
config = cls.read_config()
|
|
192
|
+
values = config.get("default", {})
|
|
193
|
+
cls._load_values_into_opts(opts, values)
|
|
194
|
+
|
|
195
|
+
if profile and profile != "default":
|
|
196
|
+
values = config.get("profile:%s" % profile, {})
|
|
197
|
+
cls._load_values_into_opts(opts, values)
|
|
198
|
+
|
|
199
|
+
return values
|
|
200
|
+
|
|
201
|
+
@staticmethod
|
|
202
|
+
def _load_values_into_opts(opts, values):
|
|
203
|
+
for k, v in values.items():
|
|
204
|
+
if v is None:
|
|
205
|
+
continue
|
|
206
|
+
if isinstance(v, str):
|
|
207
|
+
if v.startswith('"') or v.startswith("'"):
|
|
208
|
+
v = v[1:]
|
|
209
|
+
if v.endswith('"') or v.endswith("'"):
|
|
210
|
+
v = v[:-1]
|
|
211
|
+
if not v:
|
|
212
|
+
continue
|
|
213
|
+
else:
|
|
214
|
+
if v is None:
|
|
215
|
+
continue
|
|
216
|
+
setattr(opts, k, v)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
class CredentialsSchema:
|
|
220
|
+
"""Schema for credentials configuration."""
|
|
221
|
+
|
|
222
|
+
@matches_section("default")
|
|
223
|
+
class Default(SectionSchema):
|
|
224
|
+
"""Default configuration schema."""
|
|
225
|
+
|
|
226
|
+
api_key = ConfigParam(name="api_key", type=str)
|
|
227
|
+
|
|
228
|
+
@matches_section("profile:*")
|
|
229
|
+
class Profile(Default):
|
|
230
|
+
"""Profile-specific configuration schema."""
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class CredentialsReader(ConfigReader):
|
|
234
|
+
"""Reader for credentials configuration."""
|
|
235
|
+
|
|
236
|
+
config_files = ["credentials.ini"]
|
|
237
|
+
config_name = "credentials"
|
|
238
|
+
config_searchpath = list(_CFG_SEARCH_PATHS)
|
|
239
|
+
config_section_schemas = [CredentialsSchema.Default, CredentialsSchema.Profile]
|
|
240
|
+
|
|
241
|
+
@classmethod
|
|
242
|
+
def find_existing_files(cls):
|
|
243
|
+
"""Return a list of existing credentials file paths."""
|
|
244
|
+
paths = []
|
|
245
|
+
seen = set()
|
|
246
|
+
for filename in cls.config_files:
|
|
247
|
+
for searchpath in cls.config_searchpath:
|
|
248
|
+
path = os.path.join(searchpath, filename)
|
|
249
|
+
if os.path.exists(path) and path not in seen:
|
|
250
|
+
paths.append(path)
|
|
251
|
+
seen.add(path)
|
|
252
|
+
return paths
|
|
253
|
+
|
|
254
|
+
@classmethod
|
|
255
|
+
def _set_api_key(cls, path, api_key=""):
|
|
256
|
+
"""Write api_key value in a credentials file, preserving structure."""
|
|
257
|
+
with open(path) as f:
|
|
258
|
+
content = f.read()
|
|
259
|
+
replacement = rf"\1 = {api_key}" if api_key else r"\1 ="
|
|
260
|
+
content = re.sub(
|
|
261
|
+
r"^(api_key)\s*=\s*.*$",
|
|
262
|
+
replacement,
|
|
263
|
+
content,
|
|
264
|
+
flags=re.MULTILINE,
|
|
265
|
+
)
|
|
266
|
+
with open(path, "w") as f:
|
|
267
|
+
f.write(content)
|
|
268
|
+
|
|
269
|
+
@classmethod
|
|
270
|
+
def clear_api_key(cls, path):
|
|
271
|
+
"""Clear api_key values in a credentials file, preserving structure."""
|
|
272
|
+
cls._set_api_key(path)
|
|
273
|
+
|
|
274
|
+
@classmethod
|
|
275
|
+
def update_api_key(cls, path, api_key):
|
|
276
|
+
"""Update api_key value in an existing credentials file, preserving structure."""
|
|
277
|
+
cls._set_api_key(path, api_key)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
class Options: # pylint: disable=too-many-public-methods
|
|
281
|
+
"""Options object that holds config for the application."""
|
|
282
|
+
|
|
283
|
+
def __init__(self, *args, **kwargs):
|
|
284
|
+
"""Initialise a new Options object."""
|
|
285
|
+
super().__init__(*args, **kwargs)
|
|
286
|
+
self.opts = {}
|
|
287
|
+
for k, v in kwargs.items():
|
|
288
|
+
setattr(self, k, v)
|
|
289
|
+
|
|
290
|
+
@staticmethod
|
|
291
|
+
def get_config_reader():
|
|
292
|
+
"""Get the non-credentials config reader class."""
|
|
293
|
+
return ConfigReader
|
|
294
|
+
|
|
295
|
+
@staticmethod
|
|
296
|
+
def get_creds_reader():
|
|
297
|
+
"""Get the credentials config reader class."""
|
|
298
|
+
return CredentialsReader
|
|
299
|
+
|
|
300
|
+
def load_config_file(self, path, profile=None):
|
|
301
|
+
"""Load the standard config file."""
|
|
302
|
+
config_cls = self.get_config_reader()
|
|
303
|
+
return config_cls.load_config(self, path, profile=profile)
|
|
304
|
+
|
|
305
|
+
def load_creds_file(self, path, profile=None):
|
|
306
|
+
"""Load the credentials config file."""
|
|
307
|
+
config_cls = self.get_creds_reader()
|
|
308
|
+
return config_cls.load_config(self, path, profile=profile)
|
|
309
|
+
|
|
310
|
+
@property
|
|
311
|
+
def api_config(self):
|
|
312
|
+
"""Get value for API config dictionary."""
|
|
313
|
+
return self._get_option("api_config")
|
|
314
|
+
|
|
315
|
+
@api_config.setter
|
|
316
|
+
def api_config(self, value):
|
|
317
|
+
"""Set value for API config dictionary."""
|
|
318
|
+
self._set_option("api_config", value)
|
|
319
|
+
|
|
320
|
+
@property
|
|
321
|
+
def api_headers(self):
|
|
322
|
+
"""Get value for API headers."""
|
|
323
|
+
return self._get_option("api_headers")
|
|
324
|
+
|
|
325
|
+
@api_headers.setter
|
|
326
|
+
def api_headers(self, value):
|
|
327
|
+
"""Set value for API headers."""
|
|
328
|
+
value = validators.validate_api_headers("api_headers", value)
|
|
329
|
+
self._set_option("api_headers", value)
|
|
330
|
+
|
|
331
|
+
@property
|
|
332
|
+
def api_host(self):
|
|
333
|
+
"""Get value for API host."""
|
|
334
|
+
return self._get_option("api_host")
|
|
335
|
+
|
|
336
|
+
@api_host.setter
|
|
337
|
+
def api_host(self, value):
|
|
338
|
+
"""Set value for API host."""
|
|
339
|
+
self._set_option("api_host", value)
|
|
340
|
+
|
|
341
|
+
@property
|
|
342
|
+
def api_key(self):
|
|
343
|
+
"""Get value for API key."""
|
|
344
|
+
return self._get_option("api_key")
|
|
345
|
+
|
|
346
|
+
@api_key.setter
|
|
347
|
+
def api_key(self, value):
|
|
348
|
+
"""Set value for API key."""
|
|
349
|
+
self._set_option("api_key", value)
|
|
350
|
+
|
|
351
|
+
@property
|
|
352
|
+
def api_key_from_flag(self):
|
|
353
|
+
"""Get API key set explicitly via --api-key CLI flag."""
|
|
354
|
+
return self._get_option("api_key_from_flag")
|
|
355
|
+
|
|
356
|
+
@api_key_from_flag.setter
|
|
357
|
+
def api_key_from_flag(self, value):
|
|
358
|
+
self._set_option("api_key_from_flag", value, allow_clear=True)
|
|
359
|
+
|
|
360
|
+
@property
|
|
361
|
+
def api_key_from_env(self):
|
|
362
|
+
"""Get API key from CLOUDSMITH_API_KEY environment variable."""
|
|
363
|
+
return self._get_option("api_key_from_env")
|
|
364
|
+
|
|
365
|
+
@api_key_from_env.setter
|
|
366
|
+
def api_key_from_env(self, value):
|
|
367
|
+
self._set_option("api_key_from_env", value, allow_clear=True)
|
|
368
|
+
|
|
369
|
+
@property
|
|
370
|
+
def api_key_from_file(self):
|
|
371
|
+
"""Get API key loaded from credentials.ini."""
|
|
372
|
+
return self._get_option("api_key_from_file")
|
|
373
|
+
|
|
374
|
+
@api_key_from_file.setter
|
|
375
|
+
def api_key_from_file(self, value):
|
|
376
|
+
self._set_option("api_key_from_file", value, allow_clear=True)
|
|
377
|
+
|
|
378
|
+
@property
|
|
379
|
+
def api_proxy(self):
|
|
380
|
+
"""Get value for API proxy."""
|
|
381
|
+
return self._get_option("api_proxy")
|
|
382
|
+
|
|
383
|
+
@api_proxy.setter
|
|
384
|
+
def api_proxy(self, value):
|
|
385
|
+
"""Set value for API proxy."""
|
|
386
|
+
self._set_option("api_proxy", value)
|
|
387
|
+
|
|
388
|
+
@property
|
|
389
|
+
def api_ssl_verify(self):
|
|
390
|
+
"""Get value for API SSL verify."""
|
|
391
|
+
return self._get_option("api_ssl_verify", default=True)
|
|
392
|
+
|
|
393
|
+
@api_ssl_verify.setter
|
|
394
|
+
def api_ssl_verify(self, value):
|
|
395
|
+
"""Set value for API SSL verify."""
|
|
396
|
+
self._set_option("api_ssl_verify", value, allow_clear=False)
|
|
397
|
+
|
|
398
|
+
@property
|
|
399
|
+
def api_user_agent(self):
|
|
400
|
+
"""Get value for API user agent."""
|
|
401
|
+
return utils.make_user_agent(prefix=self._get_option("api_user_agent"))
|
|
402
|
+
|
|
403
|
+
@api_user_agent.setter
|
|
404
|
+
def api_user_agent(self, value):
|
|
405
|
+
"""Set value for API user agent."""
|
|
406
|
+
self._set_option("api_user_agent", value)
|
|
407
|
+
|
|
408
|
+
@property
|
|
409
|
+
def rate_limit(self):
|
|
410
|
+
"""Get value for rate limiting."""
|
|
411
|
+
return self._get_option("rate_limit", default=True)
|
|
412
|
+
|
|
413
|
+
@rate_limit.setter
|
|
414
|
+
def rate_limit(self, value):
|
|
415
|
+
"""Set value for rate limiting."""
|
|
416
|
+
self._set_option("rate_limit", value)
|
|
417
|
+
|
|
418
|
+
@property
|
|
419
|
+
def rate_limit_warning(self):
|
|
420
|
+
"""Get value for rate limiting warning (in seconds)."""
|
|
421
|
+
return self._get_option("rate_limit_warning", default=10)
|
|
422
|
+
|
|
423
|
+
@rate_limit_warning.setter
|
|
424
|
+
def rate_limit_warning(self, value):
|
|
425
|
+
"""Set value for rate limiting warning (in seconds)."""
|
|
426
|
+
self._set_option("rate_limit_warning", value)
|
|
427
|
+
|
|
428
|
+
@property
|
|
429
|
+
def always_show_rate_limit(self):
|
|
430
|
+
"""Get value for rate limiting warning (in seconds)."""
|
|
431
|
+
return self._get_option("always_show_rate_limit", default=False)
|
|
432
|
+
|
|
433
|
+
@always_show_rate_limit.setter
|
|
434
|
+
def always_show_rate_limit(self, value):
|
|
435
|
+
"""Set value for rate limiting warning (in seconds)."""
|
|
436
|
+
self._set_option("always_show_rate_limit", value)
|
|
437
|
+
|
|
438
|
+
@property
|
|
439
|
+
def debug(self):
|
|
440
|
+
"""Get value for debug flag."""
|
|
441
|
+
return self._get_option("debug", default=False)
|
|
442
|
+
|
|
443
|
+
@debug.setter
|
|
444
|
+
def debug(self, value):
|
|
445
|
+
"""Set value for debug flag."""
|
|
446
|
+
self._set_option("debug", bool(value))
|
|
447
|
+
|
|
448
|
+
@property
|
|
449
|
+
def mcp_allowed_tools(self):
|
|
450
|
+
"""Get value for Allowed MCP Tools."""
|
|
451
|
+
return self._get_option("mcp_allowed_tools")
|
|
452
|
+
|
|
453
|
+
@mcp_allowed_tools.setter
|
|
454
|
+
def mcp_allowed_tools(self, value):
|
|
455
|
+
"""Set value for Allowed MCP Tools."""
|
|
456
|
+
if not value:
|
|
457
|
+
return
|
|
458
|
+
tools = [tool.strip() for tool in value.split(",")]
|
|
459
|
+
|
|
460
|
+
self._set_option("mcp_allowed_tools", tools)
|
|
461
|
+
|
|
462
|
+
@property
|
|
463
|
+
def mcp_allowed_tool_groups(self):
|
|
464
|
+
"""Get value for Allowed MCP Tool Groups."""
|
|
465
|
+
return self._get_option("mcp_allowed_tool_groups")
|
|
466
|
+
|
|
467
|
+
@mcp_allowed_tool_groups.setter
|
|
468
|
+
def mcp_allowed_tool_groups(self, value):
|
|
469
|
+
"""Set value for Allowed MCP Tool Groups."""
|
|
470
|
+
if not value:
|
|
471
|
+
return
|
|
472
|
+
tool_groups = [group.strip() for group in value.split(",")]
|
|
473
|
+
|
|
474
|
+
self._set_option("mcp_allowed_tool_groups", tool_groups)
|
|
475
|
+
|
|
476
|
+
@property
|
|
477
|
+
def oidc_audience(self):
|
|
478
|
+
"""Get value for OIDC audience."""
|
|
479
|
+
return self._get_option("oidc_audience")
|
|
480
|
+
|
|
481
|
+
@oidc_audience.setter
|
|
482
|
+
def oidc_audience(self, value):
|
|
483
|
+
"""Set value for OIDC audience."""
|
|
484
|
+
self._set_option("oidc_audience", value)
|
|
485
|
+
|
|
486
|
+
@property
|
|
487
|
+
def oidc_org(self):
|
|
488
|
+
"""Get value for OIDC organisation slug."""
|
|
489
|
+
return self._get_option("oidc_org")
|
|
490
|
+
|
|
491
|
+
@oidc_org.setter
|
|
492
|
+
def oidc_org(self, value):
|
|
493
|
+
"""Set value for OIDC organisation slug."""
|
|
494
|
+
self._set_option("oidc_org", value)
|
|
495
|
+
|
|
496
|
+
@property
|
|
497
|
+
def oidc_service_slug(self):
|
|
498
|
+
"""Get value for OIDC service slug."""
|
|
499
|
+
return self._get_option("oidc_service_slug")
|
|
500
|
+
|
|
501
|
+
@oidc_service_slug.setter
|
|
502
|
+
def oidc_service_slug(self, value):
|
|
503
|
+
"""Set value for OIDC service slug."""
|
|
504
|
+
self._set_option("oidc_service_slug", value)
|
|
505
|
+
|
|
506
|
+
@property
|
|
507
|
+
def oidc_discovery_disabled(self):
|
|
508
|
+
"""Get value for OIDC discovery disabled flag."""
|
|
509
|
+
return self._get_option("oidc_discovery_disabled", default=False)
|
|
510
|
+
|
|
511
|
+
@oidc_discovery_disabled.setter
|
|
512
|
+
def oidc_discovery_disabled(self, value):
|
|
513
|
+
"""Set value for OIDC discovery disabled flag."""
|
|
514
|
+
self._set_option(
|
|
515
|
+
"oidc_discovery_disabled", bool(value) if value is not None else False
|
|
516
|
+
)
|
|
517
|
+
|
|
518
|
+
@property
|
|
519
|
+
def oidc_detector_order(self):
|
|
520
|
+
"""Get value for the OIDC detector evaluation order."""
|
|
521
|
+
return self._get_option("oidc_detector_order")
|
|
522
|
+
|
|
523
|
+
@oidc_detector_order.setter
|
|
524
|
+
def oidc_detector_order(self, value):
|
|
525
|
+
"""Set value for the OIDC detector evaluation order."""
|
|
526
|
+
self._set_option("oidc_detector_order", value)
|
|
527
|
+
|
|
528
|
+
@property
|
|
529
|
+
def oidc_disabled_detectors(self):
|
|
530
|
+
"""Get the comma-separated OIDC detector ids disabled via config."""
|
|
531
|
+
return self._get_option("oidc_disabled_detectors")
|
|
532
|
+
|
|
533
|
+
@oidc_disabled_detectors.setter
|
|
534
|
+
def oidc_disabled_detectors(self, value):
|
|
535
|
+
"""Set the comma-separated OIDC detector ids disabled via config."""
|
|
536
|
+
self._set_option("oidc_disabled_detectors", value)
|
|
537
|
+
|
|
538
|
+
@property
|
|
539
|
+
def metadata_failure_mode(self):
|
|
540
|
+
"""Get value for push-time metadata failure mode."""
|
|
541
|
+
return self._get_option("metadata_failure_mode")
|
|
542
|
+
|
|
543
|
+
@metadata_failure_mode.setter
|
|
544
|
+
def metadata_failure_mode(self, value):
|
|
545
|
+
"""Set value for push-time metadata failure mode."""
|
|
546
|
+
if value is None:
|
|
547
|
+
return
|
|
548
|
+
normalised = str(value).strip().lower()
|
|
549
|
+
if normalised not in {"error", "warn", "0"}:
|
|
550
|
+
raise click.UsageError(
|
|
551
|
+
f"Invalid metadata_failure_mode {value!r}. "
|
|
552
|
+
"Expected one of: 'error', 'warn', '0'."
|
|
553
|
+
)
|
|
554
|
+
self._set_option("metadata_failure_mode", normalised)
|
|
555
|
+
|
|
556
|
+
@property
|
|
557
|
+
def output(self):
|
|
558
|
+
"""Get value for output format."""
|
|
559
|
+
return self._get_option("output")
|
|
560
|
+
|
|
561
|
+
@output.setter
|
|
562
|
+
def output(self, value):
|
|
563
|
+
"""Set value for output format."""
|
|
564
|
+
self._set_option("output", value)
|
|
565
|
+
|
|
566
|
+
@property
|
|
567
|
+
def verbose(self):
|
|
568
|
+
"""Get value for verbose flag."""
|
|
569
|
+
return self._get_option("verbose", default=False)
|
|
570
|
+
|
|
571
|
+
@verbose.setter
|
|
572
|
+
def verbose(self, value):
|
|
573
|
+
"""Set value for verbose flag."""
|
|
574
|
+
self._set_option("verbose", bool(value))
|
|
575
|
+
|
|
576
|
+
@property
|
|
577
|
+
def error_retry_max(self):
|
|
578
|
+
"""Get value for error_retry_max."""
|
|
579
|
+
return self._get_option("error_retry_max", default=5)
|
|
580
|
+
|
|
581
|
+
@error_retry_max.setter
|
|
582
|
+
def error_retry_max(self, value):
|
|
583
|
+
"""Set value for error_retry_max."""
|
|
584
|
+
self._set_option("error_retry_max", int(value))
|
|
585
|
+
|
|
586
|
+
@property
|
|
587
|
+
def error_retry_backoff(self):
|
|
588
|
+
"""Get value for error_retry_backoff."""
|
|
589
|
+
return self._get_option("error_retry_backoff", default=0.23)
|
|
590
|
+
|
|
591
|
+
@error_retry_backoff.setter
|
|
592
|
+
def error_retry_backoff(self, value):
|
|
593
|
+
"""Set value for error_retry_backoff."""
|
|
594
|
+
self._set_option("error_retry_backoff", float(value))
|
|
595
|
+
|
|
596
|
+
@property
|
|
597
|
+
def error_retry_codes(self):
|
|
598
|
+
"""Get value for error_retry_codes."""
|
|
599
|
+
return self._get_option("error_retry_codes", default=[500, 502, 503, 504])
|
|
600
|
+
|
|
601
|
+
@error_retry_codes.setter
|
|
602
|
+
def error_retry_codes(self, value):
|
|
603
|
+
"""Set value for error_retry_codes."""
|
|
604
|
+
if isinstance(value, str):
|
|
605
|
+
value = [int(x) for x in value.split(",")]
|
|
606
|
+
self._set_option("error_retry_codes", value)
|
|
607
|
+
|
|
608
|
+
def _get_option(self, name, default=None):
|
|
609
|
+
"""Get value for an option."""
|
|
610
|
+
value = self.opts.get(name)
|
|
611
|
+
if value is None:
|
|
612
|
+
return default
|
|
613
|
+
return value
|
|
614
|
+
|
|
615
|
+
def _set_option(self, name, value, allow_clear=False):
|
|
616
|
+
"""Set value for an option."""
|
|
617
|
+
if not allow_clear:
|
|
618
|
+
# Prevent clears if value was set
|
|
619
|
+
try:
|
|
620
|
+
current_value = self._get_option(name)
|
|
621
|
+
if value is None and current_value is not None:
|
|
622
|
+
return
|
|
623
|
+
except AttributeError:
|
|
624
|
+
pass
|
|
625
|
+
|
|
626
|
+
self.opts[name] = value
|
|
627
|
+
|
|
628
|
+
|
|
629
|
+
def get_or_create_options(ctx):
|
|
630
|
+
"""Get or create the options object."""
|
|
631
|
+
try:
|
|
632
|
+
return OPTIONS.value
|
|
633
|
+
except AttributeError:
|
|
634
|
+
OPTIONS.value = ctx.ensure_object(Options)
|
|
635
|
+
return OPTIONS.value
|