Skip to content

feat: secrets input accepts a map; log per-call API response time#16

Merged
lukekim merged 4 commits into
trunkfrom
feat/secrets-map-and-api-timing
May 3, 2026
Merged

feat: secrets input accepts a map; log per-call API response time#16
lukekim merged 4 commits into
trunkfrom
feat/secrets-map-and-api-timing

Conversation

@lukekim
Copy link
Copy Markdown
Contributor

@lukekim lukekim commented May 3, 2026

Summary

Three changes from the latest deploy-run feedback.

1. `secrets` input is now a map (matches `tags`)

Same migration as #12 did for `tags`. Accept a YAML block mapping (canonical workflow form) or a JSON object string, instead of multi-line `KEY=VALUE`:

```yaml
with:
secrets: |
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
PG_PASSWORD: ${{ secrets.PG_PASSWORD }}
```

Secret-name validation is unchanged (starts with a letter or underscore, alphanumeric + underscores only). Secret values can contain any characters — only the name is constrained — and each value is added to the runner's secret-mask list before any API call.

Action required for existing workflows: swap `=` for `:` between key and value. This is a breaking change to a single input format.

2. Per-call response time in API logs

Every Spice Cloud Management API call now emits one line:

```
PUT /v1/apps/4573 → 200 OK (312ms)
POST /v1/apps/4573/secrets → 201 Created (148ms)
POST /v1/apps/4573/deployments → 202 Accepted (203ms)
GET /v1/apps/4573/deployments?limit=20 → 200 OK (87ms)
```

Network failures log ` → network error in ms: `. Removed the redundant pre-call path logs in `deploy.ts` (`PUT /v1/apps/X tags=...`, `POST /v1/apps/X/secrets — KEY`) — the timing line covers them and the `Tags: ...` / `Secret: ...` info lines stay for context.

To answer the question on which other inputs should be maps: I checked everything in `action.yml` — `tags` and `secrets` are the only flat key/value inputs. The rest is either scalar (URLs, region, timeouts), a file path (`spicepod`), or a structured API body (`test-search`, `test-mcp-arguments`) that needs to match the underlying API's JSON shape rather than being a flat map. No other refactors needed.

3. Empty Branch in step summary

The list-deployments endpoint occasionally returns Deployment objects without a `branch` field even when the create request explicitly set it (the user just hit this — saw `Commit: 0ceabc...` populated but `Branch:` empty in the step summary). Fix: fall back to the `branch` and `commit-sha` inputs we sent so the summary stays accurate even when the API echoes them inconsistently.

Test plan

  • `npm run all` — 107 tests passing (up from 101), lint/typecheck/build/dist freshness clean.
  • `parseSecrets` covered for both YAML block-map and JSON forms, quote stripping, special chars, duplicates, malformed JSON, non-string values, and invalid keys.
  • CI green on this PR.
  • Manual: re-run `lukekim/home` workflow on the merged commit and confirm:
    • Step summary shows `Branch: trunk`.
    • Logs include ` → (ms)` for every management call.
    • The new `secrets:` map syntax works.

Three changes from the latest deploy run feedback.

1. Map-shaped `secrets` input
   Mirror the `tags` refactor: accept a YAML block mapping (the canonical
   workflow form) or a JSON object string, instead of multi-line
   `KEY=VALUE` lines. Same key validation as before — must start with a
   letter or underscore, alphanumeric + underscores only — and secret
   values can still contain any characters. Each value is added to the
   runner's secret-mask list before any API call.

2. Per-call API response time
   Every Spice Cloud Management API call now logs
   `<METHOD> <path> → <status> <statusText> (<durationMs>ms)` so the
   user can see each request's latency inline. Network failures log
   `<METHOD> <path> → network error in <durationMs>ms: <message>`.
   Removed the redundant pre-call path logs in deploy.ts that the
   timing line now covers.

3. Branch missing from step summary
   The list-deployments endpoint sometimes returns Deployment objects
   without `branch` even when the create request set it. The summary
   now falls back to the `branch` and `commit-sha` inputs we sent so
   the right values surface even when the API echoes them inconsistently.

Tests: 107 passing (up from 101). New cases cover the YAML and JSON
secret forms (incl. quote-stripping, special characters in values, and
JSON validation) and the post-rebuild `dist/` round-trip.
Copilot AI review requested due to automatic review settings May 3, 2026 01:01
@lukekim lukekim self-assigned this May 3, 2026
- package.json: 1.0.0 → 1.1.0.
- CHANGELOG.md: collapse the [Unreleased] block into a [1.1.0] section
  dated 2026-05-03, with explicit Breaking changes / Added / Changed /
  Fixed buckets. Added an empty [Unreleased] header for future entries.

The release rolls up everything since v1.0.0:

- Breaking: secrets input switched from KEY=VALUE to YAML/JSON map
  (matches `tags`); tag-value validation tightened to alphanumeric +
  `_@-` only.
- Added: `org`, `flight-url`, `dataset-ready-timeout-seconds` inputs;
  `datasets` output; auto-captured `repository` tag; per-call
  `<METHOD> <path> → <status> (<ms>ms)` API timing in logs.
- Changed: action runtime bumped from Node 20 to Node 24.
- Fixed: app-url URL pattern, SQL probe gRPC connection, api-key field
  names, step-summary branch fallback, punycode deprecation warning,
  action.yml `${{ … }}` example evaluation bug.
@lukekim lukekim changed the title feat: secrets input accepts a map; log per-call API response time feat: secrets map, API timing, and bump to v1.1.0 May 3, 2026
@lukekim
Copy link
Copy Markdown
Contributor Author

lukekim commented May 3, 2026

Bumped package.json to 1.1.0 and consolidated the [Unreleased] block into a [1.1.0] — 2026-05-03 section with explicit Breaking changes / Added / Changed / Fixed buckets. Once this merges:

git tag v1.1.0
git push origin v1.1.0

The release workflow will move the v1 floating tag to v1.1.0 and create the GitHub release using the [1.1.0] changelog body.

Why minor (not patch, not major)?

  • Net additive features (new org, flight-url, dataset-ready-timeout-seconds inputs; new datasets output; auto-captured repository tag) → not a patch.
  • The two breaking pieces (secrets map shape, tighter tag-value validation) are scoped to recently-introduced inputs on a release tag that's a few hours old; calling out the migration in the CHANGELOG and going 1.1.0 is more useful to consumers than a fresh v2.0.0 tag this soon. The CHANGELOG "Breaking changes" section names exact lines to swap.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the deploy action’s ergonomics and observability by moving secrets to the same map-style input model as tags, adding per-request timing logs for management API calls, and making the step summary more resilient when deployment metadata is missing from list responses.

Changes:

  • Reworked secrets input handling from multiline KEY=VALUE entries to YAML/JSON map parsing, with matching docs, examples, and tests.
  • Added inline latency logging for Spice Cloud Management API calls and removed now-redundant pre-call path logs from deploy flow.
  • Passed action inputs into summary generation so branch/commit fields can fall back to the submitted inputs when the API omits them.

Reviewed changes

Copilot reviewed 11 out of 13 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/secrets.ts Replaces legacy secrets parsing with map-style parsing and centralizes name validation/masking.
src/main.ts Threads inputs into summary generation and adds branch/commit fallback logic.
src/deploy.ts Simplifies deploy logging now that API client emits per-call timing lines.
src/api.ts Logs status and duration for each management API request, including network failures.
examples/full.yml Updates the example workflow to the new secrets map syntax.
action.yml Documents the new secrets input format and examples.
__tests__/secrets.test.ts Rewrites parser coverage around YAML/JSON secret maps.
__tests__/deploy.test.ts Updates deploy test fixtures to use the new secrets syntax.
README.md Refreshes input docs and usage examples for map-style secrets.
CHANGELOG.md Records the breaking secrets change, API timing logs, and summary fallback fix.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/secrets.ts Outdated
Comment on lines +78 to +86
const colon = line.indexOf(":");
if (colon === -1) {
throw new InputValidationError(
`secrets line ${i + 1}: name "${name}" must start with a letter or underscore and contain only letters, numbers, and underscores.`,
`secrets line ${i + 1}: expected "KEY: VALUE" (got "${line.trim()}").`,
);
}

const name = line.slice(0, colon).trim();
const value = stripQuotes(line.slice(colon + 1).trim());
Comment thread src/secrets.ts Outdated
Comment on lines +85 to +86
const name = line.slice(0, colon).trim();
const value = stripQuotes(line.slice(colon + 1).trim());
Comment thread src/main.ts Outdated
Comment on lines +131 to +132
const branch = deployment.branch || inputs.branch || "";
const commitSha = deployment.commit_sha || inputs.commitSha || "";
Comment thread src/api.ts
Comment on lines 109 to +124
@@ -117,6 +120,9 @@ export class SpiceApiClient {
);
}

const durationMs = Date.now() - startMs;
core.info(`${method} ${path} → ${res.status} ${res.statusText} (${durationMs}ms)`);
Comment thread src/secrets.ts
if (colon === -1) {
throw new InputValidationError(
`secrets line ${i + 1}: name "${name}" must start with a letter or underscore and contain only letters, numbers, and underscores.`,
`secrets line ${i + 1}: expected "KEY: VALUE" (got "${line.trim()}").`,
Comment thread src/secrets.ts
Comment on lines +78 to +88
const colon = line.indexOf(":");
if (colon === -1) {
throw new InputValidationError(
`secrets line ${i + 1}: name "${name}" must start with a letter or underscore and contain only letters, numbers, and underscores.`,
`secrets line ${i + 1}: expected "KEY: VALUE" (got "${line.trim()}").`,
);
}

const name = line.slice(0, colon).trim();
const value = stripQuotes(line.slice(colon + 1).trim());

validateName(name, `secrets line ${i + 1}`);
@lukekim lukekim changed the title feat: secrets map, API timing, and bump to v1.1.0 feat: secrets input accepts a map; log per-call API response time May 3, 2026
@lukekim lukekim mentioned this pull request May 3, 2026
3 tasks
- src/main.ts: Use a length-aware fallback (deployment.branch?.length
  ? deployment.branch : inputs.branch ?? "") so the step summary still
  falls back when the API returns an empty string, while documenting
  why we treat empty-string as equivalent to missing here.
- src/secrets.ts: Stop calling `.trim()` on secret values. Strip only
  the leading whitespace separator after `:` and a matching pair of
  outer quotes; trailing whitespace and inner whitespace are preserved
  verbatim. Also pass the un-trimmed raw input to parseBlockMap so a
  trailing space on the last line isn't lost. Whitespace-significant
  values still need quoting to make the boundaries explicit.
- src/api.ts: Move the timing log to AFTER the response body is read
  so the duration reflects true end-to-end request latency, not just
  time-to-first-byte. Body-read errors retry on the next attempt and
  fail with a clear `Failed to read response body for …` message on
  exhaustion.

New tests cover trailing whitespace preserved in unquoted secret
values, leading whitespace preserved inside quoted secret values, and
multiple-spaces-after-colon handling.
@lukekim lukekim merged commit a8db3ea into trunk May 3, 2026
4 checks passed
@lukekim lukekim deleted the feat/secrets-map-and-api-timing branch May 3, 2026 01:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants