Merge pull request #27573 from aramissennyeydd/sennyeya/docsite-openapi

feat(docs): autogenerate API documentation
This commit is contained in:
Patrik Oldsberg
2025-03-02 15:05:45 +01:00
committed by GitHub
18 changed files with 4304 additions and 587 deletions
@@ -255,6 +255,7 @@ misconfiguration
misconfigured
mkdocs
Mkdocs
modularization
monorepo
Monorepo
monorepos
+47
View File
@@ -80,6 +80,22 @@ jobs:
if-no-files-found: error
retention-days: 1
- name: microsite yarn install
run: yarn install --immutable
working-directory: microsite
- name: build OpenAPI API docs
working-directory: microsite
run: yarn docusaurus gen-api-docs all
- name: upload OpenAPI API docs
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
with:
name: stable-openapi-docs
path: docs/**/*.api.mdx
if-no-files-found: error
retention-days: 1
next:
runs-on: ubuntu-latest
concurrency:
@@ -137,6 +153,22 @@ jobs:
if-no-files-found: error
retention-days: 1
- name: microsite yarn install
run: yarn install --immutable
working-directory: microsite
- name: build OpenAPI API docs
working-directory: microsite
run: yarn docusaurus gen-api-docs all
- name: upload OpenAPI API docs
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
with:
name: next-openapi-docs
path: docs/**/*.api.mdx
if-no-files-found: error
retention-days: 1
deploy-microsite-and-storybook:
permissions:
contents: write # for JamesIves/github-pages-deploy-action to push changes in repo
@@ -184,6 +216,12 @@ jobs:
name: stable-reference
path: docs/reference
- name: download stable OpenAPI API docs
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4
with:
name: stable-openapi-docs
path: docs/**/*.api.mdx
- name: grab lastest releases docs
run: |
git fetch origin master --depth 1
@@ -196,6 +234,9 @@ jobs:
- name: clear API reference
run: rm -r docs/reference
- name: clear OpenAPI reference
run: find . -name '*.api.mdx' -delete
# Next docs
- name: checkout master
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
@@ -212,6 +253,12 @@ jobs:
name: next-reference
path: docs/reference
- name: download next OpenAPI API docs
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4
with:
name: next-openapi-docs
path: docs/**/*.api.mdx
- name: build microsite
run: yarn build
working-directory: microsite
+5
View File
@@ -45,6 +45,11 @@ bower_components
# Documentation reference, generated by build:api-docs
docs/reference
# OpenAPI auto-generated API documentation.
docs/**/*.api.mdx
docs/**/*.info.mdx
docs/**/sidebar.ts
# node-waf configuration
.lock-wscript
+1 -1
View File
@@ -27,7 +27,7 @@ Records should be stored under the `architecture-decisions` directory.
- Address and integrate feedback from the community
- Eventually, assign a number
- Add the path of the ADR to the microsite sidebar in
[`sidebars.js`](https://github.com/backstage/backstage/blob/master/microsite/sidebars.js)
[`sidebars.ts`](https://github.com/backstage/backstage/blob/master/microsite/sidebars.ts)
- Add the path of the ADR to the
[`mkdocs.yml`](https://github.com/backstage/backstage/blob/master/mkdocs.yml)
- Merge the pull request
+1 -1
View File
@@ -39,7 +39,7 @@ the code.
- [`docs/`](https://github.com/backstage/backstage/tree/master/docs) - This is
where we keep all of our documentation Markdown files. These end up on
https://backstage.io/docs. Just keep in mind that changes to the
[`sidebars.js`](https://github.com/backstage/backstage/blob/master/microsite/sidebars.js)
[`sidebars.ts`](https://github.com/backstage/backstage/blob/master/microsite/sidebars.ts)
file may be needed as sections are added/removed.
- [`.editorconfig`](https://github.com/backstage/backstage/tree/master/.editorconfig) -
+16
View File
@@ -0,0 +1,16 @@
---
id: 404
title: Unable to find API docs
sidebar_label: API
description: Unable to find the requested API docs.
---
## API Docs Missing
If you're seeing this page at backstage.io/docs, please open an issue in the main repository. Something is wrong.
If you're seeing this page locally and you want to see the actual API docs for this plugin, you need to
1. cd into the `microsite/` directory.
2. run `yarn generate-openapi-docs` to generate the API docs.
3. rerun `yarn start` to pick up the new generated docs.
+1 -1
View File
@@ -53,7 +53,7 @@ Additional steps for the main line release
- Mention any security fixes
- Create Release Notes PR
- Add the release note file as [`/docs/releases/vx.y.0.md`](./releases)
- Add an entry to [`/microsite/sidebar.js`](https://github.com/backstage/backstage/blob/master/microsite/sidebars.js) for the release note
- Add an entry to [`/microsite/sidebars.ts`](https://github.com/backstage/backstage/blob/master/microsite/sidebars.ts) for the release note
- Update the navigation bar item in [`/microsite/docusaurus.config.ts`](https://github.com/backstage/backstage/blob/master/microsite/docusaurus.config.ts) to point to the new release note
- Finally copy the content, without the metadata header, into the description of the [`Version Packages` Pull Request](https://github.com/backstage/backstage/pulls?q=is%3Aopen+is%3Apr+in%3Atitle+%22Version+Packages)
+75 -1
View File
@@ -23,6 +23,7 @@ import type * as Preset from '@docusaurus/preset-classic';
import { Config } from '@docusaurus/types';
import RedirectPlugin from '@docusaurus/plugin-client-redirects';
import { releases } from './releases';
import type * as OpenApiPlugin from 'docusaurus-plugin-openapi-docs';
const backstageTheme = themes.vsDark;
backstageTheme.plain.backgroundColor = '#232323';
@@ -54,6 +55,14 @@ const PatchedRedirectPlugin: typeof RedirectPlugin = (ctx, opts) => {
};
};
const defaultOpenApiOptions = {
hideSendButton: true,
sidebarOptions: {
groupPathsBy: 'tag',
categoryLinkSource: 'tag',
},
} satisfies OpenApiPlugin.Options;
const config: Config = {
title: 'Backstage Software Catalog and Developer Platform',
tagline: 'An open source framework for building developer portals',
@@ -89,7 +98,7 @@ const config: Config = {
return `https://github.com/backstage/backstage/edit/master/docs/${docPath}`;
},
path: '../docs',
sidebarPath: 'sidebars.js',
sidebarPath: 'sidebars.ts',
...(useVersionedDocs
? {
includeCurrentVersion: true,
@@ -110,6 +119,7 @@ const config: Config = {
},
}
: undefined),
docItemComponent: '@theme/ApiItem',
},
blog: {
path: 'blog',
@@ -266,8 +276,72 @@ const config: Config = {
ratingMode: 'stars',
},
],
[
'docusaurus-plugin-openapi-docs',
{
id: 'api', // plugin id
docsPluginId: 'classic', // configured for preset-classic
config: {
catalog: {
...defaultOpenApiOptions,
specPath: '../plugins/catalog-backend/src/schema/openapi.yaml',
outputDir: '../docs/features/software-catalog/api',
} satisfies OpenApiPlugin.Options,
search: {
...defaultOpenApiOptions,
specPath: '../plugins/search-backend/src/schema/openapi.yaml',
outputDir: '../docs/features/search/api',
} satisfies OpenApiPlugin.Options,
},
},
],
],
themes: ['docusaurus-theme-openapi-docs'],
themeConfig: {
languageTabs: [
{
highlight: 'javascript',
language: 'nodejs',
logoClass: 'nodejs',
},
{
highlight: 'javascript',
language: 'javascript',
logoClass: 'javascript',
},
{
highlight: 'bash',
language: 'curl',
logoClass: 'curl',
},
{
highlight: 'powershell',
language: 'powershell',
logoClass: 'powershell',
},
{
highlight: 'python',
language: 'python',
logoClass: 'python',
},
{
highlight: 'java',
language: 'java',
logoClass: 'java',
variant: 'unirest',
},
{
highlight: 'go',
language: 'go',
logoClass: 'go',
},
{
highlight: 'rust',
language: 'rust',
logoClass: 'rust',
},
],
colorMode: {
defaultMode: 'dark',
disableSwitch: true,
+2 -4
View File
@@ -14,7 +14,7 @@
* limitations under the License.
*/
const sidebars = require('./sidebars');
const { releases } = require('./releases');
module.exports = {
ci: {
@@ -39,9 +39,7 @@ module.exports = {
/** Community */
'http://localhost:3000/community',
/** Releases */
...sidebars.releases['Release Notes'].map(
path => `http://localhost:3000/docs/${path}`,
),
...releases.map(path => `http://localhost:3000/docs/releases/${path}`),
],
settings: {
onlyCategories: ['accessibility'],
+6
View File
@@ -7,6 +7,7 @@
"build": "node scripts/pre-build.js && docusaurus build",
"deploy": "docusaurus deploy",
"docusaurus": "docusaurus",
"generate-openapi-docs": "yarn docusaurus clean-api-docs all && yarn docusaurus gen-api-docs all",
"prettier:check": "prettier --check .",
"prettier:fix": "prettier --write .",
"publish-gh-pages": "docusaurus-publish",
@@ -18,6 +19,9 @@
"write-translations": "docusaurus-write-translations"
},
"prettier": "@backstage/cli/config/prettier",
"resolutions": {
"node-polyfill-webpack-plugin": "^3.0.0"
},
"dependencies": {
"@docusaurus/core": "^3.1.1",
"@docusaurus/plugin-client-redirects": "^3.1.1",
@@ -26,8 +30,10 @@
"@mdx-js/react": "^3.0.0",
"@swc/core": "^1.3.46",
"clsx": "^2.0.0",
"docusaurus-plugin-openapi-docs": "^4.3.0",
"docusaurus-plugin-sass": "^0.2.3",
"docusaurus-pushfeedback": "^1.0.0",
"docusaurus-theme-openapi-docs": "^4.3.0",
"luxon": "^3.0.0",
"prism-react-renderer": "^2.1.0",
"react": "^18.0.2",
@@ -1,6 +1,21 @@
const { releases } = require('./releases');
import { releases } from './releases';
module.exports = {
function tryToLoadCustomSidebar(ref) {
try {
return require(ref);
} catch (e) {
return [];
}
}
const catalogSidebar = tryToLoadCustomSidebar(
'../docs/features/software-catalog/api/sidebar.ts',
);
const searchSidebar = tryToLoadCustomSidebar(
'../docs/features/search/api/sidebar.ts',
);
export default {
docs: {
Overview: [
'overview/what-is-backstage',
@@ -136,6 +151,22 @@ module.exports = {
'features/search/search-overview',
'features/search/getting-started',
'features/search/concepts',
{
type: 'category',
label: 'API',
link:
searchSidebar.length > 0
? {
type: 'generated-index',
title: 'Search API',
slug: '/category/search-api',
}
: {
type: 'doc',
id: 'openapi/generated-docs/404',
},
items: searchSidebar,
},
'features/search/architecture',
'features/search/search-engines',
'features/search/collators',
@@ -158,7 +189,22 @@ module.exports = {
'features/software-catalog/extending-the-model',
'features/software-catalog/external-integrations',
'features/software-catalog/catalog-customization',
'features/software-catalog/software-catalog-api',
{
type: 'category',
label: 'API',
link:
catalogSidebar.length > 0
? {
type: 'generated-index',
title: 'Catalog API',
slug: '/category/catalog-api',
}
: {
type: 'doc',
id: 'openapi/generated-docs/404',
},
items: catalogSidebar,
},
'features/software-catalog/creating-the-catalog-graph',
'features/software-catalog/faq',
],
+3236 -317
View File
File diff suppressed because it is too large Load Diff
@@ -1,5 +1,5 @@
/*
* Copyright 2024 The Backstage Authors
* Copyright 2025 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,20 +22,20 @@ import { FetchApi } from '../types/fetch';
import crossFetch from 'cross-fetch';
import { pluginId } from '../pluginId';
import * as parser from 'uri-template';
import { AnalyzeLocationRequest } from '../models/AnalyzeLocationRequest.model';
import { AnalyzeLocationResponse } from '../models/AnalyzeLocationResponse.model';
import { CreateLocation201Response } from '../models/CreateLocation201Response.model';
import { CreateLocationRequest } from '../models/CreateLocationRequest.model';
import { EntitiesBatchResponse } from '../models/EntitiesBatchResponse.model';
import { EntitiesQueryResponse } from '../models/EntitiesQueryResponse.model';
import { Entity } from '../models/Entity.model';
import { EntityAncestryResponse } from '../models/EntityAncestryResponse.model';
import { EntityFacetsResponse } from '../models/EntityFacetsResponse.model';
import { GetEntitiesByRefsRequest } from '../models/GetEntitiesByRefsRequest.model';
import { GetLocations200ResponseInner } from '../models/GetLocations200ResponseInner.model';
import { Location } from '../models/Location.model';
import { RefreshEntityRequest } from '../models/RefreshEntityRequest.model';
import { ValidateEntityRequest } from '../models/ValidateEntityRequest.model';
import { AnalyzeLocationRequest } from '../models/AnalyzeLocationRequest.model';
import { AnalyzeLocationResponse } from '../models/AnalyzeLocationResponse.model';
import { CreateLocation201Response } from '../models/CreateLocation201Response.model';
import { CreateLocationRequest } from '../models/CreateLocationRequest.model';
import { GetLocations200ResponseInner } from '../models/GetLocations200ResponseInner.model';
import { Location } from '../models/Location.model';
/**
* Wraps the Response type to convey a type on the json call.
@@ -54,21 +54,6 @@ export type TypedResponse<T> = Omit<Response, 'json'> & {
export interface RequestOptions {
token?: string;
}
/**
* @public
*/
export type AnalyzeLocation = {
body: AnalyzeLocationRequest;
};
/**
* @public
*/
export type CreateLocation = {
body: CreateLocationRequest;
query: {
dryRun?: string;
};
};
/**
* @public
*/
@@ -77,14 +62,6 @@ export type DeleteEntityByUid = {
uid: string;
};
};
/**
* @public
*/
export type DeleteLocation = {
path: {
id: string;
};
};
/**
* @public
*/
@@ -159,6 +136,41 @@ export type GetEntityFacets = {
filter?: Array<string>;
};
};
/**
* @public
*/
export type RefreshEntity = {
body: RefreshEntityRequest;
};
/**
* @public
*/
export type ValidateEntity = {
body: ValidateEntityRequest;
};
/**
* @public
*/
export type AnalyzeLocation = {
body: AnalyzeLocationRequest;
};
/**
* @public
*/
export type CreateLocation = {
body: CreateLocationRequest;
query: {
dryRun?: string;
};
};
/**
* @public
*/
export type DeleteLocation = {
path: {
id: string;
};
};
/**
* @public
*/
@@ -181,18 +193,6 @@ export type GetLocationByEntity = {
* @public
*/
export type GetLocations = {};
/**
* @public
*/
export type RefreshEntity = {
body: RefreshEntityRequest;
};
/**
* @public
*/
export type ValidateEntity = {
body: ValidateEntityRequest;
};
/**
* @public
@@ -209,59 +209,6 @@ export class DefaultApiClient {
this.fetchApi = options.fetchApi || { fetch: crossFetch };
}
/**
* Validate a given location.
* @param analyzeLocationRequest -
*/
public async analyzeLocation(
// @ts-ignore
request: AnalyzeLocation,
options?: RequestOptions,
): Promise<TypedResponse<AnalyzeLocationResponse>> {
const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
const uriTemplate = `/analyze-location`;
const uri = parser.parse(uriTemplate).expand({});
return await this.fetchApi.fetch(`${baseUrl}${uri}`, {
headers: {
'Content-Type': 'application/json',
...(options?.token && { Authorization: `Bearer ${options?.token}` }),
},
method: 'POST',
body: JSON.stringify(request.body),
});
}
/**
* Create a location for a given target.
* @param createLocationRequest -
* @param dryRun -
*/
public async createLocation(
// @ts-ignore
request: CreateLocation,
options?: RequestOptions,
): Promise<TypedResponse<CreateLocation201Response>> {
const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
const uriTemplate = `/locations{?dryRun}`;
const uri = parser.parse(uriTemplate).expand({
...request.query,
});
return await this.fetchApi.fetch(`${baseUrl}${uri}`, {
headers: {
'Content-Type': 'application/json',
...(options?.token && { Authorization: `Bearer ${options?.token}` }),
},
method: 'POST',
body: JSON.stringify(request.body),
});
}
/**
* Delete a single entity by UID.
* @param uid -
@@ -289,40 +236,115 @@ export class DefaultApiClient {
}
/**
* Delete a location by id.
* @param id -
*/
public async deleteLocation(
// @ts-ignore
request: DeleteLocation,
options?: RequestOptions,
): Promise<TypedResponse<void>> {
const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
* Get all entities matching a given filter.
* @param fields - By default the full entities are returned, but you can pass in a &#x60;fields&#x60; query
parameter which selects what parts of the entity data to retain. This makes the
response smaller and faster to transfer, and may allow the catalog to perform
more efficient queries.
const uriTemplate = `/locations/{id}`;
The query parameter value is a comma separated list of simplified JSON paths
like above. Each path corresponds to the key of either a value, or of a subtree
root that you want to keep in the output. The rest is pruned away. For example,
specifying &#x60;?fields&#x3D;metadata.name,metadata.annotations,spec&#x60; retains only the
&#x60;name&#x60; and &#x60;annotations&#x60; fields of the &#x60;metadata&#x60; of each entity (it&#39;ll be an
object with at most two keys), keeps the entire &#x60;spec&#x60; unchanged, and cuts out
all other roots such as &#x60;relations&#x60;.
const uri = parser.parse(uriTemplate).expand({
id: request.path.id,
});
Some more real world usable examples:
return await this.fetchApi.fetch(`${baseUrl}${uri}`, {
headers: {
'Content-Type': 'application/json',
...(options?.token && { Authorization: `Bearer ${options?.token}` }),
},
method: 'DELETE',
});
- Return only enough data to form the full ref of each entity:
&#x60;/entities/by-query?fields&#x3D;kind,metadata.namespace,metadata.name&#x60;
* @param limit - Number of records to return in the response.
* @param filter - You can pass in one or more filter sets that get matched against each entity.
Each filter set is a number of conditions that all have to match for the
condition to be true (conditions effectively have an AND between them). At least
one filter set has to be true for the entity to be part of the result set
(filter sets effectively have an OR between them).
Example:
&#x60;&#x60;&#x60;text
/entities/by-query?filter&#x3D;kind&#x3D;user,metadata.namespace&#x3D;default&amp;filter&#x3D;kind&#x3D;group,spec.type
Return entities that match
Filter set 1:
Condition 1: kind &#x3D; user
AND
Condition 2: metadata.namespace &#x3D; default
OR
Filter set 2:
Condition 1: kind &#x3D; group
AND
Condition 2: spec.type exists
&#x60;&#x60;&#x60;
Each condition is either on the form &#x60;&lt;key&gt;&#x60;, or on the form &#x60;&lt;key&gt;&#x3D;&lt;value&gt;&#x60;.
The first form asserts on the existence of a certain key (with any value), and
the second asserts that the key exists and has a certain value. All checks are
always case _insensitive_.
In all cases, the key is a simplified JSON path in a given piece of entity data.
Each part of the path is a key of an object, and the traversal also descends
through arrays. There are two special forms:
- Array items that are simple value types (such as strings) match on a key-value
pair where the key is the item as a string, and the value is the string &#x60;true&#x60;
- Relations can be matched on a &#x60;relations.&lt;type&gt;&#x3D;&lt;targetRef&gt;&#x60; form
Let&#39;s look at a simplified example to illustrate the concept:
&#x60;&#x60;&#x60;json
{
&quot;a&quot;: {
&quot;b&quot;: [&quot;c&quot;, { &quot;d&quot;: 1 }],
&quot;e&quot;: 7
}
}
&#x60;&#x60;&#x60;
/**
* Get all entities matching a given filter.
* @param fields - Restrict to just these fields in the response.
* @param limit - Number of records to return in the response.
* @param filter - Filter for just the entities defined by this filter.
* @param offset - Number of records to skip in the query page.
* @param after - Pointer to the previous page of results.
* @param order -
*/
This would match any one of the following conditions:
- &#x60;a&#x60;
- &#x60;a.b&#x60;
- &#x60;a.b.c&#x60;
- &#x60;a.b.c&#x3D;true&#x60;
- &#x60;a.b.d&#x60;
- &#x60;a.b.d&#x3D;1&#x60;
- &#x60;a.e&#x60;
- &#x60;a.e&#x3D;7&#x60;
Some more real world usable examples:
- Return all orphaned entities:
&#x60;/entities/by-query?filter&#x3D;metadata.annotations.backstage.io/orphan&#x3D;true&#x60;
- Return all users and groups:
&#x60;/entities/by-query?filter&#x3D;kind&#x3D;user&amp;filter&#x3D;kind&#x3D;group&#x60;
- Return all service components:
&#x60;/entities/by-query?filter&#x3D;kind&#x3D;component,spec.type&#x3D;service&#x60;
- Return all entities with the &#x60;java&#x60; tag:
&#x60;/entities/by-query?filter&#x3D;metadata.tags.java&#x60;
- Return all users who are members of the &#x60;ops&#x60; group (note that the full
[reference](references.md) of the group is used):
&#x60;/entities/by-query?filter&#x3D;kind&#x3D;user,relations.memberof&#x3D;group:default/ops&#x60;
* @param offset - Number of records to skip in the query page.
* @param after - Pointer to the previous page of results.
* @param order -
*/
public async getEntities(
// @ts-ignore
request: GetEntities,
@@ -346,16 +368,148 @@ export class DefaultApiClient {
}
/**
* Search for entities by a given query.
* @param fields - Restrict to just these fields in the response.
* @param limit - Number of records to return in the response.
* @param offset - Number of records to skip in the query page.
* @param orderField - The fields to sort returned results by.
* @param cursor - Cursor to a set page of results.
* @param filter - Filter for just the entities defined by this filter.
* @param fullTextFilterTerm - Text search term.
* @param fullTextFilterFields - A comma separated list of fields to sort returned results by.
*/
* Search for entities by a given query.
* @param fields - By default the full entities are returned, but you can pass in a &#x60;fields&#x60; query
parameter which selects what parts of the entity data to retain. This makes the
response smaller and faster to transfer, and may allow the catalog to perform
more efficient queries.
The query parameter value is a comma separated list of simplified JSON paths
like above. Each path corresponds to the key of either a value, or of a subtree
root that you want to keep in the output. The rest is pruned away. For example,
specifying &#x60;?fields&#x3D;metadata.name,metadata.annotations,spec&#x60; retains only the
&#x60;name&#x60; and &#x60;annotations&#x60; fields of the &#x60;metadata&#x60; of each entity (it&#39;ll be an
object with at most two keys), keeps the entire &#x60;spec&#x60; unchanged, and cuts out
all other roots such as &#x60;relations&#x60;.
Some more real world usable examples:
- Return only enough data to form the full ref of each entity:
&#x60;/entities/by-query?fields&#x3D;kind,metadata.namespace,metadata.name&#x60;
* @param limit - Number of records to return in the response.
* @param offset - Number of records to skip in the query page.
* @param orderField - By default the entities are returned ordered by their internal uid. You can
customize the &#x60;orderField&#x60; query parameters to affect that ordering.
For example, to return entities by their name:
&#x60;/entities/by-query?orderField&#x3D;metadata.name,asc&#x60;
Each parameter can be followed by &#x60;asc&#x60; for ascending lexicographical order or
&#x60;desc&#x60; for descending (reverse) lexicographical order.
* @param cursor - You may pass the &#x60;cursor&#x60; query parameters to perform cursor based pagination
through the set of entities. The value of &#x60;cursor&#x60; will be returned in the response, under the &#x60;pageInfo&#x60; property:
&#x60;&#x60;&#x60;json
&quot;pageInfo&quot;: {
&quot;nextCursor&quot;: &quot;a-cursor&quot;,
&quot;prevCursor&quot;: &quot;another-cursor&quot;
}
&#x60;&#x60;&#x60;
If &#x60;nextCursor&#x60; exists, it can be used to retrieve the next batch of entities. Following the same approach,
if &#x60;prevCursor&#x60; exists, it can be used to retrieve the previous batch of entities.
- [&#x60;filter&#x60;](#filtering), for selecting only a subset of all entities
- [&#x60;fields&#x60;](#field-selection), for selecting only parts of the full data
structure of each entity
- &#x60;limit&#x60; for limiting the number of entities returned (20 is the default)
- [&#x60;orderField&#x60;](#ordering), for deciding the order of the entities
- &#x60;fullTextFilter&#x60;
**NOTE**: [&#x60;filter&#x60;, &#x60;orderField&#x60;, &#x60;fullTextFilter&#x60;] and &#x60;cursor&#x60; are mutually exclusive. This means that,
it isn&#39;t possible to change any of [&#x60;filter&#x60;, &#x60;orderField&#x60;, &#x60;fullTextFilter&#x60;] when passing &#x60;cursor&#x60; as query parameters,
as changing any of these properties will affect pagination. If any of &#x60;filter&#x60;, &#x60;orderField&#x60;, &#x60;fullTextFilter&#x60; is specified together with &#x60;cursor&#x60;, only the latter is taken into consideration.
* @param filter - You can pass in one or more filter sets that get matched against each entity.
Each filter set is a number of conditions that all have to match for the
condition to be true (conditions effectively have an AND between them). At least
one filter set has to be true for the entity to be part of the result set
(filter sets effectively have an OR between them).
Example:
&#x60;&#x60;&#x60;text
/entities/by-query?filter&#x3D;kind&#x3D;user,metadata.namespace&#x3D;default&amp;filter&#x3D;kind&#x3D;group,spec.type
Return entities that match
Filter set 1:
Condition 1: kind &#x3D; user
AND
Condition 2: metadata.namespace &#x3D; default
OR
Filter set 2:
Condition 1: kind &#x3D; group
AND
Condition 2: spec.type exists
&#x60;&#x60;&#x60;
Each condition is either on the form &#x60;&lt;key&gt;&#x60;, or on the form &#x60;&lt;key&gt;&#x3D;&lt;value&gt;&#x60;.
The first form asserts on the existence of a certain key (with any value), and
the second asserts that the key exists and has a certain value. All checks are
always case _insensitive_.
In all cases, the key is a simplified JSON path in a given piece of entity data.
Each part of the path is a key of an object, and the traversal also descends
through arrays. There are two special forms:
- Array items that are simple value types (such as strings) match on a key-value
pair where the key is the item as a string, and the value is the string &#x60;true&#x60;
- Relations can be matched on a &#x60;relations.&lt;type&gt;&#x3D;&lt;targetRef&gt;&#x60; form
Let&#39;s look at a simplified example to illustrate the concept:
&#x60;&#x60;&#x60;json
{
&quot;a&quot;: {
&quot;b&quot;: [&quot;c&quot;, { &quot;d&quot;: 1 }],
&quot;e&quot;: 7
}
}
&#x60;&#x60;&#x60;
This would match any one of the following conditions:
- &#x60;a&#x60;
- &#x60;a.b&#x60;
- &#x60;a.b.c&#x60;
- &#x60;a.b.c&#x3D;true&#x60;
- &#x60;a.b.d&#x60;
- &#x60;a.b.d&#x3D;1&#x60;
- &#x60;a.e&#x60;
- &#x60;a.e&#x3D;7&#x60;
Some more real world usable examples:
- Return all orphaned entities:
&#x60;/entities/by-query?filter&#x3D;metadata.annotations.backstage.io/orphan&#x3D;true&#x60;
- Return all users and groups:
&#x60;/entities/by-query?filter&#x3D;kind&#x3D;user&amp;filter&#x3D;kind&#x3D;group&#x60;
- Return all service components:
&#x60;/entities/by-query?filter&#x3D;kind&#x3D;component,spec.type&#x3D;service&#x60;
- Return all entities with the &#x60;java&#x60; tag:
&#x60;/entities/by-query?filter&#x3D;metadata.tags.java&#x60;
- Return all users who are members of the &#x60;ops&#x60; group (note that the full
[reference](references.md) of the group is used):
&#x60;/entities/by-query?filter&#x3D;kind&#x3D;user,relations.memberof&#x3D;group:default/ops&#x60;
* @param fullTextFilterTerm - Text search term.
* @param fullTextFilterFields - A comma separated list of fields to sort returned results by.
*/
public async getEntitiesByQuery(
// @ts-ignore
request: GetEntitiesByQuery,
@@ -379,10 +533,93 @@ export class DefaultApiClient {
}
/**
* Get a batch set of entities given an array of entityRefs.
* @param filter - Filter for just the entities defined by this filter.
* @param getEntitiesByRefsRequest -
*/
* Get a batch set of entities given an array of entityRefs.
* @param filter - You can pass in one or more filter sets that get matched against each entity.
Each filter set is a number of conditions that all have to match for the
condition to be true (conditions effectively have an AND between them). At least
one filter set has to be true for the entity to be part of the result set
(filter sets effectively have an OR between them).
Example:
&#x60;&#x60;&#x60;text
/entities/by-query?filter&#x3D;kind&#x3D;user,metadata.namespace&#x3D;default&amp;filter&#x3D;kind&#x3D;group,spec.type
Return entities that match
Filter set 1:
Condition 1: kind &#x3D; user
AND
Condition 2: metadata.namespace &#x3D; default
OR
Filter set 2:
Condition 1: kind &#x3D; group
AND
Condition 2: spec.type exists
&#x60;&#x60;&#x60;
Each condition is either on the form &#x60;&lt;key&gt;&#x60;, or on the form &#x60;&lt;key&gt;&#x3D;&lt;value&gt;&#x60;.
The first form asserts on the existence of a certain key (with any value), and
the second asserts that the key exists and has a certain value. All checks are
always case _insensitive_.
In all cases, the key is a simplified JSON path in a given piece of entity data.
Each part of the path is a key of an object, and the traversal also descends
through arrays. There are two special forms:
- Array items that are simple value types (such as strings) match on a key-value
pair where the key is the item as a string, and the value is the string &#x60;true&#x60;
- Relations can be matched on a &#x60;relations.&lt;type&gt;&#x3D;&lt;targetRef&gt;&#x60; form
Let&#39;s look at a simplified example to illustrate the concept:
&#x60;&#x60;&#x60;json
{
&quot;a&quot;: {
&quot;b&quot;: [&quot;c&quot;, { &quot;d&quot;: 1 }],
&quot;e&quot;: 7
}
}
&#x60;&#x60;&#x60;
This would match any one of the following conditions:
- &#x60;a&#x60;
- &#x60;a.b&#x60;
- &#x60;a.b.c&#x60;
- &#x60;a.b.c&#x3D;true&#x60;
- &#x60;a.b.d&#x60;
- &#x60;a.b.d&#x3D;1&#x60;
- &#x60;a.e&#x60;
- &#x60;a.e&#x3D;7&#x60;
Some more real world usable examples:
- Return all orphaned entities:
&#x60;/entities/by-query?filter&#x3D;metadata.annotations.backstage.io/orphan&#x3D;true&#x60;
- Return all users and groups:
&#x60;/entities/by-query?filter&#x3D;kind&#x3D;user&amp;filter&#x3D;kind&#x3D;group&#x60;
- Return all service components:
&#x60;/entities/by-query?filter&#x3D;kind&#x3D;component,spec.type&#x3D;service&#x60;
- Return all entities with the &#x60;java&#x60; tag:
&#x60;/entities/by-query?filter&#x3D;metadata.tags.java&#x60;
- Return all users who are members of the &#x60;ops&#x60; group (note that the full
[reference](references.md) of the group is used):
&#x60;/entities/by-query?filter&#x3D;kind&#x3D;user,relations.memberof&#x3D;group:default/ops&#x60;
* @param getEntitiesByRefsRequest -
*/
public async getEntitiesByRefs(
// @ts-ignore
request: GetEntitiesByRefs,
@@ -493,10 +730,93 @@ export class DefaultApiClient {
}
/**
* Get all entity facets that match the given filters.
* @param facet -
* @param filter - Filter for just the entities defined by this filter.
*/
* Get all entity facets that match the given filters.
* @param facet -
* @param filter - You can pass in one or more filter sets that get matched against each entity.
Each filter set is a number of conditions that all have to match for the
condition to be true (conditions effectively have an AND between them). At least
one filter set has to be true for the entity to be part of the result set
(filter sets effectively have an OR between them).
Example:
&#x60;&#x60;&#x60;text
/entities/by-query?filter&#x3D;kind&#x3D;user,metadata.namespace&#x3D;default&amp;filter&#x3D;kind&#x3D;group,spec.type
Return entities that match
Filter set 1:
Condition 1: kind &#x3D; user
AND
Condition 2: metadata.namespace &#x3D; default
OR
Filter set 2:
Condition 1: kind &#x3D; group
AND
Condition 2: spec.type exists
&#x60;&#x60;&#x60;
Each condition is either on the form &#x60;&lt;key&gt;&#x60;, or on the form &#x60;&lt;key&gt;&#x3D;&lt;value&gt;&#x60;.
The first form asserts on the existence of a certain key (with any value), and
the second asserts that the key exists and has a certain value. All checks are
always case _insensitive_.
In all cases, the key is a simplified JSON path in a given piece of entity data.
Each part of the path is a key of an object, and the traversal also descends
through arrays. There are two special forms:
- Array items that are simple value types (such as strings) match on a key-value
pair where the key is the item as a string, and the value is the string &#x60;true&#x60;
- Relations can be matched on a &#x60;relations.&lt;type&gt;&#x3D;&lt;targetRef&gt;&#x60; form
Let&#39;s look at a simplified example to illustrate the concept:
&#x60;&#x60;&#x60;json
{
&quot;a&quot;: {
&quot;b&quot;: [&quot;c&quot;, { &quot;d&quot;: 1 }],
&quot;e&quot;: 7
}
}
&#x60;&#x60;&#x60;
This would match any one of the following conditions:
- &#x60;a&#x60;
- &#x60;a.b&#x60;
- &#x60;a.b.c&#x60;
- &#x60;a.b.c&#x3D;true&#x60;
- &#x60;a.b.d&#x60;
- &#x60;a.b.d&#x3D;1&#x60;
- &#x60;a.e&#x60;
- &#x60;a.e&#x3D;7&#x60;
Some more real world usable examples:
- Return all orphaned entities:
&#x60;/entities/by-query?filter&#x3D;metadata.annotations.backstage.io/orphan&#x3D;true&#x60;
- Return all users and groups:
&#x60;/entities/by-query?filter&#x3D;kind&#x3D;user&amp;filter&#x3D;kind&#x3D;group&#x60;
- Return all service components:
&#x60;/entities/by-query?filter&#x3D;kind&#x3D;component,spec.type&#x3D;service&#x60;
- Return all entities with the &#x60;java&#x60; tag:
&#x60;/entities/by-query?filter&#x3D;metadata.tags.java&#x60;
- Return all users who are members of the &#x60;ops&#x60; group (note that the full
[reference](references.md) of the group is used):
&#x60;/entities/by-query?filter&#x3D;kind&#x3D;user,relations.memberof&#x3D;group:default/ops&#x60;
*/
public async getEntityFacets(
// @ts-ignore
request: GetEntityFacets,
@@ -519,6 +839,135 @@ export class DefaultApiClient {
});
}
/**
* Refresh the entity related to entityRef.
* @param refreshEntityRequest -
*/
public async refreshEntity(
// @ts-ignore
request: RefreshEntity,
options?: RequestOptions,
): Promise<TypedResponse<void>> {
const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
const uriTemplate = `/refresh`;
const uri = parser.parse(uriTemplate).expand({});
return await this.fetchApi.fetch(`${baseUrl}${uri}`, {
headers: {
'Content-Type': 'application/json',
...(options?.token && { Authorization: `Bearer ${options?.token}` }),
},
method: 'POST',
body: JSON.stringify(request.body),
});
}
/**
* Validate that a passed in entity has no errors in schema.
* @param validateEntityRequest -
*/
public async validateEntity(
// @ts-ignore
request: ValidateEntity,
options?: RequestOptions,
): Promise<TypedResponse<void>> {
const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
const uriTemplate = `/validate-entity`;
const uri = parser.parse(uriTemplate).expand({});
return await this.fetchApi.fetch(`${baseUrl}${uri}`, {
headers: {
'Content-Type': 'application/json',
...(options?.token && { Authorization: `Bearer ${options?.token}` }),
},
method: 'POST',
body: JSON.stringify(request.body),
});
}
/**
* Validate a given location.
* @param analyzeLocationRequest -
*/
public async analyzeLocation(
// @ts-ignore
request: AnalyzeLocation,
options?: RequestOptions,
): Promise<TypedResponse<AnalyzeLocationResponse>> {
const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
const uriTemplate = `/analyze-location`;
const uri = parser.parse(uriTemplate).expand({});
return await this.fetchApi.fetch(`${baseUrl}${uri}`, {
headers: {
'Content-Type': 'application/json',
...(options?.token && { Authorization: `Bearer ${options?.token}` }),
},
method: 'POST',
body: JSON.stringify(request.body),
});
}
/**
* Create a location for a given target.
* @param createLocationRequest -
* @param dryRun -
*/
public async createLocation(
// @ts-ignore
request: CreateLocation,
options?: RequestOptions,
): Promise<TypedResponse<CreateLocation201Response>> {
const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
const uriTemplate = `/locations{?dryRun}`;
const uri = parser.parse(uriTemplate).expand({
...request.query,
});
return await this.fetchApi.fetch(`${baseUrl}${uri}`, {
headers: {
'Content-Type': 'application/json',
...(options?.token && { Authorization: `Bearer ${options?.token}` }),
},
method: 'POST',
body: JSON.stringify(request.body),
});
}
/**
* Delete a location by id.
* @param id -
*/
public async deleteLocation(
// @ts-ignore
request: DeleteLocation,
options?: RequestOptions,
): Promise<TypedResponse<void>> {
const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
const uriTemplate = `/locations/{id}`;
const uri = parser.parse(uriTemplate).expand({
id: request.path.id,
});
return await this.fetchApi.fetch(`${baseUrl}${uri}`, {
headers: {
'Content-Type': 'application/json',
...(options?.token && { Authorization: `Bearer ${options?.token}` }),
},
method: 'DELETE',
});
}
/**
* Get a location by id.
* @param id -
@@ -597,54 +1046,4 @@ export class DefaultApiClient {
method: 'GET',
});
}
/**
* Refresh the entity related to entityRef.
* @param refreshEntityRequest -
*/
public async refreshEntity(
// @ts-ignore
request: RefreshEntity,
options?: RequestOptions,
): Promise<TypedResponse<void>> {
const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
const uriTemplate = `/refresh`;
const uri = parser.parse(uriTemplate).expand({});
return await this.fetchApi.fetch(`${baseUrl}${uri}`, {
headers: {
'Content-Type': 'application/json',
...(options?.token && { Authorization: `Bearer ${options?.token}` }),
},
method: 'POST',
body: JSON.stringify(request.body),
});
}
/**
* Validate that a passed in entity has no errors in schema.
* @param validateEntityRequest -
*/
public async validateEntity(
// @ts-ignore
request: ValidateEntity,
options?: RequestOptions,
): Promise<TypedResponse<void>> {
const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
const uriTemplate = `/validate-entity`;
const uri = parser.parse(uriTemplate).expand({});
return await this.fetchApi.fetch(`${baseUrl}${uri}`, {
headers: {
'Content-Type': 'application/json',
...(options?.token && { Authorization: `Bearer ${options?.token}` }),
},
method: 'POST',
body: JSON.stringify(request.body),
});
}
}
+187 -5
View File
@@ -2,7 +2,24 @@ openapi: 3.0.3
info:
title: catalog
version: '1'
description: The Backstage backend plugin that provides the Backstage catalog
description: |
The API surface consists of a few distinct groups of functionality. Each has a
dedicated section below.
> **Note:** This page only describes some of the most commonly used parts of the
> API, and is a work in progress.
All of the URL paths in this article are assumed to be on top of some base URL
pointing at your catalog installation. For example, if the path given in a
section below is `/entities`, and the catalog is located at
`http://localhost:7007/api/catalog` during local development, the full URL would
be `http://localhost:7007/api/catalog/entities`. The actual URL may vary from
one organization to the other, especially in production, but is commonly your
`backend.baseUrl` in your app config, plus `/api/catalog` at the end.
Some or all of the endpoints may accept or require an `Authorization` header
with a `Bearer` token, which should then be the Backstage token returned by the
[`identity API`](https://backstage.io/docs/reference/core-plugin-api.identityapiref).
license:
name: Apache-2.0
url: http://www.apache.org/licenses/LICENSE-2.0.html
@@ -44,7 +61,29 @@ components:
cursor:
name: cursor
in: query
description: Cursor to a set page of results.
description: |
You may pass the `cursor` query parameters to perform cursor based pagination
through the set of entities. The value of `cursor` will be returned in the response, under the `pageInfo` property:
```json
"pageInfo": {
"nextCursor": "a-cursor",
"prevCursor": "another-cursor"
}
```
If `nextCursor` exists, it can be used to retrieve the next batch of entities. Following the same approach,
if `prevCursor` exists, it can be used to retrieve the previous batch of entities.
- [`filter`](#filtering), for selecting only a subset of all entities
- [`fields`](#field-selection), for selecting only parts of the full data
structure of each entity
- `limit` for limiting the number of entities returned (20 is the default)
- [`orderField`](#ordering), for deciding the order of the entities
- `fullTextFilter`
**NOTE**: [`filter`, `orderField`, `fullTextFilter`] and `cursor` are mutually exclusive. This means that,
it isn't possible to change any of [`filter`, `orderField`, `fullTextFilter`] when passing `cursor` as query parameters,
as changing any of these properties will affect pagination. If any of `filter`, `orderField`, `fullTextFilter` is specified together with `cursor`, only the latter is taken into consideration.
required: false
allowReserved: true
schema:
@@ -62,7 +101,25 @@ components:
fields:
name: fields
in: query
description: Restrict to just these fields in the response.
description: |
By default the full entities are returned, but you can pass in a `fields` query
parameter which selects what parts of the entity data to retain. This makes the
response smaller and faster to transfer, and may allow the catalog to perform
more efficient queries.
The query parameter value is a comma separated list of simplified JSON paths
like above. Each path corresponds to the key of either a value, or of a subtree
root that you want to keep in the output. The rest is pruned away. For example,
specifying `?fields=metadata.name,metadata.annotations,spec` retains only the
`name` and `annotations` fields of the `metadata` of each entity (it'll be an
object with at most two keys), keeps the entire `spec` unchanged, and cuts out
all other roots such as `relations`.
Some more real world usable examples:
- Return only enough data to form the full ref of each entity:
`/entities/by-query?fields=kind,metadata.namespace,metadata.name`
required: false
allowReserved: true
explode: false
@@ -83,7 +140,91 @@ components:
filter:
name: filter
in: query
description: Filter for just the entities defined by this filter.
description: |
You can pass in one or more filter sets that get matched against each entity.
Each filter set is a number of conditions that all have to match for the
condition to be true (conditions effectively have an AND between them). At least
one filter set has to be true for the entity to be part of the result set
(filter sets effectively have an OR between them).
Example:
```text
/entities/by-query?filter=kind=user,metadata.namespace=default&filter=kind=group,spec.type
Return entities that match
Filter set 1:
Condition 1: kind = user
AND
Condition 2: metadata.namespace = default
OR
Filter set 2:
Condition 1: kind = group
AND
Condition 2: spec.type exists
```
Each condition is either on the form `<key>`, or on the form `<key>=<value>`.
The first form asserts on the existence of a certain key (with any value), and
the second asserts that the key exists and has a certain value. All checks are
always case _insensitive_.
In all cases, the key is a simplified JSON path in a given piece of entity data.
Each part of the path is a key of an object, and the traversal also descends
through arrays. There are two special forms:
- Array items that are simple value types (such as strings) match on a key-value
pair where the key is the item as a string, and the value is the string `true`
- Relations can be matched on a `relations.<type>=<targetRef>` form
Let's look at a simplified example to illustrate the concept:
```json
{
"a": {
"b": ["c", { "d": 1 }],
"e": 7
}
}
```
This would match any one of the following conditions:
- `a`
- `a.b`
- `a.b.c`
- `a.b.c=true`
- `a.b.d`
- `a.b.d=1`
- `a.e`
- `a.e=7`
Some more real world usable examples:
- Return all orphaned entities:
`/entities/by-query?filter=metadata.annotations.backstage.io/orphan=true`
- Return all users and groups:
`/entities/by-query?filter=kind=user&filter=kind=group`
- Return all service components:
`/entities/by-query?filter=kind=component,spec.type=service`
- Return all entities with the `java` tag:
`/entities/by-query?filter=metadata.tags.java`
- Return all users who are members of the `ops` group (note that the full
[reference](references.md) of the group is used):
`/entities/by-query?filter=kind=user,relations.memberof=group:default/ops`
required: false
allowReserved: true
schema:
@@ -118,7 +259,16 @@ components:
orderField:
name: orderField
in: query
description: The fields to sort returned results by.
description: |
By default the entities are returned ordered by their internal uid. You can
customize the `orderField` query parameters to affect that ordering.
For example, to return entities by their name:
`/entities/by-query?orderField=metadata.name,asc`
Each parameter can be followed by `asc` for ascending lexicographical order or
`desc` for descending (reverse) lexicographical order.
required: false
allowReserved: true
schema:
@@ -674,6 +824,8 @@ paths:
/refresh:
post:
operationId: RefreshEntity
tags:
- Entity
description: Refresh the entity related to entityRef.
responses:
'200':
@@ -705,6 +857,8 @@ paths:
/entities:
get:
operationId: GetEntities
tags:
- Entity
description: Get all entities matching a given filter.
responses:
'200':
@@ -739,6 +893,8 @@ paths:
/entities/by-uid/{uid}:
get:
operationId: GetEntityByUid
tags:
- Entity
description: Get a single entity by the UID.
responses:
'200':
@@ -758,6 +914,8 @@ paths:
- $ref: '#/components/parameters/uid'
delete:
operationId: DeleteEntityByUid
tags:
- Entity
description: Delete a single entity by UID.
responses:
'204':
@@ -774,6 +932,8 @@ paths:
/entities/by-name/{kind}/{namespace}/{name}:
get:
operationId: GetEntityByName
tags:
- Entity
description: Get an entity by an entity ref.
responses:
'200':
@@ -796,6 +956,8 @@ paths:
/entities/by-name/{kind}/{namespace}/{name}/ancestry:
get:
operationId: GetEntityAncestryByName
tags:
- Entity
description: Get an entity's ancestry by entity ref.
responses:
'200':
@@ -818,6 +980,8 @@ paths:
/entities/by-refs:
post:
operationId: GetEntitiesByRefs
tags:
- Entity
description: Get a batch set of entities given an array of entityRefs.
responses:
'200':
@@ -867,6 +1031,8 @@ paths:
/entities/by-query:
get:
operationId: GetEntitiesByQuery
tags:
- Entity
description: Search for entities by a given query.
responses:
'200':
@@ -910,6 +1076,8 @@ paths:
/entity-facets:
get:
operationId: GetEntityFacets
tags:
- Entity
description: Get all entity facets that match the given filters.
responses:
'200':
@@ -945,6 +1113,8 @@ paths:
/locations:
post:
operationId: CreateLocation
tags:
- Locations
description: Create a location for a given target.
responses:
'201':
@@ -995,6 +1165,8 @@ paths:
- type
get:
operationId: GetLocations
tags:
- Locations
description: Get all locations
responses:
'200':
@@ -1019,6 +1191,8 @@ paths:
/locations/{id}:
get:
operationId: GetLocation
tags:
- Locations
description: Get a location by id.
responses:
'200':
@@ -1041,6 +1215,8 @@ paths:
type: string
delete:
operationId: DeleteLocation
tags:
- Locations
description: Delete a location by id.
responses:
'204':
@@ -1062,6 +1238,8 @@ paths:
/locations/by-entity/{kind}/{namespace}/{name}:
get:
operationId: getLocationByEntity
tags:
- Locations
description: Get a location for entity.
responses:
'200':
@@ -1097,6 +1275,8 @@ paths:
/analyze-location:
post:
operationId: AnalyzeLocation
tags:
- Locations
description: Validate a given location.
responses:
'200':
@@ -1129,6 +1309,8 @@ paths:
/validate-entity:
post:
operationId: ValidateEntity
tags:
- Entity
description: Validate that a passed in entity has no errors in schema.
responses:
'200':
@@ -19,39 +19,16 @@
// ******************************************************************
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
// ******************************************************************
import { AnalyzeLocationRequest } from '../models/AnalyzeLocationRequest.model';
import { AnalyzeLocationResponse } from '../models/AnalyzeLocationResponse.model';
import { CreateLocation201Response } from '../models/CreateLocation201Response.model';
import { CreateLocationRequest } from '../models/CreateLocationRequest.model';
import { EntitiesBatchResponse } from '../models/EntitiesBatchResponse.model';
import { EntitiesQueryResponse } from '../models/EntitiesQueryResponse.model';
import { Entity } from '../models/Entity.model';
import { EntityAncestryResponse } from '../models/EntityAncestryResponse.model';
import { EntityFacetsResponse } from '../models/EntityFacetsResponse.model';
import { GetEntitiesByRefsRequest } from '../models/GetEntitiesByRefsRequest.model';
import { GetLocations200ResponseInner } from '../models/GetLocations200ResponseInner.model';
import { Location } from '../models/Location.model';
import { RefreshEntityRequest } from '../models/RefreshEntityRequest.model';
import { ValidateEntity400Response } from '../models/ValidateEntity400Response.model';
import { ValidateEntityRequest } from '../models/ValidateEntityRequest.model';
/**
* @public
*/
export type AnalyzeLocation = {
body: AnalyzeLocationRequest;
response: AnalyzeLocationResponse | Error | Error;
};
/**
* @public
*/
export type CreateLocation = {
body: CreateLocationRequest;
query: {
dryRun?: string;
};
response: CreateLocation201Response | Error | Error;
};
/**
* @public
*/
@@ -61,15 +38,6 @@ export type DeleteEntityByUid = {
};
response: void | Error | Error;
};
/**
* @public
*/
export type DeleteLocation = {
path: {
id: string;
};
response: void | Error | Error;
};
/**
* @public
*/
@@ -151,6 +119,53 @@ export type GetEntityFacets = {
};
response: EntityFacetsResponse | Error | Error;
};
/**
* @public
*/
export type RefreshEntity = {
body: RefreshEntityRequest;
response: void | Error | Error;
};
/**
* @public
*/
export type ValidateEntity = {
body: ValidateEntityRequest;
response: void | ValidateEntity400Response;
};
import { AnalyzeLocationRequest } from '../models/AnalyzeLocationRequest.model';
import { AnalyzeLocationResponse } from '../models/AnalyzeLocationResponse.model';
import { CreateLocation201Response } from '../models/CreateLocation201Response.model';
import { CreateLocationRequest } from '../models/CreateLocationRequest.model';
import { GetLocations200ResponseInner } from '../models/GetLocations200ResponseInner.model';
import { Location } from '../models/Location.model';
/**
* @public
*/
export type AnalyzeLocation = {
body: AnalyzeLocationRequest;
response: AnalyzeLocationResponse | Error | Error;
};
/**
* @public
*/
export type CreateLocation = {
body: CreateLocationRequest;
query: {
dryRun?: string;
};
response: CreateLocation201Response | Error | Error;
};
/**
* @public
*/
export type DeleteLocation = {
path: {
id: string;
};
response: void | Error | Error;
};
/**
* @public
*/
@@ -177,30 +192,10 @@ export type GetLocationByEntity = {
export type GetLocations = {
response: Array<GetLocations200ResponseInner> | Error;
};
/**
* @public
*/
export type RefreshEntity = {
body: RefreshEntityRequest;
response: void | Error | Error;
};
/**
* @public
*/
export type ValidateEntity = {
body: ValidateEntityRequest;
response: void | ValidateEntity400Response;
};
export type EndpointMap = {
'#post|/analyze-location': AnalyzeLocation;
'#post|/locations': CreateLocation;
'#_delete|/entities/by-uid/{uid}': DeleteEntityByUid;
'#_delete|/locations/{id}': DeleteLocation;
'#get|/entities': GetEntities;
'#get|/entities/by-query': GetEntitiesByQuery;
@@ -215,13 +210,19 @@ export type EndpointMap = {
'#get|/entity-facets': GetEntityFacets;
'#post|/refresh': RefreshEntity;
'#post|/validate-entity': ValidateEntity;
'#post|/analyze-location': AnalyzeLocation;
'#post|/locations': CreateLocation;
'#_delete|/locations/{id}': DeleteLocation;
'#get|/locations/{id}': GetLocation;
'#get|/locations/by-entity/{kind}/{namespace}/{name}': GetLocationByEntity;
'#get|/locations': GetLocations;
'#post|/refresh': RefreshEntity;
'#post|/validate-entity': ValidateEntity;
};
@@ -26,7 +26,7 @@ export const spec = {
title: 'catalog',
version: '1',
description:
'The Backstage backend plugin that provides the Backstage catalog',
'The API surface consists of a few distinct groups of functionality. Each has a\ndedicated section below.\n\n> **Note:** This page only describes some of the most commonly used parts of the\n> API, and is a work in progress.\n\nAll of the URL paths in this article are assumed to be on top of some base URL\npointing at your catalog installation. For example, if the path given in a\nsection below is `/entities`, and the catalog is located at\n`http://localhost:7007/api/catalog` during local development, the full URL would\nbe `http://localhost:7007/api/catalog/entities`. The actual URL may vary from\none organization to the other, especially in production, but is commonly your\n`backend.baseUrl` in your app config, plus `/api/catalog` at the end.\n\nSome or all of the endpoints may accept or require an `Authorization` header\nwith a `Bearer` token, which should then be the Backstage token returned by the\n[`identity API`](https://backstage.io/docs/reference/core-plugin-api.identityapiref).\n',
license: {
name: 'Apache-2.0',
url: 'http://www.apache.org/licenses/LICENSE-2.0.html',
@@ -81,7 +81,8 @@ export const spec = {
cursor: {
name: 'cursor',
in: 'query',
description: 'Cursor to a set page of results.',
description:
'You may pass the `cursor` query parameters to perform cursor based pagination\nthrough the set of entities. The value of `cursor` will be returned in the response, under the `pageInfo` property:\n\n```json\n "pageInfo": {\n "nextCursor": "a-cursor",\n "prevCursor": "another-cursor"\n }\n```\n\nIf `nextCursor` exists, it can be used to retrieve the next batch of entities. Following the same approach,\nif `prevCursor` exists, it can be used to retrieve the previous batch of entities.\n\n- [`filter`](#filtering), for selecting only a subset of all entities\n- [`fields`](#field-selection), for selecting only parts of the full data\n structure of each entity\n- `limit` for limiting the number of entities returned (20 is the default)\n- [`orderField`](#ordering), for deciding the order of the entities\n- `fullTextFilter`\n **NOTE**: [`filter`, `orderField`, `fullTextFilter`] and `cursor` are mutually exclusive. This means that,\n it isn\'t possible to change any of [`filter`, `orderField`, `fullTextFilter`] when passing `cursor` as query parameters,\n as changing any of these properties will affect pagination. If any of `filter`, `orderField`, `fullTextFilter` is specified together with `cursor`, only the latter is taken into consideration.\n',
required: false,
allowReserved: true,
schema: {
@@ -103,7 +104,8 @@ export const spec = {
fields: {
name: 'fields',
in: 'query',
description: 'Restrict to just these fields in the response.',
description:
"By default the full entities are returned, but you can pass in a `fields` query\nparameter which selects what parts of the entity data to retain. This makes the\nresponse smaller and faster to transfer, and may allow the catalog to perform\nmore efficient queries.\n\nThe query parameter value is a comma separated list of simplified JSON paths\nlike above. Each path corresponds to the key of either a value, or of a subtree\nroot that you want to keep in the output. The rest is pruned away. For example,\nspecifying `?fields=metadata.name,metadata.annotations,spec` retains only the\n`name` and `annotations` fields of the `metadata` of each entity (it'll be an\nobject with at most two keys), keeps the entire `spec` unchanged, and cuts out\nall other roots such as `relations`.\n\nSome more real world usable examples:\n\n- Return only enough data to form the full ref of each entity:\n\n `/entities/by-query?fields=kind,metadata.namespace,metadata.name`\n",
required: false,
allowReserved: true,
explode: false,
@@ -125,7 +127,8 @@ export const spec = {
filter: {
name: 'filter',
in: 'query',
description: 'Filter for just the entities defined by this filter.',
description:
'You can pass in one or more filter sets that get matched against each entity.\nEach filter set is a number of conditions that all have to match for the\ncondition to be true (conditions effectively have an AND between them). At least\none filter set has to be true for the entity to be part of the result set\n(filter sets effectively have an OR between them).\n\nExample:\n\n```text\n/entities/by-query?filter=kind=user,metadata.namespace=default&filter=kind=group,spec.type\n\n Return entities that match\n\n Filter set 1:\n Condition 1: kind = user\n AND\n Condition 2: metadata.namespace = default\n\n OR\n\n Filter set 2:\n Condition 1: kind = group\n AND\n Condition 2: spec.type exists\n```\n\nEach condition is either on the form `<key>`, or on the form `<key>=<value>`.\nThe first form asserts on the existence of a certain key (with any value), and\nthe second asserts that the key exists and has a certain value. All checks are\nalways case _insensitive_.\n\nIn all cases, the key is a simplified JSON path in a given piece of entity data.\nEach part of the path is a key of an object, and the traversal also descends\nthrough arrays. There are two special forms:\n\n- Array items that are simple value types (such as strings) match on a key-value\n pair where the key is the item as a string, and the value is the string `true`\n- Relations can be matched on a `relations.<type>=<targetRef>` form\n\nLet\'s look at a simplified example to illustrate the concept:\n\n```json\n{\n "a": {\n "b": ["c", { "d": 1 }],\n "e": 7\n }\n}\n```\n\nThis would match any one of the following conditions:\n\n- `a`\n- `a.b`\n- `a.b.c`\n- `a.b.c=true`\n- `a.b.d`\n- `a.b.d=1`\n- `a.e`\n- `a.e=7`\n\nSome more real world usable examples:\n\n- Return all orphaned entities:\n\n `/entities/by-query?filter=metadata.annotations.backstage.io/orphan=true`\n\n- Return all users and groups:\n\n `/entities/by-query?filter=kind=user&filter=kind=group`\n\n- Return all service components:\n\n `/entities/by-query?filter=kind=component,spec.type=service`\n\n- Return all entities with the `java` tag:\n\n `/entities/by-query?filter=metadata.tags.java`\n\n- Return all users who are members of the `ops` group (note that the full\n [reference](references.md) of the group is used):\n\n `/entities/by-query?filter=kind=user,relations.memberof=group:default/ops`\n',
required: false,
allowReserved: true,
schema: {
@@ -170,7 +173,8 @@ export const spec = {
orderField: {
name: 'orderField',
in: 'query',
description: 'The fields to sort returned results by.',
description:
'By default the entities are returned ordered by their internal uid. You can\ncustomize the `orderField` query parameters to affect that ordering.\n\nFor example, to return entities by their name:\n\n`/entities/by-query?orderField=metadata.name,asc`\n\nEach parameter can be followed by `asc` for ascending lexicographical order or\n`desc` for descending (reverse) lexicographical order.\n',
required: false,
allowReserved: true,
schema: {
@@ -788,6 +792,7 @@ export const spec = {
'/refresh': {
post: {
operationId: 'RefreshEntity',
tags: ['Entity'],
description: 'Refresh the entity related to entityRef.',
responses: {
'200': {
@@ -836,6 +841,7 @@ export const spec = {
'/entities': {
get: {
operationId: 'GetEntities',
tags: ['Entity'],
description: 'Get all entities matching a given filter.',
responses: {
'200': {
@@ -898,6 +904,7 @@ export const spec = {
'/entities/by-uid/{uid}': {
get: {
operationId: 'GetEntityByUid',
tags: ['Entity'],
description: 'Get a single entity by the UID.',
responses: {
'200': {
@@ -931,6 +938,7 @@ export const spec = {
},
delete: {
operationId: 'DeleteEntityByUid',
tags: ['Entity'],
description: 'Delete a single entity by UID.',
responses: {
'204': {
@@ -959,6 +967,7 @@ export const spec = {
'/entities/by-name/{kind}/{namespace}/{name}': {
get: {
operationId: 'GetEntityByName',
tags: ['Entity'],
description: 'Get an entity by an entity ref.',
responses: {
'200': {
@@ -1000,6 +1009,7 @@ export const spec = {
'/entities/by-name/{kind}/{namespace}/{name}/ancestry': {
get: {
operationId: 'GetEntityAncestryByName',
tags: ['Entity'],
description: "Get an entity's ancestry by entity ref.",
responses: {
'200': {
@@ -1041,6 +1051,7 @@ export const spec = {
'/entities/by-refs': {
post: {
operationId: 'GetEntitiesByRefs',
tags: ['Entity'],
description:
'Get a batch set of entities given an array of entityRefs.',
responses: {
@@ -1118,6 +1129,7 @@ export const spec = {
'/entities/by-query': {
get: {
operationId: 'GetEntitiesByQuery',
tags: ['Entity'],
description: 'Search for entities by a given query.',
responses: {
'200': {
@@ -1194,6 +1206,7 @@ export const spec = {
'/entity-facets': {
get: {
operationId: 'GetEntityFacets',
tags: ['Entity'],
description: 'Get all entity facets that match the given filters.',
responses: {
'200': {
@@ -1249,6 +1262,7 @@ export const spec = {
'/locations': {
post: {
operationId: 'CreateLocation',
tags: ['Locations'],
description: 'Create a location for a given target.',
responses: {
'201': {
@@ -1322,6 +1336,7 @@ export const spec = {
},
get: {
operationId: 'GetLocations',
tags: ['Locations'],
description: 'Get all locations',
responses: {
'200': {
@@ -1359,6 +1374,7 @@ export const spec = {
'/locations/{id}': {
get: {
operationId: 'GetLocation',
tags: ['Locations'],
description: 'Get a location by id.',
responses: {
'200': {
@@ -1395,6 +1411,7 @@ export const spec = {
},
delete: {
operationId: 'DeleteLocation',
tags: ['Locations'],
description: 'Delete a location by id.',
responses: {
'204': {
@@ -1429,6 +1446,7 @@ export const spec = {
'/locations/by-entity/{kind}/{namespace}/{name}': {
get: {
operationId: 'getLocationByEntity',
tags: ['Locations'],
description: 'Get a location for entity.',
responses: {
'200': {
@@ -1485,6 +1503,7 @@ export const spec = {
'/analyze-location': {
post: {
operationId: 'AnalyzeLocation',
tags: ['Locations'],
description: 'Validate a given location.',
responses: {
'200': {
@@ -1535,6 +1554,7 @@ export const spec = {
'/validate-entity': {
post: {
operationId: 'ValidateEntity',
tags: ['Entity'],
description:
'Validate that a passed in entity has no errors in schema.',
responses: {
@@ -72,6 +72,8 @@ paths:
get:
operationId: Query
description: Query documents with a given filter.
tags:
- Search
responses:
'200':
description: Ok
@@ -1,5 +1,5 @@
/*
* Copyright 2024 The Backstage Authors
* Copyright 2025 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -114,6 +114,7 @@ export const spec = {
get: {
operationId: 'Query',
description: 'Query documents with a given filter.',
tags: ['Search'],
responses: {
'200': {
description: 'Ok',