plugin(amplication): add Amplication plugins to community plugins (#2919)
* merge branch 'backstage:main' into main * feat: add Amplication plugins for Backstage catalog and scaffolder * chore: Remove trailing whitespace in Amplication catalog-info.yaml * feat: add amplication backend modules for Backstage catalog and scaffolder * chore: add repository and backstage configuration to Amplication backend modules * test: simplify AmplicationTemplatesProcessor test entities and assertions * test: remove unused imports from AmplicationTemplatesProcessor test * docs: update Amplication plugin documentation and refactor processor * fix: Remove unnecessary error logging in Amplication template scaffolding Signed-off-by: Itai Nathaniel <itainathaniel@gmail.com>
This commit is contained in:
parent
6465acfe5f
commit
9b6048a045
|
|
@ -0,0 +1,8 @@
|
|||
# Changesets
|
||||
|
||||
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
|
||||
with multi-package repos, or single-package repos to help you version and publish your code. You can
|
||||
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
|
||||
|
||||
We have a quick list of common questions to get you started engaging with this project in
|
||||
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
|
||||
"changelog": "@changesets/cli/changelog",
|
||||
"commit": false,
|
||||
"fixed": [],
|
||||
"linked": [],
|
||||
"access": "public",
|
||||
"baseBranch": "main",
|
||||
"updateInternalDependencies": "patch",
|
||||
"privatePackages": {
|
||||
"tag": false,
|
||||
"version": false
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@backstage-community/plugin-catalog-backend-module-amplication': minor
|
||||
---
|
||||
|
||||
A backend module plugin for Amplication (https://amplication.com) has been created
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@backstage-community/plugin-scaffolder-backend-module-amplication': minor
|
||||
---
|
||||
|
||||
A scaffolder plugin for Amplication (https://amplication.com) has been created
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
.git
|
||||
.yarn/cache
|
||||
.yarn/install-state.gz
|
||||
node_modules
|
||||
packages/*/src
|
||||
packages/*/node_modules
|
||||
plugins
|
||||
*.local.yaml
|
||||
|
|
@ -0,0 +1 @@
|
|||
playwright.config.ts
|
||||
|
|
@ -0,0 +1 @@
|
|||
module.exports = require('../../.eslintrc.cjs');
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
# macOS
|
||||
.DS_Store
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Coverage directory generated when running tests with coverage
|
||||
coverage
|
||||
|
||||
# Dependencies
|
||||
node_modules/
|
||||
|
||||
# Yarn 3 files
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
|
||||
# Node version directives
|
||||
.nvmrc
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
|
||||
# Build output
|
||||
dist
|
||||
dist-types
|
||||
|
||||
# Temporary change files created by Vim
|
||||
*.swp
|
||||
|
||||
# MkDocs build output
|
||||
site
|
||||
|
||||
# Local configuration files
|
||||
*.local.yaml
|
||||
|
||||
# Sensitive credentials
|
||||
*-credentials.yaml
|
||||
|
||||
# vscode database functionality support files
|
||||
*.session.sql
|
||||
|
||||
# E2E test reports
|
||||
e2e-test-report/
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
dist
|
||||
dist-types
|
||||
coverage
|
||||
.vscode
|
||||
.eslintrc.js
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
# Changesets
|
||||
|
||||
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
|
||||
with multi-package repos, or single-package repos to help you version and publish your code. You can
|
||||
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
|
||||
|
||||
We have a quick list of common questions to get you started engaging with this project in
|
||||
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
|
||||
|
|
@ -0,0 +1 @@
|
|||
{ "version": "1.35.0" }
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
apiVersion: backstage.io/v1alpha1
|
||||
kind: Component
|
||||
metadata:
|
||||
name: amplication
|
||||
description: An example of a Backstage application.
|
||||
# Example for optional annotations
|
||||
# annotations:
|
||||
# github.com/project-slug: backstage/backstage
|
||||
# backstage.io/techdocs-ref: dir:.
|
||||
spec:
|
||||
type: website
|
||||
owner: john@example.com
|
||||
lifecycle: experimental
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
{
|
||||
"name": "@internal/amplication",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": "18 || 20"
|
||||
},
|
||||
"scripts": {
|
||||
"tsc": "tsc",
|
||||
"tsc:full": "tsc --skipLibCheck false --incremental false",
|
||||
"build:all": "backstage-cli repo build --all",
|
||||
"build:api-reports": "yarn build:api-reports:only --tsc",
|
||||
"build:api-reports:only": "backstage-repo-tools api-reports -o ae-wrong-input-file-type,ae-undocumented --validate-release-tags",
|
||||
"clean": "backstage-cli repo clean",
|
||||
"test": "backstage-cli repo test",
|
||||
"test:all": "backstage-cli repo test --coverage",
|
||||
"fix": "backstage-cli repo fix",
|
||||
"lint": "backstage-cli repo lint --since origin/main",
|
||||
"lint:all": "backstage-cli repo lint",
|
||||
"prettier:check": "prettier --check .",
|
||||
"new": "backstage-cli new --scope @backstage-community",
|
||||
"postinstall": "cd ../../ && yarn install"
|
||||
},
|
||||
"workspaces": {
|
||||
"packages": [
|
||||
"packages/*",
|
||||
"plugins/*"
|
||||
]
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/backstage/community-plugins",
|
||||
"directory": "workspaces/amplication"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.29.5",
|
||||
"@backstage/e2e-test-utils": "^0.1.1",
|
||||
"@backstage/repo-tools": "^0.12.1",
|
||||
"@changesets/cli": "^2.27.1",
|
||||
"@types/react": "^19.0.8",
|
||||
"@types/react-dom": "^19.0.3",
|
||||
"knip": "^5.27.4",
|
||||
"msw": "^2.7.0",
|
||||
"node-gyp": "^10.0.0",
|
||||
"prettier": "^2.3.2",
|
||||
"typescript": "~5.3.0"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18"
|
||||
},
|
||||
"prettier": "@backstage/cli/config/prettier",
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,ts,tsx,mjs,cjs}": [
|
||||
"eslint --fix",
|
||||
"prettier --write"
|
||||
],
|
||||
"*.{json,md}": [
|
||||
"prettier --write"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@material-ui/core": "^4.12.4"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
# The Plugins Folder
|
||||
|
||||
This is where your own plugins and their associated modules live, each in a
|
||||
separate folder of its own.
|
||||
|
||||
If you want to create a new plugin here, go to your project root directory, run
|
||||
the command `yarn new`, and follow the on-screen instructions.
|
||||
|
||||
You can also check out existing plugins on [the plugin marketplace](https://backstage.io/plugins)!
|
||||
|
|
@ -0,0 +1 @@
|
|||
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
# Amplication backend plugin for Backstage
|
||||
|
||||
The Amplication backend plugin integrates Amplication templates into Backstage.
|
||||
|
||||
## Capabilities
|
||||
|
||||
The Amplication backend plugin has the following capabilities:
|
||||
|
||||
- Synchronization of Amplication templates in a workspace
|
||||
|
||||
## Installation
|
||||
|
||||
Run the following command to install the catalog backend module in your Backstage project
|
||||
|
||||
```bash
|
||||
yarn workspace backend add @backstage-community/plugin-catalog-backend-module-amplication
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
1. Add the following configuration to the app-config.yaml file, and customize the schedule to fit your needs:
|
||||
|
||||
```yaml title="app-config.yaml"
|
||||
catalog:
|
||||
locations:
|
||||
- type: amplication
|
||||
target: https://server.amplication.com/graphql
|
||||
```
|
||||
|
||||
2. Register the plugin in the `packages/backend/src/index.ts` file:
|
||||
|
||||
```ts title="packages/backend/src/index.ts"
|
||||
const backend = createBackend();
|
||||
|
||||
/* highlight-add-next-line */
|
||||
backend.add(
|
||||
import('@backstage-community/plugin-catalog-backend-module-amplication'),
|
||||
);
|
||||
|
||||
backend.start();
|
||||
```
|
||||
|
||||
# Amplication Processor plugin
|
||||
|
||||
Welcome to the Amplication Processor plugin!
|
||||
|
||||
This plugin is designed to process amplication templates from the Amplication API and add them to the Backstage catalog, alongside with three other entities:
|
||||
|
||||
- Kind: user:amplication-bot
|
||||
- Kind: System
|
||||
- Kind: API
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Amplication Token
|
||||
|
||||
## Usage
|
||||
|
||||
Install the plugin by running the following command **from your Backstage root directory**
|
||||
|
||||
`yarn --cwd packages/app add @backstage-community/plugin-catalog-backend-module-amplication`
|
||||
|
||||
Add the collator to your backend instance, along with the search plugin itself:
|
||||
|
||||
```tsx
|
||||
// packages/backend/src/index.ts
|
||||
import { createBackend } from '@backstage/backend-defaults';
|
||||
|
||||
const backend = createBackend();
|
||||
backend.add(
|
||||
import('@backstage-community/plugin-catalog-backend-module-amplication'),
|
||||
);
|
||||
backend.start();
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Configure your `app-config.yaml` with Amplication's configuration
|
||||
|
||||
```yaml title="app-config.yaml"
|
||||
amplication:
|
||||
appUrl: https://app.amplication.com
|
||||
apiUrl: https://server.amplication.com/graphql
|
||||
token: ${AMPLICATION_TOKEN}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If you encounter any issues, please refer to the [Amplication documentation](https://docs.amplication.com) for more information.
|
||||
|
||||
If the issue persists, please contact the Amplication support team.
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
"name": "@backstage-community/plugin-catalog-backend-module-amplication",
|
||||
"description": "The amplication backend module for the catalog plugin.",
|
||||
"version": "0.1.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "dist/index.cjs.js",
|
||||
"types": "dist/index.d.ts"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/backstage/community-plugins",
|
||||
"directory": "workspaces/amplication/plugins/catalog-backend-module-amplication"
|
||||
},
|
||||
"backstage": {
|
||||
"role": "backend-plugin-module",
|
||||
"pluginId": "catalog",
|
||||
"pluginPackage": "@backstage/plugin-catalog-backend"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "backstage-cli package start",
|
||||
"build": "backstage-cli package build",
|
||||
"lint": "backstage-cli package lint",
|
||||
"test": "backstage-cli package test",
|
||||
"clean": "backstage-cli package clean",
|
||||
"prepack": "backstage-cli package prepack",
|
||||
"postpack": "backstage-cli package postpack"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-plugin-api": "^1.1.1",
|
||||
"@backstage/catalog-model": "^1.7.3",
|
||||
"@backstage/plugin-catalog-common": "^1.1.3",
|
||||
"@backstage/plugin-catalog-node": "^1.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/backend-test-utils": "^1.2.1",
|
||||
"@backstage/cli": "^0.29.5",
|
||||
"msw": "^2.7.0"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
## API Report File for "@backstage-community/plugin-catalog-backend-module-amplication"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
import { BackendFeature } from '@backstage/backend-plugin-api';
|
||||
|
||||
// @public
|
||||
const catalogModuleAmplicationGraphqlProcessor: BackendFeature;
|
||||
export default catalogModuleAmplicationGraphqlProcessor;
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
```
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* Copyright 2025 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 { catalogModuleAmplicationGraphqlProcessor as default } from './module';
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright 2025 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 {
|
||||
coreServices,
|
||||
createBackendModule,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
|
||||
import { AmplicationTemplatesProcessor } from './processor/AmplicationTemplatesProcessor';
|
||||
|
||||
/**
|
||||
* The processor module for the amplication plugin.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const catalogModuleAmplicationGraphqlProcessor = createBackendModule({
|
||||
pluginId: 'catalog',
|
||||
moduleId: 'amplication-graphql-processor',
|
||||
register(reg) {
|
||||
reg.registerInit({
|
||||
deps: {
|
||||
config: coreServices.rootConfig,
|
||||
logger: coreServices.logger,
|
||||
catalog: catalogProcessingExtensionPoint,
|
||||
},
|
||||
async init({ logger, catalog, config }) {
|
||||
catalog.addProcessor(new AmplicationTemplatesProcessor(logger, config));
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,163 @@
|
|||
/*
|
||||
* Copyright 2023 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 { AmplicationTemplatesProcessor } from './AmplicationTemplatesProcessor';
|
||||
import {
|
||||
LoggerService,
|
||||
RootConfigService,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { mockServices } from '@backstage/backend-test-utils';
|
||||
import { LocationSpec } from '@backstage/plugin-catalog-common';
|
||||
import { CatalogProcessorCache } from '@backstage/plugin-catalog-node/index';
|
||||
|
||||
import { http, HttpResponse } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
|
||||
describe('AmplicationTemplatesProcessor Name', () => {
|
||||
it('runs successfully', async () => {
|
||||
const processor = buildProcessor();
|
||||
|
||||
expect(processor.getProcessorName()).toBe('AmplicationTemplatesProcessor');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Calling the API', () => {
|
||||
const handlers = [
|
||||
http.post('http://example.com/api/templates', () => {
|
||||
return HttpResponse.json({
|
||||
data: {
|
||||
catalog: {
|
||||
totalCount: 2,
|
||||
data: [
|
||||
{
|
||||
id: 'cm6gs6t6v000fjx5te4xlrcfx',
|
||||
name: 'Resource Itai-template',
|
||||
description: 'Template created from an existing resource',
|
||||
resourceType: 'ServiceTemplate',
|
||||
project: {
|
||||
id: 'cm6gb3j0a000q14gzlq9m7h1o',
|
||||
name: 'Sample Project',
|
||||
},
|
||||
blueprint: {
|
||||
id: 'cm6grdfol000bjx5t7gixjk0q',
|
||||
name: "Itai's Blueprint",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'cm6sbna88000xutjte7zu96ee',
|
||||
name: 'pick-me-template',
|
||||
description: 'Template created from an existing resource',
|
||||
resourceType: 'ServiceTemplate',
|
||||
project: {
|
||||
id: 'cm6hv1ugf005hjx5tr087j8d3',
|
||||
name: 'ma-yesh-beze',
|
||||
},
|
||||
blueprint: {
|
||||
id: 'cm6gb3j00000p14gz2n11otq4',
|
||||
name: "Node.js Service (Amplication's Standard)",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
}),
|
||||
];
|
||||
|
||||
const server = setupServer(...handlers);
|
||||
|
||||
server.listen();
|
||||
|
||||
it('returns false if location is not a graphql', async () => {
|
||||
const processor = buildProcessor();
|
||||
|
||||
const location: LocationSpec = {
|
||||
type: 'url',
|
||||
target: 'http://example.com/api/templates',
|
||||
};
|
||||
const emit = jest.fn();
|
||||
const parser = jest.fn();
|
||||
const cache: CatalogProcessorCache = {
|
||||
get: jest.fn(),
|
||||
set: jest.fn(),
|
||||
};
|
||||
const result = await processor.readLocation(
|
||||
location,
|
||||
true,
|
||||
emit,
|
||||
parser,
|
||||
cache,
|
||||
);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('returns true if location is a graphql', async () => {
|
||||
const processor = buildProcessor();
|
||||
|
||||
const location: LocationSpec = {
|
||||
type: 'amplication',
|
||||
target: 'http://example.com/api/templates',
|
||||
};
|
||||
const emit = jest.fn();
|
||||
const parser = jest.fn();
|
||||
const cache: CatalogProcessorCache = {
|
||||
get: jest.fn(),
|
||||
set: jest.fn(),
|
||||
};
|
||||
const result = await processor.readLocation(
|
||||
location,
|
||||
true,
|
||||
emit,
|
||||
parser,
|
||||
cache,
|
||||
);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('emit() is called with the correct arguments', async () => {
|
||||
const processor = buildProcessor();
|
||||
|
||||
const location: LocationSpec = {
|
||||
type: 'amplication',
|
||||
target: 'http://example.com/api/templates',
|
||||
};
|
||||
const emit = jest.fn();
|
||||
const parser = jest.fn();
|
||||
const cache: CatalogProcessorCache = {
|
||||
get: jest.fn(),
|
||||
set: jest.fn(),
|
||||
};
|
||||
const result = await processor.readLocation(
|
||||
location,
|
||||
true,
|
||||
emit,
|
||||
parser,
|
||||
cache,
|
||||
);
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(cache.get).toHaveBeenCalled();
|
||||
expect(emit).toHaveBeenCalled();
|
||||
expect(emit).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
function buildProcessor() {
|
||||
const logger: LoggerService = mockServices.logger.mock();
|
||||
const config: RootConfigService = mockServices.rootConfig.mock();
|
||||
return new AmplicationTemplatesProcessor(logger, config);
|
||||
}
|
||||
|
|
@ -0,0 +1,188 @@
|
|||
/*
|
||||
* Copyright 2025 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 { Entity, EntityMeta } from '@backstage/catalog-model';
|
||||
import {
|
||||
processingResult,
|
||||
CatalogProcessor,
|
||||
CatalogProcessorEmit,
|
||||
CatalogProcessorCache,
|
||||
CatalogProcessorParser,
|
||||
} from '@backstage/plugin-catalog-node';
|
||||
import {
|
||||
LoggerService,
|
||||
RootConfigService,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
|
||||
import { LocationSpec } from '@backstage/plugin-catalog-common';
|
||||
|
||||
const CACHE_KEY = 'v1';
|
||||
|
||||
type CacheItem = {
|
||||
etag: string;
|
||||
json: any;
|
||||
};
|
||||
|
||||
export class AmplicationTemplatesProcessor implements CatalogProcessor {
|
||||
constructor(
|
||||
private readonly logger: LoggerService,
|
||||
private readonly config: RootConfigService,
|
||||
) {
|
||||
this.logger.info(`${this.getProcessorName()} is loading...`);
|
||||
}
|
||||
|
||||
getProcessorName(): string {
|
||||
return 'AmplicationTemplatesProcessor';
|
||||
}
|
||||
|
||||
async readLocation(
|
||||
location: LocationSpec,
|
||||
_optional: boolean,
|
||||
emit: CatalogProcessorEmit,
|
||||
_parser: CatalogProcessorParser,
|
||||
cache: CatalogProcessorCache,
|
||||
): Promise<boolean> {
|
||||
if (location.type !== 'amplication') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const cacheItem = await cache.get<CacheItem>(CACHE_KEY);
|
||||
try {
|
||||
const response = await this.getCatalog(location);
|
||||
const etag = response.headers.get('etag') || 'NO ETAG';
|
||||
|
||||
if (etag !== cacheItem?.etag) {
|
||||
const json = await response.json();
|
||||
json.data.catalog.data.forEach((item: any) => {
|
||||
this.emitComponent(
|
||||
item,
|
||||
emit,
|
||||
location,
|
||||
json.data.currentWorkspace.id,
|
||||
);
|
||||
});
|
||||
|
||||
await cache.set<CacheItem>(CACHE_KEY, {
|
||||
etag: etag,
|
||||
json: json,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
const message = `Unable to read ${location.type}, ${error}`;
|
||||
emit(processingResult.generalError(location, message));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async getCatalog(location: LocationSpec) {
|
||||
const token = this.config.getString('amplication.token');
|
||||
|
||||
return await fetch(location.target, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
variables: {
|
||||
take: 100,
|
||||
skip: 0,
|
||||
where: { resourceType: { in: ['ServiceTemplate'] } },
|
||||
},
|
||||
query: `query searchCatalog($where: ResourceWhereInputWithPropertiesFilter, $take: Int, $skip: Int) {
|
||||
currentWorkspace {
|
||||
id
|
||||
}
|
||||
catalog(where: $where, take: $take, skip: $skip) {
|
||||
totalCount
|
||||
data {
|
||||
id
|
||||
name
|
||||
description
|
||||
resourceType
|
||||
project {
|
||||
id
|
||||
name
|
||||
}
|
||||
blueprint {
|
||||
id
|
||||
name
|
||||
}
|
||||
serviceTemplate {
|
||||
id
|
||||
name
|
||||
projectId
|
||||
}
|
||||
gitRepository {
|
||||
name
|
||||
gitOrganization {
|
||||
name
|
||||
provider
|
||||
}
|
||||
}
|
||||
}
|
||||
__typename
|
||||
}
|
||||
}`,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
private getSanitizedName(name: string): string {
|
||||
return name
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9-_]/g, '-') // Replace any non-alphanumeric characters with a hyphen
|
||||
.replace(/-+/g, '-') // Replace multiple consecutive hyphens with a single hyphen
|
||||
.replace(/^-+|-+$/g, '') // Remove leading/trailing hyphens
|
||||
.slice(0, 63); // Truncate to 63 characters
|
||||
}
|
||||
|
||||
private emitComponent(
|
||||
item: any,
|
||||
emit: CatalogProcessorEmit,
|
||||
location: LocationSpec,
|
||||
workspaceId: string,
|
||||
) {
|
||||
const metadata: EntityMeta = {
|
||||
name: this.getSanitizedName(item.name),
|
||||
title: item.name,
|
||||
description: item.description,
|
||||
tags: ['amplication', item.resourceType.toLowerCase()],
|
||||
};
|
||||
|
||||
const spec = {
|
||||
id: item.id,
|
||||
type: item.resourceType,
|
||||
project: item.project.name,
|
||||
project_id: item.project.id,
|
||||
blueprint: item.blueprint?.name,
|
||||
blueprint_id: item.blueprint?.id,
|
||||
lifecycle: 'production',
|
||||
providesApis: ['amplication-api'],
|
||||
system: 'amplication',
|
||||
owner: 'user:amplication-bot',
|
||||
workspace: workspaceId,
|
||||
} as any;
|
||||
|
||||
const entity: Entity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Component',
|
||||
metadata: metadata,
|
||||
spec: spec,
|
||||
};
|
||||
emit(processingResult.entity(location, entity));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
# Amplication actions for Backstage
|
||||
|
||||
This module provides [Backstage](https://backstage.io/) template [actions](https://backstage.io/docs/features/software-templates/builtin-actions) for [Amplication](https://amplication.com/).
|
||||
|
||||
The following actions are currently supported in this module:
|
||||
|
||||
- Scaffold a new service
|
||||
|
||||
## Setup
|
||||
|
||||
You need to setup the [Amplication catalog backend plugin](../catalog-backend-module-amplication/README.md) before you move forward with any of the following steps if you haven't already.
|
||||
|
||||
## Installation
|
||||
|
||||
Run the following command to install the action package in your Backstage project
|
||||
|
||||
```bash
|
||||
yarn workspace backend add @backstage-community/plugin-scaffolder-backend-module-amplication
|
||||
```
|
||||
|
||||
### Installing the action on the backend
|
||||
|
||||
Add the following to your `packages/backend/src/index.ts` file:
|
||||
|
||||
```ts title="packages/backend/src/index.ts"
|
||||
const backend = createBackend();
|
||||
|
||||
// Add the following line
|
||||
backend.add(
|
||||
import('@backstage-community/plugin-scaffolder-backend-module-amplication'),
|
||||
);
|
||||
|
||||
backend.start();
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Configure your `app-config.yaml` with Amplication's configuration
|
||||
|
||||
```yaml title="app-config.yaml"
|
||||
amplication:
|
||||
appUrl: https://app.amplication.com
|
||||
apiUrl: https://server.amplication.com/graphql
|
||||
token: ${AMPLICATION_TOKEN}
|
||||
```
|
||||
|
||||
Add the Amplication actions to your templates, see the [example](./src/template/template.yaml) file in this repository for complete usage examples
|
||||
|
||||
```yaml
|
||||
id: scaffoldService
|
||||
name: Scaffold a New Service
|
||||
action: amplication:scaffold-service
|
||||
input:
|
||||
name: fooService
|
||||
description: fooService description
|
||||
project_id: 1a2b3c4d5e6f7g8h9i0j
|
||||
serviceTemplate_id: 9i8h7g6f5e4d3c2b1a0
|
||||
workspace_id: f1e2d3c4b5a6978685d4
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Action: amplication:scaffold-service
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter Name | Type | Required | Description | Example |
|
||||
| ------------------ | :----: | :------: | ------------------- | -------------------- |
|
||||
| name | string | Yes | Service name | foo |
|
||||
| description | string | No | Service description | foo description |
|
||||
| project_id | string | Yes | Project ID | 1a2b3c4d5e6f7g8h9i0j |
|
||||
| serviceTemplate_id | string | Yes | Service Template ID | 9i8h7g6f5e4d3c2b1a0 |
|
||||
| workspace_id | string | Yes | Workspace ID | f1e2d3c4b5a6978685d4 |
|
||||
|
||||
#### Output
|
||||
|
||||
This action doesn't have any outputs.
|
||||
|
||||
## Discover new services
|
||||
|
||||
If you're creating a new service with the right plugin installed, it will be created with a `catalog-info.yaml` file in the root of the service. With the following configuration, you could auto-discover the service and add it to your catalog:
|
||||
|
||||
```yaml
|
||||
catalog:
|
||||
providers:
|
||||
github:
|
||||
providerId:
|
||||
organization: <YOUR_GITHUB_ORGANIZATION>
|
||||
catalogPath: '**/catalog-info.yaml'
|
||||
filters:
|
||||
repository: '.*'
|
||||
```
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"name": "@backstage-community/plugin-scaffolder-backend-module-amplication",
|
||||
"description": "The amplication module for @backstage/plugin-scaffolder-backend",
|
||||
"version": "0.1.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "dist/index.cjs.js",
|
||||
"types": "dist/index.d.ts"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/backstage/community-plugins",
|
||||
"directory": "workspaces/amplication/plugins/scaffolder-backend-module-amplication"
|
||||
},
|
||||
"backstage": {
|
||||
"role": "backend-plugin-module",
|
||||
"pluginId": "scaffolder",
|
||||
"pluginPackage": "@backstage/plugin-scaffolder-backend"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "backstage-cli package start",
|
||||
"build": "backstage-cli package build",
|
||||
"lint": "backstage-cli package lint",
|
||||
"test": "backstage-cli package test",
|
||||
"clean": "backstage-cli package clean",
|
||||
"prepack": "backstage-cli package prepack",
|
||||
"postpack": "backstage-cli package postpack"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-plugin-api": "^1.1.1",
|
||||
"@backstage/config": "^1.3.2",
|
||||
"@backstage/plugin-scaffolder-node": "^0.6.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.29.5",
|
||||
"@backstage/plugin-scaffolder-node-test-utils": "^0.1.18"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
## API Report File for "@backstage-community/plugin-scaffolder-backend-module-amplication"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
import { BackendFeature } from '@backstage/backend-plugin-api';
|
||||
|
||||
// @public (undocumented)
|
||||
const amplicationModule: BackendFeature;
|
||||
export default amplicationModule;
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
```
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Config } from '@backstage/config';
|
||||
import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
|
||||
/**
|
||||
* Creates an action that creates an application in MTA.
|
||||
*/
|
||||
|
||||
/** @public */
|
||||
export function scaffoldResourceFromAmplicationTemplate({
|
||||
config,
|
||||
}: {
|
||||
config: Config;
|
||||
}) {
|
||||
return createTemplateAction<{
|
||||
name: string;
|
||||
description: string;
|
||||
project_id: string;
|
||||
serviceTemplate_id: string;
|
||||
workspace_id: string;
|
||||
}>({
|
||||
id: 'amplication:scaffold-service',
|
||||
description: 'Scaffold a new service from a template (using Amplication)',
|
||||
schema: {
|
||||
input: {
|
||||
type: 'object',
|
||||
required: ['name', 'project_id', 'serviceTemplate_id'],
|
||||
properties: {
|
||||
name: {
|
||||
title: 'Service Name',
|
||||
description: 'The name of the service',
|
||||
type: 'string',
|
||||
},
|
||||
description: {
|
||||
title: 'Service Description',
|
||||
description: 'The description of the service',
|
||||
type: 'string',
|
||||
},
|
||||
project_id: {
|
||||
title: 'Project ID',
|
||||
description: 'The ID of the project',
|
||||
type: 'string',
|
||||
},
|
||||
serviceTemplate_id: {
|
||||
title: 'Service Template ID',
|
||||
description: 'The ID of the service template',
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async handler(ctx) {
|
||||
ctx.logger.info(
|
||||
`Running amplication's service scaffolder for ${ctx.input.name}`,
|
||||
);
|
||||
|
||||
const appUrl = config.getString('amplication.appUrl');
|
||||
const apiUrl = config.getString('amplication.apiUrl');
|
||||
const token = config.getString('amplication.token');
|
||||
const httpOptions = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
operationName: 'createServiceFromTemplate',
|
||||
variables: {
|
||||
data: {
|
||||
name: ctx.input.name,
|
||||
description: ctx.input.description || '',
|
||||
project: {
|
||||
connect: {
|
||||
id: ctx.input.project_id,
|
||||
},
|
||||
},
|
||||
serviceTemplate: {
|
||||
id: ctx.input.serviceTemplate_id,
|
||||
},
|
||||
buildAfterCreation: true,
|
||||
},
|
||||
},
|
||||
query:
|
||||
'mutation createServiceFromTemplate($data: ResourceFromTemplateCreateInput!) {\n createResourceFromTemplate(data: $data) {\n id\n name\n description\n __typename\n }\n}',
|
||||
}),
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch(apiUrl, httpOptions);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const body = await response.json();
|
||||
if (body.errors) {
|
||||
throw new Error(`Error! ${body.errors[0].message}`);
|
||||
}
|
||||
|
||||
ctx.logger.info('Service created successfully');
|
||||
ctx.output(
|
||||
'url',
|
||||
`${appUrl}/${ctx.input.workspace_id}/${ctx.input.project_id}/${body.data.createResourceFromTemplate.id}`,
|
||||
);
|
||||
} catch (e) {
|
||||
throw new Error(`There was an issue with the request: ${e}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* Copyright 2025 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 { amplicationModule as default } from './module';
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright 2025 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 {
|
||||
coreServices,
|
||||
createBackendModule,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { scaffolderActionsExtensionPoint } from '@backstage/plugin-scaffolder-node/alpha';
|
||||
import { scaffoldResourceFromAmplicationTemplate } from './actions/amplication';
|
||||
/*
|
||||
* A backend module that integrates with the scaffolder to provide Amplication application creation.
|
||||
*/
|
||||
/** @public */
|
||||
export const amplicationModule = createBackendModule({
|
||||
pluginId: 'scaffolder',
|
||||
moduleId: 'amplication',
|
||||
register({ registerInit }) {
|
||||
registerInit({
|
||||
deps: {
|
||||
scaffolder: scaffolderActionsExtensionPoint,
|
||||
config: coreServices.rootConfig,
|
||||
},
|
||||
async init({ scaffolder, config }) {
|
||||
scaffolder.addActions(
|
||||
scaffoldResourceFromAmplicationTemplate({ config }),
|
||||
);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
apiVersion: scaffolder.backstage.io/v1beta3
|
||||
# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-template
|
||||
kind: Template
|
||||
metadata:
|
||||
name: scaffold-new-service-amplication
|
||||
title: Scaffold a New Service
|
||||
description: Scaffold a new service from a template (using Amplication)
|
||||
tags:
|
||||
- amplication
|
||||
spec:
|
||||
type: service
|
||||
parameters:
|
||||
- title: Service Information
|
||||
required:
|
||||
- name
|
||||
- template
|
||||
properties:
|
||||
name:
|
||||
title: Name
|
||||
type: string
|
||||
description: The name of the service
|
||||
ui:autofocus: true
|
||||
description:
|
||||
title: Description
|
||||
type: string
|
||||
description: The description of the service
|
||||
template:
|
||||
title: Template
|
||||
type: string
|
||||
description: The template to use
|
||||
ui:field: EntityPicker
|
||||
ui:options:
|
||||
allowedKinds: []
|
||||
catalogFilter:
|
||||
- kind: component
|
||||
spec.type: ServiceTemplate
|
||||
steps:
|
||||
- id: fetch
|
||||
name: Fetch catalog entity
|
||||
action: catalog:fetch
|
||||
input:
|
||||
entityRef: ${{ parameters.template }}
|
||||
- id: scaffoldService
|
||||
name: Scaffold a New Service
|
||||
action: amplication:scaffold-service
|
||||
input:
|
||||
name: ${{ parameters.name }}
|
||||
description: ${{ parameters.description }}
|
||||
project_id: ${{ steps.fetch.output.entity.spec.project_id }}
|
||||
serviceTemplate_id: ${{ steps.fetch.output.entity.spec.id }}
|
||||
workspace_id: ${{ steps.fetch.output.entity.spec.workspace }}
|
||||
output:
|
||||
links:
|
||||
- title: Service on Amplication
|
||||
url: ${{ steps.scaffoldService.output.url }}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"extends": "@backstage/cli/config/tsconfig.json",
|
||||
"include": [
|
||||
"packages/*/src",
|
||||
"plugins/*/src",
|
||||
"plugins/*/dev",
|
||||
"plugins/*/migrations"
|
||||
],
|
||||
"files": ["node_modules/@backstage/cli/asset-types/asset-types.d.ts"],
|
||||
"exclude": ["node_modules"],
|
||||
"compilerOptions": {
|
||||
"outDir": "dist-types",
|
||||
"rootDir": ".",
|
||||
"lib": ["DOM", "DOM.Iterable", "ScriptHost", "ES2022"],
|
||||
"target": "ES2022",
|
||||
"useUnknownInCatchVariables": false
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue