Skip to content

Commit 84c1f24

Browse files
Fix/unify output formats (#75)
* Unify a bunch of params and output formats * Added environment code cleanup and outfile to download * Fix download command
1 parent d7130fe commit 84c1f24

File tree

17 files changed

+739
-271
lines changed

17 files changed

+739
-271
lines changed

README.md

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ python3 pylynk.py upload --prod 'my-product' --sbom my-sbom.json
6868
### Download an SBOM
6969

7070
```bash
71-
python3 pylynk.py download --prod 'my-product' --verId 'version-id' --output sbom.json
71+
python3 pylynk.py download --prod 'my-product' --verId 'version-id' --out-file sbom.json
7272
```
7373

7474
### List Vulnerabilities
@@ -88,7 +88,7 @@ docker run -e INTERLYNK_SECURITY_TOKEN=$INTERLYNK_SECURITY_TOKEN \
8888
# Download
8989
docker run -e INTERLYNK_SECURITY_TOKEN=$INTERLYNK_SECURITY_TOKEN \
9090
-v $(pwd):/app/data \
91-
ghcr.io/interlynk-io/pylynk download --prod 'my-product' --verId 'version-id' --output /app/data/sbom.json
91+
ghcr.io/interlynk-io/pylynk download --prod 'my-product' --verId 'version-id' --out-file /app/data/sbom.json
9292
```
9393

9494
## Commands
@@ -105,14 +105,27 @@ docker run -e INTERLYNK_SECURITY_TOKEN=$INTERLYNK_SECURITY_TOKEN \
105105

106106
## Output Formats
107107

108-
Most commands support multiple output formats:
108+
All commands support multiple output formats via `--output`:
109109

110-
- `--json` - JSON format (default for most commands)
111-
- `--table` - Human-readable table format
110+
- `table` - Human-readable table format (default)
111+
- `json` - JSON format for programmatic use
112+
- `csv` - CSV format for spreadsheet import
112113

113-
The `vulns` command additionally supports:
114+
Commands with timestamps also support `--human-time` to display timestamps in human-friendly format (e.g., '2 days ago').
114115

115-
- `--output csv` - CSV format for spreadsheet import
116+
```bash
117+
# Table format (default)
118+
python3 pylynk.py prods
119+
120+
# JSON format
121+
python3 pylynk.py prods --output json
122+
123+
# CSV format
124+
python3 pylynk.py prods --output csv
125+
126+
# With human-friendly timestamps
127+
python3 pylynk.py prods --human-time
128+
```
116129

117130
## CI/CD Integration
118131

docs/download.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ python3 pylynk.py download --prod <product-name> --env <environment> --ver <vers
2020
| `--verId` | Version ID (mutually exclusive with `--ver`) |
2121
| `--ver` | Version name (mutually exclusive with `--verId`) |
2222
| `--env` | Environment name |
23-
| `--output` | Output file path |
23+
| `--out-file` | Output file path |
2424
| `--vuln` | Include vulnerabilities (true/false/yes/no/1/0) |
2525
| `--spec` | SBOM specification (SPDX or CycloneDX) |
2626
| `--spec-version` | SBOM specification version |
@@ -47,6 +47,12 @@ python3 pylynk.py download --prod 'sbomqs' --verId 'fbcc24ad-5911-4229-8943-acf8
4747
python3 pylynk.py download --prod 'sbomqs' --env 'default' --ver 'v1.0.0'
4848
```
4949

50+
### Download to a file
51+
52+
```bash
53+
python3 pylynk.py download --prod 'sbomqs' --verId 'fbcc24ad-...' --out-file sbom.json
54+
```
55+
5056
### Download with vulnerabilities
5157

5258
```bash
@@ -86,7 +92,7 @@ python3 pylynk.py download --prod 'sbomqs' --verId 'fbcc24ad-...' \
8692

8793
```bash
8894
python3 pylynk.py download --prod 'sbomqs' --verId 'fbcc24ad-...' \
89-
--support-level-only --output support-levels.csv
95+
--support-level-only --out-file support-levels.csv
9096
```
9197

9298
### Using Docker
@@ -95,7 +101,7 @@ python3 pylynk.py download --prod 'sbomqs' --verId 'fbcc24ad-...' \
95101
docker run -e INTERLYNK_SECURITY_TOKEN=$INTERLYNK_SECURITY_TOKEN \
96102
-v $(pwd):/app/data \
97103
ghcr.io/interlynk-io/pylynk download --prod 'sbomqs' --verId 'fbcc24ad-...' \
98-
--output /app/data/sbom.json
104+
--out-file /app/data/sbom.json
99105
```
100106

101107
## Download Options Explained

docs/prods.md

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,40 +12,49 @@ python3 pylynk.py prods [OPTIONS]
1212

1313
| Option | Description |
1414
|--------|-------------|
15+
| `--output` | Output format: `table` (default), `json`, or `csv` |
16+
| `--human-time` | Show timestamps in human-friendly format (e.g., '2 days ago') |
1517
| `--token` | Security token (can also use `INTERLYNK_SECURITY_TOKEN` env var) |
16-
| `--table` | Output in table format |
17-
| `--json` | Output in JSON format (default) |
1818
| `-v, --verbose` | Enable verbose/debug output |
1919

2020
## Examples
2121

22-
### List products in table format
22+
### List products in table format (default)
2323

2424
```bash
25-
python3 pylynk.py prods --table
25+
python3 pylynk.py prods
2626
```
2727

2828
### List products in JSON format
2929

3030
```bash
31-
python3 pylynk.py prods --json
32-
# or simply
33-
python3 pylynk.py prods
31+
python3 pylynk.py prods --output json
32+
```
33+
34+
### List products in CSV format
35+
36+
```bash
37+
python3 pylynk.py prods --output csv
38+
```
39+
40+
### List products with human-friendly timestamps
41+
42+
```bash
43+
python3 pylynk.py prods --human-time
3444
```
3545

3646
### Using Docker
3747

3848
```bash
3949
docker run -e INTERLYNK_SECURITY_TOKEN=$INTERLYNK_SECURITY_TOKEN \
40-
ghcr.io/interlynk-io/pylynk prods --table
50+
ghcr.io/interlynk-io/pylynk prods
4151
```
4252

4353
## Output Fields
4454

4555
| Field | Description |
4656
|-------|-------------|
57+
| NAME | Product name |
4758
| ID | Unique product identifier |
48-
| Name | Product name |
49-
| Environments | Number of environments configured |
50-
| Created | Creation timestamp |
51-
| Updated | Last update timestamp |
59+
| VERSIONS | Number of versions/SBOMs |
60+
| UPDATED AT | Last update timestamp |

docs/status.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,8 @@ python3 pylynk.py status --prod <product-name> --verId <version-id> [OPTIONS]
1616
| `--verId` | Version ID (mutually exclusive with `--ver`) |
1717
| `--ver` | Version name (mutually exclusive with `--verId`) |
1818
| `--env` | Environment name (optional, defaults to 'default') |
19+
| `--output` | Output format: `table` (default) or `json` |
1920
| `--token` | Security token (can also use `INTERLYNK_SECURITY_TOKEN` env var) |
20-
| `--table` | Output in table format |
21-
| `--json` | Output in JSON format (default) |
2221
| `-v, --verbose` | Enable verbose/debug output |
2322

2423
## Status Values

docs/vers.md

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,45 +14,55 @@ python3 pylynk.py vers --prod <product-name> [OPTIONS]
1414
|--------|-------------|
1515
| `--prod` | Product name (required) |
1616
| `--env` | Environment name (optional, defaults to 'default') |
17+
| `--output` | Output format: `table` (default), `json`, or `csv` |
18+
| `--human-time` | Show timestamps in human-friendly format (e.g., '2 days ago') |
1719
| `--token` | Security token (can also use `INTERLYNK_SECURITY_TOKEN` env var) |
18-
| `--table` | Output in table format |
19-
| `--json` | Output in JSON format (default) |
2020
| `-v, --verbose` | Enable verbose/debug output |
2121

2222
## Examples
2323

24-
### List versions for a product (default environment)
24+
### List versions for a product (default)
2525

2626
```bash
27-
python3 pylynk.py vers --prod 'sbomqs' --table
27+
python3 pylynk.py vers --prod 'sbomqs'
2828
```
2929

3030
### List versions in JSON format
3131

3232
```bash
33-
python3 pylynk.py vers --prod 'sbomqs' --json
33+
python3 pylynk.py vers --prod 'sbomqs' --output json
34+
```
35+
36+
### List versions in CSV format
37+
38+
```bash
39+
python3 pylynk.py vers --prod 'sbomqs' --output csv
40+
```
41+
42+
### List versions with human-friendly timestamps
43+
44+
```bash
45+
python3 pylynk.py vers --prod 'sbomqs' --human-time
3446
```
3547

3648
### List versions for a specific environment
3749

3850
```bash
39-
python3 pylynk.py vers --prod 'sbomqs' --env 'production' --table
51+
python3 pylynk.py vers --prod 'sbomqs' --env 'production'
4052
```
4153

4254
### Using Docker
4355

4456
```bash
4557
docker run -e INTERLYNK_SECURITY_TOKEN=$INTERLYNK_SECURITY_TOKEN \
46-
ghcr.io/interlynk-io/pylynk vers --prod 'sbomqs' --table
58+
ghcr.io/interlynk-io/pylynk vers --prod 'sbomqs'
4759
```
4860

4961
## Output Fields
5062

5163
| Field | Description |
5264
|-------|-------------|
5365
| ID | Unique version identifier (use this for `--verId` in other commands) |
54-
| Version | Version name/label |
55-
| Spec | SBOM specification (SPDX or CycloneDX) |
56-
| Components | Number of components in the SBOM |
57-
| Created | Creation timestamp |
58-
| Updated | Last update timestamp |
66+
| VERSION | Version name/label from primary component |
67+
| PRIMARY COMPONENT | Name of the primary component |
68+
| UPDATED AT | Last update timestamp |

pylynk/__main__.py

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,25 @@ def main():
5858
# Validate download parameters early
5959
has_id_params = bool(config.ver_id)
6060
has_name_params = all([config.prod, config.env, config.ver])
61-
61+
6262
if not has_id_params and not has_name_params:
63-
print("Error: Please provide either --verId OR all of --prod, --env, and --ver")
63+
print("Error: Missing required parameters for download")
64+
print()
65+
print("Please provide either:")
66+
print(" --verId <version-id>")
67+
print(" OR")
68+
print(" --prod <product> --env <environment> --ver <version>")
69+
print()
70+
print("Examples:")
71+
print(" pylynk download --verId 'abc-123-def'")
72+
print(" pylynk download --prod 'my-product' --env 'default' --ver 'v1.0.0'")
73+
print()
74+
print("For more information: pylynk download --help")
6475
return 1
65-
66-
# Download can now use minimal init if validation passes
67-
needs_full_init = False
76+
77+
# Download needs full init when using names to resolve to IDs
78+
# Only use minimal init when verId is provided directly
79+
needs_full_init = not has_id_params
6880

6981
# Initialize API client
7082
if needs_full_init:
@@ -90,8 +102,19 @@ def main():
90102
elif args.subcommand == "vulns":
91103
exit_code = vulns.execute(api_client, config)
92104
else:
93-
print("Missing or invalid command. "
94-
"Supported commands: {prods, vers, status, upload, download, version, vulns}")
105+
print("Error: No command specified")
106+
print()
107+
print("Available commands:")
108+
print(" prods List products")
109+
print(" vers List versions for a product")
110+
print(" status Check SBOM processing status")
111+
print(" upload Upload an SBOM")
112+
print(" download Download an SBOM")
113+
print(" vulns List vulnerabilities")
114+
print(" version Show version information")
115+
print()
116+
print("For help: pylynk --help")
117+
print("For command help: pylynk <command> --help")
95118
exit_code = 1
96119

97120
return exit_code

pylynk/api/client.py

Lines changed: 26 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -339,8 +339,7 @@ def get_status(self, vuln_status):
339339

340340
return result
341341

342-
def download_sbom(self, env_id=None, ver_id=None, env_name=None,
343-
prod_name=None, ver_name=None, include_vulns=False,
342+
def download_sbom(self, env_id=None, ver_id=None, include_vulns=False,
344343
spec=None, spec_version=None, lite=False,
345344
dont_package_sbom=False, original=False,
346345
exclude_parts=False, include_support_status=False,
@@ -349,11 +348,8 @@ def download_sbom(self, env_id=None, ver_id=None, env_name=None,
349348
Download an SBOM from the API.
350349
351350
Args:
352-
env_id (str): Environment ID (projectId) (optional)
353-
ver_id (str): Version ID (sbomId) (optional)
354-
env_name (str): Environment name (projectName) (optional)
355-
prod_name (str): Product name (projectGroupName) (optional)
356-
ver_name (str): Version name (versionName) (optional)
351+
env_id (str): Environment ID (projectId) - required
352+
ver_id (str): Version ID (sbomId) - required
357353
include_vulns (bool): Include vulnerabilities in download
358354
spec (str): SBOM specification (SPDX or CycloneDX)
359355
spec_version (str): SBOM specification version
@@ -367,23 +363,16 @@ def download_sbom(self, env_id=None, ver_id=None, env_name=None,
367363
Returns:
368364
tuple: (content, content_type, filename) or (None, None, None) if error
369365
"""
370-
# Build variables for new server format
371-
variables = {}
372-
373-
# Add identifier parameters
374-
if ver_id:
375-
variables["sbomId"] = ver_id
376-
if env_id:
377-
variables["projectId"] = env_id
378-
if prod_name:
379-
# Trim the product name before sending to API
380-
variables["projectGroupName"] = prod_name.strip()
381-
if env_name:
382-
# Lowercase and trim the environment name before sending to API
383-
variables["projectName"] = env_name.strip().lower()
384-
if ver_name:
385-
# Lowercase and trim the version name before sending to API
386-
variables["versionName"] = ver_name.strip().lower()
366+
# Validate required IDs
367+
if not ver_id or not env_id:
368+
print('Error: Both version ID and environment ID are required for download')
369+
return None, None, None
370+
371+
# Build variables - the query requires projectId and sbomId
372+
variables = {
373+
"projectId": env_id,
374+
"sbomId": ver_id
375+
}
387376

388377
# Add optional parameters
389378
if include_vulns:
@@ -405,20 +394,29 @@ def download_sbom(self, env_id=None, ver_id=None, env_name=None,
405394
variables["includeSupportStatus"] = include_support_status
406395

407396
logging.debug("Downloading SBOM with parameters: %s", variables)
408-
logging.debug("GraphQL Query for download:\n%s", SBOM_DOWNLOAD_NEW)
397+
logging.debug("GraphQL Query for download:\n%s", SBOM_DOWNLOAD)
409398
logging.debug("Query variables: %s", json.dumps(variables, indent=2))
410399

411400
response_data = self._make_request(
412-
SBOM_DOWNLOAD_NEW, variables, operation_name="downloadSbom")
401+
SBOM_DOWNLOAD, variables, operation_name="downloadSbom")
413402

414403
if not response_data:
415404
return None, None, None
416405

417406
if "errors" in response_data:
418407
return None, None, None
419408

420-
sbom = response_data.get('data', {}).get(
421-
'sbom', {}).get('download', {})
409+
data = response_data.get('data')
410+
if not data:
411+
print('Error: No data returned from API')
412+
return None, None, None
413+
414+
sbom_data = data.get('sbom')
415+
if not sbom_data:
416+
print('Error: SBOM not found - check product, environment, and version')
417+
return None, None, None
418+
419+
sbom = sbom_data.get('download', {})
422420

423421
if not sbom:
424422
print('No SBOM matched with the given criteria')

pylynk/api/queries.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,9 @@
6565

6666
# Query to download an SBOM (current server format)
6767
SBOM_DOWNLOAD = """
68-
query downloadSbom($projectId: Uuid!, $sbomId: Uuid!, $includeVulns: Boolean,
69-
$spec: String, $original: Boolean, $package: Boolean,
70-
$lite: Boolean, $excludeParts: Boolean,
68+
query downloadSbom($projectId: Uuid!, $sbomId: Uuid!, $includeVulns: Boolean,
69+
$spec: SbomSpec, $original: Boolean, $package: Boolean,
70+
$lite: Boolean, $excludeParts: Boolean,
7171
$supportLevelOnly: Boolean, $includeSupportStatus: Boolean) {
7272
sbom(projectId: $projectId, sbomId: $sbomId) {
7373
download(

0 commit comments

Comments
 (0)