diff --git a/app-config.yaml b/app-config.yaml index ca52ec5..f6c0919 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -111,3 +111,7 @@ kubernetes: permission: # setting this to `false` will disable permissions enabled: true + +# kusion +kusion: + baseUrl: ${KUSION_BASE_URL} diff --git a/packages/backend/package.json b/packages/backend/package.json index 7cae735..bb6ee1b 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -39,6 +39,7 @@ "@backstage/plugin-search-backend-module-techdocs": "^0.3.2", "@backstage/plugin-search-backend-node": "^1.3.5", "@backstage/plugin-techdocs-backend": "^1.11.2", + "@kusion/backstage-plugin-scaffolder-backend-module-kusion": "^0.1.0", "app": "link:../app", "better-sqlite3": "^9.0.0", "node-gyp": "^10.0.0", diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 69a7351..71b837c 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -51,4 +51,7 @@ backend.add(import('@backstage/plugin-search-backend-module-techdocs')); // kubernetes backend.add(import('@backstage/plugin-kubernetes-backend')); +// kusion +backend.add(import('@kusion/backstage-plugin-scaffolder-backend-module-kusion')); + backend.start(); diff --git a/plugins/scaffolder-backend-module-kusion/.eslintrc.js b/plugins/scaffolder-backend-module-kusion/.eslintrc.js new file mode 100644 index 0000000..e2a53a6 --- /dev/null +++ b/plugins/scaffolder-backend-module-kusion/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/scaffolder-backend-module-kusion/README.md b/plugins/scaffolder-backend-module-kusion/README.md new file mode 100644 index 0000000..01c0c3b --- /dev/null +++ b/plugins/scaffolder-backend-module-kusion/README.md @@ -0,0 +1,59 @@ +## Backstage Plugin Scaffolder Backend Module Kusion + +### Getting Started + +You need a `Kusion Server` running. You can find the server [here](https://github.com/KusionStack/kusion). + +You need to add the following to your `app-config.yaml`. For example: + +```yaml +backend: +kusion: + baseUrl: 'http://localhost:3000' +``` + +### From your Backstage root directory + +```bash +# From your Backstage root directory +yarn add --cwd packages/backend @kusion/backstage-plugin-scaffolder-backend-module-kusion +``` + +### Workspace + +#### Kusion Create Workspace + +The Kusion Workspace Create action that allows you to create a new Kusion Workspace from a template. + +`kusion:workspace:create` + +```yaml +apiVersion: scaffolder.backstage.io/v1beta3 +kind: Template +metadata: + name: create-workspace + title: Create Workspace Template + description: A template to create a workspace +tags: + - kusion + - workspace +spec: + steps: + - id: create-workspace + name: Create Workspace + action: kusion:workspace:create + input: + name: ${{ parameters.name }} + description: ${{ parameters.description }} + labels: ${{ parameters.labels }} + owners: ${{ parameters.owners }} + backendID: ${{ parameters.backend_id }} + output: + text: + - title: Workspace create status + description: The status of workspace creation + content: | + Success: `${{ steps['create-workspace'].output.success }}` + Message: `${{ steps['create-workspace'].output.message }}` + Data: `${{ steps['create-workspace'].output.data }}` +``` diff --git a/plugins/scaffolder-backend-module-kusion/package.json b/plugins/scaffolder-backend-module-kusion/package.json new file mode 100644 index 0000000..5db4fbc --- /dev/null +++ b/plugins/scaffolder-backend-module-kusion/package.json @@ -0,0 +1,50 @@ +{ + "name": "@kusion/backstage-plugin-scaffolder-backend-module-kusion", + "version": "0.1.0", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "author": { + "email": "hoangndst@gmail.com", + "name": "hoangndst" + }, + "private": true, + "publishConfig": { + "access": "public", + "main": "dist/index.cjs.js", + "types": "dist/index.d.ts" + }, + "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" + }, + "repository": { + "type": "git", + "url": "https://github.com/KusionStack/kusion-backstage-plugin", + "directory": "plugins/scaffolder-backend-module-kusion" + }, + "dependencies": { + "@backstage/backend-plugin-api": "^1.0.2", + "@backstage/config": "^1.3.0", + "@backstage/plugin-scaffolder-node": "^0.6.0", + "@kusionstack/kusion-api-client-sdk": "^1.1.3", + "yaml": "^2.6.1" + }, + "devDependencies": { + "@backstage/cli": "^0.29.0", + "@backstage/plugin-scaffolder-node-test-utils": "^0.1.15" + }, + "files": [ + "dist" + ] +} diff --git a/plugins/scaffolder-backend-module-kusion/src/actions/backend/createBackend.example.ts b/plugins/scaffolder-backend-module-kusion/src/actions/backend/createBackend.example.ts new file mode 100644 index 0000000..9623977 --- /dev/null +++ b/plugins/scaffolder-backend-module-kusion/src/actions/backend/createBackend.example.ts @@ -0,0 +1,48 @@ +/* + * 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 { TemplateExample } from '@backstage/plugin-scaffolder-node'; +import yaml from 'yaml'; + +export const examples: TemplateExample[] = [ + { + description: 'Create a backend in Kusion', + example: yaml.stringify({ + steps: [ + { + id: 'create-backend', + action: 'kusion:backend:create', + name: 'Create backend', + input: { + name: 'my-backend', + description: 'This is my backend', + backendConfig: { + "configs": { + "region": "string", + "endpoint": "string", + "accessKeyID": "string", + "accessKeySecret": "string", + "bucket": "string", + "prefix": "string", + }, + "type": "s3" + }, + }, + }, + ], + }), + }, +]; diff --git a/plugins/scaffolder-backend-module-kusion/src/actions/backend/createBackend.ts b/plugins/scaffolder-backend-module-kusion/src/actions/backend/createBackend.ts new file mode 100644 index 0000000..7fb8b3c --- /dev/null +++ b/plugins/scaffolder-backend-module-kusion/src/actions/backend/createBackend.ts @@ -0,0 +1,111 @@ +import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; +import { Config } from '@backstage/config'; +import { configKusionApi } from '../../api'; +import { + BackendService, + CreateBackendData, +} from '@kusionstack/kusion-api-client-sdk'; +import { examples } from './createBackend.example'; + +/** + * Creates a `kusion:backend:create` Scaffolder action. + * + * @public + */ +export function createCreateBackendAction(options: { config: Config }) { + const { config } = options; + return createTemplateAction<{ + name: string; + description: string; + backendConfig: { + type: string; + configs: Record; + }; + }>({ + id: 'kusion:backend:create', + examples, + schema: { + input: { + type: 'object', + required: ['name', 'backendConfig'], + properties: { + name: { + title: 'Backend Name', + type: 'string', + }, + description: { + title: 'Backend Description', + type: 'string', + }, + backendConfig: { + title: 'Backend Configuration', + type: 'object', + required: ['type', 'configs'], + properties: { + type: { + title: 'Backend Type', + type: 'string', + }, + configs: { + title: 'Backend Configs', + type: 'object', + additionalProperties: { + type: 'string', + }, + }, + }, + }, + }, + }, + output: { + type: 'object', + properties: { + success: { + title: 'Success', + type: 'boolean', + }, + message: { + title: 'Message', + type: 'string', + }, + data: { + title: 'Data', + type: 'object', + }, + }, + }, + }, + async handler(ctx) { + const { name, description, backendConfig } = ctx.input; + configKusionApi({ configApi: config }); + const requestBody: CreateBackendData = { + body: { + name: name, + description: description, + backendConfig: backendConfig, + }, + }; + + ctx.logger.info( + 'Creating backend with the following request body: ', + requestBody, + ); + + const response = await BackendService.createBackend(requestBody); + + if (!response.data?.success) { + ctx.logger.error(` + Unable to create backend, ${response.data?.message}`); + ctx.output('success', response.data?.success); + ctx.output('message', response.data?.message); + ctx.output('data', JSON.stringify(response.data?.data)); + throw new Error(`Unable to create backend, ${response.data?.message}`); + } + + ctx.logger.info('Backend created successfully'); + ctx.output('success', response.data?.success); + ctx.output('message', response.data?.message); + ctx.output('data', JSON.stringify(response.data?.data)); + }, + }); +} diff --git a/plugins/scaffolder-backend-module-kusion/src/actions/backend/deleteBackend.example.ts b/plugins/scaffolder-backend-module-kusion/src/actions/backend/deleteBackend.example.ts new file mode 100644 index 0000000..586bcd8 --- /dev/null +++ b/plugins/scaffolder-backend-module-kusion/src/actions/backend/deleteBackend.example.ts @@ -0,0 +1,36 @@ +/* + * 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 { TemplateExample } from '@backstage/plugin-scaffolder-node'; +import yaml from 'yaml'; + +export const examples: TemplateExample[] = [ + { + description: 'Delete a backend in Kusion', + example: yaml.stringify({ + steps: [ + { + id: 'delete-backend', + action: 'kusion:backend:delete', + name: 'Delete backend', + input: { + id: '1', + }, + }, + ], + }), + }, +]; diff --git a/plugins/scaffolder-backend-module-kusion/src/actions/backend/deleteBackend.ts b/plugins/scaffolder-backend-module-kusion/src/actions/backend/deleteBackend.ts new file mode 100644 index 0000000..a3e12aa --- /dev/null +++ b/plugins/scaffolder-backend-module-kusion/src/actions/backend/deleteBackend.ts @@ -0,0 +1,96 @@ +/* + * 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 { createTemplateAction } from '@backstage/plugin-scaffolder-node'; +import { Config } from '@backstage/config'; +import { configKusionApi } from '../../api'; +import { + BackendService, + DeleteBackendData, +} from '@kusionstack/kusion-api-client-sdk'; +import { examples } from './deleteBackend.example'; + +/** + * Creates a `kusion:backend:delete` Scaffolder action. + * + * @public + */ +export function createDeleteBackendAction(options: { config: Config }) { + const { config } = options; + return createTemplateAction<{ + id: string; + }>({ + id: 'kusion:backend:delete', + examples, + schema: { + input: { + type: 'object', + required: ['id'], + properties: { + id: { + title: 'Backend ID', + type: 'string', + }, + }, + }, + output: { + type: 'object', + properties: { + success: { + title: 'Success', + type: 'boolean', + }, + message: { + title: 'Message', + type: 'string', + }, + data: { + title: 'Data', + type: 'object', + }, + }, + }, + }, + async handler(ctx) { + const { id } = ctx.input; + configKusionApi({ configApi: config }); + + ctx.logger.info('Deleting backend with ID: %s', id); + + const request: DeleteBackendData = { + path: { + backendID: Number(id), + }, + }; + + const response = await BackendService.deleteBackend(request); + + if (!response.data?.success) { + ctx.logger.error(` + Unable to delete backend, ${response.data?.message}`); + ctx.output('success', response.data?.success); + ctx.output('message', response.data?.message); + ctx.output('data', JSON.stringify(response.data?.data)); + throw new Error(`Unable to delete backend, ${response.data?.message}`); + } + + ctx.logger.info('Backend deleted successfully'); + ctx.output('success', response.data?.success); + ctx.output('message', response.data?.message); + ctx.output('data', response.data?.data); + }, + }); +} diff --git a/plugins/scaffolder-backend-module-kusion/src/actions/backend/index.ts b/plugins/scaffolder-backend-module-kusion/src/actions/backend/index.ts new file mode 100644 index 0000000..6237dba --- /dev/null +++ b/plugins/scaffolder-backend-module-kusion/src/actions/backend/index.ts @@ -0,0 +1,2 @@ +export * from "./createBackend"; +export * from "./deleteBackend"; diff --git a/plugins/scaffolder-backend-module-kusion/src/actions/organization/createOrganization.example.ts b/plugins/scaffolder-backend-module-kusion/src/actions/organization/createOrganization.example.ts new file mode 100644 index 0000000..3f70484 --- /dev/null +++ b/plugins/scaffolder-backend-module-kusion/src/actions/organization/createOrganization.example.ts @@ -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. + */ + +import { TemplateExample } from '@backstage/plugin-scaffolder-node'; +import yaml from 'yaml'; + +export const examples: TemplateExample[] = [ + { + description: 'Create an organization in Kusion', + example: yaml.stringify({ + steps: [ + { + id: 'create-organization', + action: 'kusion:organization:create', + name: 'Create Organization', + input: { + name: 'my-workspace', + description: 'This is my organization', + labels: ['label1', 'label2'], + owners: ['owner1', 'owner2'], + }, + }, + ], + }), + }, +]; diff --git a/plugins/scaffolder-backend-module-kusion/src/actions/organization/createOrganization.ts b/plugins/scaffolder-backend-module-kusion/src/actions/organization/createOrganization.ts new file mode 100644 index 0000000..37d6cb0 --- /dev/null +++ b/plugins/scaffolder-backend-module-kusion/src/actions/organization/createOrganization.ts @@ -0,0 +1,123 @@ +/* + * 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 { createTemplateAction } from '@backstage/plugin-scaffolder-node'; +import { Config } from '@backstage/config'; +import { configKusionApi } from '../../api'; +import { + OrganizationService, + CreateOrganizationData, +} from '@kusionstack/kusion-api-client-sdk'; +import { examples } from './createOrganization.example'; + +/** + * Creates an `kusion:organization:create` Scaffolder action. + + * @public + */ +export function createCreateOrganizationAction(options: { config: Config }) { + const { config } = options; + return createTemplateAction<{ + name: string; + description: string; + labels: string[]; + owners: string[]; + }>({ + id: 'kusion:organization:create', + examples, + schema: { + input: { + type: 'object', + required: ['owners'], + properties: { + name: { + title: 'Organization Name', + type: 'string', + }, + description: { + title: 'Organization Description', + type: 'string', + }, + labels: { + title: 'Organization Labels', + type: 'array', + items: { + type: 'string', + }, + }, + owners: { + title: 'Organization Owners', + type: 'array', + items: { + type: 'string', + }, + }, + }, + }, + output: { + type: 'object', + properties: { + success: { + title: 'Success', + type: 'boolean', + }, + message: { + title: 'Message', + type: 'string', + }, + data: { + title: 'Data', + type: 'object', + }, + }, + }, + }, + async handler(ctx) { + const { name, description, labels, owners } = ctx.input; + configKusionApi({ configApi: config }); + const requestBody: CreateOrganizationData = { + body: { + name: name, + description: description, + labels: labels, + owners: owners, + }, + }; + ctx.logger.info( + 'Creating organization with the following request body: ', + requestBody, + ); + const response = await OrganizationService.createOrganization( + requestBody, + ); + + if (!response.data?.success) { + ctx.logger.error(` + Unable to create backend, ${response.data?.message}`); + ctx.output('success', response.data?.success); + ctx.output('message', response.data?.message); + ctx.output('data', JSON.stringify(response.data?.data)); + throw new Error( + `Unable to create organization, ${response.data?.message}`, + ); + } + ctx.logger.info('Organization created successfully'); + ctx.output('success', response.data?.success); + ctx.output('message', response.data?.message); + ctx.output('data', JSON.stringify(response.data?.data)); + }, + }); +} diff --git a/plugins/scaffolder-backend-module-kusion/src/actions/organization/index.ts b/plugins/scaffolder-backend-module-kusion/src/actions/organization/index.ts new file mode 100644 index 0000000..29bb8f0 --- /dev/null +++ b/plugins/scaffolder-backend-module-kusion/src/actions/organization/index.ts @@ -0,0 +1 @@ +export * from "./createOrganization" diff --git a/plugins/scaffolder-backend-module-kusion/src/actions/project/createProject.example.ts b/plugins/scaffolder-backend-module-kusion/src/actions/project/createProject.example.ts new file mode 100644 index 0000000..0c7ea99 --- /dev/null +++ b/plugins/scaffolder-backend-module-kusion/src/actions/project/createProject.example.ts @@ -0,0 +1,27 @@ +import { TemplateExample } from '@backstage/plugin-scaffolder-node'; +import yaml from 'yaml'; + +export const examples: TemplateExample[] = [ + { + description: 'Create a project in Kusion', + example: yaml.stringify({ + steps: [ + { + id: 'create-project', + action: 'kusion:project:create', + name: 'Create Project', + input: { + domain: 'http://localhost:3000', + name: 'my-workspace', + description: 'This is my project', + labels: ['label1', 'label2'], + owners: ['owner1', 'owner2'], + organizationID: 1, + path: '/project/tdt', + sourceID: 1, + }, + }, + ], + }), + }, +]; diff --git a/plugins/scaffolder-backend-module-kusion/src/actions/project/createProject.ts b/plugins/scaffolder-backend-module-kusion/src/actions/project/createProject.ts new file mode 100644 index 0000000..edef35b --- /dev/null +++ b/plugins/scaffolder-backend-module-kusion/src/actions/project/createProject.ts @@ -0,0 +1,136 @@ +import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; +import { Config } from '@backstage/config'; +import { configKusionApi } from '../../api'; +import { + ProjectService, + CreateProjectData, +} from '@kusionstack/kusion-api-client-sdk'; +import { examples } from './createProject.example'; + +/** + * Creates an `kusion:project:create` Scaffolder action. + + * @public + */ +export function createCreateProjectAction(options: { config: Config }) { + const { config } = options; + return createTemplateAction<{ + domain: string; + name: string; + description: string; + labels: string[]; + owners: string[]; + organizationID: number; + path: string; + sourceID: number; + }>({ + id: 'kusion:project:create', + examples, + schema: { + input: { + type: 'object', + required: ['domain'], + properties: { + domain: { + title: 'Project Domain', + type: 'string', + }, + name: { + title: 'Project Name', + type: 'string', + }, + description: { + title: 'Project Description', + type: 'string', + }, + labels: { + title: 'Project Labels', + type: 'array', + items: { + type: 'string', + }, + }, + owners: { + title: 'Project Owners', + type: 'array', + items: { + type: 'string', + }, + }, + organizationID: { + title: 'Organization ID', + type: 'number', + }, + path: { + title: 'Project Path', + type: 'string', + }, + sourceID: { + title: 'Source ID', + type: 'number', + }, + }, + }, + output: { + type: 'object', + properties: { + success: { + title: 'Success', + type: 'boolean', + }, + message: { + title: 'Message', + type: 'string', + }, + data: { + title: 'Data', + type: 'object', + }, + }, + }, + }, + async handler(ctx) { + const { + domain, + name, + description, + labels, + owners, + organizationID, + path, + sourceID, + } = ctx.input; + configKusionApi({ configApi: config }); + const requestBody: CreateProjectData = { + body: { + domain, + name, + description, + labels, + owners, + organizationID, + path, + sourceID, + }, + }; + ctx.logger.info( + 'Creating project with the following request body: ', + requestBody, + ); + const response = await ProjectService.createProject(requestBody); + + if (!response.data?.success) { + ctx.logger.error(` + Unable to create project, ${response.data?.message}`); + ctx.output('success', response.data?.success); + ctx.output('message', response.data?.message); + ctx.output('data', '{}'); + throw new Error(`Unable to create project, ${response.data?.message}`); + } + ctx.logger.info('Project created successfully'); + ctx.output('success', response.data?.success); + ctx.output('message', response.data?.message); + ctx.output('data', JSON.stringify(response.data?.data)); + }, + }); +} diff --git a/plugins/scaffolder-backend-module-kusion/src/actions/project/index.ts b/plugins/scaffolder-backend-module-kusion/src/actions/project/index.ts new file mode 100644 index 0000000..174c6d2 --- /dev/null +++ b/plugins/scaffolder-backend-module-kusion/src/actions/project/index.ts @@ -0,0 +1 @@ +export * from './createProject'; diff --git a/plugins/scaffolder-backend-module-kusion/src/actions/workspace/createWorkspace.example.ts b/plugins/scaffolder-backend-module-kusion/src/actions/workspace/createWorkspace.example.ts new file mode 100644 index 0000000..917da6f --- /dev/null +++ b/plugins/scaffolder-backend-module-kusion/src/actions/workspace/createWorkspace.example.ts @@ -0,0 +1,40 @@ +/* + * 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 { TemplateExample } from '@backstage/plugin-scaffolder-node'; +import yaml from 'yaml'; + +export const examples: TemplateExample[] = [ + { + description: 'Create a workspace in Kusion', + example: yaml.stringify({ + steps: [ + { + id: 'create-workspace', + action: 'kusion:workspace:create', + name: 'Create Workspace', + input: { + name: 'my-workspace', + description: 'This is my workspace', + labels: ['label1', 'label2'], + owners: ['owner1', 'owner2'], + backendID: 1, + }, + }, + ], + }), + }, +]; diff --git a/plugins/scaffolder-backend-module-kusion/src/actions/workspace/createWorkspace.ts b/plugins/scaffolder-backend-module-kusion/src/actions/workspace/createWorkspace.ts new file mode 100644 index 0000000..24793c0 --- /dev/null +++ b/plugins/scaffolder-backend-module-kusion/src/actions/workspace/createWorkspace.ts @@ -0,0 +1,129 @@ +/* + * 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 { createTemplateAction } from '@backstage/plugin-scaffolder-node'; +import { Config } from '@backstage/config'; +import { configKusionApi } from '../../api'; +import { + WorkspaceService, + CreateWorkspaceData, +} from '@kusionstack/kusion-api-client-sdk'; +import { examples } from './createWorkspace.example'; + +/** + * Creates an `kusion:workspace:create` Scaffolder action. + + * @public + */ +export function createCreateWorkspaceAction(options: { config: Config }) { + const { config } = options; + return createTemplateAction<{ + name: string; + description: string; + labels: string[]; + owners: string[]; + backendID: number; + }>({ + id: 'kusion:workspace:create', + examples, + schema: { + input: { + type: 'object', + required: ['name', 'owners', 'backendID'], + properties: { + name: { + title: 'Workspace Name', + type: 'string', + }, + description: { + title: 'Workspace Description', + type: 'string', + }, + labels: { + title: 'Workspace Labels', + type: 'array', + items: { + type: 'string', + }, + }, + owners: { + title: 'Workspace Owners', + type: 'array', + items: { + type: 'string', + }, + }, + backendID: { + title: 'Backend ID', + type: 'number', + }, + }, + }, + output: { + type: 'object', + properties: { + success: { + title: 'Success', + type: 'boolean', + }, + message: { + title: 'Message', + type: 'string', + }, + data: { + title: 'Data', + type: 'object', + }, + }, + }, + }, + async handler(ctx) { + const { name, description, labels, owners, backendID } = ctx.input; + configKusionApi({ configApi: config }); + const requestBody: CreateWorkspaceData = { + body: { + name: name, + description: description, + labels: labels, + owners: owners, + backendID: backendID, + }, + }; + + ctx.logger.info( + 'Creating workspace with the following request body: ', + requestBody, + ); + + const response = await WorkspaceService.createWorkspace(requestBody); + + if (!response.data?.success) { + ctx.logger.error(` + Unable to create backend, ${response.data?.message}`); + ctx.output('success', response.data?.success); + ctx.output('message', response.data?.message); + ctx.output('data', JSON.stringify(response.data?.data)); + throw new Error( + `Unable to create workspace, ${response.data?.message}`, + ); + } + ctx.logger.info('Workspace created successfully'); + ctx.output('success', response.data?.success); + ctx.output('message', response.data?.message); + ctx.output('data', JSON.stringify(response.data?.data)); + }, + }); +} diff --git a/plugins/scaffolder-backend-module-kusion/src/actions/workspace/deleteWorkspace.example.ts b/plugins/scaffolder-backend-module-kusion/src/actions/workspace/deleteWorkspace.example.ts new file mode 100644 index 0000000..3a399ef --- /dev/null +++ b/plugins/scaffolder-backend-module-kusion/src/actions/workspace/deleteWorkspace.example.ts @@ -0,0 +1,36 @@ +/* + * 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 { TemplateExample } from '@backstage/plugin-scaffolder-node'; +import yaml from 'yaml'; + +export const examples: TemplateExample[] = [ + { + description: 'Delete a workspace in Kusion', + example: yaml.stringify({ + steps: [ + { + id: 'delete-workspace', + action: 'kusion:workspace:delete', + name: 'Delete workspace', + input: { + id: '1', + }, + }, + ], + }), + }, +]; diff --git a/plugins/scaffolder-backend-module-kusion/src/actions/workspace/deleteWorkspace.ts b/plugins/scaffolder-backend-module-kusion/src/actions/workspace/deleteWorkspace.ts new file mode 100644 index 0000000..2b84a19 --- /dev/null +++ b/plugins/scaffolder-backend-module-kusion/src/actions/workspace/deleteWorkspace.ts @@ -0,0 +1,98 @@ +/* + * 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 { createTemplateAction } from '@backstage/plugin-scaffolder-node'; +import { Config } from '@backstage/config'; +import { configKusionApi } from '../../api'; +import { + WorkspaceService, + DeleteWorkspaceData, +} from '@kusionstack/kusion-api-client-sdk'; +import { examples } from './deleteWorkspace.example'; + +/** + * Creates a `kusion:workspace:delete` Scaffolder action. + * + * @public + */ +export function createDeleteWorkspaceAction(options: { config: Config }) { + const { config } = options; + return createTemplateAction<{ + id: string; + }>({ + id: 'kusion:workspace:delete', + examples, + schema: { + input: { + type: 'object', + required: ['id'], + properties: { + id: { + title: 'Workspace ID', + type: 'string', + }, + }, + }, + output: { + type: 'object', + properties: { + success: { + title: 'Success', + type: 'boolean', + }, + message: { + title: 'Message', + type: 'string', + }, + data: { + title: 'Data', + type: 'object', + }, + }, + }, + }, + async handler(ctx) { + const { id } = ctx.input; + configKusionApi({ configApi: config }); + + ctx.logger.info('Deleting workspace with ID: %s', id); + + const request: DeleteWorkspaceData = { + path: { + workspaceID: Number(id), + }, + }; + + const response = await WorkspaceService.deleteWorkspace(request); + + if (!response.data?.success) { + ctx.logger.error(` + Unable to delete workspace, ${response.data?.message}`); + ctx.output('success', response.data?.success); + ctx.output('message', response.data?.message); + ctx.output('data', JSON.stringify(response.data?.data)); + throw new Error( + `Unable to delete workspace, ${response.data?.message}`, + ); + } + + ctx.logger.info('Workspace deleted successfully'); + ctx.output('success', response.data?.success); + ctx.output('message', response.data?.message); + ctx.output('data', response.data?.data); + }, + }); +} diff --git a/plugins/scaffolder-backend-module-kusion/src/actions/workspace/index.ts b/plugins/scaffolder-backend-module-kusion/src/actions/workspace/index.ts new file mode 100644 index 0000000..f313bf6 --- /dev/null +++ b/plugins/scaffolder-backend-module-kusion/src/actions/workspace/index.ts @@ -0,0 +1,2 @@ +export * from "./createWorkspace"; +export * from "./deleteWorkspace"; diff --git a/plugins/scaffolder-backend-module-kusion/src/api/index.ts b/plugins/scaffolder-backend-module-kusion/src/api/index.ts new file mode 100644 index 0000000..79be9b7 --- /dev/null +++ b/plugins/scaffolder-backend-module-kusion/src/api/index.ts @@ -0,0 +1,47 @@ +/* + * Copyright 2024 KusionStack + * + * 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 { client } from '@kusionstack/kusion-api-client-sdk'; + +type Options = { + configApi: Config; +}; + +export const configKusionApi = (option: Options) => { + const { configApi } = option; + const getApiUrl = () => { + const kusionBaseUrl = configApi.getOptionalString('kusion.baseUrl'); + if (!kusionBaseUrl) { + throw new Error('backstage config kusion.baseUrl is required'); + } + + return kusionBaseUrl; + }; + + // TODO: Wait for the Kusion Server to support authentication. + const getKusionToken = () => { + const token = configApi.getOptionalString('kusion.token'); + if (!Boolean(token) || token?.length === 0) { + throw new Error('backstage config kusion.token is required'); + } + return token; + }; + + client.setConfig({ + baseUrl: getApiUrl(), + }); +}; diff --git a/plugins/scaffolder-backend-module-kusion/src/index.ts b/plugins/scaffolder-backend-module-kusion/src/index.ts new file mode 100644 index 0000000..737daac --- /dev/null +++ b/plugins/scaffolder-backend-module-kusion/src/index.ts @@ -0,0 +1,23 @@ +/* + * 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. + */ + +/** + * The kusion module for scaffolder backend that lets you interact with Kusion Server. + * + * @packageDocumentation + */ + +export { kusionModule as default } from './module'; diff --git a/plugins/scaffolder-backend-module-kusion/src/module.ts b/plugins/scaffolder-backend-module-kusion/src/module.ts new file mode 100644 index 0000000..f4a1ddd --- /dev/null +++ b/plugins/scaffolder-backend-module-kusion/src/module.ts @@ -0,0 +1,76 @@ +/* + * 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 { + createBackendModule, + coreServices, +} from '@backstage/backend-plugin-api'; +import { scaffolderActionsExtensionPoint } from '@backstage/plugin-scaffolder-node/alpha'; +import { createCreateOrganizationAction } from './actions/organization'; +import { createCreateProjectAction } from './actions/project'; +import { + createCreateWorkspaceAction, + createDeleteWorkspaceAction, +} from './actions/workspace'; +import { + createCreateBackendAction, + createDeleteBackendAction, +} from './actions/backend'; + +/** + * The Kusion Module for the Scaffolder Backend + * @public + */ +export const kusionModule = createBackendModule({ + pluginId: 'scaffolder', + moduleId: 'kusion', + register({ registerInit }) { + registerInit({ + deps: { + scaffolder: scaffolderActionsExtensionPoint, + config: coreServices.rootConfig, + }, + async init({ scaffolder, config }) { + scaffolder.addActions( + createCreateWorkspaceAction({ + config, + }), + createDeleteWorkspaceAction({ + config, + }), + ); + scaffolder.addActions( + createCreateBackendAction({ + config, + }), + createDeleteBackendAction({ + config, + }), + ); + scaffolder.addActions( + createCreateOrganizationAction({ + config, + }), + ); + scaffolder.addActions( + createCreateProjectAction({ + config, + }), + ); + }, + }); + }, +}); diff --git a/yarn.lock b/yarn.lock index 74f458b..7ecc8e5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3037,6 +3037,43 @@ __metadata: languageName: node linkType: hard +"@backstage/backend-test-utils@npm:^1.1.0": + version: 1.1.0 + resolution: "@backstage/backend-test-utils@npm:1.1.0" + dependencies: + "@backstage/backend-app-api": "npm:^1.0.2" + "@backstage/backend-defaults": "npm:^0.5.3" + "@backstage/backend-plugin-api": "npm:^1.0.2" + "@backstage/config": "npm:^1.3.0" + "@backstage/errors": "npm:^1.2.5" + "@backstage/plugin-auth-node": "npm:^0.5.4" + "@backstage/plugin-events-node": "npm:^0.4.5" + "@backstage/types": "npm:^1.2.0" + "@keyv/memcache": "npm:^1.3.5" + "@keyv/redis": "npm:^2.5.3" + "@types/express": "npm:^4.17.6" + "@types/express-serve-static-core": "npm:^4.17.5" + "@types/keyv": "npm:^4.2.0" + "@types/qs": "npm:^6.9.6" + better-sqlite3: "npm:^11.0.0" + cookie: "npm:^0.7.0" + express: "npm:^4.17.1" + fs-extra: "npm:^11.0.0" + keyv: "npm:^4.5.2" + knex: "npm:^3.0.0" + mysql2: "npm:^3.0.0" + pg: "npm:^8.11.3" + pg-connection-string: "npm:^2.3.0" + testcontainers: "npm:^10.0.0" + textextensions: "npm:^5.16.0" + uuid: "npm:^11.0.0" + yn: "npm:^4.0.0" + peerDependencies: + "@types/jest": "*" + checksum: 10c0/e7046ef659a4d8301ddb8c2b4b39eff07cdd5cb0c4b89af0d6ab5f6987d6d072b8d851eaff0b962d97111a23c1a09da30083616242fe0c5fdfabae7c61064be5 + languageName: node + linkType: hard + "@backstage/catalog-client@npm:^1.8.0": version: 1.8.0 resolution: "@backstage/catalog-client@npm:1.8.0" @@ -4798,7 +4835,27 @@ __metadata: languageName: node linkType: hard -"@backstage/plugin-scaffolder-node@npm:^0.6.1": +"@backstage/plugin-scaffolder-node-test-utils@npm:^0.1.15": + version: 0.1.16 + resolution: "@backstage/plugin-scaffolder-node-test-utils@npm:0.1.16" + dependencies: + "@backstage/backend-common": "npm:^0.25.0" + "@backstage/backend-test-utils": "npm:^1.1.0" + "@backstage/plugin-scaffolder-node": "npm:^0.6.1" + "@backstage/types": "npm:^1.2.0" + peerDependencies: + "@types/react": ^16.13.1 || ^17.0.0 || ^18.0.0 + react: ^16.13.1 || ^17.0.0 || ^18.0.0 + react-dom: ^16.13.1 || ^17.0.0 || ^18.0.0 + react-router-dom: 6.0.0-beta.0 || ^6.3.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/00e51b91b2cc877186fdfddc44a2e5ce6dac0b90bf29e2df9c833dde39de60d067bc43b73a0379d3ac7f9bab5df668e62c40740ac06bdd86c82af76506a20a4d + languageName: node + linkType: hard + +"@backstage/plugin-scaffolder-node@npm:^0.6.0, @backstage/plugin-scaffolder-node@npm:^0.6.1": version: 0.6.1 resolution: "@backstage/plugin-scaffolder-node@npm:0.6.1" dependencies: @@ -6146,6 +6203,13 @@ __metadata: languageName: node linkType: hard +"@fastify/busboy@npm:^2.0.0": + version: 2.1.1 + resolution: "@fastify/busboy@npm:2.1.1" + checksum: 10c0/6f8027a8cba7f8f7b736718b013f5a38c0476eea67034c94a0d3c375e2b114366ad4419e6a6fa7ffc2ef9c6d3e0435d76dd584a7a1cbac23962fda7650b579e3 + languageName: node + linkType: hard + "@floating-ui/core@npm:^1.6.0": version: 1.6.8 resolution: "@floating-ui/core@npm:1.6.8" @@ -6715,6 +6779,13 @@ __metadata: languageName: node linkType: hard +"@hey-api/client-fetch@npm:^0.5.2": + version: 0.5.3 + resolution: "@hey-api/client-fetch@npm:0.5.3" + checksum: 10c0/04e8271d09197d78890d0cfb124eefed7d38156a7dd4e578f2d083083569845cea7803744e0abf3f54cb518cb0ad3d4cbc5f082ac5aa66ec5057d96d4a5e0fac + languageName: node + linkType: hard + "@httptoolkit/httpolyglot@npm:^2.2.1": version: 2.2.2 resolution: "@httptoolkit/httpolyglot@npm:2.2.2" @@ -7246,6 +7317,15 @@ __metadata: languageName: node linkType: hard +"@keyv/serialize@npm:*": + version: 1.0.1 + resolution: "@keyv/serialize@npm:1.0.1" + dependencies: + buffer: "npm:^6.0.3" + checksum: 10c0/948fadc632f6050b67cb8ea664192a98d7743015e1b449d383addacbce371414b9cd7129b33ab36d5d81b558744ab0a56c2753b287e7c4f5b2c46401c486cbd0 + languageName: node + linkType: hard + "@kubernetes-models/apimachinery@npm:^2.0.0, @kubernetes-models/apimachinery@npm:^2.0.1": version: 2.0.1 resolution: "@kubernetes-models/apimachinery@npm:2.0.1" @@ -7339,6 +7419,30 @@ __metadata: languageName: node linkType: hard +"@kusion/backstage-plugin-scaffolder-backend-module-kusion@npm:^0.1.0, @kusion/backstage-plugin-scaffolder-backend-module-kusion@workspace:plugins/scaffolder-backend-module-kusion": + version: 0.0.0-use.local + resolution: "@kusion/backstage-plugin-scaffolder-backend-module-kusion@workspace:plugins/scaffolder-backend-module-kusion" + dependencies: + "@backstage/backend-plugin-api": "npm:^1.0.2" + "@backstage/cli": "npm:^0.29.0" + "@backstage/config": "npm:^1.3.0" + "@backstage/plugin-scaffolder-node": "npm:^0.6.0" + "@backstage/plugin-scaffolder-node-test-utils": "npm:^0.1.15" + "@kusionstack/kusion-api-client-sdk": "npm:^1.1.3" + node-fetch: "npm:^2.7.0" + yaml: "npm:^2.6.1" + languageName: unknown + linkType: soft + +"@kusionstack/kusion-api-client-sdk@npm:^1.1.3": + version: 1.1.3 + resolution: "@kusionstack/kusion-api-client-sdk@npm:1.1.3" + dependencies: + "@hey-api/client-fetch": "npm:^0.5.2" + checksum: 10c0/c6e641f5c37ff230d45fd4dfd98015f8f2d77fbb176fe9739cf5858363663f7f71ff4c938edf8a28b66e7f6b5febc7f8306098380fbd90b5d4fe4c32f777f607 + languageName: node + linkType: hard + "@leichtgewicht/ip-codec@npm:^2.0.1": version: 2.0.5 resolution: "@leichtgewicht/ip-codec@npm:2.0.5" @@ -11829,7 +11933,7 @@ __metadata: languageName: node linkType: hard -"@types/dockerode@npm:^3.3.0": +"@types/dockerode@npm:^3.3.0, @types/dockerode@npm:^3.3.29": version: 3.3.32 resolution: "@types/dockerode@npm:3.3.32" dependencies: @@ -12093,6 +12197,15 @@ __metadata: languageName: node linkType: hard +"@types/keyv@npm:^4.2.0": + version: 4.2.0 + resolution: "@types/keyv@npm:4.2.0" + dependencies: + keyv: "npm:*" + checksum: 10c0/ad626918f1843035b732b582263890a67d73dc3ff80da97e51fbe0ae3f2fe7a1ada2eef1bd89605c5fb739444110e696c0e0703d9b49a842a2f924c6e9164faa + languageName: node + linkType: hard + "@types/long@npm:^4.0.0": version: 4.0.2 resolution: "@types/long@npm:4.0.2" @@ -12261,7 +12374,7 @@ __metadata: languageName: node linkType: hard -"@types/qs@npm:*, @types/qs@npm:^6.9.11": +"@types/qs@npm:*, @types/qs@npm:^6.9.11, @types/qs@npm:^6.9.6": version: 6.9.17 resolution: "@types/qs@npm:6.9.17" checksum: 10c0/a183fa0b3464267f8f421e2d66d960815080e8aab12b9aadab60479ba84183b1cdba8f4eff3c06f76675a8e42fe6a3b1313ea76c74f2885c3e25d32499c17d1b @@ -12414,6 +12527,15 @@ __metadata: languageName: node linkType: hard +"@types/ssh2-streams@npm:*": + version: 0.1.12 + resolution: "@types/ssh2-streams@npm:0.1.12" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/6c860066e76391c937723b9f8c3953208737be5adf33b5584d7817ec90913094f2ca578e1d47717182f1d62cb5ca8e83fdec0241d73bf064221e3a2b2d132f0e + languageName: node + linkType: hard + "@types/ssh2@npm:*": version: 1.15.1 resolution: "@types/ssh2@npm:1.15.1" @@ -12423,6 +12545,16 @@ __metadata: languageName: node linkType: hard +"@types/ssh2@npm:^0.5.48": + version: 0.5.52 + resolution: "@types/ssh2@npm:0.5.52" + dependencies: + "@types/node": "npm:*" + "@types/ssh2-streams": "npm:*" + checksum: 10c0/95c52fd3438dedae6a59ca87b6558cb36568db6b9144c6c8a28c168739e04c51e27c02908aae14950b7b5020e1c40fea039b1203ae2734c356a40a050fd51c84 + languageName: node + linkType: hard + "@types/stack-utils@npm:^2.0.0": version: 2.0.3 resolution: "@types/stack-utils@npm:2.0.3" @@ -13453,7 +13585,7 @@ __metadata: languageName: node linkType: hard -"archiver@npm:^7.0.0": +"archiver@npm:^7.0.0, archiver@npm:^7.0.1": version: 7.0.1 resolution: "archiver@npm:7.0.1" dependencies: @@ -14072,6 +14204,7 @@ __metadata: "@backstage/plugin-search-backend-module-techdocs": "npm:^0.3.2" "@backstage/plugin-search-backend-node": "npm:^1.3.5" "@backstage/plugin-techdocs-backend": "npm:^1.11.2" + "@kusion/backstage-plugin-scaffolder-backend-module-kusion": "npm:^0.1.0" app: "link:../app" better-sqlite3: "npm:^9.0.0" node-gyp: "npm:^10.0.0" @@ -14100,13 +14233,49 @@ __metadata: languageName: node linkType: hard -"bare-events@npm:^2.2.0": +"bare-events@npm:^2.0.0, bare-events@npm:^2.2.0": version: 2.5.0 resolution: "bare-events@npm:2.5.0" checksum: 10c0/afbeec4e8be4d93fb4a3be65c3b4a891a2205aae30b5a38fafd42976cc76cf30dad348963fe330a0d70186e15dc507c11af42c89af5dddab2a54e5aff02e2896 languageName: node linkType: hard +"bare-fs@npm:^2.1.1": + version: 2.3.5 + resolution: "bare-fs@npm:2.3.5" + dependencies: + bare-events: "npm:^2.0.0" + bare-path: "npm:^2.0.0" + bare-stream: "npm:^2.0.0" + checksum: 10c0/ff18cc9be7c557c38e0342681ba3672ae4b01e5696b567d4035e5995255dc6bc7d4df88ed210fa4d3eb940eb29512e924ebb42814c87fc59a2bee8cf83b7c2f9 + languageName: node + linkType: hard + +"bare-os@npm:^2.1.0": + version: 2.4.4 + resolution: "bare-os@npm:2.4.4" + checksum: 10c0/e7d1a7b2100c05da8d25b60d0d48cf850c6f57064577a3f2f51cf18d417fbcfd6967ed2d8314320914ed69e0f2ebcf54eb1b36092dd172d8e8f969cf8cccf041 + languageName: node + linkType: hard + +"bare-path@npm:^2.0.0, bare-path@npm:^2.1.0": + version: 2.1.3 + resolution: "bare-path@npm:2.1.3" + dependencies: + bare-os: "npm:^2.1.0" + checksum: 10c0/35587e177fc8fa5b13fb90bac8779b5ce49c99016d221ddaefe2232d02bd4295d79b941e14ae19fda75ec42a6fe5fb66c07d83ae7ec11462178e66b7be65ca74 + languageName: node + linkType: hard + +"bare-stream@npm:^2.0.0": + version: 2.4.2 + resolution: "bare-stream@npm:2.4.2" + dependencies: + streamx: "npm:^2.20.0" + checksum: 10c0/5e64d96dc32d901c317399f14fd1057882b2bd68d1f8ab54710f0e640b0d1f3a4bf4f9c238bb4c81051ef4b687cf2223e5e05dda9f6ce08bc0cc2ac98f3b52e0 + languageName: node + linkType: hard + "base64-arraybuffer@npm:^0.1.5": version: 0.1.5 resolution: "base64-arraybuffer@npm:0.1.5" @@ -16286,7 +16455,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:^4.3.6": +"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.3.6": version: 4.3.7 resolution: "debug@npm:4.3.7" dependencies: @@ -16691,6 +16860,27 @@ __metadata: languageName: node linkType: hard +"docker-compose@npm:^0.24.8": + version: 0.24.8 + resolution: "docker-compose@npm:0.24.8" + dependencies: + yaml: "npm:^2.2.2" + checksum: 10c0/1494389e554fed8aabf9fef24210a641cd2442028b1462d7f68186919f5e75045f7bfb4ccaf47c94ed879dcb63e4d82885c389399f531550c4b244920740b2b3 + languageName: node + linkType: hard + +"docker-modem@npm:^3.0.0": + version: 3.0.8 + resolution: "docker-modem@npm:3.0.8" + dependencies: + debug: "npm:^4.1.1" + readable-stream: "npm:^3.5.0" + split-ca: "npm:^1.0.1" + ssh2: "npm:^1.11.0" + checksum: 10c0/5c00592297fabd78454621fe765a5ef0daea4bbb6692e239ad65b111f4da9d750178f448f8efcaf84f9f999598eb735bc14ad6bf5f0a2dcf9c2d453d5b683540 + languageName: node + linkType: hard + "docker-modem@npm:^5.0.3": version: 5.0.3 resolution: "docker-modem@npm:5.0.3" @@ -16703,6 +16893,17 @@ __metadata: languageName: node linkType: hard +"dockerode@npm:^3.3.5": + version: 3.3.5 + resolution: "dockerode@npm:3.3.5" + dependencies: + "@balena/dockerignore": "npm:^1.0.2" + docker-modem: "npm:^3.0.0" + tar-fs: "npm:~2.0.1" + checksum: 10c0/c45fa8ed3ad76f13fe7799d539a60fe466f8e34bea06b30d75be9e08bc00536cc9ff2d54e38fbb3b2a8a382bf9d4459a27741e6454ce7d0cda5cd35c51224c73 + languageName: node + linkType: hard + "dockerode@npm:^4.0.0": version: 4.0.2 resolution: "dockerode@npm:4.0.2" @@ -22158,6 +22359,15 @@ __metadata: languageName: node linkType: hard +"keyv@npm:*": + version: 5.2.1 + resolution: "keyv@npm:5.2.1" + dependencies: + "@keyv/serialize": "npm:*" + checksum: 10c0/95c986a4785bd169c1545c8263662c3e898dc446c4acd9640598ca454075b9dea73559909dded6b23df0a247031d101df84a757bb6db08bbfe06499896e494fc + languageName: node + linkType: hard + "keyv@npm:^4.0.0, keyv@npm:^4.5.2, keyv@npm:^4.5.3": version: 4.5.4 resolution: "keyv@npm:4.5.4" @@ -23889,7 +24099,7 @@ __metadata: languageName: node linkType: hard -"mkdirp@npm:^1.0.3": +"mkdirp@npm:^1.0.3, mkdirp@npm:^1.0.4": version: 1.0.4 resolution: "mkdirp@npm:1.0.4" bin: @@ -26347,6 +26557,26 @@ __metadata: languageName: node linkType: hard +"proper-lockfile@npm:^4.1.2": + version: 4.1.2 + resolution: "proper-lockfile@npm:4.1.2" + dependencies: + graceful-fs: "npm:^4.2.4" + retry: "npm:^0.12.0" + signal-exit: "npm:^3.0.2" + checksum: 10c0/2f265dbad15897a43110a02dae55105c04d356ec4ed560723dcb9f0d34bc4fb2f13f79bb930e7561be10278e2314db5aca2527d5d3dcbbdee5e6b331d1571f6d + languageName: node + linkType: hard + +"properties-reader@npm:^2.3.0": + version: 2.3.0 + resolution: "properties-reader@npm:2.3.0" + dependencies: + mkdirp: "npm:^1.0.4" + checksum: 10c0/f665057e3a9076c643ba1198afcc71703eda227a59913252f7ff9467ece8d29c0cf8bf14bf1abcaef71570840c32a4e257e6c39b7550451bbff1a777efcf5667 + languageName: node + linkType: hard + "property-expr@npm:^2.0.5": version: 2.0.6 resolution: "property-expr@npm:2.0.6" @@ -28799,7 +29029,17 @@ __metadata: languageName: node linkType: hard -"ssh2@npm:^1.15.0": +"ssh-remote-port-forward@npm:^1.0.4": + version: 1.0.4 + resolution: "ssh-remote-port-forward@npm:1.0.4" + dependencies: + "@types/ssh2": "npm:^0.5.48" + ssh2: "npm:^1.4.0" + checksum: 10c0/33a441af12817577ea30d089b03c19f980d2fb2370933123a35026dc6be40f2dfce067e4dfc173e23d745464537ff647aa1bb7469be5571cc21f7cdb25181c09 + languageName: node + linkType: hard + +"ssh2@npm:^1.11.0, ssh2@npm:^1.15.0, ssh2@npm:^1.4.0": version: 1.16.0 resolution: "ssh2@npm:1.16.0" dependencies: @@ -29041,6 +29281,21 @@ __metadata: languageName: node linkType: hard +"streamx@npm:^2.20.0": + version: 2.21.0 + resolution: "streamx@npm:2.21.0" + dependencies: + bare-events: "npm:^2.2.0" + fast-fifo: "npm:^1.3.2" + queue-tick: "npm:^1.0.1" + text-decoder: "npm:^1.1.0" + dependenciesMeta: + bare-events: + optional: true + checksum: 10c0/4583d1585c0b5876bc623e4c31c00358d914277b649928573002577019cb41cb8e62a7b39559aa118ff8424c1d98b03eb163536f838fa21d006f274042498180 + languageName: node + linkType: hard + "strict-uri-encode@npm:^2.0.0": version: 2.0.0 resolution: "strict-uri-encode@npm:2.0.0" @@ -29561,6 +29816,23 @@ __metadata: languageName: node linkType: hard +"tar-fs@npm:^3.0.6": + version: 3.0.6 + resolution: "tar-fs@npm:3.0.6" + dependencies: + bare-fs: "npm:^2.1.1" + bare-path: "npm:^2.1.0" + pump: "npm:^3.0.0" + tar-stream: "npm:^3.1.5" + dependenciesMeta: + bare-fs: + optional: true + bare-path: + optional: true + checksum: 10c0/207b7c0f193495668bd9dbad09a0108ce4ffcfec5bce2133f90988cdda5c81fad83c99f963d01e47b565196594f7a17dbd063ae55b97b36268fcc843975278ee + languageName: node + linkType: hard + "tar-fs@npm:~2.0.1": version: 2.0.1 resolution: "tar-fs@npm:2.0.1" @@ -29586,7 +29858,7 @@ __metadata: languageName: node linkType: hard -"tar-stream@npm:^3.0.0": +"tar-stream@npm:^3.0.0, tar-stream@npm:^3.1.5": version: 3.1.7 resolution: "tar-stream@npm:3.1.7" dependencies: @@ -29701,6 +29973,29 @@ __metadata: languageName: node linkType: hard +"testcontainers@npm:^10.0.0": + version: 10.16.0 + resolution: "testcontainers@npm:10.16.0" + dependencies: + "@balena/dockerignore": "npm:^1.0.2" + "@types/dockerode": "npm:^3.3.29" + archiver: "npm:^7.0.1" + async-lock: "npm:^1.4.1" + byline: "npm:^5.0.0" + debug: "npm:^4.3.5" + docker-compose: "npm:^0.24.8" + dockerode: "npm:^3.3.5" + get-port: "npm:^5.1.1" + proper-lockfile: "npm:^4.1.2" + properties-reader: "npm:^2.3.0" + ssh-remote-port-forward: "npm:^1.0.4" + tar-fs: "npm:^3.0.6" + tmp: "npm:^0.2.3" + undici: "npm:^5.28.4" + checksum: 10c0/17f748927d484e63718e2a745c776d54a893eba08c4fa20b84e5056f2ef45f585e39c7a1ffebea37ec63ae96e71dd50de5a611d4d313f63380bacca52b12737a + languageName: node + linkType: hard + "text-decoder@npm:^1.1.0": version: 1.2.1 resolution: "text-decoder@npm:1.2.1" @@ -29722,6 +30017,13 @@ __metadata: languageName: node linkType: hard +"textextensions@npm:^5.16.0": + version: 5.16.0 + resolution: "textextensions@npm:5.16.0" + checksum: 10c0/bc90dc60272c3ffb76eeff86dc1decab9535ba8da6a00efe2a005763d0305cb445db9ac35970538c59b89bf41820c3d19394f46c6b7346d520b9ae16fe47be80 + languageName: node + linkType: hard + "thenify-all@npm:^1.0.0": version: 1.6.0 resolution: "thenify-all@npm:1.6.0" @@ -29862,7 +30164,7 @@ __metadata: languageName: node linkType: hard -"tmp@npm:^0.2.0": +"tmp@npm:^0.2.0, tmp@npm:^0.2.3": version: 0.2.3 resolution: "tmp@npm:0.2.3" checksum: 10c0/3e809d9c2f46817475b452725c2aaa5d11985cf18d32a7a970ff25b568438e2c076c2e8609224feef3b7923fa9749b74428e3e634f6b8e520c534eef2fd24125 @@ -30533,6 +30835,15 @@ __metadata: languageName: node linkType: hard +"undici@npm:^5.28.4": + version: 5.28.4 + resolution: "undici@npm:5.28.4" + dependencies: + "@fastify/busboy": "npm:^2.0.0" + checksum: 10c0/08d0f2596553aa0a54ca6e8e9c7f45aef7d042c60918564e3a142d449eda165a80196f6ef19ea2ef2e6446959e293095d8e40af1236f0d67223b06afac5ecad7 + languageName: node + linkType: hard + "unicode-canonical-property-names-ecmascript@npm:^2.0.0": version: 2.0.1 resolution: "unicode-canonical-property-names-ecmascript@npm:2.0.1" @@ -31772,7 +32083,7 @@ __metadata: languageName: node linkType: hard -"yaml@npm:^2.0.0, yaml@npm:^2.2.1": +"yaml@npm:^2.0.0, yaml@npm:^2.2.1, yaml@npm:^2.2.2, yaml@npm:^2.6.1": version: 2.6.1 resolution: "yaml@npm:2.6.1" bin: