clean up formatting

Signed-off-by: Paul Schultz <pschultz@pobox.com>
This commit is contained in:
Paul Schultz
2023-03-08 10:13:38 -06:00
parent 34fe6c9246
commit 040b54f7e5
26 changed files with 449 additions and 417 deletions
+1 -1
View File
@@ -100,7 +100,7 @@ const app = createApp({
},
/* highlight-add-end */
// ..
})
});
```
See [Sign-In with Proxy Providers](../index.md#sign-in-with-proxy-providers) for pointers on how to set up the sign-in page to also work smoothly for local development.
+2 -2
View File
@@ -104,13 +104,13 @@ installed in `packages/app/src/App.tsx` like this:
import { ProxiedSignInPage } from '@backstage/core-components';
const app = createApp({
/* highlight-add-start */
/* highlight-add-start */
components: {
SignInPage: props => <ProxiedSignInPage {...props} provider="gcp-iap" />,
},
/* highlight-add-end */
// ..
})
});
```
See the [Sign-In with Proxy Providers](../index.md#sign-in-with-proxy-providers) section for more information.
+10 -7
View File
@@ -121,12 +121,15 @@ const app = createApp({
SignInPage: props => (
<SignInPage
{...props}
providers={['guest', {
id: 'github-auth-provider',
title: 'GitHub',
message: 'Sign in using GitHub',
apiRef: githubAuthApiRef,
}]}
providers={[
'guest',
{
id: 'github-auth-provider',
title: 'GitHub',
message: 'Sign in using GitHub',
apiRef: githubAuthApiRef,
},
]}
/>
),
},
@@ -314,7 +317,7 @@ import {
export const apis: AnyApiFactory[] = [
/* highlight-add-next-line */
ScmAuth.createDefaultApiFactory(),
// ...
// ...
];
```
+4 -2
View File
@@ -67,11 +67,13 @@ import { ProxiedSignInPage } from '@backstage/core-components';
const app = createApp({
/* highlight-add-start */
components: {
SignInPage: props => <ProxiedSignInPage {...props} provider="azure-easyauth" />,
SignInPage: props => (
<ProxiedSignInPage {...props} provider="azure-easyauth" />
),
},
/* highlight-add-end */
// ..
})
});
```
See the [Sign-In with Proxy Providers](../index.md#sign-in-with-proxy-providers) section for more information.
+4 -2
View File
@@ -70,11 +70,13 @@ import { ProxiedSignInPage } from '@backstage/core-components';
const app = createApp({
/* highlight-add-start */
components: {
SignInPage: props => <ProxiedSignInPage {...props} provider="oauth2Proxy" />,
SignInPage: props => (
<ProxiedSignInPage {...props} provider="oauth2Proxy" />
),
},
/* highlight-add-end */
// ..
})
});
```
See [Sign-In with Proxy Providers](../index.md#sign-in-with-proxy-providers) for pointers on how to set up the sign-in page to also work smoothly for local development.
+14 -19
View File
@@ -89,28 +89,23 @@ export const apis: AnyApiFactory[] = [
discoveryApi: discoveryApiRef,
oauthRequestApi: oauthRequestApiRef,
configApi: configApiRef,
},
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
OAuth2.create({
discoveryApi,
oauthRequestApi,
provider: {
id: 'my-auth-provider',
title: 'My custom auth provider',
icon: () => null,
},
environment: configApi.getOptionalString('auth.environment'),
defaultScopes: [
'openid',
'profile',
'email',
],
}),
},
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
OAuth2.create({
discoveryApi,
oauthRequestApi,
provider: {
id: 'my-auth-provider',
title: 'My custom auth provider',
icon: () => null,
},
environment: configApi.getOptionalString('auth.environment'),
defaultScopes: ['openid', 'profile', 'email'],
}),
}),
/* highlight-add-end */
// ..
]
];
```
Please note we're importing the `OAuth2` class from `@backstage/core-app-api` effectively
@@ -134,7 +134,10 @@ import { createBackend } from '@backstage/backend-defaults';
/* highlight-remove-next-line */
import { legacyPlugin } from '@backstage/backend-common';
/* highlight-add-start */
import { makeLegacyPlugin, loggerToWinstonLogger } from '@backstage/backend-common';
import {
makeLegacyPlugin,
loggerToWinstonLogger,
} from '@backstage/backend-common';
import { coreServices } from '@backstage/backend-plugin-api';
/* highlight-add-end */
@@ -187,16 +190,16 @@ In this example, we'll assume that your added environment field is named
/* highlight-add-next-line */
import { exampleServiceRef } from '<somewhere>'; // if the definition is elsewhere
const legacyPlugin = makeLegacyPlugin(
{
// ... the above core services still go here
/* highlight-add-next-line */
example: exampleServiceRef
},
{
logger: log => loggerToWinstonLogger(log),
},
);
const legacyPlugin = makeLegacyPlugin(
{
// ... the above core services still go here
/* highlight-add-next-line */
example: exampleServiceRef,
},
{
logger: log => loggerToWinstonLogger(log),
},
);
```
After this, your backend will know how to instantiate your thing on demand and
@@ -282,7 +285,7 @@ import { createBackendModule } from '@backstage/backend-plugin-api';
/* highlight-add-start */
const catalogModuleCustomExtensions = createBackendModule({
pluginId: 'catalog', // name of the plugin that the module is targeting
pluginId: 'catalog', // name of the plugin that the module is targeting
moduleId: 'customExtensions',
register(env) {
env.registerInit({
@@ -294,7 +297,7 @@ const catalogModuleCustomExtensions = createBackendModule({
// Here you have the opportunity to interact with the extension
// point before the plugin itself gets instantiated
catalog.addEntityProvider(new MyEntityProvider()); // just an example
catalog.addProcessor(new MyProcessor()); // just an example
catalog.addProcessor(new MyProcessor()); // just an example
},
});
},
@@ -352,7 +355,7 @@ import { createBackendModule } from '@backstage/backend-plugin-api';
/* highlight-add-start */
const eventsModuleCustomExtensions = createBackendModule({
pluginId: 'events', // name of the plugin that the module is targeting
pluginId: 'events', // name of the plugin that the module is targeting
moduleId: 'customExtensions',
register(env) {
env.registerInit({
@@ -420,7 +423,7 @@ import { createBackendModule } from '@backstage/backend-plugin-api';
/* highlight-add-start */
const scaffolderModuleCustomExtensions = createBackendModule({
pluginId: 'scaffolder', // name of the plugin that the module is targeting
pluginId: 'scaffolder', // name of the plugin that the module is targeting
moduleId: 'customExtensions',
register(env) {
env.registerInit({
+1 -1
View File
@@ -38,7 +38,7 @@ const serviceEntityPage = (
</EntityLayout.Route>
{/* highlight-add-end */}
</EntityLayout>
)
);
```
**Notes:**
+44 -42
View File
@@ -20,30 +20,30 @@ to do that in two steps.
[interface](https://github.com/backstage/backstage/blob/db2666b980853c281b8fe77905d7639c5d255f13/plugins/search/src/apis.ts#L31)
according to your needs.
```typescript
export class SearchClient implements SearchApi {
// your implementation
}
```
```typescript
export class SearchClient implements SearchApi {
// your implementation
}
```
2. Override the API ref `searchApiRef` with your new implemented API in the
`App.tsx` using `ApiFactories`.
[Read more about App APIs](https://backstage.io/docs/api/utility-apis#app-apis).
```typescript
const app = createApp({
apis: [
// SearchApi
createApiFactory({
api: searchApiRef,
deps: { discovery: discoveryApiRef },
factory({ discovery }) {
return new SearchClient({ discoveryApi: discovery });
},
}),
],
});
```
```typescript
const app = createApp({
apis: [
// SearchApi
createApiFactory({
api: searchApiRef,
deps: { discovery: discoveryApiRef },
factory({ discovery }) {
return new SearchClient({ discoveryApi: discovery });
},
}),
],
});
```
## How to index TechDocs documents
@@ -63,35 +63,35 @@ getting started guide.
1. Import the `DefaultTechDocsCollatorFactory` from
`@backstage/plugin-techdocs-backend`.
```typescript
import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-techdocs-backend';
```
```typescript
import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-techdocs-backend';
```
2. If there isn't an existing schedule you'd like to run the collator on, be
sure to create it first. Something like...
```typescript
import { Duration } from 'luxon';
```typescript
import { Duration } from 'luxon';
const every10MinutesSchedule = env.scheduler.createScheduledTaskRunner({
frequency: Duration.fromObject({ seconds: 600 }),
timeout: Duration.fromObject({ seconds: 900 }),
initialDelay: Duration.fromObject({ seconds: 3 }),
});
```
const every10MinutesSchedule = env.scheduler.createScheduledTaskRunner({
frequency: Duration.fromObject({ seconds: 600 }),
timeout: Duration.fromObject({ seconds: 900 }),
initialDelay: Duration.fromObject({ seconds: 3 }),
});
```
3. Register the `DefaultTechDocsCollatorFactory` with the IndexBuilder.
```typescript
indexBuilder.addCollator({
schedule: every10MinutesSchedule,
factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, {
discovery: env.discovery,
logger: env.logger,
tokenManager: env.tokenManager,
}),
});
```
```typescript
indexBuilder.addCollator({
schedule: every10MinutesSchedule,
factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, {
discovery: env.discovery,
logger: env.logger,
tokenManager: env.tokenManager,
}),
});
```
You should now have your TechDocs documents indexed to your search engine of
choice!
@@ -125,7 +125,9 @@ You can either just simply amend default behaviour, or even to write completely
> `authorization` and `location` cannot be modified via a `entityTransformer`, `location` can be modified only through `locationTemplate`.
```ts title="packages/backend/src/plugins/search.ts"
const entityTransformer: CatalogCollatorEntityTransformer = (entity: Entity) => {
const entityTransformer: CatalogCollatorEntityTransformer = (
entity: Entity,
) => {
if (entity.kind === 'SomeKind') {
return {
// customize here output for 'SomeKind' kind
@@ -173,7 +175,7 @@ indexBuilder.addCollator({
tokenManager: env.tokenManager,
/* highlight-add-start */
filter: {
kind: ['API', 'Component', 'Domain', 'Group', 'System', 'User'],
kind: ['API', 'Component', 'Domain', 'Group', 'System', 'User'],
},
/* highlight-add-end */
}),
@@ -254,7 +254,9 @@ export default async function createPlugin(
/* highlight-add-start */
await env.scheduler.scheduleTask({
id: 'run_frobs_refresh',
fn: async () => { await frobs.run(); },
fn: async () => {
await frobs.run();
},
frequency: { minutes: 30 },
timeout: { minutes: 10 },
});
@@ -46,7 +46,6 @@ The `ScaffolderPage` router has a completely different export for the `scaffolde
import { ScaffolderPage } from '@backstage/plugin-scaffolder';
/* highlight-add-next-line */
import { NextScaffolderPage } from '@backstage/plugin-scaffolder/alpha';
```
And this API should be the exact same as the previous Router, so you should be able to make a change like the following further down in this file:
@@ -136,7 +135,7 @@ export const EntityNamePicker = (
placeholder,
} = props;
// ..
}
};
```
There's another `/alpha` export that you need to replace `FieldExtensionComponentProps` with which is the `NextFieldExtensionComponentProps`.
@@ -167,7 +166,7 @@ export const EntityNamePicker = (
placeholder,
} = props;
// ..
}
};
```
You'll notice that there's an additional change here, which is that we're now defaulting the `uiSchema` to an empty object. This is because the `uiSchema` is now optional, and if you don't provide it, it will be `undefined` instead of an empty object. There's more around this in the [breaking changes](#breaking-changes) section.
@@ -218,20 +217,20 @@ Once we fully release the code that is in the `/alpha` exports right now onto th
Later releases of `react-jsonschema-form` have made the `uiSchema` optional, and if you don't provide it, it will be `undefined` instead of an empty object. This means that you will need to make sure that you're defaulting the `uiSchema` to an empty object if you're using it in your code.
```tsx
const {
onChange,
required,
schema: { title = 'Name', description = 'Unique name of the component' },
rawErrors,
formData,
/* highlight-remove-next-line */
uiSchema: { 'ui:autofocus': autoFocus },
/* highlight-add-next-line */
uiSchema: { 'ui:autofocus': autoFocus } = {},
idSchema,
placeholder,
} = props;
// ..
const {
onChange,
required,
schema: { title = 'Name', description = 'Unique name of the component' },
rawErrors,
formData,
/* highlight-remove-next-line */
uiSchema: { 'ui:autofocus': autoFocus },
/* highlight-add-next-line */
uiSchema: { 'ui:autofocus': autoFocus } = {},
idSchema,
placeholder,
} = props;
// ..
```
### `formData` can also be `undefined`
@@ -239,18 +238,18 @@ Later releases of `react-jsonschema-form` have made the `uiSchema` optional, and
If you were using the `formData` and assuming that it was set to an empty object when building `Field Extensions` that return objects, then this will be `undefined` now due to a change in the `react-jsonschema-form` library.
```tsx
const {
onChange,
required,
schema: { title = 'Name', description = 'Unique name of the component' },
rawErrors,
/* highlight-remove-next-line */
formData,
/* highlight-add-next-line */
formData = {}, // or maybe some other default value that you would prefer
uiSchema: { 'ui:autofocus': autoFocus } = {},
idSchema,
placeholder,
} = props;
// ..
const {
onChange,
required,
schema: { title = 'Name', description = 'Unique name of the component' },
rawErrors,
/* highlight-remove-next-line */
formData,
/* highlight-add-next-line */
formData = {}, // or maybe some other default value that you would prefer
uiSchema: { 'ui:autofocus': autoFocus } = {},
idSchema,
placeholder,
} = props;
// ..
```
+75 -75
View File
@@ -346,19 +346,19 @@ You can add more icons, if the [default icons](https://github.com/backstage/back
2. Then you want to import your icon, add this to the rest of your imports: `import AlarmIcon from '@material-ui/icons/Alarm';`
3. Next you want to add the icon like this to your `createApp`:
```tsx title="packages/app/src/App.tsx"
const app = createApp({
apis: ...,
plugins: ...,
/* highlight-add-start */
icons: {
alert: AlarmIcon,
},
/* highlight-add-end */
themes: ...,
components: ...,
});
```
```tsx title="packages/app/src/App.tsx"
const app = createApp({
apis: ...,
plugins: ...,
/* highlight-add-start */
icons: {
alert: AlarmIcon,
},
/* highlight-add-end */
themes: ...,
components: ...,
});
```
4. Now we can reference `alert` for our icon in our entity links like this:
@@ -413,71 +413,71 @@ For this example we'll show you how you can expand the sidebar with a sub-menu:
3. Then update the `@backstage/core-components` import like this:
```tsx
import {
Sidebar,
sidebarConfig,
SidebarDivider,
SidebarGroup,
SidebarItem,
SidebarPage,
SidebarScrollWrapper,
SidebarSpace,
useSidebarOpenState,
Link,
/* highlight-add-start */
GroupIcon,
SidebarSubmenu,
SidebarSubmenuItem,
/* highlight-add-end */
} from '@backstage/core-components';
```
```tsx
import {
Sidebar,
sidebarConfig,
SidebarDivider,
SidebarGroup,
SidebarItem,
SidebarPage,
SidebarScrollWrapper,
SidebarSpace,
useSidebarOpenState,
Link,
/* highlight-add-start */
GroupIcon,
SidebarSubmenu,
SidebarSubmenuItem,
/* highlight-add-end */
} from '@backstage/core-components';
```
4. Finally replace `<SidebarItem icon={HomeIcon} to="catalog" text="Home" />` with this:
```tsx
<SidebarItem icon={HomeIcon} to="catalog" text="Home">
<SidebarSubmenu title="Catalog">
<SidebarSubmenuItem
title="Domains"
to="catalog?filters[kind]=domain"
icon={DomainIcon}
/>
<SidebarSubmenuItem
title="Systems"
to="catalog?filters[kind]=system"
icon={SystemIcon}
/>
<SidebarSubmenuItem
title="Components"
to="catalog?filters[kind]=component"
icon={ComponentIcon}
/>
<SidebarSubmenuItem
title="APIs"
to="catalog?filters[kind]=api"
icon={ApiIcon}
/>
<SidebarDivider />
<SidebarSubmenuItem
title="Resources"
to="catalog?filters[kind]=resource"
icon={ResourceIcon}
/>
<SidebarDivider />
<SidebarSubmenuItem
title="Groups"
to="catalog?filters[kind]=group"
icon={GroupIcon}
/>
<SidebarSubmenuItem
title="Users"
to="catalog?filters[kind]=user"
icon={UserIcon}
/>
</SidebarSubmenu>
</SidebarItem>
```
```tsx
<SidebarItem icon={HomeIcon} to="catalog" text="Home">
<SidebarSubmenu title="Catalog">
<SidebarSubmenuItem
title="Domains"
to="catalog?filters[kind]=domain"
icon={DomainIcon}
/>
<SidebarSubmenuItem
title="Systems"
to="catalog?filters[kind]=system"
icon={SystemIcon}
/>
<SidebarSubmenuItem
title="Components"
to="catalog?filters[kind]=component"
icon={ComponentIcon}
/>
<SidebarSubmenuItem
title="APIs"
to="catalog?filters[kind]=api"
icon={ApiIcon}
/>
<SidebarDivider />
<SidebarSubmenuItem
title="Resources"
to="catalog?filters[kind]=resource"
icon={ResourceIcon}
/>
<SidebarDivider />
<SidebarSubmenuItem
title="Groups"
to="catalog?filters[kind]=group"
icon={GroupIcon}
/>
<SidebarSubmenuItem
title="Users"
to="catalog?filters[kind]=user"
icon={UserIcon}
/>
</SidebarSubmenu>
</SidebarItem>
```
When you startup your Backstage app and hover over the Home option on the sidebar you'll now see a nice sub-menu appear with links to the various Kinds in your Catalog. It would look like this:
@@ -33,25 +33,25 @@ to an entity in the software catalog.
2. Add the `EntityCircleCIContent` extension to the entity pages in the app:
```tsx title="packages/app/src/components/catalog/EntityPage.tsx"
/* highlight-add-start */
import {
EntityCircleCIContent,
isCircleCIAvailable,
} from '@backstage/plugin-circleci';
/* highlight-add-end */
```tsx title="packages/app/src/components/catalog/EntityPage.tsx"
/* highlight-add-start */
import {
EntityCircleCIContent,
isCircleCIAvailable,
} from '@backstage/plugin-circleci';
/* highlight-add-end */
const cicdContent = (
<EntitySwitch>
{/* ... */}
{/* highlight-add-next-line */}
<EntitySwitch.Case if={isCircleCIAvailable}>
<EntityCircleCIContent />
</EntitySwitch.Case>;
{/* highlight-add-end */}
</EntitySwitch>
);
```
const cicdContent = (
<EntitySwitch>
{/* ... */}
{/* highlight-add-next-line */}
<EntitySwitch.Case if={isCircleCIAvailable}>
<EntityCircleCIContent />
</EntitySwitch.Case>
;{/* highlight-add-end */}
</EntitySwitch>
);
```
This is just one example, but each Backstage instance may integrate content or
cards to suit their needs on different pages, tabs, etc. In addition, while some
+3 -3
View File
@@ -58,7 +58,7 @@ const routes = (
<Navigate key="/" to="catalog" />
{/* ... */}
</FlatRoutes>
)
);
```
Let's replace the `<Navigate>` line and use the new component we created in the previous step as the new homepage.
@@ -80,7 +80,7 @@ const routes = (
{/* highlight-add-end */}
{/* ... */}
</FlatRoutes>
)
);
```
#### 4. Update sidebar items
@@ -122,7 +122,7 @@ export const Root = ({ children }: PropsWithChildren<{}>) => (
</SidebarGroup>
</Sidebar>
</SidebarPage>
)
);
```
That's it! You should now have _(although slightly boring)_ a homepage!
+5 -1
View File
@@ -128,7 +128,11 @@ export default async function createPlugin(
): Promise<Router> {
const builder = await CatalogBuilder.create(env);
/* highlight-add-next-line */
builder.addProcessor(AzureDevOpsDiscoveryProcessor.fromConfig(env.config, { logger: env.logger }));
builder.addProcessor(
AzureDevOpsDiscoveryProcessor.fromConfig(env.config, {
logger: env.logger,
}),
);
// ..
}
+2 -2
View File
@@ -44,7 +44,7 @@ For large organizations, this plugin can take a long time, so be careful setting
import { MicrosoftGraphOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-msgraph';
export default async function createPlugin(
env: PluginEnvironment,
env: PluginEnvironment,
): Promise<Router> {
const builder = await CatalogBuilder.create(env);
@@ -55,7 +55,7 @@ export default async function createPlugin(
schedule: env.scheduler.createScheduledTaskRunner({
frequency: { hours: 1 },
timeout: { minutes: 50 },
initialDelay: { seconds: 15}
initialDelay: { seconds: 15 },
}),
}),
);
@@ -105,8 +105,8 @@ export default async function createCatalogEventBasedProviders(
}),
);
/* highlight-add-end */
return providers.flat();
}
return providers.flat();
}
```
**Attention:**
+10 -10
View File
@@ -44,7 +44,7 @@ export default async function createPlugin(
schedule: env.scheduler.createScheduledTaskRunner({
frequency: { minutes: 30 },
timeout: { minutes: 3 },
}),
}),
// optional: alternatively, use schedule
scheduler: env.scheduler,
}),
@@ -88,14 +88,14 @@ export default async function createCatalogEventBasedProviders(
/* highlight-add-start */
providers.push(
GithubEntityProvider.fromConfig(env.config, {
logger: env.logger,
// optional: alternatively, use scheduler with schedule defined in app-config.yaml
schedule: env.scheduler.createScheduledTaskRunner({
frequency: { minutes: 30 },
timeout: { minutes: 3 },
}),
// optional: alternatively, use schedule
scheduler: env.scheduler,
logger: env.logger,
// optional: alternatively, use scheduler with schedule defined in app-config.yaml
schedule: env.scheduler.createScheduledTaskRunner({
frequency: { minutes: 30 },
timeout: { minutes: 3 },
}),
// optional: alternatively, use schedule
scheduler: env.scheduler,
}),
);
/* highlight-add-end */
@@ -269,7 +269,7 @@ import {
} from '@backstage/plugin-catalog-backend-module-github';
import {
ScmIntegrations,
DefaultGithubCredentialsProvider
DefaultGithubCredentialsProvider,
} from '@backstage/integration';
/* highlight-add-end */
+1 -1
View File
@@ -88,7 +88,7 @@ import { PluginEnvironment } from '../types';
export default async function createCatalogEventBasedProviders(
/* highlight-remove-next-line */
_: PluginEnvironment,
/* highlight-add-next-line */
/* highlight-add-next-line */
env: PluginEnvironment,
): Promise<Array<EntityProvider & EventSubscriber>> {
const providers: Array<
+1 -1
View File
@@ -99,7 +99,7 @@ export default async function createPlugin(
const builder = await CatalogBuilder.create(env);
/* highlight-add-start */
builder.addProcessor(
GitLabDiscoveryProcessor.fromConfig(env.config, { logger: env.logger })
GitLabDiscoveryProcessor.fromConfig(env.config, { logger: env.logger }),
);
/* highlight-add-end */
+86 -86
View File
@@ -46,67 +46,67 @@ The permissions framework uses a new `permission-backend` plugin to accept autho
1. Add `@backstage/plugin-permission-backend` as a dependency of your Backstage backend:
```bash
# From your Backstage root directory
$ yarn add --cwd packages/backend @backstage/plugin-permission-backend
```
```bash
# From your Backstage root directory
$ yarn add --cwd packages/backend @backstage/plugin-permission-backend
```
2. Add the following to a new file, `packages/backend/src/plugins/permission.ts`. This adds the permission-backend router, and configures it with a policy which allows everything.
```typescript title="packages/backend/src/plugins/permission.ts"
import { createRouter } from '@backstage/plugin-permission-backend';
import {
AuthorizeResult,
PolicyDecision,
} from '@backstage/plugin-permission-common';
import { PermissionPolicy } from '@backstage/plugin-permission-node';
import { Router } from 'express';
import { PluginEnvironment } from '../types';
```typescript title="packages/backend/src/plugins/permission.ts"
import { createRouter } from '@backstage/plugin-permission-backend';
import {
AuthorizeResult,
PolicyDecision,
} from '@backstage/plugin-permission-common';
import { PermissionPolicy } from '@backstage/plugin-permission-node';
import { Router } from 'express';
import { PluginEnvironment } from '../types';
class TestPermissionPolicy implements PermissionPolicy {
async handle(): Promise<PolicyDecision> {
return { result: AuthorizeResult.ALLOW };
}
}
class TestPermissionPolicy implements PermissionPolicy {
async handle(): Promise<PolicyDecision> {
return { result: AuthorizeResult.ALLOW };
}
}
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
return await createRouter({
config: env.config,
logger: env.logger,
discovery: env.discovery,
policy: new TestPermissionPolicy(),
identity: env.identity,
});
}
```
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
return await createRouter({
config: env.config,
logger: env.logger,
discovery: env.discovery,
policy: new TestPermissionPolicy(),
identity: env.identity,
});
}
```
3. Wire up the permission policy in `packages/backend/src/index.ts`. [The index in the example backend](https://github.com/backstage/backstage/blob/master/packages/backend/src/index.ts) shows how to do this. Youll need to import the module from the previous step, create a plugin environment, and add the router to the express app:
```ts title="packages/backend/src/index.ts"
import proxy from './plugins/proxy';
import techdocs from './plugins/techdocs';
import search from './plugins/search';
/* highlight-add-next-line */
import permission from './plugins/permission';
```ts title="packages/backend/src/index.ts"
import proxy from './plugins/proxy';
import techdocs from './plugins/techdocs';
import search from './plugins/search';
/* highlight-add-next-line */
import permission from './plugins/permission';
async function main() {
const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs'));
const searchEnv = useHotMemoize(module, () => createEnv('search'));
const appEnv = useHotMemoize(module, () => createEnv('app'));
/* highlight-add-next-line */
const permissionEnv = useHotMemoize(module, () => createEnv('permission'));
// ..
async function main() {
const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs'));
const searchEnv = useHotMemoize(module, () => createEnv('search'));
const appEnv = useHotMemoize(module, () => createEnv('app'));
/* highlight-add-next-line */
const permissionEnv = useHotMemoize(module, () => createEnv('permission'));
// ..
apiRouter.use('/techdocs', await techdocs(techdocsEnv));
apiRouter.use('/proxy', await proxy(proxyEnv));
apiRouter.use('/search', await search(searchEnv));
/* highlight-add-next-line */
apiRouter.use('/permission', await permission(permissionEnv));
// ..
}
```
apiRouter.use('/techdocs', await techdocs(techdocsEnv));
apiRouter.use('/proxy', await proxy(proxyEnv));
apiRouter.use('/search', await search(searchEnv));
/* highlight-add-next-line */
apiRouter.use('/permission', await permission(permissionEnv));
// ..
}
```
### 2. Enable and test the permissions system
@@ -114,46 +114,46 @@ Now that the permission backend is running, its time to enable the permission
1. Set the property `permission.enabled` to `true` in `app-config.yaml`.
```yaml title="app-config.yaml"
permission:
enabled: true
```
```yaml title="app-config.yaml"
permission:
enabled: true
```
2. Update the PermissionPolicy in `packages/backend/src/plugins/permission.ts` to disable a permission thats easy for us to test. This policy rejects any attempt to delete a catalog entity:
```ts title="packages/backend/src/plugins/permission.ts"
import { createRouter } from '@backstage/plugin-permission-backend';
import {
AuthorizeResult,
PolicyDecision,
} from '@backstage/plugin-permission-common';
/* highlight-remove-next-line */
import { PermissionPolicy } from '@backstage/plugin-permission-node';
/* highlight-add-start */
import {
PermissionPolicy,
PolicyQuery,
} from '@backstage/plugin-permission-node';
/* highlight-add-end */
import { Router } from 'express';
import { PluginEnvironment } from '../types';
```ts title="packages/backend/src/plugins/permission.ts"
import { createRouter } from '@backstage/plugin-permission-backend';
import {
AuthorizeResult,
PolicyDecision,
} from '@backstage/plugin-permission-common';
/* highlight-remove-next-line */
import { PermissionPolicy } from '@backstage/plugin-permission-node';
/* highlight-add-start */
import {
PermissionPolicy,
PolicyQuery,
} from '@backstage/plugin-permission-node';
/* highlight-add-end */
import { Router } from 'express';
import { PluginEnvironment } from '../types';
class TestPermissionPolicy implements PermissionPolicy {
/* highlight-remove-next-line */
async handle(): Promise<PolicyDecision> {
/* highlight-add-start */
async handle(request: PolicyQuery): Promise<PolicyDecision> {
if (request.permission.name === 'catalog.entity.delete') {
return {
result: AuthorizeResult.DENY,
};
}
/* highlight-add-end */
class TestPermissionPolicy implements PermissionPolicy {
/* highlight-remove-next-line */
async handle(): Promise<PolicyDecision> {
/* highlight-add-start */
async handle(request: PolicyQuery): Promise<PolicyDecision> {
if (request.permission.name === 'catalog.entity.delete') {
return {
result: AuthorizeResult.DENY,
};
}
/* highlight-add-end */
return { result: AuthorizeResult.ALLOW };
}
}
```
return { result: AuthorizeResult.ALLOW };
}
}
```
3. Now that youve made this change, you should find that the unregister entity menu option on the catalog entity page is disabled.
+76 -76
View File
@@ -20,99 +20,99 @@ The source code is available here:
1. Copy-paste the three folders into the plugins folder of your backstage application repository (removing the `example-` prefix from each folder) or run the following script from the root of your backstage application:
```bash
$ cd $(mktemp -d)
git clone --depth 1 --quiet --no-checkout --filter=blob:none https://github.com/backstage/backstage.git .
git checkout master -- plugins/example-todo-list/
git checkout master -- plugins/example-todo-list-backend/
git checkout master -- plugins/example-todo-list-common/
sed -i '' 's/workspace:\^/\*/g' plugins/example-todo-list/package.json
sed -i '' 's/workspace:\^/\*/g' plugins/example-todo-list-backend/package.json
sed -i '' 's/workspace:\^/\*/g' plugins/example-todo-list-common/package.json
for file in plugins/*; do mv "$file" "$OLDPWD/${file/example-todo/todo}"; done
cd -
```
```bash
$ cd $(mktemp -d)
git clone --depth 1 --quiet --no-checkout --filter=blob:none https://github.com/backstage/backstage.git .
git checkout master -- plugins/example-todo-list/
git checkout master -- plugins/example-todo-list-backend/
git checkout master -- plugins/example-todo-list-common/
sed -i '' 's/workspace:\^/\*/g' plugins/example-todo-list/package.json
sed -i '' 's/workspace:\^/\*/g' plugins/example-todo-list-backend/package.json
sed -i '' 's/workspace:\^/\*/g' plugins/example-todo-list-common/package.json
for file in plugins/*; do mv "$file" "$OLDPWD/${file/example-todo/todo}"; done
cd -
```
The `plugins` directory of your project should now include `todo-list`, `todo-list-backend`, and `todo-list-common`.
The `plugins` directory of your project should now include `todo-list`, `todo-list-backend`, and `todo-list-common`.
**Important**: if you are on **Windows**, make sure you have WSL and git installed on your machine before executing the script above.
**Important**: if you are on **Windows**, make sure you have WSL and git installed on your machine before executing the script above.
2. Add these packages as dependencies for your Backstage app:
```sh
# From your Backstage root directory
$ yarn add --cwd packages/backend @internal/plugin-todo-list-backend @internal/plugin-todo-list-common
$ yarn add --cwd packages/app @internal/plugin-todo-list
```
```sh
# From your Backstage root directory
$ yarn add --cwd packages/backend @internal/plugin-todo-list-backend @internal/plugin-todo-list-common
$ yarn add --cwd packages/app @internal/plugin-todo-list
```
3. Include the backend and frontend plugin in your application:
Create a new `packages/backend/src/plugins/todolist.ts` with the following content:
Create a new `packages/backend/src/plugins/todolist.ts` with the following content:
```typescript title="packages/backend/src/plugins/todolist.ts"
import { DefaultIdentityClient } from '@backstage/plugin-auth-node';
import { createRouter } from '@internal/plugin-todo-list-backend';
import { Router } from 'express';
import { PluginEnvironment } from '../types';
```typescript title="packages/backend/src/plugins/todolist.ts"
import { DefaultIdentityClient } from '@backstage/plugin-auth-node';
import { createRouter } from '@internal/plugin-todo-list-backend';
import { Router } from 'express';
import { PluginEnvironment } from '../types';
export default async function createPlugin({
logger,
discovery,
}: PluginEnvironment): Promise<Router> {
return await createRouter({
logger,
identity: DefaultIdentityClient.create({
discovery,
issuer: await discovery.getExternalBaseUrl('auth'),
}),
});
}
```
export default async function createPlugin({
logger,
discovery,
}: PluginEnvironment): Promise<Router> {
return await createRouter({
logger,
identity: DefaultIdentityClient.create({
discovery,
issuer: await discovery.getExternalBaseUrl('auth'),
}),
});
}
```
Apply the following changes to `packages/backend/src/index.ts`:
Apply the following changes to `packages/backend/src/index.ts`:
```ts title="packages/backend/src/index.ts"
import techdocs from './plugins/techdocs';
/* highlight-add-next-line */
import todoList from './plugins/todolist';
import search from './plugins/search';
```ts title="packages/backend/src/index.ts"
import techdocs from './plugins/techdocs';
/* highlight-add-next-line */
import todoList from './plugins/todolist';
import search from './plugins/search';
async function main() {
const searchEnv = useHotMemoize(module, () => createEnv('search'));
const appEnv = useHotMemoize(module, () => createEnv('app'));
/* highlight-add-next-line */
const todoListEnv = useHotMemoize(module, () => createEnv('todolist'));
// ..
async function main() {
const searchEnv = useHotMemoize(module, () => createEnv('search'));
const appEnv = useHotMemoize(module, () => createEnv('app'));
/* highlight-add-next-line */
const todoListEnv = useHotMemoize(module, () => createEnv('todolist'));
// ..
apiRouter.use('/proxy', await proxy(proxyEnv));
apiRouter.use('/search', await search(searchEnv));
apiRouter.use('/permission', await permission(permissionEnv));
/* highlight-add-next-line */
apiRouter.use('/todolist', await todoList(todoListEnv));
// Add backends ABOVE this line; this 404 handler is the catch-all fallback
apiRouter.use(notFoundHandler());
// ..
}
```
apiRouter.use('/proxy', await proxy(proxyEnv));
apiRouter.use('/search', await search(searchEnv));
apiRouter.use('/permission', await permission(permissionEnv));
/* highlight-add-next-line */
apiRouter.use('/todolist', await todoList(todoListEnv));
// Add backends ABOVE this line; this 404 handler is the catch-all fallback
apiRouter.use(notFoundHandler());
// ..
}
```
Apply the following changes to `packages/app/src/App.tsx`:
Apply the following changes to `packages/app/src/App.tsx`:
```tsx title="packages/app/src/App.tsx"
/* highlight-add-next-line */
import { TodoListPage } from '@internal/plugin-todo-list';
```tsx title="packages/app/src/App.tsx"
/* highlight-add-next-line */
import { TodoListPage } from '@internal/plugin-todo-list';
const routes = (
<FlatRoutes>
<Route path="/search" element={<SearchPage />}>
{searchPage}
</Route>
<Route path="/settings" element={<UserSettingsPage />} />
{/* highlight-add-next-line */}
<Route path="/todo-list" element={<TodoListPage />} />
{/* ... */}
</FlatRoutes>
);
```
const routes = (
<FlatRoutes>
<Route path="/search" element={<SearchPage />}>
{searchPage}
</Route>
<Route path="/settings" element={<UserSettingsPage />} />
{/* highlight-add-next-line */}
<Route path="/todo-list" element={<TodoListPage />} />
{/* ... */}
</FlatRoutes>
);
```
Now if you start your application you should be able to reach the `/todo-list` page:
@@ -32,7 +32,10 @@ export const todoListUpdatePermission = createPermission({
/* highlight-remove-next-line */
export const todoListPermissions = [todoListCreatePermission];
/* highlight-add-next-line */
export const todoListPermissions = [todoListCreatePermission, todoListUpdatePermission];
export const todoListPermissions = [
todoListCreatePermission,
todoListUpdatePermission,
];
```
Notice that unlike `todoListCreatePermission`, the `todoListUpdatePermission` permission contains a `resourceType` field. This field indicates to the permission framework that this permission is intended to be authorized in the context of a resource with type `'todo-item'`. You can use whatever string you like as the resource type, as long as you use the same value consistently for each type of resource.
@@ -45,7 +48,10 @@ To start, let's edit `plugins/todo-list-backend/src/service/router.ts` in the sa
/* highlight-remove-next-line */
import { todoListCreatePermission } from '@internal/plugin-todo-list-common';
/* highlight-add-next-line */
import { todoListCreatePermission, todoListUpdatePermission } from '@internal/plugin-todo-list-common';
import {
todoListCreatePermission,
todoListUpdatePermission,
} from '@internal/plugin-todo-list-common';
router.put('/todos', async (req, res) => {
/* highlight-add-start */
@@ -14,11 +14,14 @@ router.get('/todos', async (req, res) => {
const token = IdentityClient.getBearerToken(req.header('authorization'));
/* highlight-remove-next-line */
res.json(getAll())
res.json(getAll());
/* highlight-add-start */
const items = getAll();
const decisions = await permissions.authorize(
items.map(({ id }) => ({ permission: todoListReadPermission, resourceRef: id })),
items.map(({ id }) => ({
permission: todoListReadPermission,
resourceRef: id,
})),
);
const filteredItems = decisions.filter(
@@ -64,9 +67,16 @@ export const todoListReadPermission = createPermission({
/* highlight-add-end */
/* highlight-add-next-line */
export const todoListPermissions = [todoListCreatePermission, todoListUpdatePermission];
export const todoListPermissions = [
todoListCreatePermission,
todoListUpdatePermission,
];
/* highlight-add-next-line */
export const todoListPermissions = [todoListCreatePermission, todoListUpdatePermission, todoListReadPermission];
export const todoListPermissions = [
todoListCreatePermission,
todoListUpdatePermission,
todoListReadPermission,
];
```
## Using conditional policy decisions
@@ -36,7 +36,9 @@ import { todoListCreatePermission } from '@internal/plugin-todo-list-common';
function AddTodo({ onAdd }: { onAdd: (title: string) => any }) {
const title = useRef('');
/* highlight-add-next-line */
const { loading: loadingPermission, allowed: canAddTodo } = usePermission({ permission: todoListCreatePermission });
const { loading: loadingPermission, allowed: canAddTodo } = usePermission({
permission: todoListCreatePermission,
});
return (
<>
@@ -78,7 +80,6 @@ Here we are using the [`usePermission` hook](https://backstage.io/docs/reference
It's really that simple! Let's change our policy to test the disabled button:
```ts title="packages/backend/src/plugins/permission.ts"
if (isPermission(request.permission, todoListCreatePermission)) {
return {
/* highlight-remove-next-line */
@@ -117,10 +118,7 @@ export const TodoListPage = () => {
</Grid>
{/* highlight-remove-end */}
{/* highlight-add-start */}
<RequirePermission
permission={todoListCreatePermission}
errorPage={<></>}
>
<RequirePermission permission={todoListCreatePermission} errorPage={<></>}>
<Grid item>
<AddTodo onAdd={handleAdd} />
</Grid>
@@ -129,13 +127,15 @@ export const TodoListPage = () => {
<Grid item>
<TodoList key={key} onEdit={setEdit} />
</Grid>
</Grid>
}
</Grid>;
};
function AddTodo({ onAdd }: { onAdd: (title: string) => any }) {
const title = useRef('');
/* highlight-remove-next-line */
const { loading: loadingPermission, allowed: canAddTodo } = usePermission({ permission: todoListCreatePermission });
const { loading: loadingPermission, allowed: canAddTodo } = usePermission({
permission: todoListCreatePermission,
});
return (
<>
@@ -181,10 +181,14 @@ It's crucial that you update to `RequirePermission` at the same time as you upda
When migrating over to React Router v6 stable, you might also see browser console warnings for the `Navigate` component. This will need to be wrapped up in a `Route` component with the `Navigate` component in the `element` prop.
```tsx
{/* highlight-remove-next-line */}
<Navigate key="/" to="catalog" />
{/* highlight-add-next-line */}
<Route path="/" element={<Navigate to="catalog" />} />
{
/* highlight-remove-next-line */
}
<Navigate key="/" to="catalog" />;
{
/* highlight-add-next-line */
}
<Route path="/" element={<Navigate to="catalog" />} />;
```
### `NavLink`