more strict error type checking in most packages and backend plugins
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
---
|
||||
'@backstage/backend-common': patch
|
||||
'@backstage/cli': patch
|
||||
'@backstage/config-loader': patch
|
||||
'@backstage/core-app-api': patch
|
||||
'@backstage/core-components': patch
|
||||
'@backstage/core-plugin-api': patch
|
||||
'@backstage/create-app': patch
|
||||
'@backstage/techdocs-common': patch
|
||||
'@backstage/plugin-auth-backend': patch
|
||||
'@backstage/plugin-catalog': patch
|
||||
'@backstage/plugin-catalog-backend': patch
|
||||
'@backstage/plugin-catalog-backend-module-ldap': patch
|
||||
'@backstage/plugin-catalog-import': patch
|
||||
'@backstage/plugin-catalog-react': patch
|
||||
'@backstage/plugin-code-coverage-backend': patch
|
||||
'@backstage/plugin-kubernetes-backend': patch
|
||||
'@backstage/plugin-scaffolder-backend': patch
|
||||
'@backstage/plugin-techdocs-backend': patch
|
||||
---
|
||||
|
||||
Internal updates to apply more strict checks to throw errors.
|
||||
@@ -17,6 +17,7 @@
|
||||
import knexFactory, { Knex } from 'knex';
|
||||
|
||||
import { Config } from '@backstage/config';
|
||||
import { ForwardedError } from '@backstage/errors';
|
||||
import { mergeDatabaseConfig } from '../config';
|
||||
import { DatabaseConnector } from '../types';
|
||||
import defaultNameOverride from './defaultNameOverride';
|
||||
@@ -94,8 +95,7 @@ function requirePgConnectionString() {
|
||||
try {
|
||||
return require('pg-connection-string').parse;
|
||||
} catch (e) {
|
||||
const message = `Postgres: Install 'pg-connection-string'`;
|
||||
throw new Error(`${message}\n${e.message}`);
|
||||
throw new ForwardedError("Postgres: Install 'pg-connection-string'", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -167,7 +167,7 @@ describe('AwsS3UrlReader', () => {
|
||||
),
|
||||
).rejects.toThrow(
|
||||
Error(
|
||||
`Could not retrieve file from S3: not a valid AWS S3 URL: https://test-bucket.s3.us-east-2.NOTamazonaws.com/file.yaml`,
|
||||
`Could not retrieve file from S3; caused by Error: not a valid AWS S3 URL: https://test-bucket.s3.us-east-2.NOTamazonaws.com/file.yaml`,
|
||||
),
|
||||
);
|
||||
});
|
||||
@@ -216,7 +216,7 @@ describe('AwsS3UrlReader', () => {
|
||||
),
|
||||
).rejects.toThrow(
|
||||
Error(
|
||||
`Could not retrieve file from S3: not a valid AWS S3 URL: https://test-bucket.s3.us-east-2.NOTamazonaws.com/file.yaml`,
|
||||
`Could not retrieve file from S3; caused by Error: not a valid AWS S3 URL: https://test-bucket.s3.us-east-2.NOTamazonaws.com/file.yaml`,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
} from './types';
|
||||
import getRawBody from 'raw-body';
|
||||
import { AwsS3Integration, ScmIntegrations } from '@backstage/integration';
|
||||
import { ForwardedError } from '@backstage/errors';
|
||||
import { ListObjectsV2Output, ObjectList } from 'aws-sdk/clients/s3';
|
||||
|
||||
const parseURL = (
|
||||
@@ -162,7 +163,7 @@ export class AwsS3UrlReader implements UrlReader {
|
||||
etag: etag,
|
||||
};
|
||||
} catch (e) {
|
||||
throw new Error(`Could not retrieve file from S3: ${e.message}`);
|
||||
throw new ForwardedError('Could not retrieve file from S3', e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,7 +204,7 @@ export class AwsS3UrlReader implements UrlReader {
|
||||
|
||||
return await this.deps.treeResponseFactory.fromReadableArray(responses);
|
||||
} catch (e) {
|
||||
throw new Error(`Could not retrieve file tree from S3: ${e.message}`);
|
||||
throw new ForwardedError('Could not retrieve file tree from S3', e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { isError } from '@backstage/errors';
|
||||
import { getVoidLogger } from '../logging';
|
||||
import { UrlReaders } from './UrlReaders';
|
||||
|
||||
@@ -71,7 +72,10 @@ function withRetries(count: number, fn: () => Promise<void>) {
|
||||
error = err;
|
||||
}
|
||||
}
|
||||
if (!error.message.match(/rate limit|Too Many Requests/)) {
|
||||
if (
|
||||
isError(error) &&
|
||||
!error.message.match(/rate limit|Too Many Requests/)
|
||||
) {
|
||||
throw error;
|
||||
} else {
|
||||
console.warn('Request was rate limited', error);
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
import Docker from 'dockerode';
|
||||
import fs from 'fs-extra';
|
||||
import { ForwardedError } from '@backstage/errors';
|
||||
import { PassThrough } from 'stream';
|
||||
import { ContainerRunner, RunContainerOptions } from './ContainerRunner';
|
||||
|
||||
@@ -45,8 +46,9 @@ export class DockerContainerRunner implements ContainerRunner {
|
||||
try {
|
||||
await this.dockerClient.ping();
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
`This operation requires Docker. Docker does not appear to be available. Docker.ping() failed with: ${e.message}`,
|
||||
throw new ForwardedError(
|
||||
'This operation requires Docker. Docker does not appear to be available. Docker.ping() failed with',
|
||||
e,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
"@backstage/cli-common": "^0.1.4",
|
||||
"@backstage/config": "^0.1.10",
|
||||
"@backstage/config-loader": "^0.6.10",
|
||||
"@backstage/errors": "^0.1.2",
|
||||
"@hot-loader/react-dom": "^16.13.0",
|
||||
"@lerna/package-graph": "^4.0.0",
|
||||
"@lerna/project": "^4.0.0",
|
||||
|
||||
@@ -24,6 +24,7 @@ import camelCase from 'lodash/camelCase';
|
||||
import upperFirst from 'lodash/upperFirst';
|
||||
import os from 'os';
|
||||
import { Command } from 'commander';
|
||||
import { assertError } from '@backstage/errors';
|
||||
import {
|
||||
parseOwnerIds,
|
||||
addCodeownersEntry,
|
||||
@@ -173,6 +174,7 @@ async function buildPlugin(pluginFolder: string) {
|
||||
);
|
||||
});
|
||||
} catch (error) {
|
||||
assertError(error);
|
||||
Task.error(error.message);
|
||||
break;
|
||||
}
|
||||
@@ -329,6 +331,7 @@ export default async (cmd: Command) => {
|
||||
Task.log();
|
||||
Task.exit();
|
||||
} catch (error) {
|
||||
assertError(error);
|
||||
Task.error(error.message);
|
||||
|
||||
Task.log('It seems that something went wrong when creating the plugin 🤔');
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { assertError } from '@backstage/errors';
|
||||
import { CommanderStatic } from 'commander';
|
||||
import { exitWithError } from '../lib/errors';
|
||||
|
||||
@@ -252,6 +253,7 @@ function lazy(
|
||||
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
assertError(error);
|
||||
exitWithError(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -20,6 +20,7 @@ import inquirer, { Answers, Question } from 'inquirer';
|
||||
import { getCodeownersFilePath } from '../../lib/codeowners';
|
||||
import { paths } from '../../lib/paths';
|
||||
import { Task } from '../../lib/tasks';
|
||||
import { assertError } from '@backstage/errors';
|
||||
|
||||
const BACKSTAGE = '@backstage';
|
||||
|
||||
@@ -35,6 +36,7 @@ export const checkExists = async (rootDir: string, pluginName: string) => {
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
assertError(e);
|
||||
throw new Error(
|
||||
chalk.red(
|
||||
` There was an error removing plugin ${chalk.cyan(pluginName)}: ${
|
||||
@@ -51,6 +53,7 @@ export const removePluginDirectory = async (destination: string) => {
|
||||
try {
|
||||
await fse.remove(destination);
|
||||
} catch (e) {
|
||||
assertError(e);
|
||||
throw Error(
|
||||
chalk.red(
|
||||
` There was a problem removing the plugin directory: ${e.message}`,
|
||||
@@ -67,6 +70,7 @@ export const removeSymLink = async (destination: string) => {
|
||||
try {
|
||||
await fse.remove(destination);
|
||||
} catch (e) {
|
||||
assertError(e);
|
||||
throw Error(
|
||||
chalk.red(
|
||||
` Could not remove symbolic link\t${chalk.cyan(destination)}: ${
|
||||
@@ -106,6 +110,7 @@ export const removeReferencesFromPluginsFile = async (
|
||||
try {
|
||||
await removeAllStatementsContainingID(pluginsFile, pluginNameCapitalized);
|
||||
} catch (e) {
|
||||
assertError(e);
|
||||
throw new Error(
|
||||
chalk.red(
|
||||
` There was an error removing export statement for plugin ${chalk.cyan(
|
||||
@@ -125,6 +130,7 @@ export const removePluginFromCodeOwners = async (
|
||||
try {
|
||||
await removeAllStatementsContainingID(codeOwnersFile, pluginName);
|
||||
} catch (e) {
|
||||
assertError(e);
|
||||
throw new Error(
|
||||
chalk.red(
|
||||
` There was an error removing code owners statement for plugin ${chalk.cyan(
|
||||
@@ -165,6 +171,7 @@ export const removeReferencesFromAppPackage = async (
|
||||
'utf-8',
|
||||
);
|
||||
} catch (e) {
|
||||
assertError(e);
|
||||
throw new Error(
|
||||
chalk.red(
|
||||
` Failed to remove plugin as dependency in app: ${chalk.cyan(
|
||||
@@ -245,6 +252,7 @@ export default async () => {
|
||||
);
|
||||
Task.log();
|
||||
} catch (error) {
|
||||
assertError(error);
|
||||
Task.error(error.message);
|
||||
Task.log('It seems that something went wrong when removing the plugin 🤔');
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import fs from 'fs-extra';
|
||||
import chalk from 'chalk';
|
||||
import semver from 'semver';
|
||||
import { isError } from '@backstage/errors';
|
||||
import { resolve as resolvePath } from 'path';
|
||||
import { run } from '../../lib/run';
|
||||
import { paths } from '../../lib/paths';
|
||||
@@ -59,7 +60,7 @@ export default async () => {
|
||||
try {
|
||||
target = await findTargetVersion(name);
|
||||
} catch (error) {
|
||||
if (error.name === 'NotFoundError') {
|
||||
if (isError(error) && error.name === 'NotFoundError') {
|
||||
console.log(`Package info not found, ignoring package ${name}`);
|
||||
return;
|
||||
}
|
||||
@@ -97,7 +98,7 @@ export default async () => {
|
||||
try {
|
||||
target = await findTargetVersion(name);
|
||||
} catch (error) {
|
||||
if (error.name === 'NotFoundError') {
|
||||
if (isError(error) && error.name === 'NotFoundError') {
|
||||
console.log(`Package info not found, ignoring package ${name}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
import { ExitCodeError } from './errors';
|
||||
import { promisify } from 'util';
|
||||
import { LogFunc } from './logging';
|
||||
import { assertError, ForwardedError } from '@backstage/errors';
|
||||
|
||||
const execFile = promisify(execFileCb);
|
||||
|
||||
@@ -75,10 +76,14 @@ export async function runPlain(cmd: string, ...args: string[]) {
|
||||
const { stdout } = await execFile(cmd, args, { shell: true });
|
||||
return stdout.trim();
|
||||
} catch (error) {
|
||||
if (error.stderr) {
|
||||
process.stderr.write(error.stderr);
|
||||
assertError(error);
|
||||
if ('stderr' in error) {
|
||||
process.stderr.write(error.stderr as Buffer);
|
||||
}
|
||||
throw new ExitCodeError(error.code, [cmd, ...args].join(' '));
|
||||
if (typeof error.code === 'number') {
|
||||
throw new ExitCodeError(error.code, [cmd, ...args].join(' '));
|
||||
}
|
||||
throw new ForwardedError('Unknown execution error', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"dependencies": {
|
||||
"@backstage/cli-common": "^0.1.4",
|
||||
"@backstage/config": "^0.1.9",
|
||||
"@backstage/errors": "^0.1.2",
|
||||
"@types/json-schema": "^7.0.6",
|
||||
"ajv": "^7.0.3",
|
||||
"chokidar": "^3.5.2",
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
import { AppConfig, JsonObject } from '@backstage/config';
|
||||
import { assertError } from '@backstage/errors';
|
||||
|
||||
const ENV_PREFIX = 'APP_CONFIG_';
|
||||
|
||||
@@ -96,6 +97,7 @@ function safeJsonParse(str: string): [Error | null, any] {
|
||||
try {
|
||||
return [null, JSON.parse(str)];
|
||||
} catch (err) {
|
||||
assertError(err);
|
||||
return [err, str];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
import { ConfigSchemaPackageEntry } from './types';
|
||||
import { getProgramFromFiles, generateSchema } from 'typescript-json-schema';
|
||||
import { JsonObject } from '@backstage/config';
|
||||
import { assertError } from '@backstage/errors';
|
||||
|
||||
type Item = {
|
||||
name?: string;
|
||||
@@ -189,6 +190,7 @@ function compileTsSchemas(paths: string[]) {
|
||||
[path.split(sep).join('/')], // Unix paths are expected for all OSes here
|
||||
) as JsonObject | null;
|
||||
} catch (error) {
|
||||
assertError(error);
|
||||
if (error.message !== 'type Config not found') {
|
||||
throw error;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
import { JsonObject, JsonValue } from '@backstage/config';
|
||||
import { assertError } from '@backstage/errors';
|
||||
import { TransformFunc } from './types';
|
||||
import { isObject } from './utils';
|
||||
|
||||
@@ -46,6 +47,7 @@ export async function applyConfigTransforms(
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
assertError(error);
|
||||
throw new Error(`error at ${path}, ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import yaml from 'yaml';
|
||||
import chokidar from 'chokidar';
|
||||
import { resolve as resolvePath, dirname, isAbsolute, basename } from 'path';
|
||||
import { AppConfig } from '@backstage/config';
|
||||
import { ForwardedError } from '@backstage/errors';
|
||||
import {
|
||||
applyConfigTransforms,
|
||||
readEnvConfig,
|
||||
@@ -114,9 +115,7 @@ export async function loadConfig(
|
||||
try {
|
||||
fileConfigs = await loadConfigFiles();
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Failed to read static configuration file, ${error.message}`,
|
||||
);
|
||||
throw new ForwardedError('Failed to read static configuration file', error);
|
||||
}
|
||||
|
||||
const envConfigs = await readEnvConfig(process.env);
|
||||
|
||||
+5
-5
@@ -45,25 +45,25 @@ describe('UrlPatternDiscovery', () => {
|
||||
it('should validate that the pattern is a valid URL', () => {
|
||||
expect(() => {
|
||||
UrlPatternDiscovery.compile('example.com');
|
||||
}).toThrow('Invalid discovery URL pattern, Invalid URL: example.com');
|
||||
}).toThrow("Invalid discovery URL pattern, URL 'example.com' is invalid");
|
||||
|
||||
expect(() => {
|
||||
UrlPatternDiscovery.compile('http://');
|
||||
}).toThrow('Invalid discovery URL pattern, Invalid URL: http://');
|
||||
}).toThrow("Invalid discovery URL pattern, URL 'http://' is invalid");
|
||||
|
||||
expect(() => {
|
||||
UrlPatternDiscovery.compile('abc123');
|
||||
}).toThrow('Invalid discovery URL pattern, Invalid URL: abc123');
|
||||
}).toThrow("Invalid discovery URL pattern, URL 'abc123' is invalid");
|
||||
|
||||
expect(() => {
|
||||
UrlPatternDiscovery.compile('http://example.com:{{pluginId}}');
|
||||
}).toThrow(
|
||||
'Invalid discovery URL pattern, Invalid URL: http://example.com:pluginId',
|
||||
"Invalid discovery URL pattern, URL 'http://example.com:pluginId' is invalid",
|
||||
);
|
||||
|
||||
expect(() => {
|
||||
UrlPatternDiscovery.compile('/{{pluginId}}');
|
||||
}).toThrow('Invalid discovery URL pattern, Invalid URL: /pluginId');
|
||||
}).toThrow("Invalid discovery URL pattern, URL '/pluginId' is invalid");
|
||||
|
||||
expect(() => {
|
||||
UrlPatternDiscovery.compile('http://localhost/{{pluginId}}?forbidden');
|
||||
|
||||
+16
-13
@@ -16,6 +16,8 @@
|
||||
|
||||
import { DiscoveryApi } from '@backstage/core-plugin-api';
|
||||
|
||||
const ERROR_PREFIX = 'Invalid discovery URL pattern,';
|
||||
|
||||
/**
|
||||
* UrlPatternDiscovery is a lightweight DiscoveryApi implementation.
|
||||
* It uses a single template string to construct URLs for each plugin.
|
||||
@@ -30,21 +32,22 @@ export class UrlPatternDiscovery implements DiscoveryApi {
|
||||
*/
|
||||
static compile(pattern: string): UrlPatternDiscovery {
|
||||
const parts = pattern.split(/\{\{\s*pluginId\s*\}\}/);
|
||||
const urlStr = parts.join('pluginId');
|
||||
|
||||
let url;
|
||||
try {
|
||||
const urlStr = parts.join('pluginId');
|
||||
const url = new URL(urlStr);
|
||||
if (url.hash) {
|
||||
throw new Error('URL must not have a hash');
|
||||
}
|
||||
if (url.search) {
|
||||
throw new Error('URL must not have a query');
|
||||
}
|
||||
if (urlStr.endsWith('/')) {
|
||||
throw new Error('URL must not end with a slash');
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error(`Invalid discovery URL pattern, ${error.message}`);
|
||||
url = new URL(urlStr);
|
||||
} catch {
|
||||
throw new Error(`${ERROR_PREFIX} URL '${urlStr}' is invalid`);
|
||||
}
|
||||
if (url.hash) {
|
||||
throw new Error(`${ERROR_PREFIX} URL must not have a hash`);
|
||||
}
|
||||
if (url.search) {
|
||||
throw new Error(`${ERROR_PREFIX} URL must not have a query`);
|
||||
}
|
||||
if (urlStr.endsWith('/')) {
|
||||
throw new Error(`${ERROR_PREFIX} URL must not end with a slash`);
|
||||
}
|
||||
|
||||
return new UrlPatternDiscovery(parts);
|
||||
|
||||
@@ -83,7 +83,7 @@ const MockRouteSource = <T extends { [name in string]: string }>(props: {
|
||||
} catch (ex) {
|
||||
return (
|
||||
<div>
|
||||
Error at {props.name}: {ex.message}
|
||||
Error at {props.name}, {String(ex)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -293,12 +293,12 @@ describe('discovery', () => {
|
||||
|
||||
expect(
|
||||
rendered.getByText(
|
||||
`Error at outsideWithParams: Cannot route to ${ref3} with parent ${ref5} as it has parameters`,
|
||||
`Error at outsideWithParams, Error: Cannot route to ${ref3} with parent ${ref5} as it has parameters`,
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
rendered.getByText(
|
||||
`Error at outsideNoParams: Cannot route to ${ref3} with parent ${ref5} as it has parameters`,
|
||||
`Error at outsideNoParams, Error: Cannot route to ${ref3} with parent ${ref5} as it has parameters`,
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -21,6 +21,7 @@ import ListItemText from '@material-ui/core/ListItemText';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import React, { useState } from 'react';
|
||||
import { isError } from '@backstage/errors';
|
||||
import { PendingAuthRequest } from '@backstage/core-plugin-api';
|
||||
|
||||
export type LoginRequestListItemClassKey = 'root';
|
||||
@@ -42,14 +43,14 @@ type RowProps = {
|
||||
|
||||
const LoginRequestListItem = ({ request, busy, setBusy }: RowProps) => {
|
||||
const classes = useItemStyles();
|
||||
const [error, setError] = useState<Error>();
|
||||
const [error, setError] = useState<string>();
|
||||
|
||||
const handleContinue = async () => {
|
||||
setBusy(true);
|
||||
try {
|
||||
await request.trigger();
|
||||
} catch (e) {
|
||||
setError(e);
|
||||
setError(isError(e) ? e.message : 'An unspecified error occurred');
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
@@ -64,13 +65,7 @@ const LoginRequestListItem = ({ request, busy, setBusy }: RowProps) => {
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
primary={request.provider.title}
|
||||
secondary={
|
||||
error && (
|
||||
<Typography color="error">
|
||||
{error.message || 'An unspecified error occurred'}
|
||||
</Typography>
|
||||
)
|
||||
}
|
||||
secondary={error && <Typography color="error">{error}</Typography>}
|
||||
/>
|
||||
<Button color="primary" variant="contained" onClick={handleContinue}>
|
||||
Log in
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
auth0AuthApiRef,
|
||||
errorApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { ForwardedError } from '@backstage/errors';
|
||||
|
||||
const Component: ProviderComponent = ({ onResult }) => {
|
||||
const auth0AuthApi = useApi(auth0AuthApiRef);
|
||||
@@ -53,7 +54,7 @@ const Component: ProviderComponent = ({ onResult }) => {
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
errorApi.post(error);
|
||||
errorApi.post(new ForwardedError('Auth0 login failed', error));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
} from './types';
|
||||
import { useApi, errorApiRef } from '@backstage/core-plugin-api';
|
||||
import { GridItem } from './styles';
|
||||
import { ForwardedError } from '@backstage/errors';
|
||||
|
||||
const Component: ProviderComponent = ({ config, onResult }) => {
|
||||
const { apiRef, title, message } = config as SignInProviderConfig;
|
||||
@@ -55,7 +56,7 @@ const Component: ProviderComponent = ({ config, onResult }) => {
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
errorApi.post(error);
|
||||
errorApi.post(new ForwardedError('Login failed', error));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -51,12 +51,18 @@ export function createRoutableExtension<
|
||||
try {
|
||||
useRouteRef(mountPoint);
|
||||
} catch (error) {
|
||||
if (error?.message.startsWith('No path for ')) {
|
||||
throw new Error(
|
||||
`Routable extension component with mount point ${mountPoint} was not discovered in the app element tree. ` +
|
||||
'Routable extension components may not be rendered by other components and must be ' +
|
||||
'directly available as an element within the App provider component.',
|
||||
);
|
||||
if (typeof error === 'object' && error !== null) {
|
||||
const { message } = error as { message?: unknown };
|
||||
if (
|
||||
typeof message === 'string' &&
|
||||
message.startsWith('No path for ')
|
||||
) {
|
||||
throw new Error(
|
||||
`Routable extension component with mount point ${mountPoint} was not discovered in the app element tree. ` +
|
||||
'Routable extension components may not be rendered by other components and must be ' +
|
||||
'directly available as an element within the App provider component.',
|
||||
);
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
@@ -45,9 +45,7 @@ async function createTemporaryAppFolder(tempDir: string) {
|
||||
try {
|
||||
await fs.mkdir(tempDir);
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Failed to create temporary app directory: ${error.message}`,
|
||||
);
|
||||
throw new Error(`Failed to create temporary app directory, ${error}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -156,7 +154,7 @@ export default async (cmd: Command): Promise<void> => {
|
||||
Task.log();
|
||||
Task.exit();
|
||||
} catch (error) {
|
||||
Task.error(error.message);
|
||||
Task.error(String(error));
|
||||
|
||||
Task.log('It seems that something went wrong when creating the app 🤔');
|
||||
Task.log('We are going to clean up, and then you can try again.');
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli-common": "^0.1.1",
|
||||
"@backstage/errors": "^0.1.2",
|
||||
"@types/fs-extra": "^9.0.1",
|
||||
"@types/node": "^14.14.32",
|
||||
"chalk": "^4.0.0",
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { assertError } from '@backstage/errors';
|
||||
import {
|
||||
spawn,
|
||||
execFile as execFileCb,
|
||||
@@ -66,11 +67,12 @@ export async function runPlain(cmd: string[], options?: SpawnOptions) {
|
||||
});
|
||||
return stdout.trim();
|
||||
} catch (error) {
|
||||
assertError(error);
|
||||
if (error.stdout) {
|
||||
process.stdout.write(error.stdout);
|
||||
process.stdout.write(error.stdout as Buffer);
|
||||
}
|
||||
if (error.stderr) {
|
||||
process.stderr.write(error.stderr);
|
||||
process.stderr.write(error.stderr as Buffer);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
@@ -147,6 +149,7 @@ export async function waitForPageWithText(
|
||||
);
|
||||
break;
|
||||
} catch (error) {
|
||||
assertError(error);
|
||||
if (error.message.match(EXPECTED_LOAD_ERRORS)) {
|
||||
loadAttempts++;
|
||||
if (loadAttempts >= maxLoadAttempts) {
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
import { isChildPath } from '@backstage/backend-common';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { assertError, ForwardedError } from '@backstage/errors';
|
||||
import { ScmIntegrationRegistry } from '@backstage/integration';
|
||||
import { spawn } from 'child_process';
|
||||
import fs from 'fs-extra';
|
||||
@@ -155,8 +156,9 @@ export const getMkdocsYml = async (
|
||||
mkdocsYmlPath = path.join(inputDir, 'mkdocs.yml');
|
||||
mkdocsYmlFileString = await fs.readFile(mkdocsYmlPath, 'utf8');
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Could not read MkDocs YAML config file mkdocs.yml or mkdocs.yaml for validation: ${error.message}`,
|
||||
throw new ForwardedError(
|
||||
'Could not read MkDocs YAML config file mkdocs.yml or mkdocs.yaml for validation',
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -225,6 +227,7 @@ export const patchMkdocsYmlPreBuild = async (
|
||||
try {
|
||||
mkdocsYmlFileString = await fs.readFile(mkdocsYmlPath, 'utf8');
|
||||
} catch (error) {
|
||||
assertError(error);
|
||||
logger.warn(
|
||||
`Could not read MkDocs YAML config file ${mkdocsYmlPath} before running the generator: ${error.message}`,
|
||||
);
|
||||
@@ -241,6 +244,7 @@ export const patchMkdocsYmlPreBuild = async (
|
||||
throw new Error('Bad YAML format.');
|
||||
}
|
||||
} catch (error) {
|
||||
assertError(error);
|
||||
logger.warn(
|
||||
`Error in parsing YAML at ${mkdocsYmlPath} before running the generator. ${error.message}`,
|
||||
);
|
||||
@@ -279,6 +283,7 @@ export const patchMkdocsYmlPreBuild = async (
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
assertError(error);
|
||||
logger.warn(
|
||||
`Could not write to ${mkdocsYmlPath} after updating it before running the generator. ${error.message}`,
|
||||
);
|
||||
@@ -307,6 +312,7 @@ export const addBuildTimestampMetadata = async (
|
||||
try {
|
||||
json = await fs.readJson(techdocsMetadataPath);
|
||||
} catch (err) {
|
||||
assertError(err);
|
||||
const message = `Invalid JSON at ${techdocsMetadataPath} with error ${err.message}`;
|
||||
logger.error(message);
|
||||
throw new Error(message);
|
||||
|
||||
@@ -36,6 +36,7 @@ import {
|
||||
GeneratorRunInType,
|
||||
GeneratorRunOptions,
|
||||
} from './types';
|
||||
import { ForwardedError } from '@backstage/errors';
|
||||
|
||||
export class TechdocsGenerator implements GeneratorBase {
|
||||
/**
|
||||
@@ -151,8 +152,9 @@ export class TechdocsGenerator implements GeneratorBase {
|
||||
this.logger.debug(
|
||||
`Failed to generate docs from ${inputDir} into ${outputDir}`,
|
||||
);
|
||||
throw new Error(
|
||||
`Failed to generate docs from ${inputDir} into ${outputDir} with error ${error.message}`,
|
||||
throw new ForwardedError(
|
||||
'Failed to generate docs from ${inputDir} into ${outputDir}',
|
||||
error,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { NotModifiedError } from '@backstage/errors';
|
||||
import { assertError } from '@backstage/errors';
|
||||
import { UrlReader } from '@backstage/backend-common';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { Logger } from 'winston';
|
||||
@@ -40,8 +40,9 @@ export class UrlPreparer implements PreparerBase {
|
||||
logger: this.logger,
|
||||
});
|
||||
} catch (error) {
|
||||
assertError(error);
|
||||
// NotModifiedError means that etag based cache is still valid.
|
||||
if (error instanceof NotModifiedError) {
|
||||
if (error.name === 'NotModifiedError') {
|
||||
this.logger.debug(`Cache is valid for etag ${options?.etag}`);
|
||||
} else {
|
||||
this.logger.debug(
|
||||
|
||||
@@ -286,7 +286,7 @@ describe('AwsS3Publish', () => {
|
||||
const fails = publisher.fetchTechDocsMetadata(invalidEntityName);
|
||||
|
||||
await expect(fails).rejects.toMatchObject({
|
||||
message: `TechDocs metadata fetch failed, The file ${techDocsMetadaFilePath} does not exist!`,
|
||||
message: `TechDocs metadata fetch failed; caused by Error: The file ${techDocsMetadaFilePath} does not exist!`,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
import { Entity, EntityName } from '@backstage/catalog-model';
|
||||
import { Config } from '@backstage/config';
|
||||
import { assertError, ForwardedError } from '@backstage/errors';
|
||||
import aws, { Credentials } from 'aws-sdk';
|
||||
import { ListObjectsV2Output } from 'aws-sdk/clients/s3';
|
||||
import { CredentialsOptions } from 'aws-sdk/lib/credentials';
|
||||
@@ -49,7 +50,7 @@ const streamToBuffer = (stream: Readable): Promise<Buffer> => {
|
||||
stream.on('error', reject);
|
||||
stream.on('end', () => resolve(Buffer.concat(chunks)));
|
||||
} catch (e) {
|
||||
throw new Error(`Unable to parse the response data ${e.message}`);
|
||||
throw new ForwardedError('Unable to parse the response data', e);
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -204,6 +205,7 @@ export class AwsS3Publish implements PublisherBase {
|
||||
prefix: remoteFolder,
|
||||
});
|
||||
} catch (e) {
|
||||
assertError(e);
|
||||
this.logger.error(
|
||||
`Unable to list files for Entity ${entity.metadata.name}: ${e.message}`,
|
||||
);
|
||||
@@ -312,12 +314,13 @@ export class AwsS3Publish implements PublisherBase {
|
||||
|
||||
resolve(techdocsMetadata);
|
||||
} catch (err) {
|
||||
assertError(err);
|
||||
this.logger.error(err.message);
|
||||
reject(new Error(err.message));
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
throw new Error(`TechDocs metadata fetch failed, ${e.message}`);
|
||||
throw new ForwardedError('TechDocs metadata fetch failed', e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -351,6 +354,7 @@ export class AwsS3Publish implements PublisherBase {
|
||||
|
||||
res.send(await streamToBuffer(stream));
|
||||
} catch (err) {
|
||||
assertError(err);
|
||||
this.logger.warn(
|
||||
`TechDocs S3 router failed to serve static files from bucket ${this.bucketName} at key ${filePath}: ${err.message}`,
|
||||
);
|
||||
@@ -396,6 +400,7 @@ export class AwsS3Publish implements PublisherBase {
|
||||
try {
|
||||
newPath = lowerCaseEntityTripletInStoragePath(file);
|
||||
} catch (e) {
|
||||
assertError(e);
|
||||
this.logger.warn(e.message);
|
||||
return;
|
||||
}
|
||||
@@ -424,6 +429,7 @@ export class AwsS3Publish implements PublisherBase {
|
||||
.promise();
|
||||
}
|
||||
} catch (e) {
|
||||
assertError(e);
|
||||
this.logger.warn(`Unable to migrate ${file}: ${e.message}`);
|
||||
}
|
||||
}, f),
|
||||
|
||||
@@ -201,7 +201,7 @@ describe('AzureBlobStoragePublish', () => {
|
||||
let error;
|
||||
try {
|
||||
await publisher.publish({ entity, directory });
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
error = e;
|
||||
}
|
||||
|
||||
@@ -305,7 +305,7 @@ describe('AzureBlobStoragePublish', () => {
|
||||
const fails = publisher.fetchTechDocsMetadata(invalidEntityName);
|
||||
|
||||
await expect(fails).rejects.toMatchObject({
|
||||
message: `TechDocs metadata fetch failed, The file ${techDocsMetadaFilePath} does not exist!`,
|
||||
message: `TechDocs metadata fetch failed; caused by Error: The file ${techDocsMetadaFilePath} does not exist!`,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
} from '@azure/storage-blob';
|
||||
import { Entity, EntityName } from '@backstage/catalog-model';
|
||||
import { Config } from '@backstage/config';
|
||||
import { assertError, ForwardedError } from '@backstage/errors';
|
||||
import express from 'express';
|
||||
import JSON5 from 'json5';
|
||||
import limiterFactory from 'p-limit';
|
||||
@@ -132,6 +133,7 @@ export class AzureBlobStoragePublish implements PublisherBase {
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
assertError(e);
|
||||
this.logger.error(`from Azure Blob Storage client library: ${e.message}`);
|
||||
}
|
||||
|
||||
@@ -165,6 +167,7 @@ export class AzureBlobStoragePublish implements PublisherBase {
|
||||
maxPageSize: BATCH_CONCURRENCY,
|
||||
});
|
||||
} catch (e) {
|
||||
assertError(e);
|
||||
this.logger.error(
|
||||
`Unable to list files for Entity ${entity.metadata.name}: ${e.message}`,
|
||||
);
|
||||
@@ -307,7 +310,7 @@ export class AzureBlobStoragePublish implements PublisherBase {
|
||||
);
|
||||
return techdocsMetadata;
|
||||
} catch (e) {
|
||||
throw new Error(`TechDocs metadata fetch failed, ${e.message}`);
|
||||
throw new ForwardedError('TechDocs metadata fetch failed', e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,6 +389,7 @@ export class AzureBlobStoragePublish implements PublisherBase {
|
||||
try {
|
||||
newPath = lowerCaseEntityTripletInStoragePath(originalPath);
|
||||
} catch (e) {
|
||||
assertError(e);
|
||||
this.logger.warn(e.message);
|
||||
return;
|
||||
}
|
||||
@@ -395,6 +399,7 @@ export class AzureBlobStoragePublish implements PublisherBase {
|
||||
this.logger.verbose(`Migrating ${originalPath}`);
|
||||
await this.renameBlob(originalPath, newPath, removeOriginal);
|
||||
} catch (e) {
|
||||
assertError(e);
|
||||
this.logger.warn(`Unable to migrate ${originalPath}: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
import { Entity, EntityName } from '@backstage/catalog-model';
|
||||
import { Config } from '@backstage/config';
|
||||
import { assertError } from '@backstage/errors';
|
||||
import { File, FileExistsResponse, Storage } from '@google-cloud/storage';
|
||||
import express from 'express';
|
||||
import JSON5 from 'json5';
|
||||
@@ -112,6 +113,7 @@ export class GoogleGCSPublish implements PublisherBase {
|
||||
isAvailable: true,
|
||||
};
|
||||
} catch (err) {
|
||||
assertError(err);
|
||||
this.logger.error(
|
||||
`Could not retrieve metadata about the GCS bucket ${this.bucketName}. ` +
|
||||
'Make sure the bucket exists. Also make sure that authentication is setup either by explicitly defining ' +
|
||||
@@ -142,6 +144,7 @@ export class GoogleGCSPublish implements PublisherBase {
|
||||
);
|
||||
existingFiles = await this.getFilesForFolder(remoteFolder);
|
||||
} catch (e) {
|
||||
assertError(e);
|
||||
this.logger.error(
|
||||
`Unable to list files for Entity ${entity.metadata.name}: ${e.message}`,
|
||||
);
|
||||
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
getHeadersForFileExtension,
|
||||
lowerCaseEntityTripletInStoragePath,
|
||||
} from './helpers';
|
||||
import { assertError } from '@backstage/errors';
|
||||
|
||||
// TODO: Use a more persistent storage than node_modules or /tmp directory.
|
||||
// Make it configurable with techdocs.publisher.local.publishDirectory
|
||||
@@ -134,6 +135,7 @@ export class LocalPublish implements PublisherBase {
|
||||
try {
|
||||
return await fs.readJson(metadataPath);
|
||||
} catch (err) {
|
||||
assertError(err);
|
||||
this.logger.error(
|
||||
`Unable to read techdocs_metadata.json at ${metadataPath}. Error: ${err}`,
|
||||
);
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { assertError } from '@backstage/errors';
|
||||
import { File } from '@google-cloud/storage';
|
||||
import { Writable } from 'stream';
|
||||
import { Logger } from 'winston';
|
||||
@@ -42,6 +43,7 @@ export class MigrateWriteStream extends Writable {
|
||||
try {
|
||||
newFile = lowerCaseEntityTripletInStoragePath(file.name);
|
||||
} catch (e) {
|
||||
assertError(e);
|
||||
this.logger.warn(e.message);
|
||||
next();
|
||||
return;
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
ReadinessResponse,
|
||||
TechDocsMetadata,
|
||||
} from './types';
|
||||
import { assertError, ForwardedError } from '@backstage/errors';
|
||||
|
||||
const streamToBuffer = (stream: Stream | Readable): Promise<Buffer> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -40,7 +41,7 @@ const streamToBuffer = (stream: Stream | Readable): Promise<Buffer> => {
|
||||
stream.on('error', reject);
|
||||
stream.on('end', () => resolve(Buffer.concat(chunks)));
|
||||
} catch (e) {
|
||||
throw new Error(`Unable to parse the response data ${e.message}`);
|
||||
throw new ForwardedError('Unable to parse the response data', e);
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -118,6 +119,7 @@ export class OpenStackSwiftPublish implements PublisherBase {
|
||||
isAvailable: false,
|
||||
};
|
||||
} catch (err) {
|
||||
assertError(err);
|
||||
this.logger.error(`from OpenStack client library: ${err.message}`);
|
||||
return {
|
||||
isAvailable: false,
|
||||
@@ -203,6 +205,7 @@ export class OpenStackSwiftPublish implements PublisherBase {
|
||||
|
||||
resolve(techdocsMetadata);
|
||||
} catch (err) {
|
||||
assertError(err);
|
||||
this.logger.error(err.message);
|
||||
reject(new Error(err.message));
|
||||
}
|
||||
@@ -245,6 +248,7 @@ export class OpenStackSwiftPublish implements PublisherBase {
|
||||
|
||||
res.send(await streamToBuffer(stream));
|
||||
} catch (err) {
|
||||
assertError(err);
|
||||
this.logger.warn(
|
||||
`TechDocs OpenStack swift router failed to serve content from container ${this.containerName} at path ${filePath}: ${err.message}`,
|
||||
);
|
||||
@@ -276,6 +280,7 @@ export class OpenStackSwiftPublish implements PublisherBase {
|
||||
}
|
||||
return false;
|
||||
} catch (err) {
|
||||
assertError(err);
|
||||
this.logger.warn(err.message);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
BackstageIdentity,
|
||||
AuthProviderConfig,
|
||||
} from '../../providers/types';
|
||||
import { InputError, NotAllowedError } from '@backstage/errors';
|
||||
import { InputError, isError, NotAllowedError } from '@backstage/errors';
|
||||
import { TokenIssuer } from '../../identity/types';
|
||||
import { readState, verifyNonce } from './helpers';
|
||||
import { postMessageResponse, ensuresXRequestedWith } from '../flow';
|
||||
@@ -153,13 +153,13 @@ export class OAuthAdapter implements AuthProviderRouteHandlers {
|
||||
response,
|
||||
});
|
||||
} catch (error) {
|
||||
const { name, message } = isError(error)
|
||||
? error
|
||||
: new Error('Encountered invalid error'); // Being a bit safe and not forwarding the bad value
|
||||
// post error message back to popup if failure
|
||||
return postMessageResponse(res, appOrigin, {
|
||||
type: 'authorization_response',
|
||||
error: {
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
},
|
||||
error: { name, message },
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -220,7 +220,7 @@ export class OAuthAdapter implements AuthProviderRouteHandlers {
|
||||
|
||||
res.status(200).json(response);
|
||||
} catch (error) {
|
||||
res.status(401).send(`${error.message}`);
|
||||
res.status(401).send(String(error));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
import { AuthProviderRouteHandlers, AuthProviderFactory } from '../types';
|
||||
import { postMessageResponse } from '../../lib/flow';
|
||||
import { TokenIssuer } from '../../identity/types';
|
||||
import { isError } from '@backstage/errors';
|
||||
|
||||
type SamlInfo = {
|
||||
fullProfile: any;
|
||||
@@ -93,12 +94,12 @@ export class SamlAuthProvider implements AuthProviderRouteHandlers {
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
const { name, message } = isError(error)
|
||||
? error
|
||||
: new Error('Encountered invalid error'); // Being a bit safe and not forwarding the bad value
|
||||
return postMessageResponse(res, this.appUrl, {
|
||||
type: 'authorization_response',
|
||||
error: {
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
},
|
||||
error: { name, message },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
PluginDatabaseManager,
|
||||
PluginEndpointDiscovery,
|
||||
} from '@backstage/backend-common';
|
||||
import { NotFoundError } from '@backstage/errors';
|
||||
import { assertError, NotFoundError } from '@backstage/errors';
|
||||
import { CatalogClient } from '@backstage/catalog-client';
|
||||
import { Config } from '@backstage/config';
|
||||
import { createOidcRouter, DatabaseKeyStore, TokenFactory } from '../identity';
|
||||
@@ -121,6 +121,7 @@ export async function createRouter({
|
||||
|
||||
router.use(`/${providerId}`, r);
|
||||
} catch (e) {
|
||||
assertError(e);
|
||||
if (process.env.NODE_ENV !== 'development') {
|
||||
throw new Error(
|
||||
`Failed to initialize ${providerId} auth provider, ${e.message}`,
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.9.4",
|
||||
"@backstage/config": "^0.1.10",
|
||||
"@backstage/errors": "^0.1.2",
|
||||
"@backstage/plugin-catalog-backend": "^0.17.0",
|
||||
"@types/ldapjs": "^2.2.0",
|
||||
"ldapjs": "^2.2.0",
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ForwardedError } from '@backstage/errors';
|
||||
import ldap, { Client, SearchEntry, SearchOptions } from 'ldapjs';
|
||||
import { Logger } from 'winston';
|
||||
import { BindConfig } from './config';
|
||||
@@ -120,7 +121,7 @@ export class LdapClient {
|
||||
clearInterval(logInterval);
|
||||
});
|
||||
} catch (e) {
|
||||
throw new Error(`LDAP search at DN "${dn}" failed, ${e.message}`);
|
||||
throw new ForwardedError(`LDAP search at DN "${dn}" failed`, e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,7 +168,7 @@ export class LdapClient {
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
throw new Error(`LDAP search at DN "${dn}" failed, ${e.message}`);
|
||||
throw new ForwardedError(`LDAP search at DN "${dn}" failed`, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import { Entity, stringifyEntityRef } from '@backstage/catalog-model';
|
||||
import { ConflictError, NotFoundError } from '@backstage/errors';
|
||||
import { ConflictError, isError, NotFoundError } from '@backstage/errors';
|
||||
import { Knex } from 'knex';
|
||||
import lodash from 'lodash';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
@@ -550,7 +550,10 @@ export class DefaultProcessingDatabase implements ProcessingDatabase {
|
||||
return result.rowCount === 1 || result.length === 1;
|
||||
} catch (error) {
|
||||
// SQLite reached this rather than the rowCount check above
|
||||
if (error.message.includes('UNIQUE constraint failed')) {
|
||||
if (
|
||||
isError(error) &&
|
||||
error.message.includes('UNIQUE constraint failed')
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
throw error;
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
import { UrlReader } from '@backstage/backend-common';
|
||||
import { LocationSpec } from '@backstage/catalog-model';
|
||||
import { isError } from '@backstage/errors';
|
||||
import limiterFactory from 'p-limit';
|
||||
import * as result from './results';
|
||||
import {
|
||||
@@ -50,7 +51,7 @@ export class AwsS3DiscoveryProcessor implements CatalogProcessor {
|
||||
} catch (error) {
|
||||
const message = `Unable to read ${location.type}, ${error}`;
|
||||
|
||||
if (error.name === 'NotFoundError') {
|
||||
if (isError(error) && error.name === 'NotFoundError') {
|
||||
if (!optional) {
|
||||
emit(result.notFoundError(location, message));
|
||||
}
|
||||
|
||||
@@ -255,7 +255,7 @@ export class LocationReaders implements LocationReader {
|
||||
const message = `Policy check failed while analyzing entity ${kind}:${namespace}/${name} at ${stringifyLocationReference(
|
||||
item.location,
|
||||
)}, ${e}`;
|
||||
emit(result.inputError(item.location, e.message));
|
||||
emit(result.inputError(item.location, message));
|
||||
logger.warn(message);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
entityEnvelopeSchemaValidator,
|
||||
stringifyEntityRef,
|
||||
} from '@backstage/catalog-model';
|
||||
import { serializeError } from '@backstage/errors';
|
||||
import { assertError, serializeError } from '@backstage/errors';
|
||||
import { Hash } from 'crypto';
|
||||
import stableStringify from 'fast-json-stable-stringify';
|
||||
import { Logger } from 'winston';
|
||||
@@ -257,6 +257,7 @@ export class DefaultCatalogProcessingEngine implements CatalogProcessingEngine {
|
||||
|
||||
track.markSuccessfulWithChanges(setOfThingsToStitch.size);
|
||||
} catch (error) {
|
||||
assertError(error);
|
||||
track.markFailed(error);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -23,7 +23,12 @@ import {
|
||||
stringifyEntityRef,
|
||||
stringifyLocationReference,
|
||||
} from '@backstage/catalog-model';
|
||||
import { ConflictError, InputError, NotAllowedError } from '@backstage/errors';
|
||||
import {
|
||||
assertError,
|
||||
ConflictError,
|
||||
InputError,
|
||||
NotAllowedError,
|
||||
} from '@backstage/errors';
|
||||
import { JsonValue } from '@backstage/config';
|
||||
import { ScmIntegrationRegistry } from '@backstage/integration';
|
||||
import path from 'path';
|
||||
@@ -160,6 +165,7 @@ export class DefaultCatalogProcessingOrchestrator
|
||||
ok: collectorResults.errors.length === 0,
|
||||
};
|
||||
} catch (error) {
|
||||
assertError(error);
|
||||
return {
|
||||
ok: false,
|
||||
errors: collector.results().errors.concat(error),
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
ORIGIN_LOCATION_ANNOTATION,
|
||||
stringifyLocationReference,
|
||||
} from '@backstage/catalog-model';
|
||||
import { assertError } from '@backstage/errors';
|
||||
import { Logger } from 'winston';
|
||||
import { CatalogProcessorResult } from '../ingestion';
|
||||
import { locationSpecToLocationEntity } from '../util/conversion';
|
||||
@@ -73,6 +74,7 @@ export class ProcessorOutputCollector {
|
||||
try {
|
||||
entity = validateEntityEnvelope(i.entity);
|
||||
} catch (e) {
|
||||
assertError(e);
|
||||
this.logger.debug(`Envelope validation failed at ${i.location}, ${e}`);
|
||||
this.errors.push(e);
|
||||
return;
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
"@backstage/catalog-model": "^0.9.4",
|
||||
"@backstage/core-components": "^0.7.0",
|
||||
"@backstage/core-plugin-api": "^0.1.10",
|
||||
"@backstage/errors": "^0.1.2",
|
||||
"@backstage/integration": "^0.6.8",
|
||||
"@backstage/integration-react": "^0.1.12",
|
||||
"@backstage/plugin-catalog-react": "^0.6.0",
|
||||
|
||||
@@ -113,8 +113,8 @@ export const StepInitAnalyzeUrl = ({
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
setError(e.data?.error?.message ?? e.message);
|
||||
} catch (e: any) {
|
||||
setError(e?.data?.error?.message ?? e.message);
|
||||
setSubmitted(false);
|
||||
}
|
||||
},
|
||||
|
||||
+2
@@ -16,6 +16,7 @@
|
||||
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { errorApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
import { assertError } from '@backstage/errors';
|
||||
import {
|
||||
catalogApiRef,
|
||||
formatEntityRefTitle,
|
||||
@@ -174,6 +175,7 @@ export const StepPrepareCreatePullRequest = ({
|
||||
{ notRepeatable: true },
|
||||
);
|
||||
} catch (e) {
|
||||
assertError(e);
|
||||
setError(e.message);
|
||||
setSubmitted(false);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import { PrepareResult, ReviewResult } from '../useImportState';
|
||||
import { configApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
import { Link } from '@backstage/core-components';
|
||||
import { stringifyEntityRef } from '@backstage/catalog-model';
|
||||
import { assertError } from '@backstage/errors';
|
||||
|
||||
type Props = {
|
||||
prepareResult: PrepareResult;
|
||||
@@ -88,6 +89,7 @@ export const StepReviewLocation = ({
|
||||
locations,
|
||||
});
|
||||
} catch (e) {
|
||||
assertError(e);
|
||||
// TODO: this error should be handled differently. We add it as 'optional' and
|
||||
// it is not uncommon that a PR has not been merged yet.
|
||||
if (
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
"@backstage/core-app-api": "^0.1.17",
|
||||
"@backstage/core-components": "^0.7.0",
|
||||
"@backstage/core-plugin-api": "^0.1.10",
|
||||
"@backstage/errors": "^0.1.2",
|
||||
"@backstage/integration": "^0.6.8",
|
||||
"@backstage/version-bridge": "^0.1.0",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
|
||||
@@ -33,6 +33,7 @@ import { useUnregisterEntityDialogState } from './useUnregisterEntityDialogState
|
||||
|
||||
import { alertApiRef, configApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
import { Progress, ResponseErrorPanel } from '@backstage/core-components';
|
||||
import { assertError } from '@backstage/errors';
|
||||
|
||||
const useStyles = makeStyles({
|
||||
advancedButton: {
|
||||
@@ -70,6 +71,7 @@ const Contents = ({
|
||||
await state.unregisterLocation();
|
||||
onConfirm();
|
||||
} catch (err) {
|
||||
assertError(err);
|
||||
alertApi.post({ message: err.message });
|
||||
} finally {
|
||||
setBusy(false);
|
||||
@@ -87,6 +89,7 @@ const Contents = ({
|
||||
await state.deleteEntity();
|
||||
onConfirm();
|
||||
} catch (err) {
|
||||
assertError(err);
|
||||
alertApi.post({ message: err.message });
|
||||
} finally {
|
||||
setBusy(false);
|
||||
|
||||
@@ -19,6 +19,7 @@ import { catalogApiRef } from '@backstage/plugin-catalog-react';
|
||||
import { Button, Dialog, DialogActions, DialogTitle } from '@material-ui/core';
|
||||
import React, { useState } from 'react';
|
||||
import { alertApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
import { assertError } from '@backstage/errors';
|
||||
|
||||
type Props = {
|
||||
open: boolean;
|
||||
@@ -44,6 +45,7 @@ export const DeleteEntityDialog = ({
|
||||
await catalogApi.removeEntityByUid(uid!);
|
||||
onConfirm();
|
||||
} catch (err) {
|
||||
assertError(err);
|
||||
alertApi.post({ message: err.message });
|
||||
} finally {
|
||||
setBusy(false);
|
||||
|
||||
@@ -208,7 +208,7 @@ describe('CodeCoverageUtils', () => {
|
||||
};
|
||||
// @ts-ignore
|
||||
utils.validateRequestBody(mockRequest as Request);
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
err = error;
|
||||
}
|
||||
expect(err?.message).toEqual('Content-Type missing');
|
||||
@@ -226,7 +226,7 @@ describe('CodeCoverageUtils', () => {
|
||||
|
||||
// @ts-ignore
|
||||
utils.validateRequestBody(mockRequest as Request);
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
err = error;
|
||||
}
|
||||
expect(err?.message).toEqual('Illegal Content-Type');
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
"@backstage/backend-common": "^0.9.4",
|
||||
"@backstage/catalog-model": "^0.9.3",
|
||||
"@backstage/config": "^0.1.10",
|
||||
"@backstage/errors": "^0.1.2",
|
||||
"@backstage/plugin-kubernetes-common": "^0.1.4",
|
||||
"@google-cloud/container": "^2.2.0",
|
||||
"@kubernetes/client-node": "^0.15.0",
|
||||
|
||||
@@ -213,7 +213,7 @@ describe('GkeClusterLocator', () => {
|
||||
} as any);
|
||||
|
||||
await expect(sut.getClusters()).rejects.toThrow(
|
||||
'There was an error retrieving clusters from GKE for projectId=some-project region=some-region : [some error]',
|
||||
'There was an error retrieving clusters from GKE for projectId=some-project region=some-region; caused by Error: some error',
|
||||
);
|
||||
|
||||
expect(mockedListClusters).toBeCalledTimes(1);
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
import { Config } from '@backstage/config';
|
||||
import { ForwardedError } from '@backstage/errors';
|
||||
import * as container from '@google-cloud/container';
|
||||
import { GKEClusterDetails, KubernetesClustersSupplier } from '../types/types';
|
||||
|
||||
@@ -65,8 +66,9 @@ export class GkeClusterLocator implements KubernetesClustersSupplier {
|
||||
skipTLSVerify,
|
||||
}));
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
`There was an error retrieving clusters from GKE for projectId=${projectId} region=${region} : [${e.message}]`,
|
||||
throw new ForwardedError(
|
||||
`There was an error retrieving clusters from GKE for projectId=${projectId} region=${region}`,
|
||||
e,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import { ScmIntegrationRegistry } from '@backstage/integration';
|
||||
import { createTemplateAction } from '../../createTemplateAction';
|
||||
import { OctokitProvider } from './OctokitProvider';
|
||||
import { emitterEventNames } from '@octokit/webhooks';
|
||||
import { assertError } from '@backstage/errors';
|
||||
|
||||
type ContentType = 'form' | 'json';
|
||||
|
||||
@@ -130,6 +131,7 @@ export function createGithubWebhookAction(options: {
|
||||
});
|
||||
ctx.logger.info(`Webhook '${webhookUrl}' created successfully`);
|
||||
} catch (e) {
|
||||
assertError(e);
|
||||
ctx.logger.warn(
|
||||
`Failed: create webhook '${webhookUrl}' on repo: '${repo}', ${e.message}`,
|
||||
);
|
||||
|
||||
@@ -19,6 +19,7 @@ import { PassThrough, Writable } from 'stream';
|
||||
import { Logger } from 'winston';
|
||||
import { Git } from '@backstage/backend-common';
|
||||
import { Octokit } from '@octokit/rest';
|
||||
import { assertError } from '@backstage/errors';
|
||||
|
||||
export type RunCommandOptions = {
|
||||
command: string;
|
||||
@@ -152,6 +153,7 @@ export const enableBranchProtectionOnDefaultRepoBranch = async ({
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
assertError(e);
|
||||
if (
|
||||
e.message.includes(
|
||||
'Upgrade to GitHub Pro or make this repository public to enable this feature',
|
||||
|
||||
@@ -22,6 +22,7 @@ import { getRepoSourceDirectory } from './util';
|
||||
import { createTemplateAction } from '../../createTemplateAction';
|
||||
import { Config } from '@backstage/config';
|
||||
import { OctokitProvider } from '../github/OctokitProvider';
|
||||
import { assertError } from '@backstage/errors';
|
||||
|
||||
type Permission = 'pull' | 'push' | 'admin' | 'maintain' | 'triage';
|
||||
type Collaborator = { access: Permission; username: string };
|
||||
@@ -198,6 +199,7 @@ export function createPublishGithubAction(options: {
|
||||
permission,
|
||||
});
|
||||
} catch (e) {
|
||||
assertError(e);
|
||||
ctx.logger.warn(
|
||||
`Skipping ${permission} access for ${team_slug}, ${e.message}`,
|
||||
);
|
||||
@@ -213,6 +215,7 @@ export function createPublishGithubAction(options: {
|
||||
names: topics.map(t => t.toLowerCase()),
|
||||
});
|
||||
} catch (e) {
|
||||
assertError(e);
|
||||
ctx.logger.warn(`Skipping topics ${topics.join(' ')}, ${e.message}`);
|
||||
}
|
||||
}
|
||||
@@ -250,6 +253,7 @@ export function createPublishGithubAction(options: {
|
||||
requireCodeOwnerReviews,
|
||||
});
|
||||
} catch (e) {
|
||||
assertError(e);
|
||||
ctx.logger.warn(
|
||||
`Skipping: default branch protection on '${newRepo.name}', ${e.message}`,
|
||||
);
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { JsonObject } from '@backstage/config';
|
||||
import { assertError } from '@backstage/errors';
|
||||
import { Logger } from 'winston';
|
||||
import {
|
||||
CompletedTaskState,
|
||||
@@ -185,6 +186,7 @@ export class StorageTaskBroker implements TaskBroker {
|
||||
try {
|
||||
callback(undefined, result);
|
||||
} catch (error) {
|
||||
assertError(error);
|
||||
callback(error, { events: [] });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,10 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Task, TaskBroker, WorkflowRunner } from './types';
|
||||
import { LegacyWorkflowRunner } from './LegacyWorkflowRunner';
|
||||
import { assertError } from '@backstage/errors';
|
||||
|
||||
type Options = {
|
||||
taskBroker: TaskBroker;
|
||||
@@ -44,6 +46,7 @@ export class TaskWorker {
|
||||
|
||||
await task.complete('completed', { output });
|
||||
} catch (error) {
|
||||
assertError(error);
|
||||
await task.complete('failed', {
|
||||
error: { name: error.name, message: error.message },
|
||||
});
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
SOURCE_LOCATION_ANNOTATION,
|
||||
} from '@backstage/catalog-model';
|
||||
import { Config } from '@backstage/config';
|
||||
import { assertError } from '@backstage/errors';
|
||||
import fs from 'fs-extra';
|
||||
import os from 'os';
|
||||
import { Logger } from 'winston';
|
||||
@@ -39,6 +40,7 @@ export async function getWorkingDirectory(
|
||||
await fs.access(workingDirectory, fs.constants.F_OK | fs.constants.W_OK);
|
||||
logger.info(`using working directory: ${workingDirectory}`);
|
||||
} catch (err) {
|
||||
assertError(err);
|
||||
logger.error(
|
||||
`working directory ${workingDirectory} ${
|
||||
err.code === 'ENOENT' ? 'does not exist' : 'is not writable'
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
stringifyEntityRef,
|
||||
} from '@backstage/catalog-model';
|
||||
import { Config } from '@backstage/config';
|
||||
import { NotModifiedError } from '@backstage/errors';
|
||||
import { assertError, isError } from '@backstage/errors';
|
||||
import { ScmIntegrationRegistry } from '@backstage/integration';
|
||||
import {
|
||||
GeneratorBase,
|
||||
@@ -131,7 +131,7 @@ export class DocsBuilder {
|
||||
preparedDir = preparerResponse.preparedDir;
|
||||
newEtag = preparerResponse.etag;
|
||||
} catch (err) {
|
||||
if (err instanceof NotModifiedError) {
|
||||
if (isError(err) && err.name === 'NotModifiedError') {
|
||||
// No need to prepare anymore since cache is valid.
|
||||
// Set last check happened to now
|
||||
new BuildMetadataStorage(this.entity.metadata.uid).setLastUpdated();
|
||||
@@ -142,7 +142,7 @@ export class DocsBuilder {
|
||||
);
|
||||
return false;
|
||||
}
|
||||
throw new Error(err.message);
|
||||
throw err;
|
||||
}
|
||||
|
||||
this.logger.info(
|
||||
@@ -195,6 +195,7 @@ export class DocsBuilder {
|
||||
// Not a blocker hence no need to await this.
|
||||
fs.remove(preparedDir);
|
||||
} catch (error) {
|
||||
assertError(error);
|
||||
this.logger.debug(`Error removing prepared directory ${error.message}`);
|
||||
}
|
||||
}
|
||||
@@ -221,6 +222,7 @@ export class DocsBuilder {
|
||||
`Removing generated directory ${outputDir} since the site has been published`,
|
||||
);
|
||||
} catch (error) {
|
||||
assertError(error);
|
||||
this.logger.debug(`Error removing generated directory ${error.message}`);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { Config } from '@backstage/config';
|
||||
import { NotFoundError } from '@backstage/errors';
|
||||
import { assertError, NotFoundError } from '@backstage/errors';
|
||||
import { ScmIntegrationRegistry } from '@backstage/integration';
|
||||
import {
|
||||
GeneratorBuilder,
|
||||
@@ -113,6 +113,7 @@ export class DocsSynchronizer {
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
assertError(e);
|
||||
const msg = `Failed to build the docs page: ${e.message}`;
|
||||
taskLogger.error(msg);
|
||||
this.logger.error(msg, e);
|
||||
|
||||
Reference in New Issue
Block a user