First implementation of vault-plugin
Signed-off-by: ivgo <ivgo@spreadgroup.com>
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2020 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 { DiscoveryApi, createApiRef } from '@backstage/core-plugin-api';
|
||||
|
||||
export const vaultApiRef = createApiRef<VaultApi>({
|
||||
id: 'plugin.vault.service',
|
||||
});
|
||||
|
||||
export type VaultSecretList = {
|
||||
data: {
|
||||
keys: string[];
|
||||
};
|
||||
};
|
||||
|
||||
export type Secret = {
|
||||
name: string;
|
||||
showUrl: string;
|
||||
editUrl: string;
|
||||
};
|
||||
|
||||
export interface VaultApi {
|
||||
listSecrets(secretPath: string): Promise<Secret[]>;
|
||||
}
|
||||
|
||||
export class VaultClient implements VaultApi {
|
||||
private readonly discoveryApi: DiscoveryApi;
|
||||
|
||||
constructor({ discoveryApi }: { discoveryApi: DiscoveryApi }) {
|
||||
this.discoveryApi = discoveryApi;
|
||||
}
|
||||
|
||||
private async callApi<T>(
|
||||
path: string,
|
||||
query: { [key in string]: any },
|
||||
): Promise<T | undefined> {
|
||||
const apiUrl = `${await this.discoveryApi.getBaseUrl('vault')}`;
|
||||
const response = await fetch(
|
||||
`${apiUrl}/${path}?${new URLSearchParams(query).toString()}`,
|
||||
{
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
},
|
||||
},
|
||||
);
|
||||
if (response.status === 200) {
|
||||
return (await response.json()) as T;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async listSecrets(secretPath: string): Promise<Secret[]> {
|
||||
const result = await this.callApi<Secret[]>('v1/secrets', {
|
||||
path: secretPath,
|
||||
});
|
||||
if (!result) {
|
||||
return [];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2020 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 { useEntity } from '@backstage/plugin-catalog-react';
|
||||
import { makeStyles, Typography } from '@material-ui/core';
|
||||
import { isVaultAvailable } from '../../conditions';
|
||||
import { CodeSnippet, InfoCard, Button } from '@backstage/core-components';
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
import { VAULT_SECRET_PATH_ANNOTATION } from '../../constants';
|
||||
import { EntityVaultTable } from '../EntityVaultTable';
|
||||
|
||||
const COMPONENT_YAML = `metadata:
|
||||
name: example
|
||||
annotations:
|
||||
${VAULT_SECRET_PATH_ANNOTATION}: value`;
|
||||
|
||||
const useStyles = makeStyles<BackstageTheme>(
|
||||
theme => ({
|
||||
code: {
|
||||
borderRadius: 6,
|
||||
margin: `${theme.spacing(2)}px 0px`,
|
||||
background: theme.palette.type === 'dark' ? '#444' : '#fff',
|
||||
},
|
||||
}),
|
||||
{ name: 'BackstageMissingVaultAnnotation' },
|
||||
);
|
||||
|
||||
export const EntityVaultCard = () => {
|
||||
const { entity } = useEntity();
|
||||
const classes = useStyles();
|
||||
|
||||
if (isVaultAvailable(entity)) {
|
||||
return <EntityVaultTable entity={entity} />;
|
||||
}
|
||||
return (
|
||||
<InfoCard title="Vault">
|
||||
<>
|
||||
<Typography variant="body1">
|
||||
Add the annotation to your component YAML as shown in the highlighted
|
||||
example below:
|
||||
</Typography>
|
||||
<div className={classes.code}>
|
||||
<CodeSnippet
|
||||
text={COMPONENT_YAML}
|
||||
language="yaml"
|
||||
showLineNumbers
|
||||
highlightedNumbers={[3, 4]}
|
||||
customStyle={{ background: 'inherit', fontSize: '115%' }}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
color="primary"
|
||||
variant="contained"
|
||||
style={{ textDecoration: 'none' }}
|
||||
to="https://backstage.io/docs/features/software-catalog/well-known-annotations"
|
||||
>
|
||||
Read more
|
||||
</Button>
|
||||
</>
|
||||
</InfoCard>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright 2020 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 { EntityVaultCard } from './EntityVaultCard';
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright 2020 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 { Entity } from '@backstage/catalog-model';
|
||||
import { Table, TableColumn } from '@backstage/core-components';
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
import { Box, Typography } from '@material-ui/core';
|
||||
import Edit from '@material-ui/icons/Edit';
|
||||
import Visibility from '@material-ui/icons/Visibility';
|
||||
import Alert from '@material-ui/lab/Alert';
|
||||
import useAsync from 'react-use/lib/useAsync';
|
||||
import { Secret, vaultApiRef } from '../../api';
|
||||
import { VAULT_SECRET_PATH_ANNOTATION } from '../../constants';
|
||||
|
||||
export const vaultSecretPath = (entity: Entity) => {
|
||||
const secretPath =
|
||||
entity.metadata.annotations?.[VAULT_SECRET_PATH_ANNOTATION] ?? '';
|
||||
|
||||
return { secretPath };
|
||||
};
|
||||
|
||||
export const EntityVaultTable = ({ entity }: { entity: Entity }) => {
|
||||
const vaultApi = useApi(vaultApiRef);
|
||||
const { secretPath } = vaultSecretPath(entity);
|
||||
const { value, loading, error } = useAsync(async (): Promise<Secret[]> => {
|
||||
return vaultApi.listSecrets(secretPath);
|
||||
}, []);
|
||||
|
||||
const columns: TableColumn[] = [
|
||||
{ title: 'Secret', field: 'secret', highlight: true },
|
||||
{ title: 'View URL', field: 'view', width: '10%' },
|
||||
{ title: 'Edit URL', field: 'edit', width: '10%' },
|
||||
];
|
||||
|
||||
const data = (value || []).map(secret => {
|
||||
return {
|
||||
secret: secret.name,
|
||||
view: (
|
||||
<a
|
||||
aria-label="View"
|
||||
title={`View ${secret.name}`}
|
||||
href={secret.showUrl}
|
||||
>
|
||||
<Visibility style={{ fontSize: 16 }} />
|
||||
</a>
|
||||
),
|
||||
edit: (
|
||||
<a
|
||||
aria-label="Edit"
|
||||
title={`Edit ${secret.name}`}
|
||||
href={secret.editUrl}
|
||||
>
|
||||
<Edit style={{ fontSize: 16 }} />
|
||||
</a>
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
if (error) {
|
||||
return <Alert severity="error">{error.message}</Alert>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Table
|
||||
title="Vault"
|
||||
subtitle={`Secrets for ${entity.metadata.name} in ${secretPath}`}
|
||||
columns={columns}
|
||||
data={data}
|
||||
isLoading={loading}
|
||||
options={{
|
||||
padding: 'dense',
|
||||
pageSize: 10,
|
||||
emptyRowsWhenPaging: false,
|
||||
search: false,
|
||||
}}
|
||||
emptyContent={
|
||||
<Box style={{ textAlign: 'center', padding: '15px' }}>
|
||||
<Typography variant="body1">
|
||||
No secrets found for {entity.metadata.name} in {secretPath}
|
||||
</Typography>
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright 2020 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 { EntityVaultTable } from './EntityVaultTable';
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2020 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 { Entity } from '@backstage/catalog-model';
|
||||
import { VAULT_SECRET_PATH_ANNOTATION } from './constants';
|
||||
|
||||
export function isVaultAvailable(entity: Entity): boolean {
|
||||
return Boolean(
|
||||
entity.metadata.annotations?.hasOwnProperty(VAULT_SECRET_PATH_ANNOTATION),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright 2020 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 const VAULT_SECRET_PATH_ANNOTATION = 'vault.io/secrets-path';
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright 2020 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 { vaultPlugin, EntityVaultCard } from './plugin';
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2020 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 { vaultPlugin } from './plugin';
|
||||
|
||||
describe('vault', () => {
|
||||
it('should export plugin', () => {
|
||||
expect(vaultPlugin).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2020 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 {
|
||||
createApiFactory,
|
||||
createComponentExtension,
|
||||
createPlugin,
|
||||
DiscoveryApi,
|
||||
discoveryApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
|
||||
import { VaultClient, vaultApiRef } from './api';
|
||||
|
||||
export const vaultPlugin = createPlugin({
|
||||
id: 'vault',
|
||||
apis: [
|
||||
createApiFactory({
|
||||
api: vaultApiRef,
|
||||
deps: { discoveryApi: discoveryApiRef },
|
||||
factory: ({ discoveryApi }: { discoveryApi: DiscoveryApi }) =>
|
||||
new VaultClient({
|
||||
discoveryApi,
|
||||
}),
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
export const EntityVaultCard = vaultPlugin.provide(
|
||||
createComponentExtension({
|
||||
name: 'EntityVaultCard',
|
||||
component: {
|
||||
lazy: () =>
|
||||
import('./components/EntityVaultCard').then(m => m.EntityVaultCard),
|
||||
},
|
||||
}),
|
||||
);
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2020 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 { createRouteRef } from '@backstage/core-plugin-api';
|
||||
|
||||
export const rootRouteRef = createRouteRef({
|
||||
id: 'vault',
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2020 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 '@testing-library/jest-dom';
|
||||
import 'cross-fetch/polyfill';
|
||||
Reference in New Issue
Block a user