Repository: stackrox/stackrox
Pull request #14558: ROX-28173: Include custom route swagger specs in API docs
Author: login: kylape
Labels: name: area/central
name: area/helm
name: konflux-build
description: Run Konflux in PR. Push commit to trigger it.
Created: 2025-03-10T17:18:26Z | Merged: 2025-03-14T21:54:51Z

This is one step towards fully automating inclusion of API docs from custom routes.

Given a swagger spec added to the `central/docs/api_custom_routes` dir, the `mergeswag.sh` script will include it in the combined swagger (v1) spec.

The `mergeswag.sh` is modified in the following ways:

* The version number is now the first argument
* The script now accepts multiple directories in which to find swagger specs
* Copies all the specs to a single temp dir and feeds them all to jq for merging
* Temp dir is cleaned up

### Testing and quality

<!--
General Availability requirements: https://github.com/stackrox/stackrox/blob/master/PR_GA.md
Feature Flags usage: https://github.com/stackrox/stackrox/blob/master/pkg/features/README.md
-->

- [ ] the change is production ready: the change is GA or otherwise the functionality is gated by a feature flag
- [ ] CI results are inspected

#### How I validated my change

I deployed Central locally with the docs generated using the modified `mergeswag.sh` script, and I observed that the new SBOM custom route swagger spec was included in the online API documentation.


Diff:
diff --git a/central/docs/api_custom_routes/README.md b/central/docs/api_custom_routes/README.md
index 6c30716b13e57..effa1ceb7cb2c 100644
--- a/central/docs/api_custom_routes/README.md
+++ b/central/docs/api_custom_routes/README.md
@@ -1,43 +1,22 @@
 # Custom Route API specs
-This directory includes manually created OpenAPI/Swagger specs for Central's custom routes defined in [/central/main.go](/central/main.go) (refer to `customRoutes()`)
-
-These particular specs are currently NOT accessible via Central, instead they are/will be available in the official docs.
-
-TODO(ROX-28173): improve doc creation, automation, serving, etc..
-
-## Manually Modifying and Converting to AsciiDoc
-
-_Disclaimer: Before executing these steps confirm with the docs team if the process, scripts, etc. is still accurate. These steps represent an initial PoC and can/should be improved, which is in scope for ROX-28173_
-
-The OpenAPI specs must be converted to AsciiDoc for publishing to the official docs.
 
-Sample steps to modify, validate, and convert these specs:
-
-1. Load the `yaml` into https://editor.swagger.io/ (or use the equiv offline variation) and make appropriate changes and validate syntax.
-   - _Refer to the [OpenAPI guide](https://swagger.io/docs/specification/v3_0/about/) and/or [official specs](https://spec.openapis.org/) for more details_
-
-2. Copy the modified `yaml` back into this repo
-
-3. Generate an AsciiDoc from the modified `yaml` (change `swagger_dir` and `srcfile` as appropriate)
-          
-       swagger_dir=/path-to-stackrox-repo/central/docs/api_custom_routes
-
-       srcfile=image_service_swagger.yaml
-
-       docker run --rm -v "$swagger_dir:/local" openapitools/openapi-generator-cli generate -i /local/$srcfile -g asciidoc -o /local/asciidoc
+This directory includes manually created OpenAPI/Swagger specs for Central's custom routes defined in [/central/main.go](/central/main.go) (refer to `customRoutes()`)
 
-4. Clone `rhacs-api-docs-gen` repo: https://github.com/gaurav-nelson/rhacs-api-docs-gen
+These specs will roll up under the v1 API inside of Central as well as in the official docs on docs.redhat.com.
 
-5. Move the generated `adoc` file to `scripts/` dir of `rhacs-api-docs-gen` tool
+## Creating a new spec for a custom route
 
-       mv $swagger_dir/asciidoc/index.adoc /path-to-rhacs-api-docs-gen-repo/scripts
+Creating a new spec for a custom route involves a few steps.
 
-6. Execute `scripts/updateasciidoc.js` to cleanup the `adoc` file (will be modified in place)
+If you want to maintain the spec in YAML:
 
-       cd /path-to-rhacs-api-docs-gen-repo
+1. Create a YAML-based swagger spec from scratch in the file format `<serviceName>.swagger.yaml` in this directory.
+2. Convert the YAML to JSON and save the JSON in the file format `<serviceName>.swagger.json` in this directory.
+3. Add both files to git.
 
-       node scripts/updateasciidoc.js index.adoc
+If you want to maintain the spec in JSON:
 
-7. Provide the new `adoc` file(s) to docs team for publishing
+1. Create a JSON-based spec from scratch in the file format `<serviceName>.swagger.json` in this directory.
+2. Add the file to git.
 
-8. Commit/push/merge updated `yaml`
\ No newline at end of file
+From there the swagger spec automation will pick up the new specs.
diff --git a/central/docs/api_custom_routes/imageService.swagger.json b/central/docs/api_custom_routes/imageService.swagger.json
new file mode 100644
index 0000000000000..ffe9da6e7a221
--- /dev/null
+++ b/central/docs/api_custom_routes/imageService.swagger.json
@@ -0,0 +1,184 @@
+{
+  "swagger": "2.0",
+  "info": {
+    "title": "Image Service Custom Routes",
+    "version": "v1"
+  },
+  "tags": [
+    {
+      "name": "ImageService"
+    }
+  ],
+  "consumes": [
+    "application/json"
+  ],
+  "produces": [
+    "application/json"
+  ],
+  "paths": {
+    "/api/v1/images/sbom": {
+      "post": {
+        "summary": "Generate an SPDX 2.3 SBOM from an image scan.",
+        "operationId": "GenerateSBOM",
+        "responses": {
+          "200": {
+            "description": "A successful response.",
+            "schema": {
+              "$ref": "#/definitions/SBOM-SPDX23-Document"
+            }
+          },
+          "default": {
+            "description": "An unexpected error response.",
+            "schema": {
+              "$ref": "#/definitions/googlerpcStatus"
+            }
+          }
+        },
+        "parameters": [
+          {
+            "name": "body",
+            "in": "body",
+            "required": true,
+            "schema": {
+              "$ref": "#/definitions/imageSBOMRequest"
+            }
+          }
+        ],
+        "tags": [
+          "ImageService"
+        ]
+      }
+    }
+  },
+  "definitions": {
+    "imageSBOMRequest": {
+      "type": "object",
+      "properties": {
+        "imageName": {
+          "type": "string",
+          "description": "Image name and reference. (e.g. nginx:latest or nginx@sha256:...)"
+        },
+        "force": {
+          "type": "boolean",
+          "description": "Bypass Central's cache for the image and force a new pull from the Scanner",
+          "default": false
+        }
+      },
+      "required": [
+        "imageName"
+      ]
+    },
+    "SBOM-SPDX23-Document": {
+      "description": "SPDX 2.3 document, refer to https://spdx.github.io/spdx-spec/v2.3/ for more details.",
+      "type": "object",
+      "properties": {
+        "spdxVersion": {
+          "type": "string",
+          "example": "SPDX-2.3"
+        },
+        "dataLicense": {
+          "type": "string",
+          "example": "CC0-1.0"
+        },
+        "SPDXID": {
+          "type": "string",
+          "example": "SPDXRef-DOCUMENT"
+        },
+        "name": {
+          "type": "string",
+          "example": "sha256:2107993405718600b5e53aae90c78647eff44086e6605862fc69389a4f9d1a09"
+        },
+        "documentNamespace": {
+          "type": "string",
+          "example": "https://quay.io/rhacs-eng/scanner-v4-665956d1-21dc-4f7c-ae96-48f7cdd460ff"
+        },
+        "creationInfo": {
+          "type": "object",
+          "properties": {
+            "created": {
+              "type": "string",
+              "example": "2025-02-18T16:33:54Z"
+            },
+            "creators": {
+              "type": "array",
+              "items": {
+                "type": "string",
+                "example": [
+                  "Tool: Claircore-<version>",
+                  "Tool: scanner-v4-matcher-<version>"
+                ]
+              }
+            }
+          }
+        },
+        "packages": {
+          "type": "array",
+          "items": {
+            "type": "object",
+            "properties": {
+              "SPDXID": {
+                "example": "SPDXRef-Package-<id>"
+              },
+              "name": {
+                "type": "string",
+                "example": "example-package"
+              },
+              "versionInfo": {
+                "type": "string",
+                "example": "example-version"
+              },
+              "packageFileName": {
+                "type": "string",
+                "example": "path/to/example/file"
+              },
+              "downloadLocation": {
+                "type": "string",
+                "example": "NOASSERTION"
+              },
+              "filesAnalyzed": {
+                "type": "boolean",
+                "example": true
+              },
+              "primaryPackagePurpose": {
+                "type": "string",
+                "example": "APPLICATION"
+              }
+            }
+          }
+        },
+        "relationships": {
+          "type": "array",
+          "items": {
+            "type": "object",
+            "properties": {
+              "spdxElementId": {
+                "type": "string",
+                "example": "SPDXRef-Package-<id>"
+              },
+              "relatedSpdxElement": {
+                "type": "string",
+                "example": "SPDXRef-Repository-<id>"
+              },
+              "relationshipType": {
+                "type": "string",
+                "example": "CONTAINED_BY"
+              }
+            }
+          }
+        }
+      }
+    },
+    "googlerpcStatus": {
+      "type": "object",
+      "properties": {
+        "code": {
+          "type": "integer",
+          "format": "int32"
+        },
+        "message": {
+          "type": "string"
+        }
+      }
+    }
+  }
+}
diff --git a/central/docs/api_custom_routes/image_service_swagger.yaml b/central/docs/api_custom_routes/imageService.swagger.yaml
similarity index 100%
rename from central/docs/api_custom_routes/image_service_swagger.yaml
rename to central/docs/api_custom_routes/imageService.swagger.yaml
diff --git a/image/rhel/konflux.Dockerfile b/image/rhel/konflux.Dockerfile
index f82aa82019680..55fe3553565e1 100644
--- a/image/rhel/konflux.Dockerfile
+++ b/image/rhel/konflux.Dockerfile
@@ -50,9 +50,9 @@ ENV CI=1
 RUN # TODO(ROX-13200): make sure roxctl cli is built without running go mod tidy. \
     make main-build-nodeps cli-build && \
     mkdir -p image/rhel/docs/api/v1 && \
-    ./scripts/mergeswag.sh generated/api/v1 1 >image/rhel/docs/api/v1/swagger.json && \
+    ./scripts/mergeswag.sh 1 generated/api/v1 central/docs/api_custom_routes >image/rhel/docs/api/v1/swagger.json && \
     mkdir -p image/rhel/docs/api/v2 && \
-    ./scripts/mergeswag.sh generated/api/v2 2 >image/rhel/docs/api/v2/swagger.json
+    ./scripts/mergeswag.sh 2 generated/api/v2 >image/rhel/docs/api/v2/swagger.json
 
 RUN make copy-go-binaries-to-image-dir
 
diff --git a/make/protogen.mk b/make/protogen.mk
index 9cf3babebc86a..cc480cd1ae737 100644
--- a/make/protogen.mk
+++ b/make/protogen.mk
@@ -16,6 +16,8 @@ API_SERVICE_PROTOS_V2 = $(filter api/v2/%, $(SERVICE_PROTOS_REL))
 
 STORAGE_PROTOS = $(filter storage/%, $(ALL_PROTOS_REL))
 
+CUSTOM_SWAGGER_SRCS = central/docs/api_custom_routes
+CUSTOM_SWAGGER_SPECS = $(shell find $(CUSTOM_SWAGGER_SRCS) -name "*.swagger.json")
 GENERATED_BASE_PATH = $(BASE_PATH)/generated
 GENERATED_DOC_PATH = image/rhel/docs
 MERGED_API_SWAGGER_SPEC = $(GENERATED_DOC_PATH)/api/v1/swagger.json
@@ -288,15 +290,15 @@ endif
 		$(dir $<)/*.proto
 
 # Generate the docs from the merged swagger specs. Dependency cleanup-swagger-json-gotags should execute the last.
-$(MERGED_API_SWAGGER_SPEC): $(BASE_PATH)/scripts/mergeswag.sh $(GENERATED_API_SWAGGER_SPECS) cleanup-swagger-json-gotags
+$(MERGED_API_SWAGGER_SPEC): $(BASE_PATH)/scripts/mergeswag.sh $(GENERATED_API_SWAGGER_SPECS) cleanup-swagger-json-gotags $(CUSTOM_SWAGGER_SPECS)
 	@echo "+ $@"
 	$(SILENT)mkdir -p "$(dir $@)"
-	$(BASE_PATH)/scripts/mergeswag.sh "$(GENERATED_BASE_PATH)/api/v1" "1" >"$@"
+	$(BASE_PATH)/scripts/mergeswag.sh "1" "$(GENERATED_BASE_PATH)/api/v1" "$(CUSTOM_SWAGGER_SRCS)" >"$@"
 
 $(MERGED_API_SWAGGER_SPEC_V2): $(BASE_PATH)/scripts/mergeswag.sh $(GENERATED_API_SWAGGER_SPECS_V2) cleanup-swagger-json-gotags
 	@echo "+ $@"
 	$(SILENT)mkdir -p "$(dir $@)"
-	$(BASE_PATH)/scripts/mergeswag.sh "$(GENERATED_BASE_PATH)/api/v2" "2" >"$@"
+	$(BASE_PATH)/scripts/mergeswag.sh "2" "$(GENERATED_BASE_PATH)/api/v2" >"$@"
 
 # Generate the docs from the merged swagger specs.
 $(GENERATED_API_DOCS): $(MERGED_API_SWAGGER_SPEC) $(MERGED_API_SWAGGER_SPEC_V2)
diff --git a/scripts/mergeswag.sh b/scripts/mergeswag.sh
index 20d46588db2e7..b35235a81460c 100755
--- a/scripts/mergeswag.sh
+++ b/scripts/mergeswag.sh
@@ -3,16 +3,21 @@
 # Mergeswag.sh merges JSON-encoded Swagger specification files into one mongo file
 # called swagger.json.
 
-[ -z "$1" ] && echo >&2 "Please specify a folder to search for .swagger.json files" && exit 1
+[ -z "$1" ] && [[ "$1" =~ ^(1|2)$ ]] && echo >&2 "Please specify a valid API version number for merged swagger.json file" && exit 1
 
-[ -z "$2" ] && [[ "$2" =~ ^(1|2)$ ]] && echo >&2 "Please specify a valid API version number for merged swagger.json file" && exit 1
+[ -z "$2" ] && echo >&2 "Please specify at least one folder to search for .swagger.json files" && exit 1
 
 set -euo pipefail
 
-folder="$1"
+src=$(mktemp -d)
+trap 'rm -rf "${src}"' EXIT
+
+for folder in "${@:2}"; do
+    find "$folder/" -name '*.swagger.json' -exec cp {} $src \;
+done
 
 export TITLE="API Reference"
-export VERSION="$2"
+export VERSION="$1"
 export DESCRIPTION="API reference for the StackRox Security Platform"
 
 metadata='{
@@ -23,6 +28,6 @@ metadata='{
   }
 }'
 
-find "$folder/" -name '*.swagger.json' -print0 \
+find "$src" -type f -name "*" -print0 \
 	| sort -zr \
 	| xargs -0 jq -s 'reduce .[] as $item ('"$metadata"'; $item * .)'


Reviews:
user: login: mtodor
body: This is a great improvement for the process of maintaining custom routes API docs.
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T10:56:13Z
user: login: kylape
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T14:26:09Z
user: login: kylape
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T14:26:58Z
user: login: mtodor
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T16:14:33Z
user: login: mtodor
body: It looks good! Tnx for making these improvements.

I didn't see how it looks like in UI when API Docs are open. If you have an instance running with the image built from this PR, it would be great to attach a screenshot.
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T16:26:55Z

Review comments:
diff_hunk: @@ -27,6 +27,7 @@ GENERATED_API_SRCS = $(ALL_PROTOS_REL:%.proto=$(GENERATED_BASE_PATH)/%_grpc.pb.g
 GENERATED_API_GW_SRCS = $(SERVICE_PROTOS_REL:%.proto=$(GENERATED_BASE_PATH)/%.pb.gw.go)
 GENERATED_API_SWAGGER_SPECS = $(API_SERVICE_PROTOS:%.proto=$(GENERATED_BASE_PATH)/%.swagger.json)
 GENERATED_API_SWAGGER_SPECS_V2 = $(API_SERVICE_PROTOS_V2:%.proto=$(GENERATED_BASE_PATH)/%.swagger.json)
+CUSTOM_SWAGGER_SPECS = $(shell find central/docs/api_custom_routes -name "*.swagger.json")
path: make/protogen.mk
user: login: mtodor
body: nitpick: It would be good to define `central/docs/api_custom_routes` as a make variable (similar to `PROTO_BASE_PATH`). In that case, we can use that variable here and also when we call `mergeswag.sh`. 
created_at: 2025-03-12T10:31:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -50,9 +50,9 @@ ENV CI=1
 RUN # TODO(ROX-13200): make sure roxctl cli is built without running go mod tidy. \
     make main-build-nodeps cli-build && \
     mkdir -p image/rhel/docs/api/v1 && \
-    ./scripts/mergeswag.sh generated/api/v1 1 >image/rhel/docs/api/v1/swagger.json && \
+    ./scripts/mergeswag.sh 1 generated/api/v1 >image/rhel/docs/api/v1/swagger.json && \
path: image/rhel/konflux.Dockerfile
user: login: mtodor
body: Should we also include `"central/docs/api_custom_routes"` here?
created_at: 2025-03-12T10:34:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -23,6 +27,8 @@ metadata='{
   }
 }'
 
-find "$folder/" -name '*.swagger.json' -print0 \
+find "$src" -type f -name "*" -print0 \
 	| sort -zr \
 	| xargs -0 jq -s 'reduce .[] as $item ('"$metadata"'; $item * .)'
+
+rm -rf $src
path: scripts/mergeswag.sh
user: login: mtodor
body: It would be good to create a `trap` for this after `src=$(mktemp -d)`. We should clean up the temp dir even if the script exits earlier.
created_at: 2025-03-12T10:39:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -3,13 +3,17 @@
 # Mergeswag.sh merges JSON-encoded Swagger specification files into one mongo file
 # called swagger.json.
 
-[ -z "$1" ] && echo >&2 "Please specify a folder to search for .swagger.json files" && exit 1
+[ -z "$1" ] && [[ "$1" =~ ^(1|2)$ ]] && echo >&2 "Please specify a valid API version number for merged swagger.json file" && exit 1
 
-[ -z "$2" ] && [[ "$2" =~ ^(1|2)$ ]] && echo >&2 "Please specify a valid API version number for merged swagger.json file" && exit 1
+[ -z "$2" ] && echo >&2 "Please specify at least one folder to search for .swagger.json files" && exit 1
 
 set -euo pipefail
 
-folder="$1"
+src=$(mktemp -d)
+
+for folder in "${@:2}"; do
+    find "$folder/" -name '*.swagger.json' -exec cp {} $src \;
+done
 
 export TITLE="API Reference"
 export VERSION="$2"
path: scripts/mergeswag.sh
user: login: mtodor
body: Because we have swapped order of arguments this should be updated accordingly:
```suggestion
export VERSION="$1"
```
Please check if there are other places, I didn't check everything.
created_at: 2025-03-12T10:44:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -3,13 +3,17 @@
 # Mergeswag.sh merges JSON-encoded Swagger specification files into one mongo file
 # called swagger.json.
 
-[ -z "$1" ] && echo >&2 "Please specify a folder to search for .swagger.json files" && exit 1
+[ -z "$1" ] && [[ "$1" =~ ^(1|2)$ ]] && echo >&2 "Please specify a valid API version number for merged swagger.json file" && exit 1
 
-[ -z "$2" ] && [[ "$2" =~ ^(1|2)$ ]] && echo >&2 "Please specify a valid API version number for merged swagger.json file" && exit 1
+[ -z "$2" ] && echo >&2 "Please specify at least one folder to search for .swagger.json files" && exit 1
 
 set -euo pipefail
 
-folder="$1"
+src=$(mktemp -d)
+
+for folder in "${@:2}"; do
+    find "$folder/" -name '*.swagger.json' -exec cp {} $src \;
path: scripts/mergeswag.sh
user: login: mtodor
body: Since my `bash` knowledge is not great, I'm interested, what is the purpose of ` \;` at the end of this command? 
created_at: 2025-03-12T10:47:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,43 +1,22 @@
 # Custom Route API specs
-This directory includes manually created OpenAPI/Swagger specs for Central's custom routes defined in [/central/main.go](/central/main.go) (refer to `customRoutes()`)
-
-These particular specs are currently NOT accessible via Central, instead they are/will be available in the official docs.
-
-TODO(ROX-28173): improve doc creation, automation, serving, etc..
-
-## Manually Modifying and Converting to AsciiDoc
-
-_Disclaimer: Before executing these steps confirm with the docs team if the process, scripts, etc. is still accurate. These steps represent an initial PoC and can/should be improved, which is in scope for ROX-28173_
-
-The OpenAPI specs must be converted to AsciiDoc for publishing to the official docs.
 
-Sample steps to modify, validate, and convert these specs:
-
-1. Load the `yaml` into https://editor.swagger.io/ (or use the equiv offline variation) and make appropriate changes and validate syntax.
-   - _Refer to the [OpenAPI guide](https://swagger.io/docs/specification/v3_0/about/) and/or [official specs](https://spec.openapis.org/) for more details_
-
-2. Copy the modified `yaml` back into this repo
-
-3. Generate an AsciiDoc from the modified `yaml` (change `swagger_dir` and `srcfile` as appropriate)
-          
-       swagger_dir=/path-to-stackrox-repo/central/docs/api_custom_routes
-
-       srcfile=image_service_swagger.yaml
-
-       docker run --rm -v "$swagger_dir:/local" openapitools/openapi-generator-cli generate -i /local/$srcfile -g asciidoc -o /local/asciidoc
+This directory includes manually created OpenAPI/Swagger specs for Central's custom routes defined in [/central/main.go](/central/main.go) (refer to `customRoutes()`)
 
-4. Clone `rhacs-api-docs-gen` repo: https://github.com/gaurav-nelson/rhacs-api-docs-gen
+These specs will roll up under the v1 API inside of Central as well as in the official docs on docs.redhat.com.
 
-5. Move the generated `adoc` file to `scripts/` dir of `rhacs-api-docs-gen` tool
+## Creating a new spec for a custom route
 
-       mv $swagger_dir/asciidoc/index.adoc /path-to-rhacs-api-docs-gen-repo/scripts
+Creating a new spec for a custom route involves a few steps.
 
-6. Execute `scripts/updateasciidoc.js` to cleanup the `adoc` file (will be modified in place)
+If you want to maintain the spec in YAML:
 
-       cd /path-to-rhacs-api-docs-gen-repo
+1. Create a YAML-based swagger spec from scratch in the file format `<service_name>_swagger.yaml` in this directory.
path: central/docs/api_custom_routes/README.md
user: login: mtodor
body: nitpick: Is there a reason for different file prefix naming convention? `service_name` for YAML vs `serviceName` for JSON?
tbh. I would prefer having the same name. Snake or camel, it's not relevant. Just to have nice sorting of files one after another.
created_at: 2025-03-12T10:52:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,43 +1,22 @@
 # Custom Route API specs
-This directory includes manually created OpenAPI/Swagger specs for Central's custom routes defined in [/central/main.go](/central/main.go) (refer to `customRoutes()`)
-
-These particular specs are currently NOT accessible via Central, instead they are/will be available in the official docs.
-
-TODO(ROX-28173): improve doc creation, automation, serving, etc..
-
-## Manually Modifying and Converting to AsciiDoc
-
-_Disclaimer: Before executing these steps confirm with the docs team if the process, scripts, etc. is still accurate. These steps represent an initial PoC and can/should be improved, which is in scope for ROX-28173_
-
-The OpenAPI specs must be converted to AsciiDoc for publishing to the official docs.
 
-Sample steps to modify, validate, and convert these specs:
-
-1. Load the `yaml` into https://editor.swagger.io/ (or use the equiv offline variation) and make appropriate changes and validate syntax.
-   - _Refer to the [OpenAPI guide](https://swagger.io/docs/specification/v3_0/about/) and/or [official specs](https://spec.openapis.org/) for more details_
-
-2. Copy the modified `yaml` back into this repo
-
-3. Generate an AsciiDoc from the modified `yaml` (change `swagger_dir` and `srcfile` as appropriate)
-          
-       swagger_dir=/path-to-stackrox-repo/central/docs/api_custom_routes
-
-       srcfile=image_service_swagger.yaml
-
-       docker run --rm -v "$swagger_dir:/local" openapitools/openapi-generator-cli generate -i /local/$srcfile -g asciidoc -o /local/asciidoc
+This directory includes manually created OpenAPI/Swagger specs for Central's custom routes defined in [/central/main.go](/central/main.go) (refer to `customRoutes()`)
 
-4. Clone `rhacs-api-docs-gen` repo: https://github.com/gaurav-nelson/rhacs-api-docs-gen
+These specs will roll up under the v1 API inside of Central as well as in the official docs on docs.redhat.com.
 
-5. Move the generated `adoc` file to `scripts/` dir of `rhacs-api-docs-gen` tool
+## Creating a new spec for a custom route
 
-       mv $swagger_dir/asciidoc/index.adoc /path-to-rhacs-api-docs-gen-repo/scripts
+Creating a new spec for a custom route involves a few steps.
 
-6. Execute `scripts/updateasciidoc.js` to cleanup the `adoc` file (will be modified in place)
+If you want to maintain the spec in YAML:
 
-       cd /path-to-rhacs-api-docs-gen-repo
+1. Create a YAML-based swagger spec from scratch in the file format `<service_name>_swagger.yaml` in this directory.
+2. Convert the YAML to JSON and save the JSON in the file format `<serviceName>.swagger.json` in this directory.
+3. Add both files to git.
 
-       node scripts/updateasciidoc.js index.adoc
+If you want to main the spec in JSON:
path: central/docs/api_custom_routes/README.md
user: login: mtodor
body: ```suggestion
If you want to maintain the spec in JSON:
```
created_at: 2025-03-12T10:54:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -3,13 +3,17 @@
 # Mergeswag.sh merges JSON-encoded Swagger specification files into one mongo file
 # called swagger.json.
 
-[ -z "$1" ] && echo >&2 "Please specify a folder to search for .swagger.json files" && exit 1
+[ -z "$1" ] && [[ "$1" =~ ^(1|2)$ ]] && echo >&2 "Please specify a valid API version number for merged swagger.json file" && exit 1
 
-[ -z "$2" ] && [[ "$2" =~ ^(1|2)$ ]] && echo >&2 "Please specify a valid API version number for merged swagger.json file" && exit 1
+[ -z "$2" ] && echo >&2 "Please specify at least one folder to search for .swagger.json files" && exit 1
 
 set -euo pipefail
 
-folder="$1"
+src=$(mktemp -d)
+
+for folder in "${@:2}"; do
+    find "$folder/" -name '*.swagger.json' -exec cp {} $src \;
path: scripts/mergeswag.sh
user: login: kylape
body: It indicates the end of the command/script to execute for every file.  It's a bizarre syntax that I've only ever seen when using the `-exec` argument to `find`. 
created_at: 2025-03-13T14:26:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,43 +1,22 @@
 # Custom Route API specs
-This directory includes manually created OpenAPI/Swagger specs for Central's custom routes defined in [/central/main.go](/central/main.go) (refer to `customRoutes()`)
-
-These particular specs are currently NOT accessible via Central, instead they are/will be available in the official docs.
-
-TODO(ROX-28173): improve doc creation, automation, serving, etc..
-
-## Manually Modifying and Converting to AsciiDoc
-
-_Disclaimer: Before executing these steps confirm with the docs team if the process, scripts, etc. is still accurate. These steps represent an initial PoC and can/should be improved, which is in scope for ROX-28173_
-
-The OpenAPI specs must be converted to AsciiDoc for publishing to the official docs.
 
-Sample steps to modify, validate, and convert these specs:
-
-1. Load the `yaml` into https://editor.swagger.io/ (or use the equiv offline variation) and make appropriate changes and validate syntax.
-   - _Refer to the [OpenAPI guide](https://swagger.io/docs/specification/v3_0/about/) and/or [official specs](https://spec.openapis.org/) for more details_
-
-2. Copy the modified `yaml` back into this repo
-
-3. Generate an AsciiDoc from the modified `yaml` (change `swagger_dir` and `srcfile` as appropriate)
-          
-       swagger_dir=/path-to-stackrox-repo/central/docs/api_custom_routes
-
-       srcfile=image_service_swagger.yaml
-
-       docker run --rm -v "$swagger_dir:/local" openapitools/openapi-generator-cli generate -i /local/$srcfile -g asciidoc -o /local/asciidoc
+This directory includes manually created OpenAPI/Swagger specs for Central's custom routes defined in [/central/main.go](/central/main.go) (refer to `customRoutes()`)
 
-4. Clone `rhacs-api-docs-gen` repo: https://github.com/gaurav-nelson/rhacs-api-docs-gen
+These specs will roll up under the v1 API inside of Central as well as in the official docs on docs.redhat.com.
 
-5. Move the generated `adoc` file to `scripts/` dir of `rhacs-api-docs-gen` tool
+## Creating a new spec for a custom route
 
-       mv $swagger_dir/asciidoc/index.adoc /path-to-rhacs-api-docs-gen-repo/scripts
+Creating a new spec for a custom route involves a few steps.
 
-6. Execute `scripts/updateasciidoc.js` to cleanup the `adoc` file (will be modified in place)
+If you want to maintain the spec in YAML:
 
-       cd /path-to-rhacs-api-docs-gen-repo
+1. Create a YAML-based swagger spec from scratch in the file format `<service_name>_swagger.yaml` in this directory.
path: central/docs/api_custom_routes/README.md
user: login: kylape
body: Yeah what was in there was snake case, but the existing swagger docs are camel case.  So I'll switch it all to came case.
created_at: 2025-03-13T14:26:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -3,13 +3,17 @@
 # Mergeswag.sh merges JSON-encoded Swagger specification files into one mongo file
 # called swagger.json.
 
-[ -z "$1" ] && echo >&2 "Please specify a folder to search for .swagger.json files" && exit 1
+[ -z "$1" ] && [[ "$1" =~ ^(1|2)$ ]] && echo >&2 "Please specify a valid API version number for merged swagger.json file" && exit 1
 
-[ -z "$2" ] && [[ "$2" =~ ^(1|2)$ ]] && echo >&2 "Please specify a valid API version number for merged swagger.json file" && exit 1
+[ -z "$2" ] && echo >&2 "Please specify at least one folder to search for .swagger.json files" && exit 1
 
 set -euo pipefail
 
-folder="$1"
+src=$(mktemp -d)
+
+for folder in "${@:2}"; do
+    find "$folder/" -name '*.swagger.json' -exec cp {} $src \;
path: scripts/mergeswag.sh
user: login: mtodor
body: Thank you!
created_at: 2025-03-13T16:14:33Z
author_association: CONTRIBUTOR

PR comments:
user: login: rhacs-bot
created_at: 2025-03-10T17:44:48Z
body: [//]: # (meta:type=build)

Images are ready for the commit at 311f489.

To use with deploy scripts, first `export MAIN_IMAGE_TAG=4.8.x-200-g311f489b9d`.
author_association: CONTRIBUTOR
user: login: codecov[bot]
created_at: 2025-03-13T16:19:37Z
body: ## [Codecov](https://app.codecov.io/gh/stackrox/stackrox/pull/14558?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=stackrox) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 49.29%. Comparing base [(`b747ce8`)](https://app.codecov.io/gh/stackrox/stackrox/commit/b747ce8bc1fad373a1c959830a20fd4e5e114531?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=stackrox) to head [(`311f489`)](https://app.codecov.io/gh/stackrox/stackrox/commit/311f489b9d309c14b616604a27a889c72196146b?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=stackrox).
> Report is 30 commits behind head on master.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master   #14558      +/-   ##
==========================================
- Coverage   49.34%   49.29%   -0.06%     
==========================================
  Files        2526     2529       +3     
  Lines      184595   185039     +444     
==========================================
+ Hits        91097    91209     +112     
- Misses      86278    86603     +325     
- Partials     7220     7227       +7     
```

| [Flag](https://app.codecov.io/gh/stackrox/stackrox/pull/14558/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=stackrox) | Coverage Δ | |
|---|---|---|
| [go-unit-tests](https://app.codecov.io/gh/stackrox/stackrox/pull/14558/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=stackrox) | `49.29% <ø> (-0.06%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=stackrox#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/stackrox/stackrox/pull/14558?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=stackrox).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=stackrox).

<details><summary>🚀 New features to boost your workflow: </summary>

- 📦 [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE
user: login: openshift-ci[bot]
created_at: 2025-03-13T20:29:30Z
body: @kylape: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
ci/prow/ocp-4-12-scanner-v4-install-tests | 311f489b9d309c14b616604a27a889c72196146b | [link](https://prow.ci.openshift.org/view/gs/test-platform-results/pr-logs/pull/stackrox_stackrox/14558/pull-ci-stackrox-stackrox-master-ocp-4-12-scanner-v4-install-tests/1900205851008831488) | false | `/test ocp-4-12-scanner-v4-install-tests`

[Full PR test history](https://prow.ci.openshift.org/pr-history?org=stackrox&repo=stackrox&pr=14558). [Your PR dashboard](https://prow.ci.openshift.org/pr?query=is:pr+state:open+author:kylape).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
author_association: NONE
user: login: kylape
created_at: 2025-03-14T21:54:33Z
body: ![image](https://github.com/user-attachments/assets/1555a3c2-bd1b-4f58-82c2-6589f3dc7471)

author_association: CONTRIBUTOR