From 8db93bc33df3d94395eb24aa7878bdab8c9fc002 Mon Sep 17 00:00:00 2001 From: Manabu Mccloskey Date: Thu, 22 Jun 2023 14:29:35 -0700 Subject: [PATCH] wip --- plugins/argo-workflows/.eslintrc.js | 1 + plugins/argo-workflows/README.md | 13 +++ plugins/argo-workflows/dev/index.tsx | 12 ++ plugins/argo-workflows/package.json | 54 +++++++++ plugins/argo-workflows/src/api/indext.ts | 109 ++++++++++++++++++ .../src/components/Overview/Overview.tsx | 24 ++++ .../src/components/Overview/index.ts | 1 + .../src/components/Version/Version.tsx | 43 +++++++ plugins/argo-workflows/src/index.ts | 1 + plugins/argo-workflows/src/plugin.test.ts | 7 ++ plugins/argo-workflows/src/plugin.ts | 42 +++++++ plugins/argo-workflows/src/routes.ts | 5 + plugins/argo-workflows/src/setupTests.ts | 2 + 13 files changed, 314 insertions(+) create mode 100644 plugins/argo-workflows/.eslintrc.js create mode 100644 plugins/argo-workflows/README.md create mode 100644 plugins/argo-workflows/dev/index.tsx create mode 100644 plugins/argo-workflows/package.json create mode 100644 plugins/argo-workflows/src/api/indext.ts create mode 100644 plugins/argo-workflows/src/components/Overview/Overview.tsx create mode 100644 plugins/argo-workflows/src/components/Overview/index.ts create mode 100644 plugins/argo-workflows/src/components/Version/Version.tsx create mode 100644 plugins/argo-workflows/src/index.ts create mode 100644 plugins/argo-workflows/src/plugin.test.ts create mode 100644 plugins/argo-workflows/src/plugin.ts create mode 100644 plugins/argo-workflows/src/routes.ts create mode 100644 plugins/argo-workflows/src/setupTests.ts diff --git a/plugins/argo-workflows/.eslintrc.js b/plugins/argo-workflows/.eslintrc.js new file mode 100644 index 0000000..e2a53a6 --- /dev/null +++ b/plugins/argo-workflows/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/argo-workflows/README.md b/plugins/argo-workflows/README.md new file mode 100644 index 0000000..e155d71 --- /dev/null +++ b/plugins/argo-workflows/README.md @@ -0,0 +1,13 @@ +# argo-workflows + +Welcome to the argo-workflows 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 [/argo-workflows](http://localhost:3000/argo-workflows). + +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/argo-workflows/dev/index.tsx b/plugins/argo-workflows/dev/index.tsx new file mode 100644 index 0000000..712176c --- /dev/null +++ b/plugins/argo-workflows/dev/index.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { createDevApp } from '@backstage/dev-utils'; +import { argoWorkflowsPlugin, ArgoWorkflowsPage } from '../src/plugin'; + +createDevApp() + .registerPlugin(argoWorkflowsPlugin) + .addPage({ + element: , + title: 'Root Page', + path: '/argo-workflows' + }) + .render(); diff --git a/plugins/argo-workflows/package.json b/plugins/argo-workflows/package.json new file mode 100644 index 0000000..6383de5 --- /dev/null +++ b/plugins/argo-workflows/package.json @@ -0,0 +1,54 @@ +{ + "name": "@internal/plugin-argo-workflows", + "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" + }, + "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.13.1", + "@backstage/core-plugin-api": "^1.5.1", + "@backstage/plugin-catalog-react": "^1.7.0", + "@backstage/plugin-kubernetes": "^0.9.1", + "@backstage/theme": "^0.3.0", + "@material-ui/core": "^4.12.2", + "@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.22.7", + "@backstage/core-app-api": "^1.8.0", + "@backstage/dev-utils": "^1.0.15", + "@backstage/test-utils": "^1.3.1", + "@testing-library/jest-dom": "^5.10.1", + "@testing-library/react": "^12.1.3", + "@testing-library/user-event": "^14.0.0", + "@types/node": "*", + "cross-fetch": "^3.1.5", + "msw": "^1.0.0" + }, + "files": [ + "dist" + ] +} diff --git a/plugins/argo-workflows/src/api/indext.ts b/plugins/argo-workflows/src/api/indext.ts new file mode 100644 index 0000000..a1e703d --- /dev/null +++ b/plugins/argo-workflows/src/api/indext.ts @@ -0,0 +1,109 @@ +import { + ConfigApi, + createApiRef, + DiscoveryApi, OAuthRequestApi, +} from '@backstage/core-plugin-api'; + +import {KubernetesApi } from "@backstage/plugin-kubernetes"; + + +const API_VERSION = 'argoproj.io/v1alpha1' +const WORKFLOW_PLURAL = 'workflows' +export const argoWorkflowsApiRef = createApiRef({ + id: 'plugin.argoworkflows', +}) +export interface ArgoWorkflowsApi { + discoveryApi: DiscoveryApi + kubernetesApi: KubernetesApi + getWorkflows(clusterName: string | undefined, namespace: string | undefined, labels: string | undefined): Promise +} + +type Metadata = { + annotations: Record + labels: Record + name: string + namespace: string +} + +type Workflows = { + workflows: Workflow[] +} + + + +export type Workflow = { + metadata: Metadata + spec: any + status?: any +} + +type WorkflowStatus = { + finishedAt: string + startedAt: string + phase: string + progress: string + +} + + +export class ArgoWorkflows implements ArgoWorkflowsApi { + discoveryApi: DiscoveryApi + kubernetesApi: KubernetesApi + configApi: ConfigApi + oauthRequestApi: OAuthRequestApi + + constructor(discoveryApi: DiscoveryApi, kubernetesApi: KubernetesApi, configApi: ConfigApi, oauthRequestApi: OAuthRequestApi) { + this.discoveryApi = discoveryApi + this.kubernetesApi = kubernetesApi + this.configApi = configApi + this.oauthRequestApi = oauthRequestApi + } + + async getWorkflows(clusterName: string | undefined, namespace: string | undefined, labels: string | undefined): Promise { + const ns = namespace !== undefined ? namespace : 'default' + const path = `/apis/${API_VERSION}/namespaces/${ns}/${WORKFLOW_PLURAL}` + const query = new URLSearchParams() + if (labels) { + query.set('labelSelector', labels) + } + const resp = await this.kubernetesApi.proxy({ + clusterName: clusterName !== undefined ? clusterName: await this.getCluster(), + path: `${path}?${query.toString()}` + }) + + if (!resp.ok) { + return Promise.reject(`failed to fetch resources: ${resp.status}, ${resp.statusText}, ${await resp.json()}`) + } + return Promise.resolve(resp.json()); + } + + async getCluster(): 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") + } + + // async getToken(clusterName: string): Promise { + // const clusters = await this.kubernetesApi.getClusters() + // const cluster = clusters.find(c => { + // return c.name === clusterName + // }) + // if (!cluster) { + // return Promise.reject(`cluster ${clusterName} not found`) + // } + // const oidc = OAuth2.create({ + // discoveryApi: this.discoveryApi, + // oauthRequestApi: this.oauthRequestApi, + // provider: { + // id: cluster.oidcTokenProvider!, + // title: 'OIDC', + // icon: () => null, + // }, + // environment: this.configApi.getOptionalString('auth.environment'), + // defaultScopes: ['openid', 'profile', 'email', 'groups'], + // }) + // return oidc.getIdToken() + // } +} diff --git a/plugins/argo-workflows/src/components/Overview/Overview.tsx b/plugins/argo-workflows/src/components/Overview/Overview.tsx new file mode 100644 index 0000000..6bc6e44 --- /dev/null +++ b/plugins/argo-workflows/src/components/Overview/Overview.tsx @@ -0,0 +1,24 @@ + +import React from 'react'; +import {Header, HeaderLabel, Page, Content, ContentHeader, SupportButton} from "@backstage/core-components"; +import {Grid} from "@material-ui/core"; +import {VersionComponent} from "../Version/Version"; + + +export const OverviewComponent = () => ( + +
+ +
+ + + + Overview of your Argo Workflows + + + + + + +
+) diff --git a/plugins/argo-workflows/src/components/Overview/index.ts b/plugins/argo-workflows/src/components/Overview/index.ts new file mode 100644 index 0000000..ff77359 --- /dev/null +++ b/plugins/argo-workflows/src/components/Overview/index.ts @@ -0,0 +1 @@ +export {OverviewComponent} from "./Overview"; diff --git a/plugins/argo-workflows/src/components/Version/Version.tsx b/plugins/argo-workflows/src/components/Version/Version.tsx new file mode 100644 index 0000000..6c7bd51 --- /dev/null +++ b/plugins/argo-workflows/src/components/Version/Version.tsx @@ -0,0 +1,43 @@ +import {useApi} from "@backstage/core-plugin-api"; +import {argoWorkflowsApiRef} from "../../api/indext"; +import useAsync from "react-use/lib/useAsync"; +import {InfoCard, Progress, StructuredMetadataTable} from '@backstage/core-components' +import React from "react"; +import Alert from "@material-ui/lab/Alert"; +import { useEntity } from '@backstage/plugin-catalog-react'; + + + +export const VersionComponent = () => { + const {entity} = useEntity() + const apiClient = useApi(argoWorkflowsApiRef) + + const ln = entity.metadata.annotations?.['backstage.io/kubernetes-namespace'] + const ns = ln !== undefined ? ln : 'default' + const clusterName = entity.metadata.annotations?.['argo-workflows/cluster-name'] + + const k8sLabelSelector = entity.metadata.annotations?.['backstage.io/kubernetes-label-selector'] + + const {value, loading, error} = useAsync( + async (): Promise => { + return await apiClient.getWorkflows(clusterName, ns, k8sLabelSelector) + } + ) + if (loading) { + return ; + } else if (error) { + return {error.message}; + } + if (value) { + const m = { + namespaces: value + } + return ( + + + + ) + } + return Oh no + +} diff --git a/plugins/argo-workflows/src/index.ts b/plugins/argo-workflows/src/index.ts new file mode 100644 index 0000000..5b080f4 --- /dev/null +++ b/plugins/argo-workflows/src/index.ts @@ -0,0 +1 @@ +export { argoWorkflowsPlugin, ArgoWorkflowsPage } from './plugin'; diff --git a/plugins/argo-workflows/src/plugin.test.ts b/plugins/argo-workflows/src/plugin.test.ts new file mode 100644 index 0000000..bcb714a --- /dev/null +++ b/plugins/argo-workflows/src/plugin.test.ts @@ -0,0 +1,7 @@ +import { argoWorkflowsPlugin } from './plugin'; + +describe('argo-workflows', () => { + it('should export plugin', () => { + expect(argoWorkflowsPlugin).toBeDefined(); + }); +}); diff --git a/plugins/argo-workflows/src/plugin.ts b/plugins/argo-workflows/src/plugin.ts new file mode 100644 index 0000000..51a2b55 --- /dev/null +++ b/plugins/argo-workflows/src/plugin.ts @@ -0,0 +1,42 @@ +import { + configApiRef, + createApiFactory, + createPlugin, + createRoutableExtension, + discoveryApiRef, oauthRequestApiRef +} from '@backstage/core-plugin-api'; + +import { rootRouteRef } from './routes'; +import {ArgoWorkflows, argoWorkflowsApiRef} from "./api/indext"; +import {kubernetesApiRef} from "@backstage/plugin-kubernetes"; + + +export const argoWorkflowsPlugin = createPlugin({ + id: 'argo-workflows', + routes: { + root: rootRouteRef, + }, + apis: [ + createApiFactory({ + api: argoWorkflowsApiRef, + deps: { + discoveryApi: discoveryApiRef, + kubernetesApi: kubernetesApiRef, + oauthRequestApi: oauthRequestApiRef, + configApi: configApiRef}, + factory: ({ + discoveryApi, kubernetesApi, configApi, oauthRequestApi, + }) => + new ArgoWorkflows(discoveryApi, kubernetesApi, configApi, oauthRequestApi) + }) + ] +}); + +export const ArgoWorkflowsPage = argoWorkflowsPlugin.provide( + createRoutableExtension({ + name: 'ArgoWorkflowsPage', + component: () => + import('./components/Overview').then(m => m.OverviewComponent), + mountPoint: rootRouteRef, + }), +); diff --git a/plugins/argo-workflows/src/routes.ts b/plugins/argo-workflows/src/routes.ts new file mode 100644 index 0000000..f971bdd --- /dev/null +++ b/plugins/argo-workflows/src/routes.ts @@ -0,0 +1,5 @@ +import { createRouteRef } from '@backstage/core-plugin-api'; + +export const rootRouteRef = createRouteRef({ + id: 'argo-workflows', +}); diff --git a/plugins/argo-workflows/src/setupTests.ts b/plugins/argo-workflows/src/setupTests.ts new file mode 100644 index 0000000..48c09b5 --- /dev/null +++ b/plugins/argo-workflows/src/setupTests.ts @@ -0,0 +1,2 @@ +import '@testing-library/jest-dom'; +import 'cross-fetch/polyfill';