Merge branch 'master' into rugvip/nouser

This commit is contained in:
Patrik Oldsberg
2022-03-04 19:48:24 +01:00
committed by GitHub
58 changed files with 320 additions and 799 deletions
+33
View File
@@ -0,0 +1,33 @@
---
'@backstage/create-app': patch
---
Builtin support for cookiecutter based templates has been removed from `@backstage/plugin-scaffolder-backend`. Due to this, the `containerRunner` argument to its `createRouter` has also been removed.
If you do not use cookiecutter templates and are fine with removing support from it in your own installation, update your `packages/backend/src/plugins/scaffolder.ts` file as follows:
```diff
-import { DockerContainerRunner } from '@backstage/backend-common';
import { CatalogClient } from '@backstage/catalog-client';
import { createRouter } from '@backstage/plugin-scaffolder-backend';
-import Docker from 'dockerode';
import { Router } from 'express';
import type { PluginEnvironment } from '../types';
export default async function createPlugin({
reader,
discovery,
}: PluginEnvironment): Promise<Router> {
- const dockerClient = new Docker();
- const containerRunner = new DockerContainerRunner({ dockerClient });
-
const catalogClient = new CatalogClient({ discoveryApi: discovery });
-
return await createRouter({
- containerRunner,
logger,
config,
// ...
```
If you want to retain cookiecutter support, please use the `@backstage/plugin-scaffolder-backend-module-cookiecutter` package explicitly (see [its README](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend-module-cookiecutter) for installation instructions).
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-react': minor
---
**BREAKING**: Removed `useEntityFromUrl`.
+8
View File
@@ -0,0 +1,8 @@
---
'@backstage/plugin-scaffolder-backend': minor
---
**BREAKING**:
- Removed the `createFetchCookiecutterAction` export, please use the `@backstage/plugin-scaffolder-backend-module-cookiecutter` package explicitly (see [its README](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend-module-cookiecutter) for installation instructions).
- Removed the `containerRunner` argument from the types `RouterOptions` (as used by `createRouter`) and `CreateBuiltInActionsOptions` (as used by `createBuiltinActions`).
+9
View File
@@ -0,0 +1,9 @@
---
'@backstage/catalog-model': minor
---
**BREAKING**:
- Removed the previously deprecated type `EntityRef`. Please use `string` for stringified entity refs, `CompoundEntityRef` for compound kind-namespace-name triplet objects, or custom objects like `{ kind?: string; namespace?: string; name: string }` and similar if you have need for partial types.
- Removed the previously deprecated type `LocationSpec` type, which has been moved to `@backstage/plugin-catalog-backend`.
- Removed the previously deprecated function `parseEntityName`. Please use `parseEntityRef` instead.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-backend': patch
---
Fixed display of the location in the log message that is printed when entity envelope validation fails.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-scaffolder-backend': minor
---
**BREAKING**: Removed the previously deprecated `OctokitProvider` class.
+7
View File
@@ -0,0 +1,7 @@
---
'@backstage/create-app': patch
---
Postgres-based search is now installed when PG is chosen as the desired database for Backstage.
There is no need to make this change in an existing Backstage backend. See [supported search engines](https://backstage.io/docs/features/search/search-engines) for details about production-ready search engines.
+7
View File
@@ -0,0 +1,7 @@
---
'@backstage/plugin-catalog-react': minor
---
**BREAKING**: Removed `useOwnedEntities` and moved its usage internally to the scaffolder-backend where it's used.
**BREAKING**: Removed `EntityTypeReturn` type which is now inlined.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/cli': patch
---
Changed the logic for how modules are marked as external in the Rollup build of packages. Rather than only marking dependencies and build-in Node.js modules as external, all non-relative imports are now considered external.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-react': patch
---
Internalized usage of `useOwnedEntities` hook.
+2 -12
View File
@@ -56,18 +56,7 @@ jobs:
github-token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }}
# TODO(Rugvip): Remove the create-app dispatch once we've been on the release version for a while
script: |
console.log('Dispatching upgrade helper sync - release version');
await github.rest.actions.createWorkflowDispatch({
owner: 'backstage',
repo: 'upgrade-helper-diff',
workflow_id: 'release.yml',
ref: 'master',
inputs: {
version: require('./backstage/package.json').version,
},
});
console.log('Dispatching upgrade helper sync - create-app version');
console.log('Dispatching upgrade helper sync');
await github.rest.actions.createWorkflowDispatch({
owner: 'backstage',
repo: 'upgrade-helper-diff',
@@ -75,5 +64,6 @@ jobs:
ref: 'master',
inputs: {
version: require('./backstage/packages/create-app/package.json').version,
releaseVersion: require('./backstage/package.json').version
},
});
+6 -6
View File
@@ -24,12 +24,12 @@ Backstage ecosystem.
## Project roadmap
| Version | Description |
| -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Backstage Search Pre-Alpha ✅ | Search Frontend letting you search through the entities of the software catalog. [See Pre-Alpha Use Cases.](#backstage-search-pre-alpha) |
| Backstage Search Alpha ✅ | Basic “out-of-the-box” in-memory indexing process of entities, and their metadata, registered to the Software Catalog. [See Alpha Use Cases](#backstage-search-alpha). |
| [Backstage Search Beta ⌛][beta] | At least one production-ready search engine that supports the same use-cases as in the alpha. [See Beta Use Cases](#backstage-search-beta). |
| [Backstage Search GA ⌛][ga] | A stable Search API for plugin developers to add search to their plugins, and app integrators to expose that to their users. [See GA Use Cases](#backstage-search-ga). |
| Version | Description |
| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Backstage Search Pre-Alpha ✅ | Search Frontend letting you search through the entities of the software catalog. [See Pre-Alpha Use Cases.](#backstage-search-pre-alpha) |
| Backstage Search Alpha ✅ | Basic “out-of-the-box” in-memory indexing process of entities, and their metadata, registered to the Software Catalog. [See Alpha Use Cases](#backstage-search-alpha). |
| Backstage Search Beta | At least one production-ready search engine that supports the same use-cases as in the alpha. [See Beta Use Cases](#backstage-search-beta). |
| [Backstage Search GA ⌛][ga] | A stable Search API for plugin developers to add search to their plugins, and app integrators to expose that to their users. [See GA Use Cases](#backstage-search-ga). |
[beta]: https://github.com/backstage/backstage/milestone/27
[ga]: https://github.com/backstage/backstage/milestone/28
+11 -7
View File
@@ -137,9 +137,10 @@ entities are indexed by the search engine.
For the purposes of this guide, Search Beta version is defined as:
- **Search Plugin**: At least `v0.x.y`
- **Search Backend Plugin**: At least `v0.x.y`
- **Search Backend Node**: At least `v0.x.y`
- **Search Plugin**: At least `v0.7.2`
- **Search Backend Plugin**: At least `v0.4.6`
- **Search Backend Node**: At least `v0.5.0`
- **Search Common**: At least `v0.3.0`
In the Beta version, the Search Platform's indexing process has been rewritten
as a stream pipeline in order to improve efficiency and performance on large
@@ -149,11 +150,14 @@ If you've not yet extended the Search Platform with custom code, and have
instead taken advantage of default collators, decorators, and search engines
provided by existing plugins, the migration process is fairly straightforward:
1. Upgrade to at least version `0.x.y` of
1. Upgrade to at least version `0.5.0` of
`@backstage/plugin-search-backend-node`, as well as any backend plugins whose
collators you are using (e.g. at least version `0.x.y` of
`@backstage/plugin-catalog-backend` and/or version `0.x.y` of
`@backstage/plugin-techdocs-backend`).
collators you are using (e.g. at least version `0.23.0` of
`@backstage/plugin-catalog-backend` and/or version `0.14.1` of
`@backstage/plugin-techdocs-backend`), as well as any search-engine specific
plugin you are using (e.g. at least version `0.3.0` of
`@backstage/plugin-search-backend-module-pg` or version `0.1.0` of
`@backstage/plugin-search-backend-module-elasticsearch`).
2. Then, make the following changes to your
`/packages/backend/src/plugins/search.ts` file:
+4 -6
View File
@@ -181,12 +181,10 @@ files like stylesheets or images. For more details on what syntax and file
formats are supported by the build process, see the [loaders section](#loaders).
When building CommonJS or ESM output, the build commands will always use
`src/index.ts` as the entrypoint. All dependencies of the package will be marked
as external, meaning that in general it is only the contents of the `src` folder
that ends up being compiled and output to `dist`. All import statements of
external dependencies, even within the same monorepo, will stay intact. The
externalized dependencies are based on dependency information in `package.json`,
which means it's important to keep it up to date.
`src/index.ts` as the entrypoint. All non-relative modules imports are considered
external, meaning the Rollup build will only compile the source code of the package
itself. All import statements of external dependencies, even within the same
monorepo, will stay intact.
The build of the type definitions works quite differently. The entrypoint of the
type definition build is the relative location of the package within the
@@ -14,10 +14,8 @@
* limitations under the License.
*/
import { DockerContainerRunner } from '@backstage/backend-common';
import { CatalogClient } from '@backstage/catalog-client';
import { createRouter } from '@backstage/plugin-scaffolder-backend';
import Docker from 'dockerode';
import { Router } from 'express';
import type { PluginEnvironment } from '../types';
@@ -28,13 +26,8 @@ export default async function createPlugin({
reader,
discovery,
}: PluginEnvironment): Promise<Router> {
const dockerClient = new Docker();
const containerRunner = new DockerContainerRunner({ dockerClient });
const catalogClient = new CatalogClient({ discoveryApi: discovery });
return await createRouter({
containerRunner,
logger,
config,
database,
-31
View File
@@ -190,15 +190,6 @@ export type EntityPolicy = {
enforce(entity: Entity): Promise<Entity | undefined>;
};
// @public @deprecated
export type EntityRef =
| string
| {
kind?: string;
namespace?: string;
name: string;
};
// @public
export type EntityRelation = {
type: string;
@@ -316,13 +307,6 @@ export { LocationEntityV1alpha1 };
// @public
export const locationEntityV1alpha1Validator: KindValidator;
// @public @deprecated
export type LocationSpec = {
type: string;
target: string;
presence?: 'optional' | 'required';
};
// @public
export function makeValidator(overrides?: Partial<Validators>): Validators;
@@ -333,21 +317,6 @@ export class NoForeignRootFieldsEntityPolicy implements EntityPolicy {
enforce(entity: Entity): Promise<Entity>;
}
// @public @deprecated
export function parseEntityName(
ref:
| string
| {
kind?: string;
namespace?: string;
name: string;
},
context?: {
defaultKind?: string;
defaultNamespace?: string;
},
): CompoundEntityRef;
// @public
export function parseEntityRef(
ref:
@@ -36,7 +36,6 @@ export * from './policies';
export {
getCompoundEntityRef,
getEntityName,
parseEntityName,
parseEntityRef,
stringifyEntityRef,
} from './ref';
+1 -163
View File
@@ -14,171 +14,9 @@
* limitations under the License.
*/
import { DEFAULT_NAMESPACE } from './constants';
import { parseEntityName, parseEntityRef } from './ref';
import { parseEntityRef } from './ref';
describe('ref', () => {
describe('parseEntityName', () => {
it('handles some omissions', () => {
expect(parseEntityName('a:b/c')).toEqual({
kind: 'a',
namespace: 'b',
name: 'c',
});
expect(() => parseEntityName('b/c')).toThrow(/kind/);
expect(parseEntityName('a:c')).toEqual({
kind: 'a',
namespace: DEFAULT_NAMESPACE,
name: 'c',
});
expect(() => parseEntityName('c')).toThrow(/kind/);
});
it('rejects bad inputs', () => {
expect(() => parseEntityName(null as any)).toThrow();
expect(() => parseEntityName(7 as any)).toThrow();
expect(() => parseEntityName('a:b:c')).toThrow();
expect(() => parseEntityName('a/b/c')).toThrow();
expect(() => parseEntityName('a/b:c')).toThrow();
expect(() => parseEntityName('a:b/c/d')).toThrow();
expect(() => parseEntityName('a:b/c:d')).toThrow();
});
it('rejects empty parts in strings', () => {
// one is empty
expect(() => parseEntityName(':b/c')).toThrow();
expect(() => parseEntityName('a:/c')).toThrow();
expect(() => parseEntityName('a:b/')).toThrow();
// two are empty
expect(() => parseEntityName('a:/')).toThrow();
expect(() => parseEntityName(':b/')).toThrow();
expect(() => parseEntityName(':/c')).toThrow();
// three are empty
expect(() => parseEntityName(':/')).toThrow();
// one is left out, one empty
expect(() => parseEntityName('/c')).toThrow();
expect(() => parseEntityName('b/')).toThrow();
expect(() => parseEntityName(':c')).toThrow();
expect(() => parseEntityName('a:')).toThrow();
// nothing at all
expect(() => parseEntityName('')).toThrow();
});
it('rejects empty parts in compounds', () => {
// one is empty
expect(() =>
parseEntityName({ kind: '', namespace: 'b', name: 'c' }),
).toThrow();
expect(() => parseEntityName({ namespace: 'b', name: 'c' })).toThrow();
expect(() =>
parseEntityName({ kind: 'a', namespace: '', name: 'c' }),
).toThrow();
expect(() => parseEntityName({ kind: 'a', name: 'c' })).not.toThrow();
expect(() =>
parseEntityName({ kind: 'a', namespace: 'b', name: '' }),
).toThrow();
expect(() =>
parseEntityName({ kind: 'a', namespace: 'b' } as any),
).toThrow();
// two are empty
expect(() =>
parseEntityName({ kind: '', namespace: '', name: 'c' }),
).toThrow();
expect(() => parseEntityName({ name: 'c' })).toThrow();
expect(() =>
parseEntityName({ kind: '', namespace: 'b', name: '' }),
).toThrow();
expect(() => parseEntityName({ namespace: 'b' } as any)).toThrow();
expect(() =>
parseEntityName({ kind: 'a', namespace: '', name: '' }),
).toThrow();
expect(() => parseEntityName({ kind: 'a' } as any)).toThrow();
// three are empty
expect(() =>
parseEntityName({ kind: '', namespace: '', name: '' }),
).toThrow();
expect(() => parseEntityName({} as any)).toThrow();
// one is left out, one empty
expect(() => parseEntityName({ namespace: '', name: 'c' })).toThrow();
expect(() => parseEntityName({ namespace: 'b', name: '' })).toThrow();
expect(() => parseEntityName({ kind: '', name: 'c' })).toThrow();
expect(() => parseEntityName({ kind: 'a', name: '' })).toThrow();
});
it('adds defaults where necessary to strings', () => {
expect(
parseEntityName('a:b/c', { defaultKind: 'x', defaultNamespace: 'y' }),
).toEqual({ kind: 'a', namespace: 'b', name: 'c' });
expect(
parseEntityName('b/c', { defaultKind: 'x', defaultNamespace: 'y' }),
).toEqual({ kind: 'x', namespace: 'b', name: 'c' });
expect(
parseEntityName('a:c', { defaultKind: 'x', defaultNamespace: 'y' }),
).toEqual({ kind: 'a', namespace: 'y', name: 'c' });
expect(parseEntityName('a:c', { defaultKind: 'x' })).toEqual({
kind: 'a',
namespace: DEFAULT_NAMESPACE,
name: 'c',
});
expect(
parseEntityName('c', { defaultKind: 'x', defaultNamespace: 'y' }),
).toEqual({ kind: 'x', namespace: 'y', name: 'c' });
expect(parseEntityName('c', { defaultKind: 'x' })).toEqual({
kind: 'x',
namespace: DEFAULT_NAMESPACE,
name: 'c',
});
});
it('adds defaults where necessary to compounds', () => {
expect(
parseEntityName(
{ kind: 'a', namespace: 'b', name: 'c' },
{ defaultKind: 'x', defaultNamespace: 'y' },
),
).toEqual({ kind: 'a', namespace: 'b', name: 'c' });
expect(
parseEntityName(
{ namespace: 'b', name: 'c' },
{ defaultKind: 'x', defaultNamespace: 'y' },
),
).toEqual({ kind: 'x', namespace: 'b', name: 'c' });
expect(
parseEntityName(
{ kind: 'a', name: 'c' },
{ defaultKind: 'x', defaultNamespace: 'y' },
),
).toEqual({ kind: 'a', namespace: 'y', name: 'c' });
expect(
parseEntityName({ kind: 'a', name: 'c' }, { defaultKind: 'x' }),
).toEqual({ kind: 'a', namespace: DEFAULT_NAMESPACE, name: 'c' });
expect(
parseEntityName(
{ name: 'c' },
{ defaultKind: 'x', defaultNamespace: 'y' },
),
).toEqual({ kind: 'x', namespace: 'y', name: 'c' });
expect(parseEntityName({ name: 'c' }, { defaultKind: 'x' })).toEqual({
kind: 'x',
namespace: DEFAULT_NAMESPACE,
name: 'c',
});
// empty strings are errors, not defaults
expect(() =>
parseEntityName(
{ kind: '', namespace: 'b', name: 'c' },
{ defaultKind: 'x', defaultNamespace: 'y' },
),
).toThrow(/kind/);
expect(() =>
parseEntityName(
{ kind: 'a', namespace: '', name: 'c' },
{ defaultKind: 'x', defaultNamespace: 'y' },
),
).toThrow(/namespace/);
});
});
describe('parseEntityRef', () => {
it('handles some omissions', () => {
expect(parseEntityRef('a:b/c')).toEqual({
-39
View File
@@ -64,45 +64,6 @@ export function getCompoundEntityRef(entity: Entity): CompoundEntityRef {
};
}
/**
* Parses an entity reference, either on string or compound form, and always
* returns a complete entity name including kind, namespace and name.
*
* @remarks
*
* This function automatically assumes the default namespace "default" unless
* otherwise specified as part of the options, and will throw an error if no
* kind was specified in the input reference and no default kind was given.
*
* @deprecated Please use parseEntityRef instead
* @public
* @param ref - The reference to parse
* @param context - The context of defaults that the parsing happens within
* @returns A complete entity name
*/
export function parseEntityName(
ref: string | { kind?: string; namespace?: string; name: string },
context: {
/** The default kind, if none is given in the reference */
defaultKind?: string;
/** The default namespace, if none is given in the reference */
defaultNamespace?: string;
} = {},
): CompoundEntityRef {
const { kind, namespace, name } = parseEntityRef(ref, {
defaultNamespace: DEFAULT_NAMESPACE,
...context,
});
if (!kind) {
throw new Error(
`Entity reference ${namespace}/${name} did not contain a kind`,
);
}
return { kind, namespace, name };
}
/**
* Parses an entity reference, either on string or compound form, and returns
* a structure with a name, and optional kind and namespace.
+1 -1
View File
@@ -24,5 +24,5 @@ export * from './entity';
export { EntityPolicies } from './EntityPolicies';
export * from './kinds';
export * from './location';
export type { EntityName, EntityRef, CompoundEntityRef } from './types';
export type { EntityName, CompoundEntityRef } from './types';
export * from './validation';
@@ -15,19 +15,25 @@
*/
/**
* Constant storing location annotation.
* Entity annotation containing the location from which the entity is sourced.
*
* @public */
* @public
*/
export const ANNOTATION_LOCATION = 'backstage.io/managed-by-location';
/**
* Constant storing origin location annotation
* Entity annotation containing the originally sourced location which ultimately
* led to this entity being ingested.
*
* @public */
* @public
*/
export const ANNOTATION_ORIGIN_LOCATION =
'backstage.io/managed-by-origin-location';
/**
* Contant storing source location annotation
* Entity annotation pointing to the source (e.g. source code repository root or
* similar) for this entity.
*
* @public */
* @public
*/
export const ANNOTATION_SOURCE_LOCATION = 'backstage.io/source-location';
@@ -24,4 +24,3 @@ export {
parseLocationRef,
stringifyLocationRef,
} from './helpers';
export type { LocationSpec } from './types';
@@ -1,33 +0,0 @@
/*
* Copyright 2020 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Holds the entity location information.
*
* @remarks
*
* `presence` flag: when using repo importer plugin, location is being created before the component yaml file is merged to the main branch.
* This flag is then set to indicate that the file can be not present.
* default value: 'required'.
*
* @public
* @deprecated Import from `@backstage/plugin-catalog-backend` instead.
*/
export type LocationSpec = {
type: string;
target: string;
presence?: 'optional' | 'required';
};
-22
View File
@@ -33,25 +33,3 @@ export type CompoundEntityRef = {
* @public
*/
export type EntityName = CompoundEntityRef;
/**
* A reference by name to an entity, either as a compact string representation,
* or as a compound reference structure.
*
* @deprecated Please use string directly, or EntityName (depending on what you actually need)
* @remarks
*
* The string representation is on the form `[<kind>:][<namespace>/]<name>`.
*
* Left-out parts of the reference need to be handled by the application,
* either by rejecting the reference or by falling back to default values.
*
* @public
*/
export type EntityRef =
| string
| {
kind?: string;
namespace?: string;
name: string;
};
-1
View File
@@ -103,7 +103,6 @@
"rollup": "^2.60.2",
"rollup-plugin-dts": "^4.0.1",
"rollup-plugin-esbuild": "^4.7.2",
"rollup-plugin-peer-deps-external": "^2.2.2",
"rollup-plugin-postcss": "^4.0.0",
"rollup-pluginutils": "^2.8.2",
"run-script-webpack-plugin": "^0.0.11",
@@ -0,0 +1,59 @@
/*
* Copyright 2020 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ExternalOption } from 'rollup';
import { makeRollupConfigs } from './config';
import { Output } from './types';
describe('makeRollupConfigs', () => {
it('should mark external modules correctly', async () => {
const importerPath = '/some/path.ts'; // when specified we don't care about the path
const [config] = await makeRollupConfigs({
outputs: new Set([Output.cjs]),
});
const external = config.external as Exclude<
ExternalOption,
string | RegExp | (string | RegExp)[]
>;
expect(external('foo', importerPath, false)).toBe(true);
expect(external('./foo', importerPath, false)).toBe(false);
expect(external('/foo', importerPath, false)).toBe(false);
expect(external('.\\foo', importerPath, false)).toBe(false);
expect(external('c:\\foo', importerPath, false)).toBe(false);
expect(external('@foo/bar', importerPath, false)).toBe(true);
expect(external('../foo', importerPath, false)).toBe(false);
// Modules without an importer are entry points, i.e. not external
expect(external('foo', undefined, false)).toBe(false);
expect(external('./foo', undefined, false)).toBe(false);
expect(external('/foo', undefined, false)).toBe(false);
expect(external('.\\foo', undefined, false)).toBe(false);
expect(external('c:\\foo', undefined, false)).toBe(false);
expect(external('@foo/bar', undefined, false)).toBe(false);
expect(external('../foo', undefined, false)).toBe(false);
// After modules have been resolved they're never marked as external
expect(external('foo', importerPath, true)).toBe(false);
expect(external('./foo', importerPath, true)).toBe(false);
expect(external('/foo', importerPath, true)).toBe(false);
expect(external('.\\foo', importerPath, true)).toBe(false);
expect(external('c:\\foo', importerPath, true)).toBe(false);
expect(external('@foo/bar', importerPath, true)).toBe(false);
expect(external('../foo', importerPath, true)).toBe(false);
});
});
+16 -6
View File
@@ -17,7 +17,6 @@
import chalk from 'chalk';
import fs from 'fs-extra';
import { relative as relativePath, resolve as resolvePath } from 'path';
import peerDepsExternal from 'rollup-plugin-peer-deps-external';
import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import postcss from 'rollup-plugin-postcss';
@@ -33,6 +32,19 @@ import { BuildOptions, Output } from './types';
import { paths } from '../paths';
import { svgrTemplate } from '../svgrTemplate';
function isFileImport(source: string) {
if (source.startsWith('.')) {
return true;
}
if (source.startsWith('/')) {
return true;
}
if (source.match(/[a-z]:/i)) {
return true;
}
return false;
}
export async function makeRollupConfigs(
options: BuildOptions,
): Promise<RollupOptions[]> {
@@ -81,12 +93,10 @@ export async function makeRollupConfigs(
output,
onwarn,
preserveEntrySignatures: 'strict',
external: require('module').builtinModules,
// All module imports are always marked as external
external: (source, importer, isResolved) =>
Boolean(importer && !isResolved && !isFileImport(source)),
plugins: [
peerDepsExternal({
packageJsonPath: resolvePath(targetDir, 'package.json'),
includeDependencies: true,
}),
resolve({ mainFields }),
commonjs({
include: /node_modules/,
@@ -99,7 +99,7 @@ export interface DependencyGraphProps<NodeData, EdgeData>
/**
* Margin on top and bottom of whole graph
*
* @remarks
* @remarks
*
* Default: 0
*/
+1
View File
@@ -86,6 +86,7 @@
"@backstage/plugin-scaffolder-backend": "",
"@backstage/plugin-search": "",
"@backstage/plugin-search-backend": "",
"@backstage/plugin-search-backend-module-pg": "",
"@backstage/plugin-search-backend-node": "",
"@backstage/plugin-tech-radar": "",
"@backstage/plugin-techdocs": "",
+2
View File
@@ -70,6 +70,7 @@ import { version as pluginScaffolder } from '../../../../plugins/scaffolder/pack
import { version as pluginScaffolderBackend } from '../../../../plugins/scaffolder-backend/package.json';
import { version as pluginSearch } from '../../../../plugins/search/package.json';
import { version as pluginSearchBackend } from '../../../../plugins/search-backend/package.json';
import { version as pluginSearchBackendModulePg } from '../../../../plugins/search-backend-module-pg/package.json';
import { version as pluginSearchBackendNode } from '../../../../plugins/search-backend-node/package.json';
import { version as pluginTechRadar } from '../../../../plugins/tech-radar/package.json';
import { version as pluginTechdocs } from '../../../../plugins/techdocs/package.json';
@@ -113,6 +114,7 @@ export const packageVersions = {
'@backstage/plugin-scaffolder-backend': pluginScaffolderBackend,
'@backstage/plugin-search': pluginSearch,
'@backstage/plugin-search-backend': pluginSearchBackend,
'@backstage/plugin-search-backend-module-pg': pluginSearchBackendModulePg,
'@backstage/plugin-search-backend-node': pluginSearchBackendNode,
'@backstage/plugin-tech-radar': pluginTechRadar,
'@backstage/plugin-techdocs': pluginTechdocs,
@@ -28,6 +28,9 @@
"@backstage/plugin-proxy-backend": "^{{version '@backstage/plugin-proxy-backend'}}",
"@backstage/plugin-scaffolder-backend": "^{{version '@backstage/plugin-scaffolder-backend'}}",
"@backstage/plugin-search-backend": "^{{version '@backstage/plugin-search-backend'}}",
{{#if dbTypePG}}
"@backstage/plugin-search-backend-module-pg": "^{{version '@backstage/plugin-search-backend-module-pg'}}",
{{/if}}
"@backstage/plugin-search-backend-node": "^{{version '@backstage/plugin-search-backend-node'}}",
"@backstage/plugin-techdocs-backend": "^{{version '@backstage/plugin-techdocs-backend'}}",
"@gitbeaker/node": "^34.6.0",
@@ -1,7 +1,5 @@
import { DockerContainerRunner } from '@backstage/backend-common';
import { CatalogClient } from '@backstage/catalog-client';
import { createRouter } from '@backstage/plugin-scaffolder-backend';
import Docker from 'dockerode';
import { Router } from 'express';
import type { PluginEnvironment } from '../types';
@@ -12,12 +10,8 @@ export default async function createPlugin({
reader,
discovery,
}: PluginEnvironment): Promise<Router> {
const dockerClient = new Docker();
const containerRunner = new DockerContainerRunner({ dockerClient });
const catalogClient = new CatalogClient({ discoveryApi: discovery });
return await createRouter({
containerRunner,
logger,
config,
database,
@@ -4,6 +4,9 @@ import {
IndexBuilder,
LunrSearchEngine,
} from '@backstage/plugin-search-backend-node';
{{#if dbTypePG}}
import { PgSearchEngine } from '@backstage/plugin-search-backend-module-pg';
{{/if}}
import { PluginEnvironment } from '../types';
import { DefaultCatalogCollatorFactory } from '@backstage/plugin-catalog-backend';
import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-techdocs-backend';
@@ -16,7 +19,14 @@ export default async function createPlugin({
tokenManager,
}: PluginEnvironment) {
// Initialize a connection to a search engine.
{{#if dbTypeSqlite}}
const searchEngine = new LunrSearchEngine({ logger });
{{/if}}
{{#if dbTypePG}}
const searchEngine = (await PgSearchEngine.supported(database))
? await PgSearchEngine.from({ database })
: new LunrSearchEngine({ logger });
{{/if}}
const indexBuilder = new IndexBuilder({ logger, searchEngine });
// Collators are responsible for gathering documents known to plugins. This
+9 -3
View File
@@ -420,9 +420,15 @@ async function testBackendStart(appDir: string, isPostgres: boolean) {
if (isPostgres) {
print('Dropping old DBs');
await Promise.all(
['catalog', 'scaffolder', 'auth', 'identity', 'proxy', 'techdocs'].map(
name => dropDB(`backstage_plugin_${name}`),
),
[
'catalog',
'scaffolder',
'auth',
'identity',
'proxy',
'techdocs',
'search',
].map(name => dropDB(`backstage_plugin_${name}`)),
);
print('Created DBs');
}
@@ -18,37 +18,44 @@
/**
* Severity levels of {@link CollectedLogs}
* @public */
* @public
*/
export type LogFuncs = 'log' | 'warn' | 'error';
/**
* AsyncLogCollector type used in {@link (withLogCollector:1)} callback function.
* @public */
* @public
*/
export type AsyncLogCollector = () => Promise<void>;
/**
* SyncLogCollector type used in {@link (withLogCollector:2)} callback function.
* @public */
* @public
*/
export type SyncLogCollector = () => void;
/**
* Union type used in {@link (withLogCollector:3)} callback function.
* @public */
* @public
*/
export type LogCollector = AsyncLogCollector | SyncLogCollector;
/**
* Map of severity level and corresponding log lines.
* @public */
* @public
*/
export type CollectedLogs<T extends LogFuncs> = { [key in T]: string[] };
const allCategories = ['log', 'warn', 'error'];
/**
* Asynchronous log collector with that collects all categories
* @public */
* @public
*/
export function withLogCollector(
callback: AsyncLogCollector,
): Promise<CollectedLogs<LogFuncs>>;
/**
* Synchronous log collector with that collects all categories
* @public */
* @public
*/
export function withLogCollector(
callback: SyncLogCollector,
): CollectedLogs<LogFuncs>;
@@ -64,7 +71,8 @@ export function withLogCollector<T extends LogFuncs>(
/**
* Synchronous log collector with that only collects selected categories
* @public */
* @public
*/
export function withLogCollector<T extends LogFuncs>(
logsToCollect: T[],
callback: SyncLogCollector,
@@ -73,7 +81,7 @@ export function withLogCollector<T extends LogFuncs>(
/**
* Log collector that collect logs either from a sync or async collector.
* @public
* */
*/
export function withLogCollector(
logsToCollect: LogFuncs[] | LogCollector,
callback?: LogCollector,
@@ -62,7 +62,7 @@ export type AnalyzeLocationExistingEntity = {
* enough info for the frontend to know what form data to show to the user
* for overriding/completing the info.
* @public
* */
*/
export type AnalyzeLocationGenerateEntity = {
// Some form of partial representation of the entity
entity: RecursivePartial<Entity>;
@@ -72,17 +72,17 @@ export class ProcessorOutputCollector {
if (i.type === 'entity') {
let entity: Entity;
const location = stringifyLocationRef(i.location);
try {
entity = validateEntityEnvelope(i.entity);
} catch (e) {
assertError(e);
this.logger.debug(`Envelope validation failed at ${i.location}, ${e}`);
this.logger.debug(`Envelope validation failed at ${location}, ${e}`);
this.errors.push(e);
return;
}
const location = stringifyLocationRef(i.location);
// Note that at this point, we have only validated the envelope part of
// the entity data. Annotations are not part of that, so we have to be
// defensive. If the annotations were malformed (e.g. were not a valid
@@ -48,7 +48,7 @@ export type EntityProcessingResult =
/**
* Responsible for executing the individual processing steps in order to fully process an entity.
* @public
* */
*/
export interface CatalogProcessingOrchestrator {
process(request: EntityProcessingRequest): Promise<EntityProcessingResult>;
}
@@ -56,7 +56,7 @@ export interface CatalogProcessingOrchestrator {
/**
* Entities that are not yet processed.
* @public
* */
*/
export type DeferredEntity = {
entity: Entity;
locationKey?: string;
+2 -1
View File
@@ -77,7 +77,8 @@ export interface RefreshService {
/**
* Interacts with the database to manage locations.
* @public */
* @public
*/
export interface LocationStore {
createLocation(location: LocationInput): Promise<Location>;
listLocations(): Promise<Location[]>;
-19
View File
@@ -12,7 +12,6 @@ import { ComponentEntity } from '@backstage/catalog-model';
import { ComponentProps } from 'react';
import { CompoundEntityRef } from '@backstage/catalog-model';
import { Entity } from '@backstage/catalog-model';
import { GetEntitiesResponse } from '@backstage/catalog-client';
import { IconButton } from '@material-ui/core';
import { LinkProps } from '@backstage/core-components';
import { Observable } from '@backstage/types';
@@ -386,15 +385,6 @@ export interface EntityTypePickerProps {
initialFilter?: string;
}
// @public @deprecated (undocumented)
export type EntityTypeReturn = {
loading: boolean;
error?: Error;
availableTypes: string[];
selectedTypes: string[];
setSelectedTypes: (types: string[]) => void;
};
// @public
export const FavoriteEntity: (props: FavoriteEntityProps) => JSX.Element;
@@ -510,9 +500,6 @@ export function useEntity<TEntity extends Entity = Entity>(): {
refresh?: VoidFunction;
};
// @public @deprecated (undocumented)
export const useEntityFromUrl: () => EntityLoadingStatus;
// @public @deprecated
export function useEntityKinds(): {
error: Error | undefined;
@@ -547,12 +534,6 @@ export function useEntityTypeFilter(): {
setSelectedTypes: (types: string[]) => void;
};
// @public @deprecated
export function useOwnedEntities(allowedKinds?: string[]): {
loading: boolean;
ownedEntities: GetEntitiesResponse | undefined;
};
// @public (undocumented)
export function useRelatedEntities(
entity: Entity,
-3
View File
@@ -15,7 +15,6 @@
*/
export {
useEntity,
useEntityFromUrl,
EntityProvider,
AsyncEntityProvider,
useAsyncEntity,
@@ -35,11 +34,9 @@ export type {
EntityListContextProps,
} from './useEntityListProvider';
export { useEntityTypeFilter } from './useEntityTypeFilter';
export type { EntityTypeReturn } from './useEntityTypeFilter';
export { useEntityKinds } from './useEntityKinds';
export { useRelatedEntities } from './useRelatedEntities';
export { useStarredEntities } from './useStarredEntities';
export { useStarredEntity } from './useStarredEntity';
export { loadCatalogOwnerRefs, useEntityOwnership } from './useEntityOwnership';
export { useOwnedEntities } from './useOwnedEntities';
export { useEntityPermission } from './useEntityPermission';
+1 -39
View File
@@ -14,21 +14,12 @@
* limitations under the License.
*/
import { Entity } from '@backstage/catalog-model';
import {
errorApiRef,
useApi,
useRouteRefParams,
} from '@backstage/core-plugin-api';
import {
createVersionedContext,
createVersionedValueMap,
useVersionedContext,
} from '@backstage/version-bridge';
import React, { ReactNode, useEffect } from 'react';
import { useNavigate } from 'react-router';
import useAsyncRetry from 'react-use/lib/useAsyncRetry';
import { catalogApiRef } from '../api';
import { entityRouteRef } from '../routes';
import React, { ReactNode } from 'react';
/** @public */
export type EntityLoadingStatus<TEntity extends Entity = Entity> = {
@@ -104,35 +95,6 @@ export const EntityProvider = ({ entity, children }: EntityProviderProps) => (
/>
);
/** @public
* @deprecated will be deleted shortly due to low external usage, re-implement if needed.
*/
export const useEntityFromUrl = (): EntityLoadingStatus => {
const { kind, namespace, name } = useRouteRefParams(entityRouteRef);
const navigate = useNavigate();
const errorApi = useApi(errorApiRef);
const catalogApi = useApi(catalogApiRef);
const {
value: entity,
error,
loading,
retry: refresh,
} = useAsyncRetry(
() => catalogApi.getEntityByRef({ kind, namespace, name }),
[catalogApi, kind, namespace, name],
);
useEffect(() => {
if (!name) {
errorApi.post(new Error('No name provided!'));
navigate('/');
}
}, [errorApi, navigate, error, loading, entity, name]);
return { entity, loading, error, refresh };
};
/**
* Grab the current entity from the context, throws if the entity has not yet been loaded
* or is not available.
@@ -23,17 +23,6 @@ import { catalogApiRef } from '../api';
import { useEntityList } from './useEntityListProvider';
import { EntityTypeFilter } from '../filters';
/** @public
* @deprecated type inlined with {@link useEntityTypeFilter}.
*/
export type EntityTypeReturn = {
loading: boolean;
error?: Error;
availableTypes: string[];
selectedTypes: string[];
setSelectedTypes: (types: string[]) => void;
};
/**
* A hook built on top of `useEntityList` for enabling selection of valid `spec.type` values
* based on the selected EntityKindFilter.
@@ -1,69 +0,0 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { catalogApiRef } from './../api';
import { loadCatalogOwnerRefs } from './useEntityOwnership';
import { identityApiRef, useApi } from '@backstage/core-plugin-api';
import { RELATION_OWNED_BY } from '@backstage/catalog-model';
import { GetEntitiesResponse } from '@backstage/catalog-client';
import useAsync from 'react-use/lib/useAsync';
import { useMemo } from 'react';
/**
* Takes the relevant parts of the Backstage identity, and translates them into
* a list of entities which are owned by the user. Takes an optional parameter
* to filter the entities based on allowedKinds
*
* @public
*
* @param allowedKinds - Array of allowed kinds to filter the entities
* @deprecated Use `ownershipEntityRefs` from `identityApi.getBackstageIdentity()` instead.
*/
export function useOwnedEntities(allowedKinds?: string[]): {
loading: boolean;
ownedEntities: GetEntitiesResponse | undefined;
} {
const identityApi = useApi(identityApiRef);
const catalogApi = useApi(catalogApiRef);
const { loading, value: refs } = useAsync(async () => {
const identity = await identityApi.getBackstageIdentity();
const identityRefs = identity.ownershipEntityRefs;
const catalogRefs = await loadCatalogOwnerRefs(catalogApi, identityRefs);
const catalogs = await catalogApi.getEntities(
allowedKinds
? {
filter: {
kind: allowedKinds,
[`relations.${RELATION_OWNED_BY}`]:
[...identityRefs, ...catalogRefs] || [],
},
}
: {
filter: {
[`relations.${RELATION_OWNED_BY}`]:
[...identityRefs, ...catalogRefs] || [],
},
},
);
return catalogs;
}, []);
const ownedEntities = useMemo(() => {
return refs;
}, [refs]);
return useMemo(() => ({ loading, ownedEntities }), [loading, ownedEntities]);
}
-28
View File
@@ -9,8 +9,6 @@ import { CatalogApi } from '@backstage/catalog-client';
import { CatalogProcessor } from '@backstage/plugin-catalog-backend';
import { CatalogProcessorEmit } from '@backstage/plugin-catalog-backend';
import { Config } from '@backstage/config';
import { ContainerRunner } from '@backstage/backend-common';
import { createFetchCookiecutterAction } from '@backstage/plugin-scaffolder-backend-module-cookiecutter';
import { createPullRequest } from 'octokit-plugin-create-pull-request';
import { Entity } from '@backstage/catalog-model';
import express from 'express';
@@ -21,7 +19,6 @@ import { Knex } from 'knex';
import { LocationSpec } from '@backstage/plugin-catalog-backend';
import { Logger as Logger_2 } from 'winston';
import { Observable } from '@backstage/types';
import { Octokit } from 'octokit';
import { PluginDatabaseManager } from '@backstage/backend-common';
import { Schema } from 'jsonschema';
import { ScmIntegrationRegistry } from '@backstage/integration';
@@ -63,8 +60,6 @@ export interface CreateBuiltInActionsOptions {
catalogClient: CatalogApi;
// (undocumented)
config: Config;
// @deprecated (undocumented)
containerRunner?: ContainerRunner;
// (undocumented)
integrations: ScmIntegrations;
// (undocumented)
@@ -99,8 +94,6 @@ export function createDebugLogAction(): TemplateAction<{
listWorkspace?: boolean | undefined;
}>;
export { createFetchCookiecutterAction };
// @public
export function createFetchPlainAction(options: {
reader: UrlReader;
@@ -388,25 +381,6 @@ export function fetchContents({
outputPath: string;
}): Promise<void>;
// Warning: (ae-missing-release-tag) "OctokitProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public @deprecated
export class OctokitProvider {
constructor(
integrations: ScmIntegrationRegistry,
githubCredentialsProvider?: GithubCredentialsProvider,
);
// Warning: (ae-forgotten-export) The symbol "OctokitIntegration" needs to be exported by the entry point index.d.ts
//
// @deprecated
getOctokit(
repoUrl: string,
options?: {
token?: string;
},
): Promise<OctokitIntegration>;
}
// @public (undocumented)
export interface OctokitWithPullRequestPluginClient {
// (undocumented)
@@ -428,8 +402,6 @@ export interface RouterOptions {
// (undocumented)
config: Config;
// (undocumented)
containerRunner?: ContainerRunner;
// (undocumented)
database: PluginDatabaseManager;
// (undocumented)
logger: Logger_2;
-1
View File
@@ -42,7 +42,6 @@
"@backstage/integration": "^0.8.0",
"@backstage/plugin-catalog-backend": "^0.23.0",
"@backstage/plugin-scaffolder-common": "^0.2.3",
"@backstage/plugin-scaffolder-backend-module-cookiecutter": "^0.2.3",
"@backstage/types": "^0.1.3",
"@gitbeaker/core": "^34.6.0",
"@gitbeaker/node": "^35.1.0",
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { ContainerRunner, UrlReader } from '@backstage/backend-common';
import { UrlReader } from '@backstage/backend-common';
import { JsonObject } from '@backstage/types';
import { CatalogApi } from '@backstage/catalog-client';
import {
@@ -30,7 +30,6 @@ import {
import { createDebugLogAction } from './debug';
import { createFetchPlainAction, createFetchTemplateAction } from './fetch';
import { createFetchCookiecutterAction } from '@backstage/plugin-scaffolder-backend-module-cookiecutter';
import {
createFilesystemDeleteAction,
createFilesystemRenameAction,
@@ -49,7 +48,6 @@ import {
} from './github';
import { TemplateFilter } from '../../../lib';
import { TemplateAction } from '../types';
import { getRootLogger } from '@backstage/backend-common';
/**
* The options passed to {@link createBuiltinActions}
@@ -59,8 +57,6 @@ export interface CreateBuiltInActionsOptions {
reader: UrlReader;
integrations: ScmIntegrations;
catalogClient: CatalogApi;
/** @deprecated when the cookiecutter action is removed this won't be necessary */
containerRunner?: ContainerRunner;
config: Config;
additionalTemplateFilters?: Record<string, TemplateFilter>;
}
@@ -78,7 +74,6 @@ export const createBuiltinActions = (
const {
reader,
integrations,
containerRunner,
catalogClient,
config,
additionalTemplateFilters,
@@ -135,20 +130,5 @@ export const createBuiltinActions = (
}),
];
if (containerRunner) {
getRootLogger().warn(
`[DEPRECATED] The fetch:cookiecutter action will be removed part of the default scaffolder actions in later versions.
You can install the package seperately and remove the containerRunner from the createBuiltInActions to remove this warning,
or you can migrate to using fetch:template https://backstage.io/docs/features/software-templates/builtin-actions#migrating-from-fetchcookiecutter-to-fetchtemplate`,
);
actions.push(
createFetchCookiecutterAction({
reader,
integrations,
containerRunner,
}),
);
}
return actions as TemplateAction<JsonObject>[];
};
@@ -1,88 +0,0 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { OctokitProvider } from './OctokitProvider';
import {
ScmIntegrations,
DefaultGithubCredentialsProvider,
} from '@backstage/integration';
import { ConfigReader } from '@backstage/config';
describe('getOctokit', () => {
const config = new ConfigReader({
integrations: {
github: [
{ host: 'github.com', token: 'tokenlols' },
{ host: 'ghe.github.com' },
],
},
});
const integrations = ScmIntegrations.fromConfig(config);
const githubCredentialsProvider =
DefaultGithubCredentialsProvider.fromIntegrations(integrations);
const octokitProvider = new OctokitProvider(
integrations,
githubCredentialsProvider,
);
beforeEach(() => {
jest.resetAllMocks();
});
it('should throw an error when the repoUrl is not well formed', async () => {
await expect(
octokitProvider.getOctokit('github.com?repo=bob'),
).rejects.toThrow(/missing owner/);
await expect(
octokitProvider.getOctokit('github.com?owner=owner'),
).rejects.toThrow(/missing repo/);
});
it('should throw if there is no integration config provided', async () => {
await expect(
octokitProvider.getOctokit('missing.com?repo=bob&owner=owner'),
).rejects.toThrow(/No matching integration configuration/);
});
it('should throw if there is no token in the integration config that is returned', async () => {
await expect(
octokitProvider.getOctokit('ghe.github.com?repo=bob&owner=owner'),
).rejects.toThrow(/No token available for host/);
});
it('should return proper Octokit', async () => {
const { client, token, owner, repo } = await octokitProvider.getOctokit(
'github.com?repo=bob&owner=owner',
);
expect(client).toBeDefined();
expect(token).toBe('tokenlols');
expect(owner).toBe('owner');
expect(repo).toBe('bob');
});
it('should return an octokit client with the passed in token if it is provided', async () => {
const { client, token, owner, repo } = await octokitProvider.getOctokit(
'github.com?repo=bob&owner=owner',
{ token: 'tokenlols2' },
);
expect(client).toBeDefined();
expect(token).toBe('tokenlols2');
expect(owner).toBe('owner');
expect(repo).toBe('bob');
});
});
@@ -1,111 +0,0 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { InputError } from '@backstage/errors';
import {
DefaultGithubCredentialsProvider,
GithubCredentialsProvider,
ScmIntegrationRegistry,
} from '@backstage/integration';
import { Octokit } from 'octokit';
import { parseRepoUrl } from '../publish/util';
export type OctokitIntegration = {
client: Octokit;
token: string;
owner: string;
repo: string;
};
/**
* OctokitProvider provides Octokit client based on ScmIntegrationsRegistry configuration.
* OctokitProvider supports GitHub credentials caching out of the box.
*
* @deprecated we are no longer providing a way from the scaffolder to generate octokit instances.
* Implement your own if you're using this method from an external package, or use the internal `getOctokitOptions` function instead
*/
export class OctokitProvider {
private readonly integrations: ScmIntegrationRegistry;
private readonly githubCredentialsProvider: GithubCredentialsProvider;
constructor(
integrations: ScmIntegrationRegistry,
githubCredentialsProvider?: GithubCredentialsProvider,
) {
this.integrations = integrations;
this.githubCredentialsProvider =
githubCredentialsProvider ||
DefaultGithubCredentialsProvider.fromIntegrations(this.integrations);
}
/**
* gets standard Octokit client based on repository URL.
*
* @param repoUrl - Repository URL
*
* @deprecated we are no longer providing a way from the scaffolder to generate octokit instances.
* Implement your own if you're using this method from an external package, or use the internal `getOctokitOptions` function instead
*/
async getOctokit(
repoUrl: string,
options?: { token?: string },
): Promise<OctokitIntegration> {
const { owner, repo, host } = parseRepoUrl(repoUrl, this.integrations);
if (!owner) {
throw new InputError(`No owner provided for repo ${repoUrl}`);
}
const integrationConfig = this.integrations.github.byHost(host)?.config;
if (!integrationConfig) {
throw new InputError(`No integration for host ${host}`);
}
// Short circuit the internal Github Token provider the token provided
// by the action or the caller.
if (options?.token) {
const client = new Octokit({
auth: options.token,
baseUrl: integrationConfig.apiBaseUrl,
previews: ['nebula-preview'],
});
return { client, token: options.token, owner, repo };
}
// TODO(blam): Consider changing this API to have owner, repoo interface instead of URL as the it's
// needless to create URL and then parse again the other side.
const { token } = await this.githubCredentialsProvider.getCredentials({
url: `https://${host}/${encodeURIComponent(owner)}/${encodeURIComponent(
repo,
)}`,
});
if (!token) {
throw new InputError(
`No token available for host: ${host}, with owner ${owner}, and repo ${repo}`,
);
}
const client = new Octokit({
auth: token,
baseUrl: integrationConfig.apiBaseUrl,
previews: ['nebula-preview'],
});
return { client, token, owner, repo };
}
}
@@ -16,4 +16,3 @@
export { createGithubActionsDispatchAction } from './githubActionsDispatch';
export { createGithubWebhookAction } from './githubWebhook';
export { OctokitProvider } from './OctokitProvider';
@@ -22,8 +22,5 @@ export * from './filesystem';
export * from './publish';
export * from './github';
/** @deprecated please add this package to your own installation manually */
export { createFetchCookiecutterAction } from '@backstage/plugin-scaffolder-backend-module-cookiecutter';
export { runCommand, executeShellCommand } from './helpers';
export type { RunCommandOptions } from './helpers';
@@ -26,7 +26,8 @@ import {
/**
* ActionContext is passed into scaffolder actions.
* @public */
* @public
*/
export type ActionContext<Input extends JsonObject> = {
/**
* Base URL for the location of the task spec, typically the url of the source entity file.
@@ -30,7 +30,6 @@ jest.doMock('fs-extra', () => ({
import {
DatabaseManager,
DockerContainerRunner,
getVoidLogger,
PluginDatabaseManager,
UrlReaders,
@@ -122,7 +121,6 @@ describe('createRouter', () => {
config: new ConfigReader({}),
database: createDatabase(),
catalogClient: createCatalogClient(template),
containerRunner: new DockerContainerRunner({} as any),
reader: mockUrlReader,
taskBroker,
});
@@ -14,11 +14,7 @@
* limitations under the License.
*/
import {
ContainerRunner,
PluginDatabaseManager,
UrlReader,
} from '@backstage/backend-common';
import { PluginDatabaseManager, UrlReader } from '@backstage/backend-common';
import { CatalogApi } from '@backstage/catalog-client';
import { parseEntityRef, stringifyEntityRef } from '@backstage/catalog-model';
import { Entity } from '@backstage/catalog-model';
@@ -61,7 +57,6 @@ export interface RouterOptions {
catalogClient: CatalogApi;
actions?: TemplateAction<any>[];
taskWorkers?: number;
containerRunner?: ContainerRunner;
taskBroker?: TaskBroker;
additionalTemplateFilters?: Record<string, TemplateFilter>;
}
@@ -89,7 +84,6 @@ export async function createRouter(
database,
catalogClient,
actions,
containerRunner,
taskWorkers,
additionalTemplateFilters,
} = options;
@@ -128,7 +122,6 @@ export async function createRouter(
: createBuiltinActions({
integrations,
catalogClient,
containerRunner,
reader,
config,
additionalTemplateFilters,
@@ -13,14 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { GetEntitiesResponse } from '@backstage/catalog-client';
import { RELATION_OWNED_BY } from '@backstage/catalog-model';
import { identityApiRef, useApi } from '@backstage/core-plugin-api';
import {
catalogApiRef,
humanizeEntityRef,
useOwnedEntities,
} from '@backstage/plugin-catalog-react';
import { TextField } from '@material-ui/core';
import FormControl from '@material-ui/core/FormControl';
import Autocomplete from '@material-ui/lab/Autocomplete';
import React from 'react';
import React, { useMemo } from 'react';
import useAsync from 'react-use/lib/useAsync';
import { FieldExtensionComponentProps } from '../../../extensions';
@@ -86,3 +90,45 @@ export const OwnedEntityPicker = (
</FormControl>
);
};
/**
* Takes the relevant parts of the Backstage identity, and translates them into
* a list of entities which are owned by the user. Takes an optional parameter
* to filter the entities based on allowedKinds
*
*
* @param allowedKinds - Array of allowed kinds to filter the entities
*/
function useOwnedEntities(allowedKinds?: string[]): {
loading: boolean;
ownedEntities: GetEntitiesResponse | undefined;
} {
const identityApi = useApi(identityApiRef);
const catalogApi = useApi(catalogApiRef);
const { loading, value: refs } = useAsync(async () => {
const identity = await identityApi.getBackstageIdentity();
const identityRefs = identity.ownershipEntityRefs;
const catalogs = await catalogApi.getEntities(
allowedKinds
? {
filter: {
kind: allowedKinds,
[`relations.${RELATION_OWNED_BY}`]: identityRefs || [],
},
}
: {
filter: {
[`relations.${RELATION_OWNED_BY}`]: identityRefs || [],
},
},
);
return catalogs;
}, []);
const ownedEntities = useMemo(() => {
return refs;
}, [refs]);
return useMemo(() => ({ loading, ownedEntities }), [loading, ownedEntities]);
}
+13 -17
View File
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { DateTime, DurationLike } from 'luxon';
import { Config } from '@backstage/config';
import { PluginEndpointDiscovery } from '@backstage/backend-common';
@@ -62,11 +63,10 @@ export type TechInsightFact = {
};
/**
* Response type used when returning from database and API.
* Adds a field for ref for easier usage
*
* @public
* @public
*/
export type FlatTechInsightFact = TechInsightFact & {
/**
@@ -76,12 +76,12 @@ export type FlatTechInsightFact = TechInsightFact & {
};
/**
* @public
*
* A record type to specify individual fact shapes
*
* Used as part of a schema to validate, identify and generically construct usage implementations
* of individual fact values in the system.
*
* @public
*/
export type FactSchema = {
/**
@@ -142,11 +142,9 @@ export type FactRetrieverContext = {
};
/**
* @public
*
* FactRetriever interface
*
* A component specifying
* @public
*/
export interface FactRetriever {
/**
@@ -191,10 +189,9 @@ export interface FactRetriever {
}
/**
* @public
*
* A Luxon duration like object for time to live value
*
* @public
* @example
* \{ timeToLive: 1209600000 \}
* \{ timeToLive: \{ weeks: 4 \} \}
@@ -203,10 +200,9 @@ export interface FactRetriever {
export type TTL = { timeToLive: DurationLike };
/**
* @public
*
* A maximum number for items to be kept in the database for each fact retriever/entity pair
*
* @public
* @example
* \{ maxItems: 10 \}
*
@@ -214,25 +210,25 @@ export type TTL = { timeToLive: DurationLike };
export type MaxItems = { maxItems: number };
/**
* @public
*
* A fact lifecycle definition. Determines which strategy to use to purge expired facts from the database.
*
* @public
*/
export type FactLifecycle = TTL | MaxItems;
/**
* @public
*
* A flat serializable structure for Facts.
* Containing information about fact schema, version, id, and entity filters
*
* @public
*/
export type FactSchemaDefinition = Omit<FactRetriever, 'handler'>;
/**
* @public
*
* Registration of a fact retriever
* Used to add and schedule individual fact retrievers to the fact retriever engine.
*
* @public
*/
export type FactRetrieverRegistration = {
/**
@@ -347,8 +347,8 @@ export function createEventStream(
}
/**
* @deprecated use event-stream implementation of the sync endpoint
* */
* @deprecated use event-stream implementation of the sync endpoint
*/
export function createHttpResponse(
res: Response<any, any>,
): DocsSynchronizerSyncOpts {
-5
View File
@@ -22048,11 +22048,6 @@ rollup-plugin-esbuild@^4.7.2:
joycon "^3.0.1"
jsonc-parser "^3.0.0"
rollup-plugin-peer-deps-external@^2.2.2:
version "2.2.4"
resolved "https://registry.npmjs.org/rollup-plugin-peer-deps-external/-/rollup-plugin-peer-deps-external-2.2.4.tgz#8a420bbfd6dccc30aeb68c9bf57011f2f109570d"
integrity sha512-AWdukIM1+k5JDdAqV/Cxd+nejvno2FVLVeZ74NKggm3Q5s9cbbcOgUPGdbxPi4BXu7xGaZ8HG12F+thImYu/0g==
rollup-plugin-postcss@*, rollup-plugin-postcss@^4.0.0:
version "4.0.2"
resolved "https://registry.npmjs.org/rollup-plugin-postcss/-/rollup-plugin-postcss-4.0.2.tgz#15e9462f39475059b368ce0e49c800fa4b1f7050"