Merge remote-tracking branch 'origin/master' into task-action-idempotency
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-stack-overflow': patch
|
||||
---
|
||||
|
||||
fix: fix decode issues in title and author fields in `StackOverflowSearchResultListItem`
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend-module-github': patch
|
||||
'@backstage/backend-app-api': patch
|
||||
'@backstage/backend-common': patch
|
||||
'@backstage/plugin-catalog-backend': patch
|
||||
'@backstage/eslint-plugin': patch
|
||||
'@backstage/plugin-auth-backend': patch
|
||||
'@backstage/repo-tools': patch
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
Bump dependency `minimatch` to v9
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-common': patch
|
||||
---
|
||||
|
||||
Fix issue where `resolveSafeChildPath` path would incorrectly resolve when operating on a symlink
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/plugin-devtools': patch
|
||||
'@backstage/plugin-linguist': patch
|
||||
---
|
||||
|
||||
Updated to use `fetchApi` as per [ADR013](https://backstage.io/docs/architecture-decisions/adrs-adr013)
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-tech-radar': patch
|
||||
---
|
||||
|
||||
Fixed an issue with the "moved in direction" table header cell getting squished and becoming unreadable if a timeline description is too long
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend-module-github': patch
|
||||
---
|
||||
|
||||
Updated dependency `octokit-plugin-create-pull-request` to `^5.0.0`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-react': patch
|
||||
---
|
||||
|
||||
Updated dependency `flatted` to `3.3.1`.
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
'@backstage/plugin-home-react': patch
|
||||
'@backstage/plugin-home': patch
|
||||
'@backstage/plugin-scaffolder-react': patch
|
||||
'@backstage/plugin-scaffolder': patch
|
||||
---
|
||||
|
||||
Updated dependency `@rjsf/utils` to `5.17.1`.
|
||||
Updated dependency `@rjsf/core` to `5.17.1`.
|
||||
Updated dependency `@rjsf/material-ui` to `5.17.1`.
|
||||
Updated dependency `@rjsf/validator-ajv8` to `5.17.1`.
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/plugin-azure-devops': minor
|
||||
---
|
||||
|
||||
**BREAKING** The `AzureDevOpsClient` no longer requires `identityAPi` but now requires `fetchApi`.
|
||||
|
||||
Updated to use `fetchApi` as per [ADR013](https://backstage.io/docs/architecture-decisions/adrs-adr013)
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-auth-backend': patch
|
||||
---
|
||||
|
||||
Internal refactor to break out how the router is constructed
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/plugin-kubernetes-common': patch
|
||||
'@backstage/plugin-kubernetes-react': patch
|
||||
---
|
||||
|
||||
Add support to fetch data for Daemon Sets and display an accordion in the same way as with Deployments
|
||||
+5
-2
@@ -23,8 +23,11 @@ module.exports = {
|
||||
'notice/notice': [
|
||||
'error',
|
||||
{
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
templateFile: path.resolve(__dirname, './scripts/copyright-header.txt'),
|
||||
templateFile: path.resolve(
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
__dirname,
|
||||
'./scripts/templates/copyright-header.txt'
|
||||
),
|
||||
onNonMatchingHeader: 'replace',
|
||||
},
|
||||
],
|
||||
|
||||
@@ -11,6 +11,7 @@ cli-report.md
|
||||
plugins/scaffolder-backend/sample-templates
|
||||
.vscode
|
||||
dist-types
|
||||
.eslintrc.js
|
||||
|
||||
# reduce the barrier for adopters to add themselves
|
||||
ADOPTERS.md
|
||||
|
||||
@@ -107,6 +107,20 @@ Scope: The TechDocs plugin and related tooling
|
||||
|
||||
These incubating project areas have shared ownership with @backstage/maintainers.
|
||||
|
||||
### Community Plugins
|
||||
|
||||
Team: @backstage/community-plugins-maintainers
|
||||
|
||||
Scope: Tooling and Community Repo Maintainers for the Backstage [Community Plugins repository](https://github.com/backstage/community-plugins)
|
||||
|
||||
| Name | Organization | GitHub | Discord |
|
||||
| -------------------- | ------------ | ------------------------------------------- | ------------ |
|
||||
| Bethany Griggs | Red Hat | [BethGriggs](https://github.com/BethGriggs) | `bethgriggs` |
|
||||
| Tomas Kral | Red Hat | [kadel](https://github.com/kadel) | `tomkral` |
|
||||
| André Wanlin | Spotify | [awanlin](https://github.com/awanlin) | `ahhhndre` |
|
||||
| Philipp Hugenroth | Spotify | [tudi2d](https://github.com/tudi2d) | `tudi2d` |
|
||||
| Vincenzo Scamporlino | Spotify | [vinzscam](https://github.com/vinzscam) | `vinzscam` |
|
||||
|
||||
### OpenAPI Tooling
|
||||
|
||||
Team: @backstage/openapi-tooling-maintainers
|
||||
|
||||
@@ -96,7 +96,6 @@ The notification backend stores notification using the [database service](https:
|
||||
|
||||
- ID
|
||||
- Read date
|
||||
- Done date
|
||||
- Saved status
|
||||
- Creation date
|
||||
- Updated date (optional, for scoped notifications)
|
||||
@@ -104,7 +103,8 @@ The notification backend stores notification using the [database service](https:
|
||||
- Title
|
||||
- Description (optional)
|
||||
- Origin
|
||||
- Link
|
||||
- Link (optional)
|
||||
- Additional links (optional)
|
||||
- Recipients
|
||||
- Severity (optional, default normal)
|
||||
- Topic (optional)
|
||||
@@ -113,6 +113,10 @@ The notification backend stores notification using the [database service](https:
|
||||
|
||||
The recipients is **not** a list of users, but rather a filter that describes who should receive the notification. It must be possible to evaluate this filter in a database query, so that we can efficiently fetch all notifications for a given user. The same filter will also be used by the signal backend to determine which users should receive a signal.
|
||||
|
||||
The read date is a timestamp of marking the notifications as read by the user. If missing, the notification is still unread.
|
||||
|
||||
The saved status is a timestamp indicating when/if the user marked the notification for better visibility in the future (to "pin" the message). Can be undefined.
|
||||
|
||||
The title is mandatory human-readable text shortly describing the notifications.
|
||||
|
||||
The description is an optional human-readable text providing more details to the user.
|
||||
@@ -143,6 +147,8 @@ The link is a relative or absolute URL. As an example, it can be used:
|
||||
- by an external system to request an action within an asynchronous task
|
||||
- by a BE plugin to provide link to other part of the Backstage UI (i.e. to the Catalog)
|
||||
|
||||
The additional links are an array of title-URL pairs. They can represent immediate actions on the notification (i.e. yes-no) or lead the user to additional details.
|
||||
|
||||
The `notification-backend` does not provide any new permissions, since creating notifications can only be done by other backend plugins, while reading notifications can only be done by the authenticated user. It is possible that we want to add a permissions for reading notifications, in particular for admin and impersonation use cases, but that is not part of this proposal or the initial implementation.
|
||||
|
||||
The `notification-backend` shall provide necessary parameters for paging and filtering notifications from the frontend.
|
||||
@@ -228,7 +234,7 @@ interface NotificationService {
|
||||
|
||||
Each notification is always routed to individual users unless it's a broadcast. The `notification-backend` will figure out which users will receive a notification based on the `recipients` parameter, resolving it to the concrete list of users at the time of sending the notification.
|
||||
|
||||
Each notification contains a `title` and a `link` for user to see further information. The `created`, `id`, `read` and `saved` properties are handled by the backend based and cannot be changed during the notification creation.
|
||||
Each notification contains a human readable `title`, `origin` and optionally `link` for additional details. The `created`, `id`, `read` and `saved` properties are handled by the backend based and cannot be passed during the notification creation.
|
||||
|
||||
Calling `sendNotification` should never throw an error so that it doesn't block the current processing. Notifications should be considered as second-level citizens that are not critical if not delivered.
|
||||
|
||||
@@ -273,37 +279,31 @@ Example signal payload for a new notification:
|
||||
|
||||
Notification frontend shows users their own notifications in its own page and the number of unread notifications in the main menu item.
|
||||
|
||||
By default, notifications that are `undone` will be shown in the notifications view. The notification `read` status is indicated by the UI.
|
||||
Notifications are set to `read` when the `Mark as read` action is triggered by the user (bulk or single).
|
||||
|
||||
Notifications are set to `read` when the notification link is opened or the notification is set as `done` by the user.
|
||||
Notifications can be saved for better visibility in the future.
|
||||
|
||||
Notifications can be set to `done` by the user, and they are filtered out of the main view.
|
||||
Notifications can be filtered by `read`, `saved`, `content` (text search in title or description), `created` (since multiple predefined options) and `severity`.
|
||||
|
||||
Notifications can be saved for later use by the user.
|
||||
|
||||
Notifications can be filtered by their `read`, `done` and `saved` statuses.
|
||||
|
||||
Notifications are displayed in pages.
|
||||
|
||||
User can search for notifications based on their title and description.
|
||||
Notifications are displayed in pages. To do so, the backend needs to implement filtering and sorting.
|
||||
|
||||
The following frontend API is added as part of this proposal.
|
||||
|
||||
#### `NotificationsApi`
|
||||
|
||||
```ts
|
||||
export type NotificationType = 'undone' | 'done' | 'saved';
|
||||
export type NotificationSeverity = 'critical' | 'high' | 'normal' | 'low';
|
||||
|
||||
export type GetNotificationsOptions = {
|
||||
type?: NotificationType;
|
||||
offset?: number;
|
||||
limit?: number;
|
||||
search?: string;
|
||||
createdSince?: Date;
|
||||
severity?: NotificationSeverity;
|
||||
};
|
||||
|
||||
export type NotificationUpdateOptions = {
|
||||
ids: string[];
|
||||
done?: boolean;
|
||||
read?: boolean;
|
||||
saved?: boolean;
|
||||
};
|
||||
@@ -358,7 +358,7 @@ interface SignalApi {
|
||||
|
||||
- Replace absent signal service with long polling. This requires changes to the `signals` plugin as well.
|
||||
- Render dynamic values with various different React elements such as the `EntityRefLink` for entity references (for example `{{ user:default/john.doe }}`) in the notification payload
|
||||
- Handle `link` values that use route references. For example instead hard-coding link to `/catalog/default/component/artist-web` it should be possible to use `catalogPlugin.catalogEntity` route reference as a link of the notification. This should also allow using parameters to be passed to the route reference
|
||||
- Handle `link` values that use route references. For example instead hard-coding link to `/catalog/default/component/artist-web` it should be possible to use `catalogPlugin.catalogEntity` route reference as a link of the notification. This should also allow using parameters to be passed to the route reference. Links to external systems are still supported.
|
||||
- Add support for `analyticsApi` to notification actions like marking notifications done, saved or opening links in the notifications
|
||||
- Add support for user settings to control how notifications are shown to the user and which notifications user wants to receive. This should also include support for different `NotificationProcessor`s that can send notification to external systems
|
||||
- Add a sound to be played when notification is received
|
||||
|
||||
@@ -4,7 +4,8 @@ status: provisional
|
||||
authors:
|
||||
- 'bnechyporenko@bol.com'
|
||||
- 'benjaminl@spotify.com'
|
||||
owners:
|
||||
owners:
|
||||
- @backstage/scaffolder-maintainers
|
||||
project-areas:
|
||||
- scaffolder
|
||||
creation-date: 2024-01-31
|
||||
@@ -153,7 +154,7 @@ export function createGithubRepoCreateAction(options: {
|
||||
username: owner,
|
||||
});
|
||||
|
||||
await ctx.checkpoint('v1.task.checkpoint.repo.creation', async () => {
|
||||
await ctx.checkpoint('repo.creation', async () => {
|
||||
const repoCreationPromise =
|
||||
user.data.type === 'Organization'
|
||||
? client.rest.repos.createInOrg({
|
||||
@@ -168,19 +169,16 @@ export function createGithubRepoCreateAction(options: {
|
||||
});
|
||||
|
||||
if (secrets) {
|
||||
await ctx.checkpoint(
|
||||
'v1.task.checkpoint.repo.create.variables',
|
||||
async () => {
|
||||
for (const [key, value] of Object.entries(repoVariables ?? {})) {
|
||||
await client.rest.actions.createRepoVariable({
|
||||
owner,
|
||||
repo,
|
||||
name: key,
|
||||
value: value,
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
await ctx.checkpoint('repo.create.variables', async () => {
|
||||
for (const [key, value] of Object.entries(repoVariables ?? {})) {
|
||||
await client.rest.actions.createRepoVariable({
|
||||
owner,
|
||||
repo,
|
||||
name: key,
|
||||
value: value,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ctx.output('remoteUrl', newRepo.clone_url);
|
||||
@@ -204,7 +202,7 @@ Checkpoints will allow action authors to create actions where code paths are ign
|
||||
This will be provided on a context object and action of author provide a key and a callback.
|
||||
|
||||
```typescript
|
||||
await ctx.checkpoint('v1.task.checkpoint.repo.creation', async () => {
|
||||
await ctx.checkpoint('repo.creation', async () => {
|
||||
const { repoUrl } = await client.rest.Repository.create({});
|
||||
return { repoUrl };
|
||||
});
|
||||
@@ -215,7 +213,7 @@ It's going look like:
|
||||
|
||||
```json
|
||||
{
|
||||
"v1.task.checkpoint.repo.creation": {
|
||||
"repo.creation": {
|
||||
"status": "success",
|
||||
"result": {
|
||||
"repoUrl": "https://github.com/backstage/backstage.git"
|
||||
@@ -224,6 +222,43 @@ It's going look like:
|
||||
}
|
||||
```
|
||||
|
||||
or a failed attempt as:
|
||||
|
||||
```json
|
||||
{
|
||||
"repo.creation": {
|
||||
"status": "failed",
|
||||
"reason": "Namespace is not valid"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
DatabaseTaskStore will provide two extra methods `saveTaskState` and `getTaskState`. The type of state in API will be
|
||||
represented as `JsonObject`.
|
||||
|
||||
Task state will be stored in the extra column `state` in the table `tasks` with the next structure:
|
||||
|
||||
```json
|
||||
{
|
||||
"state": {
|
||||
"checkpoints": {
|
||||
"repo.creation": {
|
||||
"status": "success",
|
||||
"result": {
|
||||
"repoUrl": "https://github.com/backstage/backstage.git"
|
||||
}
|
||||
},
|
||||
"repo.add.member": {
|
||||
"status": "success",
|
||||
"result": {
|
||||
"id": "2345"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Release Plan
|
||||
|
||||
<!--
|
||||
|
||||
+33
-9
@@ -84,13 +84,16 @@ receive schema validation and autocompletion. For example:
|
||||
## Visibility
|
||||
|
||||
The `https://backstage.io/schema/config-v1` meta schema is a superset of JSON
|
||||
Schema Draft 07. The single addition is a custom `visibility` keyword, which is
|
||||
used to indicate whether the given config value should be visible in the
|
||||
frontend or not. The possible values are `frontend`, `backend`, and `secret`,
|
||||
where `backend` is the default. A visibility of `secret` has the same scope at
|
||||
runtime, but it will be treated with more care in certain contexts, and defining
|
||||
both `frontend` and `secret` for the same value in two different schemas will
|
||||
result in an error during schema merging.
|
||||
Schema Draft 07. The only additions are the custom `visibility` and
|
||||
`deepVisibility` keywords, which are used to indicate whether the given config
|
||||
value should be visible in the frontend or not. The `visibility` marker applies
|
||||
only to the field it's on, while the `deepVisibility` marker applies to the
|
||||
field it's on and downwards in the hierarchy as well. The possible values are
|
||||
`frontend`, `backend`, and `secret`, where `backend` is the default. A
|
||||
visibility of `secret` has the same scope at runtime, but it will be treated
|
||||
with more care in certain contexts, and defining both `frontend` and `secret`
|
||||
for the same value in two different schemas will result in an error during
|
||||
schema merging.
|
||||
|
||||
The visibility only applies to the direct parent of where the keyword is placed
|
||||
in the schema. For example, if you set the visibility to `frontend` for a subset
|
||||
@@ -105,17 +108,38 @@ declare the visibility of a leaf node of `type: "string"`.
|
||||
| `backend` | (Default) Only in backend |
|
||||
| `secret` | Only in backend and may be excluded from logs for security reasons |
|
||||
|
||||
You can set visibility with an `@visibility` comment in the `Config` Typescript
|
||||
interface.
|
||||
You can set visibility with a `@visibility` or `@deepVisibility` comment in the
|
||||
`Config` Typescript interface.
|
||||
|
||||
```ts
|
||||
export interface Config {
|
||||
app: {
|
||||
/**
|
||||
* Frontend root URL
|
||||
* NOTE: Visibility applies to only this field
|
||||
* @visibility frontend
|
||||
*/
|
||||
baseUrl: string;
|
||||
|
||||
/**
|
||||
* Some custom complex type
|
||||
* NOTE: Visibility applies recursively downward
|
||||
* This is particularly useful for complex types like durations
|
||||
* @deepVisibility frontend
|
||||
*/
|
||||
customSchedule: HumanDuration;
|
||||
};
|
||||
|
||||
backend: {
|
||||
/**
|
||||
* Some custom credentials type
|
||||
* NOTE: Visibility applies recursively downward, and this would NOT have
|
||||
* been safe if the regular visibility keyword had been used
|
||||
* @deepVisibility secret
|
||||
*/
|
||||
customCredentials: {
|
||||
password: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
// To create a new migration in a plugin, run:
|
||||
//
|
||||
// yarn workspace <package> knex migrate:make <name_with_underscores>
|
||||
//
|
||||
// for example:
|
||||
//
|
||||
// yarn workspace @backstage/plugin-catalog-backend knex migrate:make add_feature_foo
|
||||
//
|
||||
// This creates a file similar to
|
||||
//
|
||||
// plugins/catalog-backend/migrations/20240206160252_add_feature_foo.js
|
||||
|
||||
module.exports = {
|
||||
client: 'better-sqlite3',
|
||||
connection: ':memory:',
|
||||
useNullAsDefault: true,
|
||||
migrations: {
|
||||
// unfortunately this needs to be relative to the TARGET, not this file, and
|
||||
// it just so happens to work to make it go up two steps due to our repo
|
||||
// layout
|
||||
stub: '../../scripts/templates/knex-migration.stub.js',
|
||||
},
|
||||
};
|
||||
+46
-46
@@ -2697,90 +2697,90 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-darwin-arm64@npm:1.4.1":
|
||||
version: 1.4.1
|
||||
resolution: "@swc/core-darwin-arm64@npm:1.4.1"
|
||||
"@swc/core-darwin-arm64@npm:1.4.2":
|
||||
version: 1.4.2
|
||||
resolution: "@swc/core-darwin-arm64@npm:1.4.2"
|
||||
conditions: os=darwin & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-darwin-x64@npm:1.4.1":
|
||||
version: 1.4.1
|
||||
resolution: "@swc/core-darwin-x64@npm:1.4.1"
|
||||
"@swc/core-darwin-x64@npm:1.4.2":
|
||||
version: 1.4.2
|
||||
resolution: "@swc/core-darwin-x64@npm:1.4.2"
|
||||
conditions: os=darwin & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-linux-arm-gnueabihf@npm:1.4.1":
|
||||
version: 1.4.1
|
||||
resolution: "@swc/core-linux-arm-gnueabihf@npm:1.4.1"
|
||||
"@swc/core-linux-arm-gnueabihf@npm:1.4.2":
|
||||
version: 1.4.2
|
||||
resolution: "@swc/core-linux-arm-gnueabihf@npm:1.4.2"
|
||||
conditions: os=linux & cpu=arm
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-linux-arm64-gnu@npm:1.4.1":
|
||||
version: 1.4.1
|
||||
resolution: "@swc/core-linux-arm64-gnu@npm:1.4.1"
|
||||
"@swc/core-linux-arm64-gnu@npm:1.4.2":
|
||||
version: 1.4.2
|
||||
resolution: "@swc/core-linux-arm64-gnu@npm:1.4.2"
|
||||
conditions: os=linux & cpu=arm64 & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-linux-arm64-musl@npm:1.4.1":
|
||||
version: 1.4.1
|
||||
resolution: "@swc/core-linux-arm64-musl@npm:1.4.1"
|
||||
"@swc/core-linux-arm64-musl@npm:1.4.2":
|
||||
version: 1.4.2
|
||||
resolution: "@swc/core-linux-arm64-musl@npm:1.4.2"
|
||||
conditions: os=linux & cpu=arm64 & libc=musl
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-linux-x64-gnu@npm:1.4.1":
|
||||
version: 1.4.1
|
||||
resolution: "@swc/core-linux-x64-gnu@npm:1.4.1"
|
||||
"@swc/core-linux-x64-gnu@npm:1.4.2":
|
||||
version: 1.4.2
|
||||
resolution: "@swc/core-linux-x64-gnu@npm:1.4.2"
|
||||
conditions: os=linux & cpu=x64 & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-linux-x64-musl@npm:1.4.1":
|
||||
version: 1.4.1
|
||||
resolution: "@swc/core-linux-x64-musl@npm:1.4.1"
|
||||
"@swc/core-linux-x64-musl@npm:1.4.2":
|
||||
version: 1.4.2
|
||||
resolution: "@swc/core-linux-x64-musl@npm:1.4.2"
|
||||
conditions: os=linux & cpu=x64 & libc=musl
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-win32-arm64-msvc@npm:1.4.1":
|
||||
version: 1.4.1
|
||||
resolution: "@swc/core-win32-arm64-msvc@npm:1.4.1"
|
||||
"@swc/core-win32-arm64-msvc@npm:1.4.2":
|
||||
version: 1.4.2
|
||||
resolution: "@swc/core-win32-arm64-msvc@npm:1.4.2"
|
||||
conditions: os=win32 & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-win32-ia32-msvc@npm:1.4.1":
|
||||
version: 1.4.1
|
||||
resolution: "@swc/core-win32-ia32-msvc@npm:1.4.1"
|
||||
"@swc/core-win32-ia32-msvc@npm:1.4.2":
|
||||
version: 1.4.2
|
||||
resolution: "@swc/core-win32-ia32-msvc@npm:1.4.2"
|
||||
conditions: os=win32 & cpu=ia32
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-win32-x64-msvc@npm:1.4.1":
|
||||
version: 1.4.1
|
||||
resolution: "@swc/core-win32-x64-msvc@npm:1.4.1"
|
||||
"@swc/core-win32-x64-msvc@npm:1.4.2":
|
||||
version: 1.4.2
|
||||
resolution: "@swc/core-win32-x64-msvc@npm:1.4.2"
|
||||
conditions: os=win32 & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core@npm:^1.3.46":
|
||||
version: 1.4.1
|
||||
resolution: "@swc/core@npm:1.4.1"
|
||||
version: 1.4.2
|
||||
resolution: "@swc/core@npm:1.4.2"
|
||||
dependencies:
|
||||
"@swc/core-darwin-arm64": 1.4.1
|
||||
"@swc/core-darwin-x64": 1.4.1
|
||||
"@swc/core-linux-arm-gnueabihf": 1.4.1
|
||||
"@swc/core-linux-arm64-gnu": 1.4.1
|
||||
"@swc/core-linux-arm64-musl": 1.4.1
|
||||
"@swc/core-linux-x64-gnu": 1.4.1
|
||||
"@swc/core-linux-x64-musl": 1.4.1
|
||||
"@swc/core-win32-arm64-msvc": 1.4.1
|
||||
"@swc/core-win32-ia32-msvc": 1.4.1
|
||||
"@swc/core-win32-x64-msvc": 1.4.1
|
||||
"@swc/core-darwin-arm64": 1.4.2
|
||||
"@swc/core-darwin-x64": 1.4.2
|
||||
"@swc/core-linux-arm-gnueabihf": 1.4.2
|
||||
"@swc/core-linux-arm64-gnu": 1.4.2
|
||||
"@swc/core-linux-arm64-musl": 1.4.2
|
||||
"@swc/core-linux-x64-gnu": 1.4.2
|
||||
"@swc/core-linux-x64-musl": 1.4.2
|
||||
"@swc/core-win32-arm64-msvc": 1.4.2
|
||||
"@swc/core-win32-ia32-msvc": 1.4.2
|
||||
"@swc/core-win32-x64-msvc": 1.4.2
|
||||
"@swc/counter": ^0.1.2
|
||||
"@swc/types": ^0.1.5
|
||||
peerDependencies:
|
||||
@@ -2809,7 +2809,7 @@ __metadata:
|
||||
peerDependenciesMeta:
|
||||
"@swc/helpers":
|
||||
optional: true
|
||||
checksum: 3549b4a0ba8201be2dd39954ef8375df5d028da5688a68211dc5cbf69ff7a43533e9ffd36f629095f51555552f4df52116384ef779d5ec45c2b9de938e651d57
|
||||
checksum: b17afda692b2627d3a82e589f1b29cef31bdee626a2dd997d78312dcbfc6eb701850fbab22e85f02b1261da39f0b0afb6a236c6065f6d0d7478cff939ca5a888
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -11939,13 +11939,13 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"yaml-loader@npm:^0.8.0":
|
||||
version: 0.8.0
|
||||
resolution: "yaml-loader@npm:0.8.0"
|
||||
version: 0.8.1
|
||||
resolution: "yaml-loader@npm:0.8.1"
|
||||
dependencies:
|
||||
javascript-stringify: ^2.0.1
|
||||
loader-utils: ^2.0.0
|
||||
yaml: ^2.0.0
|
||||
checksum: d12dd264666b80baec23cea9f81cb677a9102d6f34ab45d8b6c085ace4d05b7285db9ce317db57264c3317af01128ce6e5b754e6866d15ccd75e8141902fb529
|
||||
checksum: 2969eb7d6083a95a8d95369906bbc73d124125be5f61556506d230a57b7acc872abe836bf631f89aa97d9e8198bee8d100fa68d5a6524be84cd8eb5062cf76d3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
"helmet": "^6.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"logform": "^2.3.2",
|
||||
"minimatch": "^5.0.0",
|
||||
"minimatch": "^9.0.0",
|
||||
"minimist": "^1.2.5",
|
||||
"morgan": "^1.10.0",
|
||||
"node-forge": "^1.3.1",
|
||||
|
||||
@@ -93,7 +93,7 @@
|
||||
"lodash": "^4.17.21",
|
||||
"logform": "^2.3.2",
|
||||
"luxon": "^3.0.0",
|
||||
"minimatch": "^5.0.0",
|
||||
"minimatch": "^9.0.0",
|
||||
"mysql2": "^3.0.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"p-limit": "^3.1.0",
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2024 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 { createMockDirectory } from '@backstage/backend-test-utils';
|
||||
import { resolveSafeChildPath } from './paths';
|
||||
|
||||
describe('paths', () => {
|
||||
describe('resolveSafeChildPath', () => {
|
||||
const mockDir = createMockDirectory();
|
||||
const secondDirectory = createMockDirectory();
|
||||
|
||||
const workspacePath = mockDir.resolve('workspace');
|
||||
|
||||
beforeEach(() => {
|
||||
mockDir.setContent({
|
||||
[`${workspacePath}/README.md`]: '### README.md',
|
||||
});
|
||||
secondDirectory.setContent({
|
||||
[`index.md`]: '### index.md',
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error if the path is outside of the base path', () => {
|
||||
expect(() =>
|
||||
resolveSafeChildPath(workspacePath, secondDirectory.path),
|
||||
).toThrow(
|
||||
'Relative path is not allowed to refer to a directory outside its parent',
|
||||
);
|
||||
});
|
||||
|
||||
it('should resolve to the full path if the target is inside the directory', () => {
|
||||
expect(resolveSafeChildPath(workspacePath, './README.md')).toEqual(
|
||||
`${workspacePath}/README.md`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error if the path is a symlink to a directory outside of the base path', () => {
|
||||
mockDir.addContent({
|
||||
[`${workspacePath}/symlink`]: ({ symlink }) =>
|
||||
symlink(secondDirectory.path),
|
||||
});
|
||||
|
||||
expect(() =>
|
||||
resolveSafeChildPath(workspacePath, './symlink/index.md'),
|
||||
).toThrow(
|
||||
'Relative path is not allowed to refer to a directory outside its parent',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -17,6 +17,7 @@
|
||||
import { isChildPath } from '@backstage/cli-common';
|
||||
import { NotAllowedError } from '@backstage/errors';
|
||||
import { resolve as resolvePath } from 'path';
|
||||
import { realpathSync as realPath } from 'fs';
|
||||
|
||||
/** @internal */
|
||||
export const packagePathMocks = new Map<
|
||||
@@ -64,7 +65,7 @@ export function resolvePackagePath(name: string, ...paths: string[]) {
|
||||
export function resolveSafeChildPath(base: string, path: string): string {
|
||||
const targetPath = resolvePath(base, path);
|
||||
|
||||
if (!isChildPath(base, targetPath)) {
|
||||
if (!isChildPath(resolveRealPath(base), resolveRealPath(targetPath))) {
|
||||
throw new NotAllowedError(
|
||||
'Relative path is not allowed to refer to a directory outside its parent',
|
||||
);
|
||||
@@ -73,5 +74,16 @@ export function resolveSafeChildPath(base: string, path: string): string {
|
||||
return targetPath;
|
||||
}
|
||||
|
||||
function resolveRealPath(path: string): string {
|
||||
try {
|
||||
return realPath(path);
|
||||
} catch (ex) {
|
||||
if (ex.code !== 'ENOENT') {
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
// Re-export isChildPath so that backend packages don't need to depend on cli-common
|
||||
export { isChildPath };
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
development: {
|
||||
client: 'better-sqlite3',
|
||||
connection: {
|
||||
filename: './dev.sqlite3',
|
||||
},
|
||||
},
|
||||
|
||||
/*
|
||||
staging: {
|
||||
client: 'postgresql',
|
||||
connection: {
|
||||
database: 'my_db',
|
||||
user: 'username',
|
||||
password: 'password',
|
||||
},
|
||||
pool: {
|
||||
min: 2,
|
||||
max: 10,
|
||||
},
|
||||
migrations: {
|
||||
tableName: 'knex_migrations',
|
||||
},
|
||||
},
|
||||
|
||||
production: {
|
||||
client: 'postgresql',
|
||||
connection: {
|
||||
database: 'my_db',
|
||||
user: 'username',
|
||||
password: 'password',
|
||||
},
|
||||
pool: {
|
||||
min: 2,
|
||||
max: 10,
|
||||
},
|
||||
migrations: {
|
||||
tableName: 'knex_migrations',
|
||||
},
|
||||
},
|
||||
*/
|
||||
};
|
||||
@@ -107,7 +107,7 @@
|
||||
"json-schema": "^0.4.0",
|
||||
"lodash": "^4.17.21",
|
||||
"mini-css-extract-plugin": "^2.4.2",
|
||||
"minimatch": "^5.1.1",
|
||||
"minimatch": "^9.0.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"node-libs-browser": "^2.2.1",
|
||||
"npm-packlist": "^5.0.0",
|
||||
@@ -159,7 +159,6 @@
|
||||
"@types/fs-extra": "^11.0.0",
|
||||
"@types/http-proxy": "^1.17.4",
|
||||
"@types/inquirer": "^8.1.3",
|
||||
"@types/minimatch": "^5.0.0",
|
||||
"@types/node": "^18.17.8",
|
||||
"@types/npm-packlist": "^3.0.0",
|
||||
"@types/recursive-readdir": "^2.2.0",
|
||||
|
||||
@@ -18,7 +18,7 @@ import fs from 'fs-extra';
|
||||
import chalk from 'chalk';
|
||||
import ora from 'ora';
|
||||
import semver from 'semver';
|
||||
import minimatch from 'minimatch';
|
||||
import { minimatch } from 'minimatch';
|
||||
import { OptionValues } from 'commander';
|
||||
import { isError, NotFoundError } from '@backstage/errors';
|
||||
import { resolve as resolvePath } from 'path';
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import minimatch from 'minimatch';
|
||||
import { minimatch } from 'minimatch';
|
||||
import { getPackages } from '@manypkg/get-packages';
|
||||
import { NotFoundError } from '../errors';
|
||||
import { detectYarnVersion } from '../yarn';
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@manypkg/get-packages": "^1.1.3",
|
||||
"minimatch": "^5.1.2"
|
||||
"minimatch": "^9.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "workspace:^",
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
"is-glob": "^4.0.3",
|
||||
"js-yaml": "^4.1.0",
|
||||
"lodash": "^4.17.21",
|
||||
"minimatch": "^5.1.1",
|
||||
"minimatch": "^9.0.0",
|
||||
"p-limit": "^3.0.2",
|
||||
"portfinder": "^1.0.32",
|
||||
"ts-node": "^10.0.0",
|
||||
|
||||
@@ -61,7 +61,7 @@ import {
|
||||
import { IMarkdownEmitterContext } from '@microsoft/api-documenter/lib/markdown/MarkdownEmitter';
|
||||
import { AstDeclaration } from '@microsoft/api-extractor/lib/analyzer/AstDeclaration';
|
||||
import { paths as cliPaths } from '../../lib/paths';
|
||||
import minimatch from 'minimatch';
|
||||
import { minimatch } from 'minimatch';
|
||||
import { getPackageExportNames } from '../../lib/entryPoints';
|
||||
import { createBinRunner } from '../util';
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
"knex": "^3.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"luxon": "^3.0.0",
|
||||
"minimatch": "^5.0.0",
|
||||
"minimatch": "^9.0.0",
|
||||
"morgan": "^1.10.0",
|
||||
"node-cache": "^5.1.2",
|
||||
"node-fetch": "^2.6.7",
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { createOidcRouter } from './router';
|
||||
export { bindOidcRouter } from './router';
|
||||
export { TokenFactory } from './TokenFactory';
|
||||
export { DatabaseKeyStore } from './DatabaseKeyStore';
|
||||
export { MemoryKeyStore } from './MemoryKeyStore';
|
||||
|
||||
@@ -14,18 +14,21 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import express from 'express';
|
||||
import Router from 'express-promise-router';
|
||||
import { TokenIssuer } from './types';
|
||||
|
||||
export type Options = {
|
||||
baseUrl: string;
|
||||
tokenIssuer: TokenIssuer;
|
||||
};
|
||||
|
||||
export function createOidcRouter(options: Options) {
|
||||
export function bindOidcRouter(
|
||||
targetRouter: express.Router,
|
||||
options: {
|
||||
baseUrl: string;
|
||||
tokenIssuer: TokenIssuer;
|
||||
},
|
||||
) {
|
||||
const { baseUrl, tokenIssuer } = options;
|
||||
|
||||
const router = Router();
|
||||
targetRouter.use(router);
|
||||
|
||||
const config = {
|
||||
issuer: baseUrl,
|
||||
@@ -68,6 +71,4 @@ export function createOidcRouter(options: Options) {
|
||||
router.get('/v1/userinfo', (_req, res) => {
|
||||
res.status(501).send('Not Implemented');
|
||||
});
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
*/
|
||||
|
||||
export { authPlugin as default } from './authPlugin';
|
||||
export * from './service/router';
|
||||
export * from './service';
|
||||
export type { TokenParams } from './identity';
|
||||
export * from './providers';
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ export type { SamlAuthResult } from './saml';
|
||||
export type { GcpIapResult, GcpIapTokenInfo } from './gcp-iap';
|
||||
|
||||
export { providers, defaultAuthProviderFactories } from './providers';
|
||||
export { createOriginFilter, type ProviderFactories } from './router';
|
||||
|
||||
export { createAuthProviderIntegration } from './createAuthProviderIntegration';
|
||||
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright 2024 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 {
|
||||
PluginEndpointDiscovery,
|
||||
TokenManager,
|
||||
} from '@backstage/backend-common';
|
||||
import { LoggerService } from '@backstage/backend-plugin-api';
|
||||
import { CatalogApi, CatalogClient } from '@backstage/catalog-client';
|
||||
import { Config } from '@backstage/config';
|
||||
import { NotFoundError, assertError } from '@backstage/errors';
|
||||
import { AuthProviderFactory } from '@backstage/plugin-auth-node';
|
||||
import express from 'express';
|
||||
import Router from 'express-promise-router';
|
||||
import { Minimatch } from 'minimatch';
|
||||
import { CatalogAuthResolverContext } from '../lib/resolvers/CatalogAuthResolverContext';
|
||||
import { TokenIssuer } from '../identity/types';
|
||||
|
||||
/** @public */
|
||||
export type ProviderFactories = { [s: string]: AuthProviderFactory };
|
||||
|
||||
export function bindProviderRouters(
|
||||
targetRouter: express.Router,
|
||||
options: {
|
||||
providers: ProviderFactories;
|
||||
appUrl: string;
|
||||
baseUrl: string;
|
||||
config: Config;
|
||||
logger: LoggerService;
|
||||
discovery: PluginEndpointDiscovery;
|
||||
tokenManager: TokenManager;
|
||||
tokenIssuer: TokenIssuer;
|
||||
catalogApi?: CatalogApi;
|
||||
},
|
||||
) {
|
||||
const {
|
||||
providers,
|
||||
appUrl,
|
||||
baseUrl,
|
||||
config,
|
||||
logger,
|
||||
discovery,
|
||||
tokenManager,
|
||||
tokenIssuer,
|
||||
catalogApi,
|
||||
} = options;
|
||||
|
||||
const providersConfig = config.getOptionalConfig('auth.providers');
|
||||
|
||||
const isOriginAllowed = createOriginFilter(config);
|
||||
|
||||
for (const [providerId, providerFactory] of Object.entries(providers)) {
|
||||
if (providersConfig?.has(providerId)) {
|
||||
logger.info(`Configuring auth provider: ${providerId}`);
|
||||
try {
|
||||
const provider = providerFactory({
|
||||
providerId,
|
||||
appUrl,
|
||||
baseUrl: baseUrl,
|
||||
isOriginAllowed,
|
||||
globalConfig: {
|
||||
baseUrl: baseUrl,
|
||||
appUrl,
|
||||
isOriginAllowed,
|
||||
},
|
||||
config: providersConfig.getConfig(providerId),
|
||||
logger,
|
||||
resolverContext: CatalogAuthResolverContext.create({
|
||||
logger,
|
||||
catalogApi:
|
||||
catalogApi ?? new CatalogClient({ discoveryApi: discovery }),
|
||||
tokenIssuer,
|
||||
tokenManager,
|
||||
}),
|
||||
});
|
||||
|
||||
const r = Router();
|
||||
|
||||
r.get('/start', provider.start.bind(provider));
|
||||
r.get('/handler/frame', provider.frameHandler.bind(provider));
|
||||
r.post('/handler/frame', provider.frameHandler.bind(provider));
|
||||
if (provider.logout) {
|
||||
r.post('/logout', provider.logout.bind(provider));
|
||||
}
|
||||
if (provider.refresh) {
|
||||
r.get('/refresh', provider.refresh.bind(provider));
|
||||
r.post('/refresh', provider.refresh.bind(provider));
|
||||
}
|
||||
|
||||
targetRouter.use(`/${providerId}`, r);
|
||||
} catch (e) {
|
||||
assertError(e);
|
||||
if (process.env.NODE_ENV !== 'development') {
|
||||
throw new Error(
|
||||
`Failed to initialize ${providerId} auth provider, ${e.message}`,
|
||||
);
|
||||
}
|
||||
|
||||
logger.warn(`Skipping ${providerId} auth provider, ${e.message}`);
|
||||
|
||||
targetRouter.use(`/${providerId}`, () => {
|
||||
// If the user added the provider under auth.providers but the clientId and clientSecret etc. were not found.
|
||||
throw new NotFoundError(
|
||||
`Auth provider registered for '${providerId}' is misconfigured. This could mean the configs under ` +
|
||||
`auth.providers.${providerId} are missing or the environment variables used are not defined. ` +
|
||||
`Check the auth backend plugin logs when the backend starts to see more details.`,
|
||||
);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
targetRouter.use(`/${providerId}`, () => {
|
||||
throw new NotFoundError(
|
||||
`No auth provider registered for '${providerId}'`,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export function createOriginFilter(
|
||||
config: Config,
|
||||
): (origin: string) => boolean {
|
||||
const appUrl = config.getString('app.baseUrl');
|
||||
const { origin: appOrigin } = new URL(appUrl);
|
||||
|
||||
const allowedOrigins = config.getOptionalStringArray(
|
||||
'auth.experimentalExtraAllowedOrigins',
|
||||
);
|
||||
|
||||
const allowedOriginPatterns =
|
||||
allowedOrigins?.map(
|
||||
pattern => new Minimatch(pattern, { nocase: true, noglobstar: true }),
|
||||
) ?? [];
|
||||
|
||||
return origin => {
|
||||
if (origin === appOrigin) {
|
||||
return true;
|
||||
}
|
||||
return allowedOriginPatterns.some(pattern => pattern.match(origin));
|
||||
};
|
||||
}
|
||||
+2
-11
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
* Copyright 2024 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.
|
||||
@@ -14,13 +14,4 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// This file makes it possible to run "yarn knex migrate:make some_file_name"
|
||||
// to assist in making new migrations
|
||||
module.exports = {
|
||||
client: 'better-sqlite3',
|
||||
connection: ':memory:',
|
||||
useNullAsDefault: true,
|
||||
migrations: {
|
||||
directory: './migrations',
|
||||
},
|
||||
};
|
||||
export { createRouter, type RouterOptions } from './router';
|
||||
@@ -24,24 +24,19 @@ import {
|
||||
PluginEndpointDiscovery,
|
||||
TokenManager,
|
||||
} from '@backstage/backend-common';
|
||||
import { assertError, NotFoundError } from '@backstage/errors';
|
||||
import { CatalogApi, CatalogClient } from '@backstage/catalog-client';
|
||||
import { createOidcRouter, TokenFactory, KeyStores } from '../identity';
|
||||
import { NotFoundError } from '@backstage/errors';
|
||||
import { CatalogApi } from '@backstage/catalog-client';
|
||||
import { bindOidcRouter, TokenFactory, KeyStores } from '../identity';
|
||||
import session from 'express-session';
|
||||
import connectSessionKnex from 'connect-session-knex';
|
||||
import passport from 'passport';
|
||||
import { Minimatch } from 'minimatch';
|
||||
import { CatalogAuthResolverContext } from '../lib/resolvers';
|
||||
import { AuthDatabase } from '../database/AuthDatabase';
|
||||
import { readBackstageTokenExpiration } from './readBackstageTokenExpiration';
|
||||
import { TokenIssuer } from '../identity/types';
|
||||
import { StaticTokenIssuer } from '../identity/StaticTokenIssuer';
|
||||
import { StaticKeyStore } from '../identity/StaticKeyStore';
|
||||
import { Config } from '@backstage/config';
|
||||
import { AuthProviderFactory } from '@backstage/plugin-auth-node';
|
||||
|
||||
/** @public */
|
||||
export type ProviderFactories = { [s: string]: AuthProviderFactory };
|
||||
import { ProviderFactories, bindProviderRouters } from '../providers/router';
|
||||
|
||||
/** @public */
|
||||
export interface RouterOptions {
|
||||
@@ -65,10 +60,8 @@ export async function createRouter(
|
||||
config,
|
||||
discovery,
|
||||
database,
|
||||
tokenManager,
|
||||
tokenFactoryAlgorithm,
|
||||
providerFactories = {},
|
||||
catalogApi,
|
||||
} = options;
|
||||
const router = Router();
|
||||
|
||||
@@ -103,6 +96,7 @@ export async function createRouter(
|
||||
config.getOptionalString('auth.identityTokenAlgorithm'),
|
||||
});
|
||||
}
|
||||
|
||||
const secret = config.getOptionalString('auth.session.secret');
|
||||
if (secret) {
|
||||
router.use(cookieParser(secret));
|
||||
@@ -125,96 +119,31 @@ export async function createRouter(
|
||||
} else {
|
||||
router.use(cookieParser());
|
||||
}
|
||||
|
||||
router.use(express.urlencoded({ extended: false }));
|
||||
router.use(express.json());
|
||||
|
||||
const allProviderFactories = options.disableDefaultProviderFactories
|
||||
const providers = options.disableDefaultProviderFactories
|
||||
? providerFactories
|
||||
: {
|
||||
...defaultAuthProviderFactories,
|
||||
...providerFactories,
|
||||
};
|
||||
|
||||
const providersConfig = config.getOptionalConfig('auth.providers');
|
||||
bindProviderRouters(router, {
|
||||
providers,
|
||||
appUrl,
|
||||
baseUrl: authUrl,
|
||||
tokenIssuer,
|
||||
...options,
|
||||
});
|
||||
|
||||
const isOriginAllowed = createOriginFilter(config);
|
||||
|
||||
for (const [providerId, providerFactory] of Object.entries(
|
||||
allProviderFactories,
|
||||
)) {
|
||||
if (providersConfig?.has(providerId)) {
|
||||
logger.info(`Configuring auth provider: ${providerId}`);
|
||||
try {
|
||||
const provider = providerFactory({
|
||||
providerId,
|
||||
appUrl,
|
||||
baseUrl: authUrl,
|
||||
isOriginAllowed,
|
||||
globalConfig: {
|
||||
baseUrl: authUrl,
|
||||
appUrl,
|
||||
isOriginAllowed,
|
||||
},
|
||||
config: providersConfig.getConfig(providerId),
|
||||
logger,
|
||||
resolverContext: CatalogAuthResolverContext.create({
|
||||
logger,
|
||||
catalogApi:
|
||||
catalogApi ?? new CatalogClient({ discoveryApi: discovery }),
|
||||
tokenIssuer,
|
||||
tokenManager,
|
||||
}),
|
||||
});
|
||||
|
||||
const r = Router();
|
||||
|
||||
r.get('/start', provider.start.bind(provider));
|
||||
r.get('/handler/frame', provider.frameHandler.bind(provider));
|
||||
r.post('/handler/frame', provider.frameHandler.bind(provider));
|
||||
if (provider.logout) {
|
||||
r.post('/logout', provider.logout.bind(provider));
|
||||
}
|
||||
if (provider.refresh) {
|
||||
r.get('/refresh', provider.refresh.bind(provider));
|
||||
r.post('/refresh', provider.refresh.bind(provider));
|
||||
}
|
||||
|
||||
router.use(`/${providerId}`, r);
|
||||
} catch (e) {
|
||||
assertError(e);
|
||||
if (process.env.NODE_ENV !== 'development') {
|
||||
throw new Error(
|
||||
`Failed to initialize ${providerId} auth provider, ${e.message}`,
|
||||
);
|
||||
}
|
||||
|
||||
logger.warn(`Skipping ${providerId} auth provider, ${e.message}`);
|
||||
|
||||
router.use(`/${providerId}`, () => {
|
||||
// If the user added the provider under auth.providers but the clientId and clientSecret etc. were not found.
|
||||
throw new NotFoundError(
|
||||
`Auth provider registered for '${providerId}' is misconfigured. This could mean the configs under ` +
|
||||
`auth.providers.${providerId} are missing or the environment variables used are not defined. ` +
|
||||
`Check the auth backend plugin logs when the backend starts to see more details.`,
|
||||
);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
router.use(`/${providerId}`, () => {
|
||||
throw new NotFoundError(
|
||||
`No auth provider registered for '${providerId}'`,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
router.use(
|
||||
createOidcRouter({
|
||||
tokenIssuer,
|
||||
baseUrl: authUrl,
|
||||
}),
|
||||
);
|
||||
bindOidcRouter(router, {
|
||||
tokenIssuer,
|
||||
baseUrl: authUrl,
|
||||
});
|
||||
|
||||
// Gives a more helpful error message than a plain 404
|
||||
router.use('/:provider/', req => {
|
||||
const { provider } = req.params;
|
||||
throw new NotFoundError(`Unknown auth provider '${provider}'`);
|
||||
@@ -222,27 +151,3 @@ export async function createRouter(
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export function createOriginFilter(
|
||||
config: Config,
|
||||
): (origin: string) => boolean {
|
||||
const appUrl = config.getString('app.baseUrl');
|
||||
const { origin: appOrigin } = new URL(appUrl);
|
||||
|
||||
const allowedOrigins = config.getOptionalStringArray(
|
||||
'auth.experimentalExtraAllowedOrigins',
|
||||
);
|
||||
|
||||
const allowedOriginPatterns =
|
||||
allowedOrigins?.map(
|
||||
pattern => new Minimatch(pattern, { nocase: true, noglobstar: true }),
|
||||
) ?? [];
|
||||
|
||||
return origin => {
|
||||
if (origin === appOrigin) {
|
||||
return true;
|
||||
}
|
||||
return allowedOriginPatterns.some(pattern => pattern.match(origin));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ import { BuildRunOptions } from '@backstage/plugin-azure-devops-common';
|
||||
import { DashboardPullRequest } from '@backstage/plugin-azure-devops-common';
|
||||
import { DiscoveryApi } from '@backstage/core-plugin-api';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { FetchApi } from '@backstage/core-plugin-api';
|
||||
import { GitTag } from '@backstage/plugin-azure-devops-common';
|
||||
import { IdentityApi } from '@backstage/core-plugin-api';
|
||||
import { JSX as JSX_2 } from 'react';
|
||||
import { PullRequest } from '@backstage/plugin-azure-devops-common';
|
||||
import { PullRequestOptions } from '@backstage/plugin-azure-devops-common';
|
||||
@@ -121,10 +121,7 @@ export const azureDevOpsApiRef: ApiRef<AzureDevOpsApi>;
|
||||
|
||||
// @public (undocumented)
|
||||
export class AzureDevOpsClient implements AzureDevOpsApi {
|
||||
constructor(options: {
|
||||
discoveryApi: DiscoveryApi;
|
||||
identityApi: IdentityApi;
|
||||
});
|
||||
constructor(options: { discoveryApi: DiscoveryApi; fetchApi: FetchApi });
|
||||
// (undocumented)
|
||||
getAllTeams(): Promise<Team[]>;
|
||||
// (undocumented)
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
createPageExtension,
|
||||
createPlugin,
|
||||
discoveryApiRef,
|
||||
identityApiRef,
|
||||
fetchApiRef,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import { azureDevOpsApiRef, AzureDevOpsClient } from '../api';
|
||||
import {
|
||||
@@ -38,9 +38,12 @@ import { azurePullRequestDashboardRouteRef } from '../routes';
|
||||
export const azureDevOpsApi = createApiExtension({
|
||||
factory: createApiFactory({
|
||||
api: azureDevOpsApiRef,
|
||||
deps: { discoveryApi: discoveryApiRef, identityApi: identityApiRef },
|
||||
factory: ({ discoveryApi, identityApi }) =>
|
||||
new AzureDevOpsClient({ discoveryApi, identityApi }),
|
||||
deps: {
|
||||
discoveryApi: discoveryApiRef,
|
||||
fetchApi: fetchApiRef,
|
||||
},
|
||||
factory: ({ discoveryApi, fetchApi }) =>
|
||||
new AzureDevOpsClient({ discoveryApi, fetchApi }),
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@@ -27,21 +27,21 @@ import {
|
||||
RepoBuildOptions,
|
||||
Team,
|
||||
} from '@backstage/plugin-azure-devops-common';
|
||||
import { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api';
|
||||
import { DiscoveryApi, FetchApi } from '@backstage/core-plugin-api';
|
||||
import { ResponseError } from '@backstage/errors';
|
||||
import { AzureDevOpsApi } from './AzureDevOpsApi';
|
||||
|
||||
/** @public */
|
||||
export class AzureDevOpsClient implements AzureDevOpsApi {
|
||||
private readonly discoveryApi: DiscoveryApi;
|
||||
private readonly identityApi: IdentityApi;
|
||||
private readonly fetchApi: FetchApi;
|
||||
|
||||
public constructor(options: {
|
||||
discoveryApi: DiscoveryApi;
|
||||
identityApi: IdentityApi;
|
||||
fetchApi: FetchApi;
|
||||
}) {
|
||||
this.discoveryApi = options.discoveryApi;
|
||||
this.identityApi = options.identityApi;
|
||||
this.fetchApi = options.fetchApi;
|
||||
}
|
||||
|
||||
public async getRepoBuilds(
|
||||
@@ -203,10 +203,7 @@ export class AzureDevOpsClient implements AzureDevOpsApi {
|
||||
const baseUrl = `${await this.discoveryApi.getBaseUrl('azure-devops')}/`;
|
||||
const url = new URL(path, baseUrl);
|
||||
|
||||
const { token: idToken } = await this.identityApi.getCredentials();
|
||||
const response = await fetch(url.toString(), {
|
||||
headers: idToken ? { Authorization: `Bearer ${idToken}` } : {},
|
||||
});
|
||||
const response = await this.fetchApi.fetch(url.toString());
|
||||
|
||||
if (!response.ok) {
|
||||
throw await ResponseError.fromResponse(response);
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
createRoutableExtension,
|
||||
createComponentExtension,
|
||||
discoveryApiRef,
|
||||
identityApiRef,
|
||||
fetchApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
|
||||
import { AzureDevOpsClient } from './api/AzureDevOpsClient';
|
||||
@@ -56,9 +56,12 @@ export const azureDevOpsPlugin = createPlugin({
|
||||
apis: [
|
||||
createApiFactory({
|
||||
api: azureDevOpsApiRef,
|
||||
deps: { discoveryApi: discoveryApiRef, identityApi: identityApiRef },
|
||||
factory: ({ discoveryApi, identityApi }) =>
|
||||
new AzureDevOpsClient({ discoveryApi, identityApi }),
|
||||
deps: {
|
||||
discoveryApi: discoveryApiRef,
|
||||
fetchApi: fetchApiRef,
|
||||
},
|
||||
factory: ({ discoveryApi, fetchApi }) =>
|
||||
new AzureDevOpsClient({ discoveryApi, fetchApi }),
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
"@octokit/rest": "^19.0.3",
|
||||
"git-url-parse": "^14.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"minimatch": "^5.1.2",
|
||||
"minimatch": "^9.0.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"uuid": "^8.0.0",
|
||||
"winston": "^3.2.1"
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
"knex": "^3.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"luxon": "^3.0.0",
|
||||
"minimatch": "^5.0.0",
|
||||
"minimatch": "^9.0.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"p-limit": "^3.0.2",
|
||||
"prom-client": "^15.0.0",
|
||||
|
||||
@@ -18,7 +18,7 @@ import { Config } from '@backstage/config';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import path from 'path';
|
||||
import { LocationSpec } from '@backstage/plugin-catalog-common';
|
||||
import minimatch from 'minimatch';
|
||||
import { minimatch } from 'minimatch';
|
||||
|
||||
/**
|
||||
* Rules to apply to catalog entities.
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
createPageExtension,
|
||||
createPlugin,
|
||||
discoveryApiRef,
|
||||
identityApiRef,
|
||||
fetchApiRef,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
|
||||
import { devToolsApiRef, DevToolsClient } from '../api';
|
||||
@@ -37,9 +37,12 @@ import { rootRouteRef } from '../routes';
|
||||
export const devToolsApi = createApiExtension({
|
||||
factory: createApiFactory({
|
||||
api: devToolsApiRef,
|
||||
deps: { discoveryApi: discoveryApiRef, identityApi: identityApiRef },
|
||||
factory: ({ discoveryApi, identityApi }) =>
|
||||
new DevToolsClient({ discoveryApi, identityApi }),
|
||||
deps: {
|
||||
discoveryApi: discoveryApiRef,
|
||||
fetchApi: fetchApiRef,
|
||||
},
|
||||
factory: ({ discoveryApi, fetchApi }) =>
|
||||
new DevToolsClient({ discoveryApi, fetchApi }),
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api';
|
||||
import { DiscoveryApi, FetchApi } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
ConfigInfo,
|
||||
DevToolsInfo,
|
||||
@@ -25,14 +25,14 @@ import { DevToolsApi } from './DevToolsApi';
|
||||
|
||||
export class DevToolsClient implements DevToolsApi {
|
||||
private readonly discoveryApi: DiscoveryApi;
|
||||
private readonly identityApi: IdentityApi;
|
||||
private readonly fetchApi: FetchApi;
|
||||
|
||||
public constructor(options: {
|
||||
discoveryApi: DiscoveryApi;
|
||||
identityApi: IdentityApi;
|
||||
fetchApi: FetchApi;
|
||||
}) {
|
||||
this.discoveryApi = options.discoveryApi;
|
||||
this.identityApi = options.identityApi;
|
||||
this.fetchApi = options.fetchApi;
|
||||
}
|
||||
|
||||
public async getConfig(): Promise<ConfigInfo | undefined> {
|
||||
@@ -64,10 +64,7 @@ export class DevToolsClient implements DevToolsApi {
|
||||
const baseUrl = `${await this.discoveryApi.getBaseUrl('devtools')}/`;
|
||||
const url = new URL(path, baseUrl);
|
||||
|
||||
const { token } = await this.identityApi.getCredentials();
|
||||
const response = await fetch(url.toString(), {
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
});
|
||||
const response = await this.fetchApi.fetch(url.toString());
|
||||
|
||||
if (!response.ok) {
|
||||
throw await ResponseError.fromResponse(response);
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
createPlugin,
|
||||
createRoutableExtension,
|
||||
discoveryApiRef,
|
||||
identityApiRef,
|
||||
fetchApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { devToolsApiRef, DevToolsClient } from './api';
|
||||
|
||||
@@ -31,9 +31,12 @@ export const devToolsPlugin = createPlugin({
|
||||
apis: [
|
||||
createApiFactory({
|
||||
api: devToolsApiRef,
|
||||
deps: { discoveryApi: discoveryApiRef, identityApi: identityApiRef },
|
||||
factory: ({ discoveryApi, identityApi }) =>
|
||||
new DevToolsClient({ discoveryApi, identityApi }),
|
||||
deps: {
|
||||
discoveryApi: discoveryApiRef,
|
||||
fetchApi: fetchApiRef,
|
||||
},
|
||||
factory: ({ discoveryApi, fetchApi }) =>
|
||||
new DevToolsClient({ discoveryApi, fetchApi }),
|
||||
}),
|
||||
],
|
||||
routes: {
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"@backstage/core-plugin-api": "workspace:^",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@rjsf/utils": "5.17.0",
|
||||
"@rjsf/utils": "5.17.1",
|
||||
"@types/react": "^16.13.1 || ^17.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -62,10 +62,10 @@
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.61",
|
||||
"@rjsf/core": "5.17.0",
|
||||
"@rjsf/material-ui": "5.17.0",
|
||||
"@rjsf/utils": "5.17.0",
|
||||
"@rjsf/validator-ajv8": "5.17.0",
|
||||
"@rjsf/core": "5.17.1",
|
||||
"@rjsf/material-ui": "5.17.1",
|
||||
"@rjsf/utils": "5.17.1",
|
||||
"@rjsf/validator-ajv8": "5.17.1",
|
||||
"@types/react": "^16.13.1 || ^17.0.0 || ^18.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"luxon": "^3.4.3",
|
||||
|
||||
@@ -275,6 +275,8 @@ export interface GroupedResponses extends DeploymentResources {
|
||||
// (undocumented)
|
||||
customResources: any[];
|
||||
// (undocumented)
|
||||
daemonSets: V1DaemonSet[];
|
||||
// (undocumented)
|
||||
ingresses: V1Ingress[];
|
||||
// (undocumented)
|
||||
jobs: V1Job[];
|
||||
|
||||
@@ -294,4 +294,5 @@ export interface GroupedResponses extends DeploymentResources {
|
||||
cronJobs: V1CronJob[];
|
||||
customResources: any[];
|
||||
statefulsets: V1StatefulSet[];
|
||||
daemonSets: V1DaemonSet[];
|
||||
}
|
||||
|
||||
@@ -58,6 +58,9 @@ export const groupResponses = (
|
||||
case 'statefulsets':
|
||||
prev.statefulsets.push(...next.resources);
|
||||
break;
|
||||
case 'daemonsets':
|
||||
prev.daemonSets.push(...next.resources);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
return prev;
|
||||
@@ -74,6 +77,7 @@ export const groupResponses = (
|
||||
cronJobs: [],
|
||||
customResources: [],
|
||||
statefulsets: [],
|
||||
daemonSets: [],
|
||||
} as GroupedResponses,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,581 @@
|
||||
{
|
||||
"daemonSets": [
|
||||
{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "DaemonSet",
|
||||
"metadata": {
|
||||
"annotations": {
|
||||
"deprecated.daemonset.template.generation": "1",
|
||||
"kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"apps/v1\",\"kind\":\"DaemonSet\",\"metadata\":{\"annotations\":{},\"labels\":{\"backstage.io/kubernetes-id\":\"dice-roller\",\"k8s-app\":\"fluentd-logging\"},\"name\":\"fluentd-elasticsearch\",\"namespace\":\"default\"},\"spec\":{\"selector\":{\"matchLabels\":{\"name\":\"fluentd-elasticsearch\"}},\"template\":{\"metadata\":{\"labels\":{\"name\":\"fluentd-elasticsearch\"}},\"spec\":{\"containers\":[{\"image\":\"quay.io/fluentd_elasticsearch/fluentd:v2.5.2\",\"name\":\"fluentd-elasticsearch\",\"resources\":{\"limits\":{\"memory\":\"200Mi\"},\"requests\":{\"cpu\":\"100m\",\"memory\":\"200Mi\"}}}],\"terminationGracePeriodSeconds\":30}}}}\n"
|
||||
},
|
||||
"creationTimestamp": "2024-02-14T21:00:28Z",
|
||||
"generation": 1,
|
||||
"labels": {
|
||||
"backstage.io/kubernetes-id": "dice-roller",
|
||||
"k8s-app": "fluentd-logging"
|
||||
},
|
||||
"name": "fluentd-elasticsearch",
|
||||
"namespace": "default",
|
||||
"resourceVersion": "1769498",
|
||||
"uid": "2ba243f3-a733-4b63-9db9-c9b8ce303a23"
|
||||
},
|
||||
"spec": {
|
||||
"revisionHistoryLimit": 10,
|
||||
"selector": {
|
||||
"matchLabels": {
|
||||
"name": "fluentd-elasticsearch"
|
||||
}
|
||||
},
|
||||
"template": {
|
||||
"metadata": {
|
||||
"creationTimestamp": null,
|
||||
"labels": {
|
||||
"name": "fluentd-elasticsearch"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"containers": [
|
||||
{
|
||||
"image": "quay.io/fluentd_elasticsearch/fluentd:v2.5.2",
|
||||
"imagePullPolicy": "IfNotPresent",
|
||||
"name": "fluentd-elasticsearch",
|
||||
"resources": {
|
||||
"limits": {
|
||||
"memory": "200Mi"
|
||||
},
|
||||
"requests": {
|
||||
"cpu": "100m",
|
||||
"memory": "200Mi"
|
||||
}
|
||||
},
|
||||
"terminationMessagePath": "/dev/termination-log",
|
||||
"terminationMessagePolicy": "File"
|
||||
}
|
||||
],
|
||||
"dnsPolicy": "ClusterFirst",
|
||||
"restartPolicy": "Always",
|
||||
"schedulerName": "default-scheduler",
|
||||
"securityContext": {},
|
||||
"terminationGracePeriodSeconds": 30
|
||||
}
|
||||
},
|
||||
"updateStrategy": {
|
||||
"rollingUpdate": {
|
||||
"maxSurge": 0,
|
||||
"maxUnavailable": 1
|
||||
},
|
||||
"type": "RollingUpdate"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"currentNumberScheduled": 1,
|
||||
"desiredNumberScheduled": 1,
|
||||
"numberAvailable": 1,
|
||||
"numberMisscheduled": 0,
|
||||
"numberReady": 1,
|
||||
"observedGeneration": 1,
|
||||
"updatedNumberScheduled": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "DaemonSet",
|
||||
"metadata": {
|
||||
"annotations": {
|
||||
"deprecated.daemonset.template.generation": "1",
|
||||
"kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"apps/v1\",\"kind\":\"DaemonSet\",\"metadata\":{\"annotations\":{},\"labels\":{\"backstage.io/kubernetes-id\":\"dice-roller\",\"k8s-app\":\"fluentd-logging\"},\"name\":\"fluentd-elasticsearch2\",\"namespace\":\"default\"},\"spec\":{\"selector\":{\"matchLabels\":{\"name\":\"fluentd-elasticsearch2\"}},\"template\":{\"metadata\":{\"labels\":{\"backstage.io/kubernetes-id\":\"dice-roller\",\"name\":\"fluentd-elasticsearch2\"}},\"spec\":{\"containers\":[{\"image\":\"quay.io/fluentd_elasticsearch/fluentd:v2.5.2\",\"name\":\"fluentd-elasticsearch\",\"resources\":{\"limits\":{\"memory\":\"200Mi\"},\"requests\":{\"cpu\":\"100m\",\"memory\":\"200Mi\"}}}],\"terminationGracePeriodSeconds\":30}}}}\n"
|
||||
},
|
||||
"creationTimestamp": "2024-02-16T18:11:26Z",
|
||||
"generation": 1,
|
||||
"labels": {
|
||||
"backstage.io/kubernetes-id": "dice-roller",
|
||||
"k8s-app": "fluentd-logging"
|
||||
},
|
||||
"name": "fluentd-elasticsearch2",
|
||||
"namespace": "default",
|
||||
"resourceVersion": "1981736",
|
||||
"uid": "b923d035-e9a4-4a40-9472-f0f3468f30df"
|
||||
},
|
||||
"spec": {
|
||||
"revisionHistoryLimit": 10,
|
||||
"selector": {
|
||||
"matchLabels": {
|
||||
"name": "fluentd-elasticsearch2"
|
||||
}
|
||||
},
|
||||
"template": {
|
||||
"metadata": {
|
||||
"creationTimestamp": null,
|
||||
"labels": {
|
||||
"backstage.io/kubernetes-id": "dice-roller",
|
||||
"name": "fluentd-elasticsearch2"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"containers": [
|
||||
{
|
||||
"image": "quay.io/fluentd_elasticsearch/fluentd:v2.5.2",
|
||||
"imagePullPolicy": "IfNotPresent",
|
||||
"name": "fluentd-elasticsearch",
|
||||
"resources": {
|
||||
"limits": {
|
||||
"memory": "200Mi"
|
||||
},
|
||||
"requests": {
|
||||
"cpu": "100m",
|
||||
"memory": "200Mi"
|
||||
}
|
||||
},
|
||||
"terminationMessagePath": "/dev/termination-log",
|
||||
"terminationMessagePolicy": "File"
|
||||
}
|
||||
],
|
||||
"dnsPolicy": "ClusterFirst",
|
||||
"restartPolicy": "Always",
|
||||
"schedulerName": "default-scheduler",
|
||||
"securityContext": {},
|
||||
"terminationGracePeriodSeconds": 30
|
||||
}
|
||||
},
|
||||
"updateStrategy": {
|
||||
"rollingUpdate": {
|
||||
"maxSurge": 0,
|
||||
"maxUnavailable": 1
|
||||
},
|
||||
"type": "RollingUpdate"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"currentNumberScheduled": 1,
|
||||
"desiredNumberScheduled": 1,
|
||||
"numberAvailable": 1,
|
||||
"numberMisscheduled": 0,
|
||||
"numberReady": 1,
|
||||
"observedGeneration": 1,
|
||||
"updatedNumberScheduled": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"pods": [
|
||||
{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Pod",
|
||||
"metadata": {
|
||||
"creationTimestamp": "2024-02-16T16:35:06Z",
|
||||
"generateName": "fluentd-elasticsearch-",
|
||||
"labels": {
|
||||
"backstage.io/kubernetes-id": "dice-roller",
|
||||
"controller-revision-hash": "86978587c8",
|
||||
"name": "fluentd-elasticsearch",
|
||||
"pod-template-generation": "2"
|
||||
},
|
||||
"name": "fluentd-elasticsearch-mmkpf",
|
||||
"namespace": "default",
|
||||
"ownerReferences": [
|
||||
{
|
||||
"apiVersion": "apps/v1",
|
||||
"blockOwnerDeletion": true,
|
||||
"controller": true,
|
||||
"kind": "DaemonSet",
|
||||
"name": "fluentd-elasticsearch",
|
||||
"uid": "2ba243f3-a733-4b63-9db9-c9b8ce303a23"
|
||||
}
|
||||
],
|
||||
"resourceVersion": "1970744",
|
||||
"uid": "fc9f9070-1a3c-4e09-9cd6-8803a4ebfb0a"
|
||||
},
|
||||
"spec": {
|
||||
"affinity": {
|
||||
"nodeAffinity": {
|
||||
"requiredDuringSchedulingIgnoredDuringExecution": {
|
||||
"nodeSelectorTerms": [
|
||||
{
|
||||
"matchFields": [
|
||||
{
|
||||
"key": "metadata.name",
|
||||
"operator": "In",
|
||||
"values": ["ucp-control-plane"]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"containers": [
|
||||
{
|
||||
"image": "quay.io/fluentd_elasticsearch/fluentd:v2.5.2",
|
||||
"imagePullPolicy": "IfNotPresent",
|
||||
"name": "fluentd-elasticsearch",
|
||||
"resources": {
|
||||
"limits": {
|
||||
"memory": "200Mi"
|
||||
},
|
||||
"requests": {
|
||||
"cpu": "100m",
|
||||
"memory": "200Mi"
|
||||
}
|
||||
},
|
||||
"terminationMessagePath": "/dev/termination-log",
|
||||
"terminationMessagePolicy": "File",
|
||||
"volumeMounts": [
|
||||
{
|
||||
"mountPath": "/var/run/secrets/kubernetes.io/serviceaccount",
|
||||
"name": "kube-api-access-8cwbv",
|
||||
"readOnly": true
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"dnsPolicy": "ClusterFirst",
|
||||
"enableServiceLinks": true,
|
||||
"nodeName": "ucp-control-plane",
|
||||
"preemptionPolicy": "PreemptLowerPriority",
|
||||
"priority": 0,
|
||||
"restartPolicy": "Always",
|
||||
"schedulerName": "default-scheduler",
|
||||
"securityContext": {},
|
||||
"serviceAccount": "default",
|
||||
"serviceAccountName": "default",
|
||||
"terminationGracePeriodSeconds": 30,
|
||||
"tolerations": [
|
||||
{
|
||||
"effect": "NoExecute",
|
||||
"key": "node.kubernetes.io/not-ready",
|
||||
"operator": "Exists"
|
||||
},
|
||||
{
|
||||
"effect": "NoExecute",
|
||||
"key": "node.kubernetes.io/unreachable",
|
||||
"operator": "Exists"
|
||||
},
|
||||
{
|
||||
"effect": "NoSchedule",
|
||||
"key": "node.kubernetes.io/disk-pressure",
|
||||
"operator": "Exists"
|
||||
},
|
||||
{
|
||||
"effect": "NoSchedule",
|
||||
"key": "node.kubernetes.io/memory-pressure",
|
||||
"operator": "Exists"
|
||||
},
|
||||
{
|
||||
"effect": "NoSchedule",
|
||||
"key": "node.kubernetes.io/pid-pressure",
|
||||
"operator": "Exists"
|
||||
},
|
||||
{
|
||||
"effect": "NoSchedule",
|
||||
"key": "node.kubernetes.io/unschedulable",
|
||||
"operator": "Exists"
|
||||
}
|
||||
],
|
||||
"volumes": [
|
||||
{
|
||||
"name": "kube-api-access-8cwbv",
|
||||
"projected": {
|
||||
"defaultMode": 420,
|
||||
"sources": [
|
||||
{
|
||||
"serviceAccountToken": {
|
||||
"expirationSeconds": 3607,
|
||||
"path": "token"
|
||||
}
|
||||
},
|
||||
{
|
||||
"configMap": {
|
||||
"items": [
|
||||
{
|
||||
"key": "ca.crt",
|
||||
"path": "ca.crt"
|
||||
}
|
||||
],
|
||||
"name": "kube-root-ca.crt"
|
||||
}
|
||||
},
|
||||
{
|
||||
"downwardAPI": {
|
||||
"items": [
|
||||
{
|
||||
"fieldRef": {
|
||||
"apiVersion": "v1",
|
||||
"fieldPath": "metadata.namespace"
|
||||
},
|
||||
"path": "namespace"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"conditions": [
|
||||
{
|
||||
"lastProbeTime": null,
|
||||
"lastTransitionTime": "2024-02-16T16:35:06Z",
|
||||
"status": "True",
|
||||
"type": "Initialized"
|
||||
},
|
||||
{
|
||||
"lastProbeTime": null,
|
||||
"lastTransitionTime": "2024-02-16T16:35:08Z",
|
||||
"status": "True",
|
||||
"type": "Ready"
|
||||
},
|
||||
{
|
||||
"lastProbeTime": null,
|
||||
"lastTransitionTime": "2024-02-16T16:35:08Z",
|
||||
"status": "True",
|
||||
"type": "ContainersReady"
|
||||
},
|
||||
{
|
||||
"lastProbeTime": null,
|
||||
"lastTransitionTime": "2024-02-16T16:35:06Z",
|
||||
"status": "True",
|
||||
"type": "PodScheduled"
|
||||
}
|
||||
],
|
||||
"containerStatuses": [
|
||||
{
|
||||
"containerID": "containerd://958e4ec14b6a3557724fabeaac9c8f8fe2ea797337a4397e700209f6e482e898",
|
||||
"image": "quay.io/fluentd_elasticsearch/fluentd:v2.5.2",
|
||||
"imageID": "sha256:a5bf47027e067e0376708cae10750ea154b13356dfc0209610f5ea0bc7c16fe0",
|
||||
"lastState": {},
|
||||
"name": "fluentd-elasticsearch",
|
||||
"ready": true,
|
||||
"restartCount": 0,
|
||||
"started": true,
|
||||
"state": {
|
||||
"running": {
|
||||
"startedAt": "2024-02-16T16:35:07Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"hostIP": "172.19.0.3",
|
||||
"phase": "Running",
|
||||
"podIP": "10.244.0.215",
|
||||
"podIPs": [
|
||||
{
|
||||
"ip": "10.244.0.215"
|
||||
}
|
||||
],
|
||||
"qosClass": "Burstable",
|
||||
"startTime": "2024-02-16T16:35:06Z"
|
||||
}
|
||||
},
|
||||
{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Pod",
|
||||
"metadata": {
|
||||
"creationTimestamp": "2024-02-16T18:11:26Z",
|
||||
"generateName": "fluentd-elasticsearch2-",
|
||||
"labels": {
|
||||
"backstage.io/kubernetes-id": "dice-roller",
|
||||
"controller-revision-hash": "5979b599f6",
|
||||
"name": "fluentd-elasticsearch2",
|
||||
"pod-template-generation": "1"
|
||||
},
|
||||
"name": "fluentd-elasticsearch2-7hwwg",
|
||||
"namespace": "default",
|
||||
"ownerReferences": [
|
||||
{
|
||||
"apiVersion": "apps/v1",
|
||||
"blockOwnerDeletion": true,
|
||||
"controller": true,
|
||||
"kind": "DaemonSet",
|
||||
"name": "fluentd-elasticsearch2",
|
||||
"uid": "b923d035-e9a4-4a40-9472-f0f3468f30df"
|
||||
}
|
||||
],
|
||||
"resourceVersion": "1981735",
|
||||
"uid": "1b829903-2c3a-453f-a8f2-b6603eec0bbe"
|
||||
},
|
||||
"spec": {
|
||||
"affinity": {
|
||||
"nodeAffinity": {
|
||||
"requiredDuringSchedulingIgnoredDuringExecution": {
|
||||
"nodeSelectorTerms": [
|
||||
{
|
||||
"matchFields": [
|
||||
{
|
||||
"key": "metadata.name",
|
||||
"operator": "In",
|
||||
"values": ["ucp-control-plane"]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"containers": [
|
||||
{
|
||||
"image": "quay.io/fluentd_elasticsearch/fluentd:v2.5.2",
|
||||
"imagePullPolicy": "IfNotPresent",
|
||||
"name": "fluentd-elasticsearch",
|
||||
"resources": {
|
||||
"limits": {
|
||||
"memory": "200Mi"
|
||||
},
|
||||
"requests": {
|
||||
"cpu": "100m",
|
||||
"memory": "200Mi"
|
||||
}
|
||||
},
|
||||
"terminationMessagePath": "/dev/termination-log",
|
||||
"terminationMessagePolicy": "File",
|
||||
"volumeMounts": [
|
||||
{
|
||||
"mountPath": "/var/run/secrets/kubernetes.io/serviceaccount",
|
||||
"name": "kube-api-access-rpkbp",
|
||||
"readOnly": true
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"dnsPolicy": "ClusterFirst",
|
||||
"enableServiceLinks": true,
|
||||
"nodeName": "ucp-control-plane",
|
||||
"preemptionPolicy": "PreemptLowerPriority",
|
||||
"priority": 0,
|
||||
"restartPolicy": "Always",
|
||||
"schedulerName": "default-scheduler",
|
||||
"securityContext": {},
|
||||
"serviceAccount": "default",
|
||||
"serviceAccountName": "default",
|
||||
"terminationGracePeriodSeconds": 30,
|
||||
"tolerations": [
|
||||
{
|
||||
"effect": "NoExecute",
|
||||
"key": "node.kubernetes.io/not-ready",
|
||||
"operator": "Exists"
|
||||
},
|
||||
{
|
||||
"effect": "NoExecute",
|
||||
"key": "node.kubernetes.io/unreachable",
|
||||
"operator": "Exists"
|
||||
},
|
||||
{
|
||||
"effect": "NoSchedule",
|
||||
"key": "node.kubernetes.io/disk-pressure",
|
||||
"operator": "Exists"
|
||||
},
|
||||
{
|
||||
"effect": "NoSchedule",
|
||||
"key": "node.kubernetes.io/memory-pressure",
|
||||
"operator": "Exists"
|
||||
},
|
||||
{
|
||||
"effect": "NoSchedule",
|
||||
"key": "node.kubernetes.io/pid-pressure",
|
||||
"operator": "Exists"
|
||||
},
|
||||
{
|
||||
"effect": "NoSchedule",
|
||||
"key": "node.kubernetes.io/unschedulable",
|
||||
"operator": "Exists"
|
||||
}
|
||||
],
|
||||
"volumes": [
|
||||
{
|
||||
"name": "kube-api-access-rpkbp",
|
||||
"projected": {
|
||||
"defaultMode": 420,
|
||||
"sources": [
|
||||
{
|
||||
"serviceAccountToken": {
|
||||
"expirationSeconds": 3607,
|
||||
"path": "token"
|
||||
}
|
||||
},
|
||||
{
|
||||
"configMap": {
|
||||
"items": [
|
||||
{
|
||||
"key": "ca.crt",
|
||||
"path": "ca.crt"
|
||||
}
|
||||
],
|
||||
"name": "kube-root-ca.crt"
|
||||
}
|
||||
},
|
||||
{
|
||||
"downwardAPI": {
|
||||
"items": [
|
||||
{
|
||||
"fieldRef": {
|
||||
"apiVersion": "v1",
|
||||
"fieldPath": "metadata.namespace"
|
||||
},
|
||||
"path": "namespace"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"conditions": [
|
||||
{
|
||||
"lastProbeTime": null,
|
||||
"lastTransitionTime": "2024-02-16T18:11:27Z",
|
||||
"status": "True",
|
||||
"type": "Initialized"
|
||||
},
|
||||
{
|
||||
"lastProbeTime": null,
|
||||
"lastTransitionTime": "2024-02-16T18:11:28Z",
|
||||
"status": "True",
|
||||
"type": "Ready"
|
||||
},
|
||||
{
|
||||
"lastProbeTime": null,
|
||||
"lastTransitionTime": "2024-02-16T18:11:28Z",
|
||||
"status": "True",
|
||||
"type": "ContainersReady"
|
||||
},
|
||||
{
|
||||
"lastProbeTime": null,
|
||||
"lastTransitionTime": "2024-02-16T18:11:27Z",
|
||||
"status": "True",
|
||||
"type": "PodScheduled"
|
||||
}
|
||||
],
|
||||
"containerStatuses": [
|
||||
{
|
||||
"containerID": "containerd://3372cb5a300c87e5b1530b32fea7af35dce92470fc7c1cea47c714fac283feb9",
|
||||
"image": "quay.io/fluentd_elasticsearch/fluentd:v2.5.2",
|
||||
"imageID": "sha256:a5bf47027e067e0376708cae10750ea154b13356dfc0209610f5ea0bc7c16fe0",
|
||||
"lastState": {},
|
||||
"name": "fluentd-elasticsearch",
|
||||
"ready": true,
|
||||
"restartCount": 0,
|
||||
"started": true,
|
||||
"state": {
|
||||
"running": {
|
||||
"startedAt": "2024-02-16T18:11:28Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"hostIP": "172.19.0.3",
|
||||
"phase": "Running",
|
||||
"podIP": "10.244.0.88",
|
||||
"podIPs": [
|
||||
{
|
||||
"ip": "10.244.0.88"
|
||||
}
|
||||
],
|
||||
"qosClass": "Burstable",
|
||||
"startTime": "2024-02-16T18:11:27Z"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -34,6 +34,7 @@ import { IngressesAccordions } from '../IngressesAccordions';
|
||||
import { ServicesAccordions } from '../ServicesAccordions';
|
||||
import { CronJobsAccordions } from '../CronJobsAccordions';
|
||||
import { CustomResources } from '../CustomResources';
|
||||
import { DaemonSetsAccordions } from '../DaemonSetsAccordions';
|
||||
import {
|
||||
ClusterContext,
|
||||
GroupedResponsesContext,
|
||||
@@ -151,6 +152,11 @@ export const Cluster = ({ clusterObjects, podsWithErrors }: ClusterProps) => {
|
||||
<DeploymentsAccordions />
|
||||
</Grid>
|
||||
) : undefined}
|
||||
{groupedResponses.daemonSets.length > 0 ? (
|
||||
<Grid item>
|
||||
<DaemonSetsAccordions />
|
||||
</Grid>
|
||||
) : undefined}
|
||||
{groupedResponses.statefulsets.length > 0 ? (
|
||||
<Grid item>
|
||||
<StatefulSetsAccordions />
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { screen } from '@testing-library/react';
|
||||
import { DaemonSetsAccordions } from './DaemonSetsAccordions';
|
||||
import * as oneDaemonsFixture from '../../__fixtures__/2-daemonsets.json';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
import { kubernetesProviders } from '../../hooks/test-utils';
|
||||
|
||||
describe('DaemonSetsAccordions', () => {
|
||||
it('should render two daemonset', async () => {
|
||||
const wrapper = kubernetesProviders(oneDaemonsFixture);
|
||||
|
||||
await renderInTestApp(wrapper(<DaemonSetsAccordions />));
|
||||
|
||||
expect(screen.getByText('fluentd-elasticsearch')).toBeInTheDocument();
|
||||
expect(screen.getByText('fluentd-elasticsearch2')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useContext } from 'react';
|
||||
import {
|
||||
Accordion,
|
||||
AccordionDetails,
|
||||
AccordionSummary,
|
||||
Grid,
|
||||
} from '@material-ui/core';
|
||||
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
|
||||
import { V1Pod, V1DaemonSet } from '@kubernetes/client-node';
|
||||
import { PodsTable } from '../Pods';
|
||||
import { DaemonSetDrawer } from './DaemonSetsDrawer';
|
||||
import { getOwnedResources } from '../../utils/owner';
|
||||
import {
|
||||
GroupedResponsesContext,
|
||||
PodNamesWithErrorsContext,
|
||||
} from '../../hooks';
|
||||
import { StatusError, StatusOK } from '@backstage/core-components';
|
||||
import { READY_COLUMNS, RESOURCE_COLUMNS } from '../Pods/PodsTable';
|
||||
|
||||
type DaemonSetsAccordionsProps = {
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
type DaemonSetAccordionProps = {
|
||||
daemonset: V1DaemonSet;
|
||||
ownedPods: V1Pod[];
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
type DaemonSetSummaryProps = {
|
||||
daemonset: V1DaemonSet;
|
||||
numberOfCurrentPods: number;
|
||||
numberOfPodsWithErrors: number;
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
const DaemonSetSummary = ({
|
||||
daemonset,
|
||||
numberOfCurrentPods,
|
||||
numberOfPodsWithErrors,
|
||||
}: DaemonSetSummaryProps) => {
|
||||
return (
|
||||
<Grid
|
||||
container
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
spacing={0}
|
||||
>
|
||||
<Grid xs={4} item>
|
||||
<DaemonSetDrawer daemonset={daemonset} />
|
||||
</Grid>
|
||||
<Grid
|
||||
item
|
||||
container
|
||||
xs={4}
|
||||
direction="column"
|
||||
justifyContent="flex-start"
|
||||
alignItems="flex-end"
|
||||
spacing={0}
|
||||
>
|
||||
<Grid item>
|
||||
<StatusOK>{numberOfCurrentPods} pods</StatusOK>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
{numberOfPodsWithErrors > 0 ? (
|
||||
<StatusError>
|
||||
{numberOfPodsWithErrors} pod
|
||||
{numberOfPodsWithErrors > 1 ? 's' : ''} with errors
|
||||
</StatusError>
|
||||
) : (
|
||||
<StatusOK>No pods with errors</StatusOK>
|
||||
)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
const DaemonSetAccordion = ({
|
||||
daemonset,
|
||||
ownedPods,
|
||||
}: DaemonSetAccordionProps) => {
|
||||
const podNamesWithErrors = useContext(PodNamesWithErrorsContext);
|
||||
|
||||
const podsWithErrors = ownedPods.filter(p =>
|
||||
podNamesWithErrors.has(p.metadata?.name ?? ''),
|
||||
);
|
||||
|
||||
return (
|
||||
<Accordion TransitionProps={{ unmountOnExit: true }} variant="outlined">
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||
<DaemonSetSummary
|
||||
daemonset={daemonset}
|
||||
numberOfCurrentPods={ownedPods.length}
|
||||
numberOfPodsWithErrors={podsWithErrors.length}
|
||||
/>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<PodsTable
|
||||
pods={ownedPods}
|
||||
extraColumns={[READY_COLUMNS, RESOURCE_COLUMNS]}
|
||||
/>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
);
|
||||
};
|
||||
|
||||
export const DaemonSetsAccordions = ({}: DaemonSetsAccordionsProps) => {
|
||||
const groupedResponses = useContext(GroupedResponsesContext);
|
||||
|
||||
return (
|
||||
<Grid
|
||||
container
|
||||
direction="column"
|
||||
justifyContent="flex-start"
|
||||
alignItems="flex-start"
|
||||
>
|
||||
{groupedResponses.daemonSets.map((daemonset, i) => (
|
||||
<Grid container item key={i} xs>
|
||||
<Grid item xs>
|
||||
<DaemonSetAccordion
|
||||
ownedPods={getOwnedResources(daemonset, groupedResponses.pods)}
|
||||
daemonset={daemonset}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import * as daemonsets from '../../__fixtures__/2-daemonsets.json';
|
||||
import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
|
||||
import { DaemonSetDrawer } from './DaemonSetsDrawer';
|
||||
import { kubernetesClusterLinkFormatterApiRef } from '../../api';
|
||||
|
||||
describe('DaemonsetsDrawer', () => {
|
||||
it('should render daemonsets drawer', async () => {
|
||||
const { getByText, getAllByText } = await renderInTestApp(
|
||||
<TestApiProvider apis={[[kubernetesClusterLinkFormatterApiRef, {}]]}>
|
||||
<DaemonSetDrawer
|
||||
daemonset={(daemonsets as any).daemonSets[0]}
|
||||
expanded
|
||||
/>
|
||||
,
|
||||
</TestApiProvider>,
|
||||
);
|
||||
expect(getAllByText('fluentd-elasticsearch')).toHaveLength(2);
|
||||
expect(getAllByText('DaemonSet')).toHaveLength(2);
|
||||
expect(getByText('YAML')).toBeInTheDocument();
|
||||
expect(getByText('Update Strategy Type')).toBeInTheDocument();
|
||||
expect(getByText('RollingUpdate')).toBeInTheDocument();
|
||||
expect(getByText('Min Ready Seconds')).toBeInTheDocument();
|
||||
expect(getByText('???')).toBeInTheDocument();
|
||||
expect(getByText('Min Ready Seconds')).toBeInTheDocument();
|
||||
expect(getByText('Revision History Limit')).toBeInTheDocument();
|
||||
expect(getByText('Current Number Scheduled')).toBeInTheDocument();
|
||||
expect(getByText('Desired Number Scheduled')).toBeInTheDocument();
|
||||
expect(getByText('Number Available')).toBeInTheDocument();
|
||||
expect(getByText('Number Misscheduled')).toBeInTheDocument();
|
||||
expect(getByText('Number Ready')).toBeInTheDocument();
|
||||
expect(getByText('namespace: default')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render deployment drawer without namespace', async () => {
|
||||
const daemonset = (daemonsets as any).daemonSets[0];
|
||||
const { queryByText } = await renderInTestApp(
|
||||
<TestApiProvider apis={[[kubernetesClusterLinkFormatterApiRef, {}]]}>
|
||||
<DaemonSetDrawer
|
||||
daemonset={{
|
||||
...daemonset,
|
||||
metadata: { ...daemonset.metadata, namespace: undefined },
|
||||
}}
|
||||
expanded
|
||||
/>
|
||||
,
|
||||
</TestApiProvider>,
|
||||
);
|
||||
|
||||
expect(queryByText('namespace: default')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { V1DaemonSet } from '@kubernetes/client-node';
|
||||
import { KubernetesStructuredMetadataTableDrawer } from '../KubernetesDrawer';
|
||||
import { Typography, Grid, Chip } from '@material-ui/core';
|
||||
|
||||
export const DaemonSetDrawer = ({
|
||||
daemonset,
|
||||
expanded,
|
||||
}: {
|
||||
daemonset: V1DaemonSet;
|
||||
expanded?: boolean;
|
||||
}) => {
|
||||
const namespace = daemonset.metadata?.namespace;
|
||||
return (
|
||||
<KubernetesStructuredMetadataTableDrawer
|
||||
object={daemonset}
|
||||
expanded={expanded}
|
||||
kind="DaemonSet"
|
||||
renderObject={(daemonsetObj: V1DaemonSet) => {
|
||||
return {
|
||||
updateStrategyType: daemonsetObj.spec?.updateStrategy?.type ?? '???',
|
||||
minReadySeconds: daemonsetObj.spec?.minReadySeconds ?? '???',
|
||||
revisionHistoryLimit:
|
||||
daemonsetObj.spec?.revisionHistoryLimit ?? '???',
|
||||
currentNumberScheduled:
|
||||
daemonsetObj.status?.currentNumberScheduled ?? '???',
|
||||
desiredNumberScheduled:
|
||||
daemonsetObj.status?.desiredNumberScheduled ?? '???',
|
||||
numberAvailable: daemonsetObj.status?.numberAvailable ?? '???',
|
||||
numberMisscheduled: daemonsetObj.status?.numberMisscheduled ?? '???',
|
||||
numberReady: daemonsetObj.status?.numberReady ?? '???',
|
||||
};
|
||||
}}
|
||||
>
|
||||
<Grid
|
||||
container
|
||||
direction="column"
|
||||
justifyContent="flex-start"
|
||||
alignItems="flex-start"
|
||||
spacing={0}
|
||||
>
|
||||
<Grid item>
|
||||
<Typography variant="body1">
|
||||
{daemonset.metadata?.name ?? 'unknown object'}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Typography color="textSecondary" variant="subtitle1">
|
||||
DaemonSet
|
||||
</Typography>
|
||||
</Grid>
|
||||
{namespace && (
|
||||
<Grid item>
|
||||
<Chip size="small" label={`namespace: ${namespace}`} />
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</KubernetesStructuredMetadataTableDrawer>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
export { DaemonSetsAccordions } from './DaemonSetsAccordions';
|
||||
@@ -25,6 +25,7 @@ export const GroupedResponsesContext = React.createContext<GroupedResponses>({
|
||||
pods: [],
|
||||
replicaSets: [],
|
||||
deployments: [],
|
||||
daemonSets: [],
|
||||
services: [],
|
||||
configMaps: [],
|
||||
horizontalPodAutoscalers: [],
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
createApiFactory,
|
||||
createPlugin,
|
||||
discoveryApiRef,
|
||||
identityApiRef,
|
||||
fetchApiRef,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
|
||||
import { LinguistClient, linguistApiRef } from '../api';
|
||||
@@ -40,9 +40,12 @@ export const entityLinguistCard = createEntityCardExtension({
|
||||
export const linguistApi = createApiExtension({
|
||||
factory: createApiFactory({
|
||||
api: linguistApiRef,
|
||||
deps: { discoveryApi: discoveryApiRef, identityApi: identityApiRef },
|
||||
factory: ({ discoveryApi, identityApi }) =>
|
||||
new LinguistClient({ discoveryApi, identityApi }),
|
||||
deps: {
|
||||
discoveryApi: discoveryApiRef,
|
||||
fetchApi: fetchApiRef,
|
||||
},
|
||||
factory: ({ discoveryApi, fetchApi }) =>
|
||||
new LinguistClient({ discoveryApi, fetchApi }),
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@@ -14,21 +14,21 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api';
|
||||
import { DiscoveryApi, FetchApi } from '@backstage/core-plugin-api';
|
||||
import { ResponseError } from '@backstage/errors';
|
||||
import { Languages } from '@backstage/plugin-linguist-common';
|
||||
import { LinguistApi } from './LinguistApi';
|
||||
|
||||
export class LinguistClient implements LinguistApi {
|
||||
private readonly discoveryApi: DiscoveryApi;
|
||||
private readonly identityApi: IdentityApi;
|
||||
private readonly fetchApi: FetchApi;
|
||||
|
||||
public constructor(options: {
|
||||
discoveryApi: DiscoveryApi;
|
||||
identityApi: IdentityApi;
|
||||
fetchApi: FetchApi;
|
||||
}) {
|
||||
this.discoveryApi = options.discoveryApi;
|
||||
this.identityApi = options.identityApi;
|
||||
this.fetchApi = options.fetchApi;
|
||||
}
|
||||
|
||||
public async getLanguages(entityRef: string): Promise<Languages> {
|
||||
@@ -45,10 +45,7 @@ export class LinguistClient implements LinguistApi {
|
||||
const baseUrl = `${await this.discoveryApi.getBaseUrl('linguist')}/`;
|
||||
const url = new URL(path, baseUrl);
|
||||
|
||||
const { token } = await this.identityApi.getCredentials();
|
||||
const response = await fetch(url.toString(), {
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
});
|
||||
const response = await this.fetchApi.fetch(url.toString());
|
||||
|
||||
if (!response.ok) {
|
||||
throw await ResponseError.fromResponse(response);
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
createComponentExtension,
|
||||
createPlugin,
|
||||
discoveryApiRef,
|
||||
identityApiRef,
|
||||
fetchApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { linguistApiRef, LinguistClient } from './api';
|
||||
import { LINGUIST_ANNOTATION } from '@backstage/plugin-linguist-common';
|
||||
@@ -35,9 +35,12 @@ export const linguistPlugin = createPlugin({
|
||||
apis: [
|
||||
createApiFactory({
|
||||
api: linguistApiRef,
|
||||
deps: { discoveryApi: discoveryApiRef, identityApi: identityApiRef },
|
||||
factory: ({ discoveryApi, identityApi }) =>
|
||||
new LinguistClient({ discoveryApi, identityApi }),
|
||||
deps: {
|
||||
discoveryApi: discoveryApiRef,
|
||||
fetchApi: fetchApiRef,
|
||||
},
|
||||
factory: ({ discoveryApi, fetchApi }) =>
|
||||
new LinguistClient({ discoveryApi, fetchApi }),
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
"@octokit/webhooks": "^10.0.0",
|
||||
"libsodium-wrappers": "^0.7.11",
|
||||
"octokit": "^3.0.0",
|
||||
"octokit-plugin-create-pull-request": "^3.10.0",
|
||||
"octokit-plugin-create-pull-request": "^5.0.0",
|
||||
"winston": "^3.2.1",
|
||||
"yaml": "^2.0.0"
|
||||
},
|
||||
|
||||
@@ -59,14 +59,14 @@
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.61",
|
||||
"@react-hookz/web": "^24.0.0",
|
||||
"@rjsf/core": "5.17.0",
|
||||
"@rjsf/material-ui": "5.17.0",
|
||||
"@rjsf/utils": "5.17.0",
|
||||
"@rjsf/validator-ajv8": "5.17.0",
|
||||
"@rjsf/core": "5.17.1",
|
||||
"@rjsf/material-ui": "5.17.1",
|
||||
"@rjsf/utils": "5.17.1",
|
||||
"@rjsf/validator-ajv8": "5.17.1",
|
||||
"@types/json-schema": "^7.0.9",
|
||||
"@types/react": "^16.13.1 || ^17.0.0 || ^18.0.0",
|
||||
"classnames": "^2.2.6",
|
||||
"flatted": "3.2.9",
|
||||
"flatted": "3.3.1",
|
||||
"humanize-duration": "^3.25.1",
|
||||
"json-schema": "^0.4.0",
|
||||
"json-schema-library": "^7.3.9",
|
||||
|
||||
@@ -68,10 +68,10 @@
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.61",
|
||||
"@react-hookz/web": "^24.0.0",
|
||||
"@rjsf/core": "5.17.0",
|
||||
"@rjsf/material-ui": "5.17.0",
|
||||
"@rjsf/utils": "5.17.0",
|
||||
"@rjsf/validator-ajv8": "5.17.0",
|
||||
"@rjsf/core": "5.17.1",
|
||||
"@rjsf/material-ui": "5.17.1",
|
||||
"@rjsf/utils": "5.17.1",
|
||||
"@rjsf/validator-ajv8": "5.17.1",
|
||||
"@types/react": "^16.13.1 || ^17.0.0 || ^18.0.0",
|
||||
"@uiw/react-codemirror": "^4.9.3",
|
||||
"classnames": "^2.2.6",
|
||||
|
||||
@@ -10,7 +10,7 @@ import { BackstagePlugin } from '@backstage/core-plugin-api';
|
||||
import { CardExtensionProps } from '@backstage/plugin-home-react';
|
||||
import { JSX as JSX_2 } from 'react';
|
||||
import { default as React_2 } from 'react';
|
||||
import { ResultHighlight } from '@backstage/plugin-search-common';
|
||||
import type { ResultHighlight } from '@backstage/plugin-search-common';
|
||||
import { SearchResultListItemExtensionProps } from '@backstage/plugin-search-react';
|
||||
|
||||
// @public
|
||||
|
||||
+7
-6
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import _unescape from 'lodash/unescape';
|
||||
import { Link } from '@backstage/core-components';
|
||||
import {
|
||||
Divider,
|
||||
@@ -26,8 +25,9 @@ import {
|
||||
Chip,
|
||||
} from '@material-ui/core';
|
||||
import { useAnalytics } from '@backstage/core-plugin-api';
|
||||
import { ResultHighlight } from '@backstage/plugin-search-common';
|
||||
import type { ResultHighlight } from '@backstage/plugin-search-common';
|
||||
import { HighlightedSearchResultText } from '@backstage/plugin-search-react';
|
||||
import { decodeHtml } from '../../util';
|
||||
|
||||
/**
|
||||
* Props for {@link StackOverflowSearchResultListItem}
|
||||
@@ -45,6 +45,7 @@ export const StackOverflowSearchResultListItem = (
|
||||
props: StackOverflowSearchResultListItemProps,
|
||||
) => {
|
||||
const { result, highlight } = props;
|
||||
|
||||
const analytics = useAnalytics();
|
||||
|
||||
const handleClick = () => {
|
||||
@@ -69,12 +70,12 @@ export const StackOverflowSearchResultListItem = (
|
||||
<Link to={result.location} noTrack onClick={handleClick}>
|
||||
{highlight?.fields?.title ? (
|
||||
<HighlightedSearchResultText
|
||||
text={highlight.fields.title}
|
||||
text={decodeHtml(highlight.fields.title)}
|
||||
preTag={highlight.preTag}
|
||||
postTag={highlight.postTag}
|
||||
/>
|
||||
) : (
|
||||
_unescape(result.title)
|
||||
decodeHtml(result.title)
|
||||
)}
|
||||
</Link>
|
||||
}
|
||||
@@ -83,13 +84,13 @@ export const StackOverflowSearchResultListItem = (
|
||||
<>
|
||||
Author:{' '}
|
||||
<HighlightedSearchResultText
|
||||
text={highlight.fields.text}
|
||||
text={decodeHtml(highlight.fields.text)}
|
||||
preTag={highlight.preTag}
|
||||
postTag={highlight.postTag}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
`Author: ${result.text}`
|
||||
`Author: ${decodeHtml(result.text)}`
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
* Copyright 2024 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.
|
||||
@@ -14,13 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// This file makes it possible to run "yarn knex migrate:make some_file_name"
|
||||
// to assist in making new migrations
|
||||
module.exports = {
|
||||
client: 'better-sqlite3',
|
||||
connection: ':memory:',
|
||||
useNullAsDefault: true,
|
||||
migrations: {
|
||||
directory: './migrations',
|
||||
},
|
||||
};
|
||||
export function decodeHtml(input: string) {
|
||||
const textContainer = document.createElement('textarea');
|
||||
textContainer.innerHTML = input;
|
||||
return textContainer.value;
|
||||
}
|
||||
@@ -47,7 +47,9 @@ const RadarTimeline = (props: Props): JSX.Element => {
|
||||
<Table aria-label="simple table">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell align="left">Moved in direction</TableCell>
|
||||
<TableCell align="left" style={{ wordBreak: 'normal' }}>
|
||||
Moved in direction
|
||||
</TableCell>
|
||||
<TableCell align="left">Moved to ring</TableCell>
|
||||
<TableCell align="left">Moved on date</TableCell>
|
||||
<TableCell align="left">Description</TableCell>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
* Copyright 2024 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.
|
||||
@@ -14,15 +14,20 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// Update with your config settings.
|
||||
// @ts-check
|
||||
|
||||
// This file makes it possible to run "yarn knex migrate:make some_file_name"
|
||||
// to assist in making new migrations
|
||||
module.exports = {
|
||||
client: 'better-sqlite3',
|
||||
connection: ':memory:',
|
||||
useNullAsDefault: true,
|
||||
migrations: {
|
||||
directory: './migrations',
|
||||
},
|
||||
/**
|
||||
* @param { import("knex").Knex } knex
|
||||
* @returns { Promise<void> }
|
||||
*/
|
||||
exports.up = async function up(knex) {
|
||||
// await knex.schema...
|
||||
};
|
||||
|
||||
/**
|
||||
* @param { import("knex").Knex } knex
|
||||
* @returns { Promise<void> }
|
||||
*/
|
||||
exports.down = async function down(knex) {
|
||||
// await knex.schema...
|
||||
};
|
||||
+43
-43
@@ -2842,90 +2842,90 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-darwin-arm64@npm:1.4.1":
|
||||
version: 1.4.1
|
||||
resolution: "@swc/core-darwin-arm64@npm:1.4.1"
|
||||
"@swc/core-darwin-arm64@npm:1.4.2":
|
||||
version: 1.4.2
|
||||
resolution: "@swc/core-darwin-arm64@npm:1.4.2"
|
||||
conditions: os=darwin & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-darwin-x64@npm:1.4.1":
|
||||
version: 1.4.1
|
||||
resolution: "@swc/core-darwin-x64@npm:1.4.1"
|
||||
"@swc/core-darwin-x64@npm:1.4.2":
|
||||
version: 1.4.2
|
||||
resolution: "@swc/core-darwin-x64@npm:1.4.2"
|
||||
conditions: os=darwin & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-linux-arm-gnueabihf@npm:1.4.1":
|
||||
version: 1.4.1
|
||||
resolution: "@swc/core-linux-arm-gnueabihf@npm:1.4.1"
|
||||
"@swc/core-linux-arm-gnueabihf@npm:1.4.2":
|
||||
version: 1.4.2
|
||||
resolution: "@swc/core-linux-arm-gnueabihf@npm:1.4.2"
|
||||
conditions: os=linux & cpu=arm
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-linux-arm64-gnu@npm:1.4.1":
|
||||
version: 1.4.1
|
||||
resolution: "@swc/core-linux-arm64-gnu@npm:1.4.1"
|
||||
"@swc/core-linux-arm64-gnu@npm:1.4.2":
|
||||
version: 1.4.2
|
||||
resolution: "@swc/core-linux-arm64-gnu@npm:1.4.2"
|
||||
conditions: os=linux & cpu=arm64 & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-linux-arm64-musl@npm:1.4.1":
|
||||
version: 1.4.1
|
||||
resolution: "@swc/core-linux-arm64-musl@npm:1.4.1"
|
||||
"@swc/core-linux-arm64-musl@npm:1.4.2":
|
||||
version: 1.4.2
|
||||
resolution: "@swc/core-linux-arm64-musl@npm:1.4.2"
|
||||
conditions: os=linux & cpu=arm64 & libc=musl
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-linux-x64-gnu@npm:1.4.1":
|
||||
version: 1.4.1
|
||||
resolution: "@swc/core-linux-x64-gnu@npm:1.4.1"
|
||||
"@swc/core-linux-x64-gnu@npm:1.4.2":
|
||||
version: 1.4.2
|
||||
resolution: "@swc/core-linux-x64-gnu@npm:1.4.2"
|
||||
conditions: os=linux & cpu=x64 & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-linux-x64-musl@npm:1.4.1":
|
||||
version: 1.4.1
|
||||
resolution: "@swc/core-linux-x64-musl@npm:1.4.1"
|
||||
"@swc/core-linux-x64-musl@npm:1.4.2":
|
||||
version: 1.4.2
|
||||
resolution: "@swc/core-linux-x64-musl@npm:1.4.2"
|
||||
conditions: os=linux & cpu=x64 & libc=musl
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-win32-arm64-msvc@npm:1.4.1":
|
||||
version: 1.4.1
|
||||
resolution: "@swc/core-win32-arm64-msvc@npm:1.4.1"
|
||||
"@swc/core-win32-arm64-msvc@npm:1.4.2":
|
||||
version: 1.4.2
|
||||
resolution: "@swc/core-win32-arm64-msvc@npm:1.4.2"
|
||||
conditions: os=win32 & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-win32-ia32-msvc@npm:1.4.1":
|
||||
version: 1.4.1
|
||||
resolution: "@swc/core-win32-ia32-msvc@npm:1.4.1"
|
||||
"@swc/core-win32-ia32-msvc@npm:1.4.2":
|
||||
version: 1.4.2
|
||||
resolution: "@swc/core-win32-ia32-msvc@npm:1.4.2"
|
||||
conditions: os=win32 & cpu=ia32
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-win32-x64-msvc@npm:1.4.1":
|
||||
version: 1.4.1
|
||||
resolution: "@swc/core-win32-x64-msvc@npm:1.4.1"
|
||||
"@swc/core-win32-x64-msvc@npm:1.4.2":
|
||||
version: 1.4.2
|
||||
resolution: "@swc/core-win32-x64-msvc@npm:1.4.2"
|
||||
conditions: os=win32 & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core@npm:^1.3.46":
|
||||
version: 1.4.1
|
||||
resolution: "@swc/core@npm:1.4.1"
|
||||
version: 1.4.2
|
||||
resolution: "@swc/core@npm:1.4.2"
|
||||
dependencies:
|
||||
"@swc/core-darwin-arm64": 1.4.1
|
||||
"@swc/core-darwin-x64": 1.4.1
|
||||
"@swc/core-linux-arm-gnueabihf": 1.4.1
|
||||
"@swc/core-linux-arm64-gnu": 1.4.1
|
||||
"@swc/core-linux-arm64-musl": 1.4.1
|
||||
"@swc/core-linux-x64-gnu": 1.4.1
|
||||
"@swc/core-linux-x64-musl": 1.4.1
|
||||
"@swc/core-win32-arm64-msvc": 1.4.1
|
||||
"@swc/core-win32-ia32-msvc": 1.4.1
|
||||
"@swc/core-win32-x64-msvc": 1.4.1
|
||||
"@swc/core-darwin-arm64": 1.4.2
|
||||
"@swc/core-darwin-x64": 1.4.2
|
||||
"@swc/core-linux-arm-gnueabihf": 1.4.2
|
||||
"@swc/core-linux-arm64-gnu": 1.4.2
|
||||
"@swc/core-linux-arm64-musl": 1.4.2
|
||||
"@swc/core-linux-x64-gnu": 1.4.2
|
||||
"@swc/core-linux-x64-musl": 1.4.2
|
||||
"@swc/core-win32-arm64-msvc": 1.4.2
|
||||
"@swc/core-win32-ia32-msvc": 1.4.2
|
||||
"@swc/core-win32-x64-msvc": 1.4.2
|
||||
"@swc/counter": ^0.1.2
|
||||
"@swc/types": ^0.1.5
|
||||
peerDependencies:
|
||||
@@ -2954,7 +2954,7 @@ __metadata:
|
||||
peerDependenciesMeta:
|
||||
"@swc/helpers":
|
||||
optional: true
|
||||
checksum: 3549b4a0ba8201be2dd39954ef8375df5d028da5688a68211dc5cbf69ff7a43533e9ffd36f629095f51555552f4df52116384ef779d5ec45c2b9de938e651d57
|
||||
checksum: b17afda692b2627d3a82e589f1b29cef31bdee626a2dd997d78312dcbfc6eb701850fbab22e85f02b1261da39f0b0afb6a236c6065f6d0d7478cff939ca5a888
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
Reference in New Issue
Block a user