Skip to content

Manage resource types via OCI (create/get/list/publish/pull/delete/convert) - #249

Open
chrisghill wants to merge 2 commits into
mainfrom
resource-type-oci
Open

Manage resource types via OCI (create/get/list/publish/pull/delete/convert)#249
chrisghill wants to merge 2 commits into
mainfrom
resource-type-oci

Conversation

@chrisghill

Copy link
Copy Markdown
Member

The API now supports resource types being managed via OCI push/pull. This wires that up in the CLI, mirroring bundles but without any build/lint machinery, extracts the shared OCI plumbing into its own package so bundles and resource types share one code path, and moves resource-type reads onto the SDK so the local internal/api holding-pen package can be deleted entirely.

Shared OCI package

  • New internal/oci package holds the raw OCI logic: Publisher (Package with a caller-supplied artifact-type + per-file keep predicate, Publish) and Puller (Pull), plus the file/mime helpers.
  • internal/bundle keeps only its unique config (ArtifactType const + PackageKeep ignore-list predicate); internal/commands/bundle/{publish,pull} and internal/commands/instance/export.go now drive oci.Publisher/oci.Puller.
  • Bundle behavior is unchanged — same media type (application/vnd.massdriver.bundle.v1+json), same .mdignore keep semantics, same manifest/layers, same pull tag resolution.

Resource types → OCI (7 commands)

Command Behavior
create Creates a resource-type OCI repository; supports -a attributes
get Uses the SDK's ResourceTypes.Get (returns the resolved JSON schema); -o json prints the full object, new --schema prints only the schema (errors without -o json)
list Lists from the OCI-repo catalog filtered to resource-type artifacts, mirroring bundle list (columns: Name / Latest / Created At; supports -o json)
publish [path] Bundle-style directory (default .); requires version in massdriver.yaml; meta-schema validation; local duplicate-version check (stable only, no dev releases); packages only allowlisted files; rejects raw JSON schema files, pointing to convert
pull OCI pull (-d/-v/-f), latest resolves to the latest release channel or newest tag
delete Deletes the OCI repository, failing locally if it has published versions (immutable); keeps the type-the-name confirmation
convert Converts a raw JSON/YAML schema into a massdriver.yaml, extracting inlined instruction/export content back out to referenced files
  • Added a version field to the resource type massdriver.yaml.
  • Publish allowlist: massdriver.yaml, readme, changelog, icons, plus the instructions/ and exports/ directories a massdriver.yaml references (so convert → publish → pull round-trips). Everything else is silently skipped.
  • Raw JSON schema publishing is no longer supported.

Removed internal/api

  • Resource-type reads (Get, List) now go through the SDK (ResourceTypes.Get and OciRepos.Iter), so the temporary internal/api holding-pen package is deleted in full — no callers remain.
  • resourcetype.ResourceType is now an alias of the SDK's resourcetypes.ResourceType.

Tests & docs

  • New/updated tests: bundle packaging against the shared packager, OCI pull, publish validation (raw-schema rejection, missing name/version), packageKeep allowlist, convert round-trip (incl. duplicate-label collision and no-clobber), and resource-type get/list mocked via massdriver.WithGQLClient.
  • Added helpdocs for create/pull/convert, refreshed publish, and regenerated the committed CLI docs.

Notes

  • list table columns changed from ID / Name / Updated At to Name / Latest / Created At to match bundle list.
  • Requires the SDK bump to v0.2.18 (adds the ResourceTypes service).

@chrisghill

Copy link
Copy Markdown
Member Author

@claude review

@claude

claude Bot commented Aug 7, 2026

Copy link
Copy Markdown

Claude finished @chrisghill's task in 3m 34s —— View job


Code Review

Reviewed the OCI plumbing extraction (internal/oci), the resource-type command surface (create/get/list/publish/pull/delete/convert), and the bundle refactor for regressions. Build/test execution wasn't available in this environment (bash tool calls required interactive approval I couldn't get), so this is a static read-through — flagging that so the checks below can be confirmed with go build ./... / go test ./... if not already run in CI.

Overall the extraction is clean: internal/oci.Publisher/Puller is a faithful lift of the old bundle-only logic (verified byte-for-byte against git show origin/main:internal/bundle/pull.go), and bundle behavior looks unchanged (same media type, same .mdignore defaults in internal/bundle/publish.go).

A few things worth a look:

1. Test coverage regression: resourcetype.Delete and resourcetype.Pull have no tests

  • internal/resourcetype/delete_test.go was deleted (it exercised Delete via the old internal/api mock transport) and nothing replaced it. The new Delete (internal/resourcetype/delete.go:15) has real branching logic — the immutability check (len(repo.Tags) > 0) — that's now untested.
  • internal/resourcetype/pull.go is a new 78-line file with no corresponding test file. resolveTag (internal/resourcetype/pull.go:47) has several branches (empty/latest → release channel → newest tag fallback → exact tag match → channel name match → not-found) that aren't covered.
  • internal/resourcetype/get_test.go shows the pattern for mocking this cheaply via massdriver.WithGQLClient/gqltest, so this looks like an oversight rather than a hard blocker — same approach would work here.

2. Duplicated resolveTag logic between bundle and resource-type pull
internal/commands/bundle/pull.go:58 and internal/resourcetype/pull.go:47 implement nearly identical tag-resolution logic (exact tag → release channel → error), with the resource-type version just adding the latest/newest-tag fallback. Given this PR's explicit goal of sharing OCI plumbing between bundle and resource types, this looks like a missed opportunity to fold resolveTag into internal/oci alongside Publisher/Puller.

3. Possible silent content drop in the publish allowlist
packageKeep (internal/resourcetype/publish.go:40) only keeps files under the literal instructions//exports/ top-level prefixes. But Build (internal/resourcetype/build.go:92,110) reads instruction.Path/export.TemplatePath from the massdriver.yaml as an arbitrary relative path — there's no validation that these paths actually live under instructions//exports/. If an author points instructions[].path at, say, docs/setup.md, local validation and Build/Read succeed (they read the file directly off disk), but Publish will silently skip packaging it since packageKeep doesn't recognize the prefix — the schema ends up referencing content that was never uploaded, and the break only surfaces downstream when someone else pulls/consumes the resource type. Consider validating at publish time that referenced instruction/export paths fall under an allowed directory (with a clear error) rather than relying on convention.

Minor/non-blocking:

  • cmd/resource_type.go:311-336 (runTypeDelete) fetches the repo and checks len(repo.Tags) > 0 before prompting, and resourcetype.Delete re-checks the same thing — intentional per the comment (guards a race with the confirmation prompt), just noting it's duplicated logic by design, not a bug.
  • Error-wrapping strings for the same "fetch OCI repo" operation differ slightly across call sites ("fetching OCI repo: %w" vs "failed to get OCI repo: %w") — cosmetic only.

Nothing here looks blocking; #1 (test coverage) and #3 (allowlist/Build mismatch) are the two I'd actually want addressed before merge.
· Branch

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.

1 participant