Skip to content

Commit

Permalink
feat: include scaffolding helpers
Browse files Browse the repository at this point in the history
  • Loading branch information
johanneswuerbach committed May 15, 2024
1 parent 5997e4a commit f3c0fd4
Show file tree
Hide file tree
Showing 13 changed files with 443 additions and 6 deletions.
6 changes: 6 additions & 0 deletions .changeset/neat-files-laugh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@humanitec/backstage-plugin-scaffolder-backend-module': minor
'@humanitec/backstage-plugin': minor
---

Include scaffolding helpers from [humanitec-architecture/backstage](https://github.com/humanitec-architecture/backstage)
1 change: 1 addition & 0 deletions packages/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"@backstage/plugin-org": "^0.6.24",
"@backstage/plugin-permission-react": "^0.4.22",
"@backstage/plugin-scaffolder": "^1.19.3",
"@backstage/plugin-scaffolder-react": "^1.8.5",
"@backstage/plugin-search": "^1.4.10",
"@backstage/plugin-search-react": "^1.7.10",
"@backstage/plugin-tech-radar": "^0.7.3",
Expand Down
8 changes: 7 additions & 1 deletion packages/app/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
catalogImportPlugin,
} from '@backstage/plugin-catalog-import';
import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder';
import { ScaffolderFieldExtensions } from '@backstage/plugin-scaffolder-react';
import { orgPlugin } from '@backstage/plugin-org';
import { SearchPage } from '@backstage/plugin-search';
import { TechRadarPage } from '@backstage/plugin-tech-radar';
Expand All @@ -37,6 +38,7 @@ import { AppRouter, FlatRoutes } from '@backstage/core-app-api';
import { CatalogGraphPage } from '@backstage/plugin-catalog-graph';
import { RequirePermission } from '@backstage/plugin-permission-react';
import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha';
import { ValidateHumanitecAppIDFieldExtension } from '@humanitec/backstage-plugin';

const app = createApp({
apis,
Expand Down Expand Up @@ -81,7 +83,11 @@ const routes = (
<ReportIssue />
</TechDocsAddons>
</Route>
<Route path="/create" element={<ScaffolderPage />} />
<Route path="/create" element={<ScaffolderPage />}>
<ScaffolderFieldExtensions>
<ValidateHumanitecAppIDFieldExtension />
</ScaffolderFieldExtensions>
</Route>
<Route path="/api-docs" element={<ApiExplorerPage />} />
<Route
path="/tech-radar"
Expand Down
4 changes: 4 additions & 0 deletions plugins/humanitec-backend-scaffolder-module/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ Add the action to your template,
+ input:
+ setupFile: humanitec-apps.yaml
+ appId: ${{ parameters.componentName }}

+ - name: Fetch configured humanitec.orgId
+ id: humanitec-environment
+ action: humanitec:get-environment
```

### setupFile parameter
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { createTemplateAction } from '@backstage/plugin-scaffolder-node';

interface EnvironmentAction {
orgId: string
cloudProvider?: string
}

export function createGetEnvironmentAction({ orgId, cloudProvider }: EnvironmentAction) {
return createTemplateAction({
id: 'humanitec:get-environment',
schema: {
output: {
required: [
'orgId',
],
properties: {
orgId: {
type: 'string'
},
cloudProvider: {
type: 'string'
},
githubOIDCCustomization: {
type: 'object'
}
}
}
},
handler: async (ctx) => {
ctx.output('orgId', orgId);
ctx.output('cloudProvider', cloudProvider);

let githubOIDCCustomization
if (cloudProvider === 'azure') {
githubOIDCCustomization = {
"useDefault": false,
"includeClaimKeys": ["repository_owner"]
}
}
ctx.output('githubOIDCCustomization', githubOIDCCustomization);
},
});
}
5 changes: 5 additions & 0 deletions plugins/humanitec-backend-scaffolder-module/src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
} from '@backstage/backend-plugin-api';
import { scaffolderActionsExtensionPoint } from '@backstage/plugin-scaffolder-node/alpha';
import { createHumanitecApp } from './actions/create-app';
import { createGetEnvironmentAction } from './actions/get-environment';

/**
* @public
Expand All @@ -39,6 +40,10 @@ export const humanitecModule = createBackendModule({
orgId: config.getString('humanitec.orgId'),
token: config.getString('humanitec.token')
}),
createGetEnvironmentAction({
orgId: config.getString('humanitec.orgId'),
cloudProvider: config.getOptionalString('humanitec.cloudProvider'),
}),
);
},
});
Expand Down
24 changes: 24 additions & 0 deletions plugins/humanitec/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ This plugin requires `@humanitec/backstage-plugin-backend` because it connects t

## Installation

### Entity card

First, install the plugin to your backstage app:

```bash
Expand Down Expand Up @@ -58,3 +60,25 @@ humanitec:
```

When you start your backstage app be sure to pass in `HUMANITEC_TOKEN` that you must generate from your Humanitec dashboard.

### Scaffolding field extension

For an enhanced scaffolding experience (`./packages/app/src/App.tsx`) add the `ValidateHumanitecAppIDFieldExtension`, which validates that the provided input is a valid Humanitec Application ID.

```diff
+ import { ValidateHumanitecAppIDFieldExtension } from '@humanitec/backstage-plugin';
...
const routes = (
<FlatRoutes>
...
<Route path="/create" element={<ScaffolderPage />}>
<ScaffolderFieldExtensions>
+ <ValidateHumanitecAppIDFieldExtension />
</ScaffolderFieldExtensions>
</Route>
...
</FlatRoutes>
)
```

Once this has been added, `ui:field: ValidateHumanitecAppID` is available inside scaffolding templates.
3 changes: 3 additions & 0 deletions plugins/humanitec/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,13 @@
"@backstage/core-components": "^0.14.4",
"@backstage/core-plugin-api": "^1.9.2",
"@backstage/plugin-catalog-react": "^1.11.3",
"@backstage/plugin-scaffolder": "^1.19.3",
"@backstage/plugin-scaffolder-react": "^1.8.5",
"@backstage/theme": "^0.5.3",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.61",
"@rjsf/utils": "^5.18.3",
"@types/lodash.get": "^4.4.7",
"event-source-polyfill": "^1.0.31",
"lodash.get": "^4.4.2",
Expand Down
3 changes: 2 additions & 1 deletion plugins/humanitec/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { humanitecPlugin } from './plugin';
export { HumanitecCardComponent } from './components/HumanitecCardComponent';
export { HumanitecCardComponent } from './components/HumanitecCardComponent';
export { ValidateHumanitecAppIDFieldExtension } from './scaffolder/ValidateHumanitecAppID';
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import React from 'react';
import { FieldExtensionComponentProps } from '@backstage/plugin-scaffolder-react';
import type { FieldValidation } from '@rjsf/utils';
import FormControl from '@material-ui/core/FormControl';
import {
InputLabel,
Input,
FormHelperText,
List,
ListItem,
Link,
} from '@material-ui/core';

/*
Field rendered in the form
*/

export const ValidateHumanitecAppID = ({
onChange,
rawErrors,
required,
formData,
}: FieldExtensionComponentProps<string>) => {
return (
<FormControl
margin="normal"
required={required}
error={rawErrors && rawErrors?.length > 0 && !formData}
>
<InputLabel htmlFor="validateName">Name</InputLabel>
<Input
id="validateName"
aria-describedby="entityName"
onChange={e => onChange(e.target?.value)}
/>
<FormHelperText id="entityName">
<List>
<ListItem>
Must only contain alphanumeric characters, numbers, or dashes.
</ListItem>
<ListItem>Must be lowercase.</ListItem>
<ListItem>Must be at least 3 characters long.</ListItem>
<ListItem>Must be at most 50 characters long.</ListItem>
<ListItem>Cannot start or end with a dash.</ListItem>
<ListItem>
See&nbsp;
<Link
target="_blank"
rel="noopener"
href="https://developer.humanitec.com/platform-orchestrator/reference/constraints/#application-names"
>
https://developer.humanitec.com/platform-orchestrator/reference/constraints/#application-names
</Link>
&nbsp;for more details.
</ListItem>
</List>
</FormHelperText>
</FormControl>
);
};

/*
This is a validation function that will run when the form is submitted.
You will get the value from the `onChange` handler before as the value here to make sure that the types are aligned\
*/

export const validateHumanitecAppIDValidation = (
value: string,
validation: FieldValidation,
) => {
const validID = /^[a-z0-9](?:-?[a-z0-9]+)+$/g.test(value);

if (validID === false) {
validation.addError(`Invalid Application ID`);
}

if (value.length > 50) {
validation.addError('Application IDs can only be up to 50 characters.');
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { scaffolderPlugin } from '@backstage/plugin-scaffolder';
import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder-react';
import {
ValidateHumanitecAppID,
validateHumanitecAppIDValidation,
} from './ValidateHumanitecAppIDExtension';

export const ValidateHumanitecAppIDFieldExtension = scaffolderPlugin.provide(
createScaffolderFieldExtension({
name: 'ValidateHumanitecAppID',
component: ValidateHumanitecAppID,
validation: validateHumanitecAppIDValidation,
}),
);
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { ValidateHumanitecAppIDFieldExtension } from './extensions';
Loading

0 comments on commit f3c0fd4

Please sign in to comment.