Merge branch 'master' into feat/api-auth-client

This commit is contained in:
Patrik Oldsberg
2021-02-10 11:39:45 +01:00
committed by GitHub
449 changed files with 7356 additions and 2024 deletions
+29
View File
@@ -0,0 +1,29 @@
---
'@backstage/create-app': patch
---
Pass on plugin database management instance that is now required by the scaffolder plugin.
To apply this change to an existing application, add the following to `src/plugins/scaffolder.ts`:
```diff
export default async function createPlugin({
logger,
config,
+ database,
}: PluginEnvironment) {
// ...omitted...
return await createRouter({
preparers,
templaters,
publishers,
logger,
config,
dockerClient,
entityClient,
+ database,
});
}
```
@@ -1,5 +0,0 @@
---
'@backstage/plugin-catalog-import': patch
---
Export _api_ (Client, API, ref) from the catalog import plugin.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-scaffolder': patch
---
Migrated to new composability API, exporting the plugin as `scaffolderPlugin`. The template list page (`/create`) is exported as the `TemplateIndexPage` extension, and the templating page itself is exported as `TemplatePage`.
-6
View File
@@ -1,6 +0,0 @@
---
'@backstage/techdocs-common': patch
'@backstage/plugin-techdocs-backend': patch
---
1. Added option to use Azure Blob Storage as a choice to store the static generated files for TechDocs.
@@ -0,0 +1,5 @@
---
'@backstage/plugin-cost-insights': patch
---
Fix snooze quarter option
@@ -1,5 +0,0 @@
---
'@backstage/plugin-cost-insights': minor
---
add alert hooks
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-circleci': patch
---
Migrated to new composability API, exporting the plugin instance as `circleCIPlugin`, the entity page content as `EntityCircleCIContent`, and entity conditional as `isCircleCIAvailable`.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/create-app': patch
---
Remove techdocs.requestUrl and techdocs.storageUrl from app-config.yaml
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/cli': patch
---
Update `create-plugin` template to use the new composability API, by switching to exporting a single routable extension component.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-search': minor
---
Migrated to new composability API, exporting the plugin instance as `searchPlugin`, and page as `SearchPage`. Due to the old router component also being called `SearchPage`, this is a breaking change. The old page component is now exported as `Router`, which can be used to maintain the old behavior.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-graphiql': patch
---
Finalized composability API migration, now exporting the plugin as `graphiqlPlugin`.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/core': patch
---
Add className to the SidebarItem
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-backend': patch
---
use child logger, if provided, to log single location refresh
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/core': patch
---
Update `WarningPanel` component to use accordion-style expansion
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-app-backend': patch
---
Added a new `disableConfigInjection` option, which can be used to disable the configuration injection in environments where it can't be used.
-13
View File
@@ -1,13 +0,0 @@
---
'@backstage/plugin-api-docs': patch
'@backstage/plugin-catalog': patch
'@backstage/plugin-catalog-react': patch
'@backstage/plugin-org': patch
---
Display owner and system as entity page links in the tables of the `api-docs`
plugin.
Move `isOwnerOf` and `getEntityRelations` from `@backstage/plugin-catalog` to
`@backstage/plugin-catalog-react` and export it from there to use it by other
plugins.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/theme': patch
---
Updates warning text color to align to updated `WarningPanel` styling
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-catalog-import': patch
---
Migrated to new composability API, exporting the plugin instance as `catalogImportPlugin`, and the page as `CatalogImportPage`.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-lighthouse': patch
---
Migrate to new composability API, exporting the plugin instance as `lighthousePlugin`, the top-level page as `LighthousePage`, the entity card as `EntityLastLighthouseAuditCard`, and the entity content as `EntityLighthouseContent`.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-backend': patch
---
Don't respond to a request twice if an entity has not been found.
-24
View File
@@ -1,24 +0,0 @@
---
'@backstage/backend-common': patch
---
Updated the `rootLogger` in `@backstage/backend-common` to support custom logging options. This is useful when you want to make some changes without re-implementing the entire logger and calling `setRootLogger` or `logger.configure`. For example you can add additional `defaultMeta` tags to each log entry. The following changes are included:
- Added `createRootLogger` which accepts winston `LoggerOptions`. These options allow overriding the default keys.
Example Usage:
```ts
// Create the logger
const logger = createRootLogger({
defaultMeta: { appName: 'backstage', appEnv: 'prod' },
});
// Add a custom logger transport
logger.add(new MyCustomTransport());
const config = await loadBackendConfig({
argv: process.argv,
logger: getRootLogger(), // already set to new logger instance
});
```
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-cloudbuild': patch
---
Migrate to new composability API, exporting the plugin instance as `cloudbuildPlugin`, the entity content as `EntityCloudbuildContent`, the entity conditional as `isCloudbuildAvailable`, and entity cards as `EntityLatestCloudbuildRunCard` and `EntityLatestCloudbuildsForBranchCard`.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-user-settings': patch
---
Migrate to new composability API, exporting the plugin as `userSettingsPlugin` and the page as `UserSettingsPage`.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-register-component': patch
---
Migrated to new composability API, exporting the plugin instance as `registerComponentPlugin`, and page as `RegisterComponentPage`.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/catalog-model': patch
---
Adds a new optional `links` metadata field to the Entity class within the `catalog-model` package (as discussed in [[RFC] Entity Links](https://github.com/backstage/backstage/issues/3787)). This PR adds support for the entity links only. Follow up PR's will introduce the UI component to display them.
-7
View File
@@ -1,7 +0,0 @@
---
'@backstage/techdocs-common': patch
'@backstage/plugin-techdocs': patch
'@backstage/plugin-techdocs-backend': patch
---
`techdocs.requestUrl` and `techdocs.storageUrl` are now optional configs and the discovery API will be used to get the URL where techdocs plugin is hosted.
+8
View File
@@ -0,0 +1,8 @@
---
'@backstage/plugin-api-docs': patch
'@backstage/plugin-catalog': patch
'@backstage/plugin-lighthouse': patch
'@backstage/plugin-techdocs': patch
---
Improve display of error messages
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-catalog': patch
---
Add `children` option to `addPage`, which will be rendered as the children of the `Route`.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-backend': patch
---
add indices on columns referring locations(id)
+7
View File
@@ -0,0 +1,7 @@
---
'@backstage/plugin-scaffolder-backend': minor
---
Introduced `v2` Scaffolder REST API, which uses an implementation that is database backed, making the scaffolder instances stateless. The `createRouter` function now requires a `PluginDatabaseManager` instance to be passed in, commonly available as `database` in the plugin environment in the backend.
This API should be considered unstable until used by the scaffolder frontend.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-scaffolder-backend': patch
---
Attempt to fix windows test errors in master
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/catalog-model': patch
---
Replace `yup` with `ajv`, for validation of catalog entities.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/cli': patch
---
Fixed module resolution of external libraries during backend development. Modules used to be resolved relative to the backend entrypoint, but are now resolved relative to each individual module.
-8
View File
@@ -1,8 +0,0 @@
---
'@backstage/core': minor
---
Closes #3556
The scroll bar of collapsed sidebar is now hidden without full screen.
![image](https://user-images.githubusercontent.com/46953622/105390193-0bfd0080-5c19-11eb-8e86-2161bbe6e8d9.png)
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-org': patch
---
Migrate to new composability API, exporting the plugin instance as `orgPlugin`, and the entity cards as `EntityGroupProfileCard`, `EntityMembersListCard`, `EntityOwnershipCard`, and `EntityUserProfileCard`.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/create-app': patch
---
TechDocs: Add comments about migrating away from basic setup in app-config.yaml
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/config-loader': patch
---
Bump `config-loader` to `ajv` 7, to enable v7 feature use elsewhere
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/dev-utils': patch
---
Added `path` option to `addPage` that can be used to set a specific path for the page rather than a generated one. Also omit sidebar item altogether if `title` option is not set.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/integration': patch
---
Properly forward errors that occur when looking up GitLab project IDs.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/config-loader': patch
---
Each piece of the configuration schema is now validated upfront, in order to produce more informative errors.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-app-backend': patch
---
Failures to load the frontend configuration schema now throws an error that includes more context and instructions for how to fix the issue.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-techdocs': patch
---
Migrated to new composability API, exporting the plugin instance as `techdocsPlugin`, the top-level page as `TechdocsPage`, and the entity content as `EntityTechdocsContent`.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/core': patch
---
Add a `prop` union for `SignInPage` that allows it to be used for just a single provider, with inline errors, and optionally with automatic sign-in.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/backend-common': patch
---
Set explicit content-type in error handler responses.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/backend-common': patch
---
Slight refactoring in support of a future search implementation in `UrlReader`. Mostly moving code around.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/cli': patch
---
Add check for outdated/duplicate packages to yarn start
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-pagerduty': patch
---
Migrated to new composability API, exporting the plugin instance as `pagerDutyPlugin`, entity card as `EntityPagerDutyCard`, and entity conditional as `isPagerDutyAvailable`.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-gcp-projects': patch
---
Migrate to new composability API, exporting the plugin as `gcpProjectsPlugin` and page as `GcpProjectsPage`.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-catalog-backend': patch
---
Throw `NotAllowedError` when registering locations with entities of disallowed kinds
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/test-utils': patch
---
Added `mountedRoutes` option to `wrapInTestApp`, allowing routes to be associated to concrete paths to make `useRouteRef` usable in tested components.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-jenkins': patch
---
Migrate to new composability API, exporting the plugin instance as `jenkinsPlugin`, the entity content as `EntityJenkinsContent`, the entity conditional as `isJenkinsAvailable`, and the entity card as `EntityLatestJenkinsRunCard`.
-8
View File
@@ -1,8 +0,0 @@
---
'@backstage/plugin-fossa': minor
---
Port FOSSA plugin to new extension model.
If you are using the FOSSA plugin adjust the plugin import from `plugin` to
`fossaPlugin` and replace `<FossaCard entity={...} ...>` with `<EntityFossaCard />`.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog': patch
---
Fix whitespace around variable in unregister error dialog box
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-api-docs': patch
---
Migrate to new composability API, exporting the plugin as `apiDocsPlugin`, index page as `ApiExplorerPage`, and entity page cards as `EntityApiDefinitionCard`, `EntityConsumedApisCard`, `EntityConsumingComponentsCard`, `EntityProvidedApisCard`, and `EntityProvidingComponentsCard`.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-catalog': patch
---
Finalize migration to new composability API, with the plugin instance now exported `catalogPlugin`.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-github-actions': patch
---
Migrate to new composability API, exporting the plugin instance as `githubActionsPlugin`, the entity content as `EntityGithubActionsContent`, entity conditional as `isGithubActionsAvailable`, and entity cards as `EntityLatestGithubActionRunCard`, `EntityLatestGithubActionsForBranchCard`, and `EntityRecentGithubActionsRunsCard`.
-6
View File
@@ -1,6 +0,0 @@
---
'@backstage/plugin-tech-radar': patch
---
Fix mapping RadarEntry and Entry for moved and url attributes
Fix clicking of links in the radar legend
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-newrelic': patch
---
Migrate to new composability API, exporting the plugin instance as `newRelicPlugin`, and the root page as `NewRelicPage`.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/core': patch
---
Fix check that determines whether popup was closed or the messaging was misconfigured.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/backend-common': patch
---
Implement `UrlReader.search` which implements glob matching.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-kubernetes': patch
---
Migrate to new composability API, exporting the plugin instance as `kubernetesPlugin` and entity content as `EntityKubernetesContent`.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-sonarqube': patch
---
Migrate to new composability API, exporting the plugin as `sonarQubePlugin` and card as `EntitySonarQubeCard`.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/catalog-model': patch
---
Introduce json schema variants of the `yup` validation schemas
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-kafka': patch
---
Migrate to new composability API, exporting the plugin instance as `kafkaPlugin`, entity content as `EntityKafkaContent`, and entity conditional as `isKafkaAvailable`.
-23
View File
@@ -1,23 +0,0 @@
---
'@backstage/plugin-scaffolder-backend': patch
---
# Repo visibility for GitLab and BitBucket repos
**NOTE: This changes default repo visibility from `private` to `public` for GitLab and BitBucket** which
is consistent with the GitHub default. If you were counting on `private` visibility, you'll need to update
your scaffolder config to use `private`.
This adds repo visibility feature parity with GitHub for GitLab and BitBucket.
To configure the repo visibility, set scaffolder._type_.visibility as in this example:
```yaml
scaffolder:
github:
visibility: private # 'public' or 'internal' or 'private' (default is 'public')
gitlab:
visibility: public # 'public' or 'internal' or 'private' (default is 'public')
bitbucket:
visibility: public # 'public' or 'private' (default is 'public')
```
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-welcome': patch
---
Migrated to new composability API, exporting the plugin as `welcomePlugin` and the page as `WelcomePage`.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-backend': patch
---
Refactored route response handling to use more explicit types and throw errors.
@@ -1,5 +0,0 @@
---
'@backstage/techdocs-common': patch
---
dir preparer will use URL Reader in its implementation.
@@ -0,0 +1,5 @@
---
'@backstage/plugin-techdocs': patch
---
Enhance API calls to support trapping 500 errors from techdocs-backend
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-user-settings': patch
---
Keep the Pin Sidebar setting visible on small screens.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/core': patch
---
Fixed type inference of `createRouteRef`.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-gitops-profiles': patch
---
Migrated to new composability API, exporting the plugin instance as `gitopsProfilesPlugin` and pages as `GitopsProfilesClusterListPage`, `GitopsProfilesClusterPage`, and `GitopsProfilesCreatePage`.
-20
View File
@@ -1,20 +0,0 @@
---
'@backstage/plugin-api-docs': patch
'@backstage/plugin-catalog-import': patch
'@backstage/plugin-circleci': patch
'@backstage/plugin-cloudbuild': patch
'@backstage/plugin-github-actions': patch
'@backstage/plugin-jenkins': patch
'@backstage/plugin-kafka': patch
'@backstage/plugin-lighthouse': patch
'@backstage/plugin-org': patch
'@backstage/plugin-register-component': patch
'@backstage/plugin-rollbar': patch
'@backstage/plugin-scaffolder': patch
'@backstage/plugin-search': patch
'@backstage/plugin-sentry': patch
'@backstage/plugin-techdocs': patch
'@backstage/dev-utils': patch
---
Switch dependency from `@backstage/plugin-catalog` to `@backstage/plugin-catalog-react`.
-61
View File
@@ -1,61 +0,0 @@
---
'@backstage/plugin-scaffolder-backend': minor
---
The scaffolder is updated to generate a unique workspace directory inside the temp folder. This directory is cleaned up by the job processor after each run.
The prepare/template/publish steps have been refactored to operate on known directories, `template/` and `result/`, inside the temporary workspace path.
Updated preparers to accept the template url instead of the entire template. This is done primarily to allow for backwards compatibility between v1 and v2 scaffolder templates.
Fixes broken GitHub actions templating in the Create React App template.
#### For those with **custom** preparers, templates, or publishers
The preparer interface has changed, the prepare method now only takes a single argument, and doesn't return anything. As part of this change the preparers were refactored to accept a URL pointing to the target directory, rather than computing that from the template entity.
The `workingDirectory` option was also removed, and replaced with a `workspacePath` option. The difference between the two is that `workingDirectory` was a place for the preparer to create temporary directories, while the `workspacePath` is the specific folder were the entire templating process for a single template job takes place. Instead of returning a path to the folder were the prepared contents were placed, the contents are put at the `<workspacePath>/template` path.
```diff
type PreparerOptions = {
- workingDirectory?: string;
+ /**
+ * Full URL to the directory containg template data
+ */
+ url: string;
+ /**
+ * The workspace path that will eventually be the the root of the new repo
+ */
+ workspacePath: string;
logger: Logger;
};
-prepare(template: TemplateEntityV1alpha1, opts?: PreparerOptions): Promise<string>
+prepare(opts: PreparerOptions): Promise<void>;
```
Instead of returning a path to the folder were the templaters contents were placed, the contents are put at the `<workspacePath>/result` path. All templaters now also expect the source template to be present in the `template` directory within the `workspacePath`.
```diff
export type TemplaterRunOptions = {
- directory: string;
+ workspacePath: string;
values: TemplaterValues;
logStream?: Writable;
dockerClient: Docker;
};
-public async run(options: TemplaterRunOptions): Promise<TemplaterRunResult>
+public async run(options: TemplaterRunOptions): Promise<void>
```
Just like the preparer and templaters, the publishers have also switched to using `workspacePath`. The root of the new repo is expected to be located at `<workspacePath>/result`.
```diff
export type PublisherOptions = {
values: TemplaterValues;
- directory: string;
+ workspacePath: string;
logger: Logger;
};
```
-8
View File
@@ -1,8 +0,0 @@
---
'@backstage/plugin-catalog': minor
'@backstage/create-app': minor
---
`@backstage/plugin-catalog` stopped exporting hooks and helpers for other
plugins. They are migrated to `@backstage/plugin-catalog-react`.
Change both your dependencies and imports to the new package.
+1
View File
@@ -249,6 +249,7 @@ transpilation
Tuite
ui
unmanaged
unregister
untracked
upvote
url
+1
View File
@@ -3,6 +3,7 @@ name: E2E Test Linux
on:
pull_request:
paths-ignore:
- '.changeset/**'
- 'contrib/**'
- 'docs/**'
- 'microsite/**'
+3
View File
@@ -73,6 +73,9 @@ organization:
name: My Company
# Reference documentation http://backstage.io/docs/features/techdocs/configuration
# Note: After experimenting with basic setup, use CI/CD to generate docs
# and an external cloud storage when deploying TechDocs for production use-case.
# https://backstage.io/docs/features/techdocs/how-to-guides#how-to-migrate-from-techdocs-basic-to-recommended-deployment-approach
techdocs:
builder: 'local' # Alternatives - 'external'
generators:
+12 -6
View File
@@ -24,12 +24,18 @@ Backstage ecosystem.
## Project roadmap
| Version | Description |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Backstage Search V.0 ✅ | Search Frontend letting you search through the entities of the software catalog. [See V.0 Use Cases.](#backstage-search-v0) |
| Backstage Search V.1 ⌛ | Basic “out-of-the-box” in-memory indexing process of entities, and their metadata, registered to the Software Catalog. [See V.1 Use Cases.](#backstage-search-v1) |
| Backstage Search V.2 ⌛ | Search Backend responsible for the indexing process of entities, and their metadata, registered to the Software Catalog. [See V.2 Use Cases.](#backstage-search-v2) |
| Backstage Search V.3 ⌛ | Standardized Search API lets you index other plugins data to the search engine of choice. [See V.3 Use Cases.](#backstage-search-v3) |
| Version | Description |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Backstage Search v0 ✅ | Search Frontend letting you search through the entities of the software catalog. [See v0 Use Cases.](#backstage-search-v0) |
| [Backstage Search V0.5 ⌛][v0.5] | Foundations for the architecture. |
| [Backstage Search v1 ⌛][v1] | Basic “out-of-the-box” in-memory indexing process of entities, and their metadata, registered to the Software Catalog. [See v1 Use Cases.](#backstage-search-v1) |
| [Backstage Search v2 ⌛][v2] | Search Backend responsible for the indexing process of entities, and their metadata, registered to the Software Catalog. [See v2 Use Cases.](#backstage-search-v2) |
| [Backstage Search v3 ⌛][v3] | Standardized Search API lets you index other plugins data to the search engine of choice. [See v3 Use Cases.](#backstage-search-v3) |
[v0.5]: https://github.com/backstage/backstage/milestone/25
[v1]: https://github.com/backstage/backstage/milestone/26
[v2]: https://github.com/backstage/backstage/milestone/27
[v3]: https://github.com/backstage/backstage/milestone/28
## Use Cases
@@ -80,6 +80,10 @@ Create a `/docs` folder in the root of the project with at least an `index.md`
file. _(If you add more markdown files, make sure to update the nav in the
mkdocs.yml file to get a proper navigation for your documentation.)_
> Note - Although `docs` is a popular directory name for storing documentation,
> it can be renamed to something else and can be configured by `mkdocs.yml`. See
> https://www.mkdocs.org/user-guide/configuration/#docs_dir
The `docs/index.md` can for example have the following content:
```md
+1 -1
View File
@@ -90,7 +90,7 @@ $ docker build -t example-deployment .
To run the image locally you can run:
```sh
$ docker run -p -it 7000:7000 example-deployment
$ docker run -it -p 7000:7000 example-deployment
```
You should then start to get logs in your terminal, and then you can open your
-8
View File
@@ -384,11 +384,3 @@ A frontend plugin that provides a page where the user can tweak various
settings.
Stability: `1`
### `welcome` [GitHub](https://github.com/backstage/backstage/tree/master/plugins/welcome/)
A plugin that can be used to welcome the user to Backstage.
Stability: `0`. This used to be the start page for the example app, but has been
replaced by the catalog plugin. It is still viewable at `/welcome` but may be
removed.
+1 -1
View File
@@ -14,7 +14,7 @@ Here's a code sample:
import { createPlugin } from '@backstage/core';
export default createPlugin({
id: 'welcome',
id: 'plugin-name',
register({ featureFlags }) {
featureFlags.register('enable-example-feature');
},
+4
View File
@@ -1115,6 +1115,10 @@ code {
font-size: 115%;
}
.post pre {
color: #e4e4e4;
}
.Block__GIF {
width: 190px;
height: auto;
+2 -1
View File
@@ -37,11 +37,12 @@
},
"resolutions": {
"**/@roadiehq/**/@backstage/core": "*",
"**/@roadiehq/**/@backstage/plugin-catalog": "*",
"**/@roadiehq/**/@backstage/catalog-model": "*"
},
"version": "1.0.0",
"devDependencies": {
"@changesets/cli": "^2.11.0",
"@changesets/cli": "^2.14.0",
"@octokit/openapi-types": "^2.2.0",
"@spotify/eslint-config-oss": "^1.0.1",
"@spotify/prettier-config": "^9.0.0",
+76
View File
@@ -1,5 +1,81 @@
# example-app
## 0.2.14
### Patch Changes
- 9d6ef14bc: Migrated to new composability API, exporting the plugin instance as `rollbarPlugin`, the entity page content as `EntityRollbarContent`, and entity conditional as `isRollbarAvailable`. Updated the `EntityPage` for the `example-app` to include a composite `ErrorsSwitcher` component that works with both `Sentry` & `Rollbar`. Also removed the unused and undocumented `RollbarHome` related components.
- Updated dependencies [ceef4dd89]
- Updated dependencies [720149854]
- Updated dependencies [19172f5a9]
- Updated dependencies [4c6a6dddd]
- Updated dependencies [398e1f83e]
- Updated dependencies [87b189d00]
- Updated dependencies [12ece98cd]
- Updated dependencies [d82246867]
- Updated dependencies [7fc89bae2]
- Updated dependencies [c810082ae]
- Updated dependencies [b712841d6]
- Updated dependencies [a5628df40]
- Updated dependencies [5fa3bdb55]
- Updated dependencies [bc5082a00]
- Updated dependencies [6e612ce25]
- Updated dependencies [e44925723]
- Updated dependencies [b37501a3d]
- Updated dependencies [9d6ef14bc]
- Updated dependencies [025e122c3]
- Updated dependencies [e9aab60c7]
- Updated dependencies [21e624ba9]
- Updated dependencies [0269f4fd9]
- Updated dependencies [19fe61c27]
- Updated dependencies [da9f53c60]
- Updated dependencies [a08c4b0b0]
- Updated dependencies [bc5082a00]
- Updated dependencies [bc5082a00]
- Updated dependencies [b37501a3d]
- Updated dependencies [90c8f20b9]
- Updated dependencies [32c95605f]
- Updated dependencies [7881f2117]
- Updated dependencies [8dfdec613]
- Updated dependencies [54c7d02f7]
- Updated dependencies [de98c32ed]
- Updated dependencies [806929fe2]
- Updated dependencies [019fe39a0]
- Updated dependencies [019fe39a0]
- Updated dependencies [11cb5ef94]
- @backstage/plugin-catalog-import@0.3.7
- @backstage/plugin-scaffolder@0.4.2
- @backstage/plugin-cost-insights@0.8.0
- @backstage/cli@0.6.0
- @backstage/plugin-graphiql@0.2.7
- @backstage/core@0.6.0
- @backstage/plugin-api-docs@0.4.4
- @backstage/plugin-catalog@0.3.0
- @backstage/plugin-catalog-react@0.0.2
- @backstage/plugin-org@0.3.5
- @backstage/theme@0.2.3
- @backstage/plugin-lighthouse@0.2.9
- @backstage/plugin-techdocs@0.5.5
- @backstage/plugin-user-settings@0.2.5
- @backstage/catalog-model@0.7.1
- @backstage/plugin-rollbar@0.2.9
- @backstage/plugin-gcp-projects@0.2.4
- @backstage/plugin-tech-radar@0.3.4
- @backstage/plugin-welcome@0.2.5
- @backstage/plugin-explore@0.2.4
- @backstage/plugin-circleci@0.2.7
- @backstage/plugin-cloudbuild@0.2.8
- @backstage/plugin-github-actions@0.3.1
- @backstage/plugin-jenkins@0.3.8
- @backstage/plugin-kafka@0.2.1
- @backstage/plugin-register-component@0.2.8
- @backstage/plugin-search@0.2.7
- @backstage/plugin-sentry@0.3.4
- @backstage/plugin-gitops-profiles@0.2.4
- @backstage/plugin-kubernetes@0.3.8
- @backstage/plugin-newrelic@0.2.4
- @backstage/plugin-pagerduty@0.2.7
## 0.2.13
### Patch Changes
+32 -33
View File
@@ -1,41 +1,40 @@
{
"name": "example-app",
"version": "0.2.13",
"version": "0.2.14",
"private": true,
"bundled": true,
"dependencies": {
"@backstage/catalog-model": "^0.7.0",
"@backstage/cli": "^0.5.0",
"@backstage/core": "^0.5.0",
"@backstage/plugin-api-docs": "^0.4.3",
"@backstage/plugin-catalog": "^0.2.14",
"@backstage/plugin-catalog-react": "^0.0.1",
"@backstage/plugin-catalog-import": "^0.3.6",
"@backstage/plugin-circleci": "^0.2.6",
"@backstage/plugin-cloudbuild": "^0.2.7",
"@backstage/plugin-cost-insights": "^0.7.0",
"@backstage/plugin-explore": "^0.2.3",
"@backstage/plugin-gcp-projects": "^0.2.3",
"@backstage/plugin-github-actions": "^0.3.0",
"@backstage/plugin-gitops-profiles": "^0.2.3",
"@backstage/plugin-graphiql": "^0.2.6",
"@backstage/plugin-org": "^0.3.4",
"@backstage/plugin-jenkins": "^0.3.6",
"@backstage/plugin-kafka": "^0.2.0",
"@backstage/plugin-kubernetes": "^0.3.7",
"@backstage/plugin-lighthouse": "^0.2.8",
"@backstage/plugin-newrelic": "^0.2.3",
"@backstage/plugin-pagerduty": "0.2.6",
"@backstage/plugin-register-component": "^0.2.7",
"@backstage/plugin-rollbar": "^0.2.8",
"@backstage/plugin-scaffolder": "^0.4.1",
"@backstage/plugin-sentry": "^0.3.3",
"@backstage/plugin-search": "^0.2.6",
"@backstage/plugin-tech-radar": "^0.3.3",
"@backstage/plugin-techdocs": "^0.5.4",
"@backstage/plugin-user-settings": "^0.2.4",
"@backstage/plugin-welcome": "^0.2.4",
"@backstage/theme": "^0.2.2",
"@backstage/catalog-model": "^0.7.1",
"@backstage/cli": "^0.6.0",
"@backstage/core": "^0.6.0",
"@backstage/plugin-api-docs": "^0.4.4",
"@backstage/plugin-catalog": "^0.3.0",
"@backstage/plugin-catalog-react": "^0.0.2",
"@backstage/plugin-catalog-import": "^0.3.7",
"@backstage/plugin-circleci": "^0.2.7",
"@backstage/plugin-cloudbuild": "^0.2.8",
"@backstage/plugin-cost-insights": "^0.8.0",
"@backstage/plugin-explore": "^0.2.4",
"@backstage/plugin-gcp-projects": "^0.2.4",
"@backstage/plugin-github-actions": "^0.3.1",
"@backstage/plugin-gitops-profiles": "^0.2.4",
"@backstage/plugin-graphiql": "^0.2.7",
"@backstage/plugin-org": "^0.3.5",
"@backstage/plugin-jenkins": "^0.3.8",
"@backstage/plugin-kafka": "^0.2.1",
"@backstage/plugin-kubernetes": "^0.3.8",
"@backstage/plugin-lighthouse": "^0.2.9",
"@backstage/plugin-newrelic": "^0.2.4",
"@backstage/plugin-pagerduty": "0.2.7",
"@backstage/plugin-register-component": "^0.2.8",
"@backstage/plugin-rollbar": "^0.2.9",
"@backstage/plugin-scaffolder": "^0.4.2",
"@backstage/plugin-sentry": "^0.3.4",
"@backstage/plugin-search": "^0.2.7",
"@backstage/plugin-tech-radar": "^0.3.4",
"@backstage/plugin-techdocs": "^0.5.5",
"@backstage/plugin-user-settings": "^0.2.5",
"@backstage/theme": "^0.2.3",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@octokit/rest": "^18.0.12",
+15 -14
View File
@@ -15,30 +15,30 @@
*/
import {
createApp,
AlertDisplay,
OAuthRequestDialog,
SignInPage,
createApp,
createRouteRef,
FlatRoutes,
OAuthRequestDialog,
SignInPage,
} from '@backstage/core';
import React from 'react';
import Root from './components/Root';
import * as plugins from './plugins';
import { apis } from './apis';
import { hot } from 'react-hot-loader/root';
import { providers } from './identityProviders';
import { Router as CatalogRouter } from '@backstage/plugin-catalog';
import { Router as DocsRouter } from '@backstage/plugin-techdocs';
import { Router as ImportComponentRouter } from '@backstage/plugin-catalog-import';
import { ExplorePage } from '@backstage/plugin-explore';
import { Router as GraphiQLRouter } from '@backstage/plugin-graphiql';
import { Router as TechRadarRouter } from '@backstage/plugin-tech-radar';
import { Router as LighthouseRouter } from '@backstage/plugin-lighthouse';
import { Router as RegisterComponentRouter } from '@backstage/plugin-register-component';
import { Router as TechRadarRouter } from '@backstage/plugin-tech-radar';
import { Router as DocsRouter } from '@backstage/plugin-techdocs';
import { Router as SettingsRouter } from '@backstage/plugin-user-settings';
import { Router as ImportComponentRouter } from '@backstage/plugin-catalog-import';
import { Route, Navigate } from 'react-router';
import React from 'react';
import { hot } from 'react-hot-loader/root';
import { Navigate, Route } from 'react-router';
import { apis } from './apis';
import { EntityPage } from './components/catalog/EntityPage';
import Root from './components/Root';
import { providers } from './identityProviders';
import * as plugins from './plugins';
const app = createApp({
apis,
@@ -78,6 +78,7 @@ const routes = (
element={<CatalogRouter EntityPage={EntityPage} />}
/>
<Route path="/docs" element={<DocsRouter />} />
<Route path="/explore" element={<ExplorePage />} />
<Route
path="/tech-radar"
element={<TechRadarRouter width={1500} height={800} />}
@@ -20,6 +20,7 @@ import HomeIcon from '@material-ui/icons/Home';
import ExtensionIcon from '@material-ui/icons/Extension';
import RuleIcon from '@material-ui/icons/AssignmentTurnedIn';
import MapIcon from '@material-ui/icons/MyLocation';
import LayersIcon from '@material-ui/icons/Layers';
import LibraryBooks from '@material-ui/icons/LibraryBooks';
import CreateComponentIcon from '@material-ui/icons/AddCircleOutline';
import MoneyIcon from '@material-ui/icons/MonetizationOn';
@@ -82,6 +83,7 @@ const Root = ({ children }: PropsWithChildren<{}>) => (
<SidebarItem icon={HomeIcon} to="/catalog" text="Home" />
<SidebarItem icon={ExtensionIcon} to="api-docs" text="APIs" />
<SidebarItem icon={LibraryBooks} to="docs" text="Docs" />
<SidebarItem icon={LayersIcon} to="explore" text="Explore" />
<SidebarItem icon={CreateComponentIcon} to="create" text="Create..." />
{/* End global nav */}
<SidebarDivider />
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
ApiEntity,
Entity,
@@ -64,6 +65,10 @@ import {
isPluginApplicableToEntity as isPagerDutyAvailable,
PagerDutyCard,
} from '@backstage/plugin-pagerduty';
import {
isRollbarAvailable,
Router as RollbarRouter,
} from '@backstage/plugin-rollbar';
import { Router as SentryRouter } from '@backstage/plugin-sentry';
import { EmbeddedDocsRouter as DocsRouter } from '@backstage/plugin-techdocs';
import { Button, Grid } from '@material-ui/core';
@@ -153,6 +158,15 @@ const RecentCICDRunsSwitcher = ({ entity }: { entity: Entity }) => {
);
};
export const ErrorsSwitcher = ({ entity }: { entity: Entity }) => {
switch (true) {
case isRollbarAvailable(entity):
return <RollbarRouter entity={entity} />;
default:
return <SentryRouter entity={entity} />;
}
};
const ComponentOverviewContent = ({ entity }: { entity: Entity }) => (
<Grid container spacing={3} alignItems="stretch">
<Grid item md={6}>
@@ -212,9 +226,9 @@ const ServiceEntityPage = ({ entity }: { entity: Entity }) => (
element={<CICDSwitcher entity={entity} />}
/>
<EntityPageLayout.Content
path="/sentry"
title="Sentry"
element={<SentryRouter entity={entity} />}
path="/errors/*"
title="Errors"
element={<ErrorsSwitcher entity={entity} />}
/>
<EntityPageLayout.Content
path="/api/*"
@@ -267,9 +281,9 @@ const WebsiteEntityPage = ({ entity }: { entity: Entity }) => (
element={<LighthouseRouter entity={entity} />}
/>
<EntityPageLayout.Content
path="/sentry"
title="Sentry"
element={<SentryRouter entity={entity} />}
path="/errors/*"
title="Errors"
element={<ErrorsSwitcher entity={entity} />}
/>
<EntityPageLayout.Content
path="/docs/*"
+1 -2
View File
@@ -13,12 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { plugin as WelcomePlugin } from '@backstage/plugin-welcome';
export { plugin as LighthousePlugin } from '@backstage/plugin-lighthouse';
export { plugin as CatalogPlugin } from '@backstage/plugin-catalog';
export { plugin as ScaffolderPlugin } from '@backstage/plugin-scaffolder';
export { plugin as TechRadar } from '@backstage/plugin-tech-radar';
export { plugin as Explore } from '@backstage/plugin-explore';
export { explorePlugin } from '@backstage/plugin-explore';
export { plugin as Circleci } from '@backstage/plugin-circleci';
export { plugin as RegisterComponent } from '@backstage/plugin-register-component';
export { plugin as Sentry } from '@backstage/plugin-sentry';
+34
View File
@@ -1,5 +1,39 @@
# @backstage/backend-common
## 0.5.2
### Patch Changes
- 2430ee7c2: Updated the `rootLogger` in `@backstage/backend-common` to support custom logging options. This is useful when you want to make some changes without re-implementing the entire logger and calling `setRootLogger` or `logger.configure`. For example you can add additional `defaultMeta` tags to each log entry. The following changes are included:
- Added `createRootLogger` which accepts winston `LoggerOptions`. These options allow overriding the default keys.
Example Usage:
```ts
// Create the logger
const logger = createRootLogger({
defaultMeta: { appName: 'backstage', appEnv: 'prod' },
});
// Add a custom logger transport
logger.add(new MyCustomTransport());
const config = await loadBackendConfig({
argv: process.argv,
logger: getRootLogger(), // already set to new logger instance
});
```
- Updated dependencies [c4abcdb60]
- Updated dependencies [062df71db]
- Updated dependencies [064c513e1]
- Updated dependencies [e9aab60c7]
- Updated dependencies [3149bfe63]
- Updated dependencies [2e62aea6f]
- @backstage/integration@0.3.2
- @backstage/config-loader@0.5.1
## 0.5.1
### Patch Changes
+6 -4
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/backend-common",
"description": "Common functionality library for Backstage backends",
"version": "0.5.1",
"version": "0.5.2",
"main": "src/index.ts",
"types": "src/index.ts",
"private": false,
@@ -31,8 +31,9 @@
"dependencies": {
"@backstage/cli-common": "^0.1.1",
"@backstage/config": "^0.1.2",
"@backstage/config-loader": "^0.5.0",
"@backstage/integration": "^0.3.1",
"@backstage/config-loader": "^0.5.1",
"@backstage/integration": "^0.3.2",
"@octokit/rest": "^18.0.12",
"@types/cors": "^2.8.6",
"@types/express": "^4.17.6",
"archiver": "^5.0.2",
@@ -49,6 +50,7 @@
"knex": "^0.21.6",
"lodash": "^4.17.15",
"logform": "^2.1.1",
"minimatch": "^3.0.4",
"minimist": "^1.2.5",
"morgan": "^1.10.0",
"selfsigned": "^1.10.7",
@@ -66,7 +68,7 @@
}
},
"devDependencies": {
"@backstage/cli": "^0.5.0",
"@backstage/cli": "^0.6.0",
"@backstage/test-utils": "^0.1.5",
"@types/archiver": "^5.1.0",
"@types/compression": "^1.7.0",
@@ -63,10 +63,10 @@ export function errorHandler(
return (
error: Error,
_request: Request,
response: Response,
res: Response,
next: NextFunction,
) => {
if (response.headersSent) {
if (res.headersSent) {
// If the headers have already been sent, do not send the response again
// as this will throw an error in the backend.
next(error);
@@ -80,7 +80,9 @@ export function errorHandler(
logger.error(error);
}
response.status(status).send(message);
res.status(status);
res.setHeader('content-type', 'text/plain');
res.send(message);
};
}
@@ -29,6 +29,7 @@ import {
ReaderFactory,
ReadTreeOptions,
ReadTreeResponse,
SearchResponse,
UrlReader,
} from './types';
import { ReadTreeResponseFactory } from './tree';
@@ -116,6 +117,10 @@ export class AzureUrlReader implements UrlReader {
});
}
async search(): Promise<SearchResponse> {
throw new Error('AzureUrlReader does not implement search');
}
toString() {
const { host, token } = this.options;
return `azure{host=${host},authed=${Boolean(token)}}`;
@@ -31,6 +31,7 @@ import {
ReaderFactory,
ReadTreeOptions,
ReadTreeResponse,
SearchResponse,
UrlReader,
} from './types';
@@ -129,6 +130,10 @@ export class BitbucketUrlReader implements UrlReader {
});
}
async search(): Promise<SearchResponse> {
throw new Error('BitbucketUrlReader does not implement search');
}
toString() {
const { host, token, username, appPassword } = this.config;
let authed = Boolean(token);
@@ -16,7 +16,12 @@
import fetch from 'cross-fetch';
import { NotFoundError } from '../errors';
import { ReaderFactory, ReadTreeResponse, UrlReader } from './types';
import {
ReaderFactory,
ReadTreeResponse,
SearchResponse,
UrlReader,
} from './types';
/**
* A UrlReader that does a plain fetch of the URL.
@@ -68,10 +73,14 @@ export class FetchUrlReader implements UrlReader {
throw new Error(message);
}
readTree(): Promise<ReadTreeResponse> {
async readTree(): Promise<ReadTreeResponse> {
throw new Error('FetchUrlReader does not implement readTree');
}
async search(): Promise<SearchResponse> {
throw new Error('FetchUrlReader does not implement search');
}
toString() {
return 'fetch{}';
}
@@ -23,7 +23,13 @@ import { rest } from 'msw';
import { setupServer } from 'msw/node';
import path from 'path';
import { NotFoundError, NotModifiedError } from '../errors';
import { GithubUrlReader } from './GithubUrlReader';
import {
GhBlobResponse,
GhBranchResponse,
GhRepoResponse,
GhTreeResponse,
GithubUrlReader,
} from './GithubUrlReader';
import { ReadTreeResponseFactory } from './tree';
const treeResponseFactory = ReadTreeResponseFactory.create({
@@ -69,6 +75,10 @@ describe('GithubUrlReader', () => {
});
});
/*
* read
*/
describe('read', () => {
it('should use the headers from the credentials provider to the fetch request when doing read', async () => {
expect.assertions(2);
@@ -107,6 +117,10 @@ describe('GithubUrlReader', () => {
});
});
/*
* readTree
*/
describe('readTree', () => {
beforeEach(() => {
mockFs({
@@ -128,29 +142,31 @@ describe('GithubUrlReader', () => {
);
const reposGithubApiResponse = {
id: '123',
id: 123,
full_name: 'backstage/mock',
default_branch: 'main',
branches_url:
'https://api.github.com/repos/backstage/mock/branches{/branch}',
archive_url:
'https://api.github.com/repos/backstage/mock/{archive_format}{/ref}',
};
} as Partial<GhRepoResponse>;
const reposGheApiResponse = {
...reposGithubApiResponse,
id: 123,
full_name: 'backstage/mock',
default_branch: 'main',
branches_url:
'https://ghe.github.com/api/v3/repos/backstage/mock/branches{/branch}',
archive_url:
'https://ghe.github.com/api/v3/repos/backstage/mock/{archive_format}{/ref}',
};
} as Partial<GhRepoResponse>;
const branchesApiResponse = {
name: 'main',
commit: {
sha: 'etag123abc',
},
};
} as Partial<GhBranchResponse>;
beforeEach(() => {
worker.use(
@@ -392,4 +408,359 @@ describe('GithubUrlReader', () => {
}).toThrowError('must configure an explicit apiBaseUrl');
});
});
/*
* search
*/
describe('search', () => {
beforeEach(() => {
mockFs({ '/tmp': mockFs.directory() });
});
afterEach(() => {
mockFs.restore();
});
const repoBuffer = fs.readFileSync(
path.resolve(
'src',
'reading',
'__fixtures__',
'backstage-mock-etag123.tar.gz',
),
);
const githubTreeContents: GhTreeResponse['tree'] = [
{
path: 'mkdocs.yml',
type: 'blob',
url: 'https://api.github.com/repos/backstage/mock/git/blobs/1',
},
{
path: 'docs',
type: 'tree',
url: 'https://api.github.com/repos/backstage/mock/git/trees/2',
},
{
path: 'docs/index.md',
type: 'blob',
url: 'https://api.github.com/repos/backstage/mock/git/blobs/3',
},
];
const gheTreeContents: GhTreeResponse['tree'] = [
{
path: 'mkdocs.yml',
type: 'blob',
url: 'https://ghe.github.com/api/v3/repos/backstage/mock/git/blobs/1',
},
{
path: 'docs',
type: 'tree',
url: 'https://ghe.github.com/api/v3/repos/backstage/mock/git/trees/2',
},
{
path: 'docs/index.md',
type: 'blob',
url: 'https://ghe.github.com/api/v3/repos/backstage/mock/git/blobs/3',
},
];
// Tarballs
beforeEach(() => {
worker.use(
rest.get(
'https://api.github.com/repos/backstage/mock/tarball/etag123abc',
(_, res, ctx) =>
res(
ctx.status(200),
ctx.set('Content-Type', 'application/x-gzip'),
ctx.set(
'content-disposition',
'attachment; filename=backstage-mock-etag123.tar.gz',
),
ctx.body(repoBuffer),
),
),
rest.get(
'https://ghe.github.com/api/v3/repos/backstage/mock/tarball/etag123abc',
(_, res, ctx) =>
res(
ctx.status(200),
ctx.set('Content-Type', 'application/x-gzip'),
ctx.set(
'content-disposition',
'attachment; filename=backstage-mock-etag123.tar.gz',
),
ctx.body(repoBuffer),
),
),
);
});
// Repo details
beforeEach(() => {
const githubResponse = {
id: 123,
full_name: 'backstage/mock',
default_branch: 'main',
branches_url:
'https://api.github.com/repos/backstage/mock/branches{/branch}',
archive_url:
'https://api.github.com/repos/backstage/mock/{archive_format}{/ref}',
trees_url:
'https://api.github.com/repos/backstage/mock/git/trees{/sha}',
} as Partial<GhRepoResponse>;
const gheResponse = {
id: 123,
full_name: 'backstage/mock',
default_branch: 'main',
branches_url:
'https://ghe.github.com/api/v3/repos/backstage/mock/branches{/branch}',
archive_url:
'https://ghe.github.com/api/v3/repos/backstage/mock/{archive_format}{/ref}',
trees_url:
'https://ghe.github.com/api/v3/repos/backstage/mock/git/trees{/sha}',
} as Partial<GhRepoResponse>;
worker.use(
rest.get('https://api.github.com/repos/backstage/mock', (_, res, ctx) =>
res(
ctx.status(200),
ctx.set('Content-Type', 'application/json'),
ctx.json(githubResponse),
),
),
rest.get(
'https://ghe.github.com/api/v3/repos/backstage/mock',
(_, res, ctx) =>
res(
ctx.status(200),
ctx.set('Content-Type', 'application/json'),
ctx.json(gheResponse),
),
),
);
});
// Branch details
beforeEach(() => {
const response = {
name: 'main',
commit: {
sha: 'etag123abc',
},
} as Partial<GhBranchResponse>;
worker.use(
rest.get(
'https://api.github.com/repos/backstage/mock/branches/main',
(_, res, ctx) =>
res(
ctx.status(200),
ctx.set('Content-Type', 'application/json'),
ctx.json(response),
),
),
rest.get(
'https://ghe.github.com/api/v3/repos/backstage/mock/branches/main',
(_, res, ctx) =>
res(
ctx.status(200),
ctx.set('Content-Type', 'application/json'),
ctx.json(response),
),
),
rest.get(
'https://api.github.com/repos/backstage/mock/branches/branchDoesNotExist',
(_, res, ctx) => res(ctx.status(404)),
),
);
});
// Blobs
beforeEach(() => {
const blob1Response = {
content: Buffer.from('site_name: Test\n').toString('base64'),
} as Partial<GhBlobResponse>;
const blob3Response = {
content: Buffer.from('# Test\n').toString('base64'),
} as Partial<GhBlobResponse>;
worker.use(
rest.get(
'https://api.github.com/repos/backstage/mock/git/blobs/1',
(_, res, ctx) =>
res(
ctx.status(200),
ctx.set('Content-Type', 'application/json'),
ctx.json(blob1Response),
),
),
rest.get(
'https://api.github.com/repos/backstage/mock/git/blobs/3',
(_, res, ctx) =>
res(
ctx.status(200),
ctx.set('Content-Type', 'application/json'),
ctx.json(blob3Response),
),
),
rest.get(
'https://ghe.github.com/api/v3/repos/backstage/mock/git/blobs/1',
(_, res, ctx) =>
res(
ctx.status(200),
ctx.set('Content-Type', 'application/json'),
ctx.json(blob1Response),
),
),
rest.get(
'https://ghe.github.com/api/v3/repos/backstage/mock/git/blobs/3',
(_, res, ctx) =>
res(
ctx.status(200),
ctx.set('Content-Type', 'application/json'),
ctx.json(blob3Response),
),
),
);
});
async function runTests(reader: GithubUrlReader, baseUrl: string) {
const r1 = await reader.search(
`${baseUrl}/backstage/mock/tree/main/**/*`,
);
expect(r1.etag).toBe('etag123abc');
expect(r1.files.length).toBe(2);
const r2 = await reader.search(
`${baseUrl}/backstage/mock/tree/main/**/*`,
{ etag: 'somethingElse' },
);
expect(r2.etag).toBe('etag123abc');
expect(r2.files.length).toBe(2);
const r3 = await reader.search(`${baseUrl}/backstage/mock/tree/main/o`);
expect(r3.files.length).toBe(0);
const r4 = await reader.search(
`${baseUrl}/backstage/mock/tree/main/*docs*`,
);
expect(r4.files.length).toBe(1);
expect(r4.files[0].url).toBe(
`${baseUrl}/backstage/mock/tree/main/mkdocs.yml`,
);
await expect(r4.files[0].content()).resolves.toEqual(
Buffer.from('site_name: Test\n'),
);
const r5 = await reader.search(
`${baseUrl}/backstage/mock/tree/main/*/index.*`,
);
expect(r5.files.length).toBe(1);
expect(r5.files[0].url).toBe(
`${baseUrl}/backstage/mock/tree/main/docs/index.md`,
);
await expect(r5.files[0].content()).resolves.toEqual(
Buffer.from('# Test\n'),
);
}
// eslint-disable-next-line jest/expect-expect
it('succeeds on github when going via repo listing', async () => {
worker.use(
rest.get(
'https://api.github.com/repos/backstage/mock/git/trees/etag123abc',
(_, res, ctx) =>
res(
ctx.status(200),
ctx.set('Content-Type', 'application/json'),
ctx.json({
truncated: false,
tree: githubTreeContents,
} as Partial<GhTreeResponse>),
),
),
);
await runTests(githubProcessor, 'https://github.com');
});
// eslint-disable-next-line jest/expect-expect
it('succeeds on github when going via readTree', async () => {
worker.use(
rest.get(
'https://api.github.com/repos/backstage/mock/git/trees/etag123abc',
(_, res, ctx) =>
res(
ctx.status(200),
ctx.set('Content-Type', 'application/json'),
ctx.json({
truncated: true,
tree: [],
} as Partial<GhTreeResponse>),
),
),
);
await runTests(githubProcessor, 'https://github.com');
});
// eslint-disable-next-line jest/expect-expect
it('succeeds on ghe when going via repo listing', async () => {
worker.use(
rest.get(
'https://ghe.github.com/api/v3/repos/backstage/mock/git/trees/etag123abc',
(_, res, ctx) =>
res(
ctx.status(200),
ctx.set('Content-Type', 'application/json'),
ctx.json({
truncated: false,
tree: gheTreeContents,
} as Partial<GhTreeResponse>),
),
),
);
await runTests(gheProcessor, 'https://ghe.github.com');
});
// eslint-disable-next-line jest/expect-expect
it('succeeds on ghe when going via readTree', async () => {
worker.use(
rest.get(
'https://ghe.github.com/api/v3/repos/backstage/mock/git/trees/etag123abc',
(_, res, ctx) =>
res(
ctx.status(200),
ctx.set('Content-Type', 'application/json'),
ctx.json({
truncated: true,
tree: [],
} as Partial<GhTreeResponse>),
),
),
);
await runTests(gheProcessor, 'https://ghe.github.com');
});
it('throws NotModifiedError when same etag', async () => {
await expect(
githubProcessor.search(
'https://githib.com/backstage/mock/tree/main/**/*',
{ etag: 'etag123abc' },
),
).rejects.toThrow(NotModifiedError);
});
it('throws NotFoundError when missing branch', async () => {
await expect(
githubProcessor.search(
'https://githib.com/backstage/mock/tree/branchDoesNotExist/**/*',
),
).rejects.toThrow(NotFoundError);
});
});
});
@@ -15,13 +15,15 @@
*/
import {
GitHubIntegrationConfig,
readGitHubIntegrationConfigs,
getGitHubFileFetchUrl,
GithubCredentialsProvider,
GitHubIntegrationConfig,
readGitHubIntegrationConfigs,
} from '@backstage/integration';
import { RestEndpointMethodTypes } from '@octokit/rest';
import fetch from 'cross-fetch';
import parseGitUrl from 'git-url-parse';
import { Minimatch } from 'minimatch';
import { Readable } from 'stream';
import { NotFoundError, NotModifiedError } from '../errors';
import { ReadTreeResponseFactory } from './tree';
@@ -29,9 +31,17 @@ import {
ReaderFactory,
ReadTreeOptions,
ReadTreeResponse,
SearchOptions,
SearchResponse,
SearchResponseFile,
UrlReader,
} from './types';
export type GhRepoResponse = RestEndpointMethodTypes['repos']['get']['response']['data'];
export type GhBranchResponse = RestEndpointMethodTypes['repos']['getBranch']['response']['data'];
export type GhTreeResponse = RestEndpointMethodTypes['git']['getTree']['response']['data'];
export type GhBlobResponse = RestEndpointMethodTypes['git']['getBlob']['response']['data'];
/**
* A processor that adds the ability to read files from GitHub v3 APIs, such as
* the one exposed by GitHub itself.
@@ -98,86 +108,190 @@ export class GithubUrlReader implements UrlReader {
url: string,
options?: ReadTreeOptions,
): Promise<ReadTreeResponse> {
const { ref, filepath, full_name } = parseGitUrl(url);
// Caveat: The ref will totally be incorrect if the branch name includes a /
// Thus, readTree can not work on url containing branch name that has a /
const { headers } = await this.deps.credentialsProvider.getCredentials({
url,
});
// Get GitHub API urls for the repository
const repoGitHubResponse = await fetch(
new URL(`${this.config.apiBaseUrl}/repos/${full_name}`).toString(),
{
headers,
},
);
if (!repoGitHubResponse.ok) {
const message = `Failed to read tree (repository) from ${url}, ${repoGitHubResponse.status} ${repoGitHubResponse.statusText}`;
if (repoGitHubResponse.status === 404) {
throw new NotFoundError(message);
}
throw new Error(message);
}
const repoResponseJson = await repoGitHubResponse.json();
// ref is an empty string if no branch is set in provided url to readTree.
// Use GitHub API to get the default branch of the repository.
const branch = ref || repoResponseJson.default_branch;
const branchesApiUrl = repoResponseJson.branches_url;
const archiveApiUrl = repoResponseJson.archive_url;
// Fetch the latest commit in the provided or default branch to compare against
// the provided sha.
const branchGitHubResponse = await fetch(
// branchesApiUrl looks like "https://api.github.com/repos/owner/repo/branches{/branch}"
branchesApiUrl.replace('{/branch}', `/${branch}`),
{
headers,
},
);
if (!branchGitHubResponse.ok) {
const message = `Failed to read tree (branch) from ${url}, ${branchGitHubResponse.status} ${branchGitHubResponse.statusText}`;
if (branchGitHubResponse.status === 404) {
throw new NotFoundError(message);
}
throw new Error(message);
}
const commitSha = (await branchGitHubResponse.json()).commit.sha;
const repoDetails = await this.getRepoDetails(url);
const commitSha = repoDetails.branch.commit.sha!;
if (options?.etag && options.etag === commitSha) {
throw new NotModifiedError();
}
const archive = await fetch(
// archiveApiUrl looks like "https://api.github.com/repos/owner/repo/{archive_format}{/ref}"
archiveApiUrl
.replace('{archive_format}', 'tarball')
.replace('{/ref}', `/${commitSha}`),
const { filepath } = parseGitUrl(url);
const { headers } = await this.deps.credentialsProvider.getCredentials({
url,
});
return this.doReadTree(
repoDetails.repo.archive_url,
commitSha,
filepath,
{ headers },
options,
);
if (!archive.ok) {
const message = `Failed to read tree (archive) from ${url}, ${archive.status} ${archive.statusText}`;
if (archive.status === 404) {
throw new NotFoundError(message);
}
throw new Error(message);
}
async search(url: string, options?: SearchOptions): Promise<SearchResponse> {
const repoDetails = await this.getRepoDetails(url);
const commitSha = repoDetails.branch.commit.sha!;
if (options?.etag && options.etag === commitSha) {
throw new NotModifiedError();
}
return await this.deps.treeResponseFactory.fromTarArchive({
// TODO(Rugvip): Underlying implementation of fetch will be node-fetch, we probably want
// to stick to using that in exclusively backend code.
stream: (archive.body as unknown) as Readable,
subpath: filepath,
etag: commitSha,
filter: options?.filter,
const { filepath } = parseGitUrl(url);
const { headers } = await this.deps.credentialsProvider.getCredentials({
url,
});
const files = await this.doSearch(
url,
repoDetails.repo.trees_url,
repoDetails.repo.archive_url,
commitSha,
filepath,
{ headers },
);
return { files, etag: commitSha };
}
toString() {
const { host, token } = this.config;
return `github{host=${host},authed=${Boolean(token)}}`;
}
private async doReadTree(
archiveUrl: string,
sha: string,
subpath: string,
init: RequestInit,
options?: ReadTreeOptions,
): Promise<ReadTreeResponse> {
// archive_url looks like "https://api.github.com/repos/owner/repo/{archive_format}{/ref}"
const archive = await this.fetchResponse(
archiveUrl
.replace('{archive_format}', 'tarball')
.replace('{/ref}', `/${sha}`),
init,
);
return await this.deps.treeResponseFactory.fromTarArchive({
// TODO(Rugvip): Underlying implementation of fetch will be node-fetch, we probably want
// to stick to using that in exclusively backend code.
stream: (archive.body as unknown) as Readable,
subpath,
etag: sha,
filter: options?.filter,
});
}
private async doSearch(
url: string,
treesUrl: string,
archiveUrl: string,
sha: string,
query: string,
init: RequestInit,
): Promise<SearchResponseFile[]> {
function pathToUrl(path: string): string {
// TODO(freben): Use the integration package facility for this instead
// pathname starts as /backstage/backstage/blob/master/<path>
const updated = new URL(url);
const base = updated.pathname.split('/').slice(1, 5).join('/');
updated.pathname = `${base}/${path}`;
return updated.toString();
}
const matcher = new Minimatch(query.replace(/^\/+/, ''));
// trees_url looks like "https://api.github.com/repos/octocat/Hello-World/git/trees{/sha}"
const recursiveTree: GhTreeResponse = await this.fetchJson(
treesUrl.replace('{/sha}', `/${sha}?recursive=true`),
init,
);
// The simple case is that we got the entire tree in a single operation.
if (!recursiveTree.truncated) {
const matching = recursiveTree.tree.filter(
item =>
item.type === 'blob' &&
item.path &&
item.url &&
matcher.match(item.path),
);
return matching.map(item => ({
url: pathToUrl(item.path!),
content: async () => {
const blob: GhBlobResponse = await this.fetchJson(item.url!, init);
return Buffer.from(blob.content, 'base64');
},
}));
}
// For larger repos, we leverage readTree and filter through that instead
const tree = await this.doReadTree(archiveUrl, sha, '', init, {
filter: path => matcher.match(path),
});
const files = await tree.files();
return files.map(file => ({
url: pathToUrl(file.path),
content: file.content,
}));
}
private async getRepoDetails(
url: string,
): Promise<{
repo: GhRepoResponse;
branch: GhBranchResponse;
}> {
const parsed = parseGitUrl(url);
const { ref, full_name } = parsed;
// Caveat: The ref will totally be incorrect if the branch name includes a
// slash. Thus, some operations can not work on URLs containing branch
// names that have a slash in them.
const { headers } = await this.deps.credentialsProvider.getCredentials({
url,
});
const repo: GhRepoResponse = await this.fetchJson(
`${this.config.apiBaseUrl}/repos/${full_name}`,
{ headers },
);
// branches_url looks like "https://api.github.com/repos/owner/repo/branches{/branch}"
const branch: GhBranchResponse = await this.fetchJson(
repo.branches_url.replace('{/branch}', `/${ref || repo.default_branch}`),
{ headers },
);
return { repo, branch };
}
private async fetchResponse(
url: string | URL,
init: RequestInit,
): Promise<Response> {
const urlAsString = url.toString();
const response = await fetch(urlAsString, init);
if (!response.ok) {
const message = `Request failed for ${urlAsString}, ${response.status} ${response.statusText}`;
if (response.status === 404) {
throw new NotFoundError(message);
}
throw new Error(message);
}
return response;
}
private async fetchJson(url: string | URL, init: RequestInit): Promise<any> {
const response = await this.fetchResponse(url, init);
return await response.json();
}
}
@@ -21,16 +21,17 @@ import {
readGitLabIntegrationConfigs,
} from '@backstage/integration';
import fetch from 'cross-fetch';
import parseGitUrl from 'git-url-parse';
import { Readable } from 'stream';
import { NotFoundError, NotModifiedError } from '../errors';
import { ReadTreeResponseFactory } from './tree';
import {
ReaderFactory,
ReadTreeOptions,
ReadTreeResponse,
SearchResponse,
UrlReader,
} from './types';
import parseGitUrl from 'git-url-parse';
import { Readable } from 'stream';
export class GitlabUrlReader implements UrlReader {
private readonly treeResponseFactory: ReadTreeResponseFactory;
@@ -154,6 +155,10 @@ export class GitlabUrlReader implements UrlReader {
});
}
async search(): Promise<SearchResponse> {
throw new Error('GitlabUrlReader does not implement search');
}
toString() {
const { host, token } = this.config;
return `gitlab{host=${host},authed=${Boolean(token)}}`;

Some files were not shown because too many files have changed in this diff Show More