Skip to content

openapi manager should support multiple documents for versioned APIs #7564

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 23 commits into from
Feb 28, 2025

Conversation

davepacheco
Copy link
Collaborator

@davepacheco davepacheco commented Feb 19, 2025

Goal: update the openapi-manager (cargo xtask openapi) to support multiple versions of OpenAPI documents. This is aimed at supporting client-side and server-side-only versioning as described in RFD 432 ("Versioning for internal HTTP APIs"). Critically, we want this workflow to be as simple as possible and as close as possible to the existing one for non-versioned APIs. And it should be really hard to accidentally mess things up (e.g., break compatibility with an older version that's supposed to be supported).

Status: I think this is basically ready for review. There's still some work left (see below) but I don't expect it to change the shape of this all that much and it'd be nice to parallelize here.

This change includes:

  • A rewrite of the OpenAPI manager. The user interface is basically unchanged. But the guts required a rewrite because the previous one was oriented around a simpler model of one OpenAPI document per API, plus maybe one or more extra validation files. The new one is aware of versions and many different types of problems (e.g., extra files, some types of files shouldn't be changeable, etc.) and various kinds of fixes for each one.
  • The dns-server API has been converted from lockstep to versioned. Well, kind of. We still don't have progenitor clients should specify API version progenitor#564, so I'm not able to update the DNS server implementation to support multiple versions. But in terms of the OpenAPI documents, this API is now versioned.

I realize this is a big change. (At least it's mostly new code?) But I didn't see a way to break it up well. If there's anything I can do to help review (e.g., video chat walkthrough), let me know. I'd suggest looking at stuff in this order:

  • dev-tools/openapi-manager/README.adoc: particularly "More about versioned APIs". This gives a brief overview of the goals and how the tool works now.
  • Some changes to the CLI-related parts:
    • The CLI-related files (src/{dispatch,check,generate,list}.rs) have been moved into src/cmd. Some have also been rewritten but they're logically equivalent to what they did before.
    • I'd suggest looking at dispatch.rs, then the rest of the PR, then coming back to the rest of this directory.
    • There's also a new "debug" command that prints out precisely what the tool found from all the sources it was given and how it resolved everything. Normally, the "check" and "generate" commands take care of interpreting all this and printing user-facing messages. The "debug" command is really only for understanding what happened when the tool did something unexpected.
    • "check", "generate", and "debug" all accept the same set of arguments that let you override how the tool finds blessed files, local files, and generated files. This is mainly intended for debugging and testing, though it might be useful for users to override the parent branch for blessed files when using stacked PRs or the like.
  • the guts of "collecting information from all the sources":
    • environment.rs: shows how the tool collects information from its various sources
    • spec_files_{blessed,generated,local}.rs: these are relatively small modules that use a common builder to build up a model of OpenAPI documents found
    • spec_files_generic.rs: has the common stuff used by the specific three sources
  • resolved.rs: the guts of the logic of this tool: given what we found, figure out what to do
  • go back to the CLI parts skipped: "list", "check", and "generate" commands
  • dns-server-api / clients/dns-service-client`: shows what the changes to a versioned API look like
  • anything else

If you want to see this in action: I demo'd this at last Friday's demo day. It's somewhat anti-climactic because the workflow is almost the same for versioned APIs as it was for APIs before (those are now called lockstep APIs). If you want to see exactly what's changed, check out the dns-server-api / clients/dns-service-client changes. This is also described in the README.

Work I plan to do before landing this (but don't want to block review on):

  • Add a few small unit tests.
  • You'll note a bunch of XXX-dap-last-step: these are all basically the same thing. To reduce the risk of mismerge with other changes to src/spec.rs upstream, I have kept the API definitions in src/spec.rs in roughly the same format as they were before. But before I land this, I intend to replace all those with literal definitions of ManagedApiConfig instead and rip out src/spec.rs altogether.
  • Re-run my manual test plan (I've done all this before, but I will do it again after any changes from code review):
    • Basic lockstep workflow (file missing, file stale, file up-to-date)
    • Basic versioned workflow (add version, make change; file missing, file stale, file up-to-date)
    • Retiring an old supported version
    • Error cases:
      • Cannot change a blessed version
      • Removed document for a blessed version
      • Wrong document for a blessed version
      • Warning (not error) generated for unexpected files
      • generated for stuff we can't parse (e.g., bad checksum, bad filename)
      • validation error: bad JSON file
      • validation error: not valid OpenAPI
      • validation error: Oxide-specific lint
      • validation error: stale or missing "extra" file (nexus_tags.txt)
      • bogus symlink: missing, stale
    • Test merge with a change that adds a version (older, the same as, newer than) a locally-added one.

Work to do after landing this:

  • OpenAPI manager should be usable by other repos #7569
  • Write a small integration test suite. I think this will be easier after it's extracted because we can parametrize it by 1-2 APIs for testing only. We can use the flags that allow loading files from disk and exercise basically every possible problem/fix case. Although it's common for dev tools to have minimal tests (and there were no tests here before), I think it'd be worth it (and not too much work) to have some here.
  • @ahl is working on a tool for better determining compatibility between two OpenAPI specs. Right now, this tool considers any change to the OpenAPI spec as breaking. That includes even things like doc changes. Once that tool is available, I'll update this to use it. (But I think we'll still want to treat almost everything as breaking.)

Copy link
Contributor

@sunshowers sunshowers left a comment

Choose a reason for hiding this comment

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

have to run, but have gotten through to spec_files_generated.rs. More tomorrow.

Comment on lines +199 to +201
* If there is a blessed file for that version, then the version is blessed. The generated file must exactly match the blessed one. If not, the tool cannot fix this. You have to undo whatever changes you made that affected the blessed version. (See above on how to make changes to the API trait without affecting older versions.)
* If there is no blessed file for that version, then the version is locally-added. There should be exactly one local file for it and it should exactly match the generated file. The tool can fix any problems here by removing all local files and generating a new one based on the generated one.
* The tool also ensures that a "latest" symlink exists and points to the highest-numbered OpenAPI document.
Copy link
Contributor

Choose a reason for hiding this comment

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

Thoughts on putting a Mermaid flowchart here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I took a swing at adding one in 62a416d. Let me know what you think.


for api in apis.iter_apis() {
if api.is_lockstep() {
for version in api.iter_versions_semver() {
Copy link
Contributor

Choose a reason for hiding this comment

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

There's just one version here, right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yup.

Copy link
Contributor

Choose a reason for hiding this comment

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

We have the exactly-once iterator now, would it make sense to use it here?

Copy link
Contributor

@sunshowers sunshowers left a comment

Choose a reason for hiding this comment

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

Reviewed the rest of the files, they all look great. I'll play around with the latest version of your PR a bit locally as well. Thank you for all of this work.

@sunshowers
Copy link
Contributor

Yeah, some local experimentation shows this is really fantastic. All the important error cases are taken care of -- this is so great and I'm excited we're going to get this into the hands of folks.

With my comments above this is good to go.

Copy link
Contributor

@sunshowers sunshowers left a comment

Choose a reason for hiding this comment

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

Thanks!

@davepacheco davepacheco marked this pull request as ready for review February 28, 2025 04:07
@davepacheco
Copy link
Collaborator Author

davepacheco commented Feb 28, 2025

Final manual testing

Testing from:

$ git status
On branch dap/versioning-dev-workflow
Your branch is up to date with 'origin/dap/versioning-dev-workflow'.

nothing to commit, working tree clean

$ git log -1
commit 55a49a9bf6f8e977f6e9d6e43792eeb44c93ed09 (HEAD -> dap/versioning-dev-workflow, origin/dap/versioning-dev-workflow)
Author: David Pacheco <[email protected]>
Date:   Thu Feb 27 15:59:50 2025 -0800

    fix

Fresh clone:

$ cargo xtask openapi check
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.49s
     Running `target/debug/xtask openapi check`
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.89s
     Running `target/debug/openapi-manager check`
     -------
  Generating OpenAPI documents from API definitions ... 
     Loading local OpenAPI documents from "/home/dap/omicron-merge/openapi" ... 
     Loading blessed OpenAPI documents from git revision "origin/main" path "openapi"
     Warning skipping file "dns-server.json": this API is not a lockstep API
     -------
    Checking 14 OpenAPI documents...
       Fresh bootstrap-agent (lockstep v0.0.1): Bootstrap Agent API
       Fresh clickhouse-admin-keeper (lockstep v0.0.1): ClickHouse Cluster Admin Keeper API
       Fresh clickhouse-admin-server (lockstep v0.0.1): ClickHouse Cluster Admin Server API
       Fresh clickhouse-admin-single (lockstep v0.0.1): ClickHouse Single-Node Admin Server API
       Fresh cockroach-admin (lockstep v0.0.1): CockroachDB Cluster Admin API
       Fresh dns-server (versioned v1.0.0 (added locally)): Internal DNS
       Fresh dns-server "latest" symlink
       Fresh gateway (lockstep v0.0.1): Oxide Management Gateway Service API
       Fresh installinator (lockstep v0.0.1): Installinator API
       Fresh nexus (lockstep v20250212.0.0): Oxide Region API
       Fresh nexus-internal (lockstep v0.0.1): Nexus internal API
       Fresh oximeter (lockstep v0.0.1): Oxide Oximeter API
       Fresh repo-depot (lockstep v0.0.1): Oxide TUF Repo Depot API
       Fresh sled-agent (lockstep v0.0.1): Oxide Sled Agent API
       Fresh wicketd (lockstep v0.0.1): Oxide Technician Port Control Service
     -------
     Success 14 documents checked: 15 fresh, 0 stale, 0 failed, 0 other problems

$ echo $?
0

$ cargo xtask openapi generate
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.49s
     Running `target/debug/xtask openapi generate`
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.88s
     Running `target/debug/openapi-manager generate`
  Generating OpenAPI documents from API definitions ... 
     Loading local OpenAPI documents from "/home/dap/omicron-merge/openapi" ... 
     Loading blessed OpenAPI documents from git revision "origin/main" path "openapi"
     Warning skipping file "dns-server.json": this API is not a lockstep API
     -------
    Updating 14 OpenAPI documents...
   Unchanged bootstrap-agent (lockstep v0.0.1): Bootstrap Agent API
   Unchanged clickhouse-admin-keeper (lockstep v0.0.1): ClickHouse Cluster Admin Keeper API
   Unchanged clickhouse-admin-server (lockstep v0.0.1): ClickHouse Cluster Admin Server API
   Unchanged clickhouse-admin-single (lockstep v0.0.1): ClickHouse Single-Node Admin Server API
   Unchanged cockroach-admin (lockstep v0.0.1): CockroachDB Cluster Admin API
   Unchanged dns-server (versioned v1.0.0 (added locally)): Internal DNS
   Unchanged dns-server "latest" symlink
   Unchanged gateway (lockstep v0.0.1): Oxide Management Gateway Service API
   Unchanged installinator (lockstep v0.0.1): Installinator API
   Unchanged nexus (lockstep v20250212.0.0): Oxide Region API
   Unchanged nexus-internal (lockstep v0.0.1): Nexus internal API
   Unchanged oximeter (lockstep v0.0.1): Oxide Oximeter API
   Unchanged repo-depot (lockstep v0.0.1): Oxide TUF Repo Depot API
   Unchanged sled-agent (lockstep v0.0.1): Oxide Sled Agent API
   Unchanged wicketd (lockstep v0.0.1): Oxide Technician Port Control Service
     Loading local OpenAPI documents from "/home/dap/omicron-merge/openapi" ... 
  Rechecking all local files
     -------
     Success 14 documents: 0 changes made, 14 unchanged, 0 failed

$ echo $?
0

$ git status
On branch dap/versioning-dev-workflow
Your branch is up to date with 'origin/dap/versioning-dev-workflow'.

nothing to commit, working tree clean

Basic lockstep workflow (file missing, file stale, file up-to-date):

$ git diff
diff --git a/sled-agent/types/src/rack_ops.rs b/sled-agent/types/src/rack_ops.rs
index 3ff56a02f..983578536 100644
--- a/sled-agent/types/src/rack_ops.rs
+++ b/sled-agent/types/src/rack_ops.rs
@@ -24,6 +24,7 @@ pub enum RackOperationStatus {
         id: RackInitUuid,
         message: String,
     },
+    Dummy,
     InitializationPanicked {
         id: RackInitUuid,
     },

$ cargo xtask openapi check
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.75s
     Running `target/debug/xtask openapi check`
   Compiling sled-agent-types v0.1.0 (/home/dap/omicron-merge/sled-agent/types)
   Compiling sled-agent-api v0.1.0 (/home/dap/omicron-merge/sled-agent/api)
   Compiling bootstrap-agent-api v0.1.0 (/home/dap/omicron-merge/sled-agent/bootstrap-agent-api)
   Compiling openapi-manager v0.1.0 (/home/dap/omicron-merge/dev-tools/openapi-manager)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 10.33s
     Running `target/debug/openapi-manager check`
     -------
  Generating OpenAPI documents from API definitions ... 
     Loading local OpenAPI documents from "/home/dap/omicron-merge/openapi" ... 
     Loading blessed OpenAPI documents from git revision "origin/main" path "openapi"
     Warning skipping file "dns-server.json": this API is not a lockstep API
     -------
    Checking 14 OpenAPI documents...
       Stale bootstrap-agent (lockstep v0.0.1): Bootstrap Agent API
         problem: For this lockstep API, OpenAPI document generated from the current
                  code does not match the local file: "bootstrap-agent.json".  This
                  tool can update the local file for you.
             fix: will rewrite lockstep file bootstrap-agent.json from generated
             --- /home/dap/omicron-merge/openapi/bootstrap-agent.json
             +++ /home/dap/omicron-merge/openapi/bootstrap-agent.json
             @@ -1139,6 +1139,20 @@
                        {
                          "type": "object",
                          "properties": {
             +              "status": {
             +                "type": "string",
             +                "enum": [
             +                  "dummy"
             +                ]
             +              }
             +            },
             +            "required": [
             +              "status"
             +            ]
             +          },
             +          {
             +            "type": "object",
             +            "properties": {
                            "id": {
                              "$ref": "#/components/schemas/TypedUuidForRackInitKind"
                            },

       Fresh clickhouse-admin-keeper (lockstep v0.0.1): ClickHouse Cluster Admin Keeper API
       Fresh clickhouse-admin-server (lockstep v0.0.1): ClickHouse Cluster Admin Server API
       Fresh clickhouse-admin-single (lockstep v0.0.1): ClickHouse Single-Node Admin Server API
       Fresh cockroach-admin (lockstep v0.0.1): CockroachDB Cluster Admin API
       Fresh dns-server (versioned v1.0.0 (added locally)): Internal DNS
       Fresh dns-server "latest" symlink
       Fresh gateway (lockstep v0.0.1): Oxide Management Gateway Service API
       Fresh installinator (lockstep v0.0.1): Installinator API
       Fresh nexus (lockstep v20250212.0.0): Oxide Region API
       Fresh nexus-internal (lockstep v0.0.1): Nexus internal API
       Fresh oximeter (lockstep v0.0.1): Oxide Oximeter API
       Fresh repo-depot (lockstep v0.0.1): Oxide TUF Repo Depot API
       Fresh sled-agent (lockstep v0.0.1): Oxide Sled Agent API
       Fresh wicketd (lockstep v0.0.1): Oxide Technician Port Control Service
     -------
       Stale 14 documents checked: 14 fresh, 1 stale, 0 failed, 0 other problems
             (run cargo xtask openapi generate to update)

$ echo $?
4

$ cargo xtask openapi generate
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.54s
     Running `target/debug/xtask openapi generate`
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.93s
     Running `target/debug/openapi-manager generate`
  Generating OpenAPI documents from API definitions ... 
     Loading local OpenAPI documents from "/home/dap/omicron-merge/openapi" ... 
     Loading blessed OpenAPI documents from git revision "origin/main" path "openapi"
     Warning skipping file "dns-server.json": this API is not a lockstep API
     -------
    Updating 14 OpenAPI documents...
       Stale bootstrap-agent (lockstep v0.0.1): Bootstrap Agent API
       Fixed updated /home/dap/omicron-merge/openapi/bootstrap-agent.json: Updated
   Unchanged clickhouse-admin-keeper (lockstep v0.0.1): ClickHouse Cluster Admin Keeper API
   Unchanged clickhouse-admin-server (lockstep v0.0.1): ClickHouse Cluster Admin Server API
   Unchanged clickhouse-admin-single (lockstep v0.0.1): ClickHouse Single-Node Admin Server API
   Unchanged cockroach-admin (lockstep v0.0.1): CockroachDB Cluster Admin API
   Unchanged dns-server (versioned v1.0.0 (added locally)): Internal DNS
   Unchanged dns-server "latest" symlink
   Unchanged gateway (lockstep v0.0.1): Oxide Management Gateway Service API
   Unchanged installinator (lockstep v0.0.1): Installinator API
   Unchanged nexus (lockstep v20250212.0.0): Oxide Region API
   Unchanged nexus-internal (lockstep v0.0.1): Nexus internal API
   Unchanged oximeter (lockstep v0.0.1): Oxide Oximeter API
   Unchanged repo-depot (lockstep v0.0.1): Oxide TUF Repo Depot API
   Unchanged sled-agent (lockstep v0.0.1): Oxide Sled Agent API
   Unchanged wicketd (lockstep v0.0.1): Oxide Technician Port Control Service
     Loading local OpenAPI documents from "/home/dap/omicron-merge/openapi" ... 
  Rechecking all local files
     -------
     Success 14 documents: 1 change made, 13 unchanged, 0 failed

$ echo $?
0

...

$ rm -f openapi/wicketd.json 

$ git status
On branch dap/versioning-dev-workflow
Your branch is up to date with 'origin/dap/versioning-dev-workflow'.

Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	deleted:    openapi/wicketd.json

no changes added to commit (use "git add" and/or "git commit -a")

$ cargo xtask openapi check
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.50s
     Running `target/debug/xtask openapi check`
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.89s
     Running `target/debug/openapi-manager check`
     -------
  Generating OpenAPI documents from API definitions ... 
     Loading local OpenAPI documents from "/home/dap/omicron-merge/openapi" ... 
     Loading blessed OpenAPI documents from git revision "origin/main" path "openapi"
     Warning skipping file "dns-server.json": this API is not a lockstep API
     -------
    Checking 14 OpenAPI documents...
       Fresh bootstrap-agent (lockstep v0.0.1): Bootstrap Agent API
       Fresh clickhouse-admin-keeper (lockstep v0.0.1): ClickHouse Cluster Admin Keeper API
       Fresh clickhouse-admin-server (lockstep v0.0.1): ClickHouse Cluster Admin Server API
       Fresh clickhouse-admin-single (lockstep v0.0.1): ClickHouse Single-Node Admin Server API
       Fresh cockroach-admin (lockstep v0.0.1): CockroachDB Cluster Admin API
       Fresh dns-server (versioned v1.0.0 (added locally)): Internal DNS
       Fresh dns-server "latest" symlink
       Fresh gateway (lockstep v0.0.1): Oxide Management Gateway Service API
       Fresh installinator (lockstep v0.0.1): Installinator API
       Fresh nexus (lockstep v20250212.0.0): Oxide Region API
       Fresh nexus-internal (lockstep v0.0.1): Nexus internal API
       Fresh oximeter (lockstep v0.0.1): Oxide Oximeter API
       Fresh repo-depot (lockstep v0.0.1): Oxide TUF Repo Depot API
       Fresh sled-agent (lockstep v0.0.1): Oxide Sled Agent API
       Stale wicketd (lockstep v0.0.1): Oxide Technician Port Control Service
         problem: No local OpenAPI document was found for this lockstep API.  This is
                  only expected if you're adding a new lockstep API.  This tool can
                  generate the file for you.
             fix: will rewrite lockstep file wicketd.json from generated
     -------
       Stale 14 documents checked: 14 fresh, 1 stale, 0 failed, 0 other problems
             (run cargo xtask openapi generate to update)

$ echo $?
4

$ cargo xtask openapi generate
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.50s
     Running `target/debug/xtask openapi generate`
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.88s
     Running `target/debug/openapi-manager generate`
  Generating OpenAPI documents from API definitions ... 
     Loading local OpenAPI documents from "/home/dap/omicron-merge/openapi" ... 
     Loading blessed OpenAPI documents from git revision "origin/main" path "openapi"
     Warning skipping file "dns-server.json": this API is not a lockstep API
     -------
    Updating 14 OpenAPI documents...
   Unchanged bootstrap-agent (lockstep v0.0.1): Bootstrap Agent API
   Unchanged clickhouse-admin-keeper (lockstep v0.0.1): ClickHouse Cluster Admin Keeper API
   Unchanged clickhouse-admin-server (lockstep v0.0.1): ClickHouse Cluster Admin Server API
   Unchanged clickhouse-admin-single (lockstep v0.0.1): ClickHouse Single-Node Admin Server API
   Unchanged cockroach-admin (lockstep v0.0.1): CockroachDB Cluster Admin API
   Unchanged dns-server (versioned v1.0.0 (added locally)): Internal DNS
   Unchanged dns-server "latest" symlink
   Unchanged gateway (lockstep v0.0.1): Oxide Management Gateway Service API
   Unchanged installinator (lockstep v0.0.1): Installinator API
   Unchanged nexus (lockstep v20250212.0.0): Oxide Region API
   Unchanged nexus-internal (lockstep v0.0.1): Nexus internal API
   Unchanged oximeter (lockstep v0.0.1): Oxide Oximeter API
   Unchanged repo-depot (lockstep v0.0.1): Oxide TUF Repo Depot API
   Unchanged sled-agent (lockstep v0.0.1): Oxide Sled Agent API
       Stale wicketd (lockstep v0.0.1): Oxide Technician Port Control Service
       Fixed updated /home/dap/omicron-merge/openapi/wicketd.json: Updated
     Loading local OpenAPI documents from "/home/dap/omicron-merge/openapi" ... 
  Rechecking all local files
     -------
     Success 14 documents: 1 change made, 13 unchanged, 0 failed

$ echo $?
0

$ git status
On branch dap/versioning-dev-workflow
Your branch is up to date with 'origin/dap/versioning-dev-workflow'.

nothing to commit, working tree clean

Basic versioned workflow (add version, make change; file missing, file stale, file up-to-date)

$ git status
On branch dap/versioning-dev-workflow
Your branch is up to date with 'origin/dap/versioning-dev-workflow'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   dns-server-api/src/lib.rs

no changes added to commit (use "git add" and/or "git commit -a")

$ git diff
diff --git a/dns-server-api/src/lib.rs b/dns-server-api/src/lib.rs
index 6cf0b7f1c..af174b014 100644
--- a/dns-server-api/src/lib.rs
+++ b/dns-server-api/src/lib.rs
@@ -107,6 +107,7 @@ api_versions!([
     // |  example for the next person.
     // v
     // (next_int, IDENT),
+    (2, TEST),
     (1, INITIAL),
 ]);
 
@@ -134,6 +135,17 @@ pub trait DnsServerApi {
         rqctx: RequestContext<Self::Context>,
     ) -> Result<HttpResponseOk<DnsConfig>, HttpError>;
 
+    #[endpoint(
+        method = GET,
+        path = "/dummy",
+        versions = VERSION_TEST..,
+    )]
+    async fn dns_dummy(
+        _rqctx: RequestContext<Self::Context>,
+    ) -> Result<HttpResponseOk<u8>, HttpError> {
+        unimplemented!();
+    }
+
     #[endpoint(
         method = PUT,
         path = "/config",

$ cargo xtask openapi check --blessed-from-git=HEAD
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.56s
     Running `target/debug/xtask openapi check --blessed-from-git=HEAD`
   Compiling dns-server-api v0.1.0 (/home/dap/omicron-merge/dns-server-api)
   Compiling openapi-manager v0.1.0 (/home/dap/omicron-merge/dev-tools/openapi-manager)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 8.22s
     Running `target/debug/openapi-manager check --blessed-from-git=HEAD`
     -------
  Generating OpenAPI documents from API definitions ... 
     Loading local OpenAPI documents from "/home/dap/omicron-merge/openapi" ... 
     Loading blessed OpenAPI documents from git revision "HEAD" path "openapi"
     -------
    Checking 15 OpenAPI documents...
       Fresh bootstrap-agent (lockstep v0.0.1): Bootstrap Agent API
       Fresh clickhouse-admin-keeper (lockstep v0.0.1): ClickHouse Cluster Admin Keeper API
       Fresh clickhouse-admin-server (lockstep v0.0.1): ClickHouse Cluster Admin Server API
       Fresh clickhouse-admin-single (lockstep v0.0.1): ClickHouse Single-Node Admin Server API
       Fresh cockroach-admin (lockstep v0.0.1): CockroachDB Cluster Admin API
       Fresh dns-server (versioned v1.0.0 (blessed)): Internal DNS
       Stale dns-server (versioned v2.0.0 (added locally)): Internal DNS
         problem: No OpenAPI document was found for this locally-added API version.
                  This is normal if you have added or changed this API version.  This
                  tool can generate the file for you.
             fix: will write new file dns-server/dns-server-2.0.0-d9825c.json from
                  generated
       Stale dns-server "latest" symlink
         problem: "Latest" symlink for versioned API "dns-server" is stale: points
                  to dns-server-1.0.0-49359e.json, but should be dns-server-2.0.0-
                  d9825c.json
             fix: will update symlink to point to dns-server-2.0.0-d9825c.json
       Fresh gateway (lockstep v0.0.1): Oxide Management Gateway Service API
       Fresh installinator (lockstep v0.0.1): Installinator API
       Fresh nexus (lockstep v20250212.0.0): Oxide Region API
       Fresh nexus-internal (lockstep v0.0.1): Nexus internal API
       Fresh oximeter (lockstep v0.0.1): Oxide Oximeter API
       Fresh repo-depot (lockstep v0.0.1): Oxide TUF Repo Depot API
       Fresh sled-agent (lockstep v0.0.1): Oxide Sled Agent API
       Fresh wicketd (lockstep v0.0.1): Oxide Technician Port Control Service
     -------
       Stale 15 documents checked: 14 fresh, 1 stale, 0 failed, 1 other problem
             (run cargo xtask openapi generate to update)

$ echo $?
4

$ cargo xtask openapi generate --blessed-from-git=HEAD
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.61s
     Running `target/debug/xtask openapi generate --blessed-from-git=HEAD`
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.96s
     Running `target/debug/openapi-manager generate --blessed-from-git=HEAD`
  Generating OpenAPI documents from API definitions ... 
     Loading local OpenAPI documents from "/home/dap/omicron-merge/openapi" ... 
     Loading blessed OpenAPI documents from git revision "HEAD" path "openapi"
     -------
    Updating 15 OpenAPI documents...
   Unchanged bootstrap-agent (lockstep v0.0.1): Bootstrap Agent API
   Unchanged clickhouse-admin-keeper (lockstep v0.0.1): ClickHouse Cluster Admin Keeper API
   Unchanged clickhouse-admin-server (lockstep v0.0.1): ClickHouse Cluster Admin Server API
   Unchanged clickhouse-admin-single (lockstep v0.0.1): ClickHouse Single-Node Admin Server API
   Unchanged cockroach-admin (lockstep v0.0.1): CockroachDB Cluster Admin API
   Unchanged dns-server (versioned v1.0.0 (blessed)): Internal DNS
       Stale dns-server (versioned v2.0.0 (added locally)): Internal DNS
       Fixed created /home/dap/omicron-merge/openapi/dns-server/dns-server-2.0.0-d9825c.json: Updated
       Stale dns-server "latest" symlink
       Fixed wrote link /home/dap/omicron-merge/openapi/dns-server/dns-server-latest.json -> dns-server-2.0.0-d9825c.json
   Unchanged gateway (lockstep v0.0.1): Oxide Management Gateway Service API
   Unchanged installinator (lockstep v0.0.1): Installinator API
   Unchanged nexus (lockstep v20250212.0.0): Oxide Region API
   Unchanged nexus-internal (lockstep v0.0.1): Nexus internal API
   Unchanged oximeter (lockstep v0.0.1): Oxide Oximeter API
   Unchanged repo-depot (lockstep v0.0.1): Oxide TUF Repo Depot API
   Unchanged sled-agent (lockstep v0.0.1): Oxide Sled Agent API
   Unchanged wicketd (lockstep v0.0.1): Oxide Technician Port Control Service
     Loading local OpenAPI documents from "/home/dap/omicron-merge/openapi" ... 
  Rechecking all local files
     -------
     Success 15 documents: 2 changes made, 14 unchanged, 0 failed

$ git status
On branch dap/versioning-dev-workflow
Your branch is up to date with 'origin/dap/versioning-dev-workflow'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   dns-server-api/src/lib.rs
	modified:   openapi/dns-server/dns-server-latest.json

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	openapi/dns-server/dns-server-2.0.0-d9825c.json

no changes added to commit (use "git add" and/or "git commit -a")

$ git diff
diff --git a/dns-server-api/src/lib.rs b/dns-server-api/src/lib.rs
index 6cf0b7f1c..af174b014 100644
--- a/dns-server-api/src/lib.rs
+++ b/dns-server-api/src/lib.rs
@@ -107,6 +107,7 @@ api_versions!([
     // |  example for the next person.
     // v
     // (next_int, IDENT),
+    (2, TEST),
     (1, INITIAL),
 ]);
 
@@ -134,6 +135,17 @@ pub trait DnsServerApi {
         rqctx: RequestContext<Self::Context>,
     ) -> Result<HttpResponseOk<DnsConfig>, HttpError>;
 
+    #[endpoint(
+        method = GET,
+        path = "/dummy",
+        versions = VERSION_TEST..,
+    )]
+    async fn dns_dummy(
+        _rqctx: RequestContext<Self::Context>,
+    ) -> Result<HttpResponseOk<u8>, HttpError> {
+        unimplemented!();
+    }
+
     #[endpoint(
         method = PUT,
         path = "/config",
diff --git a/openapi/dns-server/dns-server-latest.json b/openapi/dns-server/dns-server-latest.json
index d4ceb55e4..40861bc71 120000
--- a/openapi/dns-server/dns-server-latest.json
+++ b/openapi/dns-server/dns-server-latest.json
@@ -1 +1 @@
-dns-server-1.0.0-49359e.json
\ No newline at end of file
+dns-server-2.0.0-d9825c.json
\ No newline at end of file

Error case: cannot change a blessed version

$ git status
On branch dap/versioning-dev-workflow
Your branch is up to date with 'origin/dap/versioning-dev-workflow'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   dns-server-api/src/lib.rs

no changes added to commit (use "git add" and/or "git commit -a")

$ git diff
diff --git a/dns-server-api/src/lib.rs b/dns-server-api/src/lib.rs
index 6cf0b7f1c..e5926cbd8 100644
--- a/dns-server-api/src/lib.rs
+++ b/dns-server-api/src/lib.rs
@@ -134,6 +134,16 @@ pub trait DnsServerApi {
         rqctx: RequestContext<Self::Context>,
     ) -> Result<HttpResponseOk<DnsConfig>, HttpError>;
 
+    #[endpoint(
+        method = GET,
+        path = "/dummy",
+    )]
+    async fn dns_dummy(
+        _rqctx: RequestContext<Self::Context>,
+    ) -> Result<HttpResponseOk<u8>, HttpError> {
+        unimplemented!();
+    }
+
     #[endpoint(
         method = PUT,
         path = "/config",

$ cargo xtask openapi check --blessed-from-git=HEAD
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.66s
     Running `target/debug/xtask openapi check --blessed-from-git=HEAD`
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.15s
     Running `target/debug/openapi-manager check --blessed-from-git=HEAD`
     -------
  Generating OpenAPI documents from API definitions ... 
     Loading local OpenAPI documents from "/home/dap/omicron-merge/openapi" ... 
     Loading blessed OpenAPI documents from git revision "HEAD" path "openapi"
     -------
    Checking 14 OpenAPI documents...
       Fresh bootstrap-agent (lockstep v0.0.1): Bootstrap Agent API
       Fresh clickhouse-admin-keeper (lockstep v0.0.1): ClickHouse Cluster Admin Keeper API
       Fresh clickhouse-admin-server (lockstep v0.0.1): ClickHouse Cluster Admin Server API
       Fresh clickhouse-admin-single (lockstep v0.0.1): ClickHouse Single-Node Admin Server API
       Fresh cockroach-admin (lockstep v0.0.1): CockroachDB Cluster Admin API
     Failure dns-server (versioned v1.0.0 (blessed)): Internal DNS
           error: OpenAPI document generated from the current code is not compatible
                  with the blessed document (from upstream): all changes are
                  considered incompatible right now
       Stale dns-server "latest" symlink
         problem: "Latest" symlink for versioned API "dns-server" is stale: points
                  to dns-server-1.0.0-49359e.json, but should be dns-server-1.0.0-
                  a67f9f.json
             fix: will update symlink to point to dns-server-1.0.0-a67f9f.json
       Fresh gateway (lockstep v0.0.1): Oxide Management Gateway Service API
       Fresh installinator (lockstep v0.0.1): Installinator API
       Fresh nexus (lockstep v20250212.0.0): Oxide Region API
       Fresh nexus-internal (lockstep v0.0.1): Nexus internal API
       Fresh oximeter (lockstep v0.0.1): Oxide Oximeter API
       Fresh repo-depot (lockstep v0.0.1): Oxide TUF Repo Depot API
       Fresh sled-agent (lockstep v0.0.1): Oxide Sled Agent API
       Fresh wicketd (lockstep v0.0.1): Oxide Technician Port Control Service
     -------
     Failure 14 documents checked: 13 fresh, 0 stale, 1 failed, 1 other problem
             (fix failures, then run cargo xtask openapi generate to update)

$ echo $?
100

$ cargo xtask openapi generate --blessed-from-git=HEAD
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.65s
     Running `target/debug/xtask openapi generate --blessed-from-git=HEAD`
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.12s
     Running `target/debug/openapi-manager generate --blessed-from-git=HEAD`
  Generating OpenAPI documents from API definitions ... 
     Loading local OpenAPI documents from "/home/dap/omicron-merge/openapi" ... 
     Loading blessed OpenAPI documents from git revision "HEAD" path "openapi"
     -------
    Updating 14 OpenAPI documents...
    Checking 14 OpenAPI documents...
       Fresh bootstrap-agent (lockstep v0.0.1): Bootstrap Agent API
       Fresh clickhouse-admin-keeper (lockstep v0.0.1): ClickHouse Cluster Admin Keeper API
       Fresh clickhouse-admin-server (lockstep v0.0.1): ClickHouse Cluster Admin Server API
       Fresh clickhouse-admin-single (lockstep v0.0.1): ClickHouse Single-Node Admin Server API
       Fresh cockroach-admin (lockstep v0.0.1): CockroachDB Cluster Admin API
     Failure dns-server (versioned v1.0.0 (blessed)): Internal DNS
           error: OpenAPI document generated from the current code is not compatible
                  with the blessed document (from upstream): all changes are
                  considered incompatible right now
       Stale dns-server "latest" symlink
         problem: "Latest" symlink for versioned API "dns-server" is stale: points
                  to dns-server-1.0.0-49359e.json, but should be dns-server-1.0.0-
                  a67f9f.json
             fix: will update symlink to point to dns-server-1.0.0-a67f9f.json
       Fresh gateway (lockstep v0.0.1): Oxide Management Gateway Service API
       Fresh installinator (lockstep v0.0.1): Installinator API
       Fresh nexus (lockstep v20250212.0.0): Oxide Region API
       Fresh nexus-internal (lockstep v0.0.1): Nexus internal API
       Fresh oximeter (lockstep v0.0.1): Oxide Oximeter API
       Fresh repo-depot (lockstep v0.0.1): Oxide TUF Repo Depot API
       Fresh sled-agent (lockstep v0.0.1): Oxide Sled Agent API
       Fresh wicketd (lockstep v0.0.1): Oxide Technician Port Control Service
     -------
     Failure 14 documents checked: 13 fresh, 0 stale, 1 failed, 1 other problem
             (fix failures, then run cargo xtask openapi generate to update)

$ echo $?
100

@davepacheco
Copy link
Collaborator Author

davepacheco commented Feb 28, 2025

Final manual testing (continued)

Retiring an old supported version:

From the same state as above, removing INITIAL:

$ git status
On branch dap/versioning-dev-workflow
Your branch is up to date with 'origin/dap/versioning-dev-workflow'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   dns-server-api/src/lib.rs
	modified:   openapi/dns-server/dns-server-latest.json

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	openapi/dns-server/dns-server-2.0.0-d9825c.json

no changes added to commit (use "git add" and/or "git commit -a")

$ git diff
diff --git a/dns-server-api/src/lib.rs b/dns-server-api/src/lib.rs
index 6cf0b7f1c..08cba5790 100644
--- a/dns-server-api/src/lib.rs
+++ b/dns-server-api/src/lib.rs
@@ -107,7 +107,7 @@ api_versions!([
     // |  example for the next person.
     // v
     // (next_int, IDENT),
-    (1, INITIAL),
+    (2, TEST),
 ]);
 
 // WHEN CHANGING THE API (part 2 of 2):
@@ -134,6 +134,17 @@ pub trait DnsServerApi {
         rqctx: RequestContext<Self::Context>,
     ) -> Result<HttpResponseOk<DnsConfig>, HttpError>;
 
+    #[endpoint(
+        method = GET,
+        path = "/dummy",
+        versions = VERSION_TEST..,
+    )]
+    async fn dns_dummy(
+        _rqctx: RequestContext<Self::Context>,
+    ) -> Result<HttpResponseOk<u8>, HttpError> {
+        unimplemented!();
+    }
+
     #[endpoint(
         method = PUT,
         path = "/config",
diff --git a/openapi/dns-server/dns-server-latest.json b/openapi/dns-server/dns-server-latest.json
index d4ceb55e4..40861bc71 120000
--- a/openapi/dns-server/dns-server-latest.json
+++ b/openapi/dns-server/dns-server-latest.json
@@ -1 +1 @@
-dns-server-1.0.0-49359e.json
\ No newline at end of file
+dns-server-2.0.0-d9825c.json
\ No newline at end of file

$ cargo xtask openapi check --blessed-from-git=HEAD
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.65s
     Running `target/debug/xtask openapi check --blessed-from-git=HEAD`
   Compiling dns-server-api v0.1.0 (/home/dap/omicron-merge/dns-server-api)
   Compiling openapi-manager v0.1.0 (/home/dap/omicron-merge/dev-tools/openapi-manager)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 9.09s
     Running `target/debug/openapi-manager check --blessed-from-git=HEAD`
     -------
  Generating OpenAPI documents from API definitions ... 
     Loading local OpenAPI documents from "/home/dap/omicron-merge/openapi" ... 
     Loading blessed OpenAPI documents from git revision "HEAD" path "openapi"
     -------
    Checking 14 OpenAPI documents...
       Fresh bootstrap-agent (lockstep v0.0.1): Bootstrap Agent API
       Fresh clickhouse-admin-keeper (lockstep v0.0.1): ClickHouse Cluster Admin Keeper API
       Fresh clickhouse-admin-server (lockstep v0.0.1): ClickHouse Cluster Admin Server API
       Fresh clickhouse-admin-single (lockstep v0.0.1): ClickHouse Single-Node Admin Server API
       Fresh cockroach-admin (lockstep v0.0.1): CockroachDB Cluster Admin API
       Fresh dns-server (versioned v2.0.0 (added locally)): Internal DNS
       Fresh dns-server "latest" symlink
       Fresh gateway (lockstep v0.0.1): Oxide Management Gateway Service API
       Fresh installinator (lockstep v0.0.1): Installinator API
       Fresh nexus (lockstep v20250212.0.0): Oxide Region API
       Fresh nexus-internal (lockstep v0.0.1): Nexus internal API
       Fresh oximeter (lockstep v0.0.1): Oxide Oximeter API
       Fresh repo-depot (lockstep v0.0.1): Oxide TUF Repo Depot API
       Fresh sled-agent (lockstep v0.0.1): Oxide Sled Agent API
       Fresh wicketd (lockstep v0.0.1): Oxide Technician Port Control Service

       Other problems not associated with a specific supported API version:
         problem: A local OpenAPI document was found that does not correspond to
                  a supported version of this API: dns-server/dns-server-1.0.0-
                  49359e.json.  This is unusual, but it could happen if you're
                  either retiring an older version of this API or if you created this
                  version in this branch and later merged with upstream and had to
                  change your local version number.  In either case, this tool can
                  remove the unused file for you.
             fix: will delete file: dns-server/dns-server-1.0.0-49359e.json

        Note API dns-server version 1.0.0: formerly blessed version has been removed.
             This version will no longer be supported!  This will break upgrade from
             software that still uses this version.  If this is unexpected, check the
             list of supported versions in Rust for a possible mismerge.

     -------
       Stale 14 documents checked: 15 fresh, 0 stale, 0 failed, 1 other problem
             (run cargo xtask openapi generate to update)

$ echo $?
4

$ cargo xtask openapi generate --blessed-from-git=HEAD
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.58s
     Running `target/debug/xtask openapi generate --blessed-from-git=HEAD`
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.95s
     Running `target/debug/openapi-manager generate --blessed-from-git=HEAD`
  Generating OpenAPI documents from API definitions ... 
     Loading local OpenAPI documents from "/home/dap/omicron-merge/openapi" ... 
     Loading blessed OpenAPI documents from git revision "HEAD" path "openapi"
     -------
    Updating 14 OpenAPI documents...
   Unchanged bootstrap-agent (lockstep v0.0.1): Bootstrap Agent API
   Unchanged clickhouse-admin-keeper (lockstep v0.0.1): ClickHouse Cluster Admin Keeper API
   Unchanged clickhouse-admin-server (lockstep v0.0.1): ClickHouse Cluster Admin Server API
   Unchanged clickhouse-admin-single (lockstep v0.0.1): ClickHouse Single-Node Admin Server API
   Unchanged cockroach-admin (lockstep v0.0.1): CockroachDB Cluster Admin API
   Unchanged dns-server (versioned v2.0.0 (added locally)): Internal DNS
   Unchanged dns-server "latest" symlink
   Unchanged gateway (lockstep v0.0.1): Oxide Management Gateway Service API
   Unchanged installinator (lockstep v0.0.1): Installinator API
   Unchanged nexus (lockstep v20250212.0.0): Oxide Region API
   Unchanged nexus-internal (lockstep v0.0.1): Nexus internal API
   Unchanged oximeter (lockstep v0.0.1): Oxide Oximeter API
   Unchanged repo-depot (lockstep v0.0.1): Oxide TUF Repo Depot API
   Unchanged sled-agent (lockstep v0.0.1): Oxide Sled Agent API
   Unchanged wicketd (lockstep v0.0.1): Oxide Technician Port Control Service
       Fixed removed /home/dap/omicron-merge/openapi/dns-server/dns-server-1.0.0-49359e.json
     Loading local OpenAPI documents from "/home/dap/omicron-merge/openapi" ... 
  Rechecking all local files
     -------
     Success 14 documents: 1 change made, 14 unchanged, 0 failed

$ echo $?
0

$ cargo xtask openapi check --blessed-from-git=HEAD
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.64s
     Running `target/debug/xtask openapi check --blessed-from-git=HEAD`
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.10s
     Running `target/debug/openapi-manager check --blessed-from-git=HEAD`
     -------
  Generating OpenAPI documents from API definitions ... 
     Loading local OpenAPI documents from "/home/dap/omicron-merge/openapi" ... 
     Loading blessed OpenAPI documents from git revision "HEAD" path "openapi"
     -------
    Checking 14 OpenAPI documents...
       Fresh bootstrap-agent (lockstep v0.0.1): Bootstrap Agent API
       Fresh clickhouse-admin-keeper (lockstep v0.0.1): ClickHouse Cluster Admin Keeper API
       Fresh clickhouse-admin-server (lockstep v0.0.1): ClickHouse Cluster Admin Server API
       Fresh clickhouse-admin-single (lockstep v0.0.1): ClickHouse Single-Node Admin Server API
       Fresh cockroach-admin (lockstep v0.0.1): CockroachDB Cluster Admin API
       Fresh dns-server (versioned v2.0.0 (added locally)): Internal DNS
       Fresh dns-server "latest" symlink
       Fresh gateway (lockstep v0.0.1): Oxide Management Gateway Service API
       Fresh installinator (lockstep v0.0.1): Installinator API
       Fresh nexus (lockstep v20250212.0.0): Oxide Region API
       Fresh nexus-internal (lockstep v0.0.1): Nexus internal API
       Fresh oximeter (lockstep v0.0.1): Oxide Oximeter API
       Fresh repo-depot (lockstep v0.0.1): Oxide TUF Repo Depot API
       Fresh sled-agent (lockstep v0.0.1): Oxide Sled Agent API
       Fresh wicketd (lockstep v0.0.1): Oxide Technician Port Control Service

        Note API dns-server version 1.0.0: formerly blessed version has been removed.
             This version will no longer be supported!  This will break upgrade from
             software that still uses this version.  If this is unexpected, check the
             list of supported versions in Rust for a possible mismerge.

     -------
     Success 14 documents checked: 15 fresh, 0 stale, 0 failed, 0 other problems

$ echo $?
0

Error case: remove local document for a blessed version that hasn't been retired:


$ git status
On branch dap/versioning-dev-workflow
Your branch is up to date with 'origin/dap/versioning-dev-workflow'.

Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   dns-server-api/src/lib.rs
	deleted:    openapi/dns-server/dns-server-1.0.0-49359e.json
	modified:   openapi/dns-server/dns-server-latest.json

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	openapi/dns-server/dns-server-2.0.0-d9825c.json

no changes added to commit (use "git add" and/or "git commit -a")

$ git diff dns-server-api/src/lib.rs openapi/dns-server/dns-server-latest.json
diff --git a/dns-server-api/src/lib.rs b/dns-server-api/src/lib.rs
index 6cf0b7f1c..af174b014 100644
--- a/dns-server-api/src/lib.rs
+++ b/dns-server-api/src/lib.rs
@@ -107,6 +107,7 @@ api_versions!([
     // |  example for the next person.
     // v
     // (next_int, IDENT),
+    (2, TEST),
     (1, INITIAL),
 ]);
 
@@ -134,6 +135,17 @@ pub trait DnsServerApi {
         rqctx: RequestContext<Self::Context>,
     ) -> Result<HttpResponseOk<DnsConfig>, HttpError>;
 
+    #[endpoint(
+        method = GET,
+        path = "/dummy",
+        versions = VERSION_TEST..,
+    )]
+    async fn dns_dummy(
+        _rqctx: RequestContext<Self::Context>,
+    ) -> Result<HttpResponseOk<u8>, HttpError> {
+        unimplemented!();
+    }
+
     #[endpoint(
         method = PUT,
         path = "/config",
diff --git a/openapi/dns-server/dns-server-latest.json b/openapi/dns-server/dns-server-latest.json
index d4ceb55e4..40861bc71 120000
--- a/openapi/dns-server/dns-server-latest.json
+++ b/openapi/dns-server/dns-server-latest.json
@@ -1 +1 @@
-dns-server-1.0.0-49359e.json
\ No newline at end of file
+dns-server-2.0.0-d9825c.json
\ No newline at end of file

$ cargo xtask openapi check --blessed-from-git=HEAD
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.69s
     Running `target/debug/xtask openapi check --blessed-from-git=HEAD`
   Compiling dns-server-api v0.1.0 (/home/dap/omicron-merge/dns-server-api)
   Compiling openapi-manager v0.1.0 (/home/dap/omicron-merge/dev-tools/openapi-manager)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 9.25s
     Running `target/debug/openapi-manager check --blessed-from-git=HEAD`
     -------
  Generating OpenAPI documents from API definitions ... 
     Loading local OpenAPI documents from "/home/dap/omicron-merge/openapi" ... 
     Loading blessed OpenAPI documents from git revision "HEAD" path "openapi"
     -------
    Checking 15 OpenAPI documents...
       Fresh bootstrap-agent (lockstep v0.0.1): Bootstrap Agent API
       Fresh clickhouse-admin-keeper (lockstep v0.0.1): ClickHouse Cluster Admin Keeper API
       Fresh clickhouse-admin-server (lockstep v0.0.1): ClickHouse Cluster Admin Server API
       Fresh clickhouse-admin-single (lockstep v0.0.1): ClickHouse Single-Node Admin Server API
       Fresh cockroach-admin (lockstep v0.0.1): CockroachDB Cluster Admin API
     Failure dns-server (versioned v1.0.0 (blessed)): Internal DNS
           error: This version is blessed, and it's a supported version, but it's
                  missing a local OpenAPI document.  This is unusual.  If you
                  intended to remove this version, you must also update the list of
                  supported versions in Rust.  If you didn't, restore the file from
                  git: dns-server/dns-server-1.0.0-49359e.json
       Fresh dns-server (versioned v2.0.0 (added locally)): Internal DNS
       Fresh dns-server "latest" symlink
       Fresh gateway (lockstep v0.0.1): Oxide Management Gateway Service API
       Fresh installinator (lockstep v0.0.1): Installinator API
       Fresh nexus (lockstep v20250212.0.0): Oxide Region API
       Fresh nexus-internal (lockstep v0.0.1): Nexus internal API
       Fresh oximeter (lockstep v0.0.1): Oxide Oximeter API
       Fresh repo-depot (lockstep v0.0.1): Oxide TUF Repo Depot API
       Fresh sled-agent (lockstep v0.0.1): Oxide Sled Agent API
       Fresh wicketd (lockstep v0.0.1): Oxide Technician Port Control Service
     -------
     Failure 15 documents checked: 15 fresh, 0 stale, 1 failed, 0 other problems
             (fix failures, then run cargo xtask openapi generate to update)

$ echo $?
100

$ cargo xtask openapi generate --blessed-from-git=HEAD
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.58s
     Running `target/debug/xtask openapi generate --blessed-from-git=HEAD`
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.98s
     Running `target/debug/openapi-manager generate --blessed-from-git=HEAD`
  Generating OpenAPI documents from API definitions ... 
     Loading local OpenAPI documents from "/home/dap/omicron-merge/openapi" ... 
     Loading blessed OpenAPI documents from git revision "HEAD" path "openapi"
     -------
    Updating 15 OpenAPI documents...
    Checking 15 OpenAPI documents...
       Fresh bootstrap-agent (lockstep v0.0.1): Bootstrap Agent API
       Fresh clickhouse-admin-keeper (lockstep v0.0.1): ClickHouse Cluster Admin Keeper API
       Fresh clickhouse-admin-server (lockstep v0.0.1): ClickHouse Cluster Admin Server API
       Fresh clickhouse-admin-single (lockstep v0.0.1): ClickHouse Single-Node Admin Server API
       Fresh cockroach-admin (lockstep v0.0.1): CockroachDB Cluster Admin API
     Failure dns-server (versioned v1.0.0 (blessed)): Internal DNS
           error: This version is blessed, and it's a supported version, but it's
                  missing a local OpenAPI document.  This is unusual.  If you
                  intended to remove this version, you must also update the list of
                  supported versions in Rust.  If you didn't, restore the file from
                  git: dns-server/dns-server-1.0.0-49359e.json
       Fresh dns-server (versioned v2.0.0 (added locally)): Internal DNS
       Fresh dns-server "latest" symlink
       Fresh gateway (lockstep v0.0.1): Oxide Management Gateway Service API
       Fresh installinator (lockstep v0.0.1): Installinator API
       Fresh nexus (lockstep v20250212.0.0): Oxide Region API
       Fresh nexus-internal (lockstep v0.0.1): Nexus internal API
       Fresh oximeter (lockstep v0.0.1): Oxide Oximeter API
       Fresh repo-depot (lockstep v0.0.1): Oxide TUF Repo Depot API
       Fresh sled-agent (lockstep v0.0.1): Oxide Sled Agent API
       Fresh wicketd (lockstep v0.0.1): Oxide Technician Port Control Service
     -------
     Failure 15 documents checked: 15 fresh, 0 stale, 1 failed, 0 other problems
             (fix failures, then run cargo xtask openapi generate to update)

$ echo $?
100

Error cases: hash mismatch, corrupt document for a blessed version.

It's a little tricky to corrupt the file in a way that really exercises something interesting. If you just change its contents, you get a hash mismatch:

$ cargo xtask openapi check --blessed-from-git=HEAD
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.58s
     Running `target/debug/xtask openapi check --blessed-from-git=HEAD`
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.97s
     Running `target/debug/openapi-manager check --blessed-from-git=HEAD`
     -------
  Generating OpenAPI documents from API definitions ... 
     Loading local OpenAPI documents from "/home/dap/omicron-merge/openapi" ... 
     Failure file "dns-server/dns-server-1.0.0-49359e.json": computed hash "050b2c", but file name has different hash "49359e"
failure: bailing out after 1 error above

So I renamed the file with the right hash and tried again:

$ git mv "openapi/dns-server/dns-server-1.0.0-49359e.json" "openapi/dns-server/dns-server-1.0.0-050b2c.json"

$ cargo xtask openapi check --blessed-from-git=HEAD
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.62s
     Running `target/debug/xtask openapi check --blessed-from-git=HEAD`
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.00s
     Running `target/debug/openapi-manager check --blessed-from-git=HEAD`
     -------
  Generating OpenAPI documents from API definitions ... 
     Loading local OpenAPI documents from "/home/dap/omicron-merge/openapi" ... 
     Loading blessed OpenAPI documents from git revision "HEAD" path "openapi"
     -------
    Checking 15 OpenAPI documents...
       Fresh bootstrap-agent (lockstep v0.0.1): Bootstrap Agent API
       Fresh clickhouse-admin-keeper (lockstep v0.0.1): ClickHouse Cluster Admin Keeper API
       Fresh clickhouse-admin-server (lockstep v0.0.1): ClickHouse Cluster Admin Server API
       Fresh clickhouse-admin-single (lockstep v0.0.1): ClickHouse Single-Node Admin Server API
       Fresh cockroach-admin (lockstep v0.0.1): CockroachDB Cluster Admin API
     Failure dns-server (versioned v1.0.0 (blessed)): Internal DNS
           error: This version is blessed, and it's a supported version, but it's
                  missing a local OpenAPI document.  This is unusual.  If you
                  intended to remove this version, you must also update the list of
                  supported versions in Rust.  If you didn't, restore the file from
                  git: dns-server/dns-server-1.0.0-49359e.json
         problem: For this blessed version, found an extra OpenAPI document that
                  does not match the blessed (upstream) OpenAPI document: dns-server/
                  dns-server-1.0.0-050b2c.json.  This can happen if you created this
                  version of the API in this branch, then merged with an upstream
                  commit that also added the same version number.  In that case, you
                  likely already bumped your local version number (when you merged
                  the list of supported versions in Rust) and this file is vestigial.
                  This tool can remove the unused file for you.
             fix: will delete file: dns-server/dns-server-1.0.0-050b2c.json
       Fresh dns-server (versioned v2.0.0 (added locally)): Internal DNS
       Fresh dns-server "latest" symlink
       Fresh gateway (lockstep v0.0.1): Oxide Management Gateway Service API
       Fresh installinator (lockstep v0.0.1): Installinator API
       Fresh nexus (lockstep v20250212.0.0): Oxide Region API
       Fresh nexus-internal (lockstep v0.0.1): Nexus internal API
       Fresh oximeter (lockstep v0.0.1): Oxide Oximeter API
       Fresh repo-depot (lockstep v0.0.1): Oxide TUF Repo Depot API
       Fresh sled-agent (lockstep v0.0.1): Oxide Sled Agent API
       Fresh wicketd (lockstep v0.0.1): Oxide Technician Port Control Service
     -------
     Failure 15 documents checked: 15 fresh, 0 stale, 1 failed, 0 other problems
             (fix failures, then run cargo xtask openapi generate to update)


$ echo $?
100

$ cargo xtask openapi generate --blessed-from-git=HEAD
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.67s
     Running `target/debug/xtask openapi generate --blessed-from-git=HEAD`
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.10s
     Running `target/debug/openapi-manager generate --blessed-from-git=HEAD`
  Generating OpenAPI documents from API definitions ... 
     Loading local OpenAPI documents from "/home/dap/omicron-merge/openapi" ... 
     Loading blessed OpenAPI documents from git revision "HEAD" path "openapi"
     -------
    Updating 15 OpenAPI documents...
    Checking 15 OpenAPI documents...
       Fresh bootstrap-agent (lockstep v0.0.1): Bootstrap Agent API
       Fresh clickhouse-admin-keeper (lockstep v0.0.1): ClickHouse Cluster Admin Keeper API
       Fresh clickhouse-admin-server (lockstep v0.0.1): ClickHouse Cluster Admin Server API
       Fresh clickhouse-admin-single (lockstep v0.0.1): ClickHouse Single-Node Admin Server API
       Fresh cockroach-admin (lockstep v0.0.1): CockroachDB Cluster Admin API
     Failure dns-server (versioned v1.0.0 (blessed)): Internal DNS
           error: This version is blessed, and it's a supported version, but it's
                  missing a local OpenAPI document.  This is unusual.  If you
                  intended to remove this version, you must also update the list of
                  supported versions in Rust.  If you didn't, restore the file from
                  git: dns-server/dns-server-1.0.0-49359e.json
         problem: For this blessed version, found an extra OpenAPI document that
                  does not match the blessed (upstream) OpenAPI document: dns-server/
                  dns-server-1.0.0-050b2c.json.  This can happen if you created this
                  version of the API in this branch, then merged with an upstream
                  commit that also added the same version number.  In that case, you
                  likely already bumped your local version number (when you merged
                  the list of supported versions in Rust) and this file is vestigial.
                  This tool can remove the unused file for you.
             fix: will delete file: dns-server/dns-server-1.0.0-050b2c.json
       Fresh dns-server (versioned v2.0.0 (added locally)): Internal DNS
       Fresh dns-server "latest" symlink
       Fresh gateway (lockstep v0.0.1): Oxide Management Gateway Service API
       Fresh installinator (lockstep v0.0.1): Installinator API
       Fresh nexus (lockstep v20250212.0.0): Oxide Region API
       Fresh nexus-internal (lockstep v0.0.1): Nexus internal API
       Fresh oximeter (lockstep v0.0.1): Oxide Oximeter API
       Fresh repo-depot (lockstep v0.0.1): Oxide TUF Repo Depot API
       Fresh sled-agent (lockstep v0.0.1): Oxide Sled Agent API
       Fresh wicketd (lockstep v0.0.1): Oxide Technician Port Control Service
     -------
     Failure 15 documents checked: 15 fresh, 0 stale, 1 failed, 0 other problems
             (fix failures, then run cargo xtask openapi generate to update)

$ echo $?
100

@davepacheco
Copy link
Collaborator Author

davepacheco commented Feb 28, 2025

Final manual testing (continued)

Warning (not error) for unexpected files:

$ touch openapi/dummy

$ cargo xtask openapi check 
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.66s
     Running `target/debug/xtask openapi check`
   Compiling dns-server-api v0.1.0 (/home/dap/omicron-merge/dns-server-api)
   Compiling openapi-manager v0.1.0 (/home/dap/omicron-merge/dev-tools/openapi-manager)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 9.02s
     Running `target/debug/openapi-manager check`
     -------
  Generating OpenAPI documents from API definitions ... 
     Loading local OpenAPI documents from "/home/dap/omicron-merge/openapi" ... 
     Warning skipping file "dummy": expected lockstep API file name to end in ".json"
     Loading blessed OpenAPI documents from git revision "origin/main" path "openapi"
     Warning skipping file "dns-server.json": this API is not a lockstep API
     -------
    Checking 14 OpenAPI documents...
       Fresh bootstrap-agent (lockstep v0.0.1): Bootstrap Agent API
       Fresh clickhouse-admin-keeper (lockstep v0.0.1): ClickHouse Cluster Admin Keeper API
       Fresh clickhouse-admin-server (lockstep v0.0.1): ClickHouse Cluster Admin Server API
       Fresh clickhouse-admin-single (lockstep v0.0.1): ClickHouse Single-Node Admin Server API
       Fresh cockroach-admin (lockstep v0.0.1): CockroachDB Cluster Admin API
       Fresh dns-server (versioned v1.0.0 (added locally)): Internal DNS
       Fresh dns-server "latest" symlink
       Fresh gateway (lockstep v0.0.1): Oxide Management Gateway Service API
       Fresh installinator (lockstep v0.0.1): Installinator API
       Fresh nexus (lockstep v20250212.0.0): Oxide Region API
       Fresh nexus-internal (lockstep v0.0.1): Nexus internal API
       Fresh oximeter (lockstep v0.0.1): Oxide Oximeter API
       Fresh repo-depot (lockstep v0.0.1): Oxide TUF Repo Depot API
       Fresh sled-agent (lockstep v0.0.1): Oxide Sled Agent API
       Fresh wicketd (lockstep v0.0.1): Oxide Technician Port Control Service
     -------
     Success 14 documents checked: 15 fresh, 0 stale, 0 failed, 0 other problems

$ echo $?
0

Error for files that look more like mistakes:

$ touch openapi/dummy.json

$ cargo xtask openapi check 
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.56s
     Running `target/debug/xtask openapi check`
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.99s
     Running `target/debug/openapi-manager check`
     -------
  Generating OpenAPI documents from API definitions ... 
     Loading local OpenAPI documents from "/home/dap/omicron-merge/openapi" ... 
     Warning skipping file "dummy": expected lockstep API file name to end in ".json"
     Failure file "dummy.json": does not match a known API
failure: bailing out after 1 error above

Error case: parse error:

$ git diff
diff --git a/openapi/wicketd.json b/openapi/wicketd.json
index 1334dd448..ada3e4c71 100644
--- a/openapi/wicketd.json
+++ b/openapi/wicketd.json
@@ -1,4 +1,3 @@
-{
   "openapi": "3.0.3",
   "info": {
     "title": "Oxide Technician Port Control Service",

$ cargo xtask openapi check 
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.61s
     Running `target/debug/xtask openapi check`
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.13s
     Running `target/debug/openapi-manager check`
     -------
  Generating OpenAPI documents from API definitions ... 
     Loading local OpenAPI documents from "/home/dap/omicron-merge/openapi" ... 
     Failure parse "wicketd.json": invalid type: string "openapi", expected struct OpenAPI at line 1 column 11
failure: bailing out after 1 error above

$ echo $?
1

Error case: invalid OpenAPI:

$ git diff
diff --git a/openapi/wicketd.json b/openapi/wicketd.json
index 1334dd448..21798e300 100644
--- a/openapi/wicketd.json
+++ b/openapi/wicketd.json
@@ -1,5 +1,4 @@
 {
-  "openapi": "3.0.3",
   "info": {
     "title": "Oxide Technician Port Control Service",
     "description": "API for use by the technician port TUI: wicket",

$ cargo xtask openapi check 
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.59s
     Running `target/debug/xtask openapi check`
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.97s
     Running `target/debug/openapi-manager check`
     -------
  Generating OpenAPI documents from API definitions ... 
     Loading local OpenAPI documents from "/home/dap/omicron-merge/openapi" ... 
     Failure parse "wicketd.json": missing field `openapi` at line 7685 column 1
failure: bailing out after 1 error above

$ echo $?
1

Error case: Oxide-specific OpenAPI lint:

$ git diff
diff --git a/wicketd-api/src/lib.rs b/wicketd-api/src/lib.rs
index 8bf96d7f8..8e67d05d6 100644
--- a/wicketd-api/src/lib.rs
+++ b/wicketd-api/src/lib.rs
@@ -390,7 +390,7 @@ pub struct CurrentRssUserConfig {
 }
 
 #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
-#[serde(tag = "status", rename_all = "snake_case")]
+#[serde(tag = "status", rename_all = "kebab-case")]
 pub enum CertificateUploadResponse {
     /// The key has been uploaded, but we're waiting on its corresponding
     /// certificate chain.

$ cargo xtask openapi check 
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.67s
     Running `target/debug/xtask openapi check`
   Compiling wicketd-api v0.1.0 (/home/dap/omicron-merge/wicketd-api)
   Compiling openapi-manager v0.1.0 (/home/dap/omicron-merge/dev-tools/openapi-manager)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 9.03s
     Running `target/debug/openapi-manager check`
     -------
  Generating OpenAPI documents from API definitions ... 
     Loading local OpenAPI documents from "/home/dap/omicron-merge/openapi" ... 
     Loading blessed OpenAPI documents from git revision "origin/main" path "openapi"
     Warning skipping file "dns-server.json": this API is not a lockstep API
     -------
    Checking 14 OpenAPI documents...
       Fresh bootstrap-agent (lockstep v0.0.1): Bootstrap Agent API
       Fresh clickhouse-admin-keeper (lockstep v0.0.1): ClickHouse Cluster Admin Keeper API
       Fresh clickhouse-admin-server (lockstep v0.0.1): ClickHouse Cluster Admin Server API
       Fresh clickhouse-admin-single (lockstep v0.0.1): ClickHouse Single-Node Admin Server API
       Fresh cockroach-admin (lockstep v0.0.1): CockroachDB Cluster Admin API
       Fresh dns-server (versioned v1.0.0 (added locally)): Internal DNS
       Fresh dns-server "latest" symlink
       Fresh gateway (lockstep v0.0.1): Oxide Management Gateway Service API
       Fresh installinator (lockstep v0.0.1): Installinator API
       Fresh nexus (lockstep v20250212.0.0): Oxide Region API
       Fresh nexus-internal (lockstep v0.0.1): Nexus internal API
       Fresh oximeter (lockstep v0.0.1): Oxide Oximeter API
       Fresh repo-depot (lockstep v0.0.1): Oxide TUF Repo Depot API
       Fresh sled-agent (lockstep v0.0.1): Oxide Sled Agent API
     Failure wicketd (lockstep v0.0.1): Oxide Technician Port Control Service
           error: Generated OpenAPI document for API "wicketd" version 0.0.1 is not
                  valid: An enumerated string contains a value 'waiting-on-cert' that
                  is neither snake_case nor SCREAMING_SNAKE_CASE:
                  Schema {
                      schema_data: SchemaData {
                          nullable: false,
                          read_only: false,
                          write_only: false,
                          deprecated: false,
                          external_docs: None,
                          example: None,
                          title: None,
                          description: None,
                          discriminator: None,
                          default: None,
                          extensions: {},
                      },
                      schema_kind: Type(
                          String(
                              StringType {
                                  format: Empty,
                                  pattern: None,
                                  enumeration: [
                                      Some(
                                          "waiting-on-cert",
                                      ),
                                  ],
                                  min_length: None,
                                  max_length: None,
                              },
                          ),
                      ),
                  }
                  Add #[serde(rename = "waiting_on_cert")] to the variant or
                  #[serde(rename_all = "snake_case")] to the enum.
                  For more info see https://github.com/oxidecomputer/openapi-
                  lint#naming
                  
                  An enumerated string contains a value 'waiting-on-key' that is
                  neither snake_case nor SCREAMING_SNAKE_CASE:
                  Schema {
                      schema_data: SchemaData {
                          nullable: false,
                          read_only: false,
                          write_only: false,
                          deprecated: false,
                          external_docs: None,
                          example: None,
                          title: None,
                          description: None,
                          discriminator: None,
                          default: None,
                          extensions: {},
                      },
                      schema_kind: Type(
                          String(
                              StringType {
                                  format: Empty,
                                  pattern: None,
                                  enumeration: [
                                      Some(
                                          "waiting-on-key",
                                      ),
                                  ],
                                  min_length: None,
                                  max_length: None,
                              },
                          ),
                      ),
                  }
                  Add #[serde(rename = "waiting_on_key")] to the variant or
                  #[serde(rename_all = "snake_case")] to the enum.
                  For more info see https://github.com/oxidecomputer/openapi-
                  lint#naming
                  
                  An enumerated string contains a value 'cert-key-accepted' that is
                  neither snake_case nor SCREAMING_SNAKE_CASE:
                  Schema {
                      schema_data: SchemaData {
                          nullable: false,
                          read_only: false,
                          write_only: false,
                          deprecated: false,
                          external_docs: None,
                          example: None,
                          title: None,
                          description: None,
                          discriminator: None,
                          default: None,
                          extensions: {},
                      },
                      schema_kind: Type(
                          String(
                              StringType {
                                  format: Empty,
                                  pattern: None,
                                  enumeration: [
                                      Some(
                                          "cert-key-accepted",
                                      ),
                                  ],
                                  min_length: None,
                                  max_length: None,
                              },
                          ),
                      ),
                  }
                  Add #[serde(rename = "cert_key_accepted")] to the variant or
                  #[serde(rename_all = "snake_case")] to the enum.
                  For more info see https://github.com/oxidecomputer/openapi-
                  lint#naming
                  
                  An enumerated string contains a value 'cert-key-duplicate-ignored'
                  that is neither snake_case nor SCREAMING_SNAKE_CASE:
                  Schema {
                      schema_data: SchemaData {
                          nullable: false,
                          read_only: false,
                          write_only: false,
                          deprecated: false,
                          external_docs: None,
                          example: None,
                          title: None,
                          description: None,
                          discriminator: None,
                          default: None,
                          extensions: {},
                      },
                      schema_kind: Type(
                          String(
                              StringType {
                                  format: Empty,
                                  pattern: None,
                                  enumeration: [
                                      Some(
                                          "cert-key-duplicate-ignored",
                                      ),
                                  ],
                                  min_length: None,
                                  max_length: None,
                              },
                          ),
                      ),
                  }
                  Add #[serde(rename = "cert_key_duplicate_ignored")] to the variant
                  or #[serde(rename_all = "snake_case")] to the enum.
                  For more info see https://github.com/oxidecomputer/openapi-
                  lint#naming
         problem: For this lockstep API, OpenAPI document generated from the current
                  code does not match the local file: "wicketd.json".  This tool can
                  update the local file for you.
             fix: will rewrite lockstep file wicketd.json from generated
             --- /home/dap/omicron-merge/openapi/wicketd.json
             +++ /home/dap/omicron-merge/openapi/wicketd.json
             @@ -1329,7 +1329,7 @@
                            "status": {
                              "type": "string",
                              "enum": [
             -                  "waiting_on_cert"
             +                  "waiting-on-cert"
                              ]
                            }
                          },
             @@ -1344,7 +1344,7 @@
                            "status": {
                              "type": "string",
                              "enum": [
             -                  "waiting_on_key"
             +                  "waiting-on-key"
                              ]
                            }
                          },
             @@ -1359,7 +1359,7 @@
                            "status": {
                              "type": "string",
                              "enum": [
             -                  "cert_key_accepted"
             +                  "cert-key-accepted"
                              ]
                            }
                          },
             @@ -1374,7 +1374,7 @@
                            "status": {
                              "type": "string",
                              "enum": [
             -                  "cert_key_duplicate_ignored"
             +                  "cert-key-duplicate-ignored"
                              ]
                            }
                          },

     -------
     Failure 14 documents checked: 14 fresh, 0 stale, 1 failed, 0 other problems
             (fix failures, then run cargo xtask openapi generate to update)

$ echo $?
100

Error: stale or missing "extra" file (nexus_tags.txt):

$ git status
On branch dap/versioning-dev-workflow
Your branch is up to date with 'origin/dap/versioning-dev-workflow'.

nothing to commit, working tree clean

$ rm -f nexus/external-api/output/nexus_tags.txt 

$ cargo xtask openapi check 
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.68s
     Running `target/debug/xtask openapi check`
   Compiling wicketd-api v0.1.0 (/home/dap/omicron-merge/wicketd-api)
   Compiling openapi-manager v0.1.0 (/home/dap/omicron-merge/dev-tools/openapi-manager)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 9.02s
     Running `target/debug/openapi-manager check`
     -------
  Generating OpenAPI documents from API definitions ... 
     Loading local OpenAPI documents from "/home/dap/omicron-merge/openapi" ... 
     Loading blessed OpenAPI documents from git revision "origin/main" path "openapi"
     Warning skipping file "dns-server.json": this API is not a lockstep API
     -------
    Checking 14 OpenAPI documents...
       Fresh bootstrap-agent (lockstep v0.0.1): Bootstrap Agent API
       Fresh clickhouse-admin-keeper (lockstep v0.0.1): ClickHouse Cluster Admin Keeper API
       Fresh clickhouse-admin-server (lockstep v0.0.1): ClickHouse Cluster Admin Server API
       Fresh clickhouse-admin-single (lockstep v0.0.1): ClickHouse Single-Node Admin Server API
       Fresh cockroach-admin (lockstep v0.0.1): CockroachDB Cluster Admin API
       Fresh dns-server (versioned v1.0.0 (added locally)): Internal DNS
       Fresh dns-server "latest" symlink
       Fresh gateway (lockstep v0.0.1): Oxide Management Gateway Service API
       Fresh installinator (lockstep v0.0.1): Installinator API
       Stale nexus (lockstep v20250212.0.0): Oxide Region API
         problem: Additional validated file associated with API "nexus" is stale:
                  nexus/external-api/output/nexus_tags.txt
             fix: will write new file nexus/external-api/output/nexus_tags.txt from
                  generated
       Fresh nexus-internal (lockstep v0.0.1): Nexus internal API
       Fresh oximeter (lockstep v0.0.1): Oxide Oximeter API
       Fresh repo-depot (lockstep v0.0.1): Oxide TUF Repo Depot API
       Fresh sled-agent (lockstep v0.0.1): Oxide Sled Agent API
       Fresh wicketd (lockstep v0.0.1): Oxide Technician Port Control Service
     -------
       Stale 14 documents checked: 14 fresh, 1 stale, 0 failed, 0 other problems
             (run cargo xtask openapi generate to update)

$ echo $?
4

$ cargo xtask openapi generate
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.65s
     Running `target/debug/xtask openapi generate`
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.97s
     Running `target/debug/openapi-manager generate`
  Generating OpenAPI documents from API definitions ... 
     Loading local OpenAPI documents from "/home/dap/omicron-merge/openapi" ... 
     Loading blessed OpenAPI documents from git revision "origin/main" path "openapi"
     Warning skipping file "dns-server.json": this API is not a lockstep API
     -------
    Updating 14 OpenAPI documents...
   Unchanged bootstrap-agent (lockstep v0.0.1): Bootstrap Agent API
   Unchanged clickhouse-admin-keeper (lockstep v0.0.1): ClickHouse Cluster Admin Keeper API
   Unchanged clickhouse-admin-server (lockstep v0.0.1): ClickHouse Cluster Admin Server API
   Unchanged clickhouse-admin-single (lockstep v0.0.1): ClickHouse Single-Node Admin Server API
   Unchanged cockroach-admin (lockstep v0.0.1): CockroachDB Cluster Admin API
   Unchanged dns-server (versioned v1.0.0 (added locally)): Internal DNS
   Unchanged dns-server "latest" symlink
   Unchanged gateway (lockstep v0.0.1): Oxide Management Gateway Service API
   Unchanged installinator (lockstep v0.0.1): Installinator API
       Stale nexus (lockstep v20250212.0.0): Oxide Region API
       Fixed wrote nexus/external-api/output/nexus_tags.txt: Updated
   Unchanged nexus-internal (lockstep v0.0.1): Nexus internal API
   Unchanged oximeter (lockstep v0.0.1): Oxide Oximeter API
   Unchanged repo-depot (lockstep v0.0.1): Oxide TUF Repo Depot API
   Unchanged sled-agent (lockstep v0.0.1): Oxide Sled Agent API
   Unchanged wicketd (lockstep v0.0.1): Oxide Technician Port Control Service
     Loading local OpenAPI documents from "/home/dap/omicron-merge/openapi" ... 
  Rechecking all local files
     -------
     Success 14 documents: 1 change made, 13 unchanged, 0 failed

$ echo $?
0

$ git status
On branch dap/versioning-dev-workflow
Your branch is up to date with 'origin/dap/versioning-dev-workflow'.

nothing to commit, working tree clean

Stale:

$ git status
On branch dap/versioning-dev-workflow
Your branch is up to date with 'origin/dap/versioning-dev-workflow'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   nexus/external-api/output/nexus_tags.txt

no changes added to commit (use "git add" and/or "git commit -a")

$ git diff
diff --git a/nexus/external-api/output/nexus_tags.txt b/nexus/external-api/output/nexus_tags.txt
index 59ba14e5f..2e28fe54a 100644
--- a/nexus/external-api/output/nexus_tags.txt
+++ b/nexus/external-api/output/nexus_tags.txt
@@ -3,7 +3,6 @@ OPERATION ID                             METHOD   URL PATH
 affinity_group_create                    POST     /v1/affinity-groups
 affinity_group_delete                    DELETE   /v1/affinity-groups/{affinity_group}
 affinity_group_list                      GET      /v1/affinity-groups
-affinity_group_member_instance_add       POST     /v1/affinity-groups/{affinity_group}/members/instance/{instance}
 affinity_group_member_instance_delete    DELETE   /v1/affinity-groups/{affinity_group}/members/instance/{instance}
 affinity_group_member_instance_view      GET      /v1/affinity-groups/{affinity_group}/members/instance/{instance}
 affinity_group_member_list               GET      /v1/affinity-groups/{affinity_group}/members

$ cargo xtask openapi check 
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.62s
     Running `target/debug/xtask openapi check`
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.97s
     Running `target/debug/openapi-manager check`
     -------
  Generating OpenAPI documents from API definitions ... 
     Loading local OpenAPI documents from "/home/dap/omicron-merge/openapi" ... 
     Loading blessed OpenAPI documents from git revision "origin/main" path "openapi"
     Warning skipping file "dns-server.json": this API is not a lockstep API
     -------
    Checking 14 OpenAPI documents...
       Fresh bootstrap-agent (lockstep v0.0.1): Bootstrap Agent API
       Fresh clickhouse-admin-keeper (lockstep v0.0.1): ClickHouse Cluster Admin Keeper API
       Fresh clickhouse-admin-server (lockstep v0.0.1): ClickHouse Cluster Admin Server API
       Fresh clickhouse-admin-single (lockstep v0.0.1): ClickHouse Single-Node Admin Server API
       Fresh cockroach-admin (lockstep v0.0.1): CockroachDB Cluster Admin API
       Fresh dns-server (versioned v1.0.0 (added locally)): Internal DNS
       Fresh dns-server "latest" symlink
       Fresh gateway (lockstep v0.0.1): Oxide Management Gateway Service API
       Fresh installinator (lockstep v0.0.1): Installinator API
       Stale nexus (lockstep v20250212.0.0): Oxide Region API
         problem: Additional validated file associated with API "nexus" is stale:
                  nexus/external-api/output/nexus_tags.txt
             fix: will rewrite file nexus/external-api/output/nexus_tags.txt from
                  generated
             --- /home/dap/omicron-merge/nexus/external-api/output/nexus_tags.txt
             +++ /home/dap/omicron-merge/nexus/external-api/output/nexus_tags.txt
             @@ -3,6 +3,7 @@
              affinity_group_create                    POST     /v1/affinity-groups
              affinity_group_delete                    DELETE   /v1/affinity-groups/{affinity_group}
              affinity_group_list                      GET      /v1/affinity-groups
             +affinity_group_member_instance_add       POST     /v1/affinity-groups/{affinity_group}/members/instance/{instance}
              affinity_group_member_instance_delete    DELETE   /v1/affinity-groups/{affinity_group}/members/instance/{instance}
              affinity_group_member_instance_view      GET      /v1/affinity-groups/{affinity_group}/members/instance/{instance}
              affinity_group_member_list               GET      /v1/affinity-groups/{affinity_group}/members

       Fresh nexus-internal (lockstep v0.0.1): Nexus internal API
       Fresh oximeter (lockstep v0.0.1): Oxide Oximeter API
       Fresh repo-depot (lockstep v0.0.1): Oxide TUF Repo Depot API
       Fresh sled-agent (lockstep v0.0.1): Oxide Sled Agent API
       Fresh wicketd (lockstep v0.0.1): Oxide Technician Port Control Service
     -------
       Stale 14 documents checked: 14 fresh, 1 stale, 0 failed, 0 other problems
             (run cargo xtask openapi generate to update)

$ echo $?
4

$ cargo xtask openapi generate
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.63s
     Running `target/debug/xtask openapi generate`
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.13s
     Running `target/debug/openapi-manager generate`
  Generating OpenAPI documents from API definitions ... 
     Loading local OpenAPI documents from "/home/dap/omicron-merge/openapi" ... 
     Loading blessed OpenAPI documents from git revision "origin/main" path "openapi"
     Warning skipping file "dns-server.json": this API is not a lockstep API
     -------
    Updating 14 OpenAPI documents...
   Unchanged bootstrap-agent (lockstep v0.0.1): Bootstrap Agent API
   Unchanged clickhouse-admin-keeper (lockstep v0.0.1): ClickHouse Cluster Admin Keeper API
   Unchanged clickhouse-admin-server (lockstep v0.0.1): ClickHouse Cluster Admin Server API
   Unchanged clickhouse-admin-single (lockstep v0.0.1): ClickHouse Single-Node Admin Server API
   Unchanged cockroach-admin (lockstep v0.0.1): CockroachDB Cluster Admin API
   Unchanged dns-server (versioned v1.0.0 (added locally)): Internal DNS
   Unchanged dns-server "latest" symlink
   Unchanged gateway (lockstep v0.0.1): Oxide Management Gateway Service API
   Unchanged installinator (lockstep v0.0.1): Installinator API
       Stale nexus (lockstep v20250212.0.0): Oxide Region API
       Fixed wrote nexus/external-api/output/nexus_tags.txt: Updated
   Unchanged nexus-internal (lockstep v0.0.1): Nexus internal API
   Unchanged oximeter (lockstep v0.0.1): Oxide Oximeter API
   Unchanged repo-depot (lockstep v0.0.1): Oxide TUF Repo Depot API
   Unchanged sled-agent (lockstep v0.0.1): Oxide Sled Agent API
   Unchanged wicketd (lockstep v0.0.1): Oxide Technician Port Control Service
     Loading local OpenAPI documents from "/home/dap/omicron-merge/openapi" ... 
  Rechecking all local files
     -------
     Success 14 documents: 1 change made, 13 unchanged, 0 failed

$ echo $?
0

@davepacheco
Copy link
Collaborator Author

davepacheco commented Feb 28, 2025

Final manual testing (continued)

Missing symlink:

$ git status
On branch dap/versioning-dev-workflow
Your branch is up to date with 'origin/dap/versioning-dev-workflow'.

nothing to commit, working tree clean

$ rm -f openapi/dns-server/dns-server-latest.json 

$ cargo xtask openapi check 
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.61s
     Running `target/debug/xtask openapi check`
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.98s
     Running `target/debug/openapi-manager check`
     -------
  Generating OpenAPI documents from API definitions ... 
     Loading local OpenAPI documents from "/home/dap/omicron-merge/openapi" ... 
     Loading blessed OpenAPI documents from git revision "origin/main" path "openapi"
     Warning skipping file "dns-server.json": this API is not a lockstep API
     -------
    Checking 14 OpenAPI documents...
       Fresh bootstrap-agent (lockstep v0.0.1): Bootstrap Agent API
       Fresh clickhouse-admin-keeper (lockstep v0.0.1): ClickHouse Cluster Admin Keeper API
       Fresh clickhouse-admin-server (lockstep v0.0.1): ClickHouse Cluster Admin Server API
       Fresh clickhouse-admin-single (lockstep v0.0.1): ClickHouse Single-Node Admin Server API
       Fresh cockroach-admin (lockstep v0.0.1): CockroachDB Cluster Admin API
       Fresh dns-server (versioned v1.0.0 (added locally)): Internal DNS
       Stale dns-server "latest" symlink
         problem: "Latest" symlink for versioned API "dns-server" is missing
             fix: will update symlink to point to dns-server-1.0.0-49359e.json
       Fresh gateway (lockstep v0.0.1): Oxide Management Gateway Service API
       Fresh installinator (lockstep v0.0.1): Installinator API
       Fresh nexus (lockstep v20250212.0.0): Oxide Region API
       Fresh nexus-internal (lockstep v0.0.1): Nexus internal API
       Fresh oximeter (lockstep v0.0.1): Oxide Oximeter API
       Fresh repo-depot (lockstep v0.0.1): Oxide TUF Repo Depot API
       Fresh sled-agent (lockstep v0.0.1): Oxide Sled Agent API
       Fresh wicketd (lockstep v0.0.1): Oxide Technician Port Control Service
     -------
       Stale 14 documents checked: 14 fresh, 0 stale, 0 failed, 1 other problem
             (run cargo xtask openapi generate to update)

$ echo $?
4

$ cargo xtask openapi generate
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.51s
     Running `target/debug/xtask openapi generate`
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.89s
     Running `target/debug/openapi-manager generate`
  Generating OpenAPI documents from API definitions ... 
     Loading local OpenAPI documents from "/home/dap/omicron-merge/openapi" ... 
     Loading blessed OpenAPI documents from git revision "origin/main" path "openapi"
     Warning skipping file "dns-server.json": this API is not a lockstep API
     -------
    Updating 14 OpenAPI documents...
   Unchanged bootstrap-agent (lockstep v0.0.1): Bootstrap Agent API
   Unchanged clickhouse-admin-keeper (lockstep v0.0.1): ClickHouse Cluster Admin Keeper API
   Unchanged clickhouse-admin-server (lockstep v0.0.1): ClickHouse Cluster Admin Server API
   Unchanged clickhouse-admin-single (lockstep v0.0.1): ClickHouse Single-Node Admin Server API
   Unchanged cockroach-admin (lockstep v0.0.1): CockroachDB Cluster Admin API
   Unchanged dns-server (versioned v1.0.0 (added locally)): Internal DNS
       Stale dns-server "latest" symlink
       Fixed wrote link /home/dap/omicron-merge/openapi/dns-server/dns-server-latest.json -> dns-server-1.0.0-49359e.json
   Unchanged gateway (lockstep v0.0.1): Oxide Management Gateway Service API
   Unchanged installinator (lockstep v0.0.1): Installinator API
   Unchanged nexus (lockstep v20250212.0.0): Oxide Region API
   Unchanged nexus-internal (lockstep v0.0.1): Nexus internal API
   Unchanged oximeter (lockstep v0.0.1): Oxide Oximeter API
   Unchanged repo-depot (lockstep v0.0.1): Oxide TUF Repo Depot API
   Unchanged sled-agent (lockstep v0.0.1): Oxide Sled Agent API
   Unchanged wicketd (lockstep v0.0.1): Oxide Technician Port Control Service
     Loading local OpenAPI documents from "/home/dap/omicron-merge/openapi" ... 
  Rechecking all local files
     -------
     Success 14 documents: 1 change made, 14 unchanged, 0 failed

$

Error case: hand-crafted bogus symlink:

$ git status
On branch dap/versioning-dev-workflow
Your branch is up to date with 'origin/dap/versioning-dev-workflow'.

nothing to commit, working tree clean

$ ln -sf bogus-bogus openapi/dns-server/dns-server-latest.json 

$ cargo xtask openapi check 
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.59s
     Running `target/debug/xtask openapi check`
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.95s
     Running `target/debug/openapi-manager check`
     -------
  Generating OpenAPI documents from API definitions ... 
     Loading local OpenAPI documents from "/home/dap/omicron-merge/openapi" ... 
     Warning ignoring symlink /home/dap/omicron-merge/openapi/dns-server/dns-server-latest.json pointing to bogus-bogus: expected a versioned API document filename for API "dns-server" to look like ""dns-server"-SEMVER-HASH.json": unexpected prefix
     Loading blessed OpenAPI documents from git revision "origin/main" path "openapi"
     Warning skipping file "dns-server.json": this API is not a lockstep API
     -------
    Checking 14 OpenAPI documents...
       Fresh bootstrap-agent (lockstep v0.0.1): Bootstrap Agent API
       Fresh clickhouse-admin-keeper (lockstep v0.0.1): ClickHouse Cluster Admin Keeper API
       Fresh clickhouse-admin-server (lockstep v0.0.1): ClickHouse Cluster Admin Server API
       Fresh clickhouse-admin-single (lockstep v0.0.1): ClickHouse Single-Node Admin Server API
       Fresh cockroach-admin (lockstep v0.0.1): CockroachDB Cluster Admin API
       Fresh dns-server (versioned v1.0.0 (added locally)): Internal DNS
       Stale dns-server "latest" symlink
         problem: "Latest" symlink for versioned API "dns-server" is missing
             fix: will update symlink to point to dns-server-1.0.0-49359e.json
       Fresh gateway (lockstep v0.0.1): Oxide Management Gateway Service API
       Fresh installinator (lockstep v0.0.1): Installinator API
       Fresh nexus (lockstep v20250212.0.0): Oxide Region API
       Fresh nexus-internal (lockstep v0.0.1): Nexus internal API
       Fresh oximeter (lockstep v0.0.1): Oxide Oximeter API
       Fresh repo-depot (lockstep v0.0.1): Oxide TUF Repo Depot API
       Fresh sled-agent (lockstep v0.0.1): Oxide Sled Agent API
       Fresh wicketd (lockstep v0.0.1): Oxide Technician Port Control Service
     -------
       Stale 14 documents checked: 14 fresh, 0 stale, 0 failed, 1 other problem
             (run cargo xtask openapi generate to update)

$ echo $?
4

$ cargo xtask openapi generate
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.52s
     Running `target/debug/xtask openapi generate`
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.89s
     Running `target/debug/openapi-manager generate`
  Generating OpenAPI documents from API definitions ... 
     Loading local OpenAPI documents from "/home/dap/omicron-merge/openapi" ... 
     Warning ignoring symlink /home/dap/omicron-merge/openapi/dns-server/dns-server-latest.json pointing to bogus-bogus: expected a versioned API document filename for API "dns-server" to look like ""dns-server"-SEMVER-HASH.json": unexpected prefix
     Loading blessed OpenAPI documents from git revision "origin/main" path "openapi"
     Warning skipping file "dns-server.json": this API is not a lockstep API
     -------
    Updating 14 OpenAPI documents...
   Unchanged bootstrap-agent (lockstep v0.0.1): Bootstrap Agent API
   Unchanged clickhouse-admin-keeper (lockstep v0.0.1): ClickHouse Cluster Admin Keeper API
   Unchanged clickhouse-admin-server (lockstep v0.0.1): ClickHouse Cluster Admin Server API
   Unchanged clickhouse-admin-single (lockstep v0.0.1): ClickHouse Single-Node Admin Server API
   Unchanged cockroach-admin (lockstep v0.0.1): CockroachDB Cluster Admin API
   Unchanged dns-server (versioned v1.0.0 (added locally)): Internal DNS
       Stale dns-server "latest" symlink
       Fixed wrote link /home/dap/omicron-merge/openapi/dns-server/dns-server-latest.json -> dns-server-1.0.0-49359e.json
   Unchanged gateway (lockstep v0.0.1): Oxide Management Gateway Service API
   Unchanged installinator (lockstep v0.0.1): Installinator API
   Unchanged nexus (lockstep v20250212.0.0): Oxide Region API
   Unchanged nexus-internal (lockstep v0.0.1): Nexus internal API
   Unchanged oximeter (lockstep v0.0.1): Oxide Oximeter API
   Unchanged repo-depot (lockstep v0.0.1): Oxide TUF Repo Depot API
   Unchanged sled-agent (lockstep v0.0.1): Oxide Sled Agent API
   Unchanged wicketd (lockstep v0.0.1): Oxide Technician Port Control Service
     Loading local OpenAPI documents from "/home/dap/omicron-merge/openapi" ... 
  Rechecking all local files
     -------
     Success 14 documents: 1 change made, 14 unchanged, 0 failed

$ echo $?
0

$ git status
On branch dap/versioning-dev-workflow
Your branch is up to date with 'origin/dap/versioning-dev-workflow'.

nothing to commit, working tree clean

@davepacheco
Copy link
Collaborator Author

Final manual testing (continued)

Lastly I tested every possible merge problem I could think of. It's a little harder to show the transcript.

First, I created a dap/demo/main branch from the tip of this branch and added this one commit to simulate a history with blessed versions 1 and 2:

$ git log -1 --name-only
commit df1f0e6240991e7e259ad9c0f2d1b5941e6371c6 (HEAD -> dap/demo/main)
Author: David Pacheco <[email protected]>
Date:   Thu Feb 27 20:55:58 2025 -0800

    add a fake v2

dns-server-api/src/lib.rs
openapi/dns-server/dns-server-2.0.0-a65644.json
openapi/dns-server/dns-server-latest.json


$ git diff HEAD^ dns-server-api/ openapi/dns-server/dns-server-latest.json
diff --git a/dns-server-api/src/lib.rs b/dns-server-api/src/lib.rs
index 6cf0b7f1c..e487ed0e6 100644
--- a/dns-server-api/src/lib.rs
+++ b/dns-server-api/src/lib.rs
@@ -107,6 +107,7 @@ api_versions!([
     // |  example for the next person.
     // v
     // (next_int, IDENT),
+    (2, DEMO),
     (1, INITIAL),
 ]);

I created branches:

  • dap/demo/change-v3: simulates a pull request that adds version 3 onto dap/demo/main (adds one commit similar to above, but with version 3)
  • dap/demo/change-v4: simulates a pull request that adds version 4 onto dap/demo/main (skipping 3) (adds one commit similar to above, but with version 4)
  • dap/demo/change-v5: simulates a pull request that adds version 5 onto dap/demo/main (skipping 3 and 4) (adds one commit similar to above, but with version 5)

Then I created dap/demo/develop-v4 to simulate someone's own pull request branch where they've added v4 (skipping v3). The reason I did all this was to be able to simulate merging with (1) a change that introduces a version earlier than yours, (2) a change that introduces a version exactly matching yours, and (3) a change that introduces a version strictly newer than yours.

In a successful merge, the only expected steps are:

  • git merge dap/demo/change-v3 (or v4 or v5, whichever thing I was testing)
  • correctly merge the api_versions! macro invocation
  • correctly merge any Rust conflict in the API trait (this was only needed in one case and is mostly an artifact of the specific test changes I made)
  • run cargo xtask openapi generate --blessed-from-git=dap/demo/change-v3 (or v4 or v5, whichever I was testing). This is necessary to properly simulate what would happen if you were merging with a "main" that had landed one of the pull requests above. We're telling the tool to get its blessed files directly from the branch we're merging from.
  • commit the merge
  • run cargo xtask openapi check -- it should show no issues.

I also tested a bunch of cases where I intentionally botched the api_versions! merge in various ways. I wanted to make sure the tool would never allow a change to land that changed a blessed version and ideally produced clear error messages about what was wrong if you botch the merge in any way.

Merging with v3 (older version than mine)

  • success case: works (confirmed check works after merge committed)
  • mismerge (dropped theirs): fails as expected (compile error referencing VERSION that's now missing)
  • mismerge (dropped theirs, also removed their APIs to avoid the compile error): get a warning about retiring an old version. Indeed, this isn't an error -- it's indistinguishable from retiring an old version. You don't get a note about the retiring until you commit the merge and run it again -- more on this below.
  • mismerge (dropped mine): failed as expected (compile error referencing VERSION that's now missing)
  • mismerge (dropped mine, removed reference to missing VERSION constant): fails as expected: error about trying to change a blessed version
  • mismerge (dropped mine, also removed my APIs to eliminate the compile error): happily removes my local version file, which is about the best it can do
  • mismerge (put the versions in the wrong order): clear panic message on exactly the right line: "list of supported versions for an API must be sorted"

Merging with v4 (same version as mine)

I skipped several error cases that were identical to the ones covered above.

  • success case: works (confirmed check works after merge committed)
  • mismerge (duplicate version): clear panic message on exactly the right line: version 4.0.0 appears multiple times (labels: "CHANGE_FOUR", "MY_CHANGE")
  • mismerge (numbered upstream change after mine): tool will regenerate stuff happily until after you commit the merge. Then check will report failure due to having changed a blessed version. This is not ideal but it will not allow a change to land that breaks a blessed version. (This behavior happens because during the merge, the merge-base with the branch we're merging from hasn't yet been updated to include the commit we're merging in, so the tool doesn't find the blessed version that it should. But it does once you commit the merge. This could be improved in the future.)

Merging with v5 (newer version than mine)

I skipped several error cases that were identical to the ones covered above.

  • success case: works (confirmed check works after merge committed)
  • mismerge: numbered my version earlier than the newer one: fails as expected because v5 is blessed and we've changed it (because it now incorporates my version's changes)

@davepacheco davepacheco enabled auto-merge (squash) February 28, 2025 06:02
@davepacheco davepacheco merged commit eab2e66 into main Feb 28, 2025
19 checks passed
@davepacheco davepacheco deleted the dap/versioning-dev-workflow branch February 28, 2025 07:05
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