steve-cli 0.3.0__tar.gz → 0.3.2__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: steve-cli
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: A simple CLI tool to run jobs from jobs.yaml with proper environment setup
5
5
  Author: Frank
6
6
  License: MIT
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
 
6
6
  [project]
7
7
  name = "steve-cli"
8
- version = "0.3.0"
8
+ version = "0.3.2"
9
9
  description = "A simple CLI tool to run jobs from jobs.yaml with proper environment setup"
10
10
  readme = "README.md"
11
11
  license = {text = "MIT"}
@@ -233,5 +233,100 @@ def setup_env():
233
233
  click.echo(f" 💡 Run: {click.style(f'source {out_file.name}', fg='yellow')}")
234
234
 
235
235
 
236
+ def _detect_workspaces() -> List[str]:
237
+ tiers = {"BRONZE", "SILVER", "GOLD"}
238
+ workspaces = set()
239
+ for key in os.environ:
240
+ if key.endswith("_ACCESS_KEY"):
241
+ prefix = key[: -len("_ACCESS_KEY")]
242
+ if prefix not in tiers:
243
+ workspaces.add(prefix)
244
+ return sorted(workspaces)
245
+
246
+
247
+ def _build_tree(keys: List[str]) -> Dict[str, Any]:
248
+ tree: Dict[str, Any] = {}
249
+ for key in keys:
250
+ parts = key.split("/")
251
+ node = tree
252
+ for part in parts[:-1]:
253
+ node = node.setdefault(part, {})
254
+ node[parts[-1]] = None
255
+ return tree
256
+
257
+
258
+ def _print_tree(node: Dict[str, Any], prefix: str = "", is_last: bool = True) -> None:
259
+ items = sorted(node.items())
260
+ for i, (name, child) in enumerate(items):
261
+ last = i == len(items) - 1
262
+ connector = "└── " if last else "├── "
263
+ click.echo(f"{prefix}{connector}{name}")
264
+ if child is not None:
265
+ extension = " " if last else "│ "
266
+ _print_tree(child, prefix + extension, last)
267
+
268
+
269
+ def _list_bucket(storage_kwargs: dict, label: str, bucket_name: str) -> None:
270
+ click.echo(f" {click.style(label, fg='cyan')} ({bucket_name})")
271
+ try:
272
+ from steve_cli.storage import S3Storage
273
+ storage = S3Storage(**storage_kwargs)
274
+ keys = storage.list_all()
275
+ if not keys:
276
+ click.echo(" (empty)")
277
+ else:
278
+ tree = _build_tree(keys)
279
+ _print_tree(tree, prefix=" ")
280
+ except EnvironmentError as e:
281
+ click.echo(f" ⚠️ {e}", err=True)
282
+ except Exception as e:
283
+ click.echo(f" ❌ {e}", err=True)
284
+
285
+
286
+ @main.command("buckets")
287
+ def buckets():
288
+ """List all S3 buckets detected from env variables and show their files as a tree."""
289
+ tiers = ["bronze", "silver", "gold"]
290
+
291
+ options: List[Dict[str, Any]] = []
292
+
293
+ bare_tiers = [t for t in tiers if os.getenv(f"{t.upper()}_ACCESS_KEY")]
294
+ for tier in bare_tiers:
295
+ bucket_name = os.getenv(f"{tier.upper()}_BUCKET", "")
296
+ options.append({
297
+ "label": f"default / {tier} ({bucket_name})",
298
+ "kwargs": {"tier": tier},
299
+ "bucket_name": bucket_name,
300
+ "tier": tier,
301
+ })
302
+
303
+ for ws in _detect_workspaces():
304
+ for tier in tiers:
305
+ bucket_name = os.getenv(f"{ws}_BUCKET_{tier.upper()}")
306
+ if not bucket_name:
307
+ continue
308
+ options.append({
309
+ "label": f"{ws} / {tier} ({bucket_name})",
310
+ "kwargs": {"tier": tier, "workspace": ws.lower().replace("_", "-")},
311
+ "bucket_name": bucket_name,
312
+ "tier": tier,
313
+ })
314
+
315
+ if not options:
316
+ click.echo("No bucket env variables found (expected: BRONZE_ACCESS_KEY or {WORKSPACE}_ACCESS_KEY).")
317
+ return
318
+
319
+ choice = questionary.select(
320
+ "Select a bucket to list:",
321
+ choices=[o["label"] for o in options],
322
+ ).ask()
323
+
324
+ if choice is None:
325
+ sys.exit(0)
326
+
327
+ selected = next(o for o in options if o["label"] == choice)
328
+ _list_bucket(selected["kwargs"], selected["tier"], selected["bucket_name"])
329
+
330
+
236
331
  if __name__ == '__main__':
237
332
  main()
@@ -16,24 +16,34 @@ class Storage(Protocol):
16
16
 
17
17
 
18
18
  class S3Storage:
19
- def __init__(self, tier: str = "bronze"):
19
+ def __init__(self, tier: str = "bronze", workspace: str | None = None):
20
20
  self.tier = tier.lower()
21
21
 
22
- if self.tier == "bronze":
23
- access_key = os.getenv("BRONZE_ACCESS_KEY")
24
- secret_key = os.getenv("BRONZE_SECRET_KEY")
25
- self.bucket = os.getenv("BRONZE_BUCKET")
26
- required_vars = ["BRONZE_ACCESS_KEY", "BRONZE_SECRET_KEY", "BRONZE_BUCKET"]
27
- elif self.tier == "silver":
28
- access_key = os.getenv("SILVER_ACCESS_KEY")
29
- secret_key = os.getenv("SILVER_SECRET_KEY")
30
- self.bucket = os.getenv("SILVER_BUCKET")
31
- required_vars = ["SILVER_ACCESS_KEY", "SILVER_SECRET_KEY", "SILVER_BUCKET"]
22
+ if workspace is not None:
23
+ prefix = workspace.upper().replace("-", "_")
24
+ tier_suffix = f"BUCKET_{tier.upper()}"
25
+ access_key = os.getenv(f"{prefix}_ACCESS_KEY")
26
+ secret_key = os.getenv(f"{prefix}_SECRET_KEY")
27
+ self.bucket = os.getenv(f"{prefix}_{tier_suffix}")
28
+ required_vars = [f"{prefix}_ACCESS_KEY", f"{prefix}_SECRET_KEY", f"{prefix}_{tier_suffix}"]
29
+ endpoint = os.getenv(f"{prefix}_S3_ENDPOINT", os.getenv("S3_ENDPOINT", "http://localhost:9000"))
32
30
  else:
33
- access_key = os.getenv("GOLD_ACCESS_KEY")
34
- secret_key = os.getenv("GOLD_SECRET_KEY")
35
- self.bucket = os.getenv("GOLD_BUCKET")
36
- required_vars = ["GOLD_ACCESS_KEY", "GOLD_SECRET_KEY", "GOLD_BUCKET"]
31
+ if self.tier == "bronze":
32
+ access_key = os.getenv("BRONZE_ACCESS_KEY")
33
+ secret_key = os.getenv("BRONZE_SECRET_KEY")
34
+ self.bucket = os.getenv("BRONZE_BUCKET")
35
+ required_vars = ["BRONZE_ACCESS_KEY", "BRONZE_SECRET_KEY", "BRONZE_BUCKET"]
36
+ elif self.tier == "silver":
37
+ access_key = os.getenv("SILVER_ACCESS_KEY")
38
+ secret_key = os.getenv("SILVER_SECRET_KEY")
39
+ self.bucket = os.getenv("SILVER_BUCKET")
40
+ required_vars = ["SILVER_ACCESS_KEY", "SILVER_SECRET_KEY", "SILVER_BUCKET"]
41
+ else:
42
+ access_key = os.getenv("GOLD_ACCESS_KEY")
43
+ secret_key = os.getenv("GOLD_SECRET_KEY")
44
+ self.bucket = os.getenv("GOLD_BUCKET")
45
+ required_vars = ["GOLD_ACCESS_KEY", "GOLD_SECRET_KEY", "GOLD_BUCKET"]
46
+ endpoint = os.getenv("S3_ENDPOINT", "http://localhost:9000")
37
47
 
38
48
  missing_vars = [var for var in required_vars if not os.getenv(var)]
39
49
  if missing_vars:
@@ -41,8 +51,6 @@ class S3Storage:
41
51
  f"Missing required environment variables for {self.tier} tier: {', '.join(missing_vars)}"
42
52
  )
43
53
 
44
- endpoint = os.getenv("S3_ENDPOINT", "http://localhost:9000")
45
-
46
54
  self.client = boto3.client(
47
55
  "s3",
48
56
  endpoint_url=endpoint,
@@ -153,6 +161,14 @@ class S3Storage:
153
161
  for obj in resp.get("Contents", [])
154
162
  ]
155
163
 
164
+ def list_all(self) -> List[str]:
165
+ keys = []
166
+ paginator = self.client.get_paginator("list_objects_v2")
167
+ for page in paginator.paginate(Bucket=self.bucket):
168
+ for obj in page.get("Contents", []):
169
+ keys.append(obj["Key"])
170
+ return keys
171
+
156
172
 
157
- def get_storage(tier: str = "bronze") -> Storage:
158
- return S3Storage(tier=tier)
173
+ def get_storage(tier: str = "bronze", workspace: str | None = None) -> Storage:
174
+ return S3Storage(tier=tier, workspace=workspace)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: steve-cli
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: A simple CLI tool to run jobs from jobs.yaml with proper environment setup
5
5
  Author: Frank
6
6
  License: MIT
File without changes
File without changes