diff --git a/.yarnrc.yml b/.yarnrc.yml new file mode 100644 index 0000000..3186f3f --- /dev/null +++ b/.yarnrc.yml @@ -0,0 +1 @@ +nodeLinker: node-modules diff --git a/Dockerfile b/Dockerfile index c21e91b..90c8337 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Stage 1 - Create yarn install skeleton layer -FROM node:18-bookworm-slim AS packages +FROM node:20.18.1 AS packages WORKDIR /app COPY package.json yarn.lock ./ @@ -12,7 +12,7 @@ COPY plugins plugins RUN find packages \! -name "package.json" -mindepth 2 -maxdepth 2 -exec rm -rf {} \+ # Stage 2 - Install dependencies and build packages -FROM node:18-bookworm-slim AS build +FROM node:20.18.1 AS build # Required for macOS RUN apt update -y @@ -24,7 +24,7 @@ WORKDIR /app COPY --from=packages --chown=node:node /app . RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ - yarn install --frozen-lockfile --network-timeout 600000 + yarn install --network-timeout 600000 COPY --chown=node:node . . @@ -38,7 +38,7 @@ RUN mkdir packages/backend/dist/skeleton packages/backend/dist/bundle \ && tar xzf packages/backend/dist/bundle.tar.gz -C packages/backend/dist/bundle # Stage 3 - Build the actual backend image and install production dependencies -FROM node:18-bookworm-slim +FROM node:20.18.1 # Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend. # Install packages needed to get utility binaries @@ -83,7 +83,7 @@ WORKDIR /app COPY --from=build --chown=node:node /app/yarn.lock /app/package.json /app/packages/backend/dist/skeleton/ ./ RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ - yarn install --frozen-lockfile --production --network-timeout 600000 + yarn install --production --network-timeout 600000 # Copy the built packages from the build stage COPY --from=build --chown=node:node /app/packages/backend/dist/bundle/ ./ diff --git a/backstage.json b/backstage.json index b67976c..e235680 100644 --- a/backstage.json +++ b/backstage.json @@ -1,3 +1,3 @@ { - "version": "1.28.4" + "version": "1.36.1" } diff --git a/package.json b/package.json index a7f7fc5..7eab290 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ ] }, "devDependencies": { - "@backstage/cli": "^0.26.10", + "@backstage/cli": "^0.30.0", "@backstage/e2e-test-utils": "^0.1.1", "@playwright/test": "^1.32.3", "@spotify/prettier-config": "^12.0.0", diff --git a/packages/app/package.json b/packages/app/package.json index c7b1b72..697d54d 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -14,35 +14,33 @@ "lint": "backstage-cli package lint" }, "dependencies": { - "@backstage/app-defaults": "^1.5.7", - "@backstage/catalog-model": "^1.5.0", - "@backstage/cli": "^0.26.10", - "@backstage/core-app-api": "^1.13.0", - "@backstage/core-components": "^0.14.8", - "@backstage/core-plugin-api": "^1.9.3", - "@backstage/integration-react": "^1.1.28", - "@backstage/plugin-api-docs": "^0.11.6", - "@backstage/plugin-catalog": "^1.21.0", - "@backstage/plugin-catalog-common": "^1.0.24", - "@backstage/plugin-catalog-graph": "^0.4.6", - "@backstage/plugin-catalog-import": "^0.12.0", - "@backstage/plugin-catalog-react": "^1.12.1", - "@backstage/plugin-home": "^0.7.6", - "@backstage/plugin-kubernetes": "^0.11.11", - "@backstage/plugin-org": "^0.6.26", - "@backstage/plugin-permission-react": "^0.4.23", - "@backstage/plugin-scaffolder": "^1.22.0", - "@backstage/plugin-search": "^1.4.13", - "@backstage/plugin-search-react": "^1.7.12", - "@backstage/plugin-techdocs": "^1.10.6", - "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.11", - "@backstage/plugin-techdocs-react": "^1.2.5", - "@backstage/plugin-user-settings": "^0.8.8", - "@backstage/theme": "^0.5.6", - "@internal/plugin-apache-spark": "^0.1.0", - "@internal/plugin-argo-workflows": "^0.1.0", - "@internal/plugin-cnoe-ui": "^0.1.0", - "@internal/plugin-terraform": "^0.1.0", + "@backstage-community/plugin-github-actions": "^0.6.16", + "@backstage-community/plugin-tech-radar": "^0.7.4", + "@backstage/app-defaults": "^1.5.17", + "@backstage/catalog-model": "^1.7.3", + "@backstage/cli": "^0.30.0", + "@backstage/core-app-api": "^1.15.5", + "@backstage/core-components": "^0.16.4", + "@backstage/core-plugin-api": "^1.10.4", + "@backstage/integration-react": "^1.2.4", + "@backstage/plugin-api-docs": "^0.12.4", + "@backstage/plugin-catalog": "^1.27.0", + "@backstage/plugin-catalog-common": "^1.1.3", + "@backstage/plugin-catalog-graph": "^0.4.16", + "@backstage/plugin-catalog-import": "^0.12.10", + "@backstage/plugin-catalog-react": "^1.15.2", + "@backstage/plugin-home": "^0.8.5", + "@backstage/plugin-kubernetes": "^0.12.4", + "@backstage/plugin-org": "^0.6.36", + "@backstage/plugin-permission-react": "^0.4.31", + "@backstage/plugin-scaffolder": "^1.28.0", + "@backstage/plugin-search": "^1.4.23", + "@backstage/plugin-search-react": "^1.8.6", + "@backstage/plugin-techdocs": "^1.12.3", + "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.21", + "@backstage/plugin-techdocs-react": "^1.2.14", + "@backstage/plugin-user-settings": "^0.8.19", + "@backstage/theme": "^0.6.4", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@roadiehq/backstage-plugin-argo-cd": "^2.5.1", @@ -51,12 +49,10 @@ "react-dom": "^18.0.2", "react-router": "^6.3.0", "react-router-dom": "^6.3.0", - "react-use": "^17.2.4", - "@backstage-community/plugin-github-actions": "^0.6.16", - "@backstage-community/plugin-tech-radar": "^0.7.4" + "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/test-utils": "^1.5.7", + "@backstage/test-utils": "^1.7.5", "@playwright/test": "^1.32.3", "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index ae87f47..74fc096 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -33,19 +33,7 @@ import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; import { RequirePermission } from '@backstage/plugin-permission-react'; import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; -import LightIcon from '@material-ui/icons/WbSunny'; -import { - CNOEHomepage, - cnoeLightTheme, - cnoeDarkTheme, -} from '@internal/plugin-cnoe-ui'; import {configApiRef, useApi} from "@backstage/core-plugin-api"; -import { ArgoWorkflowsPage } from '@internal/plugin-argo-workflows'; -import { ApacheSparkPage } from '@internal/plugin-apache-spark'; -import { - UnifiedThemeProvider -} from "@backstage/theme"; -import { TerraformPluginPage } from '@internal/plugin-terraform'; const app = createApp({ apis, @@ -84,33 +72,12 @@ const app = createApp({ bind(orgPlugin.externalRoutes, { catalogIndex: catalogPlugin.routes.catalogIndex, }); - }, - themes: [ - { - id: 'cnoe-light-theme', - title: 'Light Theme', - variant: 'light', - icon: , - Provider: ({ children }) => ( - - ), - }, - { - id: 'cnoe-dark-theme', - title: 'Dark Theme', - variant: 'dark', - icon: , - Provider: ({ children }) => ( - - ), - }, - ], + } }); const routes = ( } /> - } /> } /> } /> } /> - } /> - } /> - } /> ); @@ -161,6 +125,3 @@ export default app.createRoot( , ); - - - diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx index 28d7342..51a4e86 100644 --- a/packages/app/src/components/Root/Root.tsx +++ b/packages/app/src/components/Root/Root.tsx @@ -5,7 +5,6 @@ import ExtensionIcon from '@material-ui/icons/Extension'; import MapIcon from '@material-ui/icons/MyLocation'; import LibraryBooks from '@material-ui/icons/LibraryBooks'; import CreateComponentIcon from '@material-ui/icons/AddCircleOutline'; -import {LogoFull, LogoIcon} from '@internal/plugin-cnoe-ui'; import { Settings as SidebarSettings, UserSettingsSignInAvatar, @@ -20,7 +19,6 @@ import { SidebarPage, SidebarScrollWrapper, SidebarSpace, - useSidebarOpenState, Link, } from '@backstage/core-components'; import MenuIcon from '@material-ui/icons/Menu'; @@ -43,12 +41,10 @@ const useSidebarLogoStyles = makeStyles({ const SidebarLogo = () => { const classes = useSidebarLogoStyles(); - const { isOpen } = useSidebarOpenState(); return (
- {isOpen ? : }
); diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx index 98f05aa..a56185c 100644 --- a/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/app/src/components/catalog/EntityPage.tsx @@ -58,13 +58,6 @@ import { isArgocdAvailable } from '@roadiehq/backstage-plugin-argo-cd'; -import { - EntityArgoWorkflowsOverviewCard, - isArgoWorkflowsAvailable, -} from '@internal/plugin-argo-workflows'; -import {ApacheSparkPage, isApacheSparkAvailable} from "@internal/plugin-apache-spark"; -import { isTerraformAvailable, TerraformPluginPage } from '@internal/plugin-terraform'; - const techdocsContent = ( @@ -75,10 +68,6 @@ const techdocsContent = ( const cicdContent = ( - isArgoWorkflowsAvailable(e)}> - - - - - isTerraformAvailable(e)}> - - - - - @@ -169,10 +151,6 @@ const serviceEntityPage = ( - - - - diff --git a/packages/backend/package.json b/packages/backend/package.json index 85a58b7..27deae1 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -16,42 +16,41 @@ "build-image": "docker build ../.. -f Dockerfile --tag backstage" }, "dependencies": { - "@backstage/backend-common": "^0.23.2", - "@backstage/backend-defaults": "^0.4.0", - "@backstage/backend-plugin-api": "^0.7.0", - "@backstage/backend-tasks": "^0.5.26", - "@backstage/catalog-client": "^1.6.5", - "@backstage/catalog-model": "^1.5.0", - "@backstage/config": "^1.2.0", - "@backstage/errors": "^1.2.4", - "@backstage/integration": "^1.12.0", - "@backstage/plugin-app-backend": "^0.3.70", - "@backstage/plugin-auth-backend": "^0.22.8", - "@backstage/plugin-auth-backend-module-guest-provider": "^0.1.7", - "@backstage/plugin-auth-backend-module-oidc-provider": "^0.2.2", - "@backstage/plugin-auth-node": "^0.4.16", - "@backstage/plugin-catalog-backend": "^1.23.2", - "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.1.19", - "@backstage/plugin-kubernetes-backend": "^0.18.2", - "@backstage/plugin-permission-common": "^0.8.0", - "@backstage/plugin-permission-node": "^0.8.0", - "@backstage/plugin-proxy-backend": "^0.5.2", - "@backstage/plugin-scaffolder-backend": "^1.22.11", - "@backstage/plugin-scaffolder-backend-module-gitea": "^0.1.11", - "@backstage/plugin-scaffolder-backend-module-github": "^0.4.0", - "@backstage/plugin-scaffolder-node": "^0.4.7", - "@backstage/plugin-search-backend": "^1.5.13", - "@backstage/plugin-search-backend-module-catalog": "^0.1.27", - "@backstage/plugin-search-backend-module-pg": "^0.5.31", - "@backstage/plugin-search-backend-module-techdocs": "^0.1.26", - "@backstage/plugin-search-backend-node": "^1.2.26", - "@backstage/plugin-techdocs-backend": "^1.10.8", - "@backstage/types": "^1.1.1", - "@internal/backstage-plugin-terraform-backend": "^0.1.0", + "@backstage/backend-common": "^0.25.0", + "@backstage/backend-defaults": "^0.8.1", + "@backstage/backend-plugin-api": "^1.2.0", + "@backstage/backend-tasks": "^0.6.1", + "@backstage/catalog-client": "^1.9.1", + "@backstage/catalog-model": "^1.7.3", + "@backstage/config": "^1.3.2", + "@backstage/errors": "^1.2.7", + "@backstage/integration": "^1.16.1", + "@backstage/plugin-app-backend": "^0.4.5", + "@backstage/plugin-auth-backend": "^0.24.3", + "@backstage/plugin-auth-backend-module-guest-provider": "^0.2.5", + "@backstage/plugin-auth-backend-module-oidc-provider": "^0.4.0", + "@backstage/plugin-auth-node": "^0.6.0", + "@backstage/plugin-catalog-backend": "^1.31.0", + "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.2.5", + "@backstage/plugin-kubernetes-backend": "^0.19.3", + "@backstage/plugin-permission-common": "^0.8.4", + "@backstage/plugin-permission-node": "^0.8.8", + "@backstage/plugin-proxy-backend": "^0.5.11", + "@backstage/plugin-scaffolder-backend": "^1.30.0", + "@backstage/plugin-scaffolder-backend-module-gitea": "^0.2.6", + "@backstage/plugin-scaffolder-backend-module-github": "^0.6.0", + "@backstage/plugin-scaffolder-node": "^0.7.0", + "@backstage/plugin-search-backend": "^1.8.2", + "@backstage/plugin-search-backend-module-catalog": "^0.3.1", + "@backstage/plugin-search-backend-module-pg": "^0.5.41", + "@backstage/plugin-search-backend-module-techdocs": "^0.3.6", + "@backstage/plugin-search-backend-node": "^1.3.8", + "@backstage/plugin-techdocs-backend": "^1.11.6", + "@backstage/types": "^1.2.1", "@kubernetes/client-node": "~0.20.0", - "@roadiehq/backstage-plugin-argo-cd-backend": "3.0.2", + "@roadiehq/backstage-plugin-argo-cd-backend": "3.1.0", "@roadiehq/scaffolder-backend-module-http-request": "^4.3.5", - "@roadiehq/scaffolder-backend-module-utils": "^1.17.0", + "@roadiehq/scaffolder-backend-module-utils": "3.0.0", "app": "link:../app", "better-sqlite3": "^9.0.0", "dockerode": "^3.3.1", @@ -63,7 +62,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.26.10", + "@backstage/cli": "^0.30.0", "@types/dockerode": "^3.3.0", "@types/express": "^4.17.6", "@types/express-serve-static-core": "^4.17.5", diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 7517332..9f99e44 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -1,40 +1,43 @@ import { createBackend } from '@backstage/backend-defaults'; -import { authModuleKeycloakOIDCProvider } from './plugins/auth'; import { cnoeScaffolderActions } from './plugins/scaffolder'; -import { legacyPlugin } from '@backstage/backend-common'; const backend = createBackend(); // core plugins -backend.add(import('@backstage/plugin-app-backend/alpha')); -backend.add(import('@backstage/plugin-catalog-backend/alpha')); -backend.add(import('@backstage/plugin-proxy-backend/alpha')); +backend.add(import('@backstage/plugin-app-backend')); +backend.add(import('@backstage/plugin-catalog-backend')); +backend.add(import('@backstage/plugin-proxy-backend')); backend.add(import('@backstage/plugin-techdocs-backend/alpha')); + // auth plugins backend.add(import('@backstage/plugin-auth-backend')); backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); + // scaffolder plugins backend.add(import('@backstage/plugin-scaffolder-backend/alpha')); backend.add( import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'), ); backend.add(import('@backstage/plugin-scaffolder-backend-module-github')); + // search plugins backend.add(import('@backstage/plugin-search-backend/alpha')); -backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha')); + +backend.add(import('@backstage/plugin-search-backend-module-catalog')); backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha')); + // other @backstage plugins -backend.add(import('@backstage/plugin-kubernetes-backend/alpha')); -// non-core plugins +backend.add(import('@backstage/plugin-kubernetes-backend')); + // roadie plugins backend.add(import('@roadiehq/scaffolder-backend-module-utils/new-backend')); -backend.add(legacyPlugin('argocd', import('./plugins/argocd'))); +backend.add(import('./plugins/argocd_index')); + backend.add( import('@roadiehq/scaffolder-backend-module-http-request/new-backend'), ); + // cnoe plugins -backend.add(authModuleKeycloakOIDCProvider); backend.add(cnoeScaffolderActions); -backend.add(import('@internal/backstage-plugin-terraform-backend')); backend.start(); diff --git a/packages/backend/src/plugins/argocd.ts b/packages/backend/src/plugins/argocd.ts index 611bf54..3a96f32 100644 --- a/packages/backend/src/plugins/argocd.ts +++ b/packages/backend/src/plugins/argocd.ts @@ -2,18 +2,55 @@ import { Config } from '@backstage/config'; import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import { examples } from './gitea-actions'; import { Logger } from 'winston'; - import { ArgoService } from '@roadiehq/backstage-plugin-argo-cd-backend'; - import { createRouter } from '@roadiehq/backstage-plugin-argo-cd-backend'; -import { PluginEnvironment } from '../types'; +//import { PluginEnvironment } from '../types'; -export default async function createPlugin({ + +/*export default async function createPlugin({ logger, config, }: PluginEnvironment) { return await createRouter({ logger, config }); -} +}*/ + +import { loggerToWinstonLogger } from '@backstage/backend-common'; + +import { + coreServices, + createBackendPlugin, +} from '@backstage/backend-plugin-api'; + +export const argocdPlugin = createBackendPlugin({ + pluginId: 'argocd', + register(env) { + env.registerInit({ + deps: { + logger: coreServices.logger, + config: coreServices.rootConfig, + reader: coreServices.urlReader, + discovery: coreServices.discovery, + auth: coreServices.auth, + //tokenManager: coreServices.tokenManager, + httpRouter: coreServices.httpRouter, + }, + async init({ + logger, + config, + httpRouter, + }) { + httpRouter.use( + await createRouter({ + logger: loggerToWinstonLogger(logger), + config, + }), + ); + }, + }); + }, +}); + + export function createArgoCDApp(options: { config: Config; logger: Logger }) { const { config, logger } = options; diff --git a/packages/backend/src/plugins/argocd_index.ts b/packages/backend/src/plugins/argocd_index.ts new file mode 100644 index 0000000..bd0bc7e --- /dev/null +++ b/packages/backend/src/plugins/argocd_index.ts @@ -0,0 +1 @@ +export { argocdPlugin as default } from './argocd'; \ No newline at end of file diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts index 9cd2c74..0dad120 100644 --- a/packages/backend/src/types.ts +++ b/packages/backend/src/types.ts @@ -5,9 +5,8 @@ import { PluginDatabaseManager, PluginEndpointDiscovery, TokenManager, - UrlReader, -} from '@backstage/backend-common'; -import { PluginTaskScheduler } from '@backstage/backend-tasks'; +} from '@backstage/backend-common/dist'; //TODO: deprecated +import { PluginTaskScheduler } from '@backstage/backend-tasks/dist'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; import { IdentityApi } from '@backstage/plugin-auth-node'; @@ -16,7 +15,6 @@ export type PluginEnvironment = { database: PluginDatabaseManager; cache: PluginCacheManager; config: Config; - reader: UrlReader; discovery: PluginEndpointDiscovery; tokenManager: TokenManager; scheduler: PluginTaskScheduler; diff --git a/plugins/apache-spark/.eslintrc.js b/plugins/apache-spark/.eslintrc.js deleted file mode 100644 index e2a53a6..0000000 --- a/plugins/apache-spark/.eslintrc.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/apache-spark/README.md b/plugins/apache-spark/README.md deleted file mode 100644 index 976aba2..0000000 --- a/plugins/apache-spark/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# apache-spark - -Welcome to the apache-spark plugin! - -_This plugin was created through the Backstage CLI_ - -## Getting started - -Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn start` in the root directory, and then navigating to [/apache-spark](http://localhost:3000/apache-spark). - -You can also serve the plugin in isolation by running `yarn start` in the plugin directory. -This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads. -It is only meant for local development, and the setup for it can be found inside the [/dev](./dev) directory. diff --git a/plugins/apache-spark/dev/index.tsx b/plugins/apache-spark/dev/index.tsx deleted file mode 100644 index 5f2b474..0000000 --- a/plugins/apache-spark/dev/index.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; -import { createDevApp } from '@backstage/dev-utils'; -import { apacheSparkPlugin, ApacheSparkPage } from '../src/plugin'; - -createDevApp() - .registerPlugin(apacheSparkPlugin) - .addPage({ - element: , - title: 'Root Page', - path: '/apache-spark' - }) - .render(); diff --git a/plugins/apache-spark/package.json b/plugins/apache-spark/package.json deleted file mode 100644 index 71639db..0000000 --- a/plugins/apache-spark/package.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "name": "@internal/plugin-apache-spark", - "version": "0.1.0", - "main": "src/index.ts", - "types": "src/index.ts", - "license": "Apache-2.0", - "private": true, - "publishConfig": { - "access": "public", - "main": "dist/index.esm.js", - "types": "dist/index.d.ts" - }, - "backstage": { - "role": "frontend-plugin" - }, - "sideEffects": false, - "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/core-components": "^0.14.8", - "@backstage/core-plugin-api": "^1.9.3", - "@backstage/theme": "^0.5.6", - "@material-ui/core": "^4.9.13", - "@material-ui/icons": "^4.9.1", - "@material-ui/lab": "^4.0.0-alpha.61", - "react-use": "^17.2.4" - }, - "peerDependencies": { - "react": "^16.13.1 || ^17.0.0" - }, - "devDependencies": { - "@backstage/cli": "^0.26.10", - "@backstage/core-app-api": "^1.13.0", - "@backstage/dev-utils": "^1.0.34", - "@backstage/test-utils": "^1.5.7", - "@testing-library/jest-dom": "^5.10.1", - "@testing-library/react": "^12.1.3", - "@testing-library/user-event": "^14.0.0", - "msw": "^1.0.0" - }, - "files": [ - "dist" - ] -} diff --git a/plugins/apache-spark/src/api/index.test.ts b/plugins/apache-spark/src/api/index.test.ts deleted file mode 100644 index 20f775b..0000000 --- a/plugins/apache-spark/src/api/index.test.ts +++ /dev/null @@ -1,113 +0,0 @@ -// import { ApacheSparkClient } from './index'; -// import { ApacheSpark } from './model'; -// -// const mockKubernetesApi = { -// proxy: jest.fn(), -// getClusters: jest.fn(), -// getObjectsByEntity: jest.fn(), -// getWorkloadsByEntity: jest.fn(), -// getCustomObjectsByEntity: jest.fn(), -// }; -// -// describe('ApacheSparkClient', () => { -// let apacheSparkClient: ApacheSparkClient; -// -// beforeEach(() => { -// apacheSparkClient = new ApacheSparkClient(mockKubernetesApi); -// }); -// -// afterEach(() => { -// jest.clearAllMocks(); -// }); -// -// it('should fetch Spark application logs', async () => { -// mockKubernetesApi.proxy.mockResolvedValue({ -// ok: true, -// text: () => { -// return 'logs'; -// }, -// }); -// const logs = await apacheSparkClient.getLogs( -// 'cluster1', -// 'spark-namespace', -// 'spark-pod-name', -// 'abc', -// ); -// expect(logs).toEqual('logs'); -// expect(mockKubernetesApi.proxy).toHaveBeenCalledWith({ -// clusterName: 'cluster1', -// path: '/api/v1/namespaces/spark-namespace/pods/spark-pod-name/log?tailLines=1000&container=abc', -// }); -// }); -// -// it('should throw error if Spark application logs are not fetched', async () => { -// mockKubernetesApi.proxy.mockResolvedValueOnce({ -// status: 500, -// statusText: 'Internal Server Error', -// ok: false, -// text: () => { -// return 'oh noes'; -// }, -// }); -// -// await expect( -// apacheSparkClient.getLogs( -// 'spark-app-name', -// 'spark-namespace', -// 'spark-pod-name', -// 'abc', -// ), -// ).rejects.toEqual( -// 'failed to fetch logs: 500, Internal Server Error, oh noes', -// ); -// }); -// -// // test getSparkApp method -// it('should fetch Spark application', async () => { -// // @ts-ignore -// const mockResponse: ApacheSpark = { -// apiVersion: 'sparkoperator.k8s.io/v1beta2', -// kind: 'SparkApplication', -// metadata: { -// name: 'spark-app-name', -// namespace: 'spark-namespace', -// labels: { -// app: 'spark-app-name', -// }, -// creationTimestamp: '2021-01-01T00:00:00Z', -// }, -// spec: { -// image: 'abc', -// mainApplicationFile: 'main.py', -// mode: 'cluster', -// sparkVersion: 'v3.1.1.', -// type: 'Python', -// driver: { -// cores: 1, -// }, -// executor: { -// cores: 1, -// }, -// }, -// status: { -// applicationState: { -// state: 'RUNNING', -// }, -// }, -// }; -// -// mockKubernetesApi.proxy.mockResolvedValue({ -// ok: true, -// text: () => { -// return JSON.stringify(mockResponse); -// }, -// }); -// -// const application = await apacheSparkClient.getSparkApp( -// 'spark-app-name', -// 'spark-namespace', -// 'abc', -// ); -// expect(application).toEqual(mockResponse); -// }); -// }); diff --git a/plugins/apache-spark/src/api/index.ts b/plugins/apache-spark/src/api/index.ts deleted file mode 100644 index cda9454..0000000 --- a/plugins/apache-spark/src/api/index.ts +++ /dev/null @@ -1,176 +0,0 @@ -import { createApiRef } from '@backstage/core-plugin-api'; -import { ApacheSpark, ApacheSparkList, Pod } from './model'; -import { KubernetesApi } from '@backstage/plugin-kubernetes'; - -export const apacheSparkApiRef = createApiRef({ - id: 'plugin.apachespark', -}); - -const API_VERSION = 'sparkoperator.k8s.io/v1beta2'; -const SPARK_APP_PLURAL = 'sparkapplications'; -const K8s_API_TIMEOUT = 'timeoutSeconds'; - -export interface ApacheSparkApi { - getSparkApps( - clusterName: string | undefined, - namespace: string | undefined, - labels: string | undefined, - ): Promise; - - getSparkApp( - clusterName: string | undefined, - namespace: string | undefined, - name: string, - ): Promise; - - getLogs( - clusterName: string | undefined, - namespace: string | undefined, - podName: string, - containerName?: string | undefined, - tailLine?: number, - ): Promise; - - getContainers( - clusterName: string | undefined, - namespace: string | undefined, - podName: string, - ): Promise; -} - -export class ApacheSparkClient implements ApacheSparkApi { - private kubernetesApi: KubernetesApi; - constructor(kubernetesApi: KubernetesApi) { - this.kubernetesApi = kubernetesApi; - } - async getSparkApps( - clusterName: string | undefined, - namespace: string | undefined, - labels: string | undefined, - ): Promise { - const ns = namespace !== undefined ? namespace : 'default'; - const path = `/apis/${API_VERSION}/namespaces/${ns}/${SPARK_APP_PLURAL}`; - const query = new URLSearchParams({ - [K8s_API_TIMEOUT]: '30', - }); - if (labels) { - query.set('labelSelector', labels); - } - const resp = await this.kubernetesApi.proxy({ - clusterName: - clusterName !== undefined ? clusterName : await this.getFirstCluster(), - path: `${path}?${query.toString()}`, - }); - - if (!resp.ok) { - return Promise.reject( - `failed to fetch resources: ${resp.status}, ${ - resp.statusText - }, ${await resp.text()}`, - ); - } - const out = JSON.parse(await resp.text()); - this.removeManagedField(out); - return out; - } - - async getSparkApp( - clusterName: string | undefined, - namespace: string | undefined, - name: string, - ): Promise { - const ns = namespace !== undefined ? namespace : 'default'; - const path = `/apis/${API_VERSION}/namespaces/${ns}/${SPARK_APP_PLURAL}/${name}`; - const resp = await this.kubernetesApi.proxy({ - clusterName: - clusterName !== undefined ? clusterName : await this.getFirstCluster(), - path: `${path}`, - }); - if (!resp.ok) { - return Promise.reject( - `failed to fetch resources: ${resp.status}, ${ - resp.statusText - }, ${await resp.text()}`, - ); - } - const out = JSON.parse(await resp.text()); - this.removeManagedField(out); - return out; - } - - async getLogs( - clusterName: string | undefined, - namespace: string | undefined, - podName: string, - containerName: string | undefined, - tailLine: number = 1000, - ): Promise { - const ns = namespace !== undefined ? namespace : 'default'; - const path = `/api/v1/namespaces/${ns}/pods/${podName}/log`; - const query = new URLSearchParams({ - tailLines: tailLine.toString(), - }); - if (containerName) { - query.set('container', containerName); - } - - const resp = await this.kubernetesApi.proxy({ - clusterName: - clusterName !== undefined ? clusterName : await this.getFirstCluster(), - path: `${path}?${query.toString()}`, - }); - if (!resp.ok) { - return Promise.reject( - `failed to fetch logs: ${resp.status}, ${ - resp.statusText - }, ${await resp.text()}`, - ); - } - return resp.text(); - } - - async getContainers( - clusterName: string | undefined, - namespace: string | undefined, - podName: string, - ): Promise { - const ns = namespace !== undefined ? namespace : 'default'; - const path = `/api/v1/namespaces/${ns}/pods/${podName}`; - const query = new URLSearchParams({ - [K8s_API_TIMEOUT]: '30', - }); - const resp = await this.kubernetesApi.proxy({ - clusterName: - clusterName !== undefined ? clusterName : await this.getFirstCluster(), - path: `${path}?${query.toString()}`, - }); - if (!resp.ok) { - throw new Error( - `failed to fetch logs: ${resp.status}, ${ - resp.statusText - }, ${await resp.text()}`, - ); - } - const pod = JSON.parse(await resp.text()) as Pod; - return pod.spec.containers.map(c => c.name); - } - - async getFirstCluster(): Promise { - const clusters = await this.kubernetesApi.getClusters(); - if (clusters.length > 0) { - return Promise.resolve(clusters[0].name); - } - return Promise.reject('no clusters found in configuration'); - } - - removeManagedField(spark: any) { - if (spark.metadata?.hasOwnProperty('managedFields')) { - delete spark.metadata.managedFields; - } - if (spark.items) { - for (const i of spark.items) { - this.removeManagedField(i); - } - } - } -} diff --git a/plugins/apache-spark/src/api/model.ts b/plugins/apache-spark/src/api/model.ts deleted file mode 100644 index 1d6455c..0000000 --- a/plugins/apache-spark/src/api/model.ts +++ /dev/null @@ -1,100 +0,0 @@ -export type Metadata = { - name: string; - namespace?: string; - labels?: Record; - annotations?: Record; - creationTimestamp: string; - managedFields?: any; -}; - -export type Spec = { - arguments?: string[]; - batchScheduler?: string; - driver: { - coreLimit?: string; - coreRequest?: string; - cores?: number; - gpu?: { - name: string; - quantity: number; - }; - labels?: Record; - memory?: string; - memoryOverhead?: string; - podName?: string; - schedulerName?: string; - serviceAccount?: string; - }; - executor: { - coreLimit?: string; - coreRequest?: string; - cores?: number; - gpu?: { - name: string; - quantity: number; - }; - instances?: number; - labels?: Record; - memory?: string; - memoryOverhead?: string; - schedulerName?: string; - serviceAccount?: string; - }; - image: string; - mainClass?: string; - mainApplicationFile?: string; - mode: string; - pythonVersion?: string; - sparkVersion: string; - type: string; -}; - -export type Status = { - applicationState: { - errorMessage?: string; - state: string; - }; - driverInfo?: { - podName: string; - webUIAddress: string; - webUIIngressAddress: string; - webUIIngressName: string; - webUIPort: string; - webUIServiceName: string; - }; - executionAttempts?: number; - executorState?: { [key: string]: string }; - lastSubmissionAttemptTime?: string; - sparkApplicationId?: string; - submissionAttempts?: number; - submissionID?: string; - terminationTime?: string; -}; - -export type ApacheSpark = { - apiVersion: string; - kind: string; - metadata: Metadata; - spec: Spec; - status: Status; -}; - -export type ApacheSparkList = { - apiVersion: string; - kind: string; - items?: ApacheSpark[]; -}; - -export type Pod = { - apiVersion: string; - kind: string; - metadata: Metadata; - spec: PodSpec; -}; - -export type PodSpec = { - containers: { - image: string; - name: string; - }[]; -}; diff --git a/plugins/apache-spark/src/components/ApacheSparkLogs/ApacheSparkLogs.test.tsx b/plugins/apache-spark/src/components/ApacheSparkLogs/ApacheSparkLogs.test.tsx deleted file mode 100644 index 01d3ade..0000000 --- a/plugins/apache-spark/src/components/ApacheSparkLogs/ApacheSparkLogs.test.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import React from 'react'; -import { render, screen } from '@testing-library/react'; -import { useApi } from '@backstage/core-plugin-api'; -import { useEntity } from '@backstage/plugin-catalog-react'; -import useAsync from 'react-use/lib/useAsync'; -import { ApacheSpark } from '../../api/model'; -import { ApacheSparkDriverLogs } from './ApacheSparkLogs'; -import { - APACHE_SPARK_LABEL_SELECTOR_ANNOTATION, - CLUSTER_NAME_ANNOTATION, - K8S_NAMESPACE_ANNOTATION, -} from '../../consts'; - -jest.mock('@backstage/core-plugin-api'); -jest.mock('react-use/lib/useAsync'); -jest.mock('@backstage/plugin-catalog-react'); - -jest.mock('@backstage/core-components', () => ({ - LogViewer: (props: { text: string }) => { - return
{props.text}
; - }, -})); - -describe('ApacheSparkDriverLogs', () => { - const mockUseApi = useApi as jest.MockedFunction; - const mockUseAsync = useAsync as jest.MockedFunction; - const mockUseEntity = useEntity as jest.MockedFunction; - const mockGetLogs = jest.fn(); - const mockSparkApp = { - status: { - driverInfo: { - podName: 'test-pod', - }, - }, - } as ApacheSpark; - - beforeEach(() => { - mockUseApi.mockReturnValue({ - getLogs: mockGetLogs, - }); - mockUseEntity.mockReturnValue({ - entity: { - apiVersion: 'version', - kind: 'kind', - metadata: { - name: 'name', - namespace: 'ns1', - annotations: { - [K8S_NAMESPACE_ANNOTATION]: 'k8s-ns', - [CLUSTER_NAME_ANNOTATION]: 'my-cluster', - [APACHE_SPARK_LABEL_SELECTOR_ANNOTATION]: 'env=test', - }, - }, - }, - }); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it('should render error message if there is an error', () => { - mockUseAsync.mockReturnValue({ - value: undefined, - loading: false, - error: new Error('Test error'), - }); - - render(); - expect(screen.getByText('Error: Test error')).toBeInTheDocument(); - expect(screen.getByRole('alert')).toBeInTheDocument(); - }); - - it('should render the log viewer with the fetched logs', async () => { - mockUseAsync.mockReturnValue({ - value: 'test logs', - loading: false, - error: undefined, - }); - render(); - expect(screen.getByText('test logs')).toBeInTheDocument(); - }); -}); diff --git a/plugins/apache-spark/src/components/ApacheSparkLogs/ApacheSparkLogs.tsx b/plugins/apache-spark/src/components/ApacheSparkLogs/ApacheSparkLogs.tsx deleted file mode 100644 index e892856..0000000 --- a/plugins/apache-spark/src/components/ApacheSparkLogs/ApacheSparkLogs.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import { useApi } from '@backstage/core-plugin-api'; -import { apacheSparkApiRef } from '../../api'; -import useAsync from 'react-use/lib/useAsync'; -import { ApacheSpark } from '../../api/model'; -import { - LogViewer, - Progress, - Select, - SelectedItems, - SelectItem, -} from '@backstage/core-components'; -import Alert from '@material-ui/lab/Alert'; -import React, { useEffect, useState } from 'react'; -import { useEntity } from '@backstage/plugin-catalog-react'; -import { getAnnotationValues } from '../utils'; - -export const ApacheSparkDriverLogs = (props: { sparkApp: ApacheSpark }) => { - const apiClient = useApi(apacheSparkApiRef); - const { entity } = useEntity(); - const { ns, clusterName } = getAnnotationValues(entity); - - const { value, loading, error } = useAsync(async (): Promise => { - return await apiClient.getLogs( - clusterName, - ns, - props.sparkApp.status.driverInfo?.podName!, - 'spark-kubernetes-driver', - ); - }, [props]); - if (loading) { - return ; - } else if (error) { - return {`${error}`}; - } - return ; -}; - -const ExecutorLogs = (props: { name: string }) => { - const apiClient = useApi(apacheSparkApiRef); - const { entity } = useEntity(); - const [logs, setLogs] = useState(''); - const { ns, clusterName } = getAnnotationValues(entity); - - useEffect(() => { - async function getLogs() { - try { - const val = await apiClient.getLogs( - clusterName, - ns, - props.name, - 'spark-kubernetes-executor', - ); - setLogs(val); - } catch (e) { - if (typeof e === 'string') { - setLogs(e); - } - } - } - if (props.name !== '') { - getLogs(); - } - }, [apiClient, clusterName, ns, props]); - - return ; -}; - -export const ApacheSparkExecutorLogs = (props: { sparkApp: ApacheSpark }) => { - const [selected, setSelected] = useState(''); - if (props.sparkApp.status.applicationState.state !== 'RUNNING') { - return ( - - Executor logs are only available for Spark Applications in RUNNING state - - ); - } - const executors: SelectItem[] = [{ label: '', value: '' }]; - for (const key in props.sparkApp.status.executorState) { - if (props.sparkApp.status.executorState.hasOwnProperty(key)) { - executors.push({ label: key, value: key }); - } - } - - const handleChange = (item: SelectedItems) => { - if (typeof item === 'string' && item !== '') { - setSelected(item); - } - }; - return ( - <> -