From 497d47aafa53a6c4de2aee24d9281785471329a6 Mon Sep 17 00:00:00 2001 From: Matt Benson Date: Fri, 7 Feb 2025 14:16:51 -0600 Subject: [PATCH] document built-in template filters Signed-off-by: Matt Benson --- .changeset/odd-bobcats-hang.md | 5 ++ .../src/lib/templating/filters.ts | 40 ------------ .../src/lib/templating/filters/index.ts | 29 +++++++++ .../filters/parseEntityRef/examples.ts | 48 +++++++++++++++ .../filters/parseEntityRef/index.ts | 61 +++++++++++++++++++ .../filters/parseRepoUrl/examples.ts | 31 ++++++++++ .../templating/filters/parseRepoUrl/index.ts | 50 +++++++++++++++ .../lib/templating/filters/pick/examples.ts | 31 ++++++++++ .../src/lib/templating/filters/pick/index.ts | 31 ++++++++++ .../filters/projectSlug/examples.ts | 32 ++++++++++ .../templating/filters/projectSlug/index.ts | 37 +++++++++++ .../actions/builtin/fetch/template.ts | 2 +- .../actions/builtin/fetch/templateFile.ts | 2 +- .../tasks/NunjucksWorkflowRunner.ts | 2 +- .../src/util/templating.test.ts | 4 +- 15 files changed, 360 insertions(+), 45 deletions(-) create mode 100644 .changeset/odd-bobcats-hang.md delete mode 100644 plugins/scaffolder-backend/src/lib/templating/filters.ts create mode 100644 plugins/scaffolder-backend/src/lib/templating/filters/index.ts create mode 100644 plugins/scaffolder-backend/src/lib/templating/filters/parseEntityRef/examples.ts create mode 100644 plugins/scaffolder-backend/src/lib/templating/filters/parseEntityRef/index.ts create mode 100644 plugins/scaffolder-backend/src/lib/templating/filters/parseRepoUrl/examples.ts create mode 100644 plugins/scaffolder-backend/src/lib/templating/filters/parseRepoUrl/index.ts create mode 100644 plugins/scaffolder-backend/src/lib/templating/filters/pick/examples.ts create mode 100644 plugins/scaffolder-backend/src/lib/templating/filters/pick/index.ts create mode 100644 plugins/scaffolder-backend/src/lib/templating/filters/projectSlug/examples.ts create mode 100644 plugins/scaffolder-backend/src/lib/templating/filters/projectSlug/index.ts diff --git a/.changeset/odd-bobcats-hang.md b/.changeset/odd-bobcats-hang.md new file mode 100644 index 0000000000..a7c0d59754 --- /dev/null +++ b/.changeset/odd-bobcats-hang.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +document built-in template filters diff --git a/plugins/scaffolder-backend/src/lib/templating/filters.ts b/plugins/scaffolder-backend/src/lib/templating/filters.ts deleted file mode 100644 index bc3f95b18c..0000000000 --- a/plugins/scaffolder-backend/src/lib/templating/filters.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2023 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. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { parseEntityRef } from '@backstage/catalog-model'; -import { ScmIntegrations } from '@backstage/integration'; -import type { JsonObject, JsonValue } from '@backstage/types'; -import { - parseRepoUrl, - TemplateFilter, -} from '@backstage/plugin-scaffolder-node'; -import get from 'lodash/get'; - -export default ({ - integrations, -}: { - integrations: ScmIntegrations; -}): Record => { - return { - parseRepoUrl: url => parseRepoUrl(url as string, integrations), - parseEntityRef: (ref: JsonValue, context?: JsonValue) => - parseEntityRef(ref as string, context as JsonObject), - pick: (obj: JsonValue, key: JsonValue) => get(obj, key as string), - projectSlug: repoUrl => { - const { owner, repo } = parseRepoUrl(repoUrl as string, integrations); - return `${owner}/${repo}`; - }, - }; -}; diff --git a/plugins/scaffolder-backend/src/lib/templating/filters/index.ts b/plugins/scaffolder-backend/src/lib/templating/filters/index.ts new file mode 100644 index 0000000000..5eb4de5ac3 --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/templating/filters/index.ts @@ -0,0 +1,29 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ScmIntegrations } from '@backstage/integration'; +import { createParseRepoUrl } from './parseRepoUrl'; +import { parseEntityRef } from './parseEntityRef'; +import { pick } from './pick'; +import { createProjectSlug } from './projectSlug'; + +export const createDefaultFilters = (options: { + integrations: ScmIntegrations; +}) => [ + createParseRepoUrl(options.integrations), + parseEntityRef, + pick, + createProjectSlug(options.integrations), +]; diff --git a/plugins/scaffolder-backend/src/lib/templating/filters/parseEntityRef/examples.ts b/plugins/scaffolder-backend/src/lib/templating/filters/parseEntityRef/examples.ts new file mode 100644 index 0000000000..fd6219c998 --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/templating/filters/parseEntityRef/examples.ts @@ -0,0 +1,48 @@ +import { TemplateFilterExample } from '@backstage/plugin-scaffolder-node/alpha'; + +/* + * 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export const examples: TemplateFilterExample[] = [ + { + description: 'Without context', + example: `\ +- id: log + name: Parse Entity Reference + action: debug:log + input: + message: \${{ parameters.owner | parseEntityRef }} +`, + notes: `\ +- **Input**: \`group:techdocs\` +- **Output**: \`{"kind": "group", "namespace": "default", "name": "techdocs"}\` +`, + }, + { + description: 'With context', + example: `\ +- id: log + name: Parse Entity Reference + action: debug:log + input: + message: \${{ parameters.owner | parseEntityRef({ defaultKind:"group", defaultNamespace:"another-namespace" }) }} +`, + notes: `\ +- **Input**: \`techdocs\` +- **Arguments:**: \`[{ "defaultKind": "group", "defaultNamespace": "another-namespace" }]\` +- **Output**: \`{"kind": "group", "namespace": "another-namespace", "name": "techdocs"}\` +`, + }, +]; diff --git a/plugins/scaffolder-backend/src/lib/templating/filters/parseEntityRef/index.ts b/plugins/scaffolder-backend/src/lib/templating/filters/parseEntityRef/index.ts new file mode 100644 index 0000000000..221be44055 --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/templating/filters/parseEntityRef/index.ts @@ -0,0 +1,61 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { parseEntityRef as filter } from '@backstage/catalog-model'; +import { createTemplateFilter } from '@backstage/plugin-scaffolder-node/alpha'; +import { examples } from './examples'; + +export const parseEntityRef = createTemplateFilter({ + id: 'parseEntityRef', + description: + 'Extracts the parts of an entity reference, such as the kind, namespace, and name.', + schema: z => + z.function( + z.tuple([ + z.union([ + z.string().describe('compact entity reference'), + z + .object({ + kind: z.string().optional(), + namespace: z.string().optional(), + name: z.string(), + }) + .describe('`CompoundEntityRef`'), + ]), + z + .object({ + defaultKind: z + .string() + .describe('The default kind, if none is given in the reference'), + defaultNamespace: z + .string() + .describe( + 'The default namespace, if none is given in the reference', + ), + }) + .partial() + .optional(), + ]), + z + .object({ + kind: z.string(), + namespace: z.string(), + name: z.string(), + }) + .describe('`CompoundEntityRef`'), + ), + examples, + filter, +}); diff --git a/plugins/scaffolder-backend/src/lib/templating/filters/parseRepoUrl/examples.ts b/plugins/scaffolder-backend/src/lib/templating/filters/parseRepoUrl/examples.ts new file mode 100644 index 0000000000..4348305737 --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/templating/filters/parseRepoUrl/examples.ts @@ -0,0 +1,31 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { TemplateFilterExample } from '@backstage/plugin-scaffolder-node/alpha'; + +export const examples: TemplateFilterExample[] = [ + { + example: `\ +- id: log + name: Parse Repo URL + action: debug:log + input: + message: \${{ parameters.repoUrl | parseRepoUrl }}`, + notes: ` - **Input**: \`github.com?repo=backstage&owner=backstage\` + - **Output**: \`{"host":"github.com","owner":"backstage","repo":"backstage"}\` +`, + }, +]; diff --git a/plugins/scaffolder-backend/src/lib/templating/filters/parseRepoUrl/index.ts b/plugins/scaffolder-backend/src/lib/templating/filters/parseRepoUrl/index.ts new file mode 100644 index 0000000000..a726c140cf --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/templating/filters/parseRepoUrl/index.ts @@ -0,0 +1,50 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ScmIntegrations } from '@backstage/integration'; +import { parseRepoUrl } from '@backstage/plugin-scaffolder-node'; +import { createTemplateFilter } from '@backstage/plugin-scaffolder-node/alpha'; +import { examples } from './examples'; + +export const createParseRepoUrl = (integrations: ScmIntegrations) => + createTemplateFilter({ + id: 'parseRepoUrl', + description: + 'Parses a repository URL into its constituent parts: owner, repository name, etc.', + schema: z => + z.function( + z.tuple([ + z.string().describe('repo URL as collected from repository picker'), + ]), + z + .object({ + repo: z.string(), + host: z.string(), + }) + .merge( + z + .object({ + owner: z.string(), + organization: z.string(), + workspace: z.string(), + project: z.string(), + }) + .partial(), + ) + .describe('`RepoSpec`'), + ), + examples, + filter: (url: string) => parseRepoUrl(url, integrations), + }); diff --git a/plugins/scaffolder-backend/src/lib/templating/filters/pick/examples.ts b/plugins/scaffolder-backend/src/lib/templating/filters/pick/examples.ts new file mode 100644 index 0000000000..28593847ef --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/templating/filters/pick/examples.ts @@ -0,0 +1,31 @@ +import { TemplateFilterExample } from '@backstage/plugin-scaffolder-node/alpha'; + +/* + * 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export const examples: TemplateFilterExample[] = [ + { + example: `\ +- id: log + name: Pick + action: debug:log + input: + message: \${{ parameters.owner | parseEntityRef | pick('name') }}`, + notes: `\ +- **Input**: \`{ kind: 'Group', namespace: 'default', name: 'techdocs'\` } +- **Output**: \`techdocs\` +`, + }, +]; diff --git a/plugins/scaffolder-backend/src/lib/templating/filters/pick/index.ts b/plugins/scaffolder-backend/src/lib/templating/filters/pick/index.ts new file mode 100644 index 0000000000..5d8a0d664a --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/templating/filters/pick/index.ts @@ -0,0 +1,31 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { createTemplateFilter } from '@backstage/plugin-scaffolder-node/alpha'; +import { get as filter } from 'lodash'; +import { examples } from './examples'; + +export const pick = createTemplateFilter({ + id: 'pick', + description: + 'Selects a specific property (e.g. kind, namespace, name) from an object.', + schema: z => + z.function( + z.tuple([z.any(), z.string().describe('Property')]), + z.any().describe('Selected property'), + ), + examples, + filter, +}); diff --git a/plugins/scaffolder-backend/src/lib/templating/filters/projectSlug/examples.ts b/plugins/scaffolder-backend/src/lib/templating/filters/projectSlug/examples.ts new file mode 100644 index 0000000000..dde36ea888 --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/templating/filters/projectSlug/examples.ts @@ -0,0 +1,32 @@ +import { TemplateFilterExample } from '@backstage/plugin-scaffolder-node/alpha'; + +/* + * 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export const examples: TemplateFilterExample[] = [ + { + example: `\ +- id: log + name: Project Slug + action: debug:log + input: + message: \${{ parameters.repoUrl | projectSlug }} +`, + notes: `\ +- **Input**: \`github.com?repo=backstage&owner=backstage\` +- **Output**: backstage/backstage +`, + }, +]; diff --git a/plugins/scaffolder-backend/src/lib/templating/filters/projectSlug/index.ts b/plugins/scaffolder-backend/src/lib/templating/filters/projectSlug/index.ts new file mode 100644 index 0000000000..a833121423 --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/templating/filters/projectSlug/index.ts @@ -0,0 +1,37 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ScmIntegrations } from '@backstage/integration'; +import { parseRepoUrl } from '@backstage/plugin-scaffolder-node'; +import { createTemplateFilter } from '@backstage/plugin-scaffolder-node/alpha'; +import { examples } from './examples'; + +export const createProjectSlug = (integrations: ScmIntegrations) => + createTemplateFilter({ + id: 'projectSlug', + description: 'Generates a project slug from a repository URL.', + schema: z => + z.function( + z.tuple([ + z.string().describe('repo URL as collected from repository picker'), + ]), + z.string(), + ), + examples, + filter: (repoUrl: string) => { + const { owner, repo } = parseRepoUrl(repoUrl, integrations); + return `${owner}/${repo}`; + }, + }); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts index 543c25ab3e..2e036abc19 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts @@ -29,7 +29,7 @@ import globby from 'globby'; import fs from 'fs-extra'; import { isBinaryFile } from 'isbinaryfile'; import { SecureTemplater } from '../../../../lib/templating/SecureTemplater'; -import createDefaultFilters from '../../../../lib/templating/filters'; +import { createDefaultFilters } from '../../../../lib/templating/filters'; import { examples } from './template.examples'; import { convertFiltersToRecord } from '../../../../util/templating'; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateFile.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateFile.ts index f3b98d148a..9353f5e8d2 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateFile.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateFile.ts @@ -25,7 +25,7 @@ import { TemplateGlobal, } from '@backstage/plugin-scaffolder-node'; import { SecureTemplater } from '../../../../lib/templating/SecureTemplater'; -import createDefaultFilters from '../../../../lib/templating/filters'; +import { createDefaultFilters } from '../../../../lib/templating/filters'; import path from 'path'; import fs from 'fs-extra'; import { convertFiltersToRecord } from '../../../../util/templating'; diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index b771c5ded1..e918511354 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -55,7 +55,7 @@ import { TemplateFilter, TemplateGlobal, } from '@backstage/plugin-scaffolder-node'; -import createDefaultFilters from '../../lib/templating/filters'; +import { createDefaultFilters } from '../../lib/templating/filters'; import { scaffolderActionRules } from '../../service/rules'; import { createCounterMetric, createHistogramMetric } from '../../util/metrics'; import { BackstageLoggerTransport, WinstonLogger } from './logger'; diff --git a/plugins/scaffolder-backend/src/util/templating.test.ts b/plugins/scaffolder-backend/src/util/templating.test.ts index 5ac6db601b..51493f08a1 100644 --- a/plugins/scaffolder-backend/src/util/templating.test.ts +++ b/plugins/scaffolder-backend/src/util/templating.test.ts @@ -28,14 +28,14 @@ import { extractGlobalValueMetadata, } from './templating'; import { JsonValue } from '@backstage/types'; -import builtInFilters from '../lib/templating/filters'; +import { createDefaultFilters } from '../lib/templating/filters'; import { ScmIntegrations } from '@backstage/integration'; import { ConfigReader } from '@backstage/config'; describe('templating utilities', () => { describe('built-in filters', () => { const integrations = ScmIntegrations.fromConfig(new ConfigReader({})); - const filters = builtInFilters({ integrations }); + const filters = createDefaultFilters({ integrations }); it('generates equivalent filter metadata', () => { const metadata = extractFilterMetadata(filters); expect(metadata).toMatchObject(extractFilterMetadata(filters));