Merge branch 'master' into scaffolderPromMetrics
Signed-off-by: spencerrichardhenry <46569542+spencerrichardhenry@users.noreply.github.com>
This commit is contained in:
@@ -12,7 +12,7 @@ Using Cookiecutter extensions is a two-step process:
|
||||
This step depends on how the scaffolder is setup to use Cookiecutter:
|
||||
|
||||
- Using a local Cookiecutter, or
|
||||
- Using a Cookiecutter Docker image, e.g. [spotify/backstage-cookiecutter](https://github.com/backstage/backstage/blob/37e35b91/plugins/scaffolder-backend/scripts/Cookiecutter.dockerfile).
|
||||
- Using a Cookiecutter Docker image, e.g. [spotify/backstage-cookiecutter](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend/scripts/Cookiecutter.dockerfile).
|
||||
|
||||
Say we want to install [`jinja2_custom_filters_extension`](https://pypi.org/project/jinja2-custom-filters-extension/) to use the `upper_case_first_letter` filter in a Cookiecutter template.
|
||||
|
||||
|
||||
@@ -8,30 +8,28 @@ API requests from frontend plugins include an authorization header with a Backst
|
||||
|
||||
As techdocs HTML pages load assets without an Authorization header the code below also sets a token cookie when the user logs in (and when the token is about to expire).
|
||||
|
||||
Create `packages/backend/src/authMiddleware.ts`:
|
||||
|
||||
```typescript
|
||||
// packages/backend/src/index.ts from a create-app deployment
|
||||
|
||||
import cookieParser from 'cookie-parser';
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { JWT } from 'jose';
|
||||
import { URL } from 'url';
|
||||
import { SingleHostDiscovery } from '@backstage/backend-common';
|
||||
import type { Config } from '@backstage/config';
|
||||
import {
|
||||
IdentityClient,
|
||||
getBearerTokenFromAuthorizationHeader,
|
||||
IdentityClient,
|
||||
} from '@backstage/plugin-auth-node';
|
||||
|
||||
// ...
|
||||
import { NextFunction, Request, Response, RequestHandler } from 'express';
|
||||
import { decodeJwt } from 'jose';
|
||||
import { URL } from 'url';
|
||||
import { PluginEnvironment } from './types';
|
||||
|
||||
function setTokenCookie(
|
||||
res: Response,
|
||||
options: { token: string; secure: boolean; cookieDomain: string },
|
||||
) {
|
||||
try {
|
||||
const payload = JWT.decode(options.token) as object & {
|
||||
exp: number;
|
||||
};
|
||||
res.cookie(`token`, options.token, {
|
||||
expires: new Date(payload?.exp ? payload?.exp * 1000 : 0),
|
||||
const payload = decodeJwt(options.token);
|
||||
res.cookie('token', options.token, {
|
||||
expires: new Date(payload.exp ? payload.exp * 1000 : 0),
|
||||
secure: options.secure,
|
||||
sameSite: 'lax',
|
||||
domain: options.cookieDomain,
|
||||
@@ -43,9 +41,10 @@ function setTokenCookie(
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
// ...
|
||||
|
||||
export const createAuthMiddleware = async (
|
||||
config: Config,
|
||||
appEnv: PluginEnvironment,
|
||||
) => {
|
||||
const discovery = SingleHostDiscovery.fromConfig(config);
|
||||
const identity = IdentityClient.create({
|
||||
discovery,
|
||||
@@ -54,7 +53,7 @@ async function main() {
|
||||
const baseUrl = config.getString('backend.baseUrl');
|
||||
const secure = baseUrl.startsWith('https://');
|
||||
const cookieDomain = new URL(baseUrl).hostname;
|
||||
const authMiddleware = async (
|
||||
const authMiddleware: RequestHandler = async (
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction,
|
||||
@@ -62,13 +61,21 @@ async function main() {
|
||||
try {
|
||||
const token =
|
||||
getBearerTokenFromAuthorizationHeader(req.headers.authorization) ||
|
||||
req.cookies['token'];
|
||||
req.user = await identity.authenticate(token);
|
||||
(req.cookies.token as string | undefined);
|
||||
if (!token) {
|
||||
res.status(401).send('Unauthorized');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
req.user = await identity.authenticate(token);
|
||||
} catch {
|
||||
await appEnv.tokenManager.authenticate(token);
|
||||
}
|
||||
if (!req.headers.authorization) {
|
||||
// Authorization header may be forwarded by plugin requests
|
||||
req.headers.authorization = `Bearer ${token}`;
|
||||
}
|
||||
if (token !== req.cookies['token']) {
|
||||
if (token && token !== req.cookies.token) {
|
||||
setTokenCookie(res, {
|
||||
token,
|
||||
secure,
|
||||
@@ -77,9 +84,24 @@ async function main() {
|
||||
}
|
||||
next();
|
||||
} catch (error) {
|
||||
res.status(401).send(`Unauthorized`);
|
||||
res.status(401).send('Unauthorized');
|
||||
}
|
||||
};
|
||||
return authMiddleware;
|
||||
};
|
||||
```
|
||||
|
||||
```typescript
|
||||
// packages/backend/src/index.ts from a create-app deployment
|
||||
|
||||
import { createAuthMiddleware } from './authMiddleware';
|
||||
|
||||
// ...
|
||||
|
||||
async function main() {
|
||||
// ...
|
||||
|
||||
const authMiddleware = await createAuthMiddleware(config, appEnv);
|
||||
|
||||
const apiRouter = Router();
|
||||
apiRouter.use(cookieParser());
|
||||
@@ -99,12 +121,10 @@ async function main() {
|
||||
}
|
||||
```
|
||||
|
||||
Create `packages/app/src/cookieAuth.ts`:
|
||||
|
||||
```typescript
|
||||
// packages/app/src/App.tsx from a create-app deployment
|
||||
|
||||
import { discoveryApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
|
||||
// ...
|
||||
import type { IdentityApi } from '@backstage/core-plugin-api';
|
||||
|
||||
// Parses supplied JWT token and returns the payload
|
||||
function parseJwt(token: string): { exp: number } {
|
||||
@@ -113,9 +133,11 @@ function parseJwt(token: string): { exp: number } {
|
||||
const jsonPayload = decodeURIComponent(
|
||||
atob(base64)
|
||||
.split('')
|
||||
.map(function (c) {
|
||||
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
|
||||
})
|
||||
.map(
|
||||
c =>
|
||||
// eslint-disable-next-line prefer-template
|
||||
'%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2),
|
||||
)
|
||||
.join(''),
|
||||
);
|
||||
|
||||
@@ -132,7 +154,7 @@ function msUntilExpiry(token: string): number {
|
||||
|
||||
// Calls the specified url regularly using an auth token to set a token cookie
|
||||
// to authorize regular HTTP requests when loading techdocs
|
||||
async function setTokenCookie(url: string, identityApi: IdentityApi) {
|
||||
export async function setTokenCookie(url: string, identityApi: IdentityApi) {
|
||||
const { token } = await identityApi.getCredentials();
|
||||
if (!token) {
|
||||
return;
|
||||
@@ -155,6 +177,14 @@ async function setTokenCookie(url: string, identityApi: IdentityApi) {
|
||||
ms > 0 ? ms : 10000,
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
// packages/app/src/App.tsx from a create-app deployment
|
||||
|
||||
import { setTokenCookie } from './cookieAuth';
|
||||
|
||||
// ...
|
||||
|
||||
const app = createApp({
|
||||
// ...
|
||||
|
||||
@@ -2,20 +2,47 @@
|
||||
|
||||
## Overview
|
||||
|
||||
The Orphan Clean Up script is a basic PowerShell script to delete orphaned entities in the catalog. This script also assumes that you do not have authentication setup for your Backstage API endpoints.
|
||||
The Orphan Clean Up scripts are a basic scripts to delete orphaned entities in the catalog.
|
||||
|
||||
This script also assumes that you do not have authentication setup for your Backstage API endpoints.
|
||||
|
||||
_Warning:_ There is a risk of entities being orphaned (and being deleted by this script) in case of the location having problems and returning a 404 status code. This might lead to accidental deletion of entities until the processing loop has recreated the entity.
|
||||
|
||||
## Requirements
|
||||
## PowerShell
|
||||
|
||||
A PowerShell implementation of the orphan cleanup script.
|
||||
|
||||
### Requirements
|
||||
|
||||
This script is PowerShell based so therefore needs to be ran in a PowerShell session. If you are not able to use PowerShell the script should give you a clear idea as to how to create it with another scripting language like Bash.
|
||||
|
||||
## Usage
|
||||
### Usage
|
||||
|
||||
Here's how to use the script:
|
||||
|
||||
1. Download the script
|
||||
1. Download the `OrphanCleanUp.ps1` script
|
||||
2. Now start a PowerShell session
|
||||
3. Next navigate to the location you downloaded the script
|
||||
4. Then run this command replacing `https:\\backstage.my-company.com` with the URL of your Backstage instance: `.\OrphanCleanUp.ps1 https:\\backstage.my-company.com`
|
||||
5. The script will output the number of orphaned entities it finds and then the name for each one it deletes
|
||||
|
||||
## Bash
|
||||
|
||||
A bash implementation of the orphan cleanup script.
|
||||
|
||||
### Requirements
|
||||
|
||||
This script is shell based and requires the following programs to be installed on your system:
|
||||
|
||||
- curl
|
||||
- jq
|
||||
|
||||
### Usage
|
||||
|
||||
Here's how to use the script:
|
||||
|
||||
1. Download the `orphan_cleanup.sh` script
|
||||
2. Now start a bash session
|
||||
3. Next navigate to the location you downloaded the script
|
||||
4. Then run this command replacing `https://backstage.my-company.com` with the URL of your Backstage instance: `./orphan_cleanup.sh https://backstage.my-company.com`
|
||||
5. The script will output the number of orphaned entities it finds and then the name for each one it deletes
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Cleanes up orphaned entities for the provided Backstage URL, defaults to the local backend
|
||||
BACKSTAGE_URL=${1:-'http://localhost:7007'}
|
||||
echo $BACKSTAGE_URL
|
||||
|
||||
ORPHAN_API_URL="$BACKSTAGE_URL/api/catalog/entities?filter=metadata.annotations.backstage.io/orphan=true"
|
||||
ORPHAN_DELETE_API_URL="$BACKSTAGE_URL/api/catalog/entities/by-uid"
|
||||
|
||||
ORPHANS=$(curl -s $ORPHAN_API_URL)
|
||||
|
||||
echo ""
|
||||
echo "Found $(echo $ORPHANS | jq length ) orphaned entities"
|
||||
echo ""
|
||||
|
||||
jq -c '.[]' <<< $ORPHANS | while read ORPHAN; do
|
||||
echo $ORPHAN | jq "."
|
||||
echo "Deleting orphan entity: $(echo $ORPHAN | jq -r .metadata.name) of kind: $(echo $ORPHAN | jq -r .kind)"
|
||||
curl -X DELETE "$ORPHAN_DELETE_API_URL/$(echo $ORPHAN | jq -r .metadata.uid)"
|
||||
done
|
||||
@@ -0,0 +1,30 @@
|
||||
# Upgrade Backstage App
|
||||
|
||||
## Overview
|
||||
|
||||
The script upgrades the version of Backstage created via the create-app plugin. It lets you resolve conflicts iteratively with your own merge [tool](https://www.git-scm.com/docs/git-mergetool).
|
||||
|
||||
## Requirements
|
||||
|
||||
You will need to have the following tools in your shell environment:
|
||||
|
||||
- git
|
||||
- curl
|
||||
- jq
|
||||
|
||||
## Usage
|
||||
|
||||
Here's how to use the script:
|
||||
|
||||
1. download the script
|
||||
2. copy it in the root of your app
|
||||
3. bootstrap a git repository (you may already have done so):
|
||||
|
||||
```bash
|
||||
git init
|
||||
git add .
|
||||
git commit -m "initial commit"
|
||||
```
|
||||
|
||||
4. run `sh upgrade-backstage-app.sh [optional-backstage-version]`
|
||||
5. resolve any conflicts iteratively
|
||||
@@ -0,0 +1,15 @@
|
||||
#!/bin/sh
|
||||
CURRENT_VERSION="v$(cat backstage.json | jq -r '.version')"
|
||||
TARGET_VERSION=${1:-"$(curl -s https://api.github.com/repos/backstage/backstage/releases/latest | jq -r '.tag_name')"}
|
||||
CREATE_APP_CURRENT_VERSION=$(curl -s https://raw.githubusercontent.com/backstage/backstage/$CURRENT_VERSION/packages/create-app/package.json | jq -r '.version')
|
||||
CREATE_APP_TARGET_VERSION=$(curl -s https://raw.githubusercontent.com/backstage/backstage/$TARGET_VERSION/packages/create-app/package.json | jq -r '.version')
|
||||
|
||||
if [ "$CURRENT_VERSION" == "$TARGET_VERSION" ]; then
|
||||
echo "Already up to date"
|
||||
else
|
||||
echo "Attempting upgrade from Backstage $CURRENT_VERSION (create-app $CREATE_APP_CURRENT_VERSION) to $TARGET_VERSION ($CREATE_APP_TARGET_VERSION)"
|
||||
rm -rf .upgrade && mkdir .upgrade
|
||||
curl -s https://raw.githubusercontent.com/backstage/upgrade-helper-diff/master/diffs/$CREATE_APP_CURRENT_VERSION..$CREATE_APP_TARGET_VERSION.diff > .upgrade/upgrade.diff
|
||||
git apply -3 .upgrade/upgrade.diff
|
||||
git mergetool
|
||||
fi
|
||||
Reference in New Issue
Block a user