Skip to content

Commit 036d495

Browse files
committed
fix(submit): send labels (not tags); drop --group, keep --label
Align submit with the DevRadar API: the grouping field on POST /v1/sboms is `labels`, but the client was sending `tags`, which the server ignored — so grouping labels were silently dropped. Rename the wire field and SubmitRequest field to Labels, remove the --group flag (keep --label), and rename the env var DEVRADAR_TAGS -> DEVRADAR_LABELS. Docs realigned to the API field table (README config table + --tag/--image-ref rows; DEVELOPMENT.md and CLAUDE.md API contract, incl. optional format_hint / generated_at the CLI does not send).
1 parent 09d7631 commit 036d495

7 files changed

Lines changed: 42 additions & 29 deletions

File tree

.claude/CLAUDE.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ Thin `main.go` → `internal/cli`. Nothing is meant for external import, hence
5454
- `ref.go``SplitRef`/`Repository`/`Tag` (ported from devradar `pkg/sbom/ref.go`).
5555
- `digest.go` — manifest digest resolution **in-process** via `go-containerregistry` (crane lib); no `crane` binary needed.
5656
- `generate.go` — shells out to `syft` (`-q --scope all-layers -o cyclonedx-json <ref>`); `EnsureSyft` fails fast if absent.
57-
- `internal/client` — HTTP client for `POST /v1/sboms` (Bearer auth, base64 `sbom`, optional `image_ref`/`version`/`tags`).
57+
- `internal/client` — HTTP client for `POST /v1/sboms` (Bearer auth, base64 `sbom`, optional `image_ref`/`version`/`labels`).
5858
- `internal/logging` — slog setup; **warn** default level, `--debug` → debug, `--log-json` → JSON handler.
5959

6060
## Key conventions
@@ -66,9 +66,11 @@ Thin `main.go` → `internal/cli`. Nothing is meant for external import, hence
6666
## API contract
6767

6868
`POST {base}/v1/sboms`, header `Authorization: Bearer <token>`, body
69-
`{ "sbom": <base64>, "image_ref"?, "version"?, "tags"?[] }`. Response
70-
`{ sbom_id, image_ref, digest, format, existing }`. Source of truth:
71-
devradar `pkg/server/ingest.go`. Default base URL `https://devradar.thingz.io`.
69+
`{ "sbom": <base64>, "image_ref"?, "version"?, "labels"?[], "format_hint"?, "generated_at"? }`.
70+
The CLI sends `sbom`/`image_ref`/`version`/`labels` only. Response
71+
`{ sbom_id, image_ref, digest, format, existing }`. Source of truth: devradar
72+
`pkg/server/ingest.go` (details in its `IMPLEMENTATION.md`). Default base URL
73+
`https://devradar.thingz.io`.
7274

7375
## Release
7476

DEVELOPMENT.md

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -74,14 +74,24 @@ hence `internal/` rather than `pkg/`.
7474

7575
## API contract
7676

77-
`POST {base}/v1/sboms`, header `Authorization: Bearer <token>`, body:
78-
79-
```json
80-
{ "sbom": "<base64>", "image_ref": "", "version": "", "tags": [""] }
81-
```
82-
83-
Response: `{ sbom_id, image_ref, digest, format, existing }`. Only `sbom` is
84-
required. Source of truth: devradar `pkg/server/ingest.go`.
77+
`POST {base}/v1/sboms`, header `Authorization: Bearer <token>`. Only `sbom` is
78+
required:
79+
80+
| Field | Req? | Purpose |
81+
| -------------- | ---- | --------------------------------------------------------- |
82+
| `sbom` | yes | base64 SBOM bytes |
83+
| `image_ref` | no | override the image reference |
84+
| `version` | no | the image tag (e.g. `v1.20.2`); else parsed from image_ref |
85+
| `format_hint` | no | `cyclonedx`\|`spdx` fast-fail assist (auto-detect otherwise) |
86+
| `generated_at` | no | RFC3339 timestamp override |
87+
| `labels` | no | grouping labels (e.g. `team-x`, `prod`) |
88+
89+
devradarctl sets `sbom`, `image_ref`, `version`, and `labels`; it relies on
90+
syft for format auto-detection and the SBOM's own `generated_at`, so it does not
91+
send `format_hint` or `generated_at`.
92+
93+
Response: `{ sbom_id, image_ref, digest, format, existing }`. Source of truth:
94+
devradar `pkg/server/ingest.go` (see also its `IMPLEMENTATION.md`).
8595

8696
## Releasing
8797

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ it does everything in one step: resolves the manifest digest, generates an
4949
every layer, not just the final squashed filesystem), and submits it.
5050

5151
```sh
52-
echo "$DEVRADAR_TOKEN" | devradarctl submit --image alpine:3.20 --group team-x --group prod
52+
echo "$DEVRADAR_TOKEN" | devradarctl submit --image alpine:3.20 --label team-x --label prod
5353
```
5454

5555
This requires [`syft`](https://github.com/anchore/syft) on `PATH`.
@@ -70,7 +70,9 @@ DEVRADAR_TOKEN=xxx devradarctl submit --file alpine.cdx.json --image-ref alpine@
7070
| ---------------- | -------------------- | ----------------------------- | --------------------------------------------- |
7171
| `--base-url` | `DEVRADAR_BASE_URL` | `https://devradar.thingz.io` | DevRadar service base URL |
7272
| (token) | `DEVRADAR_TOKEN` || API token (or piped via stdin) |
73-
| `--group` | `DEVRADAR_TAGS` || Grouping label(s); repeatable / comma env |
73+
| `--label` | `DEVRADAR_LABELS` || Grouping label(s); repeatable |
74+
| `--tag` || image's tag | Image version to record (e.g. `v1.20.2`) |
75+
| `--image-ref` ||| Digest-pinned image reference (file mode) |
7476
| `--syft-path` | `DEVRADAR_SYFT_PATH` | `syft` | Path to the syft binary |
7577
| `--scope` || `all-layers` | syft cataloging scope |
7678
| `--debug` | `DEVRADAR_DEBUG` | `false` | Debug logging (default level is warn) |

internal/cli/flags.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const (
1616
flagFile = "file"
1717
flagImageRef = "image-ref"
1818
flagTag = "tag"
19-
flagGroup = "group"
19+
flagLabel = "label"
2020
flagBaseURL = "base-url"
2121
)
2222

internal/cli/submit.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,9 @@ func submitCmd() *cli.Command {
4444
Usage: "human version tag to record (e.g. v1.20.2); defaults to the image's tag",
4545
},
4646
&cli.StringSliceFlag{
47-
Name: flagGroup,
48-
Aliases: []string{"label"},
47+
Name: flagLabel,
4948
Usage: "grouping label(s) to attach; repeatable",
50-
Sources: cli.EnvVars("DEVRADAR_TAGS"),
49+
Sources: cli.EnvVars("DEVRADAR_LABELS"),
5150
},
5251
&cli.StringFlag{
5352
Name: flagBaseURL,
@@ -76,7 +75,7 @@ func runSubmit(ctx context.Context, c *cli.Command) error {
7675
}
7776

7877
req := client.SubmitRequest{
79-
Tags: c.StringSlice(flagGroup),
78+
Labels: c.StringSlice(flagLabel),
8079
Version: c.String(flagTag),
8180
}
8281

internal/client/client.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,10 @@ type SubmitRequest struct {
4848
SBOM []byte
4949
// ImageRef is the digest-pinned image reference (repo@sha256:…), optional.
5050
ImageRef string
51-
// Version is the human tag (e.g. "v1.20.2"), optional.
51+
// Version is the image tag (e.g. "v1.20.2"), optional.
5252
Version string
53-
// Tags are tenant grouping labels (e.g. "team-x", "prod"), optional.
54-
Tags []string
53+
// Labels are tenant grouping labels (e.g. "team-x", "prod"), optional.
54+
Labels []string
5555
}
5656

5757
// SubmitResponse mirrors the POST /v1/sboms response body.
@@ -63,12 +63,12 @@ type SubmitResponse struct {
6363
Existing bool `json:"existing"`
6464
}
6565

66-
// wireRequest is the JSON shape sent to the service.
66+
// wireRequest is the JSON shape sent to the service (POST /v1/sboms).
6767
type wireRequest struct {
6868
SBOM string `json:"sbom"`
6969
ImageRef string `json:"image_ref,omitempty"`
7070
Version string `json:"version,omitempty"`
71-
Tags []string `json:"tags,omitempty"`
71+
Labels []string `json:"labels,omitempty"`
7272
}
7373

7474
// Submit posts an SBOM to {baseURL}/v1/sboms and returns the decoded response.
@@ -81,14 +81,14 @@ func (c *Client) Submit(ctx context.Context, in SubmitRequest) (*SubmitResponse,
8181
SBOM: base64.StdEncoding.EncodeToString(in.SBOM),
8282
ImageRef: in.ImageRef,
8383
Version: in.Version,
84-
Tags: in.Tags,
84+
Labels: in.Labels,
8585
})
8686
if err != nil {
8787
return nil, fmt.Errorf("encode request: %w", err)
8888
}
8989

9090
url := c.baseURL + "/v1/sboms"
91-
slog.Debug("submitting SBOM", "url", url, "image_ref", in.ImageRef, "version", in.Version, "tags", in.Tags, "bytes", len(in.SBOM))
91+
slog.Debug("submitting SBOM", "url", url, "image_ref", in.ImageRef, "version", in.Version, "labels", in.Labels, "bytes", len(in.SBOM))
9292

9393
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
9494
if err != nil {

internal/client/client_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ func TestSubmit_Success(t *testing.T) {
3232
SBOM: sbomDoc,
3333
ImageRef: "alpine@sha256:x",
3434
Version: "3.20",
35-
Tags: []string{"team-x", "prod"},
35+
Labels: []string{"team-x", "prod"},
3636
})
3737
if err != nil {
3838
t.Fatalf("Submit: %v", err)
@@ -54,8 +54,8 @@ func TestSubmit_Success(t *testing.T) {
5454
if gotBody.ImageRef != "alpine@sha256:x" || gotBody.Version != "3.20" {
5555
t.Errorf("body image_ref/version = %q/%q", gotBody.ImageRef, gotBody.Version)
5656
}
57-
if len(gotBody.Tags) != 2 {
58-
t.Errorf("body.tags = %v, want 2 tags", gotBody.Tags)
57+
if len(gotBody.Labels) != 2 {
58+
t.Errorf("body.labels = %v, want 2 labels", gotBody.Labels)
5959
}
6060
if resp.SBOMID != "sb-1" || resp.Format != "cyclonedx-json" {
6161
t.Errorf("resp = %+v", resp)

0 commit comments

Comments
 (0)