Add Octopus Deploy custom field extensions
Signed-off-by: Niek Rossen <niek.rossen@infosupport.com>
This commit is contained in:
@@ -41,7 +41,9 @@ octopusdeploy:
|
||||
webBaseUrl: "<your-octopus-web-url>"
|
||||
```
|
||||
|
||||
2. Add the following to `EntityPage.tsx` to display Octopus Releases
|
||||
#### Adding the Entities
|
||||
|
||||
1. Add the following to `EntityPage.tsx` to display Octopus Releases
|
||||
|
||||
```
|
||||
// In packages/app/src/components/catalog/EntityPage.tsx
|
||||
@@ -60,7 +62,7 @@ const cicdContent = (
|
||||
)
|
||||
```
|
||||
|
||||
3. Add `octopus.com/project-id` annotation in the catalog descriptor file.
|
||||
2. Add `octopus.com/project-id` annotation in the catalog descriptor file.
|
||||
|
||||
To obtain a projects ID you will have to query the Octopus API. In the future we'll add support for using a projects slug as well.
|
||||
|
||||
@@ -93,3 +95,39 @@ spec:
|
||||
You can get the ID of the space from the URL in the Octopus Deploy UI.
|
||||
|
||||
All set, you will be able to see the plugin in action!
|
||||
|
||||
### Adding Scaffolder field extensions
|
||||
|
||||
To add the Octopus Deploy custom fields extensions, add the following to your `App.tsx`:
|
||||
|
||||
```tsx
|
||||
// In packages/app/src/App.tsx
|
||||
import { OctopusDeployDropdownFieldExtension } from '@backstage/plugin-octopus-deploy';
|
||||
const routes = (
|
||||
<FlatRoutes>
|
||||
...
|
||||
<Route path="/create" element={<ScaffolderPage />}>
|
||||
<ScaffolderFieldExtensions>
|
||||
<OctopusDeployDropdownFieldExtension />
|
||||
</ScaffolderFieldExtensions>
|
||||
</Route>
|
||||
...
|
||||
</FlatRoutes>
|
||||
```
|
||||
|
||||
To use the Octopus Deploy custom field extensions, add the following to your `template.yaml`:
|
||||
|
||||
```yaml
|
||||
# In the template.yaml
|
||||
apiVersion: scaffolder.backstage.io/v1beta3
|
||||
kind: Template
|
||||
metadata:
|
||||
# ...
|
||||
parameters:
|
||||
- title: Octopus Project Group
|
||||
properties:
|
||||
projectName:
|
||||
title: Octopus Project Group
|
||||
type: string
|
||||
ui:field: OctopusDeployProjectGroupDropdown
|
||||
```
|
||||
|
||||
@@ -37,9 +37,12 @@
|
||||
"@backstage/core-components": "workspace:^",
|
||||
"@backstage/core-plugin-api": "workspace:^",
|
||||
"@backstage/plugin-catalog-react": "workspace:^",
|
||||
"@backstage/plugin-scaffolder": "workspace:^",
|
||||
"@backstage/plugin-scaffolder-react": "workspace:^",
|
||||
"@material-ui/core": "^4.9.13",
|
||||
"@types/react": "^16.13.1 || ^17.0.0 || ^18.0.0",
|
||||
"react-use": "^17.2.4"
|
||||
"react-use": "^17.2.4",
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "workspace:^",
|
||||
|
||||
@@ -62,6 +62,13 @@ export type OctopusProject = {
|
||||
Links: OctopusLinks;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export type OctopusProjectGroup = {
|
||||
Id: string;
|
||||
Name: string;
|
||||
Description: string;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export type OctopusPluginConfig = {
|
||||
WebUiBaseUrl: string;
|
||||
@@ -82,6 +89,7 @@ export interface OctopusDeployApi {
|
||||
releaseHistoryCount: number;
|
||||
}): Promise<OctopusProgression>;
|
||||
getProjectInfo(projectReference: ProjectReference): Promise<OctopusProject>;
|
||||
getProjectGroups(): Promise<OctopusProjectGroup[]>;
|
||||
getConfig(): Promise<OctopusPluginConfig>;
|
||||
}
|
||||
|
||||
@@ -156,6 +164,30 @@ export class OctopusDeployClient implements OctopusDeployApi {
|
||||
return responseJson;
|
||||
}
|
||||
|
||||
async getProjectGroups(): Promise<OctopusProjectGroup[]> {
|
||||
const proxyUrl = await this.discoveryApi.getBaseUrl('proxy');
|
||||
const url = `${proxyUrl}${this.proxyPathBase}/projectgroups/all`;
|
||||
const response = await this.fetchApi.fetch(url);
|
||||
|
||||
let responseJson;
|
||||
|
||||
try {
|
||||
responseJson = await response.json();
|
||||
} catch (e) {
|
||||
responseJson = { releases: [] };
|
||||
}
|
||||
|
||||
if (response.status !== 200) {
|
||||
throw new Error(
|
||||
`Error communicating with Octopus Deploy: ${
|
||||
responseJson?.error?.title || response.statusText
|
||||
}`,
|
||||
);
|
||||
}
|
||||
|
||||
return responseJson;
|
||||
}
|
||||
|
||||
async getConfig(): Promise<OctopusPluginConfig> {
|
||||
return {
|
||||
WebUiBaseUrl: this.configApi.getString(WEB_UI_BASE_URL_CONFIG_KEY),
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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 React from 'react';
|
||||
import FormControl from '@material-ui/core/FormControl';
|
||||
import { InputLabel, Input, FormHelperText } from '@material-ui/core';
|
||||
import { Select, SelectItem } from '@backstage/core-components';
|
||||
import { useProjectGroups } from '../../hooks/useProjectGroups';
|
||||
import { ProjectGroupDropdownProps } from './schema';
|
||||
|
||||
export const ProjectGroupDropdown = ({
|
||||
onChange,
|
||||
rawErrors,
|
||||
required,
|
||||
formData,
|
||||
}: ProjectGroupDropdownProps) => {
|
||||
const [selectedProjectGroup, setSelectedProjectGroup] =
|
||||
React.useState<string>('');
|
||||
const { projectGroups } = useProjectGroups();
|
||||
|
||||
const projectGroupItems: SelectItem[] = projectGroups
|
||||
? projectGroups.map(pg => ({ label: pg.Name, value: pg.Name }))
|
||||
: [];
|
||||
|
||||
const updateProjectGroup = (pg: string) => {
|
||||
setSelectedProjectGroup(pg);
|
||||
onChange(pg);
|
||||
};
|
||||
|
||||
return (
|
||||
<FormControl
|
||||
margin="normal"
|
||||
required={required}
|
||||
error={rawErrors?.length > 0 && !formData}
|
||||
>
|
||||
{projectGroups?.length ? (
|
||||
<Select
|
||||
label="Project Group"
|
||||
onChange={p => {
|
||||
updateProjectGroup(String(Array.isArray(p) ? p[0] : p));
|
||||
}}
|
||||
disabled={projectGroups.length === 1}
|
||||
selected={selectedProjectGroup}
|
||||
items={projectGroupItems}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<InputLabel>Project Group</InputLabel>
|
||||
<Input
|
||||
id="project-group-input"
|
||||
onChange={e => onChange(e.target.value)}
|
||||
value={formData}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<FormHelperText>
|
||||
The project group of the within Octopus Deploy.
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
@@ -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 { ProjectGroupDropdown } from './ProjectGroupDropdown';
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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 z from 'zod';
|
||||
import { makeFieldSchemaFromZod } from '@backstage/plugin-scaffolder';
|
||||
|
||||
const ProjectGroupDropdownFieldSchema = makeFieldSchemaFromZod(z.string());
|
||||
|
||||
export const ProjectGroupDropdownSchema =
|
||||
ProjectGroupDropdownFieldSchema.schema;
|
||||
|
||||
export type ProjectGroupDropdownProps =
|
||||
typeof ProjectGroupDropdownFieldSchema.type;
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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 { useApi } from '@backstage/core-plugin-api';
|
||||
import useAsync from 'react-use/lib/useAsync';
|
||||
import { octopusDeployApiRef, OctopusProjectGroup } from '../api';
|
||||
|
||||
export function useProjectGroups(): {
|
||||
projectGroups?: OctopusProjectGroup[];
|
||||
loading: boolean;
|
||||
error?: Error;
|
||||
} {
|
||||
const api = useApi(octopusDeployApiRef);
|
||||
|
||||
const { value, loading, error } = useAsync(() => {
|
||||
return api.getProjectGroups();
|
||||
}, [api]);
|
||||
|
||||
return {
|
||||
projectGroups: value,
|
||||
loading,
|
||||
error,
|
||||
};
|
||||
}
|
||||
@@ -27,7 +27,11 @@ import {
|
||||
configApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
|
||||
import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder-react';
|
||||
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { ProjectGroupDropdown } from './components/ScaffolderDropdown';
|
||||
import { ProjectGroupDropdownSchema } from './components/ScaffolderDropdown/schema';
|
||||
|
||||
/** @public */
|
||||
export const isOctopusDeployAvailable = (entity: Entity) =>
|
||||
@@ -61,3 +65,11 @@ export const EntityOctopusDeployContent = octopusDeployPlugin.provide(
|
||||
mountPoint: octopusDeployEntityContentRouteRef,
|
||||
}),
|
||||
);
|
||||
|
||||
export const OctopusDeployDropdownFieldExtension = octopusDeployPlugin.provide(
|
||||
createScaffolderFieldExtension({
|
||||
name: 'OctopusDeployProjectGroupDropdown',
|
||||
schema: ProjectGroupDropdownSchema,
|
||||
component: ProjectGroupDropdown,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -7961,12 +7961,15 @@ __metadata:
|
||||
"@backstage/core-plugin-api": "workspace:^"
|
||||
"@backstage/dev-utils": "workspace:^"
|
||||
"@backstage/plugin-catalog-react": "workspace:^"
|
||||
"@backstage/plugin-scaffolder": "workspace:^"
|
||||
"@backstage/plugin-scaffolder-react": "workspace:^"
|
||||
"@material-ui/core": ^4.9.13
|
||||
"@testing-library/dom": ^9.0.0
|
||||
"@testing-library/jest-dom": ^6.0.0
|
||||
"@testing-library/react": ^14.0.0
|
||||
"@types/react": ^16.13.1 || ^17.0.0 || ^18.0.0
|
||||
react-use: ^17.2.4
|
||||
zod: ^3.22.4
|
||||
peerDependencies:
|
||||
react: ^16.13.1 || ^17.0.0 || ^18.0.0
|
||||
react-dom: ^16.13.1 || ^17.0.0 || ^18.0.0
|
||||
|
||||
Reference in New Issue
Block a user