Merge pull request #1043 from spotify/ndudnik/use-catalog-backend
Ndudnik/use catalog backend
This commit is contained in:
@@ -71,6 +71,13 @@
|
||||
"pathRewrite": {
|
||||
"^/circleci/api/": "/"
|
||||
}
|
||||
},
|
||||
"/catalog/api": {
|
||||
"target": "http://localhost:3003",
|
||||
"changeOrigin": true,
|
||||
"pathRewrite": {
|
||||
"^/catalog/api/": "/"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ import {
|
||||
import { techRadarApiRef, TechRadar } from '@backstage/plugin-tech-radar';
|
||||
|
||||
import { CircleCIApi, circleCIApiRef } from '@backstage/plugin-circleci';
|
||||
import { catalogApiRef, CatalogClient } from '@backstage/plugin-catalog';
|
||||
|
||||
const builder = ApiRegistry.builder();
|
||||
|
||||
@@ -71,4 +72,12 @@ builder.add(
|
||||
}),
|
||||
);
|
||||
|
||||
builder.add(
|
||||
catalogApiRef,
|
||||
new CatalogClient({
|
||||
apiOrigin: 'http://localhost:3000',
|
||||
basePath: '/catalog/api',
|
||||
}),
|
||||
);
|
||||
|
||||
export default builder.build() as ApiHolder;
|
||||
|
||||
@@ -19,18 +19,18 @@ import {
|
||||
getVoidLogger,
|
||||
NotFoundError,
|
||||
} from '@backstage/backend-common';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import Knex from 'knex';
|
||||
import path from 'path';
|
||||
import { Database } from './Database';
|
||||
import {
|
||||
AddDatabaseLocation,
|
||||
DbEntityRequest,
|
||||
DbEntityResponse,
|
||||
Database,
|
||||
AddDatabaseLocation,
|
||||
DbLocationsRow,
|
||||
DbLocationsRowWithStatus,
|
||||
DatabaseLocationUpdateLogStatus,
|
||||
} from './types';
|
||||
} from '.';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
|
||||
describe('Database', () => {
|
||||
let database: Knex;
|
||||
|
||||
@@ -15,9 +15,7 @@
|
||||
*/
|
||||
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import { Entity, EntityPolicy } from '@backstage/catalog-model';
|
||||
import Knex from 'knex';
|
||||
import { IngestionModel } from '../ingestion/types';
|
||||
import { Database } from './Database';
|
||||
import { DatabaseManager } from './DatabaseManager';
|
||||
import {
|
||||
@@ -25,6 +23,8 @@ import {
|
||||
DbLocationsRow,
|
||||
DbLocationsRowWithStatus,
|
||||
} from './types';
|
||||
import { EntityPolicy, Entity } from '@backstage/catalog-model';
|
||||
import { IngestionModel } from '..';
|
||||
|
||||
describe('DatabaseManager', () => {
|
||||
describe('refreshLocations', () => {
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
addLocationSchema,
|
||||
EntitiesCatalog,
|
||||
EntityFilters,
|
||||
LocationsCatalog
|
||||
LocationsCatalog,
|
||||
} from '../catalog';
|
||||
import { validateRequestBody } from './util';
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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 { CatalogApi } from './types';
|
||||
import { DescriptorEnvelope } from '../types';
|
||||
|
||||
export class CatalogClient implements CatalogApi {
|
||||
private apiOrigin: string;
|
||||
private basePath: string;
|
||||
constructor({
|
||||
apiOrigin,
|
||||
basePath,
|
||||
}: {
|
||||
apiOrigin: string;
|
||||
basePath: string;
|
||||
}) {
|
||||
this.apiOrigin = apiOrigin;
|
||||
this.basePath = basePath;
|
||||
}
|
||||
async getEntities(): Promise<DescriptorEnvelope[]> {
|
||||
const response = await fetch(`${this.apiOrigin}${this.basePath}/entities`);
|
||||
return await response.json();
|
||||
}
|
||||
async getEntityByName(name: string): Promise<DescriptorEnvelope> {
|
||||
const response = await fetch(
|
||||
`${this.apiOrigin}${this.basePath}/entities/by-name/Component/default/${name}`,
|
||||
);
|
||||
const entity = await response.json();
|
||||
if (entity) return entity;
|
||||
throw new Error(`'Entity not found: ${name}`);
|
||||
}
|
||||
}
|
||||
@@ -13,14 +13,16 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import * as React from 'react';
|
||||
import { ComponentFactory } from './component';
|
||||
import { MockComponentFactory } from './mock-factory';
|
||||
import { createApiRef } from '@backstage/core';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
|
||||
const componentFactory: ComponentFactory = MockComponentFactory;
|
||||
export const catalogApiRef = createApiRef<CatalogApi>({
|
||||
id: 'plugin.catalog.service',
|
||||
description:
|
||||
'Used by the Catalog plugin to make requests to accompanying backend',
|
||||
});
|
||||
|
||||
export const withMockStore = (Component: React.ElementType) => {
|
||||
return (props: any) => (
|
||||
<Component {...props} componentFactory={componentFactory} />
|
||||
);
|
||||
};
|
||||
export interface CatalogApi {
|
||||
getEntities(): Promise<Entity[]>;
|
||||
getEntityByName(name: string): Promise<Entity>;
|
||||
}
|
||||
@@ -17,14 +17,12 @@
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import CatalogPage from './CatalogPage';
|
||||
import { wrapInThemedTestApp } from '@backstage/test-utils';
|
||||
import { ComponentFactory } from '../../data/component';
|
||||
import { ApiRegistry, ApiProvider, errorApiRef } from '@backstage/core';
|
||||
import { wrapInTheme } from '@backstage/test-utils';
|
||||
import { catalogApiRef } from '../..';
|
||||
|
||||
const testComponentFactory: ComponentFactory = {
|
||||
getAllComponents: jest.fn(() => Promise.resolve([{ name: 'test' }])),
|
||||
getComponentByName: jest.fn(() => Promise.resolve({ name: 'test' })),
|
||||
removeComponentByName: jest.fn(() => Promise.resolve(true)),
|
||||
};
|
||||
const errorApi = { post: () => {} };
|
||||
const catalogApi = { getEntities: () => Promise.resolve([{ kind: '' }]) };
|
||||
|
||||
describe('CatalogPage', () => {
|
||||
// this test right now causes some red lines in the log output when running tests
|
||||
@@ -32,8 +30,15 @@ describe('CatalogPage', () => {
|
||||
// https://github.com/mbrn/material-table/issues/1293
|
||||
it('should render', async () => {
|
||||
const rendered = render(
|
||||
wrapInThemedTestApp(
|
||||
<CatalogPage componentFactory={testComponentFactory} />,
|
||||
wrapInTheme(
|
||||
<ApiProvider
|
||||
apis={ApiRegistry.from([
|
||||
[errorApiRef, errorApi],
|
||||
[catalogApiRef, catalogApi],
|
||||
])}
|
||||
>
|
||||
<CatalogPage />
|
||||
</ApiProvider>,
|
||||
),
|
||||
);
|
||||
expect(
|
||||
|
||||
@@ -24,9 +24,9 @@ import {
|
||||
SupportButton,
|
||||
Page,
|
||||
pageTheme,
|
||||
useApi,
|
||||
} from '@backstage/core';
|
||||
import { useAsync } from 'react-use';
|
||||
import { ComponentFactory } from '../../data/component';
|
||||
import CatalogTable from '../CatalogTable/CatalogTable';
|
||||
import {
|
||||
CatalogFilter,
|
||||
@@ -44,12 +44,12 @@ const useStyles = makeStyles(theme => ({
|
||||
},
|
||||
}));
|
||||
|
||||
type CatalogPageProps = {
|
||||
componentFactory: ComponentFactory;
|
||||
};
|
||||
import { catalogApiRef } from '../..';
|
||||
import { envelopeToComponent } from '../../data/utils';
|
||||
|
||||
const CatalogPage: FC<CatalogPageProps> = ({ componentFactory }) => {
|
||||
const { value, error, loading } = useAsync(componentFactory.getAllComponents);
|
||||
const CatalogPage: FC<{}> = () => {
|
||||
const catalogApi = useApi(catalogApiRef);
|
||||
const { value, error, loading } = useAsync(() => catalogApi.getEntities());
|
||||
const [selectedFilter, setSelectedFilter] = React.useState<CatalogFilterItem>(
|
||||
defaultFilter,
|
||||
);
|
||||
@@ -58,8 +58,8 @@ const CatalogPage: FC<CatalogPageProps> = ({ componentFactory }) => {
|
||||
selected => setSelectedFilter(selected),
|
||||
[],
|
||||
);
|
||||
|
||||
const styles = useStyles();
|
||||
|
||||
return (
|
||||
<Page theme={pageTheme.home}>
|
||||
<Header title="Service Catalog" subtitle="Keep track of your software">
|
||||
@@ -98,7 +98,7 @@ const CatalogPage: FC<CatalogPageProps> = ({ componentFactory }) => {
|
||||
</div>
|
||||
<CatalogTable
|
||||
titlePreamble={selectedFilter.label}
|
||||
components={value || []}
|
||||
components={(value && value.map(envelopeToComponent)) || []}
|
||||
loading={loading}
|
||||
error={error}
|
||||
/>
|
||||
|
||||
@@ -20,9 +20,9 @@ import CatalogTable from './CatalogTable';
|
||||
import { Component } from '../../data/component';
|
||||
|
||||
const components: Component[] = [
|
||||
{ name: 'component1' },
|
||||
{ name: 'component2' },
|
||||
{ name: 'component3' },
|
||||
{ name: 'component1', kind: 'Component', description: 'Placeholder' },
|
||||
{ name: 'component2', kind: 'Component', description: 'Placeholder' },
|
||||
{ name: 'component3', kind: 'Component', description: 'Placeholder' },
|
||||
];
|
||||
|
||||
describe('CatalogTable component', () => {
|
||||
|
||||
@@ -15,13 +15,7 @@
|
||||
*/
|
||||
import React, { FC } from 'react';
|
||||
import { Component } from '../../data/component';
|
||||
import {
|
||||
InfoCard,
|
||||
Progress,
|
||||
Table,
|
||||
TableColumn,
|
||||
StatusOK,
|
||||
} from '@backstage/core';
|
||||
import { InfoCard, Progress, Table, TableColumn } from '@backstage/core';
|
||||
import { Typography, Link } from '@material-ui/core';
|
||||
|
||||
const columns: TableColumn[] = [
|
||||
@@ -34,26 +28,8 @@ const columns: TableColumn[] = [
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'System',
|
||||
field: 'system',
|
||||
},
|
||||
{
|
||||
title: 'Owner',
|
||||
field: 'owner',
|
||||
},
|
||||
{
|
||||
title: 'Lifecycle',
|
||||
field: 'lifecycle',
|
||||
},
|
||||
{
|
||||
title: 'Status',
|
||||
field: 'status',
|
||||
render: (componentData: any) => (
|
||||
<>
|
||||
<StatusOK />
|
||||
{componentData.status || 'Up and running'}
|
||||
</>
|
||||
),
|
||||
title: 'Kind',
|
||||
field: 'kind',
|
||||
},
|
||||
{
|
||||
title: 'Description',
|
||||
|
||||
@@ -22,6 +22,8 @@ describe('ComponentMetadataCard component', () => {
|
||||
it('should display component name if provided', async () => {
|
||||
const testComponent: Component = {
|
||||
name: 'test',
|
||||
kind: 'Component',
|
||||
description: 'Placeholder',
|
||||
};
|
||||
const rendered = await render(
|
||||
<ComponentMetadataCard loading={false} component={testComponent} />,
|
||||
|
||||
@@ -17,7 +17,6 @@ import ComponentPage from './ComponentPage';
|
||||
import { render } from '@testing-library/react';
|
||||
import * as React from 'react';
|
||||
import { wrapInTheme } from '@backstage/test-utils';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { ApiProvider, ApiRegistry, errorApiRef } from '@backstage/core';
|
||||
|
||||
const getTestProps = (componentName: string) => {
|
||||
@@ -30,11 +29,6 @@ const getTestProps = (componentName: string) => {
|
||||
history: {
|
||||
push: jest.fn(),
|
||||
},
|
||||
componentFactory: {
|
||||
getAllComponents: jest.fn(() => Promise.resolve([{ name: 'test' }])),
|
||||
getComponentByName: jest.fn(() => Promise.resolve({ name: 'test' })),
|
||||
removeComponentByName: jest.fn(() => Promise.resolve(true)),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -52,19 +46,4 @@ describe('ComponentPage', () => {
|
||||
);
|
||||
expect(props.history.push).toHaveBeenCalledWith('/catalog');
|
||||
});
|
||||
it('should use factory to fetch component by name and display it', async () => {
|
||||
await act(async () => {
|
||||
const props = getTestProps('test');
|
||||
await render(
|
||||
wrapInTheme(
|
||||
<ApiProvider apis={ApiRegistry.from([[errorApiRef, errorApi]])}>
|
||||
<ComponentPage {...props} />
|
||||
</ApiProvider>,
|
||||
),
|
||||
);
|
||||
expect(props.componentFactory.getComponentByName).toHaveBeenCalledWith(
|
||||
'test',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
import React, { FC, useEffect, useState } from 'react';
|
||||
import { useAsync } from 'react-use';
|
||||
import { ComponentFactory } from '../../data/component';
|
||||
import ComponentMetadataCard from '../ComponentMetadataCard/ComponentMetadataCard';
|
||||
import {
|
||||
Content,
|
||||
@@ -30,11 +29,12 @@ import ComponentContextMenu from '../ComponentContextMenu/ComponentContextMenu';
|
||||
import ComponentRemovalDialog from '../ComponentRemovalDialog/ComponentRemovalDialog';
|
||||
import { SentryIssuesWidget } from '@backstage/plugin-sentry';
|
||||
import { Grid } from '@material-ui/core';
|
||||
import { catalogApiRef } from '../..';
|
||||
import { envelopeToComponent } from '../../data/utils';
|
||||
|
||||
const REDIRECT_DELAY = 1000;
|
||||
|
||||
type ComponentPageProps = {
|
||||
componentFactory: ComponentFactory;
|
||||
match: {
|
||||
params: {
|
||||
name: string;
|
||||
@@ -45,11 +45,7 @@ type ComponentPageProps = {
|
||||
};
|
||||
};
|
||||
|
||||
const ComponentPage: FC<ComponentPageProps> = ({
|
||||
match,
|
||||
history,
|
||||
componentFactory,
|
||||
}) => {
|
||||
const ComponentPage: FC<ComponentPageProps> = ({ match, history }) => {
|
||||
const [confirmationDialogOpen, setConfirmationDialogOpen] = useState(false);
|
||||
const [removingPending, setRemovingPending] = useState(false);
|
||||
const showRemovalDialog = () => setConfirmationDialogOpen(true);
|
||||
@@ -62,8 +58,9 @@ const ComponentPage: FC<ComponentPageProps> = ({
|
||||
return null;
|
||||
}
|
||||
|
||||
const catalogApi = useApi(catalogApiRef);
|
||||
const catalogRequest = useAsync(() =>
|
||||
componentFactory.getComponentByName(match.params.name),
|
||||
catalogApi.getEntityByName(match.params.name),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -78,18 +75,20 @@ const ComponentPage: FC<ComponentPageProps> = ({
|
||||
const removeComponent = async () => {
|
||||
setConfirmationDialogOpen(false);
|
||||
setRemovingPending(true);
|
||||
await componentFactory.removeComponentByName(componentName);
|
||||
// await componentFactory.removeComponentByName(componentName);
|
||||
history.push('/catalog');
|
||||
};
|
||||
|
||||
const component = envelopeToComponent(catalogRequest.value! ?? {});
|
||||
|
||||
return (
|
||||
<Page theme={pageTheme.service}>
|
||||
<Header title={catalogRequest?.value?.name || 'Catalog'} type="service">
|
||||
<Page theme={pageTheme.home}>
|
||||
<Header title={component.name || 'Catalog'}>
|
||||
<ComponentContextMenu onUnregisterComponent={showRemovalDialog} />
|
||||
</Header>
|
||||
{confirmationDialogOpen && catalogRequest.value && (
|
||||
<ComponentRemovalDialog
|
||||
component={catalogRequest.value}
|
||||
component={component}
|
||||
onClose={hideRemovalDialog}
|
||||
onConfirm={removeComponent}
|
||||
onCancel={hideRemovalDialog}
|
||||
@@ -100,7 +99,7 @@ const ComponentPage: FC<ComponentPageProps> = ({
|
||||
<Grid item>
|
||||
<ComponentMetadataCard
|
||||
loading={catalogRequest.loading || removingPending}
|
||||
component={catalogRequest.value}
|
||||
component={component}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
|
||||
@@ -15,10 +15,6 @@
|
||||
*/
|
||||
export type Component = {
|
||||
name: string;
|
||||
kind: string;
|
||||
description: string;
|
||||
};
|
||||
|
||||
export interface ComponentFactory {
|
||||
getAllComponents(): Promise<Component[]>;
|
||||
getComponentByName(name: string): Promise<Component | undefined>;
|
||||
removeComponentByName(name: string): Promise<boolean>;
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
[
|
||||
{
|
||||
"name": "example.com"
|
||||
},
|
||||
{
|
||||
"name": "subdomain.example.com"
|
||||
},
|
||||
{
|
||||
"name": "subdomain2.example.com"
|
||||
},
|
||||
{
|
||||
"name": "User data pipeline 1"
|
||||
},
|
||||
{
|
||||
"name": "User data pipeline 2"
|
||||
},
|
||||
{
|
||||
"name": "User data pipeline 3"
|
||||
},
|
||||
{
|
||||
"name": "Aggregation CRON job"
|
||||
},
|
||||
{
|
||||
"name": "Authentication service"
|
||||
},
|
||||
{
|
||||
"name": "Payments service"
|
||||
},
|
||||
{
|
||||
"name": "Backstage supervisor"
|
||||
},
|
||||
{
|
||||
"name": "Identity service"
|
||||
}
|
||||
]
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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 { Component, ComponentFactory } from './component';
|
||||
import mock from './mock-factory-data.json';
|
||||
|
||||
const ARTIFICIAL_TIMEOUT = 800;
|
||||
let inMemoryStore = [...mock];
|
||||
export const MockComponentFactory: ComponentFactory = {
|
||||
getAllComponents(): Promise<Component[]> {
|
||||
return new Promise(resolve =>
|
||||
setTimeout(() => resolve(inMemoryStore), ARTIFICIAL_TIMEOUT),
|
||||
);
|
||||
},
|
||||
getComponentByName(name: string): Promise<Component | undefined> {
|
||||
return new Promise((resolve, reject) =>
|
||||
setTimeout(() => {
|
||||
const mockComponent = inMemoryStore.find(
|
||||
component => component.name === name,
|
||||
);
|
||||
if (mockComponent) return resolve(mockComponent);
|
||||
return reject({ code: 'Component not found!' });
|
||||
}, ARTIFICIAL_TIMEOUT),
|
||||
);
|
||||
},
|
||||
removeComponentByName(name: string): Promise<boolean> {
|
||||
return new Promise(resolve =>
|
||||
setTimeout(() => {
|
||||
inMemoryStore = inMemoryStore.filter(
|
||||
component => component.name !== name,
|
||||
);
|
||||
resolve(true);
|
||||
}, ARTIFICIAL_TIMEOUT),
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -13,13 +13,13 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export const proxySettings = {
|
||||
'/circleci/api': {
|
||||
target: 'https://circleci.com/api/v1.1',
|
||||
changeOrigin: true,
|
||||
logLevel: 'debug',
|
||||
pathRewrite: {
|
||||
'^/circleci/api/': '/',
|
||||
},
|
||||
},
|
||||
};
|
||||
import { Component } from './component';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
|
||||
export function envelopeToComponent(envelope: Entity): Component {
|
||||
return {
|
||||
name: envelope.metadata?.name ?? '',
|
||||
kind: envelope.kind ?? 'unknown',
|
||||
description: envelope.metadata?.annotations?.description ?? 'placeholder',
|
||||
};
|
||||
}
|
||||
@@ -15,3 +15,6 @@
|
||||
*/
|
||||
|
||||
export { plugin } from './plugin';
|
||||
export * from './api/CatalogClient';
|
||||
export * from './api/types';
|
||||
export * from './types';
|
||||
|
||||
@@ -17,12 +17,11 @@
|
||||
import { createPlugin } from '@backstage/core';
|
||||
import CatalogPage from './components/CatalogPage';
|
||||
import ComponentPage from './components/ComponentPage/ComponentPage';
|
||||
import { withMockStore } from './data/with-mock-store';
|
||||
|
||||
export const plugin = createPlugin({
|
||||
id: 'catalog',
|
||||
register({ router }) {
|
||||
router.registerRoute('/', withMockStore(CatalogPage));
|
||||
router.registerRoute('/catalog/:name/', withMockStore(ComponentPage));
|
||||
router.registerRoute('/', CatalogPage);
|
||||
router.registerRoute('/catalog/:name/', ComponentPage);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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 interface ComponentDescriptorV1beta1 extends DescriptorEnvelope {
|
||||
spec: {
|
||||
type: string;
|
||||
};
|
||||
}
|
||||
|
||||
export type ComponentDescriptor = ComponentDescriptorV1beta1;
|
||||
|
||||
/**
|
||||
* Metadata fields common to all versions/kinds of entity.
|
||||
*
|
||||
* @see https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#objectmeta-v1-meta
|
||||
*/
|
||||
export type EntityMeta = {
|
||||
/**
|
||||
* A globally unique ID for the entity.
|
||||
*
|
||||
* This field can not be set by the user at creation time, and the server
|
||||
* will reject an attempt to do so. The field will be populated in read
|
||||
* operations. The field can (optionally) be specified when performing
|
||||
* update or delete operations, but the server is free to reject requests
|
||||
* that do so in such a way that it breaks semantics.
|
||||
*/
|
||||
uid?: string;
|
||||
|
||||
/**
|
||||
* An opaque string that changes for each update operation to any part of
|
||||
* the entity, including metadata.
|
||||
*
|
||||
* This field can not be set by the user at creation time, and the server
|
||||
* will reject an attempt to do so. The field will be populated in read
|
||||
* operations. The field can (optionally) be specified when performing
|
||||
* update or delete operations, and the server will then reject the
|
||||
* operation if it does not match the current stored value.
|
||||
*/
|
||||
etag?: string;
|
||||
|
||||
/**
|
||||
* A positive nonzero number that indicates the current generation of data
|
||||
* for this entity; the value is incremented each time the spec changes.
|
||||
*
|
||||
* This field can not be set by the user at creation time, and the server
|
||||
* will reject an attempt to do so. The field will be populated in read
|
||||
* operations.
|
||||
*/
|
||||
generation?: number;
|
||||
|
||||
/**
|
||||
* The name of the entity.
|
||||
*
|
||||
* Must be uniqe within the catalog at any given point in time, for any
|
||||
* given namespace, for any given kind.
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* The short description of the entity.
|
||||
*
|
||||
* A a human readable string.
|
||||
*/
|
||||
description: string;
|
||||
|
||||
/**
|
||||
* The namespace that the entity belongs to.
|
||||
*/
|
||||
namespace?: string;
|
||||
|
||||
/**
|
||||
* Key/value pairs of identifying information attached to the entity.
|
||||
*/
|
||||
labels?: Record<string, string>;
|
||||
|
||||
/**
|
||||
* Key/value pairs of non-identifying auxiliary information attached to the
|
||||
* entity.
|
||||
*/
|
||||
annotations?: Record<string, string>;
|
||||
};
|
||||
|
||||
/**
|
||||
* The format envelope that's common to all versions/kinds.
|
||||
*
|
||||
* @see https://kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/
|
||||
*/
|
||||
export type DescriptorEnvelope = {
|
||||
/**
|
||||
* The version of specification format for this particular entity that
|
||||
* this is written against.
|
||||
*/
|
||||
apiVersion: string;
|
||||
|
||||
/**
|
||||
* The high level entity type being described.
|
||||
*/
|
||||
kind: string;
|
||||
|
||||
/**
|
||||
* Optional metadata related to the entity.
|
||||
*/
|
||||
metadata: EntityMeta;
|
||||
|
||||
/**
|
||||
* The specification data describing the entity itself.
|
||||
*/
|
||||
spec?: object;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses and validates descriptors.
|
||||
*
|
||||
* The output must be validated and well formed.
|
||||
*/
|
||||
export type DescriptorParser = {
|
||||
/**
|
||||
* Parses and validates a single raw descriptor.
|
||||
*
|
||||
* @param descriptor A raw descriptor object
|
||||
* @returns A structure describing the parsed and validated descriptor
|
||||
* @throws An Error if the descriptor was malformed
|
||||
*/
|
||||
parse(descriptor: object): Promise<DescriptorEnvelope>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses and validates a single envelope into its materialized kind.
|
||||
*
|
||||
* These parsers may assume that the envelope is already validated and well
|
||||
* formed.
|
||||
*/
|
||||
export type KindParser = {
|
||||
/**
|
||||
* Try to parse an envelope into a materialized kind.
|
||||
*
|
||||
* @param envelope A valid descriptor envelope
|
||||
* @returns A materialized type, or undefined if the given version/kind is
|
||||
* not meant to be handled by this parser
|
||||
* @throws An Error if the type was handled and found to not be properly
|
||||
* formatted
|
||||
*/
|
||||
tryParse(
|
||||
envelope: DescriptorEnvelope,
|
||||
): Promise<DescriptorEnvelope | undefined>;
|
||||
};
|
||||
@@ -16,6 +16,5 @@
|
||||
|
||||
export { plugin } from './plugin';
|
||||
export * from './api';
|
||||
export * from './proxy';
|
||||
export * from './route-refs';
|
||||
export { CircleCIWidget } from './components/App';
|
||||
|
||||
@@ -4290,15 +4290,7 @@
|
||||
"@types/passport" "*"
|
||||
"@types/passport-oauth2" "*"
|
||||
|
||||
"@types/passport-oauth2-refresh@^1.1.1":
|
||||
version "1.1.1"
|
||||
resolved "https://registry.npmjs.org/@types/passport-oauth2-refresh/-/passport-oauth2-refresh-1.1.1.tgz#cbe466d4fcac36182fd75bf55279c0b1e953c382"
|
||||
integrity sha512-Tw0JvfDPv9asgFPACd9oOGCaD/0/Uyi+QF7fmrJC74cJKC6I8N8wwhJJHyfd1N2E/qaLgTh431lhOa9jicpNdg==
|
||||
dependencies:
|
||||
"@types/oauth" "*"
|
||||
"@types/passport-oauth2" "*"
|
||||
|
||||
"@types/passport-oauth2@*", "@types/passport-oauth2@^1.4.9":
|
||||
"@types/passport-oauth2@*":
|
||||
version "1.4.9"
|
||||
resolved "https://registry.npmjs.org/@types/passport-oauth2/-/passport-oauth2-1.4.9.tgz#134007c4b505a82548c9cb19094c5baeb2205c92"
|
||||
integrity sha512-QP0q+NVQOaIu2r0e10QWkiUA0Ya5mOBHRJN0UrI+LolMLOP1/VN4EVIpJ3xVwFo+xqNFRoFvFwJhBvKnk7kpUA==
|
||||
@@ -16110,12 +16102,7 @@ passport-google-oauth20@^2.0.0:
|
||||
dependencies:
|
||||
passport-oauth2 "1.x.x"
|
||||
|
||||
passport-oauth2-refresh@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/passport-oauth2-refresh/-/passport-oauth2-refresh-2.0.0.tgz#7b19c77ff3cc000819c69f6ad9e318450f57b85e"
|
||||
integrity sha512-yXvCB6nem/O+WThhiyI3TlPXpzSGY+9+hy9OTx9QF8e9GInplyRHxHaaOhFylKvnof9UmWHAufQFZk8cO1Fb2g==
|
||||
|
||||
passport-oauth2@1.x.x, passport-oauth2@^1.5.0:
|
||||
passport-oauth2@1.x.x:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.5.0.tgz#64babbb54ac46a4dcab35e7f266ed5294e3c4108"
|
||||
integrity sha512-kqBt6vR/5VlCK8iCx1/KpY42kQ+NEHZwsSyt4Y6STiNjU+wWICG1i8ucc1FapXDGO15C5O5VZz7+7vRzrDPXXQ==
|
||||
|
||||
Reference in New Issue
Block a user