From 9ce9e49da32d57da0d5ea0e3a48ff062fe91d5c0 Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Wed, 19 Nov 2025 12:39:23 +0000 Subject: [PATCH] Add attributes to mark specs as generated files --- .github/workflows/frontend-lint.yml | 4 +- Makefile | 3 +- package.json | 2 +- packages/grafana-api-clients/package.json | 3 +- .../src/generator/README.md | 2 +- .../src/generator/helpers.ts | 2 +- .../src/scripts/generate-rtk-apis.ts | 2 +- packages/grafana-openapi/.gitattributes | 2 + packages/grafana-openapi/package.json | 23 ++-- .../grafana-openapi/src/api/openapi3.json | 90 ++++++------ .../apis/dashboard.grafana.app-v0alpha1.json | 9 ++ .../src/apis/iam.grafana.app-v0alpha1.json | 129 ++++++++++++++++++ .../src/scripts/process-specs.ts | 18 ++- pkg/tests/apis/openapi_snapshots/README.md | 8 +- yarn.lock | 1 + 15 files changed, 219 insertions(+), 79 deletions(-) create mode 100644 packages/grafana-openapi/.gitattributes rename packages/{grafana-api-clients => grafana-openapi}/src/scripts/process-specs.ts (93%) diff --git a/.github/workflows/frontend-lint.yml b/.github/workflows/frontend-lint.yml index 2bc962c870a..01dab970d6c 100644 --- a/.github/workflows/frontend-lint.yml +++ b/.github/workflows/frontend-lint.yml @@ -172,11 +172,11 @@ jobs: - name: Generate OpenAPI specs run: | extract_error_message='ERROR! OpenAPI generation failed!' - yarn generate-openapi || (echo "${extract_error_message}" && false) + yarn generate:openapi || (echo "${extract_error_message}" && false) - name: Verify generated specs run: | git add -N . - uncommited_error_message="ERROR! OpenAPI generation has not been committed. Please run 'yarn generate-openapi', commit the changes and push again." + uncommited_error_message="ERROR! OpenAPI generation has not been committed. Please run 'yarn generate:openapi', commit the changes and push again." file_diff="$(git diff --name-only ':!conf')" if [ -n "$file_diff" ]; then echo "$file_diff" diff --git a/Makefile b/Makefile index d15a732ce3d..ff0b5941c6f 100644 --- a/Makefile +++ b/Makefile @@ -130,8 +130,7 @@ openapi3-gen: swagger-gen ## Generates OpenApi 3 specs from the Swagger 2 alread .PHONY: all-openapi3-gen all-openapi3-gen: openapi3-gen $(GO) test ./pkg/tests/apis || true - yarn workspace @grafana/api-clients process-specs - yarn workspace @grafana/openapi copyfiles + yarn workspace @grafana/openapi process-specs ##@ Internationalisation .PHONY: i18n-extract-enterprise diff --git a/package.json b/package.json index f0ca6d5362d..85b9ccc1646 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,7 @@ "plugin:i18n-extract": "nx run-many -t i18n-extract --projects='tag:scope:plugin'", "generate-apis": "yarn workspace @grafana/api-clients generate-apis", "generate:api-client": "yarn workspace @grafana/api-clients generate:api-client", - "generate-openapi": "yarn workspace @grafana/openapi generate-openapi" + "generate:openapi": "yarn workspace @grafana/openapi generate:openapi" }, "grafana": { "whatsNewUrl": "https://grafana.com/docs/grafana/next/whatsnew/whats-new-in-v%[1]s-%[2]s/", diff --git a/packages/grafana-api-clients/package.json b/packages/grafana-api-clients/package.json index 37fdf558cf3..c3e2c84160d 100644 --- a/packages/grafana-api-clients/package.json +++ b/packages/grafana-api-clients/package.json @@ -104,8 +104,7 @@ "typecheck": "tsc --emitDeclarationOnly false --noEmit", "prepack": "cp package.json package.json.bak && node ../../scripts/prepare-npm-package.js", "postpack": "mv package.json.bak package.json", - "process-specs": "NODE_OPTIONS='--disable-warning=ExperimentalWarning' node --experimental-strip-types src/scripts/process-specs.ts", - "generate-apis": "yarn process-specs && NODE_OPTIONS='--disable-warning=ExperimentalWarning' rtk-query-codegen-openapi ./src/scripts/generate-rtk-apis.ts", + "generate-apis": "yarn workspace @grafana/openapi process-specs && NODE_OPTIONS='--disable-warning=ExperimentalWarning' rtk-query-codegen-openapi ./src/scripts/generate-rtk-apis.ts", "generate:api-client": "NODE_OPTIONS='--experimental-strip-types --disable-warning=ExperimentalWarning' plop --plopfile src/generator/plopfile.ts" }, "devDependencies": { diff --git a/packages/grafana-api-clients/src/generator/README.md b/packages/grafana-api-clients/src/generator/README.md index b0598b9d005..11f16c3681c 100644 --- a/packages/grafana-api-clients/src/generator/README.md +++ b/packages/grafana-api-clients/src/generator/README.md @@ -45,7 +45,7 @@ If an error about a missing OpenAPI schema appears, check that: 1. The API group and version exist in the backend 2. The `TestIntegrationOpenAPIs` test has been run to generate the schema (step 1 in the [main API documentation](../../public/app/api/README.md)). -3. The schema file exists at `data/openapi/-.json` +3. The schema file exists at `packages/grafana-openapi/src/apis/-.json` ### Validation Errors diff --git a/packages/grafana-api-clients/src/generator/helpers.ts b/packages/grafana-api-clients/src/generator/helpers.ts index 2ac2ce01389..4563a042eb1 100644 --- a/packages/grafana-api-clients/src/generator/helpers.ts +++ b/packages/grafana-api-clients/src/generator/helpers.ts @@ -66,7 +66,7 @@ export const runGenerateApis = let command; if (isEnterprise) { command = - 'yarn workspace @grafana/api-clients process-specs && npx rtk-query-codegen-openapi ./local/generate-enterprise-apis.ts'; + 'yarn workspace @grafana/openapi process-specs && npx rtk-query-codegen-openapi ./local/generate-enterprise-apis.ts'; } else { command = 'yarn workspace @grafana/api-clients generate-apis'; } diff --git a/packages/grafana-api-clients/src/scripts/generate-rtk-apis.ts b/packages/grafana-api-clients/src/scripts/generate-rtk-apis.ts index 776ad4131ae..4106968ec18 100644 --- a/packages/grafana-api-clients/src/scripts/generate-rtk-apis.ts +++ b/packages/grafana-api-clients/src/scripts/generate-rtk-apis.ts @@ -30,7 +30,7 @@ const createAPIConfig = (app: string, version: string, filterEndpoints?: Endpoin const filePath = `../clients/rtkq/${app}/${version}/endpoints.gen.ts`; return { [filePath]: { - schemaFile: path.join(basePath, `data/openapi/${app}.grafana.app-${version}.json`), + schemaFile: path.join(basePath, `packages/grafana-openapi/src/apis/${app}.grafana.app-${version}.json`), apiFile: `../clients/rtkq/${app}/${version}/baseAPI.ts`, filterEndpoints, tag: true, diff --git a/packages/grafana-openapi/.gitattributes b/packages/grafana-openapi/.gitattributes new file mode 100644 index 00000000000..c02f8c5bbc0 --- /dev/null +++ b/packages/grafana-openapi/.gitattributes @@ -0,0 +1,2 @@ +api/*.json linguist-generated +apis/*.json linguist-generated diff --git a/packages/grafana-openapi/package.json b/packages/grafana-openapi/package.json index c4c5301d81a..8337a53d6e0 100644 --- a/packages/grafana-openapi/package.json +++ b/packages/grafana-openapi/package.json @@ -9,6 +9,7 @@ "openapi", "typescript" ], + "type": "module", "sideEffects": false, "repository": { "type": "git", @@ -35,24 +36,24 @@ "LICENSE_APACHE2" ], "scripts": { - "build": "", - "bundle": "cp ./src/apis/* ./dist/apis && cp ./src/api/* ./dist/api", + "build": "yarn clean && yarn create:folders && yarn copy:dist", "clean": "rm -rf ./dist && rm -f package.tgz", - "copy-api": "cp ../../public/openapi3.json ./src/api", - "copy-apis": "cp ../../data/openapi/* ./src/apis", - "copyfiles": "yarn copy-api && yarn copy-apis && yarn format", - "createfolders": "mkdir dist && mkdir dist/api && mkdir dist/apis", - "format": "prettier --list-different \"**/*.json\" --write", - "generate-openapi": "make -C ../../ all-openapi3-gen", + "copy:api": "cp ../../public/openapi3.json ./src/api", + "copy:dist": "cp ./src/apis/* ./dist/apis && cp ./src/api/* ./dist/api", + "create:folders": "mkdir -p dist/api dist/apis", + "format": "prettier \"**/*.json\" --write --log-level=warn", + "generate:openapi": "make -C ../../ all-openapi3-gen", "typecheck": "", - "prepack": "yarn clean && yarn createfolders && yarn bundle", - "postpack": "" + "prepack": "yarn build", + "postpack": "", + "process-specs": "node src/scripts/process-specs.ts && yarn copy:api && yarn format" }, "engines": { - "node": ">= 22" + "node": ">= 24 <25" }, "packageManager": "yarn@4.11.0", "devDependencies": { + "openapi-types": "^12.1.3", "prettier": "3.6.2" } } diff --git a/packages/grafana-openapi/src/api/openapi3.json b/packages/grafana-openapi/src/api/openapi3.json index 478006cd278..3e606dab1b6 100644 --- a/packages/grafana-openapi/src/api/openapi3.json +++ b/packages/grafana-openapi/src/api/openapi3.json @@ -6079,6 +6079,10 @@ "is_paused": { "type": "boolean" }, + "message": { + "description": "Field is only populated when listing alert rule versions.", + "type": "string" + }, "metadata": { "$ref": "#/components/schemas/AlertRuleMetadata" }, @@ -7604,6 +7608,28 @@ "TLSConfig": { "$ref": "#/components/schemas/TLSConfig" }, + "audience": { + "description": "Audience optionally specifies the intended audience of the\nrequest. If empty, the value of TokenURL is used as the\nintended audience. Only used if\nGrantType is set to \"urn:ietf:params:oauth:grant-type:jwt-bearer\".", + "type": "string" + }, + "claims": { + "additionalProperties": {}, + "description": "Claims is a map of claims to be added to the JWT token. Only used if\nGrantType is set to \"urn:ietf:params:oauth:grant-type:jwt-bearer\".", + "type": "object" + }, + "client_certificate_key": { + "$ref": "#/components/schemas/Secret" + }, + "client_certificate_key_file": { + "type": "string" + }, + "client_certificate_key_id": { + "type": "string" + }, + "client_certificate_key_ref": { + "description": "ClientCertificateKeyRef is the name of the secret within the secret manager to use as the client\nsecret.", + "type": "string" + }, "client_id": { "type": "string" }, @@ -7623,6 +7649,14 @@ }, "type": "object" }, + "grant_type": { + "description": "GrantType is the OAuth2 grant type to use. It can be one of\n\"client_credentials\" or \"urn:ietf:params:oauth:grant-type:jwt-bearer\" (RFC 7523).\nDefault value is \"client_credentials\"", + "type": "string" + }, + "iss": { + "description": "Iss is the OAuth client identifier used when communicating with\nthe configured OAuth provider. Default value is client_id. Only used if\nGrantType is set to \"urn:ietf:params:oauth:grant-type:jwt-bearer\".", + "type": "string" + }, "no_proxy": { "description": "NoProxy contains addresses that should not use a proxy.", "type": "string" @@ -7643,6 +7677,10 @@ }, "type": "array" }, + "signature_algorithm": { + "description": "SignatureAlgorithm is the RSA algorithm used to sign JWT token. Only used if\nGrantType is set to \"urn:ietf:params:oauth:grant-type:jwt-bearer\".\nDefault value is RS256 and valid values RS256, RS384, RS512", + "type": "string" + }, "token_url": { "type": "string" } @@ -11347,6 +11385,9 @@ "teamId": { "format": "int64", "type": "integer" + }, + "uid": { + "type": "string" } }, "type": "object" @@ -16492,7 +16533,7 @@ }, { "in": "header", - "name": "x-grafana-alerting-notification-receiver", + "name": "x-grafana-alerting-notification-settings", "schema": { "type": "string" } @@ -16837,7 +16878,7 @@ }, { "in": "header", - "name": "x-grafana-alerting-notification-receiver", + "name": "x-grafana-alerting-notification-settings", "schema": { "type": "string" } @@ -17017,51 +17058,6 @@ "tags": ["dashboards", "snapshots"] } }, - "/dashboards/calculate-diff": { - "post": { - "operationId": "calculateDashboardDiff", - "requestBody": { - "content": { - "application/json": { - "schema": { - "properties": { - "base": { - "$ref": "#/components/schemas/CalculateDiffTarget" - }, - "diffType": { - "description": "The type of diff to return\nDescription:\n`basic`\n`json`", - "enum": ["basic", "json"], - "type": "string" - }, - "new": { - "$ref": "#/components/schemas/CalculateDiffTarget" - } - }, - "type": "object" - } - } - }, - "required": true, - "x-originalParamName": "Body" - }, - "responses": { - "200": { - "$ref": "#/components/responses/calculateDashboardDiffResponse" - }, - "401": { - "$ref": "#/components/responses/unauthorisedError" - }, - "403": { - "$ref": "#/components/responses/forbiddenError" - }, - "500": { - "$ref": "#/components/responses/internalServerError" - } - }, - "summary": "Perform diff on two dashboards.", - "tags": ["dashboards"] - } - }, "/dashboards/db": { "post": { "description": "Creates a new dashboard or updates an existing dashboard.\nNote: This endpoint is not intended for creating folders, use `POST /api/folders` for that.", diff --git a/packages/grafana-openapi/src/apis/dashboard.grafana.app-v0alpha1.json b/packages/grafana-openapi/src/apis/dashboard.grafana.app-v0alpha1.json index abc6e3c923f..4bfdc7db4fa 100644 --- a/packages/grafana-openapi/src/apis/dashboard.grafana.app-v0alpha1.json +++ b/packages/grafana-openapi/src/apis/dashboard.grafana.app-v0alpha1.json @@ -1723,6 +1723,15 @@ "type": "string" } }, + { + "name": "permission", + "in": "query", + "description": "permission needed for the resource (view, edit, admin)", + "schema": { + "type": "string", + "enum": ["view", "edit", "admin"] + } + }, { "name": "sort", "in": "query", diff --git a/packages/grafana-openapi/src/apis/iam.grafana.app-v0alpha1.json b/packages/grafana-openapi/src/apis/iam.grafana.app-v0alpha1.json index 463a79a1e01..e1a3046e05c 100644 --- a/packages/grafana-openapi/src/apis/iam.grafana.app-v0alpha1.json +++ b/packages/grafana-openapi/src/apis/iam.grafana.app-v0alpha1.json @@ -3809,6 +3809,43 @@ } ] }, + "/teams/{name}/groups": { + "get": { + "tags": ["Team"], + "description": "connect GET requests to groups of Team", + "operationId": "getTeamGroups", + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/GetGroups" + } + } + } + } + }, + "x-kubernetes-action": "connect", + "x-kubernetes-group-version-kind": { + "group": "iam.grafana.app", + "version": "v0alpha1", + "kind": "GetGroups" + } + }, + "parameters": [ + { + "name": "name", + "in": "path", + "description": "name of the GetGroups", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + } + ] + }, "/teams/{name}/members": { "get": { "tags": ["Team"], @@ -4789,6 +4826,38 @@ } } }, + "GetGroups": { + "type": "object", + "required": ["items"], + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "items": { + "type": "array", + "items": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/VersionsV0alpha1Kinds7RoutesGroupsGETResponseExternalGroupMapping" + } + ] + } + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "iam.grafana.app", + "kind": "GetGroups", + "version": "v0alpha1" + } + ] + }, "ServiceAccount": { "type": "object", "required": ["metadata", "spec"], @@ -5236,6 +5305,20 @@ } } }, + "VersionsV0alpha1Kinds7RoutesGroupsGETResponseExternalGroupMapping": { + "type": "object", + "required": ["name", "externalGroup"], + "properties": { + "externalGroup": { + "type": "string", + "default": "" + }, + "name": { + "type": "string", + "default": "" + } + } + }, "Unstructured": { "type": "object", "additionalProperties": true, @@ -5856,6 +5939,38 @@ } } }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GetGroups": { + "type": "object", + "required": ["items"], + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "items": { + "type": "array", + "items": { + "default": {} + } + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GetGroupsBody": { + "type": "object", + "required": ["items"], + "properties": { + "items": { + "type": "array", + "items": { + "default": {} + } + } + } + }, "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRole": { "type": "object", "required": ["metadata", "spec"], @@ -7036,6 +7151,20 @@ } } }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.VersionsV0alpha1Kinds7RoutesGroupsGETResponseExternalGroupMapping": { + "type": "object", + "required": ["name", "externalGroup"], + "properties": { + "externalGroup": { + "type": "string", + "default": "" + }, + "name": { + "type": "string", + "default": "" + } + } + }, "APIResource": { "description": "APIResource specifies the name of a resource and whether it is namespaced.", "type": "object", diff --git a/packages/grafana-api-clients/src/scripts/process-specs.ts b/packages/grafana-openapi/src/scripts/process-specs.ts similarity index 93% rename from packages/grafana-api-clients/src/scripts/process-specs.ts rename to packages/grafana-openapi/src/scripts/process-specs.ts index 5503b24408a..5ce2d636e9a 100644 --- a/packages/grafana-api-clients/src/scripts/process-specs.ts +++ b/packages/grafana-openapi/src/scripts/process-specs.ts @@ -175,12 +175,16 @@ function processDirectory(sourceDir: string, outputDir: string) { // Grafana root path - navigate up from this script's directory const basePath = path.resolve(import.meta.dirname, '../../../..'); -const sourceDirs = [ - path.join(basePath, 'pkg/tests/apis/openapi_snapshots'), - path.join(basePath, 'pkg/extensions/apiserver/tests/openapi_snapshots'), -]; -const outputDir = path.join(basePath, 'data/openapi'); +const oss = { + source: path.join(basePath, 'pkg/tests/apis/openapi_snapshots'), + output: path.join(import.meta.dirname, '../apis'), +}; -for (const sourceDir of sourceDirs) { - processDirectory(sourceDir, outputDir); +const enterprise = { + source: path.join(basePath, 'pkg/extensions/apiserver/tests/openapi_snapshots'), + output: path.join(basePath, 'data/openapi'), +}; + +for (const config of [oss, enterprise]) { + processDirectory(config.source, config.output); } diff --git a/pkg/tests/apis/openapi_snapshots/README.md b/pkg/tests/apis/openapi_snapshots/README.md index 7ca313c4be3..c10c20d2083 100644 --- a/pkg/tests/apis/openapi_snapshots/README.md +++ b/pkg/tests/apis/openapi_snapshots/README.md @@ -2,14 +2,14 @@ This folder contains a rendered OpenAPI file for each group/version. The “real ## Generating RTK API Clients -The RTK API clients are generated from processed OpenAPI files using the `scripts/process-specs.ts` script. The source files are in `pkg/tests/apis/openapi_snapshots`, and the processed files are stored in the `data/openapi` directory. Spec processing happens as part of `yarn generate-apis` task, but can also be triggered separately (see below). +The RTK API clients are generated from processed OpenAPI files using the `packages/grafana-openapi/src/scripts/process-specs.ts` script. The source files are in `pkg/tests/apis/openapi_snapshots`, and the processed files are stored in the `packages/grafana-openapi/src/apis` directory. Spec processing happens as part of `yarn generate-apis` task, but can also be triggered separately (see below). To generate or update the RTK API clients: 1. _If generating or updating an RTK client for the first time_, update `scripts/generate-rtk-apis.js` so `schemaFile` points to the processed spec files, for example: ```typescript '../public/app/features/dashboards/api/endpoints.gen.ts': { - schemaFile: '../data/openapi/dashboard.grafana.app-v0alpha1.json', + schemaFile: 'packages/grafana-openapi/src/apis/dashboard.grafana.app-v0alpha1.json', }, ``` @@ -24,9 +24,9 @@ To generate or update the RTK API clients: ```bash yarn generate-apis ``` - This command generates (or updates) the spec files in the `data/openapi` directory and generates the RTK API clients. + This command generates (or updates) the spec files in the `packages/grafana-openapi/src/apis` directory and generates the RTK API clients. If you want to process the OpenAPI files without generating the RTK API clients (for example, if you have a separate `generate-rtk-apis` file), run: ```bash -yarn process-specs +yarn workspace @grafana/openapi process-specs ``` diff --git a/yarn.lock b/yarn.lock index 180ffe53391..bc7ec316fec 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3416,6 +3416,7 @@ __metadata: version: 0.0.0-use.local resolution: "@grafana/openapi@workspace:packages/grafana-openapi" dependencies: + openapi-types: "npm:^12.1.3" prettier: "npm:3.6.2" languageName: unknown linkType: soft