This commit is contained in:
Manabu Mccloskey 2023-06-22 14:29:35 -07:00
parent 4e04538e18
commit 8db93bc33d
13 changed files with 314 additions and 0 deletions

View file

@ -0,0 +1 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);

View file

@ -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.

View file

@ -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: <ArgoWorkflowsPage />,
title: 'Root Page',
path: '/argo-workflows'
})
.render();

View file

@ -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"
]
}

View file

@ -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<ArgoWorkflowsApi>({
id: 'plugin.argoworkflows',
})
export interface ArgoWorkflowsApi {
discoveryApi: DiscoveryApi
kubernetesApi: KubernetesApi
getWorkflows(clusterName: string | undefined, namespace: string | undefined, labels: string | undefined): Promise<string>
}
type Metadata = {
annotations: Record<string, string>
labels: Record<string, string>
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<string> {
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<string> {
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<string> {
// 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()
// }
}

View file

@ -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 = () => (
<Page themeId="tool">
<Header title="Argo Workflows" subtitle="Workflows overview">
<HeaderLabel label="Lifecycle" value="Alpha" />
</Header>
<Content>
<ContentHeader title="Overview">
<SupportButton>
Overview of your Argo Workflows
</SupportButton>
</ContentHeader>
<Grid item>
<VersionComponent />
</Grid>
</Content>
</Page>
)

View file

@ -0,0 +1 @@
export {OverviewComponent} from "./Overview";

View file

@ -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<string> => {
return await apiClient.getWorkflows(clusterName, ns, k8sLabelSelector)
}
)
if (loading) {
return <Progress />;
} else if (error) {
return <Alert severity="error">{error.message}</Alert>;
}
if (value) {
const m = {
namespaces: value
}
return (
<InfoCard title="Testing" variant="fullHeight">
<StructuredMetadataTable metadata={m} />
</InfoCard>
)
}
return <Alert severity="warning">Oh no</Alert>
}

View file

@ -0,0 +1 @@
export { argoWorkflowsPlugin, ArgoWorkflowsPage } from './plugin';

View file

@ -0,0 +1,7 @@
import { argoWorkflowsPlugin } from './plugin';
describe('argo-workflows', () => {
it('should export plugin', () => {
expect(argoWorkflowsPlugin).toBeDefined();
});
});

View file

@ -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,
}),
);

View file

@ -0,0 +1,5 @@
import { createRouteRef } from '@backstage/core-plugin-api';
export const rootRouteRef = createRouteRef({
id: 'argo-workflows',
});

View file

@ -0,0 +1,2 @@
import '@testing-library/jest-dom';
import 'cross-fetch/polyfill';