Merge pull request #15798 from backstage/prfc/scaffolder-permissions

PRFC: Scaffolder permissions
This commit is contained in:
Ben Lambert
2023-03-22 11:06:39 +01:00
committed by GitHub
32 changed files with 1182 additions and 92 deletions
@@ -0,0 +1,21 @@
## API Report File for "@backstage/plugin-scaffolder-common"
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
import { ResourcePermission } from '@backstage/plugin-permission-common';
// @alpha
export const RESOURCE_TYPE_SCAFFOLDER_TEMPLATE = 'scaffolder-template';
// @alpha
export const scaffolderPermissions: ResourcePermission<'scaffolder-template'>[];
// @alpha
export const templateParameterReadPermission: ResourcePermission<'scaffolder-template'>;
// @alpha
export const templateStepReadPermission: ResourcePermission<'scaffolder-template'>;
// (No @packageDocumentation comment for this package)
```
+30 -8
View File
@@ -42,20 +42,30 @@ export interface TaskStep {
name: string;
}
// @public
export interface TemplateEntityStepV1beta3 extends JsonObject {
// (undocumented)
'backstage:permissions'?: TemplatePermissionsV1beta3;
// (undocumented)
action: string;
// (undocumented)
id?: string;
// (undocumented)
if?: string | boolean;
// (undocumented)
input?: JsonObject;
// (undocumented)
name?: string;
}
// @public
export interface TemplateEntityV1beta3 extends Entity {
apiVersion: 'scaffolder.backstage.io/v1beta3';
kind: 'Template';
spec: {
type: string;
parameters?: JsonObject | JsonObject[];
steps: Array<{
id?: string;
name?: string;
action: string;
input?: JsonObject;
if?: string | boolean;
}>;
parameters?: TemplateParametersV1beta3 | TemplateParametersV1beta3[];
steps: Array<TemplateEntityStepV1beta3>;
output?: {
[name: string]: string;
};
@@ -74,4 +84,16 @@ export type TemplateInfo = {
metadata: EntityMeta;
};
};
// @public
export interface TemplateParametersV1beta3 extends JsonObject {
// (undocumented)
'backstage:permissions'?: TemplatePermissionsV1beta3;
}
// @public
export interface TemplatePermissionsV1beta3 extends JsonObject {
// (undocumented)
tags?: string[];
}
```
+16
View File
@@ -11,6 +11,21 @@
"module": "dist/index.esm.js",
"types": "dist/index.d.ts"
},
"exports": {
".": "./src/index.ts",
"./alpha": "./src/alpha.ts",
"./package.json": "./package.json"
},
"typesVersions": {
"*": {
"alpha": [
"src/alpha.ts"
],
"package.json": [
"package.json"
]
}
},
"backstage": {
"role": "common-library"
},
@@ -39,6 +54,7 @@
},
"dependencies": {
"@backstage/catalog-model": "workspace:^",
"@backstage/plugin-permission-common": "workspace:^",
"@backstage/types": "workspace:^"
},
"devDependencies": {
@@ -16,6 +16,9 @@
"owner": "artist-relations-team",
"parameters": {
"required": ["name", "description", "repoUrl"],
"backstage:permissions": {
"tags": ["one", "two"]
},
"properties": {
"name": {
"title": "Name",
@@ -41,6 +44,9 @@
"action": "fetch:plain",
"parameters": {
"url": "./template"
},
"backstage:permissions": {
"tags": ["one", "two"]
}
},
{
@@ -92,14 +98,42 @@
"oneOf": [
{
"type": "object",
"description": "The JSONSchema describing the inputs for the template."
"description": "The JSONSchema describing the inputs for the template.",
"properties": {
"backstage:permissions": {
"type": "object",
"description": "Object used for authorizing the parameter",
"properties": {
"tags": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
},
{
"type": "array",
"description": "A list of separate forms to collect parameters.",
"items": {
"type": "object",
"description": "The JSONSchema describing the inputs for the template."
"description": "The JSONSchema describing the inputs for the template.",
"properties": {
"backstage:permissions": {
"type": "object",
"description": "Object used for authorizing the parameter",
"properties": {
"tags": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
}
}
]
@@ -131,6 +165,18 @@
"if": {
"type": ["string", "boolean"],
"description": "A templated condition that skips the step when evaluated to false. If the condition is true or not defined, the step is executed. The condition is true, if the input is not `false`, `undefined`, `null`, `\"\"`, `0`, or `[]`."
},
"backstage:permissions": {
"type": "object",
"description": "Object used for authorizing the step",
"properties": {
"tags": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
}
@@ -15,7 +15,10 @@
*/
import { entityKindSchemaValidator } from '@backstage/catalog-model';
import type { TemplateEntityV1beta3 } from './TemplateEntityV1beta3';
import type {
TemplateEntityV1beta3,
TemplateParametersV1beta3,
} from './TemplateEntityV1beta3';
import schema from './Template.v1beta3.schema.json';
const validator = entityKindSchemaValidator(schema);
@@ -35,6 +38,7 @@ describe('templateEntityV1beta3Validator', () => {
owner: 'team-b',
parameters: {
required: ['owner'],
'backstage:permissions': { tags: ['one', 'two'] },
properties: {
owner: {
type: 'string',
@@ -52,6 +56,9 @@ describe('templateEntityV1beta3Validator', () => {
url: './template',
},
if: '${{ parameters.owner }}',
'backstage:permissions': {
tags: ['one', 'two'],
},
},
],
output: {
@@ -154,4 +161,24 @@ describe('templateEntityV1beta3Validator', () => {
(entity as any).spec.steps[0].if = 5;
expect(() => validator(entity)).toThrow(/if/);
});
it('rejects parameters with wrong backstage:permissions', async () => {
(entity.spec.parameters as TemplateParametersV1beta3)[
'backstage:permissions'
]!.tags = true as unknown as [];
expect(() => validator(entity)).toThrow(/must be array/);
(entity.spec.parameters as TemplateParametersV1beta3)[
'backstage:permissions'
] = true as {};
expect(() => validator(entity)).toThrow(/must be object/);
});
it('rejects steps with wrong backstage:permissions', async () => {
entity.spec.steps[0]['backstage:permissions']!.tags = true as unknown as [];
expect(() => validator(entity)).toThrow(/must be array/);
entity.spec.steps[0]['backstage:permissions'] = true as {};
expect(() => validator(entity)).toThrow(/must be object/);
});
});
@@ -50,18 +50,12 @@ export interface TemplateEntityV1beta3 extends Entity {
* to collect user input and validate it against that schema. This can then be used in the `steps` part below to template
* variables passed from the user into each action in the template.
*/
parameters?: JsonObject | JsonObject[];
parameters?: TemplateParametersV1beta3 | TemplateParametersV1beta3[];
/**
* A list of steps to be executed in sequence which are defined by the template. These steps are a list of the underlying
* javascript action and some optional input parameters that may or may not have been collected from the end user.
*/
steps: Array<{
id?: string;
name?: string;
action: string;
input?: JsonObject;
if?: string | boolean;
}>;
steps: Array<TemplateEntityStepV1beta3>;
/**
* The output is an object where template authors can pull out information from template actions and return them in a known standard way.
*/
@@ -73,6 +67,38 @@ export interface TemplateEntityV1beta3 extends Entity {
};
}
/**
* Step that is part of a Template Entity.
*
* @public
*/
export interface TemplateEntityStepV1beta3 extends JsonObject {
id?: string;
name?: string;
action: string;
input?: JsonObject;
if?: string | boolean;
'backstage:permissions'?: TemplatePermissionsV1beta3;
}
/**
* Parameter that is part of a Template Entity.
*
* @public
*/
export interface TemplateParametersV1beta3 extends JsonObject {
'backstage:permissions'?: TemplatePermissionsV1beta3;
}
/**
* Access control properties for parts of a template.
*
* @public
*/
export interface TemplatePermissionsV1beta3 extends JsonObject {
tags?: string[];
}
const validator = entityKindSchemaValidator(schema);
/**
+16
View File
@@ -0,0 +1,16 @@
/*
* 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.
*/
export * from './permissions';
+7 -1
View File
@@ -21,8 +21,14 @@
*/
export * from './TaskSpec';
export {
templateEntityV1beta3Validator,
isTemplateEntityV1beta3,
} from './TemplateEntityV1beta3';
export type { TemplateEntityV1beta3 } from './TemplateEntityV1beta3';
export type {
TemplateEntityV1beta3,
TemplateEntityStepV1beta3,
TemplateParametersV1beta3,
TemplatePermissionsV1beta3,
} from './TemplateEntityV1beta3';
@@ -0,0 +1,69 @@
/*
* Copyright 2022 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 { createPermission } from '@backstage/plugin-permission-common';
/**
* Permission resource type which corresponds to a scaffolder templates.
*
* @alpha
*/
export const RESOURCE_TYPE_SCAFFOLDER_TEMPLATE = 'scaffolder-template';
/**
* This permission is used to authorize actions that involve reading
* one or more parameters from a template.
*
* If this permission is not authorized, it will appear that the
* parameter does not exist in the template — both in the frontend
* and in API responses.
*
* @alpha
*/
export const templateParameterReadPermission = createPermission({
name: 'scaffolder.template.parameter.read',
attributes: {
action: 'read',
},
resourceType: RESOURCE_TYPE_SCAFFOLDER_TEMPLATE,
});
/**
* This permission is used to authorize actions that involve reading
* one or more steps from a template.
*
* If this permission is not authorized, it will appear that the
* step does not exist in the template — both in the frontend
* and in API responses. Steps will also not be executed.
*
* @alpha
*/
export const templateStepReadPermission = createPermission({
name: 'scaffolder.template.step.read',
attributes: {
action: 'read',
},
resourceType: RESOURCE_TYPE_SCAFFOLDER_TEMPLATE,
});
/**
* List of all the scaffolder permissions
* @alpha
*/
export const scaffolderPermissions = [
templateParameterReadPermission,
templateStepReadPermission,
];