Merge pull request #25975 from backstage/blam/secret-collection

scaffolder: Implement hooks for collecting secrets
This commit is contained in:
Ben Lambert
2024-11-26 10:29:01 +01:00
committed by GitHub
45 changed files with 971 additions and 168 deletions
+57 -2
View File
@@ -5,6 +5,7 @@
```ts
/// <reference types="react" />
import { AnyApiRef } from '@backstage/core-plugin-api';
import { ApiHolder } from '@backstage/core-plugin-api';
import { ComponentType } from 'react';
import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api';
@@ -75,6 +76,39 @@ export function createFormField<
TUiOptions extends z.ZodType,
>(opts: FormFieldExtensionData<TReturnValue, TUiOptions>): FormField;
// @alpha
export function createScaffolderFormDecorator<
TInputSchema extends {
[key in string]: (zImpl: typeof z) => z.ZodType;
} = {
[key in string]: (zImpl: typeof z) => z.ZodType;
},
TDeps extends {
[key in string]: AnyApiRef;
} = {
[key in string]: AnyApiRef;
},
TInput extends JsonObject = {
[key in keyof TInputSchema]: z.infer<ReturnType<TInputSchema[key]>>;
},
>(options: {
id: string;
schema?: {
input?: TInputSchema;
};
deps?: TDeps;
decorator: (
ctx: ScaffolderFormDecoratorContext<TInput>,
deps: TDeps extends {
[key in string]: AnyApiRef;
}
? {
[key in keyof TDeps]: TDeps[key]['T'];
}
: never,
) => Promise<void>;
}): ScaffolderFormDecorator<TInput>;
// @alpha
export const DefaultTemplateOutputs: (props: {
output?: ScaffolderTaskOutput;
@@ -190,6 +224,27 @@ export interface ScaffolderFieldProps {
required?: boolean;
}
// @alpha (undocumented)
export type ScaffolderFormDecorator<TInput extends JsonObject = JsonObject> = {
readonly $$type: '@backstage/scaffolder/FormDecorator';
readonly id: string;
readonly TInput: TInput;
};
// @alpha (undocumented)
export type ScaffolderFormDecoratorContext<
TInput extends JsonObject = JsonObject,
> = {
input: TInput;
formState: Record<string, JsonValue>;
setFormState: (
fn: (currentState: Record<string, JsonValue>) => Record<string, JsonValue>,
) => void;
setSecrets: (
fn: (currentState: Record<string, string>) => Record<string, string>,
) => void;
};
// @alpha (undocumented)
export function ScaffolderPageContextMenu(
props: ScaffolderPageContextMenuProps,
@@ -346,9 +401,9 @@ export const useFormDataFromQuery: (
// @alpha (undocumented)
export const useTemplateParameterSchema: (templateRef: string) => {
manifest: TemplateParameterSchema | undefined;
manifest?: TemplateParameterSchema | undefined;
loading: boolean;
error: Error | undefined;
error?: Error | undefined;
};
// @alpha
+4
View File
@@ -538,6 +538,10 @@ export type TemplateParameterSchema = {
description?: string;
schema: JsonObject;
}>;
EXPERIMENTAL_formDecorators?: {
id: string;
input?: JsonObject;
}[];
};
// @public
@@ -0,0 +1,16 @@
/*
* Copyright 2024 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 * from './createScaffolderFieldExtension';
@@ -0,0 +1,75 @@
/*
* Copyright 2024 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 { createScaffolderFormDecorator } from './createScaffolderFormDecorator';
describe('createScaffolderFormDecorator', () => {
it('should return a decorator', () => {
const decorator = createScaffolderFormDecorator({
id: 'test',
deps: {},
decorator: async () => {},
});
expect(decorator).toMatchInlineSnapshot(`
{
"$$type": "@backstage/scaffolder/FormDecorator",
"TInput": null,
"decorator": [Function],
"deps": {},
"id": "test",
"version": "v1",
}
`);
});
it('should allow passing schema and be typesafe', () => {
const decorator = createScaffolderFormDecorator({
id: 'test',
deps: {},
schema: {
input: {
name: z => z.string(),
age: z => z.number(),
},
},
decorator: async ctx => {
const name: string = ctx.input.name;
// @ts-expect-error
const number: string = ctx.input.age;
expect([name, number]).toBeDefined();
},
});
expect(decorator).toMatchInlineSnapshot(`
{
"$$type": "@backstage/scaffolder/FormDecorator",
"TInput": null,
"decorator": [Function],
"deps": {},
"id": "test",
"schema": {
"input": {
"age": [Function],
"name": [Function],
},
},
"version": "v1",
}
`);
});
});
@@ -0,0 +1,84 @@
/*
* Copyright 2024 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 { AnyApiRef } from '@backstage/core-plugin-api';
import { JsonObject, JsonValue } from '@backstage/types';
import { OpaqueFormDecorator } from '@internal/scaffolder';
import { z } from 'zod';
/** @alpha */
export type ScaffolderFormDecoratorContext<
TInput extends JsonObject = JsonObject,
> = {
input: TInput;
formState: Record<string, JsonValue>;
setFormState: (
fn: (currentState: Record<string, JsonValue>) => Record<string, JsonValue>,
) => void;
setSecrets: (
fn: (currentState: Record<string, string>) => Record<string, string>,
) => void;
};
/** @alpha */
export type ScaffolderFormDecorator<TInput extends JsonObject = JsonObject> = {
readonly $$type: '@backstage/scaffolder/FormDecorator';
readonly id: string;
readonly TInput: TInput;
};
/**
* Method for creating decorators which can be used to collect
* secrets from the user before submitting to the backend.
* @alpha
*/
export function createScaffolderFormDecorator<
TInputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType } = {
[key in string]: (zImpl: typeof z) => z.ZodType;
},
TDeps extends { [key in string]: AnyApiRef } = { [key in string]: AnyApiRef },
TInput extends JsonObject = {
[key in keyof TInputSchema]: z.infer<ReturnType<TInputSchema[key]>>;
},
>(options: {
id: string;
schema?: {
input?: TInputSchema;
};
deps?: TDeps;
decorator: (
ctx: ScaffolderFormDecoratorContext<TInput>,
deps: TDeps extends { [key in string]: AnyApiRef }
? { [key in keyof TDeps]: TDeps[key]['T'] }
: never,
) => Promise<void>;
}): ScaffolderFormDecorator<TInput> {
return OpaqueFormDecorator.createInstance('v1', {
...options,
TInput: null as unknown as TInput,
} as {
id: string;
schema?: {
input?: TInputSchema;
};
TInput: TInput;
deps?: TDeps;
decorator: (
ctx: ScaffolderFormDecoratorContext,
deps: { [key in string]: AnyApiRef['T'] },
) => Promise<void>;
});
}
@@ -0,0 +1,16 @@
/*
* Copyright 2024 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 * from './createScaffolderFormDecorator';
@@ -22,15 +22,21 @@ import { TemplateParameterSchema } from '@backstage/plugin-scaffolder-react';
/**
* @alpha
*/
export const useTemplateParameterSchema = (templateRef: string) => {
export const useTemplateParameterSchema = (
templateRef: string,
): { manifest?: TemplateParameterSchema; loading: boolean; error?: Error } => {
const scaffolderApi = useApi(scaffolderApiRef);
const { value, loading, error } = useAsync(
const {
value: manifest,
loading,
error,
} = useAsync(
() => scaffolderApi.getTemplateParameterSchema(templateRef),
[scaffolderApi, templateRef],
);
return {
manifest: value as TemplateParameterSchema | undefined,
manifest,
loading,
error,
};
@@ -18,3 +18,4 @@ export * from './lib';
export * from './hooks';
export * from './overridableComponents';
export * from './blueprints';
export * from './extensions';
+1
View File
@@ -33,4 +33,5 @@ export type TemplateParameterSchema = {
description?: string;
schema: JsonObject;
}>;
EXPERIMENTAL_formDecorators?: { id: string; input?: JsonObject }[];
};