Merge remote-tracking branch 'origin/master' into erikengervall/plugin-release-manager-as-a-service
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend': patch
|
||||
---
|
||||
|
||||
Forward user token to scaffolder task for subsequent api requests
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/core': patch
|
||||
'@backstage/plugin-scaffolder': patch
|
||||
---
|
||||
|
||||
Adding Headings for Accessibility on the Scaffolder Plugin
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog': patch
|
||||
---
|
||||
|
||||
SystemDiagramCard UI improvements
|
||||
@@ -78,6 +78,7 @@ cluster. Valid values are:
|
||||
| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `serviceAccount` | This will use a Kubernetes [service account](https://kubernetes.io/docs/reference/access-authn-authz/service-accounts-admin/) to access the Kubernetes API. When this is used the `serviceAccountToken` field should also be set. |
|
||||
| `google` | This will use a user's Google auth token from the [Google auth plugin](https://backstage.io/docs/auth/) to access the Kubernetes API. |
|
||||
| `aws` | This will use AWS credentials to access resources in EKS clusters |
|
||||
|
||||
##### `clusters.\*.skipTLSVerify`
|
||||
|
||||
|
||||
@@ -135,7 +135,21 @@ techdocs:
|
||||
bucketName: 'name-of-techdocs-storage-bucket'
|
||||
```
|
||||
|
||||
**3a. (Recommended) Setup authentication the AWS way, using environment
|
||||
**3. Create minimal AWS IAM policies to manage TechDocs**
|
||||
|
||||
To _write_ TechDocs into the S3 bucket the IAM policy needs to have at a minimum
|
||||
permissions to:
|
||||
|
||||
- `s3:ListBucket` to retrieve bucket metadata
|
||||
- `s3:PutObject` to upload files to the bucket
|
||||
|
||||
To _read_ TechDocs from the S3 bucket the IAM policy needs to have at a minimum
|
||||
permissions to:
|
||||
|
||||
- `s3:ListBucket` - To retrieve bucket metadata
|
||||
- `s3:GetObject` - To retrieve files from the bucket
|
||||
|
||||
**4a. (Recommended) Setup authentication the AWS way, using environment
|
||||
variables**
|
||||
|
||||
You should follow the
|
||||
@@ -166,7 +180,7 @@ more in
|
||||
The AWS Region of the bucket is optional since TechDocs uses AWS SDK V2 and not
|
||||
V3.
|
||||
|
||||
**3b. Authentication using app-config.yaml**
|
||||
**4b. Authentication using app-config.yaml**
|
||||
|
||||
AWS credentials and region can be provided to the AWS SDK via `app-config.yaml`.
|
||||
If the configs below are present, they will be used over existing `AWS_*`
|
||||
@@ -187,7 +201,7 @@ techdocs:
|
||||
Refer to the
|
||||
[official AWS documentation for obtaining the credentials](https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/getting-your-credentials.html).
|
||||
|
||||
**3c. Authentication using an assumed role** Users with multiple AWS accounts
|
||||
**4c. Authentication using an assumed role** Users with multiple AWS accounts
|
||||
may want to use a role for S3 storage that is in a different AWS account. Using
|
||||
the `roleArn` parameter as seen below, you can instruct the TechDocs publisher
|
||||
to assume a role before accessing S3.
|
||||
@@ -207,7 +221,7 @@ Note: Assuming a role requires that primary credentials are already configured
|
||||
at `AWS.config.credentials`. Read more about
|
||||
[assuming roles in AWS](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html).
|
||||
|
||||
**4. That's it!**
|
||||
**5. That's it!**
|
||||
|
||||
Your Backstage app is now ready to use AWS S3 for TechDocs, to store and read
|
||||
the static generated documentation files. When you start the backend of the app,
|
||||
|
||||
@@ -203,6 +203,7 @@
|
||||
],
|
||||
"Deployment": [
|
||||
"deployment/index",
|
||||
"deployment/docker",
|
||||
"deployment/k8s",
|
||||
"deployment/helm",
|
||||
"deployment/heroku"
|
||||
|
||||
@@ -306,9 +306,6 @@ export class PrivateAppImpl implements BackstageApp {
|
||||
this.verifyPlugins(this.plugins);
|
||||
|
||||
// Initialize APIs once all plugins are available
|
||||
if (this.apiHolder) {
|
||||
throw new Error('Plugin holder was initialized too soon');
|
||||
}
|
||||
this.getApiHolder();
|
||||
|
||||
return result;
|
||||
|
||||
@@ -25,7 +25,7 @@ type Props = CSS.Properties & {
|
||||
|
||||
const useStyles = makeStyles({
|
||||
alpha: {
|
||||
color: '#d00150',
|
||||
color: '#ffffff',
|
||||
fontFamily: 'serif',
|
||||
fontWeight: 'normal',
|
||||
fontStyle: 'italic',
|
||||
|
||||
@@ -66,7 +66,12 @@ const DefaultTitle = ({
|
||||
title = 'Unknown page',
|
||||
className,
|
||||
}: DefaultTitleProps) => (
|
||||
<Typography variant="h4" className={className} data-testid="header-title">
|
||||
<Typography
|
||||
variant="h4"
|
||||
component="h2"
|
||||
className={className}
|
||||
data-testid="header-title"
|
||||
>
|
||||
{title}
|
||||
</Typography>
|
||||
);
|
||||
|
||||
@@ -140,7 +140,7 @@ const TypeFragment = ({
|
||||
|
||||
const TitleFragment = ({ pageTitle, classes, tooltip }: TitleFragmentProps) => {
|
||||
const FinalTitle = (
|
||||
<Typography className={classes.title} variant="h4">
|
||||
<Typography className={classes.title} variant="h1">
|
||||
{pageTitle}
|
||||
</Typography>
|
||||
);
|
||||
@@ -166,7 +166,11 @@ const SubtitleFragment = ({ classes, subtitle }: SubtitleFragmentProps) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Typography className={classes.subtitle} variant="subtitle2">
|
||||
<Typography
|
||||
className={classes.subtitle}
|
||||
variant="subtitle2"
|
||||
component="span"
|
||||
>
|
||||
{subtitle}
|
||||
</Typography>
|
||||
);
|
||||
|
||||
@@ -125,6 +125,7 @@ type Props = {
|
||||
actionsTopRight?: ReactNode;
|
||||
className?: string;
|
||||
noPadding?: boolean;
|
||||
titleTypographyProps?: object;
|
||||
};
|
||||
|
||||
export const InfoCard = ({
|
||||
@@ -143,6 +144,7 @@ export const InfoCard = ({
|
||||
actionsTopRight,
|
||||
className,
|
||||
noPadding,
|
||||
titleTypographyProps,
|
||||
}: Props): JSX.Element => {
|
||||
const classes = useStyles();
|
||||
/**
|
||||
@@ -183,6 +185,7 @@ export const InfoCard = ({
|
||||
title={title}
|
||||
subheader={subheader}
|
||||
style={{ ...headerStyle }}
|
||||
titleTypographyProps={titleTypographyProps}
|
||||
{...headerProps}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -77,8 +77,16 @@ export const ItemCardHeader = (props: ItemCardHeaderProps) => {
|
||||
const classes = useStyles(props);
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
{subtitle && <Typography variant="subtitle2">{subtitle}</Typography>}
|
||||
{title && <Typography variant="h6">{title}</Typography>}
|
||||
{subtitle && (
|
||||
<Typography variant="subtitle2" component="h3">
|
||||
{subtitle}
|
||||
</Typography>
|
||||
)}
|
||||
{title && (
|
||||
<Typography variant="h6" component="h4">
|
||||
{title}
|
||||
</Typography>
|
||||
)}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
catalogApiRef,
|
||||
CatalogApi,
|
||||
EntityProvider,
|
||||
entityRouteRef,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { Entity, RELATION_PART_OF } from '@backstage/catalog-model';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
@@ -59,13 +60,16 @@ describe('<SystemDiagramCard />', () => {
|
||||
<SystemDiagramCard />
|
||||
</EntityProvider>
|
||||
</ApiProvider>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/catalog/:namespace/:kind/:name': entityRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(queryByText(/System Diagram/)).toBeInTheDocument();
|
||||
expect(queryByText(/system:my-namespace2\/my-system2/)).toBeInTheDocument();
|
||||
expect(
|
||||
queryByText(/component:my-namespace\/my-entity/),
|
||||
).not.toBeInTheDocument();
|
||||
expect(queryByText(/my-namespace2\/my-system2/)).toBeInTheDocument();
|
||||
expect(queryByText(/my-namespace\/my-entity/)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows related systems', async () => {
|
||||
@@ -115,10 +119,15 @@ describe('<SystemDiagramCard />', () => {
|
||||
<SystemDiagramCard />
|
||||
</EntityProvider>
|
||||
</ApiProvider>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/catalog/:namespace/:kind/:name': entityRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(getByText('System Diagram')).toBeInTheDocument();
|
||||
expect(getByText('system:my-namespace/my-system')).toBeInTheDocument();
|
||||
expect(getByText('component:my-namespace/my-entity')).toBeInTheDocument();
|
||||
expect(getByText('my-namespace/my-system')).toBeInTheDocument();
|
||||
expect(getByText('my-namespace/my-entity')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,10 +19,13 @@ import {
|
||||
RELATION_DEPENDS_ON,
|
||||
RELATION_PROVIDES_API,
|
||||
RELATION_PART_OF,
|
||||
serializeEntityRef,
|
||||
stringifyEntityRef,
|
||||
ENTITY_DEFAULT_NAMESPACE,
|
||||
parseEntityRef,
|
||||
} from '@backstage/catalog-model';
|
||||
import {
|
||||
catalogApiRef,
|
||||
entityRouteRef,
|
||||
getEntityRelations,
|
||||
useEntity,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
@@ -33,26 +36,107 @@ import {
|
||||
Progress,
|
||||
useApi,
|
||||
ResponseErrorPanel,
|
||||
Link,
|
||||
useRouteRef,
|
||||
} from '@backstage/core';
|
||||
import { Box, Typography } from '@material-ui/core';
|
||||
import { Box, makeStyles, Typography } from '@material-ui/core';
|
||||
import ZoomOutMap from '@material-ui/icons/ZoomOutMap';
|
||||
import React from 'react';
|
||||
import { useAsync } from 'react-use';
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
|
||||
function simplifiedEntityName(
|
||||
const useStyles = makeStyles((theme: BackstageTheme) => ({
|
||||
domainNode: {
|
||||
fill: theme.palette.primary.main,
|
||||
stroke: theme.palette.border,
|
||||
},
|
||||
systemNode: {
|
||||
fill: 'coral',
|
||||
stroke: theme.palette.border,
|
||||
},
|
||||
componentNode: {
|
||||
fill: 'yellowgreen',
|
||||
stroke: theme.palette.border,
|
||||
},
|
||||
apiNode: {
|
||||
fill: theme.palette.gold,
|
||||
stroke: theme.palette.border,
|
||||
},
|
||||
resourceNode: {
|
||||
fill: 'grey',
|
||||
stroke: theme.palette.border,
|
||||
},
|
||||
}));
|
||||
|
||||
// Simplifies the diagram output by hiding the default namespace and kind
|
||||
function readableEntityName(
|
||||
ref:
|
||||
| Entity
|
||||
| {
|
||||
kind?: string;
|
||||
kind: string;
|
||||
namespace?: string;
|
||||
name: string;
|
||||
},
|
||||
): string {
|
||||
// Simplify the diagram output by hiding only the default namespace
|
||||
return serializeEntityRef(ref)
|
||||
.toString()
|
||||
return stringifyEntityRef(ref)
|
||||
.toLocaleLowerCase('en-US')
|
||||
.replace(':default/', ':');
|
||||
.replace(`:${ENTITY_DEFAULT_NAMESPACE}/`, ':')
|
||||
.split(':')[1];
|
||||
}
|
||||
|
||||
function RenderNode(props: DependencyGraphTypes.RenderNodeProps<any>) {
|
||||
const classes = useStyles();
|
||||
const catalogEntityRoute = useRouteRef(entityRouteRef);
|
||||
const kind = props.node.kind || 'Component';
|
||||
const ref = parseEntityRef(props.node.id);
|
||||
let nodeClass = classes.componentNode;
|
||||
|
||||
switch (kind) {
|
||||
case 'Domain':
|
||||
nodeClass = classes.domainNode;
|
||||
break;
|
||||
case 'System':
|
||||
nodeClass = classes.systemNode;
|
||||
break;
|
||||
case 'Component':
|
||||
nodeClass = classes.componentNode;
|
||||
break;
|
||||
case 'API':
|
||||
nodeClass = classes.apiNode;
|
||||
break;
|
||||
case 'Resource':
|
||||
nodeClass = classes.resourceNode;
|
||||
break;
|
||||
default:
|
||||
nodeClass = classes.componentNode;
|
||||
}
|
||||
|
||||
return (
|
||||
<g>
|
||||
<rect width={200} height={100} rx={20} className={nodeClass} />
|
||||
<Link
|
||||
to={catalogEntityRoute({
|
||||
kind: kind,
|
||||
namespace: ref.namespace,
|
||||
name: ref.name,
|
||||
})}
|
||||
>
|
||||
<text
|
||||
x={100}
|
||||
y={45}
|
||||
textAnchor="middle"
|
||||
alignmentBaseline="baseline"
|
||||
style={{ fontWeight: 'bold' }}
|
||||
>
|
||||
{props.node.name}
|
||||
</text>
|
||||
</Link>
|
||||
|
||||
<text x={100} y={65} textAnchor="middle" alignmentBaseline="hanging">
|
||||
{props.node.kind}
|
||||
</text>
|
||||
</g>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,8 +146,8 @@ function simplifiedEntityName(
|
||||
export function SystemDiagramCard() {
|
||||
const { entity } = useEntity();
|
||||
const currentSystemName = entity.metadata.name;
|
||||
const currentSystemNode = simplifiedEntityName(entity);
|
||||
const systemNodes = new Array<{ id: string }>();
|
||||
const currentSystemNode = stringifyEntityRef(entity);
|
||||
const systemNodes = new Array<{ id: string; kind: string; name: string }>();
|
||||
const systemEdges = new Array<{ from: string; to: string; label: string }>();
|
||||
|
||||
const catalogApi = useApi(catalogApiRef);
|
||||
@@ -71,7 +155,10 @@ export function SystemDiagramCard() {
|
||||
return catalogApi.getEntities({
|
||||
filter: {
|
||||
kind: ['Component', 'API', 'Resource', 'System', 'Domain'],
|
||||
'spec.system': currentSystemName,
|
||||
'spec.system': [
|
||||
currentSystemName,
|
||||
`${ENTITY_DEFAULT_NAMESPACE}/${currentSystemName}`,
|
||||
],
|
||||
},
|
||||
});
|
||||
}, [catalogApi, currentSystemName]);
|
||||
@@ -79,22 +166,26 @@ export function SystemDiagramCard() {
|
||||
// pick out the system itself
|
||||
systemNodes.push({
|
||||
id: currentSystemNode,
|
||||
kind: 'System',
|
||||
name: readableEntityName(entity),
|
||||
});
|
||||
|
||||
// check if the system has an assigned domain
|
||||
// even if the domain object doesn't exist in the catalog, display it in the map
|
||||
const catalogItemDomain = getEntityRelations(entity, RELATION_PART_OF, {
|
||||
kind: 'domain',
|
||||
kind: 'Domain',
|
||||
});
|
||||
catalogItemDomain.forEach(foundDomain =>
|
||||
systemNodes.push({
|
||||
id: simplifiedEntityName(foundDomain),
|
||||
id: stringifyEntityRef(foundDomain),
|
||||
kind: foundDomain.kind,
|
||||
name: readableEntityName(foundDomain),
|
||||
}),
|
||||
);
|
||||
catalogItemDomain.forEach(foundDomain =>
|
||||
systemEdges.push({
|
||||
from: currentSystemNode,
|
||||
to: simplifiedEntityName(foundDomain),
|
||||
to: stringifyEntityRef(foundDomain),
|
||||
label: 'part of',
|
||||
}),
|
||||
);
|
||||
@@ -102,7 +193,9 @@ export function SystemDiagramCard() {
|
||||
if (catalogResponse && catalogResponse.items) {
|
||||
for (const catalogItem of catalogResponse.items) {
|
||||
systemNodes.push({
|
||||
id: simplifiedEntityName(catalogItem),
|
||||
id: stringifyEntityRef(catalogItem),
|
||||
kind: catalogItem.kind,
|
||||
name: readableEntityName(catalogItem),
|
||||
});
|
||||
|
||||
// Check relations of the entity assigned to this system to see
|
||||
@@ -115,8 +208,8 @@ export function SystemDiagramCard() {
|
||||
);
|
||||
catalogItemRelations_partOf.forEach(foundRelation =>
|
||||
systemEdges.push({
|
||||
from: simplifiedEntityName(catalogItem),
|
||||
to: simplifiedEntityName(foundRelation),
|
||||
from: stringifyEntityRef(catalogItem),
|
||||
to: stringifyEntityRef(foundRelation),
|
||||
label: 'part of',
|
||||
}),
|
||||
);
|
||||
@@ -127,9 +220,9 @@ export function SystemDiagramCard() {
|
||||
);
|
||||
catalogItemRelations_providesApi.forEach(foundRelation =>
|
||||
systemEdges.push({
|
||||
from: simplifiedEntityName(catalogItem),
|
||||
to: simplifiedEntityName(foundRelation),
|
||||
label: 'provides API',
|
||||
from: stringifyEntityRef(catalogItem),
|
||||
to: stringifyEntityRef(foundRelation),
|
||||
label: 'provides',
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -139,8 +232,8 @@ export function SystemDiagramCard() {
|
||||
);
|
||||
catalogItemRelations_dependsOn.forEach(foundRelation =>
|
||||
systemEdges.push({
|
||||
from: simplifiedEntityName(catalogItem),
|
||||
to: simplifiedEntityName(foundRelation),
|
||||
from: stringifyEntityRef(catalogItem),
|
||||
to: stringifyEntityRef(foundRelation),
|
||||
label: 'depends on',
|
||||
}),
|
||||
);
|
||||
@@ -160,6 +253,7 @@ export function SystemDiagramCard() {
|
||||
edges={systemEdges}
|
||||
nodeMargin={10}
|
||||
direction={DependencyGraphTypes.Direction.BOTTOM_TOP}
|
||||
renderNode={RenderNode}
|
||||
/>
|
||||
<Box m={1} />
|
||||
<Typography
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.up = async function up(knex) {
|
||||
await knex.schema.alterTable('tasks', table => {
|
||||
table
|
||||
.text('secrets')
|
||||
.nullable()
|
||||
.comment('JSON encoded secrets to authenticate tasks with');
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.down = async function down(knex) {
|
||||
await knex.schema.alterTable('tasks', table => {
|
||||
table.dropColumn('secrets');
|
||||
});
|
||||
};
|
||||
@@ -84,10 +84,13 @@ describe('catalog:register', () => {
|
||||
catalogInfoUrl: 'http://foo/var',
|
||||
},
|
||||
});
|
||||
expect(addLocation).toBeCalledWith({
|
||||
type: 'url',
|
||||
target: 'http://foo/var',
|
||||
});
|
||||
expect(addLocation).toBeCalledWith(
|
||||
{
|
||||
type: 'url',
|
||||
target: 'http://foo/var',
|
||||
},
|
||||
{},
|
||||
);
|
||||
|
||||
expect(mockContext.output).toBeCalledWith(
|
||||
'entityRef',
|
||||
|
||||
@@ -95,10 +95,13 @@ export function createCatalogRegisterAction(options: {
|
||||
|
||||
ctx.logger.info(`Registering ${catalogInfoUrl} in the catalog`);
|
||||
|
||||
const result = await catalogClient.addLocation({
|
||||
type: 'url',
|
||||
target: catalogInfoUrl,
|
||||
});
|
||||
const result = await catalogClient.addLocation(
|
||||
{
|
||||
type: 'url',
|
||||
target: catalogInfoUrl,
|
||||
},
|
||||
ctx.token ? { token: ctx.token } : {},
|
||||
);
|
||||
if (result.entities.length >= 1) {
|
||||
const { kind, name, namespace } = getEntityName(result.entities[0]);
|
||||
ctx.output('entityRef', `${kind}:${namespace}/${name}`);
|
||||
|
||||
@@ -32,6 +32,10 @@ export type ActionContext<Input extends InputBase> = {
|
||||
logger: Logger;
|
||||
logStream: Writable;
|
||||
|
||||
/**
|
||||
* User token forwarded from initial request, for use in subsequent api requests
|
||||
*/
|
||||
token?: string | undefined;
|
||||
workspacePath: string;
|
||||
input: Input;
|
||||
output(name: string, value: JsonValue): void;
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
DbTaskRow,
|
||||
Status,
|
||||
TaskEventType,
|
||||
TaskSecrets,
|
||||
TaskSpec,
|
||||
TaskStore,
|
||||
TaskStoreEmitOptions,
|
||||
@@ -42,6 +43,7 @@ export type RawDbTaskRow = {
|
||||
status: Status;
|
||||
last_heartbeat_at?: string;
|
||||
created_at: string;
|
||||
secrets?: string;
|
||||
};
|
||||
|
||||
export type RawDbTaskEventRow = {
|
||||
@@ -71,23 +73,29 @@ export class DatabaseTaskStore implements TaskStore {
|
||||
}
|
||||
try {
|
||||
const spec = JSON.parse(result.spec);
|
||||
const secrets = result.secrets ? JSON.parse(result.secrets) : undefined;
|
||||
return {
|
||||
id: result.id,
|
||||
spec,
|
||||
status: result.status,
|
||||
lastHeartbeatAt: result.last_heartbeat_at,
|
||||
createdAt: result.created_at,
|
||||
secrets,
|
||||
};
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to parse spec of task '${taskId}', ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
async createTask(spec: TaskSpec): Promise<{ taskId: string }> {
|
||||
async createTask(
|
||||
spec: TaskSpec,
|
||||
secrets?: TaskSecrets,
|
||||
): Promise<{ taskId: string }> {
|
||||
const taskId = uuid();
|
||||
await this.db<RawDbTaskRow>('tasks').insert({
|
||||
id: taskId,
|
||||
spec: JSON.stringify(spec),
|
||||
secrets: secrets ? JSON.stringify(secrets) : undefined,
|
||||
status: 'open',
|
||||
});
|
||||
return { taskId };
|
||||
@@ -119,12 +127,14 @@ export class DatabaseTaskStore implements TaskStore {
|
||||
|
||||
try {
|
||||
const spec = JSON.parse(task.spec);
|
||||
const secrets = task.secrets ? JSON.parse(task.secrets) : undefined;
|
||||
return {
|
||||
id: task.id,
|
||||
spec,
|
||||
status: 'processing',
|
||||
lastHeartbeatAt: task.last_heartbeat_at,
|
||||
createdAt: task.created_at,
|
||||
secrets,
|
||||
};
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to parse spec of task '${task.id}', ${error}`);
|
||||
@@ -209,6 +219,7 @@ export class DatabaseTaskStore implements TaskStore {
|
||||
})
|
||||
.update({
|
||||
status,
|
||||
secrets: null as any,
|
||||
});
|
||||
if (updateCount !== 1) {
|
||||
throw new ConflictError(
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { DatabaseTaskStore } from './DatabaseTaskStore';
|
||||
import { StorageTaskBroker, TaskAgent } from './StorageTaskBroker';
|
||||
import { TaskSpec, DbTaskEventRow } from './types';
|
||||
import { TaskSecrets, TaskSpec, DbTaskEventRow } from './types';
|
||||
|
||||
async function createStore(): Promise<DatabaseTaskStore> {
|
||||
const manager = SingleConnectionDatabaseManager.fromConfig(
|
||||
@@ -39,6 +39,7 @@ async function createStore(): Promise<DatabaseTaskStore> {
|
||||
|
||||
describe('StorageTaskBroker', () => {
|
||||
let storage: DatabaseTaskStore;
|
||||
const fakeSecrets = { token: 'secret' } as TaskSecrets;
|
||||
|
||||
beforeAll(async () => {
|
||||
storage = await createStore();
|
||||
@@ -78,6 +79,13 @@ describe('StorageTaskBroker', () => {
|
||||
await expect(taskC.spec.steps[0].id).toBe('c');
|
||||
});
|
||||
|
||||
it('should store secrets', async () => {
|
||||
const broker = new StorageTaskBroker(storage, logger);
|
||||
await broker.dispatch({} as TaskSpec, fakeSecrets);
|
||||
const task = await broker.claim();
|
||||
expect(task.secrets).toEqual(fakeSecrets);
|
||||
}, 10000);
|
||||
|
||||
it('should complete a task', async () => {
|
||||
const broker = new StorageTaskBroker(storage, logger);
|
||||
const dispatchResult = await broker.dispatch({} as TaskSpec);
|
||||
@@ -87,6 +95,16 @@ describe('StorageTaskBroker', () => {
|
||||
expect(taskRow.status).toBe('completed');
|
||||
}, 10000);
|
||||
|
||||
it('should remove secrets after completing a task', async () => {
|
||||
const broker = new StorageTaskBroker(storage, logger);
|
||||
const dispatchResult = await broker.dispatch({} as TaskSpec, fakeSecrets);
|
||||
const task = await broker.claim();
|
||||
await task.complete('completed');
|
||||
const taskRow = await storage.getTask(dispatchResult.taskId);
|
||||
expect(taskRow.status).toBe('completed');
|
||||
expect(taskRow.secrets).toBeUndefined();
|
||||
}, 10000);
|
||||
|
||||
it('should fail a task', async () => {
|
||||
const broker = new StorageTaskBroker(storage, logger);
|
||||
const dispatchResult = await broker.dispatch({} as TaskSpec);
|
||||
@@ -96,6 +114,16 @@ describe('StorageTaskBroker', () => {
|
||||
expect(taskRow.status).toBe('failed');
|
||||
});
|
||||
|
||||
it('should remove secrets after failing a task', async () => {
|
||||
const broker = new StorageTaskBroker(storage, logger);
|
||||
const dispatchResult = await broker.dispatch({} as TaskSpec, fakeSecrets);
|
||||
const task = await broker.claim();
|
||||
await task.complete('failed');
|
||||
const taskRow = await storage.getTask(dispatchResult.taskId);
|
||||
expect(taskRow.status).toBe('failed');
|
||||
expect(taskRow.secrets).toBeUndefined();
|
||||
});
|
||||
|
||||
it('multiple brokers should be able to observe a single task', async () => {
|
||||
const broker1 = new StorageTaskBroker(storage, logger);
|
||||
const broker2 = new StorageTaskBroker(storage, logger);
|
||||
|
||||
@@ -18,6 +18,7 @@ import { Logger } from 'winston';
|
||||
import {
|
||||
CompletedTaskState,
|
||||
Task,
|
||||
TaskSecrets,
|
||||
TaskSpec,
|
||||
TaskStore,
|
||||
TaskBroker,
|
||||
@@ -48,6 +49,10 @@ export class TaskAgent implements Task {
|
||||
return this.state.spec;
|
||||
}
|
||||
|
||||
get secrets() {
|
||||
return this.state.secrets;
|
||||
}
|
||||
|
||||
async getWorkspaceName() {
|
||||
return this.state.taskId;
|
||||
}
|
||||
@@ -101,6 +106,7 @@ export class TaskAgent implements Task {
|
||||
interface TaskState {
|
||||
spec: TaskSpec;
|
||||
taskId: string;
|
||||
secrets?: TaskSecrets;
|
||||
}
|
||||
|
||||
function defer() {
|
||||
@@ -126,6 +132,7 @@ export class StorageTaskBroker implements TaskBroker {
|
||||
{
|
||||
taskId: pendingTask.id,
|
||||
spec: pendingTask.spec,
|
||||
secrets: pendingTask.secrets,
|
||||
},
|
||||
this.storage,
|
||||
this.logger,
|
||||
@@ -136,8 +143,11 @@ export class StorageTaskBroker implements TaskBroker {
|
||||
}
|
||||
}
|
||||
|
||||
async dispatch(spec: TaskSpec): Promise<DispatchResult> {
|
||||
const taskRow = await this.storage.createTask(spec);
|
||||
async dispatch(
|
||||
spec: TaskSpec,
|
||||
secrets?: TaskSecrets,
|
||||
): Promise<DispatchResult> {
|
||||
const taskRow = await this.storage.createTask(spec, secrets);
|
||||
this.signalDispatch();
|
||||
return {
|
||||
taskId: taskRow.taskId,
|
||||
|
||||
@@ -164,6 +164,7 @@ export class TaskWorker {
|
||||
logger: taskLogger,
|
||||
logStream: stream,
|
||||
input,
|
||||
token: task.secrets?.token,
|
||||
workspacePath,
|
||||
async createTemporaryDirectory() {
|
||||
const tmpDir = await fs.mkdtemp(
|
||||
|
||||
@@ -31,6 +31,7 @@ export type DbTaskRow = {
|
||||
status: Status;
|
||||
createdAt: string;
|
||||
lastHeartbeatAt?: string;
|
||||
secrets?: TaskSecrets;
|
||||
};
|
||||
|
||||
export type TaskEventType = 'completion' | 'log';
|
||||
@@ -54,12 +55,17 @@ export type TaskSpec = {
|
||||
output: { [name: string]: string };
|
||||
};
|
||||
|
||||
export type TaskSecrets = {
|
||||
token: string | undefined;
|
||||
};
|
||||
|
||||
export type DispatchResult = {
|
||||
taskId: string;
|
||||
};
|
||||
|
||||
export interface Task {
|
||||
spec: TaskSpec;
|
||||
secrets?: TaskSecrets;
|
||||
done: boolean;
|
||||
emitLog(message: string, metadata?: JsonValue): Promise<void>;
|
||||
complete(result: CompletedTaskState, metadata?: JsonValue): Promise<void>;
|
||||
@@ -68,7 +74,7 @@ export interface Task {
|
||||
|
||||
export interface TaskBroker {
|
||||
claim(): Promise<Task>;
|
||||
dispatch(spec: TaskSpec): Promise<DispatchResult>;
|
||||
dispatch(spec: TaskSpec, secrets?: TaskSecrets): Promise<DispatchResult>;
|
||||
vacuumTasks(timeoutS: { timeoutS: number }): Promise<void>;
|
||||
observe(
|
||||
options: {
|
||||
@@ -93,7 +99,10 @@ export type TaskStoreGetEventsOptions = {
|
||||
};
|
||||
|
||||
export interface TaskStore {
|
||||
createTask(task: TaskSpec): Promise<{ taskId: string }>;
|
||||
createTask(
|
||||
task: TaskSpec,
|
||||
secrets?: TaskSecrets,
|
||||
): Promise<{ taskId: string }>;
|
||||
getTask(taskId: string): Promise<DbTaskRow>;
|
||||
claimTask(): Promise<DbTaskRow | undefined>;
|
||||
completeTask(options: {
|
||||
|
||||
@@ -301,4 +301,27 @@ describe('createRouter', () => {
|
||||
expect(response.status).toEqual(201);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /v2/tasks/:taskId', () => {
|
||||
it('does not divulge secrets', async () => {
|
||||
const postResponse = await request(app)
|
||||
.post('/v2/tasks')
|
||||
.set('Authorization', 'Bearer secret')
|
||||
.send({
|
||||
templateName: 'create-react-app-template',
|
||||
values: {
|
||||
storePath: 'https://github.com/backstage/backstage',
|
||||
component_id: '123',
|
||||
name: 'test',
|
||||
use_typescript: false,
|
||||
},
|
||||
});
|
||||
|
||||
const response = await request(app)
|
||||
.get(`/v2/tasks/${postResponse.body.id}`)
|
||||
.send();
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.body.secrets).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -358,8 +358,9 @@ export async function createRouter(
|
||||
.post('/v2/tasks', async (req, res) => {
|
||||
const templateName: string = req.body.templateName;
|
||||
const values: TemplaterValues = req.body.values;
|
||||
const token = getBearerToken(req.headers.authorization);
|
||||
const template = await entityClient.findTemplate(templateName, {
|
||||
token: getBearerToken(req.headers.authorization),
|
||||
token,
|
||||
});
|
||||
|
||||
let taskSpec;
|
||||
@@ -402,7 +403,9 @@ export async function createRouter(
|
||||
);
|
||||
}
|
||||
|
||||
const result = await taskBroker.dispatch(taskSpec);
|
||||
const result = await taskBroker.dispatch(taskSpec, {
|
||||
token: token,
|
||||
});
|
||||
|
||||
res.status(201).json({ id: result.taskId });
|
||||
})
|
||||
@@ -412,6 +415,8 @@ export async function createRouter(
|
||||
if (!task) {
|
||||
throw new NotFoundError(`Task with id ${taskId} does not exist`);
|
||||
}
|
||||
// Do not disclose secrets
|
||||
delete task.secrets;
|
||||
res.status(200).json(task);
|
||||
})
|
||||
.get('/v2/tasks/:taskId/eventstream', async (req, res) => {
|
||||
|
||||
@@ -71,11 +71,17 @@ export const MultistepJsonForm = ({
|
||||
return (
|
||||
<>
|
||||
<Stepper activeStep={activeStep} orientation="vertical">
|
||||
{steps.map(({ title, schema, ...formProps }) => {
|
||||
{steps.map(({ title, schema, ...formProps }, index) => {
|
||||
return (
|
||||
<StepUI key={title}>
|
||||
<StepLabel>
|
||||
<Typography variant="h6">{title}</Typography>
|
||||
<StepLabel
|
||||
aria-label={`Step ${index + 1} ${title}`}
|
||||
aria-disabled="false"
|
||||
tabIndex={0}
|
||||
>
|
||||
<Typography variant="h6" component="h3">
|
||||
{title}
|
||||
</Typography>
|
||||
</StepLabel>
|
||||
<StepContent key={title}>
|
||||
<Form
|
||||
|
||||
@@ -85,7 +85,7 @@ export const TemplateCard = ({
|
||||
<Box className={classes.description}>{description}</Box>
|
||||
</CardContent>
|
||||
<CardActions>
|
||||
<Button color="primary" to={href}>
|
||||
<Button color="primary" to={href} aria-label={`Choose ${title} `}>
|
||||
Choose
|
||||
</Button>
|
||||
</CardActions>
|
||||
|
||||
@@ -187,7 +187,11 @@ export const TemplatePage = () => {
|
||||
<Content>
|
||||
{loading && <LinearProgress data-testid="loading-progress" />}
|
||||
{schema && (
|
||||
<InfoCard title={schema.title} noPadding>
|
||||
<InfoCard
|
||||
title={schema.title}
|
||||
noPadding
|
||||
titleTypographyProps={{ component: 'h2' }}
|
||||
>
|
||||
<MultistepJsonForm
|
||||
formData={formState}
|
||||
fields={{ RepoUrlPicker, OwnerPicker }}
|
||||
|
||||
@@ -1616,10 +1616,10 @@
|
||||
core-js-pure "^3.0.0"
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.0", "@babel/runtime@^7.10.1", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.4", "@babel/runtime@^7.10.5", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.3.1", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.0", "@babel/runtime@^7.6.2", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
|
||||
version "7.12.5"
|
||||
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e"
|
||||
integrity sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==
|
||||
"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.0", "@babel/runtime@^7.10.1", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.4", "@babel/runtime@^7.10.5", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.3.1", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.0", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
|
||||
version "7.13.10"
|
||||
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz#47d42a57b6095f4468da440388fdbad8bebf0d7d"
|
||||
integrity sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
@@ -3720,21 +3720,21 @@
|
||||
integrity sha512-aIRd0Z9b/HJ/O24KOaP7dNsXypMnXhpWrpLVYvQB/JesVqXYSbioYND200sR+C14a0LSCp+qWnWCnSXmN1hWGw==
|
||||
|
||||
"@material-ui/core@^4.11.0", "@material-ui/core@^4.9.1":
|
||||
version "4.11.0"
|
||||
resolved "https://registry.npmjs.org/@material-ui/core/-/core-4.11.0.tgz#b69b26e4553c9e53f2bfaf1053e216a0af9be15a"
|
||||
integrity sha512-bYo9uIub8wGhZySHqLQ833zi4ZML+XCBE1XwJ8EuUVSpTWWG57Pm+YugQToJNFsEyiKFhPh8DPD0bgupz8n01g==
|
||||
version "4.11.3"
|
||||
resolved "https://registry.npmjs.org/@material-ui/core/-/core-4.11.3.tgz#f22e41775b0bd075e36a7a093d43951bf7f63850"
|
||||
integrity sha512-Adt40rGW6Uds+cAyk3pVgcErpzU/qxc7KBR94jFHBYretU4AtWZltYcNsbeMn9tXL86jjVL1kuGcIHsgLgFGRw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.4.4"
|
||||
"@material-ui/styles" "^4.10.0"
|
||||
"@material-ui/system" "^4.9.14"
|
||||
"@material-ui/styles" "^4.11.3"
|
||||
"@material-ui/system" "^4.11.3"
|
||||
"@material-ui/types" "^5.1.0"
|
||||
"@material-ui/utils" "^4.10.2"
|
||||
"@material-ui/utils" "^4.11.2"
|
||||
"@types/react-transition-group" "^4.2.0"
|
||||
clsx "^1.0.4"
|
||||
hoist-non-react-statics "^3.3.2"
|
||||
popper.js "1.16.1-lts"
|
||||
prop-types "^15.7.2"
|
||||
react-is "^16.8.0"
|
||||
react-is "^16.8.0 || ^17.0.0"
|
||||
react-transition-group "^4.4.0"
|
||||
|
||||
"@material-ui/icons@^4.9.1":
|
||||
@@ -3778,35 +3778,35 @@
|
||||
react-transition-group "^4.0.0"
|
||||
rifm "^0.7.0"
|
||||
|
||||
"@material-ui/styles@^4.10.0", "@material-ui/styles@^4.9.6":
|
||||
version "4.10.0"
|
||||
resolved "https://registry.npmjs.org/@material-ui/styles/-/styles-4.10.0.tgz#2406dc23aa358217aa8cc772e6237bd7f0544071"
|
||||
integrity sha512-XPwiVTpd3rlnbfrgtEJ1eJJdFCXZkHxy8TrdieaTvwxNYj42VnnCyFzxYeNW9Lhj4V1oD8YtQ6S5Gie7bZDf7Q==
|
||||
"@material-ui/styles@^4.10.0", "@material-ui/styles@^4.11.3", "@material-ui/styles@^4.9.6":
|
||||
version "4.11.3"
|
||||
resolved "https://registry.npmjs.org/@material-ui/styles/-/styles-4.11.3.tgz#1b8d97775a4a643b53478c895e3f2a464e8916f2"
|
||||
integrity sha512-HzVzCG+PpgUGMUYEJ2rTEmQYeonGh41BYfILNFb/1ueqma+p1meSdu4RX6NjxYBMhf7k+jgfHFTTz+L1SXL/Zg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.4.4"
|
||||
"@emotion/hash" "^0.8.0"
|
||||
"@material-ui/types" "^5.1.0"
|
||||
"@material-ui/utils" "^4.9.6"
|
||||
"@material-ui/utils" "^4.11.2"
|
||||
clsx "^1.0.4"
|
||||
csstype "^2.5.2"
|
||||
hoist-non-react-statics "^3.3.2"
|
||||
jss "^10.0.3"
|
||||
jss-plugin-camel-case "^10.0.3"
|
||||
jss-plugin-default-unit "^10.0.3"
|
||||
jss-plugin-global "^10.0.3"
|
||||
jss-plugin-nested "^10.0.3"
|
||||
jss-plugin-props-sort "^10.0.3"
|
||||
jss-plugin-rule-value-function "^10.0.3"
|
||||
jss-plugin-vendor-prefixer "^10.0.3"
|
||||
jss "^10.5.1"
|
||||
jss-plugin-camel-case "^10.5.1"
|
||||
jss-plugin-default-unit "^10.5.1"
|
||||
jss-plugin-global "^10.5.1"
|
||||
jss-plugin-nested "^10.5.1"
|
||||
jss-plugin-props-sort "^10.5.1"
|
||||
jss-plugin-rule-value-function "^10.5.1"
|
||||
jss-plugin-vendor-prefixer "^10.5.1"
|
||||
prop-types "^15.7.2"
|
||||
|
||||
"@material-ui/system@^4.9.14":
|
||||
version "4.9.14"
|
||||
resolved "https://registry.npmjs.org/@material-ui/system/-/system-4.9.14.tgz#4b00c48b569340cefb2036d0596b93ac6c587a5f"
|
||||
integrity sha512-oQbaqfSnNlEkXEziDcJDDIy8pbvwUmZXWNqlmIwDqr/ZdCK8FuV3f4nxikUh7hvClKV2gnQ9djh5CZFTHkZj3w==
|
||||
"@material-ui/system@^4.11.3":
|
||||
version "4.11.3"
|
||||
resolved "https://registry.npmjs.org/@material-ui/system/-/system-4.11.3.tgz#466bc14c9986798fd325665927c963eb47cc4143"
|
||||
integrity sha512-SY7otguNGol41Mu2Sg6KbBP1ZRFIbFLHGK81y4KYbsV2yIcaEPOmsCK6zwWlp+2yTV3J/VwT6oSBARtGIVdXPw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.4.4"
|
||||
"@material-ui/utils" "^4.9.6"
|
||||
"@material-ui/utils" "^4.11.2"
|
||||
csstype "^2.5.2"
|
||||
prop-types "^15.7.2"
|
||||
|
||||
@@ -3815,16 +3815,7 @@
|
||||
resolved "https://registry.npmjs.org/@material-ui/types/-/types-5.1.0.tgz#efa1c7a0b0eaa4c7c87ac0390445f0f88b0d88f2"
|
||||
integrity sha512-7cqRjrY50b8QzRSYyhSpx4WRw2YuO0KKIGQEVk5J8uoz2BanawykgZGoWEqKm7pVIbzFDN0SpPcVV4IhOFkl8A==
|
||||
|
||||
"@material-ui/utils@^4.10.2", "@material-ui/utils@^4.7.1", "@material-ui/utils@^4.9.6":
|
||||
version "4.10.2"
|
||||
resolved "https://registry.npmjs.org/@material-ui/utils/-/utils-4.10.2.tgz#3fd5470ca61b7341f1e0468ac8f29a70bf6df321"
|
||||
integrity sha512-eg29v74P7W5r6a4tWWDAAfZldXIzfyO1am2fIsC39hdUUHm/33k6pGOKPbgDjg/U/4ifmgAePy/1OjkKN6rFRw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.4.4"
|
||||
prop-types "^15.7.2"
|
||||
react-is "^16.8.0"
|
||||
|
||||
"@material-ui/utils@^4.11.2":
|
||||
"@material-ui/utils@^4.11.2", "@material-ui/utils@^4.7.1":
|
||||
version "4.11.2"
|
||||
resolved "https://registry.npmjs.org/@material-ui/utils/-/utils-4.11.2.tgz#f1aefa7e7dff2ebcb97d31de51aecab1bb57540a"
|
||||
integrity sha512-Uul8w38u+PICe2Fg2pDKCaIG7kOyhowZ9vjiC1FsVwPABTW8vPPKfF6OvxRq3IiBaI1faOJmgdvMG7rMJARBhA==
|
||||
@@ -10696,12 +10687,12 @@ css-unit-converter@^1.1.2:
|
||||
resolved "https://registry.npmjs.org/css-unit-converter/-/css-unit-converter-1.1.2.tgz#4c77f5a1954e6dbff60695ecb214e3270436ab21"
|
||||
integrity sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA==
|
||||
|
||||
css-vendor@^2.0.7:
|
||||
version "2.0.7"
|
||||
resolved "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.7.tgz#4e6d53d953c187981576d6a542acc9fb57174bda"
|
||||
integrity sha512-VS9Rjt79+p7M0WkPqcAza4Yq1ZHrsHrwf7hPL/bjQB+c1lwmAI+1FXxYTYt818D/50fFVflw0XKleiBN5RITkg==
|
||||
css-vendor@^2.0.8:
|
||||
version "2.0.8"
|
||||
resolved "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.8.tgz#e47f91d3bd3117d49180a3c935e62e3d9f7f449d"
|
||||
integrity sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.6.2"
|
||||
"@babel/runtime" "^7.8.3"
|
||||
is-in-browser "^1.0.2"
|
||||
|
||||
css-what@2.1:
|
||||
@@ -10837,12 +10828,12 @@ cssstyle@^2.2.0:
|
||||
dependencies:
|
||||
cssom "~0.3.6"
|
||||
|
||||
csstype@^2.2.0, csstype@^2.5.2, csstype@^2.5.5, csstype@^2.5.7, csstype@^2.6.5, csstype@^2.6.7:
|
||||
csstype@^2.2.0, csstype@^2.5.2, csstype@^2.5.5, csstype@^2.5.7, csstype@^2.6.7:
|
||||
version "2.6.9"
|
||||
resolved "https://registry.npmjs.org/csstype/-/csstype-2.6.9.tgz#05141d0cd557a56b8891394c1911c40c8a98d098"
|
||||
integrity sha512-xz39Sb4+OaTsULgUERcCk+TJj8ylkL4aSVDQiX/ksxbELSqwkgt4d4RD7fovIdgJGSuNYqwZEiVjYY5l0ask+Q==
|
||||
|
||||
csstype@^3.0.6:
|
||||
csstype@^3.0.2, csstype@^3.0.6:
|
||||
version "3.0.7"
|
||||
resolved "https://registry.npmjs.org/csstype/-/csstype-3.0.7.tgz#2a5fb75e1015e84dd15692f71e89a1450290950b"
|
||||
integrity sha512-KxnUB0ZMlnUWCsx2Z8MUsr6qV6ja1w9ArPErJaJaF8a5SOWoHLIszeCTKGRGRgtLgYrs1E8CHkNSP1VZTTPc9g==
|
||||
@@ -13154,9 +13145,9 @@ fetch-readablestream@^0.2.0:
|
||||
integrity sha512-qu4mXWf4wus4idBIN/kVH+XSer8IZ9CwHP+Pd7DL7TuKNC1hP7ykon4kkBjwJF3EMX2WsFp4hH7gU7CyL7ucXw==
|
||||
|
||||
figgy-pudding@^3.5.1:
|
||||
version "3.5.1"
|
||||
resolved "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790"
|
||||
integrity sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==
|
||||
version "3.5.2"
|
||||
resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e"
|
||||
integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==
|
||||
|
||||
figures@^1.7.0:
|
||||
version "1.7.0"
|
||||
@@ -14234,9 +14225,9 @@ google-p12-pem@^3.0.3:
|
||||
node-forge "^0.10.0"
|
||||
|
||||
got@^11.5.2, got@^11.7.0, got@^11.8.0:
|
||||
version "11.8.0"
|
||||
resolved "https://registry.npmjs.org/got/-/got-11.8.0.tgz#be0920c3586b07fd94add3b5b27cb28f49e6545f"
|
||||
integrity sha512-k9noyoIIY9EejuhaBNLyZ31D5328LeqnyPNXJQb2XlJZcKakLqN5m6O/ikhq/0lw56kUYS54fVm+D1x57YC9oQ==
|
||||
version "11.8.2"
|
||||
resolved "https://registry.npmjs.org/got/-/got-11.8.2.tgz#7abb3959ea28c31f3576f1576c1effce23f33599"
|
||||
integrity sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ==
|
||||
dependencies:
|
||||
"@sindresorhus/is" "^4.0.0"
|
||||
"@szmarczak/http-timer" "^4.0.5"
|
||||
@@ -15214,6 +15205,13 @@ imurmurhash@^0.1.4:
|
||||
resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
|
||||
integrity sha1-khi5srkoojixPcT7a21XbyMUU+o=
|
||||
|
||||
indefinite-observable@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.npmjs.org/indefinite-observable/-/indefinite-observable-2.0.1.tgz#574af29bfbc17eb5947793797bddc94c9d859400"
|
||||
integrity sha512-G8vgmork+6H9S8lUAg1gtXEj2JxIQTo0g2PbFiYOdjkziSI0F7UYBiVwhZRuixhBCNGczAls34+5HJPyZysvxQ==
|
||||
dependencies:
|
||||
symbol-observable "1.2.0"
|
||||
|
||||
indent-string@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80"
|
||||
@@ -16615,9 +16613,9 @@ joi@^17.3.0:
|
||||
"@sideway/pinpoint" "^2.0.0"
|
||||
|
||||
jose@^1.27.1:
|
||||
version "1.28.0"
|
||||
resolved "https://registry.npmjs.org/jose/-/jose-1.28.0.tgz#0803f8c71f43cd293a9d931c555c30531f5ca5dc"
|
||||
integrity sha512-JmfDRzt/HSj8ipd9TsDtEHoLUnLYavG+7e8F6s1mx2jfVSfXOTaFQsJUydbjJpTnTDHP1+yKL9Ke7ktS/a0Eiw==
|
||||
version "1.28.1"
|
||||
resolved "https://registry.npmjs.org/jose/-/jose-1.28.1.tgz#34a0f851a534be59ffab82a6e8845f6874e8c128"
|
||||
integrity sha512-6JK28rFu5ENp/yxMwM+iN7YeaInnY9B9Bggjkz5fuwLiJhbVrl2O4SJr65bdNBPl9y27fdC3Mymh+FVCvozLIg==
|
||||
dependencies:
|
||||
"@panva/asn1.js" "^1.0.0"
|
||||
|
||||
@@ -16992,72 +16990,74 @@ jsprim@^1.2.2:
|
||||
json-schema "0.2.3"
|
||||
verror "1.10.0"
|
||||
|
||||
jss-plugin-camel-case@^10.0.3:
|
||||
version "10.1.1"
|
||||
resolved "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.1.1.tgz#8e73ecc4f1d0f8dfe4dd31f6f9f2782588970e78"
|
||||
integrity sha512-MDIaw8FeD5uFz1seQBKz4pnvDLnj5vIKV5hXSVdMaAVq13xR6SVTVWkIV/keyTs5txxTvzGJ9hXoxgd1WTUlBw==
|
||||
jss-plugin-camel-case@^10.5.1:
|
||||
version "10.6.0"
|
||||
resolved "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.6.0.tgz#93d2cd704bf0c4af70cc40fb52d74b8a2554b170"
|
||||
integrity sha512-JdLpA3aI/npwj3nDMKk308pvnhoSzkW3PXlbgHAzfx0yHWnPPVUjPhXFtLJzgKZge8lsfkUxvYSQ3X2OYIFU6A==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.3.1"
|
||||
hyphenate-style-name "^1.0.3"
|
||||
jss "10.1.1"
|
||||
jss "10.6.0"
|
||||
|
||||
jss-plugin-default-unit@^10.0.3:
|
||||
version "10.1.1"
|
||||
resolved "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.1.1.tgz#2df86016dfe73085eead843f5794e3890e9c5c47"
|
||||
integrity sha512-UkeVCA/b3QEA4k0nIKS4uWXDCNmV73WLHdh2oDGZZc3GsQtlOCuiH3EkB/qI60v2MiCq356/SYWsDXt21yjwdg==
|
||||
jss-plugin-default-unit@^10.5.1:
|
||||
version "10.6.0"
|
||||
resolved "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.6.0.tgz#af47972486819b375f0f3a9e0213403a84b5ef3b"
|
||||
integrity sha512-7y4cAScMHAxvslBK2JRK37ES9UT0YfTIXWgzUWD5euvR+JR3q+o8sQKzBw7GmkQRfZijrRJKNTiSt1PBsLI9/w==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.3.1"
|
||||
jss "10.1.1"
|
||||
jss "10.6.0"
|
||||
|
||||
jss-plugin-global@^10.0.3:
|
||||
version "10.1.1"
|
||||
resolved "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.1.1.tgz#36b0d6d9facb74dfd99590643708a89260747d14"
|
||||
integrity sha512-VBG3wRyi3Z8S4kMhm8rZV6caYBegsk+QnQZSVmrWw6GVOT/Z4FA7eyMu5SdkorDlG/HVpHh91oFN56O4R9m2VA==
|
||||
jss-plugin-global@^10.5.1:
|
||||
version "10.6.0"
|
||||
resolved "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.6.0.tgz#3e8011f760f399cbadcca7f10a485b729c50e3ed"
|
||||
integrity sha512-I3w7ji/UXPi3VuWrTCbHG9rVCgB4yoBQLehGDTmsnDfXQb3r1l3WIdcO8JFp9m0YMmyy2CU7UOV6oPI7/Tmu+w==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.3.1"
|
||||
jss "10.1.1"
|
||||
jss "10.6.0"
|
||||
|
||||
jss-plugin-nested@^10.0.3:
|
||||
version "10.1.1"
|
||||
resolved "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.1.1.tgz#5c3de2b8bda344de1ebcef3a4fd30870a29a8a8c"
|
||||
integrity sha512-ozEu7ZBSVrMYxSDplPX3H82XHNQk2DQEJ9TEyo7OVTPJ1hEieqjDFiOQOxXEj9z3PMqkylnUbvWIZRDKCFYw5Q==
|
||||
jss-plugin-nested@^10.5.1:
|
||||
version "10.6.0"
|
||||
resolved "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.6.0.tgz#5f83c5c337d3b38004834e8426957715a0251641"
|
||||
integrity sha512-fOFQWgd98H89E6aJSNkEh2fAXquC9aZcAVjSw4q4RoQ9gU++emg18encR4AT4OOIFl4lQwt5nEyBBRn9V1Rk8g==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.3.1"
|
||||
jss "10.1.1"
|
||||
jss "10.6.0"
|
||||
tiny-warning "^1.0.2"
|
||||
|
||||
jss-plugin-props-sort@^10.0.3:
|
||||
version "10.1.1"
|
||||
resolved "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.1.1.tgz#34bddcbfaf9430ec8ccdf92729f03bb10caf1785"
|
||||
integrity sha512-g/joK3eTDZB4pkqpZB38257yD4LXB0X15jxtZAGbUzcKAVUHPl9Jb47Y7lYmiGsShiV4YmQRqG1p2DHMYoK91g==
|
||||
jss-plugin-props-sort@^10.5.1:
|
||||
version "10.6.0"
|
||||
resolved "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.6.0.tgz#297879f35f9fe21196448579fee37bcde28ce6bc"
|
||||
integrity sha512-oMCe7hgho2FllNc60d9VAfdtMrZPo9n1Iu6RNa+3p9n0Bkvnv/XX5San8fTPujrTBScPqv9mOE0nWVvIaohNuw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.3.1"
|
||||
jss "10.1.1"
|
||||
jss "10.6.0"
|
||||
|
||||
jss-plugin-rule-value-function@^10.0.3:
|
||||
version "10.1.1"
|
||||
resolved "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.1.1.tgz#be00dac6fc394aaddbcef5860b9eca6224d96382"
|
||||
integrity sha512-ClV1lvJ3laU9la1CUzaDugEcwnpjPTuJ0yGy2YtcU+gG/w9HMInD5vEv7xKAz53Bk4WiJm5uLOElSEshHyhKNw==
|
||||
jss-plugin-rule-value-function@^10.5.1:
|
||||
version "10.6.0"
|
||||
resolved "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.6.0.tgz#3c1a557236a139d0151e70a82c810ccce1c1c5ea"
|
||||
integrity sha512-TKFqhRTDHN1QrPTMYRlIQUOC2FFQb271+AbnetURKlGvRl/eWLswcgHQajwuxI464uZk91sPiTtdGi7r7XaWfA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.3.1"
|
||||
jss "10.1.1"
|
||||
jss "10.6.0"
|
||||
tiny-warning "^1.0.2"
|
||||
|
||||
jss-plugin-vendor-prefixer@^10.0.3:
|
||||
version "10.1.1"
|
||||
resolved "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.1.1.tgz#8348b20749f790beebab3b6a8f7075b07c2cfcfd"
|
||||
integrity sha512-09MZpQ6onQrhaVSF6GHC4iYifQ7+4YC/tAP6D4ZWeZotvCMq1mHLqNKRIaqQ2lkgANjlEot2JnVi1ktu4+L4pw==
|
||||
jss-plugin-vendor-prefixer@^10.5.1:
|
||||
version "10.6.0"
|
||||
resolved "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.6.0.tgz#e1fcd499352846890c38085b11dbd7aa1c4f2c78"
|
||||
integrity sha512-doJ7MouBXT1lypLLctCwb4nJ6lDYqrTfVS3LtXgox42Xz0gXusXIIDboeh6UwnSmox90QpVnub7au8ybrb0krQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.3.1"
|
||||
css-vendor "^2.0.7"
|
||||
jss "10.1.1"
|
||||
css-vendor "^2.0.8"
|
||||
jss "10.6.0"
|
||||
|
||||
jss@10.1.1, jss@^10.0.3:
|
||||
version "10.1.1"
|
||||
resolved "https://registry.npmjs.org/jss/-/jss-10.1.1.tgz#450b27d53761af3e500b43130a54cdbe157ea332"
|
||||
integrity sha512-Xz3qgRUFlxbWk1czCZibUJqhVPObrZHxY3FPsjCXhDld4NOj1BgM14Ir5hVm+Qr6OLqVljjGvoMcCdXNOAbdkQ==
|
||||
jss@10.6.0, jss@^10.5.1:
|
||||
version "10.6.0"
|
||||
resolved "https://registry.npmjs.org/jss/-/jss-10.6.0.tgz#d92ff9d0f214f65ca1718591b68e107be4774149"
|
||||
integrity sha512-n7SHdCozmxnzYGXBHe0NsO0eUf9TvsHVq2MXvi4JmTn3x5raynodDVE/9VQmBdWFyyj9HpHZ2B4xNZ7MMy7lkw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.3.1"
|
||||
csstype "^2.6.5"
|
||||
csstype "^3.0.2"
|
||||
indefinite-observable "^2.0.1"
|
||||
is-in-browser "^1.1.3"
|
||||
tiny-warning "^1.0.2"
|
||||
|
||||
@@ -18619,14 +18619,7 @@ minipass@^2.6.0, minipass@^2.8.6, minipass@^2.9.0:
|
||||
safe-buffer "^5.1.2"
|
||||
yallist "^3.0.0"
|
||||
|
||||
minipass@^3.0.0, minipass@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.npmjs.org/minipass/-/minipass-3.1.1.tgz#7607ce778472a185ad6d89082aa2070f79cedcd5"
|
||||
integrity sha512-UFqVihv6PQgwj8/yTGvl9kPz7xIAY+R5z6XYjRInD3Gk3qx6QGSD6zEcpeG4Dy/lQnv1J6zv8ejV90hyYIKf3w==
|
||||
dependencies:
|
||||
yallist "^4.0.0"
|
||||
|
||||
minipass@^3.1.0, minipass@^3.1.3:
|
||||
minipass@^3.0.0, minipass@^3.1.0, minipass@^3.1.1, minipass@^3.1.3:
|
||||
version "3.1.3"
|
||||
resolved "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz#7d42ff1f39635482e15f9cdb53184deebd5815fd"
|
||||
integrity sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==
|
||||
@@ -24096,20 +24089,13 @@ sshpk@^1.7.0:
|
||||
tweetnacl "~0.14.0"
|
||||
|
||||
ssri@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8"
|
||||
integrity sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.2.tgz#157939134f20464e7301ddba3e90ffa8f7728ac5"
|
||||
integrity sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==
|
||||
dependencies:
|
||||
figgy-pudding "^3.5.1"
|
||||
|
||||
ssri@^8.0.0:
|
||||
version "8.0.0"
|
||||
resolved "https://registry.npmjs.org/ssri/-/ssri-8.0.0.tgz#79ca74e21f8ceaeddfcb4b90143c458b8d988808"
|
||||
integrity sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA==
|
||||
dependencies:
|
||||
minipass "^3.1.1"
|
||||
|
||||
ssri@^8.0.1:
|
||||
ssri@^8.0.0, ssri@^8.0.1:
|
||||
version "8.0.1"
|
||||
resolved "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz#638e4e439e2ffbd2cd289776d5ca457c4f51a2af"
|
||||
integrity sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==
|
||||
@@ -24788,7 +24774,7 @@ swr@^0.3.0:
|
||||
dependencies:
|
||||
fast-deep-equal "2.0.1"
|
||||
|
||||
symbol-observable@^1.0.3, symbol-observable@^1.0.4, symbol-observable@^1.1.0, symbol-observable@^1.2.0:
|
||||
symbol-observable@1.2.0, symbol-observable@^1.0.3, symbol-observable@^1.0.4, symbol-observable@^1.1.0, symbol-observable@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"
|
||||
integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==
|
||||
|
||||
Reference in New Issue
Block a user