Merge branch 'master' into feat/dynatrace-plugin-synthetics
Signed-off-by: Isaiah Thiessen <isaiah.thiessen@telus.com>
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend-module-gitlab': patch
|
||||
---
|
||||
|
||||
Enhancing GitLab provider with filtering projects by pattern RegExp
|
||||
|
||||
```yaml
|
||||
providers:
|
||||
gitlab:
|
||||
stg:
|
||||
host: gitlab.stg.company.io
|
||||
branch: main
|
||||
projectPattern: 'john/' # new option
|
||||
entityFilename: template.yaml
|
||||
```
|
||||
|
||||
With the aforementioned parameter you can filter projects, and keep only who belongs to the namespace "john".
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/create-app': patch
|
||||
---
|
||||
|
||||
Bumped create-app version.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-sonarqube-backend': minor
|
||||
---
|
||||
|
||||
Initial creation of the plugin
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/core-components': patch
|
||||
---
|
||||
|
||||
Adding a `className` prop to the `MarkdownContent` component
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder': minor
|
||||
'@backstage/plugin-scaffolder-backend': minor
|
||||
---
|
||||
|
||||
Starting the implementation of the Wizard page for the `next` scaffolder plugin
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-kubernetes': patch
|
||||
---
|
||||
|
||||
Adds namespace column to Kubernetes error reporting table
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
'@backstage/plugin-sonarqube': minor
|
||||
---
|
||||
|
||||
**BREAKING** This plugin now call the `sonarqube-backend` plugin instead of relying on the proxy plugin
|
||||
|
||||
The whole proxy's `'/sonarqube':` key can be removed from your configuration files.
|
||||
|
||||
Then head to the [README in sonarqube-backend plugin page](https://github.com/backstage/backstage/tree/master/plugins/sonarqube-backend/README.md) to learn how to set-up the link to your Sonarqube instances.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-common': patch
|
||||
---
|
||||
|
||||
Export aggregated list of all catalog permissions
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend-module-github': patch
|
||||
---
|
||||
|
||||
Github Entity Provider functionality for adding entities to the catalog.
|
||||
|
||||
This provider replaces the GithubDiscoveryProcessor functionality as providers offer more flexibility with scheduling ingestion, removing and preventing orphaned entities.
|
||||
|
||||
More information can be found on the [GitHub Discovery](https://backstage.io/docs/integrations/github/discovery) page.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/plugin-cost-insights': patch
|
||||
'@backstage/plugin-cost-insights-common': patch
|
||||
---
|
||||
|
||||
Add name property to Group
|
||||
+21
-1
@@ -165,34 +165,54 @@
|
||||
"@backstage/plugin-vault": "0.1.1",
|
||||
"@backstage/plugin-vault-backend": "0.2.0",
|
||||
"@backstage/plugin-xcmetrics": "0.2.27",
|
||||
"@internal/plugin-catalog-customized": "0.0.0"
|
||||
"@internal/plugin-catalog-customized": "0.0.0",
|
||||
"@backstage/plugin-sonarqube-backend": "0.0.0"
|
||||
},
|
||||
"changesets": [
|
||||
"big-mirrors-play",
|
||||
"calm-clocks-drum",
|
||||
"create-app-1658824524",
|
||||
"create-app-1659429685",
|
||||
"dull-owls-grab",
|
||||
"dull-pumas-hope",
|
||||
"dull-starfishes-chew",
|
||||
"eighty-radios-look",
|
||||
"empty-apple-pie",
|
||||
"empty-apples-tie",
|
||||
"famous-bikes-brush",
|
||||
"fast-panthers-fold",
|
||||
"few-berries-deny",
|
||||
"forty-lobsters-guess",
|
||||
"fresh-hounds-argue",
|
||||
"friendly-sheep-flash",
|
||||
"itchy-mice-kiss",
|
||||
"khaki-meals-hammer",
|
||||
"little-laws-heal",
|
||||
"loud-panthers-arrive",
|
||||
"lovely-walls-brush",
|
||||
"mean-ants-hang",
|
||||
"metal-points-itch",
|
||||
"mighty-penguins-tap",
|
||||
"modern-shrimps-wave",
|
||||
"nine-mails-crash",
|
||||
"odd-adults-smash",
|
||||
"odd-tomatoes-juggle",
|
||||
"olive-tips-camp",
|
||||
"pretty-gifts-do",
|
||||
"purple-apricots-build",
|
||||
"red-turtles-melt",
|
||||
"renovate-15030f1",
|
||||
"renovate-5b7b62b",
|
||||
"renovate-5ba3a71",
|
||||
"rotten-moles-give",
|
||||
"short-trains-roll",
|
||||
"silver-poets-push",
|
||||
"strange-crabs-confess",
|
||||
"strange-moles-design",
|
||||
"techdocs-eagles-stare",
|
||||
"ten-roses-walk",
|
||||
"thick-readers-invite",
|
||||
"twenty-humans-visit",
|
||||
"violet-mayflies-mix",
|
||||
"violet-trees-play"
|
||||
]
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/integration': minor
|
||||
'@backstage/plugin-techdocs-node': minor
|
||||
'@backstage/plugin-techdocs-module-addons-contrib': patch
|
||||
---
|
||||
|
||||
feat(techdocs): add edit button support for bitbucketServer
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-techdocs': patch
|
||||
---
|
||||
|
||||
Scroll techdocs navigation into focus and expand any nested navigation items.
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/integration': patch
|
||||
---
|
||||
|
||||
Fixed bug in getGitLabFileFetchUrl where a target whose path did not contain the
|
||||
`/-/` scope would result in a fetch URL that did not support
|
||||
private-token-based authentication.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-github-issues': minor
|
||||
---
|
||||
|
||||
New plugin for displaying GitHub Issues added
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend': patch
|
||||
---
|
||||
|
||||
Fix issue for conditional decisions based on properties stored as arrays, like tags.
|
||||
|
||||
Before this change, having a permission policy returning conditional decisions based on metadata like tags, such like `createCatalogConditionalDecision(permission, catalogConditions.hasMetadata('tags', 'java'),)`, was producing wrong results. The issue occurred when authorizing entities already loaded from the database, for example when authorizing `catalogEntityDeletePermission`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-techdocs-node': patch
|
||||
---
|
||||
|
||||
Bump default `TechDocs` image to `v1.1.0`, see the release [here](https://github.com/backstage/techdocs-container/releases/tag/v1.1.0).
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-sonarqube': patch
|
||||
---
|
||||
|
||||
Add ability to provide an optional Sonarqube instance into the annotation in the `catalog-info.yaml` file
|
||||
@@ -283,6 +283,7 @@ shoutout
|
||||
siloed
|
||||
Sinon
|
||||
Snyk
|
||||
Sonarqube
|
||||
sourcemaps
|
||||
sparklines
|
||||
Splunk
|
||||
|
||||
+11
-6
@@ -168,7 +168,7 @@ _You can do this by using the [Adopter form](https://form.typeform.com/to/zcOaKi
|
||||
| [Forto](https://forto.com) | [Rodolfo Matos](mailto:rodolfo.matos@forto.com) | Still in a experimental phase/assessing the organisational fit. We will be using it mostly a developer portal -- pretty standard use case. |
|
||||
| [BetterUp](https://betterup.com) | [Jordan Hochenbaum](mailto:jordan.hochenbaum@betterup.co) | We're starting to use Backstage as the central hub for service discovery, documentation, and develop experience. |
|
||||
| [warung pintar](https://warungpintar.co.id/) | [Muhammad Rafly Andrianza](mailto:rafly.andrianza@warungpintar.co) | Initial Work Developer Portal with TechRadar, Service Catalogue, TechDocs, anything about platform & infrastructure resources. |
|
||||
| [RaiaDrogasil](https://rd.com.br/) | [Michael Lima](mailto:midsilva@rd.com.br) | We are building our developer portal. Software catalog, Tech Radar and Scaffolding are among the initial features. |
|
||||
| [RaiaDrogasil](https://rd.com.br/) | [Michael Lima](mailto:midsilva@rd.com.br) | We are building our developer portal. Software catalog, Tech Radar and Scaffolding are among the initial features. |
|
||||
| [AEB](https://www.aeb.com/) | [David Fankhänel](mailto:dfl@aeb.com) | Central developer platform for creating new apps via templates, getting an overview via software catalog, etc |
|
||||
| [SALTO Systems](https://saltosystems.com) | [Ian Cowley](mailto:i.cowley@saltosystems.com) | Currently using Backstage as an internal documentation portal. |
|
||||
| [Lummo](https://lummo.com) | [Anjul Sahu](mailto:anjul@lummo.com) | We are building the internal developer portal using Backstage and bringing up all integrations and service information at one place. |
|
||||
@@ -196,9 +196,14 @@ _You can do this by using the [Adopter form](https://form.typeform.com/to/zcOaKi
|
||||
| [Adaptavist](https://adaptavist.com) | [Lilly Holden](mailto:lholden@adaptavist.com) | Developer portal, service catalog, documentation and self-service platform |
|
||||
| [Shipshape](https://shipshape.io) | [Chuck Carpenter](mailto:chuck@shipshape.io) | We are a consultancy helping teams implement and customize Backstage. |
|
||||
| [Backbase](https://backbase.com) | [Nicolas Torres](mailto:ntorres.dev@gmail.com) | To catalog components and teams |
|
||||
| [https://www.clear.sale](https://www.clear.sale) | [Paulo Baima](mailto:paulo.filho@clear.sale) | Central Hub for all the company modules, enabling the track of ownership of components and resources and how they relate to each other. |
|
||||
| [www.leroymerlin.com.br](https://www.leroymerlin.com.br) | [Rodrigo Franzoni](mailto:rfranzoni@leroymerlin.com.br) | Our engineers use the Backstage to solve problems around ownership and visibility of our applications, access service catalog, documentation, observability and infrastructure. |
|
||||
| [clearSale](https://www.clear.sale) | [Paulo Baima](mailto:paulo.filho@clear.sale) | Central Hub for all the company modules, enabling the track of ownership of components and resources and how they relate to each other. |
|
||||
| [LeroyMerlin](https://www.leroymerlin.com.br) | [Rodrigo Franzoni](mailto:rfranzoni@leroymerlin.com.br) | Our engineers use the Backstage to solve problems around ownership and visibility of our applications, access service catalog, documentation, observability and infrastructure. |
|
||||
| [Intility](https://intility.no/en/) | [@daniwk](https://github.com/daniwk) | We are creating a developer portal powered by Backstage, with software catalog, documentation, templates and integrations to our infrastructure and internal tools. |
|
||||
| [ImmobiliareLabs](https://labs.immobiliare.it/) | [@JellyBellyDev](https://github.com/JellyBellyDev) | Centralized portal with our internal services, infrastructures, relationships between systems, technical documentation, templates, monitoring and custom integrations with our own DX tools. |
|
||||
| [Skillz](https://skillz.com/) | [Peiman Jafari](https://github.com/peimanja) | Internal developers portal for technical documentations, components ownership and relationship, software templates and integrations with internal tools |
|
||||
| [Telus](https://www.telus.com/en/) | [Leo Li](mailto:leo.li@telus.com) and [Laurent Robichaud](mailto:laurent.robichaud@telus.com) | Simplifying the developer experience through centralized team member portals. Our current focus includes the adoption of Tech Docs, Software Catalog, Software Templates, the plethora of plugins, and contributing features back to Backstage. 🤖 |
|
||||
| [ImmobiliareLabs](https://labs.immobiliare.it/) | [@JellyBellyDev](https://github.com/JellyBellyDev) | Centralized portal with our internal services, infrastructures, relationships between systems, technical documentation, templates, monitoring and custom integrations with our own DX tools. |
|
||||
| [Skillz](https://skillz.com/) | [Peiman Jafari](https://github.com/peimanja) | Internal developers portal for technical documentations, components ownership and relationship, software templates and integrations with internal tools |
|
||||
| [Telus](https://www.telus.com/en/) | [Leo Li](mailto:leo.li@telus.com), [Laurent Robichaud](mailto:laurent.robichaud@telus.com) | Simplifying the developer experience through centralized team member portals. Our current focus includes the adoption of Tech Docs, Software Catalog, Software Templates, the plethora of plugins, and contributing features back to Backstage. 🤖 |
|
||||
| [Fidelity Investments](https://fidelity.com) | [Ankita Upadhyay](mailto:ankita.upadhyay@fmr.com) | Getting started with the adoption for Monorepo projects |
|
||||
| [Verisk](https://verisk.com) | [Callen Barton](mailto:cbarton@verisk.com) | Developer portal to quickly create and deploy microservices. |
|
||||
| [iodigital](https://iodigital.com) | [Jan-Willem Mulder](mailto:jan-willem.mulder@iodigital.com) | Internal developer portal for discovery of applications, projects and teams. Using several plugins like the Software Catalog and Tech Insights for promoting best practices and supporting our SDLC toolchain |
|
||||
| [Fanatics](https://www.fanaticsinc.com/) | [Rory Scott](mailto:rscott@fanatics.com) | Internal Portal consolidating documentation, making it easier to manage applications, internal developer community platform, and self-service cloud infrastructure + pipelines. |
|
||||
| [Appfolio](https://appfolio.com) | [Andy Vaughn](mailto:andy.vaughn@appfolio.com) | Internal software catalog, tech radar, documentation portal to disambiguate software and domain ownership, foster exploration of available developer platform services and tools, improve communication, democratize documentation and knowledge sharing, and coordinate the software lifecycle; all in service of a best-in-class developer experience. |
|
||||
|
||||
@@ -84,19 +84,6 @@ Now the backend is ready to serve auth requests on the
|
||||
frontend sign-in mechanism to poll that endpoint through Cloudflare Access, on
|
||||
the user's behalf.
|
||||
|
||||
## Frontend Changes
|
||||
|
||||
It is recommended to use the `ProxiedSignInPage` for this provider, which is
|
||||
installed in `packages/app/src/App.tsx` like this:
|
||||
|
||||
```diff
|
||||
+import { ProxiedSignInPage } from '@backstage/core-components';
|
||||
|
||||
const app = createApp({
|
||||
components: {
|
||||
+ SignInPage: props => <ProxiedSignInPage {...props} provider="cfaccess" />,
|
||||
```
|
||||
|
||||
## Adding the provider to the Backstage frontend
|
||||
|
||||
It is recommended to use the `ProxiedSignInPage` for this provider, which is
|
||||
|
||||
@@ -236,10 +236,12 @@ kubernetes:
|
||||
|
||||
##### `clusters.\*.caData` (optional)
|
||||
|
||||
PEM-encoded certificate authority certificates.
|
||||
Base64-encoded certificate authority bundle in PEM format. The Kubernetes client
|
||||
will verify that TLS certificate presented by the API server is signed by this
|
||||
CA.
|
||||
|
||||
This values could be obtained via inspecting the Kubernetes config file (usually
|
||||
at `~/.kube/config`) under `clusters.cluster.certificate-authority-data`. For
|
||||
This value could be obtained via inspecting the kubeconfig file (usually
|
||||
at `~/.kube/config`) under `clusters[*].cluster.certificate-authority-data`. For
|
||||
GKE, execute the following command to obtain the value
|
||||
|
||||
```
|
||||
|
||||
@@ -377,6 +377,10 @@ import {
|
||||
export class SystemXReaderProcessor implements CatalogProcessor {
|
||||
constructor(private readonly reader: UrlReader) {}
|
||||
|
||||
getProcessorName(): string {
|
||||
return 'SystemXReaderProcessor';
|
||||
}
|
||||
|
||||
async readLocation(
|
||||
location: LocationSpec,
|
||||
_optional: boolean,
|
||||
|
||||
@@ -278,10 +278,7 @@ Note: We recommend Python version 3.7 or higher.
|
||||
|
||||
> Caveat: Please install the `mkdocs-techdocs-core` package after all other
|
||||
> Python packages. The order is important to make sure we get correct version of
|
||||
> some of the dependencies. For example, we want `Markdown` version to be
|
||||
> [3.2.2](https://github.com/backstage/backstage/blob/f9f70c225548017b6a14daea75b00fbd399c11eb/packages/techdocs-container/techdocs-core/requirements.txt#L11).
|
||||
> You can also explicitly install `Markdown==3.2.2` after installing all other
|
||||
> Python packages.
|
||||
> some of the dependencies.
|
||||
|
||||
## Running Backstage locally
|
||||
|
||||
|
||||
@@ -6,6 +6,119 @@ sidebar_label: Discovery
|
||||
description: Automatically discovering catalog entities from repositories in a GitHub organization
|
||||
---
|
||||
|
||||
## GitHub Provider
|
||||
|
||||
The GitHub integration has a discovery provider for discovering catalog
|
||||
entities within a GitHub organization. The provider will crawl the GitHub
|
||||
organization and register entities matching the configured path. This can be
|
||||
useful as an alternative to static locations or manually adding things to the
|
||||
catalog. This is the prefered method for ingesting entities into the catalog.
|
||||
|
||||
## Installation
|
||||
|
||||
You will have to add the provider in the catalog initialization code of your
|
||||
backend. They are not installed by default, therefore you have to add a
|
||||
dependency on `@backstage/plugin-catalog-backend-module-github` to your backend
|
||||
package.
|
||||
|
||||
```bash
|
||||
# From your Backstage root directory
|
||||
yarn add --cwd packages/backend @backstage/plugin-catalog-backend-module-github
|
||||
```
|
||||
|
||||
And then add the entity provider to your catalog builder:
|
||||
|
||||
```diff
|
||||
// In packages/backend/src/plugins/catalog.ts
|
||||
+ import { GitHubEntityProvider } from '@backstage/plugin-catalog-backend-module-github';
|
||||
|
||||
export default async function createPlugin(
|
||||
env: PluginEnvironment,
|
||||
): Promise<Router> {
|
||||
const builder = await CatalogBuilder.create(env);
|
||||
+ builder.addEntityProvider(
|
||||
+ GitHubEntityProvider.fromConfig(env.config, {
|
||||
+ logger: env.logger,
|
||||
+ schedule: env.scheduler.createScheduledTaskRunner({
|
||||
+ frequency: { minutes: 30 },
|
||||
+ timeout: { minutes: 3 },
|
||||
+ }),
|
||||
+ }),
|
||||
+ );
|
||||
|
||||
// [...]
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
To use the discovery provider, you'll need a GitHub integration
|
||||
[set up](locations.md) with either a [Personal Access Token](../../getting-started/configuration.md#setting-up-a-github-integration) or [GitHub Apps](./github-apps.md).
|
||||
|
||||
Then you can add a github config to the catalog providers configuration:
|
||||
|
||||
```yaml
|
||||
catalog:
|
||||
providers:
|
||||
github:
|
||||
# the provider ID can be any camelCase string
|
||||
providerId:
|
||||
organization: 'backstage' # string
|
||||
catalogPath: '/catalog-info.yaml' # string
|
||||
filters:
|
||||
branch: 'main' # string
|
||||
repository: '.*' # Regex
|
||||
customProviderId:
|
||||
organization: 'new-org' # string
|
||||
catalogPath: '/custom/path/catalog-info.yaml' # string
|
||||
filters: # optional filters
|
||||
branch: 'develop' # optional string
|
||||
repository: '.*' # optional Regex
|
||||
```
|
||||
|
||||
This provider supports multiple organizations via unique provider IDs.
|
||||
|
||||
> **Note:** It is possible but certainly not recommended to skip the provider ID level.
|
||||
> If you do so, `default` will be used as provider ID.
|
||||
|
||||
- **`catalogPath`** _(optional)_:
|
||||
Default: `/catalog-info.yaml`.
|
||||
Path where to look for `catalog-info.yaml` files.
|
||||
When started with `/`, it is an absolute path from the repo root.
|
||||
- **filters** _(optional)_:
|
||||
- **branch** _(optional)_:
|
||||
String used to filter results based on the branch name.
|
||||
- **repository** _(optional)_:
|
||||
Regular expression used to filter results based on the repository name.
|
||||
- **organization**:
|
||||
Name of your organization account/workspace.
|
||||
If you want to add multiple organizations, you need to add one provider config each.
|
||||
|
||||
## GitHub API Rate Limits
|
||||
|
||||
GitHub [rate limits](https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting) API requests to 5,000 per hour (or more for Enterprise
|
||||
accounts). The snippet below refreshes the Backstage catalog data every 35 minutes, which issues an API request for each discovered location.
|
||||
|
||||
If your requests are too frequent then you may get throttled by
|
||||
rate limiting. You can change the refresh frequency of the catalog in your `packages/backend/src/plugins/catalog.ts` file:
|
||||
|
||||
```typescript
|
||||
schedule: env.scheduler.createScheduledTaskRunner({
|
||||
frequency: { minutes: 35 },
|
||||
timeout: { minutes: 30 },
|
||||
}),
|
||||
```
|
||||
|
||||
More information about scheduling can be found on the [TaskScheduleDefinition](https://backstage.io/docs/reference/backend-tasks.taskscheduledefinition) page.
|
||||
|
||||
Alternatively, or additionally, you can configure [github-apps](github-apps.md) authentication
|
||||
which carries a much higher rate limit at GitHub.
|
||||
|
||||
This is true for any method of adding GitHub entities to the catalog, but
|
||||
especially easy to hit with automatic discovery.
|
||||
|
||||
## GitHub Processor (To Be Deprecated)
|
||||
|
||||
The GitHub integration has a special discovery processor for discovering catalog
|
||||
entities within a GitHub organization. The processor will crawl the GitHub
|
||||
organization and register entities matching the configured path. This can be
|
||||
|
||||
@@ -24,6 +24,7 @@ catalog:
|
||||
branch: main # Optional. Uses `master` as default
|
||||
group: example-group # Optional. Group and subgroup (if needed) to look for repositories. If not present the whole project will be scanned
|
||||
entityFilename: catalog-info.yaml # Optional. Defaults to `catalog-info.yaml`
|
||||
projectPattern: /[\s\S]*/ # Optional. Filters found projects based on provided patter. Defaults to `/[\s\S]*/`, what means to not filter anything
|
||||
```
|
||||
|
||||
As this provider is not one of the default providers, you will first need to install
|
||||
|
||||
@@ -0,0 +1,274 @@
|
||||
# Release v1.5.0-next.1
|
||||
|
||||
## @backstage/integration@1.3.0-next.1
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- ad35364e97: feat(techdocs): add edit button support for bitbucketServer
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 1f27d83933: Fixed bug in getGitLabFileFetchUrl where a target whose path did not contain the
|
||||
`/-/` scope would result in a fetch URL that did not support
|
||||
private-token-based authentication.
|
||||
|
||||
## @backstage/plugin-catalog@1.5.0-next.1
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- fe94398418: Allow changing the subtitle of the `CatalogTable` component
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/core-components@0.10.1-next.1
|
||||
- @backstage/plugin-catalog-common@1.0.5-next.0
|
||||
- @backstage/plugin-catalog-react@1.1.3-next.1
|
||||
|
||||
## @backstage/plugin-scaffolder@1.5.0-next.1
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- c4b452e16a: Starting the implementation of the Wizard page for the `next` scaffolder plugin
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/core-components@0.10.1-next.1
|
||||
- @backstage/plugin-catalog-common@1.0.5-next.0
|
||||
- @backstage/integration@1.3.0-next.1
|
||||
- @backstage/plugin-catalog-react@1.1.3-next.1
|
||||
|
||||
## @backstage/plugin-scaffolder-backend@1.5.0-next.1
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- c4b452e16a: Starting the implementation of the Wizard page for the `next` scaffolder plugin
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/backend-common@0.15.0-next.1
|
||||
- @backstage/integration@1.3.0-next.1
|
||||
- @backstage/plugin-catalog-backend@1.3.1-next.1
|
||||
|
||||
## @backstage/plugin-sonarqube@0.4.0-next.1
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 619b515172: **BREAKING** This plugin now call the `sonarqube-backend` plugin instead of relying on the proxy plugin
|
||||
|
||||
The whole proxy's `'/sonarqube':` key can be removed from your configuration files.
|
||||
|
||||
Then head to the [README in sonarqube-backend plugin page](https://github.com/backstage/backstage/tree/master/plugins/sonarqube-backend/README.md) to learn how to set-up the link to your Sonarqube instances.
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- f9c310a439: Add ability to provide an optional Sonarqube instance into the annotation in the `catalog-info.yaml` file
|
||||
- Updated dependencies
|
||||
- @backstage/core-components@0.10.1-next.1
|
||||
- @backstage/plugin-catalog-react@1.1.3-next.1
|
||||
|
||||
## @backstage/plugin-sonarqube-backend@0.1.0-next.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- e2be9ab3a4: Initial creation of the plugin
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/backend-common@0.15.0-next.1
|
||||
|
||||
## @backstage/plugin-techdocs-node@1.3.0-next.1
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- ad35364e97: feat(techdocs): add edit button support for bitbucketServer
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- f833344611: Bump default `TechDocs` image to `v1.1.0`, see the release [here](https://github.com/backstage/techdocs-container/releases/tag/v1.1.0).
|
||||
- Updated dependencies
|
||||
- @backstage/backend-common@0.15.0-next.1
|
||||
- @backstage/integration@1.3.0-next.1
|
||||
|
||||
## @backstage/backend-common@0.15.0-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 1732a18a7a: Exported `redactLogLine` function to be able to use it in custom loggers and renamed it to `redactWinstonLogLine`.
|
||||
- Updated dependencies
|
||||
- @backstage/integration@1.3.0-next.1
|
||||
|
||||
## @backstage/core-components@0.10.1-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- a22af3edc8: Adding a `className` prop to the `MarkdownContent` component
|
||||
|
||||
## @backstage/create-app@0.4.30-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Bumped create-app version.
|
||||
|
||||
## @techdocs/cli@1.1.4-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/backend-common@0.15.0-next.1
|
||||
- @backstage/plugin-techdocs-node@1.3.0-next.1
|
||||
|
||||
## @backstage/plugin-api-docs@0.8.8-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- dae12c71cf: Updated dependency `@asyncapi/react-component` to `1.0.0-next.40`.
|
||||
- Updated dependencies
|
||||
- @backstage/core-components@0.10.1-next.1
|
||||
- @backstage/plugin-catalog@1.5.0-next.1
|
||||
- @backstage/plugin-catalog-react@1.1.3-next.1
|
||||
|
||||
## @backstage/plugin-catalog-backend@1.3.1-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- e3d3018531: Fix issue for conditional decisions based on properties stored as arrays, like tags.
|
||||
|
||||
Before this change, having a permission policy returning conditional decisions based on metadata like tags, such like `createCatalogConditionalDecision(permission, catalogConditions.hasMetadata('tags', 'java'),)`, was producing wrong results. The issue occurred when authorizing entities already loaded from the database, for example when authorizing `catalogEntityDeletePermission`.
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/plugin-catalog-common@1.0.5-next.0
|
||||
- @backstage/backend-common@0.15.0-next.1
|
||||
- @backstage/integration@1.3.0-next.1
|
||||
|
||||
## @backstage/plugin-catalog-backend-module-github@0.1.6-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- f48950e34b: Github Entity Provider functionality for adding entities to the catalog.
|
||||
|
||||
This provider replaces the GithubDiscoveryProcessor functionality as providers offer more flexibility with scheduling ingestion, removing and preventing orphaned entities.
|
||||
|
||||
More information can be found on the [GitHub Discovery](https://backstage.io/docs/integrations/github/discovery) page.
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/backend-common@0.15.0-next.1
|
||||
- @backstage/integration@1.3.0-next.1
|
||||
- @backstage/plugin-catalog-backend@1.3.1-next.1
|
||||
|
||||
## @backstage/plugin-catalog-common@1.0.5-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 92103db537: Export aggregated list of all catalog permissions
|
||||
|
||||
## @backstage/plugin-catalog-react@1.1.3-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/core-components@0.10.1-next.1
|
||||
- @backstage/plugin-catalog-common@1.0.5-next.0
|
||||
- @backstage/integration@1.3.0-next.1
|
||||
|
||||
## @backstage/plugin-home@0.4.24-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- df7b9158b8: Add wrap-around for the listing of tools to prevent increasing width with name length.
|
||||
- Updated dependencies
|
||||
- @backstage/core-components@0.10.1-next.1
|
||||
- @backstage/plugin-catalog-react@1.1.3-next.1
|
||||
|
||||
## @backstage/plugin-jenkins@0.7.7-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/core-components@0.10.1-next.1
|
||||
- @backstage/plugin-catalog-react@1.1.3-next.1
|
||||
- @backstage/plugin-jenkins-common@0.1.7-next.0
|
||||
|
||||
## @backstage/plugin-jenkins-backend@0.1.25-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/backend-common@0.15.0-next.1
|
||||
- @backstage/plugin-jenkins-common@0.1.7-next.0
|
||||
|
||||
## @backstage/plugin-jenkins-common@0.1.7-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/plugin-catalog-common@1.0.5-next.0
|
||||
|
||||
## @backstage/plugin-kubernetes@0.7.1-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 860ed68343: Fixed bug in CronJobsAccordions component that causes an error when cronjobs use a kubernetes alias, such as `@hourly` or `@daily` instead of standard cron syntax.
|
||||
- Updated dependencies
|
||||
- @backstage/core-components@0.10.1-next.1
|
||||
- @backstage/plugin-catalog-react@1.1.3-next.1
|
||||
|
||||
## @backstage/plugin-techdocs@1.3.1-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- b86ed4d990: Add highlight to active navigation item and navigation parents.
|
||||
- Updated dependencies
|
||||
- @backstage/core-components@0.10.1-next.1
|
||||
- @backstage/integration@1.3.0-next.1
|
||||
- @backstage/plugin-techdocs-react@1.0.3-next.1
|
||||
- @backstage/plugin-catalog-react@1.1.3-next.1
|
||||
|
||||
## @backstage/plugin-techdocs-backend@1.2.1-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/plugin-catalog-common@1.0.5-next.0
|
||||
- @backstage/backend-common@0.15.0-next.1
|
||||
- @backstage/integration@1.3.0-next.1
|
||||
- @backstage/plugin-techdocs-node@1.3.0-next.1
|
||||
|
||||
## @backstage/plugin-techdocs-module-addons-contrib@1.0.3-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- ad35364e97: feat(techdocs): add edit button support for bitbucketServer
|
||||
- Updated dependencies
|
||||
- @backstage/core-components@0.10.1-next.1
|
||||
- @backstage/integration@1.3.0-next.1
|
||||
- @backstage/plugin-techdocs-react@1.0.3-next.1
|
||||
|
||||
## @backstage/plugin-techdocs-react@1.0.3-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 29d6cf0147: Add `toLowerEntityRefMaybe()` helper function for handling `techdocs.legacyUseCaseSensitiveTripletPaths` flag.
|
||||
Pass modified `entityRef` to `TechDocsReaderPageContext` to handle the `techdocs.legacyUseCaseSensitiveTripletPaths` flag.
|
||||
- Updated dependencies
|
||||
- @backstage/core-components@0.10.1-next.1
|
||||
|
||||
## example-app@0.2.74-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/plugin-kubernetes@0.7.1-next.1
|
||||
- @backstage/plugin-home@0.4.24-next.1
|
||||
- @backstage/core-components@0.10.1-next.1
|
||||
- @backstage/plugin-scaffolder@1.5.0-next.1
|
||||
- @backstage/plugin-techdocs@1.3.1-next.1
|
||||
- @backstage/plugin-catalog-common@1.0.5-next.0
|
||||
- @backstage/plugin-techdocs-module-addons-contrib@1.0.3-next.1
|
||||
- @backstage/plugin-api-docs@0.8.8-next.1
|
||||
- @backstage/plugin-techdocs-react@1.0.3-next.1
|
||||
- @backstage/plugin-catalog-react@1.1.3-next.1
|
||||
- @backstage/plugin-jenkins@0.7.7-next.1
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: Adopters Community Sessions
|
||||
date: July 20, 2022
|
||||
category: Upcoming
|
||||
category: Meetup
|
||||
description: Adopters Community Session ✨. It's the monthly meetup where we all come together to listen to the latest maintainer updates, learn from each other about adopting, share exciting new demos or discuss any relevant topic like developer effectiveness, developer experience, developer portals, etc.
|
||||
youtubeUrl: https://youtu.be/4VFNlPxWcx8
|
||||
youtubeImgUrl: https://backstage.io/img/b-sessions.png
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
---
|
||||
title: Contributor Community Sessions
|
||||
date: July 27, 2022
|
||||
category: Upcoming
|
||||
category: Meetup
|
||||
description: Join the maintainers and contributors for the Contributor Community Sessions
|
||||
youtubeUrl: https://youtu.be/4VFNlPxWcx8
|
||||
youtubeUrl: https://youtu.be/pNLLrNN_hkE
|
||||
youtubeImgUrl: https://backstage.io/img/b-sessions.png
|
||||
rsvpUrl: https://calendar.google.com/calendar/embed?src=c_qup9gbhn9sqpuao6trttd8mk5s@group.calendar.google.com
|
||||
eventUrl: https://github.com/backstage/community/issues/52
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
title: Adopters Community Sessions
|
||||
date: August 17, 2022
|
||||
category: Upcoming
|
||||
description: Adopters Community Session ✨. It's the monthly meetup where we all come together to listen to the latest maintainer updates, learn from each other about adopting, share exciting new demos or discuss any relevant topic like developer effectiveness, developer experience, developer portals, etc.
|
||||
youtubeUrl: https://youtu.be/qYnvc8ge1kg
|
||||
youtubeImgUrl: https://backstage.io/img/b-sessions.png
|
||||
rsvpUrl: https://calendar.google.com/calendar/embed?src=c_qup9gbhn9sqpuao6trttd8mk5s@group.calendar.google.com
|
||||
eventUrl: https://github.com/backstage/community/issues/54
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
title: Contributor Community Sessions
|
||||
date: August 24, 2022
|
||||
category: Upcoming
|
||||
description: Join the maintainers and contributors for the Contributor Community Sessions
|
||||
youtubeUrl: https://youtu.be/qYnvc8ge1kg
|
||||
youtubeImgUrl: https://backstage.io/img/b-sessions.png
|
||||
rsvpUrl: https://calendar.google.com/calendar/embed?src=c_qup9gbhn9sqpuao6trttd8mk5s@group.calendar.google.com
|
||||
eventUrl: https://github.com/backstage/community/issues/54
|
||||
@@ -34,7 +34,7 @@ const Background = props => {
|
||||
<Block.Container style={{ justifyContent: 'flex-start' }}>
|
||||
<Block.MediaFrame>
|
||||
<iframe
|
||||
src="https://www.youtube.com/embed/4VFNlPxWcx8"
|
||||
src="https://www.youtube.com/embed/qYnvc8ge1kg"
|
||||
title="YouTube video player"
|
||||
frameborder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
@@ -45,7 +45,7 @@ const Background = props => {
|
||||
<iframe
|
||||
width="300"
|
||||
height="500"
|
||||
src="https://www.youtube.com/live_chat?v=4VFNlPxWcx8&embed_domain=backstage.io&dark_theme=1"
|
||||
src="https://www.youtube.com/live_chat?v=qYnvc8ge1kg&embed_domain=backstage.io&dark_theme=1"
|
||||
></iframe>
|
||||
</Block.MediaFrame>
|
||||
</Block.Container>
|
||||
|
||||
+1
-1
@@ -49,7 +49,7 @@
|
||||
"@types/react": "^17",
|
||||
"@types/react-dom": "^17"
|
||||
},
|
||||
"version": "1.5.0-next.0",
|
||||
"version": "1.5.0-next.1",
|
||||
"dependencies": {
|
||||
"@manypkg/get-packages": "^1.1.3",
|
||||
"@microsoft/api-documenter": "^7.17.11",
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
# example-app
|
||||
|
||||
## 0.2.74-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/plugin-kubernetes@0.7.1-next.1
|
||||
- @backstage/plugin-home@0.4.24-next.1
|
||||
- @backstage/core-components@0.10.1-next.1
|
||||
- @backstage/plugin-scaffolder@1.5.0-next.1
|
||||
- @backstage/plugin-techdocs@1.3.1-next.1
|
||||
- @backstage/plugin-catalog-common@1.0.5-next.0
|
||||
- @backstage/plugin-techdocs-module-addons-contrib@1.0.3-next.1
|
||||
- @backstage/plugin-api-docs@0.8.8-next.1
|
||||
- @backstage/plugin-techdocs-react@1.0.3-next.1
|
||||
- @backstage/plugin-catalog-react@1.1.3-next.1
|
||||
- @backstage/plugin-jenkins@0.7.7-next.1
|
||||
|
||||
## 0.2.74-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
+12
-12
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "example-app",
|
||||
"version": "0.2.74-next.0",
|
||||
"version": "0.2.74-next.1",
|
||||
"private": true,
|
||||
"backstage": {
|
||||
"role": "frontend"
|
||||
@@ -12,18 +12,18 @@
|
||||
"@backstage/cli": "^0.18.1-next.0",
|
||||
"@backstage/config": "^1.0.1",
|
||||
"@backstage/core-app-api": "^1.0.5-next.0",
|
||||
"@backstage/core-components": "^0.10.1-next.0",
|
||||
"@backstage/core-components": "^0.10.1-next.1",
|
||||
"@backstage/core-plugin-api": "^1.0.5-next.0",
|
||||
"@backstage/integration-react": "^1.1.3-next.0",
|
||||
"@backstage/plugin-airbrake": "^0.3.8-next.0",
|
||||
"@backstage/plugin-api-docs": "^0.8.8-next.0",
|
||||
"@backstage/plugin-api-docs": "^0.8.8-next.1",
|
||||
"@backstage/plugin-azure-devops": "^0.1.24-next.0",
|
||||
"@backstage/plugin-apache-airflow": "^0.2.1-next.0",
|
||||
"@backstage/plugin-badges": "^0.2.32-next.0",
|
||||
"@backstage/plugin-catalog-common": "^1.0.4",
|
||||
"@backstage/plugin-catalog-common": "^1.0.5-next.0",
|
||||
"@backstage/plugin-catalog-graph": "^0.2.20-next.0",
|
||||
"@backstage/plugin-catalog-import": "^0.8.11-next.0",
|
||||
"@backstage/plugin-catalog-react": "^1.1.3-next.0",
|
||||
"@backstage/plugin-catalog-react": "^1.1.3-next.1",
|
||||
"@backstage/plugin-circleci": "^0.3.8-next.0",
|
||||
"@backstage/plugin-cloudbuild": "^0.3.8-next.0",
|
||||
"@backstage/plugin-code-coverage": "^0.2.1-next.0",
|
||||
@@ -35,10 +35,10 @@
|
||||
"@backstage/plugin-github-actions": "^0.5.8-next.0",
|
||||
"@backstage/plugin-gocd": "^0.1.14-next.0",
|
||||
"@backstage/plugin-graphiql": "^0.2.40-next.0",
|
||||
"@backstage/plugin-home": "^0.4.24-next.0",
|
||||
"@backstage/plugin-jenkins": "^0.7.7-next.0",
|
||||
"@backstage/plugin-home": "^0.4.24-next.1",
|
||||
"@backstage/plugin-jenkins": "^0.7.7-next.1",
|
||||
"@backstage/plugin-kafka": "^0.3.8-next.0",
|
||||
"@backstage/plugin-kubernetes": "^0.7.1-next.0",
|
||||
"@backstage/plugin-kubernetes": "^0.7.1-next.1",
|
||||
"@backstage/plugin-lighthouse": "^0.3.8-next.0",
|
||||
"@backstage/plugin-newrelic": "^0.3.26-next.0",
|
||||
"@backstage/plugin-newrelic-dashboard": "^0.2.1-next.0",
|
||||
@@ -46,7 +46,7 @@
|
||||
"@backstage/plugin-pagerduty": "0.5.1-next.0",
|
||||
"@backstage/plugin-permission-react": "^0.4.4-next.0",
|
||||
"@backstage/plugin-rollbar": "^0.4.8-next.0",
|
||||
"@backstage/plugin-scaffolder": "^1.4.1-next.0",
|
||||
"@backstage/plugin-scaffolder": "^1.5.0-next.1",
|
||||
"@backstage/plugin-search": "^1.0.1-next.0",
|
||||
"@backstage/plugin-search-common": "^1.0.0",
|
||||
"@backstage/plugin-search-react": "^1.0.1-next.0",
|
||||
@@ -55,9 +55,9 @@
|
||||
"@backstage/plugin-stack-overflow": "^0.1.4-next.0",
|
||||
"@backstage/plugin-tech-insights": "^0.2.4-next.0",
|
||||
"@backstage/plugin-tech-radar": "^0.5.15-next.0",
|
||||
"@backstage/plugin-techdocs": "^1.3.1-next.0",
|
||||
"@backstage/plugin-techdocs-module-addons-contrib": "^1.0.3-next.0",
|
||||
"@backstage/plugin-techdocs-react": "^1.0.3-next.0",
|
||||
"@backstage/plugin-techdocs": "^1.3.1-next.1",
|
||||
"@backstage/plugin-techdocs-module-addons-contrib": "^1.0.3-next.1",
|
||||
"@backstage/plugin-techdocs-react": "^1.0.3-next.1",
|
||||
"@backstage/plugin-todo": "^0.2.10-next.0",
|
||||
"@backstage/plugin-user-settings": "^0.4.7-next.0",
|
||||
"@backstage/theme": "^0.2.16",
|
||||
|
||||
@@ -62,6 +62,7 @@ import { NewRelicPage } from '@backstage/plugin-newrelic';
|
||||
import {
|
||||
ScaffolderFieldExtensions,
|
||||
ScaffolderPage,
|
||||
NextScaffolderPage,
|
||||
scaffolderPlugin,
|
||||
} from '@backstage/plugin-scaffolder';
|
||||
import { SearchPage } from '@backstage/plugin-search';
|
||||
@@ -212,6 +213,24 @@ const routes = (
|
||||
<LowerCaseValuePickerFieldExtension />
|
||||
</ScaffolderFieldExtensions>
|
||||
</Route>
|
||||
<Route
|
||||
path="/create/next"
|
||||
element={
|
||||
<NextScaffolderPage
|
||||
groups={[
|
||||
{
|
||||
title: 'Recommended',
|
||||
filter: entity =>
|
||||
entity?.metadata?.tags?.includes('recommended') ?? false,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<ScaffolderFieldExtensions>
|
||||
<LowerCaseValuePickerFieldExtension />
|
||||
</ScaffolderFieldExtensions>
|
||||
</Route>
|
||||
<Route path="/explore" element={<ExplorePage />} />
|
||||
<Route
|
||||
path="/tech-radar"
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @backstage/backend-common
|
||||
|
||||
## 0.15.0-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 1732a18a7a: Exported `redactLogLine` function to be able to use it in custom loggers and renamed it to `redactWinstonLogLine`.
|
||||
- Updated dependencies
|
||||
- @backstage/integration@1.3.0-next.1
|
||||
|
||||
## 0.15.0-next.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/backend-common",
|
||||
"description": "Common functionality library for Backstage backends",
|
||||
"version": "0.15.0-next.0",
|
||||
"version": "0.15.0-next.1",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"private": false,
|
||||
@@ -38,7 +38,7 @@
|
||||
"@backstage/config": "^1.0.1",
|
||||
"@backstage/config-loader": "^1.1.3",
|
||||
"@backstage/errors": "^1.1.0",
|
||||
"@backstage/integration": "^1.3.0-next.0",
|
||||
"@backstage/integration": "^1.3.0-next.1",
|
||||
"@backstage/types": "^1.0.0",
|
||||
"@google-cloud/storage": "^6.0.0",
|
||||
"@keyv/redis": "^2.2.3",
|
||||
|
||||
@@ -106,7 +106,7 @@ describe('GitlabUrlReader', () => {
|
||||
);
|
||||
|
||||
it.each([
|
||||
// Project URLs
|
||||
// Scoped routes
|
||||
{
|
||||
url: 'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path/to/file.yaml',
|
||||
config: createConfig(),
|
||||
@@ -135,12 +135,12 @@ describe('GitlabUrlReader', () => {
|
||||
}),
|
||||
},
|
||||
|
||||
// Raw URLs
|
||||
// Unscoped route
|
||||
{
|
||||
url: 'https://gitlab.example.com/a/b/blob/master/c.yaml',
|
||||
config: createConfig(),
|
||||
response: expect.objectContaining({
|
||||
url: 'https://gitlab.example.com/a/b/raw/master/c.yaml',
|
||||
url: 'https://gitlab.example.com/api/v4/projects/12345/repository/files/c.yaml/raw?ref=master',
|
||||
}),
|
||||
},
|
||||
])('should handle happy path %#', async ({ url, config, response }) => {
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @backstage/core-components
|
||||
|
||||
## 0.10.1-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- a22af3edc8: Adding a `className` prop to the `MarkdownContent` component
|
||||
|
||||
## 0.10.1-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/core-components",
|
||||
"description": "Core components used by Backstage plugins and apps",
|
||||
"version": "0.10.1-next.0",
|
||||
"version": "0.10.1-next.1",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
|
||||
@@ -70,6 +70,7 @@ type Props = {
|
||||
linkTarget?: Options['linkTarget'];
|
||||
transformLinkUri?: (href: string) => string;
|
||||
transformImageUri?: (href: string) => string;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const components: Options['components'] = {
|
||||
@@ -99,12 +100,13 @@ export function MarkdownContent(props: Props) {
|
||||
linkTarget,
|
||||
transformLinkUri,
|
||||
transformImageUri,
|
||||
className,
|
||||
} = props;
|
||||
const classes = useStyles();
|
||||
return (
|
||||
<ReactMarkdown
|
||||
remarkPlugins={dialect === 'gfm' ? [gfm] : []}
|
||||
className={classes.markdown}
|
||||
className={`${classes.markdown} ${className}`}
|
||||
children={content}
|
||||
components={components}
|
||||
linkTarget={linkTarget}
|
||||
|
||||
@@ -134,6 +134,7 @@ export type SelectProps = {
|
||||
triggerReset?: boolean;
|
||||
native?: boolean;
|
||||
disabled?: boolean;
|
||||
margin?: 'dense' | 'none';
|
||||
};
|
||||
|
||||
/** @public */
|
||||
@@ -148,6 +149,7 @@ export function SelectComponent(props: SelectProps) {
|
||||
triggerReset,
|
||||
native = false,
|
||||
disabled = false,
|
||||
margin,
|
||||
} = props;
|
||||
const classes = useStyles();
|
||||
const [value, setValue] = useState<SelectedItems>(
|
||||
@@ -206,6 +208,7 @@ export function SelectComponent(props: SelectProps) {
|
||||
data-testid="select"
|
||||
displayEmpty
|
||||
multiple={multiple}
|
||||
margin={margin}
|
||||
onChange={handleChange}
|
||||
onClick={handleClick}
|
||||
open={isOpen}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @backstage/create-app
|
||||
|
||||
## 0.4.30-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Bumped create-app version.
|
||||
|
||||
## 0.4.30-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/create-app",
|
||||
"description": "A CLI that helps you create your own Backstage app",
|
||||
"version": "0.4.30-next.0",
|
||||
"version": "0.4.30-next.1",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# @backstage/integration
|
||||
|
||||
## 1.3.0-next.1
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- ad35364e97: feat(techdocs): add edit button support for bitbucketServer
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 1f27d83933: Fixed bug in getGitLabFileFetchUrl where a target whose path did not contain the
|
||||
`/-/` scope would result in a fetch URL that did not support
|
||||
private-token-based authentication.
|
||||
|
||||
## 1.3.0-next.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/integration",
|
||||
"description": "Helpers for managing integrations towards external systems",
|
||||
"version": "1.3.0-next.0",
|
||||
"version": "1.3.0-next.1",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -113,9 +113,21 @@ export class ScmIntegrations implements ScmIntegrationRegistry {
|
||||
}
|
||||
|
||||
byUrl(url: string | URL): ScmIntegration | undefined {
|
||||
return Object.values(this.byType)
|
||||
let candidates = Object.values(this.byType)
|
||||
.map(i => i.byUrl(url))
|
||||
.find(Boolean);
|
||||
.filter(Boolean);
|
||||
|
||||
// Do not return deprecated integrations if there are other options
|
||||
if (candidates.length > 1) {
|
||||
const filteredCandidates = candidates.filter(
|
||||
x => !(x instanceof BitbucketIntegration),
|
||||
);
|
||||
if (filteredCandidates.length !== 0) {
|
||||
candidates = filteredCandidates;
|
||||
}
|
||||
}
|
||||
|
||||
return candidates[0];
|
||||
}
|
||||
|
||||
byHost(host: string): ScmIntegration | undefined {
|
||||
|
||||
@@ -44,31 +44,58 @@ describe('BitbucketServerIntegration', () => {
|
||||
expect(integration.title).toBe('h.com');
|
||||
});
|
||||
|
||||
it('resolves url line number correctly', () => {
|
||||
it('resolves url', () => {
|
||||
const integration = new BitbucketServerIntegration({
|
||||
host: 'h.com',
|
||||
} as any);
|
||||
|
||||
expect(
|
||||
integration.resolveUrl({
|
||||
url: './a.yaml',
|
||||
base: 'https://h.com/my-owner/my-project/src/master/README.md',
|
||||
lineNumber: 14,
|
||||
url: './README.md',
|
||||
base: 'https://h.com/projects/my-project/repos/my-repo/browse/?at=master',
|
||||
}),
|
||||
).toBe('https://h.com/my-owner/my-project/src/master/a.yaml#a.yaml-14');
|
||||
).toBe(
|
||||
'https://h.com/projects/my-project/repos/my-repo/browse/README.md?at=master',
|
||||
);
|
||||
});
|
||||
|
||||
it('resolve edit URL', () => {
|
||||
it('resolves url with line number', () => {
|
||||
const integration = new BitbucketServerIntegration({
|
||||
host: 'h.com',
|
||||
} as any);
|
||||
|
||||
expect(
|
||||
integration.resolveUrl({
|
||||
url: './README.md',
|
||||
base: 'https://h.com/projects/my-project/repos/my-repo/browse/?at=master',
|
||||
lineNumber: 14,
|
||||
}),
|
||||
).toBe(
|
||||
'https://h.com/projects/my-project/repos/my-repo/browse/README.md?at=master#14',
|
||||
);
|
||||
});
|
||||
|
||||
it('resolves edit url', () => {
|
||||
const integration = new BitbucketServerIntegration({
|
||||
host: 'h.com',
|
||||
} as any);
|
||||
|
||||
expect(
|
||||
integration.resolveEditUrl(
|
||||
'https://h.com/my-owner/my-project/src/master/README.md',
|
||||
'https://h.com/projects/my-project/repos/my-repo/browse/README.md',
|
||||
),
|
||||
).toBe(
|
||||
'https://h.com/my-owner/my-project/src/master/README.md?mode=edit&spa=0&at=master',
|
||||
);
|
||||
).toBe('https://h.com/projects/my-project/repos/my-repo/browse/README.md');
|
||||
});
|
||||
|
||||
it('resolves edit url with query params', () => {
|
||||
const integration = new BitbucketServerIntegration({
|
||||
host: 'h.com',
|
||||
} as any);
|
||||
|
||||
expect(
|
||||
integration.resolveEditUrl(
|
||||
'https://h.com/projects/my-project/repos/my-repo/browse/README.md?at=master',
|
||||
),
|
||||
).toBe('https://h.com/projects/my-project/repos/my-repo/browse/README.md');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import parseGitUrl from 'git-url-parse';
|
||||
import { basicIntegrations, defaultScmResolveUrl } from '../helpers';
|
||||
import { ScmIntegration, ScmIntegrationsFactory } from '../types';
|
||||
import {
|
||||
@@ -63,12 +62,11 @@ export class BitbucketServerIntegration implements ScmIntegration {
|
||||
}): string {
|
||||
const resolved = defaultScmResolveUrl(options);
|
||||
|
||||
// Bitbucket Server line numbers use the syntax #example.txt-42, rather than #L42
|
||||
// Bitbucket Server line numbers use the syntax #42, rather than #L42
|
||||
if (options.lineNumber) {
|
||||
const url = new URL(resolved);
|
||||
|
||||
const filename = url.pathname.split('/').slice(-1)[0];
|
||||
url.hash = `${filename}-${options.lineNumber}`;
|
||||
url.hash = options.lineNumber.toString();
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
@@ -76,14 +74,11 @@ export class BitbucketServerIntegration implements ScmIntegration {
|
||||
}
|
||||
|
||||
resolveEditUrl(url: string): string {
|
||||
const urlData = parseGitUrl(url);
|
||||
const editUrl = new URL(url);
|
||||
|
||||
editUrl.searchParams.set('mode', 'edit');
|
||||
// TODO: Not sure what spa=0 does, at least bitbucket.org doesn't support it
|
||||
// but this is taken over from the initial implementation.
|
||||
editUrl.searchParams.set('spa', '0');
|
||||
editUrl.searchParams.set('at', urlData.ref);
|
||||
return editUrl.toString();
|
||||
// Bitbucket Server doesn't support deep linking to edit mode, therefore there's nothing to do here.
|
||||
// We just remove query parameters since they cause issues with TechDocs edit button.
|
||||
if (url.includes('?')) {
|
||||
return url.substring(0, url.indexOf('?'));
|
||||
}
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,19 +28,15 @@ describe('gitlab core', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
worker.use(
|
||||
rest.get('*/api/v4/projects/:name', (_, res, ctx) =>
|
||||
rest.get('*/api/v4/projects/group%2Fproject', (_, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json({ id: 12345 })),
|
||||
),
|
||||
rest.get('*/api/v4/projects/group%2Fsubgroup%2Fproject', (_, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json({ id: 12345 })),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
const configWithToken: GitLabIntegrationConfig = {
|
||||
host: 'gitlab.com',
|
||||
token: '0123456789',
|
||||
apiBaseUrl: '<ignored>',
|
||||
baseUrl: '<ignored>',
|
||||
};
|
||||
|
||||
const configWithNoToken: GitLabIntegrationConfig = {
|
||||
host: 'gitlab.com',
|
||||
apiBaseUrl: '<ignored>',
|
||||
@@ -61,163 +57,133 @@ describe('gitlab core', () => {
|
||||
baseUrl: 'https://gitlab.mycompany.com',
|
||||
};
|
||||
|
||||
describe('getGitLabFileFetchUrl with .yaml extension', () => {
|
||||
it.each([
|
||||
// Project URLs
|
||||
{
|
||||
config: configWithNoToken,
|
||||
url: 'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path/to/file.yaml',
|
||||
result:
|
||||
'https://gitlab.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile.yaml/raw?ref=branch',
|
||||
},
|
||||
{
|
||||
config: configSelfHosteWithRelativePath,
|
||||
url: 'https://gitlab.mycompany.com/gitlab/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path/to/file.yaml',
|
||||
result:
|
||||
'https://gitlab.mycompany.com/gitlab/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile.yaml/raw?ref=branch',
|
||||
},
|
||||
{
|
||||
config: configSelfHostedWithoutRelativePath,
|
||||
url: 'https://gitlab.mycompany.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path/to/file.yaml',
|
||||
result:
|
||||
'https://gitlab.mycompany.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile.yaml/raw?ref=branch',
|
||||
},
|
||||
{
|
||||
config: configWithNoToken,
|
||||
// Works with non URI encoded link
|
||||
url: 'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path/to/file with spaces.yaml',
|
||||
result:
|
||||
'https://gitlab.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile%20with%20spaces.yaml/raw?ref=branch',
|
||||
},
|
||||
{
|
||||
config: configSelfHosteWithRelativePath,
|
||||
url: 'https://gitlab.mycompany.com/gitlab/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path/to/file with spaces.yaml',
|
||||
result:
|
||||
'https://gitlab.mycompany.com/gitlab/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile%20with%20spaces.yaml/raw?ref=branch',
|
||||
},
|
||||
{
|
||||
config: configSelfHostedWithoutRelativePath,
|
||||
url: 'https://gitlab.mycompany.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path/to/file with spaces.yaml',
|
||||
result:
|
||||
'https://gitlab.mycompany.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile%20with%20spaces.yaml/raw?ref=branch',
|
||||
},
|
||||
{
|
||||
config: configWithNoToken,
|
||||
url: 'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path%20with%20spaces/to/file.yaml',
|
||||
result:
|
||||
'https://gitlab.com/api/v4/projects/12345/repository/files/my%2Fpath%20with%20spaces%2Fto%2Ffile.yaml/raw?ref=branch',
|
||||
},
|
||||
{
|
||||
config: configWithToken,
|
||||
url: 'https://gitlab.example.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path%20with%20spaces/to/file.yaml',
|
||||
result:
|
||||
'https://gitlab.example.com/api/v4/projects/12345/repository/files/my%2Fpath%20with%20spaces%2Fto%2Ffile.yaml/raw?ref=branch',
|
||||
},
|
||||
{
|
||||
config: configWithNoToken,
|
||||
url: 'https://gitlab.com/groupA/teams/teamA/repoA/-/blob/branch/my/path%20with%20spaces/to/file.yaml', // Repo not in subgroup
|
||||
result:
|
||||
'https://gitlab.com/api/v4/projects/12345/repository/files/my%2Fpath%20with%20spaces%2Fto%2Ffile.yaml/raw?ref=branch',
|
||||
},
|
||||
// Raw URLs
|
||||
{
|
||||
config: configWithNoToken,
|
||||
url: 'https://gitlab.example.com/a/b/blob/master/c.yaml',
|
||||
result: 'https://gitlab.example.com/a/b/raw/master/c.yaml',
|
||||
},
|
||||
{
|
||||
config: configWithNoToken,
|
||||
url: 'https://gitlab.example.com/a/b/repo/blob/master/c.yaml',
|
||||
result: 'https://gitlab.example.com/a/b/repo/raw/master/c.yaml',
|
||||
},
|
||||
{
|
||||
config: configWithNoToken,
|
||||
url: 'https://gitlab.example.com/a/blob/blob/master/c.yaml',
|
||||
result: 'https://gitlab.example.com/a/blob/raw/master/c.yaml',
|
||||
},
|
||||
{
|
||||
config: configWithNoToken,
|
||||
url: 'https://gitlab.example.com/a/b/blob/blob/c.yaml',
|
||||
result: 'https://gitlab.example.com/a/b/raw/blob/c.yaml',
|
||||
},
|
||||
{
|
||||
config: configWithNoToken,
|
||||
url: 'https://gitlab.example.com/a//blob/blob/c.yaml',
|
||||
result: 'https://gitlab.example.com/a/blob/raw/c.yaml',
|
||||
},
|
||||
])('should handle happy path %#', async ({ config, url, result }) => {
|
||||
await expect(getGitLabFileFetchUrl(url, config)).resolves.toBe(result);
|
||||
});
|
||||
});
|
||||
describe('getGitLabFileFetchUrl', () => {
|
||||
describe('when target has a scoped route', () => {
|
||||
it('returns a projects API URL', async () => {
|
||||
const target =
|
||||
'https://gitlab.com/group/project/-/blob/branch/folder/file.yaml';
|
||||
const fetchUrl =
|
||||
'https://gitlab.com/api/v4/projects/12345/repository/files/folder%2Ffile.yaml/raw?ref=branch';
|
||||
await expect(
|
||||
getGitLabFileFetchUrl(target, configWithNoToken),
|
||||
).resolves.toBe(fetchUrl);
|
||||
});
|
||||
|
||||
describe('getGitLabFileFetchUrl with .yml extension', () => {
|
||||
it.each([
|
||||
// Project URLs
|
||||
{
|
||||
config: configWithNoToken,
|
||||
url: 'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path/to/file.yml',
|
||||
result:
|
||||
'https://gitlab.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile.yml/raw?ref=branch',
|
||||
},
|
||||
{
|
||||
config: configSelfHosteWithRelativePath,
|
||||
url: 'https://gitlab.mycompany.com/gitlab/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path/to/file.yml',
|
||||
result:
|
||||
'https://gitlab.mycompany.com/gitlab/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile.yml/raw?ref=branch',
|
||||
},
|
||||
{
|
||||
config: configSelfHostedWithoutRelativePath,
|
||||
url: 'https://gitlab.mycompany.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path/to/file.yml',
|
||||
result:
|
||||
'https://gitlab.mycompany.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile.yml/raw?ref=branch',
|
||||
},
|
||||
{
|
||||
config: configWithNoToken,
|
||||
// Works with non URI encoded link
|
||||
url: 'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path/to/file with spaces.yml',
|
||||
result:
|
||||
'https://gitlab.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile%20with%20spaces.yml/raw?ref=branch',
|
||||
},
|
||||
{
|
||||
config: configSelfHosteWithRelativePath,
|
||||
// Works with non URI encoded link
|
||||
url: 'https://gitlab.mycompany.com/gitlab/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path/to/file with spaces.yml',
|
||||
result:
|
||||
'https://gitlab.mycompany.com/gitlab/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile%20with%20spaces.yml/raw?ref=branch',
|
||||
},
|
||||
{
|
||||
config: configSelfHostedWithoutRelativePath,
|
||||
// Works with non URI encoded link
|
||||
url: 'https://gitlab.mycompany.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path/to/file with spaces.yml',
|
||||
result:
|
||||
'https://gitlab.mycompany.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile%20with%20spaces.yml/raw?ref=branch',
|
||||
},
|
||||
{
|
||||
config: configWithNoToken,
|
||||
url: 'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path%20with%20spaces/to/file.yml',
|
||||
result:
|
||||
'https://gitlab.com/api/v4/projects/12345/repository/files/my%2Fpath%20with%20spaces%2Fto%2Ffile.yml/raw?ref=branch',
|
||||
},
|
||||
{
|
||||
config: configWithToken,
|
||||
url: 'https://gitlab.example.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path%20with%20spaces/to/file.yml',
|
||||
result:
|
||||
'https://gitlab.example.com/api/v4/projects/12345/repository/files/my%2Fpath%20with%20spaces%2Fto%2Ffile.yml/raw?ref=branch',
|
||||
},
|
||||
{
|
||||
config: configWithNoToken,
|
||||
url: 'https://gitlab.com/groupA/teams/teamA/repoA/-/blob/branch/my/path%20with%20spaces/to/file.yml', // Repo not in subgroup
|
||||
result:
|
||||
'https://gitlab.com/api/v4/projects/12345/repository/files/my%2Fpath%20with%20spaces%2Fto%2Ffile.yml/raw?ref=branch',
|
||||
},
|
||||
// Raw URLs
|
||||
{
|
||||
config: configWithNoToken,
|
||||
url: 'https://gitlab.example.com/a/b/blob/master/c.yml',
|
||||
result: 'https://gitlab.example.com/a/b/raw/master/c.yml',
|
||||
},
|
||||
])('should handle happy path %#', async ({ config, url, result }) => {
|
||||
await expect(getGitLabFileFetchUrl(url, config)).resolves.toBe(result);
|
||||
it('supports folder named "blob"', async () => {
|
||||
const target =
|
||||
'https://gitlab.com/group/project/-/blob/branch/blob/file.yaml';
|
||||
const fetchUrl =
|
||||
'https://gitlab.com/api/v4/projects/12345/repository/files/blob%2Ffile.yaml/raw?ref=branch';
|
||||
await expect(
|
||||
getGitLabFileFetchUrl(target, configWithNoToken),
|
||||
).resolves.toBe(fetchUrl);
|
||||
});
|
||||
|
||||
it('locates projects in subgroups', async () => {
|
||||
const target =
|
||||
'https://gitlab.com/group/subgroup/project/-/blob/branch/folder/file.yaml';
|
||||
const fetchUrl =
|
||||
'https://gitlab.com/api/v4/projects/12345/repository/files/folder%2Ffile.yaml/raw?ref=branch';
|
||||
await expect(
|
||||
getGitLabFileFetchUrl(target, configWithNoToken),
|
||||
).resolves.toBe(fetchUrl);
|
||||
});
|
||||
|
||||
it('supports filename with .yml extension', async () => {
|
||||
const target =
|
||||
'https://gitlab.com/group/project/-/blob/branch/folder/file.yml';
|
||||
const fetchUrl =
|
||||
'https://gitlab.com/api/v4/projects/12345/repository/files/folder%2Ffile.yml/raw?ref=branch';
|
||||
await expect(
|
||||
getGitLabFileFetchUrl(target, configWithNoToken),
|
||||
).resolves.toBe(fetchUrl);
|
||||
});
|
||||
|
||||
it('supports non-URI-encoded target', async () => {
|
||||
const target =
|
||||
'https://gitlab.com/group/project/-/blob/branch/folder/file with spaces.yaml';
|
||||
const fetchUrl =
|
||||
'https://gitlab.com/api/v4/projects/12345/repository/files/folder%2Ffile%20with%20spaces.yaml/raw?ref=branch';
|
||||
await expect(
|
||||
getGitLabFileFetchUrl(target, configWithNoToken),
|
||||
).resolves.toBe(fetchUrl);
|
||||
});
|
||||
|
||||
describe('when gitlab is self-hosted', () => {
|
||||
it('returns projects API URL', async () => {
|
||||
const target =
|
||||
'https://gitlab.mycompany.com/group/project/-/blob/branch/folder/file.yaml';
|
||||
const fetchUrl =
|
||||
'https://gitlab.mycompany.com/api/v4/projects/12345/repository/files/folder%2Ffile.yaml/raw?ref=branch';
|
||||
await expect(
|
||||
getGitLabFileFetchUrl(target, configSelfHostedWithoutRelativePath),
|
||||
).resolves.toBe(fetchUrl);
|
||||
});
|
||||
|
||||
it('handles non-URI-encoded target', async () => {
|
||||
const target =
|
||||
'https://gitlab.mycompany.com/group/project/-/blob/branch/folder/file with spaces.yaml';
|
||||
const fetchUrl =
|
||||
'https://gitlab.mycompany.com/api/v4/projects/12345/repository/files/folder%2Ffile%20with%20spaces.yaml/raw?ref=branch';
|
||||
await expect(
|
||||
getGitLabFileFetchUrl(target, configSelfHostedWithoutRelativePath),
|
||||
).resolves.toBe(fetchUrl);
|
||||
});
|
||||
|
||||
describe('with a relative path', () => {
|
||||
it('returns projects API URL', async () => {
|
||||
const target =
|
||||
'https://gitlab.mycompany.com/gitlab/group/project/-/blob/branch/folder/file.yaml';
|
||||
const fetchUrl =
|
||||
'https://gitlab.mycompany.com/gitlab/api/v4/projects/12345/repository/files/folder%2Ffile.yaml/raw?ref=branch';
|
||||
await expect(
|
||||
getGitLabFileFetchUrl(target, configSelfHosteWithRelativePath),
|
||||
).resolves.toBe(fetchUrl);
|
||||
});
|
||||
|
||||
it('handles non-URI-encoded target', async () => {
|
||||
const target =
|
||||
'https://gitlab.mycompany.com/gitlab/group/project/-/blob/branch/folder/file with spaces.yaml';
|
||||
const fetchUrl =
|
||||
'https://gitlab.mycompany.com/gitlab/api/v4/projects/12345/repository/files/folder%2Ffile%20with%20spaces.yaml/raw?ref=branch';
|
||||
await expect(
|
||||
getGitLabFileFetchUrl(target, configSelfHosteWithRelativePath),
|
||||
).resolves.toBe(fetchUrl);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when target has an unscoped route', () => {
|
||||
it('returns projects API URL', async () => {
|
||||
const target =
|
||||
'https://gitlab.com/group/project/blob/branch/folder/file.yaml';
|
||||
const fetchUrl =
|
||||
'https://gitlab.com/api/v4/projects/12345/repository/files/folder%2Ffile.yaml/raw?ref=branch';
|
||||
await expect(
|
||||
getGitLabFileFetchUrl(target, configWithNoToken),
|
||||
).resolves.toBe(fetchUrl);
|
||||
});
|
||||
|
||||
it('supports project in subgroup', async () => {
|
||||
const target =
|
||||
'https://gitlab.com/group/subgroup/project/blob/branch/folder/file.yaml';
|
||||
const fetchUrl =
|
||||
'https://gitlab.com/api/v4/projects/12345/repository/files/folder%2Ffile.yaml/raw?ref=branch';
|
||||
await expect(
|
||||
getGitLabFileFetchUrl(target, configWithNoToken),
|
||||
).resolves.toBe(fetchUrl);
|
||||
});
|
||||
|
||||
it('supports repo with branch named "blob"', async () => {
|
||||
const target =
|
||||
'https://gitlab.com/group/project/blob/blob/folder/file.yaml';
|
||||
const fetchUrl =
|
||||
'https://gitlab.com/api/v4/projects/12345/repository/files/folder%2Ffile.yaml/raw?ref=blob';
|
||||
await expect(
|
||||
getGitLabFileFetchUrl(target, configWithNoToken),
|
||||
).resolves.toBe(fetchUrl);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,7 +19,6 @@ import {
|
||||
GitLabIntegrationConfig,
|
||||
} from './config';
|
||||
import fetch from 'cross-fetch';
|
||||
import { InputError } from '@backstage/errors';
|
||||
|
||||
/**
|
||||
* Given a URL pointing to a file on a provider, returns a URL that is suitable
|
||||
@@ -29,7 +28,7 @@ import { InputError } from '@backstage/errors';
|
||||
*
|
||||
* Converts
|
||||
* from: https://gitlab.example.com/a/b/blob/master/c.yaml
|
||||
* to: https://gitlab.example.com/a/b/raw/master/c.yaml
|
||||
* to: https://gitlab.com/api/v4/projects/projectId/repository/c.yaml?ref=master
|
||||
* -or-
|
||||
* from: https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/filepath
|
||||
* to: https://gitlab.com/api/v4/projects/projectId/repository/files/filepath?ref=branch
|
||||
@@ -42,15 +41,8 @@ export async function getGitLabFileFetchUrl(
|
||||
url: string,
|
||||
config: GitLabIntegrationConfig,
|
||||
): Promise<string> {
|
||||
// TODO(Rugvip): From the old GitlabReaderProcessor; used
|
||||
// the existence of /-/blob/ to switch the logic. Don't know if this
|
||||
// makes sense and it might require some more work.
|
||||
|
||||
if (url.includes('/-/blob/')) {
|
||||
const projectID = await getProjectId(url, config);
|
||||
return buildProjectUrl(url, projectID, config).toString();
|
||||
}
|
||||
return buildRawUrl(url).toString();
|
||||
const projectID = await getProjectId(url, config);
|
||||
return buildProjectUrl(url, projectID, config).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,38 +62,6 @@ export function getGitLabRequestOptions(config: GitLabIntegrationConfig): {
|
||||
};
|
||||
}
|
||||
|
||||
// Converts
|
||||
// from: https://gitlab.example.com/groupA/teams/repoA/blob/master/c.yaml
|
||||
// to: https://gitlab.example.com/groupA/teams/repoA/raw/master/c.yaml
|
||||
export function buildRawUrl(target: string): URL {
|
||||
try {
|
||||
const url = new URL(target);
|
||||
|
||||
const splitPath = url.pathname.split('/').filter(Boolean);
|
||||
|
||||
// Check blob existence
|
||||
const blobIndex = splitPath.indexOf('blob', 2);
|
||||
if (blobIndex < 2 || blobIndex === splitPath.length - 1) {
|
||||
throw new InputError('Wrong GitLab URL');
|
||||
}
|
||||
|
||||
// Take repo path
|
||||
const repoPath = splitPath.slice(0, blobIndex);
|
||||
const restOfPath = splitPath.slice(blobIndex + 1);
|
||||
|
||||
if (!restOfPath.join('/').match(/\.(yaml|yml)$/)) {
|
||||
throw new InputError('Wrong GitLab URL');
|
||||
}
|
||||
|
||||
// Replace 'blob' with 'raw'
|
||||
url.pathname = [...repoPath, 'raw', ...restOfPath].join('/');
|
||||
|
||||
return url;
|
||||
} catch (e) {
|
||||
throw new InputError(`Incorrect url: ${target}, ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Converts
|
||||
// from: https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/filepath
|
||||
// to: https://gitlab.com/api/v4/projects/projectId/repository/files/filepath?ref=branch
|
||||
@@ -113,7 +73,10 @@ export function buildProjectUrl(
|
||||
try {
|
||||
const url = new URL(target);
|
||||
|
||||
const branchAndFilePath = url.pathname.split('/-/blob/')[1];
|
||||
const branchAndFilePath = url.pathname
|
||||
.split('/blob/')
|
||||
.slice(1)
|
||||
.join('/blob/');
|
||||
const [branch, ...filePath] = branchAndFilePath.split('/');
|
||||
const relativePath = getGitLabIntegrationRelativePath(config);
|
||||
|
||||
@@ -143,12 +106,12 @@ export async function getProjectId(
|
||||
): Promise<number> {
|
||||
const url = new URL(target);
|
||||
|
||||
if (!url.pathname.includes('/-/blob/')) {
|
||||
if (!url.pathname.includes('/blob/')) {
|
||||
throw new Error('Please provide full path to yaml file from GitLab');
|
||||
}
|
||||
|
||||
try {
|
||||
let repo = url.pathname.split('/-/blob/')[0];
|
||||
let repo = url.pathname.split('/-/blob/')[0].split('/blob/')[0];
|
||||
|
||||
// Get gitlab relative path
|
||||
const relativePath = getGitLabIntegrationRelativePath(config);
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @techdocs/cli
|
||||
|
||||
## 1.1.4-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/backend-common@0.15.0-next.1
|
||||
- @backstage/plugin-techdocs-node@1.3.0-next.1
|
||||
|
||||
## 1.1.4-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@techdocs/cli",
|
||||
"description": "Utility CLI for managing TechDocs sites in Backstage.",
|
||||
"version": "1.1.4-next.0",
|
||||
"version": "1.1.4-next.1",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
@@ -62,11 +62,11 @@
|
||||
"ext": "ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.15.0-next.0",
|
||||
"@backstage/backend-common": "^0.15.0-next.1",
|
||||
"@backstage/catalog-model": "^1.1.0",
|
||||
"@backstage/cli-common": "^0.1.9",
|
||||
"@backstage/config": "^1.0.1",
|
||||
"@backstage/plugin-techdocs-node": "^1.2.1-next.0",
|
||||
"@backstage/plugin-techdocs-node": "^1.3.0-next.1",
|
||||
"@types/dockerode": "^3.3.0",
|
||||
"commander": "^9.1.0",
|
||||
"dockerode": "^3.3.1",
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^1.1.0",
|
||||
"@backstage/core-components": "^0.10.1-next.0",
|
||||
"@backstage/core-components": "^0.10.1-next.1",
|
||||
"@backstage/core-plugin-api": "^1.0.5-next.0",
|
||||
"@backstage/dev-utils": "^1.0.5-next.0",
|
||||
"@backstage/plugin-catalog-react": "^1.1.3-next.0",
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^1.1.0",
|
||||
"@backstage/core-components": "^0.10.1-next.0",
|
||||
"@backstage/core-components": "^0.10.1-next.1",
|
||||
"@backstage/core-plugin-api": "^1.0.5-next.0",
|
||||
"@backstage/plugin-catalog-react": "^1.1.3-next.0",
|
||||
"@backstage/theme": "^0.2.16",
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/config": "^1.0.1",
|
||||
"@backstage/core-components": "^0.10.1-next.0",
|
||||
"@backstage/core-components": "^0.10.1-next.1",
|
||||
"@backstage/core-plugin-api": "^1.0.5-next.0",
|
||||
"@backstage/theme": "^0.2.16",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"clean": "backstage-cli package clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/core-components": "^0.10.1-next.0",
|
||||
"@backstage/core-components": "^0.10.1-next.1",
|
||||
"@backstage/core-plugin-api": "^1.0.5-next.0",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# @backstage/plugin-api-docs
|
||||
|
||||
## 0.8.8-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- dae12c71cf: Updated dependency `@asyncapi/react-component` to `1.0.0-next.40`.
|
||||
- Updated dependencies
|
||||
- @backstage/core-components@0.10.1-next.1
|
||||
- @backstage/plugin-catalog@1.5.0-next.1
|
||||
- @backstage/plugin-catalog-react@1.1.3-next.1
|
||||
|
||||
## 0.8.8-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/plugin-api-docs",
|
||||
"description": "A Backstage plugin that helps represent API entities in the frontend",
|
||||
"version": "0.8.8-next.0",
|
||||
"version": "0.8.8-next.1",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -35,10 +35,10 @@
|
||||
"dependencies": {
|
||||
"@asyncapi/react-component": "1.0.0-next.40",
|
||||
"@backstage/catalog-model": "^1.1.0",
|
||||
"@backstage/core-components": "^0.10.1-next.0",
|
||||
"@backstage/core-components": "^0.10.1-next.1",
|
||||
"@backstage/core-plugin-api": "^1.0.5-next.0",
|
||||
"@backstage/plugin-catalog": "^1.5.0-next.0",
|
||||
"@backstage/plugin-catalog-react": "^1.1.3-next.0",
|
||||
"@backstage/plugin-catalog": "^1.5.0-next.1",
|
||||
"@backstage/plugin-catalog-react": "^1.1.3-next.1",
|
||||
"@backstage/theme": "^0.2.16",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^1.1.0",
|
||||
"@backstage/core-components": "^0.10.1-next.0",
|
||||
"@backstage/core-components": "^0.10.1-next.1",
|
||||
"@backstage/core-plugin-api": "^1.0.5-next.0",
|
||||
"@backstage/errors": "^1.1.0",
|
||||
"@backstage/plugin-azure-devops-common": "^0.2.4",
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^1.1.0",
|
||||
"@backstage/core-components": "^0.10.1-next.0",
|
||||
"@backstage/core-components": "^0.10.1-next.1",
|
||||
"@backstage/core-plugin-api": "^1.0.5-next.0",
|
||||
"@backstage/errors": "^1.1.0",
|
||||
"@backstage/plugin-catalog-react": "^1.1.3-next.0",
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
"@backstage/catalog-client": "^1.0.4",
|
||||
"@backstage/catalog-model": "^1.1.0",
|
||||
"@backstage/cli": "^0.18.1-next.0",
|
||||
"@backstage/core-components": "^0.10.1-next.0",
|
||||
"@backstage/core-components": "^0.10.1-next.1",
|
||||
"@backstage/core-plugin-api": "^1.0.5-next.0",
|
||||
"@backstage/plugin-catalog": "^1.5.0-next.0",
|
||||
"@backstage/plugin-catalog-react": "^1.1.3-next.0",
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^1.1.0",
|
||||
"@backstage/core-components": "^0.10.1-next.0",
|
||||
"@backstage/core-components": "^0.10.1-next.1",
|
||||
"@backstage/core-plugin-api": "^1.0.5-next.0",
|
||||
"@backstage/plugin-catalog-react": "^1.1.3-next.0",
|
||||
"@backstage/theme": "^0.2.16",
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
# @backstage/plugin-catalog-backend-module-github
|
||||
|
||||
## 0.1.6-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- f48950e34b: Github Entity Provider functionality for adding entities to the catalog.
|
||||
|
||||
This provider replaces the GithubDiscoveryProcessor functionality as providers offer more flexibility with scheduling ingestion, removing and preventing orphaned entities.
|
||||
|
||||
More information can be found on the [GitHub Discovery](https://backstage.io/docs/integrations/github/discovery) page.
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/backend-common@0.15.0-next.1
|
||||
- @backstage/integration@1.3.0-next.1
|
||||
- @backstage/plugin-catalog-backend@1.3.1-next.1
|
||||
|
||||
## 0.1.6-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -40,6 +40,24 @@ export class GithubDiscoveryProcessor implements CatalogProcessor {
|
||||
): Promise<boolean>;
|
||||
}
|
||||
|
||||
// @public
|
||||
export class GitHubEntityProvider implements EntityProvider {
|
||||
// (undocumented)
|
||||
connect(connection: EntityProviderConnection): Promise<void>;
|
||||
// (undocumented)
|
||||
static fromConfig(
|
||||
config: Config,
|
||||
options: {
|
||||
logger: Logger;
|
||||
schedule: TaskRunner;
|
||||
},
|
||||
): GitHubEntityProvider[];
|
||||
// (undocumented)
|
||||
getProviderName(): string;
|
||||
// (undocumented)
|
||||
refresh(logger: Logger): Promise<void>;
|
||||
}
|
||||
|
||||
// @public
|
||||
export type GithubMultiOrgConfig = Array<{
|
||||
name: string;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/plugin-catalog-backend-module-github",
|
||||
"description": "A Backstage catalog backend module that helps integrate towards GitHub",
|
||||
"version": "0.1.6-next.0",
|
||||
"version": "0.1.6-next.1",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -33,13 +33,13 @@
|
||||
"start": "backstage-cli package start"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.15.0-next.0",
|
||||
"@backstage/backend-common": "^0.15.0-next.1",
|
||||
"@backstage/backend-tasks": "^0.3.4-next.0",
|
||||
"@backstage/catalog-model": "^1.1.0",
|
||||
"@backstage/config": "^1.0.1",
|
||||
"@backstage/errors": "^1.1.0",
|
||||
"@backstage/integration": "^1.3.0-next.0",
|
||||
"@backstage/plugin-catalog-backend": "^1.3.1-next.0",
|
||||
"@backstage/integration": "^1.3.0-next.1",
|
||||
"@backstage/plugin-catalog-backend": "^1.3.1-next.1",
|
||||
"@backstage/types": "^1.0.0",
|
||||
"@octokit/graphql": "^5.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
|
||||
@@ -20,9 +20,10 @@
|
||||
* @packageDocumentation
|
||||
*/
|
||||
|
||||
export { GithubDiscoveryProcessor } from './GithubDiscoveryProcessor';
|
||||
export { GithubMultiOrgReaderProcessor } from './GithubMultiOrgReaderProcessor';
|
||||
export { GitHubOrgEntityProvider } from './GitHubOrgEntityProvider';
|
||||
export type { GitHubOrgEntityProviderOptions } from './GitHubOrgEntityProvider';
|
||||
export { GithubOrgReaderProcessor } from './GithubOrgReaderProcessor';
|
||||
export { GithubDiscoveryProcessor } from './processors/GithubDiscoveryProcessor';
|
||||
export { GithubMultiOrgReaderProcessor } from './processors/GithubMultiOrgReaderProcessor';
|
||||
export { GithubOrgReaderProcessor } from './processors/GithubOrgReaderProcessor';
|
||||
export { GitHubEntityProvider } from './providers/GitHubEntityProvider';
|
||||
export { GitHubOrgEntityProvider } from './providers/GitHubOrgEntityProvider';
|
||||
export type { GitHubOrgEntityProviderOptions } from './providers/GitHubOrgEntityProvider';
|
||||
export type { GithubMultiOrgConfig } from './lib';
|
||||
|
||||
+2
-2
@@ -22,9 +22,9 @@ import {
|
||||
} from '@backstage/integration';
|
||||
import { LocationSpec } from '@backstage/plugin-catalog-backend';
|
||||
import { GithubDiscoveryProcessor, parseUrl } from './GithubDiscoveryProcessor';
|
||||
import { getOrganizationRepositories } from './lib';
|
||||
import { getOrganizationRepositories } from '../lib';
|
||||
|
||||
jest.mock('./lib');
|
||||
jest.mock('../lib');
|
||||
const mockGetOrganizationRepositories =
|
||||
getOrganizationRepositories as jest.MockedFunction<
|
||||
typeof getOrganizationRepositories
|
||||
+1
-1
@@ -29,7 +29,7 @@ import {
|
||||
} from '@backstage/plugin-catalog-backend';
|
||||
import { graphql } from '@octokit/graphql';
|
||||
import { Logger } from 'winston';
|
||||
import { getOrganizationRepositories } from './lib';
|
||||
import { getOrganizationRepositories } from '../lib';
|
||||
|
||||
/**
|
||||
* Extracts repositories out of a GitHub org.
|
||||
+1
-1
@@ -37,7 +37,7 @@ import {
|
||||
getOrganizationUsers,
|
||||
GithubMultiOrgConfig,
|
||||
readGithubMultiOrgConfig,
|
||||
} from './lib';
|
||||
} from '../lib';
|
||||
|
||||
/**
|
||||
* Extracts teams and users out of a multiple GitHub orgs namespaced per org.
|
||||
+1
-1
@@ -36,7 +36,7 @@ import {
|
||||
getOrganizationTeams,
|
||||
getOrganizationUsers,
|
||||
parseGitHubOrgUrl,
|
||||
} from './lib';
|
||||
} from '../lib';
|
||||
|
||||
type GraphQL = typeof graphql;
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* 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 { getVoidLogger } from '@backstage/backend-common';
|
||||
import { TaskInvocationDefinition, TaskRunner } from '@backstage/backend-tasks';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { EntityProviderConnection } from '@backstage/plugin-catalog-backend';
|
||||
import { GitHubEntityProvider } from './GitHubEntityProvider';
|
||||
import * as helpers from '../lib/github';
|
||||
|
||||
class PersistingTaskRunner implements TaskRunner {
|
||||
private tasks: TaskInvocationDefinition[] = [];
|
||||
|
||||
getTasks() {
|
||||
return this.tasks;
|
||||
}
|
||||
|
||||
run(task: TaskInvocationDefinition): Promise<void> {
|
||||
this.tasks.push(task);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
const logger = getVoidLogger();
|
||||
|
||||
describe('GitHubEntityProvider', () => {
|
||||
afterEach(() => jest.resetAllMocks());
|
||||
|
||||
it('no provider config', () => {
|
||||
const schedule = new PersistingTaskRunner();
|
||||
const config = new ConfigReader({});
|
||||
const providers = GitHubEntityProvider.fromConfig(config, {
|
||||
logger,
|
||||
schedule,
|
||||
});
|
||||
|
||||
expect(providers).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('single simple provider config', () => {
|
||||
const schedule = new PersistingTaskRunner();
|
||||
const config = new ConfigReader({
|
||||
catalog: {
|
||||
providers: {
|
||||
github: {
|
||||
organization: 'test-org',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const providers = GitHubEntityProvider.fromConfig(config, {
|
||||
logger,
|
||||
schedule,
|
||||
});
|
||||
|
||||
expect(providers).toHaveLength(1);
|
||||
expect(providers[0].getProviderName()).toEqual('github-provider:default');
|
||||
});
|
||||
|
||||
it('multiple provider configs', () => {
|
||||
const schedule = new PersistingTaskRunner();
|
||||
const config = new ConfigReader({
|
||||
catalog: {
|
||||
providers: {
|
||||
github: {
|
||||
myProvider: {
|
||||
organization: 'test-org1',
|
||||
},
|
||||
anotherProvider: {
|
||||
organization: 'test-org2',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const providers = GitHubEntityProvider.fromConfig(config, {
|
||||
logger,
|
||||
schedule,
|
||||
});
|
||||
|
||||
expect(providers).toHaveLength(2);
|
||||
expect(providers[0].getProviderName()).toEqual(
|
||||
'github-provider:myProvider',
|
||||
);
|
||||
expect(providers[1].getProviderName()).toEqual(
|
||||
'github-provider:anotherProvider',
|
||||
);
|
||||
});
|
||||
|
||||
it('apply full update on scheduled execution', async () => {
|
||||
const config = new ConfigReader({
|
||||
catalog: {
|
||||
providers: {
|
||||
github: {
|
||||
myProvider: {
|
||||
organization: 'test-org',
|
||||
catalogPath: 'custom/path/catalog-custom.yaml',
|
||||
filters: {
|
||||
branch: 'main',
|
||||
repository: 'test-.*',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const schedule = new PersistingTaskRunner();
|
||||
const entityProviderConnection: EntityProviderConnection = {
|
||||
applyMutation: jest.fn(),
|
||||
};
|
||||
|
||||
const provider = GitHubEntityProvider.fromConfig(config, {
|
||||
logger,
|
||||
schedule,
|
||||
})[0];
|
||||
|
||||
const mockGetOrganizationRepositories = jest.spyOn(
|
||||
helpers,
|
||||
'getOrganizationRepositories',
|
||||
);
|
||||
|
||||
mockGetOrganizationRepositories.mockReturnValue(
|
||||
Promise.resolve({
|
||||
repositories: [
|
||||
{
|
||||
name: 'test-repo',
|
||||
url: 'https://github.com/test-org/test-repo',
|
||||
isArchived: false,
|
||||
defaultBranchRef: {
|
||||
name: 'main',
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
await provider.connect(entityProviderConnection);
|
||||
|
||||
const taskDef = schedule.getTasks()[0];
|
||||
expect(taskDef.id).toEqual('github-provider:myProvider:refresh');
|
||||
await (taskDef.fn as () => Promise<void>)();
|
||||
|
||||
const url = `https://github.com/test-org/test-repo/blob/main/catalog-custom.yaml`;
|
||||
const expectedEntities = [
|
||||
{
|
||||
entity: {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Location',
|
||||
metadata: {
|
||||
annotations: {
|
||||
'backstage.io/managed-by-location': `url:${url}`,
|
||||
'backstage.io/managed-by-origin-location': `url:${url}`,
|
||||
},
|
||||
name: 'generated-21936a3d1e926b8bb3b00ac4398dc9a8dbb90b45',
|
||||
},
|
||||
spec: {
|
||||
presence: 'optional',
|
||||
target: `${url}`,
|
||||
type: 'url',
|
||||
},
|
||||
},
|
||||
locationKey: 'github-provider:myProvider',
|
||||
},
|
||||
];
|
||||
|
||||
expect(entityProviderConnection.applyMutation).toBeCalledTimes(1);
|
||||
expect(entityProviderConnection.applyMutation).toBeCalledWith({
|
||||
type: 'full',
|
||||
entities: expectedEntities,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,253 @@
|
||||
/*
|
||||
* Copyright 2022 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 { TaskRunner } from '@backstage/backend-tasks';
|
||||
import { Config } from '@backstage/config';
|
||||
import {
|
||||
GithubCredentialsProvider,
|
||||
ScmIntegrations,
|
||||
GitHubIntegrationConfig,
|
||||
GitHubIntegration,
|
||||
SingleInstanceGithubCredentialsProvider,
|
||||
} from '@backstage/integration';
|
||||
import {
|
||||
EntityProvider,
|
||||
EntityProviderConnection,
|
||||
LocationSpec,
|
||||
locationSpecToLocationEntity,
|
||||
} from '@backstage/plugin-catalog-backend';
|
||||
|
||||
import { graphql } from '@octokit/graphql';
|
||||
import * as uuid from 'uuid';
|
||||
import { Logger } from 'winston';
|
||||
import {
|
||||
readProviderConfigs,
|
||||
GitHubEntityProviderConfig,
|
||||
} from './GitHubEntityProviderConfig';
|
||||
import { getOrganizationRepositories, Repository } from '../lib/github';
|
||||
|
||||
/**
|
||||
* Discovers catalog files located in [GitHub](https://github.com).
|
||||
* The provider will search your GitHub account and register catalog files matching the configured path
|
||||
* as Location entity and via following processing steps add all contained catalog entities.
|
||||
* This can be useful as an alternative to static locations or manually adding things to the catalog.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class GitHubEntityProvider implements EntityProvider {
|
||||
private readonly config: GitHubEntityProviderConfig;
|
||||
private readonly logger: Logger;
|
||||
private readonly integration: GitHubIntegrationConfig;
|
||||
private readonly scheduleFn: () => Promise<void>;
|
||||
private connection?: EntityProviderConnection;
|
||||
private readonly githubCredentialsProvider: GithubCredentialsProvider;
|
||||
|
||||
static fromConfig(
|
||||
config: Config,
|
||||
options: {
|
||||
logger: Logger;
|
||||
schedule: TaskRunner;
|
||||
},
|
||||
): GitHubEntityProvider[] {
|
||||
const integrations = ScmIntegrations.fromConfig(config);
|
||||
const integration = integrations.github.byHost('github.com');
|
||||
|
||||
if (!integration) {
|
||||
throw new Error(
|
||||
`There is no GitHub config that matches github. Please add a configuration entry for it under integrations.github`,
|
||||
);
|
||||
}
|
||||
|
||||
return readProviderConfigs(config).map(
|
||||
providerConfig =>
|
||||
new GitHubEntityProvider(
|
||||
providerConfig,
|
||||
integration,
|
||||
options.logger,
|
||||
options.schedule,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
private constructor(
|
||||
config: GitHubEntityProviderConfig,
|
||||
integration: GitHubIntegration,
|
||||
logger: Logger,
|
||||
schedule: TaskRunner,
|
||||
) {
|
||||
this.config = config;
|
||||
this.integration = integration.config;
|
||||
this.logger = logger.child({
|
||||
target: this.getProviderName(),
|
||||
});
|
||||
this.scheduleFn = this.createScheduleFn(schedule);
|
||||
this.githubCredentialsProvider =
|
||||
SingleInstanceGithubCredentialsProvider.create(integration.config);
|
||||
this.scheduleFn = this.createScheduleFn(schedule);
|
||||
}
|
||||
|
||||
/** {@inheritdoc @backstage/plugin-catalog-backend#EntityProvider.getProviderName} */
|
||||
getProviderName(): string {
|
||||
return `github-provider:${this.config.id}`;
|
||||
}
|
||||
|
||||
/** {@inheritdoc @backstage/plugin-catalog-backend#EntityProvider.connect} */
|
||||
async connect(connection: EntityProviderConnection): Promise<void> {
|
||||
this.connection = connection;
|
||||
return await this.scheduleFn();
|
||||
}
|
||||
|
||||
private createScheduleFn(schedule: TaskRunner): () => Promise<void> {
|
||||
return async () => {
|
||||
const taskId = `${this.getProviderName()}:refresh`;
|
||||
return schedule.run({
|
||||
id: taskId,
|
||||
fn: async () => {
|
||||
const logger = this.logger.child({
|
||||
class: GitHubEntityProvider.prototype.constructor.name,
|
||||
taskId,
|
||||
taskInstanceId: uuid.v4(),
|
||||
});
|
||||
try {
|
||||
await this.refresh(logger);
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
async refresh(logger: Logger) {
|
||||
if (!this.connection) {
|
||||
throw new Error('Not initialized');
|
||||
}
|
||||
|
||||
const targets = await this.findCatalogFiles();
|
||||
const matchingTargets = this.matchesFilters(targets);
|
||||
const entities = matchingTargets
|
||||
.map(repository => this.createLocationUrl(repository))
|
||||
.map(GitHubEntityProvider.toLocationSpec)
|
||||
.map(location => {
|
||||
return {
|
||||
locationKey: this.getProviderName(),
|
||||
entity: locationSpecToLocationEntity({ location }),
|
||||
};
|
||||
});
|
||||
|
||||
await this.connection.applyMutation({
|
||||
type: 'full',
|
||||
entities,
|
||||
});
|
||||
|
||||
logger.info(
|
||||
`Read ${targets.length} GitHub repositories (${entities.length} matching the pattern)`,
|
||||
);
|
||||
}
|
||||
|
||||
// go to the server and get all of the repositories
|
||||
private async findCatalogFiles(): Promise<Repository[]> {
|
||||
const organization = this.config.organization;
|
||||
const host = this.integration.host;
|
||||
const orgUrl = `https://${host}/${organization}`;
|
||||
|
||||
const { headers } = await this.githubCredentialsProvider.getCredentials({
|
||||
url: orgUrl,
|
||||
});
|
||||
|
||||
const client = graphql.defaults({
|
||||
baseUrl: this.integration.apiBaseUrl,
|
||||
headers,
|
||||
});
|
||||
|
||||
const { repositories } = await getOrganizationRepositories(
|
||||
client,
|
||||
organization,
|
||||
);
|
||||
|
||||
return repositories;
|
||||
}
|
||||
|
||||
private matchesFilters(repositories: Repository[]) {
|
||||
const repositoryFilter = this.config.filters?.repository;
|
||||
|
||||
const matchingRepositories = repositories.filter(r => {
|
||||
return !r.isArchived && repositoryFilter
|
||||
? repositoryFilter?.test(r.name)
|
||||
: true && r.defaultBranchRef?.name;
|
||||
});
|
||||
return matchingRepositories;
|
||||
}
|
||||
|
||||
private createLocationUrl(repository: Repository): string {
|
||||
const branch =
|
||||
this.config.filters?.branch || repository.defaultBranchRef?.name || '-';
|
||||
const catalogFile = this.config.catalogPath.substring(
|
||||
this.config.catalogPath.lastIndexOf('/') + 1,
|
||||
);
|
||||
return `${repository.url}/blob/${branch}/${catalogFile}`;
|
||||
}
|
||||
|
||||
private static toLocationSpec(target: string): LocationSpec {
|
||||
return {
|
||||
type: 'url',
|
||||
target: target,
|
||||
presence: 'optional',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Helpers
|
||||
*/
|
||||
|
||||
export function parseUrl(urlString: string): {
|
||||
org: string;
|
||||
repoSearchPath: RegExp;
|
||||
catalogPath: string;
|
||||
branch: string;
|
||||
host: string;
|
||||
} {
|
||||
const url = new URL(urlString);
|
||||
const path = url.pathname.substr(1).split('/');
|
||||
|
||||
// /backstage/techdocs-*/blob/master/catalog-info.yaml
|
||||
// can also be
|
||||
// /backstage
|
||||
if (path.length > 2 && path[0].length && path[1].length) {
|
||||
return {
|
||||
org: decodeURIComponent(path[0]),
|
||||
repoSearchPath: escapeRegExp(decodeURIComponent(path[1])),
|
||||
catalogPath: `/${decodeURIComponent(path.slice(4).join('/'))}`,
|
||||
branch: decodeURIComponent(path[3]),
|
||||
host: url.host,
|
||||
};
|
||||
} else if (path.length === 1 && path[0].length) {
|
||||
return {
|
||||
org: decodeURIComponent(path[0]),
|
||||
repoSearchPath: escapeRegExp('*'),
|
||||
catalogPath: '/catalog-info.yaml',
|
||||
branch: '-',
|
||||
host: url.host,
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error(`Failed to parse ${urlString}`);
|
||||
}
|
||||
|
||||
export function escapeRegExp(str: string): RegExp {
|
||||
return new RegExp(`^${str.replace(/\*/g, '.*')}$`);
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright 2022 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 { ConfigReader } from '@backstage/config';
|
||||
import { readProviderConfigs } from './GitHubEntityProviderConfig';
|
||||
|
||||
describe('readProviderConfigs', () => {
|
||||
afterEach(() => jest.resetAllMocks());
|
||||
|
||||
it('no provider config', () => {
|
||||
const config = new ConfigReader({});
|
||||
const providerConfigs = readProviderConfigs(config);
|
||||
|
||||
expect(providerConfigs).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('single simple provider config', () => {
|
||||
const config = new ConfigReader({
|
||||
catalog: {
|
||||
providers: {
|
||||
github: {
|
||||
organization: 'test-org',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const providerConfigs = readProviderConfigs(config);
|
||||
|
||||
expect(providerConfigs).toHaveLength(1);
|
||||
expect(providerConfigs[0].id).toEqual('default');
|
||||
expect(providerConfigs[0].organization).toEqual('test-org');
|
||||
});
|
||||
|
||||
it('multiple provider configs', () => {
|
||||
const config = new ConfigReader({
|
||||
catalog: {
|
||||
providers: {
|
||||
github: {
|
||||
providerOrganizationOnly: {
|
||||
organization: 'test-org1',
|
||||
},
|
||||
providerCustomCatalogPath: {
|
||||
organization: 'test-org2',
|
||||
catalogPath: 'custom/path/catalog-info.yaml',
|
||||
},
|
||||
providerWithRepositoryFilter: {
|
||||
organization: 'test-org3',
|
||||
filters: {
|
||||
repository: 'repository.*filter',
|
||||
},
|
||||
},
|
||||
providerWithBranchFilter: {
|
||||
organization: 'test-org4',
|
||||
filters: {
|
||||
branch: 'branch-name',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const providerConfigs = readProviderConfigs(config);
|
||||
|
||||
expect(providerConfigs).toHaveLength(4);
|
||||
expect(providerConfigs[0]).toEqual({
|
||||
id: 'providerOrganizationOnly',
|
||||
organization: 'test-org1',
|
||||
catalogPath: '/catalog-info.yaml',
|
||||
filters: {
|
||||
repository: undefined,
|
||||
branch: undefined,
|
||||
},
|
||||
});
|
||||
expect(providerConfigs[1]).toEqual({
|
||||
id: 'providerCustomCatalogPath',
|
||||
organization: 'test-org2',
|
||||
catalogPath: 'custom/path/catalog-info.yaml',
|
||||
filters: {
|
||||
repository: undefined,
|
||||
branch: undefined,
|
||||
},
|
||||
});
|
||||
expect(providerConfigs[2]).toEqual({
|
||||
id: 'providerWithRepositoryFilter',
|
||||
organization: 'test-org3', // organization
|
||||
catalogPath: '/catalog-info.yaml', // file
|
||||
filters: {
|
||||
repository: /^repository.*filter$/, // repo
|
||||
branch: undefined, // branch
|
||||
},
|
||||
});
|
||||
expect(providerConfigs[3]).toEqual({
|
||||
id: 'providerWithBranchFilter',
|
||||
organization: 'test-org4',
|
||||
catalogPath: '/catalog-info.yaml',
|
||||
filters: {
|
||||
repository: undefined,
|
||||
branch: 'branch-name',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 2022 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 { Config } from '@backstage/config';
|
||||
|
||||
const DEFAULT_CATALOG_PATH = '/catalog-info.yaml';
|
||||
const DEFAULT_PROVIDER_ID = 'default';
|
||||
|
||||
export type GitHubEntityProviderConfig = {
|
||||
id: string;
|
||||
catalogPath: string;
|
||||
organization: string;
|
||||
filters?: {
|
||||
repository?: RegExp;
|
||||
branch?: string;
|
||||
};
|
||||
};
|
||||
|
||||
export function readProviderConfigs(
|
||||
config: Config,
|
||||
): GitHubEntityProviderConfig[] {
|
||||
const providersConfig = config.getOptionalConfig('catalog.providers.github');
|
||||
if (!providersConfig) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (providersConfig.has('organization')) {
|
||||
// simple/single config variant
|
||||
return [readProviderConfig(DEFAULT_PROVIDER_ID, providersConfig)];
|
||||
}
|
||||
|
||||
return providersConfig.keys().map(id => {
|
||||
const providerConfig = providersConfig.getConfig(id);
|
||||
|
||||
return readProviderConfig(id, providerConfig);
|
||||
});
|
||||
}
|
||||
|
||||
function readProviderConfig(
|
||||
id: string,
|
||||
config: Config,
|
||||
): GitHubEntityProviderConfig {
|
||||
const organization = config.getString('organization');
|
||||
const catalogPath =
|
||||
config.getOptionalString('catalogPath') ?? DEFAULT_CATALOG_PATH;
|
||||
const repositoryPattern = config.getOptionalString('filters.repository');
|
||||
const branchPattern = config.getOptionalString('filters.branch');
|
||||
|
||||
return {
|
||||
id,
|
||||
catalogPath,
|
||||
organization,
|
||||
filters: {
|
||||
repository: repositoryPattern
|
||||
? compileRegExp(repositoryPattern)
|
||||
: undefined,
|
||||
branch: branchPattern || undefined,
|
||||
},
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Compiles a RegExp while enforcing the pattern to contain
|
||||
* the start-of-line and end-of-line anchors.
|
||||
*
|
||||
* @param pattern
|
||||
*/
|
||||
function compileRegExp(pattern: string): RegExp {
|
||||
let fullLinePattern = pattern;
|
||||
if (!fullLinePattern.startsWith('^')) {
|
||||
fullLinePattern = `^${fullLinePattern}`;
|
||||
}
|
||||
if (!fullLinePattern.endsWith('$')) {
|
||||
fullLinePattern = `${fullLinePattern}$`;
|
||||
}
|
||||
|
||||
return new RegExp(fullLinePattern);
|
||||
}
|
||||
+1
-1
@@ -42,7 +42,7 @@ import {
|
||||
getOrganizationTeams,
|
||||
getOrganizationUsers,
|
||||
parseGitHubOrgUrl,
|
||||
} from './lib';
|
||||
} from '../lib';
|
||||
|
||||
/**
|
||||
* Options for {@link GitHubOrgEntityProvider}.
|
||||
@@ -35,4 +35,5 @@ export type GitlabProviderConfig = {
|
||||
id: string;
|
||||
branch: string;
|
||||
catalogFile: string;
|
||||
projectPattern: RegExp;
|
||||
};
|
||||
|
||||
+109
@@ -227,4 +227,113 @@ describe('GitlabDiscoveryEntityProvider', () => {
|
||||
entities: expectedEntities,
|
||||
});
|
||||
});
|
||||
|
||||
it('should filter found projects based on a provided project pattern', async () => {
|
||||
const config = new ConfigReader({
|
||||
integrations: {
|
||||
gitlab: [
|
||||
{
|
||||
host: 'test-gitlab',
|
||||
apiBaseUrl: 'https://api.gitlab.example/api/v4',
|
||||
token: '1234',
|
||||
},
|
||||
],
|
||||
},
|
||||
catalog: {
|
||||
providers: {
|
||||
gitlab: {
|
||||
'test-id': {
|
||||
host: 'test-gitlab',
|
||||
projectPattern: 'john/',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const schedule = new PersistingTaskRunner();
|
||||
const entityProviderConnection: EntityProviderConnection = {
|
||||
applyMutation: jest.fn(),
|
||||
};
|
||||
const provider = GitlabDiscoveryEntityProvider.fromConfig(config, {
|
||||
logger,
|
||||
schedule,
|
||||
})[0];
|
||||
|
||||
server.use(
|
||||
rest.get(
|
||||
`https://api.gitlab.example/api/v4/projects`,
|
||||
(_req, res, ctx) => {
|
||||
const response = [
|
||||
{
|
||||
id: 123,
|
||||
default_branch: 'master',
|
||||
archived: false,
|
||||
last_activity_at: new Date().toString(),
|
||||
web_url: 'https://api.gitlab.example/test-group/test-repo',
|
||||
path_with_namespace: 'test-group/test-repo',
|
||||
},
|
||||
{
|
||||
id: 124,
|
||||
default_branch: 'master',
|
||||
archived: false,
|
||||
last_activity_at: new Date().toString(),
|
||||
web_url: 'https://api.gitlab.example/john/example',
|
||||
path_with_namespace: 'john/example',
|
||||
},
|
||||
];
|
||||
return res(ctx.json(response));
|
||||
},
|
||||
),
|
||||
rest.head(
|
||||
'https://api.gitlab.example/api/v4/projects/test-group%2Ftest-repo/repository/files/catalog-info.yaml',
|
||||
(req, res, ctx) => {
|
||||
if (req.url.searchParams.get('ref') === 'master') {
|
||||
return res(ctx.status(200));
|
||||
}
|
||||
return res(ctx.status(404, 'Not Found'));
|
||||
},
|
||||
),
|
||||
rest.head(
|
||||
'https://api.gitlab.example/api/v4/projects/john%2Fexample/repository/files/catalog-info.yaml',
|
||||
(req, res, ctx) => {
|
||||
if (req.url.searchParams.get('ref') === 'master') {
|
||||
return res(ctx.status(200));
|
||||
}
|
||||
return res(ctx.status(404, 'Not Found'));
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
await provider.connect(entityProviderConnection);
|
||||
|
||||
await provider.refresh(logger);
|
||||
|
||||
expect(entityProviderConnection.applyMutation).toBeCalledWith({
|
||||
type: 'full',
|
||||
entities: [
|
||||
{
|
||||
entity: {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Location',
|
||||
metadata: {
|
||||
annotations: {
|
||||
'backstage.io/managed-by-location':
|
||||
'url:https://api.gitlab.example/john/example/-/blob/master/catalog-info.yaml',
|
||||
'backstage.io/managed-by-origin-location':
|
||||
'url:https://api.gitlab.example/john/example/-/blob/master/catalog-info.yaml',
|
||||
},
|
||||
name: 'generated-2045212e5b3e9e6bacf51cec709e362282e3cda9',
|
||||
},
|
||||
spec: {
|
||||
presence: 'optional',
|
||||
target:
|
||||
'https://api.gitlab.example/john/example/-/blob/master/catalog-info.yaml',
|
||||
type: 'url',
|
||||
},
|
||||
},
|
||||
locationKey: 'GitlabDiscoveryEntityProvider:test-id',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -147,6 +147,10 @@ export class GitlabDiscoveryEntityProvider implements EntityProvider {
|
||||
};
|
||||
|
||||
for await (const project of projects) {
|
||||
if (!this.config.projectPattern.test(project.path_with_namespace ?? '')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
res.scanned++;
|
||||
|
||||
if (project.archived) {
|
||||
|
||||
@@ -52,6 +52,7 @@ describe('config', () => {
|
||||
branch: 'master',
|
||||
host: 'host',
|
||||
catalogFile: 'catalog-info.yaml',
|
||||
projectPattern: /[\s\S]*/,
|
||||
}),
|
||||
);
|
||||
});
|
||||
@@ -81,6 +82,7 @@ describe('config', () => {
|
||||
branch: 'not-master',
|
||||
host: 'host',
|
||||
catalogFile: 'custom-file.yaml',
|
||||
projectPattern: /[\s\S]*/,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -15,13 +15,14 @@
|
||||
*/
|
||||
|
||||
import { Config } from '@backstage/config';
|
||||
import { GitlabProviderConfig } from '../lib/types';
|
||||
import { GitlabProviderConfig } from '../lib';
|
||||
|
||||
/**
|
||||
* Extracts the gitlab config from a config object
|
||||
*
|
||||
* @public
|
||||
*
|
||||
* @param id - The provider key
|
||||
* @param config - The config object to extract from
|
||||
*/
|
||||
function readGitlabConfig(id: string, config: Config): GitlabProviderConfig {
|
||||
@@ -30,6 +31,9 @@ function readGitlabConfig(id: string, config: Config): GitlabProviderConfig {
|
||||
const branch = config.getOptionalString('branch') ?? 'master';
|
||||
const catalogFile =
|
||||
config.getOptionalString('entityFilename') ?? 'catalog-info.yaml';
|
||||
const projectPattern = new RegExp(
|
||||
config.getOptionalString('projectPattern') ?? /[\s\S]*/,
|
||||
);
|
||||
|
||||
return {
|
||||
id,
|
||||
@@ -37,6 +41,7 @@ function readGitlabConfig(id: string, config: Config): GitlabProviderConfig {
|
||||
branch,
|
||||
host,
|
||||
catalogFile,
|
||||
projectPattern,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
# @backstage/plugin-catalog-backend
|
||||
|
||||
## 1.3.1-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- e3d3018531: Fix issue for conditional decisions based on properties stored as arrays, like tags.
|
||||
|
||||
Before this change, having a permission policy returning conditional decisions based on metadata like tags, such like `createCatalogConditionalDecision(permission, catalogConditions.hasMetadata('tags', 'java'),)`, was producing wrong results. The issue occurred when authorizing entities already loaded from the database, for example when authorizing `catalogEntityDeletePermission`.
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/plugin-catalog-common@1.0.5-next.0
|
||||
- @backstage/backend-common@0.15.0-next.1
|
||||
- @backstage/integration@1.3.0-next.1
|
||||
|
||||
## 1.3.1-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/plugin-catalog-backend",
|
||||
"description": "The Backstage backend plugin that provides the Backstage catalog",
|
||||
"version": "1.3.1-next.0",
|
||||
"version": "1.3.1-next.1",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -36,13 +36,13 @@
|
||||
"dependencies": {
|
||||
"@backstage/backend-plugin-api": "^0.1.1-next.0",
|
||||
"@backstage/plugin-catalog-node": "^1.0.1-next.0",
|
||||
"@backstage/backend-common": "^0.15.0-next.0",
|
||||
"@backstage/backend-common": "^0.15.0-next.1",
|
||||
"@backstage/catalog-client": "^1.0.4",
|
||||
"@backstage/catalog-model": "^1.1.0",
|
||||
"@backstage/config": "^1.0.1",
|
||||
"@backstage/errors": "^1.1.0",
|
||||
"@backstage/integration": "^1.3.0-next.0",
|
||||
"@backstage/plugin-catalog-common": "^1.0.4",
|
||||
"@backstage/integration": "^1.3.0-next.1",
|
||||
"@backstage/plugin-catalog-common": "^1.0.5-next.0",
|
||||
"@backstage/plugin-permission-common": "^0.6.3",
|
||||
"@backstage/plugin-permission-node": "^0.6.4-next.0",
|
||||
"@backstage/plugin-scaffolder-common": "^1.1.2",
|
||||
|
||||
@@ -46,6 +46,22 @@ describe('createPropertyRule', () => {
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false when specified key is an empty array', () => {
|
||||
expect(
|
||||
apply(
|
||||
{
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Component',
|
||||
metadata: {
|
||||
name: 'test-component',
|
||||
tags: [],
|
||||
},
|
||||
},
|
||||
'tags',
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('returns true when specified key is present', () => {
|
||||
expect(
|
||||
apply(
|
||||
@@ -63,6 +79,22 @@ describe('createPropertyRule', () => {
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('returns true when specified key is an array containing more than an element', () => {
|
||||
expect(
|
||||
apply(
|
||||
{
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Component',
|
||||
metadata: {
|
||||
name: 'test-component',
|
||||
tags: ['java'],
|
||||
},
|
||||
},
|
||||
'tags',
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('key and value', () => {
|
||||
@@ -101,6 +133,23 @@ describe('createPropertyRule', () => {
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it(`returns false when key is an array and doesn't contain the specified value`, () => {
|
||||
expect(
|
||||
apply(
|
||||
{
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Component',
|
||||
metadata: {
|
||||
name: 'test-component',
|
||||
tags: ['java'],
|
||||
},
|
||||
},
|
||||
'tags',
|
||||
'python',
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('returns true when specified key and value is present', () => {
|
||||
expect(
|
||||
apply(
|
||||
@@ -119,6 +168,23 @@ describe('createPropertyRule', () => {
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it(`returns true when key is an array and contains the specified value`, () => {
|
||||
expect(
|
||||
apply(
|
||||
{
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Component',
|
||||
metadata: {
|
||||
name: 'test-component',
|
||||
tags: ['java', 'java11'],
|
||||
},
|
||||
},
|
||||
'tags',
|
||||
'java',
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -26,6 +26,13 @@ export const createPropertyRule = (propertyType: 'metadata' | 'spec') =>
|
||||
resourceType: RESOURCE_TYPE_CATALOG_ENTITY,
|
||||
apply: (resource: Entity, key: string, value?: string) => {
|
||||
const foundValue = get(resource[propertyType], key);
|
||||
|
||||
if (Array.isArray(foundValue)) {
|
||||
if (value !== undefined) {
|
||||
return foundValue.includes(value);
|
||||
}
|
||||
return foundValue.length > 0;
|
||||
}
|
||||
if (value !== undefined) {
|
||||
return value === foundValue;
|
||||
}
|
||||
|
||||
@@ -91,7 +91,10 @@ import {
|
||||
} from '@backstage/plugin-permission-node';
|
||||
import { AuthorizedEntitiesCatalog } from './AuthorizedEntitiesCatalog';
|
||||
import { basicEntityFilter } from './request/basicEntityFilter';
|
||||
import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common';
|
||||
import {
|
||||
catalogPermissions,
|
||||
RESOURCE_TYPE_CATALOG_ENTITY,
|
||||
} from '@backstage/plugin-catalog-common';
|
||||
import { AuthorizedLocationService } from './AuthorizedLocationService';
|
||||
|
||||
/** @public */
|
||||
@@ -435,6 +438,7 @@ export class CatalogBuilder {
|
||||
entitiesByRef[stringifyEntityRef(parseEntityRef(resourceRef))],
|
||||
);
|
||||
},
|
||||
permissions: catalogPermissions,
|
||||
rules: this.permissionRules,
|
||||
});
|
||||
const stitcher = new Stitcher(dbClient, logger);
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @backstage/plugin-catalog-common
|
||||
|
||||
## 1.0.5-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 92103db537: Export aggregated list of all catalog permissions
|
||||
|
||||
## 1.0.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -49,6 +49,12 @@ export const catalogLocationDeletePermission: BasicPermission;
|
||||
// @alpha
|
||||
export const catalogLocationReadPermission: BasicPermission;
|
||||
|
||||
// @alpha
|
||||
export const catalogPermissions: (
|
||||
| BasicPermission
|
||||
| ResourcePermission<'catalog-entity'>
|
||||
)[];
|
||||
|
||||
// @alpha
|
||||
export const RESOURCE_TYPE_CATALOG_ENTITY = 'catalog-entity';
|
||||
```
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/plugin-catalog-common",
|
||||
"description": "Common functionalities for the catalog plugin",
|
||||
"version": "1.0.4",
|
||||
"version": "1.0.5-next.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -30,6 +30,7 @@ export {
|
||||
catalogLocationReadPermission,
|
||||
catalogLocationCreatePermission,
|
||||
catalogLocationDeletePermission,
|
||||
catalogPermissions,
|
||||
} from './permissions';
|
||||
export type { CatalogEntityPermission } from './permissions';
|
||||
|
||||
|
||||
@@ -129,3 +129,17 @@ export const catalogLocationDeletePermission = createPermission({
|
||||
action: 'delete',
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* List of all catalog permissions.
|
||||
* @alpha
|
||||
*/
|
||||
export const catalogPermissions = [
|
||||
catalogEntityReadPermission,
|
||||
catalogEntityCreatePermission,
|
||||
catalogEntityDeletePermission,
|
||||
catalogEntityRefreshPermission,
|
||||
catalogLocationReadPermission,
|
||||
catalogLocationCreatePermission,
|
||||
catalogLocationDeletePermission,
|
||||
];
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"dependencies": {
|
||||
"@backstage/catalog-client": "^1.0.4",
|
||||
"@backstage/catalog-model": "^1.1.0",
|
||||
"@backstage/core-components": "^0.10.1-next.0",
|
||||
"@backstage/core-components": "^0.10.1-next.1",
|
||||
"@backstage/core-plugin-api": "^1.0.5-next.0",
|
||||
"@backstage/plugin-catalog-react": "^1.1.3-next.0",
|
||||
"@backstage/theme": "^0.2.16",
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
"@backstage/catalog-client": "^1.0.4",
|
||||
"@backstage/catalog-model": "^1.1.0",
|
||||
"@backstage/config": "^1.0.1",
|
||||
"@backstage/core-components": "^0.10.1-next.0",
|
||||
"@backstage/core-components": "^0.10.1-next.1",
|
||||
"@backstage/core-plugin-api": "^1.0.5-next.0",
|
||||
"@backstage/errors": "^1.1.0",
|
||||
"@backstage/integration": "^1.3.0-next.0",
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
# @backstage/plugin-catalog-react
|
||||
|
||||
## 1.1.3-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/core-components@0.10.1-next.1
|
||||
- @backstage/plugin-catalog-common@1.0.5-next.0
|
||||
- @backstage/integration@1.3.0-next.1
|
||||
|
||||
## 1.1.3-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/plugin-catalog-react",
|
||||
"description": "A frontend library that helps other Backstage plugins interact with the catalog",
|
||||
"version": "1.1.3-next.0",
|
||||
"version": "1.1.3-next.1",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -36,11 +36,11 @@
|
||||
"dependencies": {
|
||||
"@backstage/catalog-client": "^1.0.4",
|
||||
"@backstage/catalog-model": "^1.1.0",
|
||||
"@backstage/core-components": "^0.10.1-next.0",
|
||||
"@backstage/core-components": "^0.10.1-next.1",
|
||||
"@backstage/core-plugin-api": "^1.0.5-next.0",
|
||||
"@backstage/errors": "^1.1.0",
|
||||
"@backstage/integration": "^1.3.0-next.0",
|
||||
"@backstage/plugin-catalog-common": "^1.0.4",
|
||||
"@backstage/integration": "^1.3.0-next.1",
|
||||
"@backstage/plugin-catalog-common": "^1.0.5-next.0",
|
||||
"@backstage/plugin-permission-common": "^0.6.3",
|
||||
"@backstage/plugin-permission-react": "^0.4.4-next.0",
|
||||
"@backstage/theme": "^0.2.16",
|
||||
@@ -65,7 +65,7 @@
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.18.1-next.0",
|
||||
"@backstage/core-app-api": "^1.0.5-next.0",
|
||||
"@backstage/plugin-catalog-common": "^1.0.4",
|
||||
"@backstage/plugin-catalog-common": "^1.0.5-next.0",
|
||||
"@backstage/plugin-scaffolder-common": "^1.1.2",
|
||||
"@backstage/test-utils": "^1.1.3-next.0",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
# @backstage/plugin-catalog
|
||||
|
||||
## 1.5.0-next.1
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- fe94398418: Allow changing the subtitle of the `CatalogTable` component
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/core-components@0.10.1-next.1
|
||||
- @backstage/plugin-catalog-common@1.0.5-next.0
|
||||
- @backstage/plugin-catalog-react@1.1.3-next.1
|
||||
|
||||
## 1.5.0-next.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/plugin-catalog",
|
||||
"description": "The Backstage plugin for browsing the Backstage catalog",
|
||||
"version": "1.5.0-next.0",
|
||||
"version": "1.5.0-next.1",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -36,12 +36,12 @@
|
||||
"dependencies": {
|
||||
"@backstage/catalog-client": "^1.0.4",
|
||||
"@backstage/catalog-model": "^1.1.0",
|
||||
"@backstage/core-components": "^0.10.1-next.0",
|
||||
"@backstage/core-components": "^0.10.1-next.1",
|
||||
"@backstage/core-plugin-api": "^1.0.5-next.0",
|
||||
"@backstage/errors": "^1.1.0",
|
||||
"@backstage/integration-react": "^1.1.3-next.0",
|
||||
"@backstage/plugin-catalog-common": "^1.0.4",
|
||||
"@backstage/plugin-catalog-react": "^1.1.3-next.0",
|
||||
"@backstage/plugin-catalog-common": "^1.0.5-next.0",
|
||||
"@backstage/plugin-catalog-react": "^1.1.3-next.1",
|
||||
"@backstage/plugin-search-common": "^1.0.0",
|
||||
"@backstage/plugin-search-react": "^1.0.1-next.0",
|
||||
"@backstage/theme": "^0.2.16",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user