diff --git a/plugins/nomad/package.json b/plugins/nomad/package.json index e195a2fbe0..79a0fb0607 100644 --- a/plugins/nomad/package.json +++ b/plugins/nomad/package.json @@ -23,8 +23,10 @@ }, "dependencies": { "@backstage/catalog-model": "workspace:^", + "@backstage/config": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", + "@backstage/plugin-catalog-react": "workspace:^", "@backstage/theme": "workspace:^", "@material-ui/core": "^4.9.13", "@material-ui/icons": "^4.9.1", @@ -32,7 +34,8 @@ "react-use": "^17.2.4" }, "peerDependencies": { - "react": "^16.13.1 || ^17.0.0" + "react": "^16.13.1 || ^17.0.0", + "react-router-dom": "*" }, "devDependencies": { "@backstage/cli": "workspace:^", diff --git a/plugins/nomad/src/components/Router.tsx b/plugins/nomad/src/Router.tsx similarity index 63% rename from plugins/nomad/src/components/Router.tsx rename to plugins/nomad/src/Router.tsx index c6b4ac47e8..d65fda777a 100644 --- a/plugins/nomad/src/components/Router.tsx +++ b/plugins/nomad/src/Router.tsx @@ -14,10 +14,12 @@ * limitations under the License. */ +import React from 'react'; import { Entity } from '@backstage/catalog-model'; - -/** @public */ -export const NOMAD_JOB_ANNOTATION = 'nomad.io/job'; +import { useEntity } from '@backstage/plugin-catalog-react'; +import { MissingAnnotationEmptyState } from '@backstage/core-components'; +import { Route, Routes } from 'react-router-dom'; +import { GroupListForEntity } from './components/GroupList/GroupListForEntity'; /** @public */ export const NOMAD_GROUP_ANNOTATION = 'nomad.io/group'; @@ -27,6 +29,20 @@ export const NOMAD_TASK_ANNOTATION = 'nomad.io/task'; /** @public */ export const isNomadAvailable = (entity: Entity) => - Boolean(entity.metadata.annotations?.[NOMAD_JOB_ANNOTATION]) || Boolean(entity.metadata.annotations?.[NOMAD_GROUP_ANNOTATION]) || Boolean(entity.metadata.annotations?.[NOMAD_TASK_ANNOTATION]); + +/** @public */ +export const EmbeddedRouter = () => { + const { entity } = useEntity(); + + if (!isNomadAvailable(entity)) { + return ; + } + + return ( + + } /> + + ); +}; diff --git a/plugins/nomad/src/api.ts b/plugins/nomad/src/api.ts new file mode 100644 index 0000000000..ca5113524b --- /dev/null +++ b/plugins/nomad/src/api.ts @@ -0,0 +1,91 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Config } from '@backstage/config'; +import { createApiRef } from '@backstage/core-plugin-api'; + +/** @public */ +export const nomadApiRef = createApiRef({ + id: 'plugin.nomad.service', +}); + +/** @public */ +export type NomadApi = { + /** + * listAllocations is for listing all allocations matching some part of 'prefix'. + * + * See: https://developer.hashicorp.com/nomad/api-docs/allocations#list-allocations + */ + listAllocations: ( + options: ListAllocationsRequest, + ) => Promise; +}; + +/** @public */ +export interface ListAllocationsRequest { + namespace?: string; + filter?: string; +} + +/** @public */ +export interface ListAllocationsResponse { + allocations: Allocation[]; +} + +/** @public */ +export interface Allocation { + id: string; + clientStatus: string; + createTime: number; + namespace: string; + name: string; + modifyTime: number; + nodeID: string; + taskGroup: string; +} + +/** @public */ +export interface DeploymentStatus { + healthy: boolean; + timestamp: string; +} + +/** @public */ +export class NomadHttpApi implements NomadApi { + static fromConfig(config: Config) { + return new NomadHttpApi( + config.getOptionalString('nomad.nomadAddr'), + config.getOptionalString('nomad.nomadRegion'), + config.getOptionalString('nomad.nomadNamespace'), + config.getOptionalString('nomad.nomadToken'), + ); + } + + constructor( + private nomadAddr?: string, + private nomadRegion?: string, + private nomadNamespace?: string, + private nomadToken?: string, + ) {} + + async listAllocations( + options: ListAllocationsRequest, + ): Promise { + return Promise.resolve({ + allocations: [], + }); + } +} diff --git a/plugins/nomad/src/components/GroupList/GroupListForEntity.tsx b/plugins/nomad/src/components/GroupList/GroupListForEntity.tsx new file mode 100644 index 0000000000..101d894a81 --- /dev/null +++ b/plugins/nomad/src/components/GroupList/GroupListForEntity.tsx @@ -0,0 +1,31 @@ +/* + * Copyright 2020 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 { Table } from '@backstage/core-components'; +import React from 'react'; + +export const GroupListForEntity = () => { + return ( + + ); +}; diff --git a/plugins/nomad/src/hooks/useGroupForEntity.tsx b/plugins/nomad/src/hooks/useGroupForEntity.tsx new file mode 100644 index 0000000000..22a853cbb1 --- /dev/null +++ b/plugins/nomad/src/hooks/useGroupForEntity.tsx @@ -0,0 +1,26 @@ +/* + * Copyright 2020 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 { useEntity } from '@backstage/plugin-catalog-react'; +import { NOMAD_GROUP_ANNOTATION } from '../Router'; + +/** + * Get the entity's group and query it from the Nomad API. + */ +export const useGroupForEntity = () => { + const { entity } = useEntity(); + const group = entity.metadata.annotations?.[NOMAD_GROUP_ANNOTATION] ?? ''; +}; diff --git a/plugins/nomad/src/plugin.test.ts b/plugins/nomad/src/plugin.test.ts index 0de9050c70..def0f8ab89 100644 --- a/plugins/nomad/src/plugin.test.ts +++ b/plugins/nomad/src/plugin.test.ts @@ -13,10 +13,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { nomadPlugin } from './plugin'; +import { + nomadPlugin, + entityContentRouteRef, + EntityNomadContent, +} from './plugin'; describe('nomad', () => { it('should export plugin', () => { expect(nomadPlugin).toBeDefined(); }); + + it('should export Nomad entity ref', () => { + expect(entityContentRouteRef).toBeDefined(); + }); + + it('should export Nomad entity', () => { + expect(EntityNomadContent).toBeDefined(); + }); }); diff --git a/plugins/nomad/src/plugin.ts b/plugins/nomad/src/plugin.ts index c5c8f81aad..535b898723 100644 --- a/plugins/nomad/src/plugin.ts +++ b/plugins/nomad/src/plugin.ts @@ -14,19 +14,36 @@ * limitations under the License. */ import { + configApiRef, + createApiFactory, createPlugin, createRoutableExtension, + createRouteRef, } from '@backstage/core-plugin-api'; import { rootRouteRef } from './routes'; +import { NomadHttpApi, nomadApiRef } from './api'; + +export const entityContentRouteRef = createRouteRef({ + id: 'nomad:entity-content', +}); export const nomadPlugin = createPlugin({ id: 'nomad', + apis: [ + createApiFactory({ + api: nomadApiRef, + deps: { configApi: configApiRef }, + factory: ({ configApi }) => NomadHttpApi.fromConfig(configApi), + }), + ], routes: { root: rootRouteRef, + entityContent: entityContentRouteRef, }, }); +/** @public */ export const NomadPage = nomadPlugin.provide( createRoutableExtension({ name: 'NomadPage', @@ -35,3 +52,12 @@ export const NomadPage = nomadPlugin.provide( mountPoint: rootRouteRef, }), ); + +/** @public */ +export const EntityNomadContent = nomadPlugin.provide( + createRoutableExtension({ + name: 'EntityNomadContent', + component: () => import('./Router').then(m => m.EmbeddedRouter), + mountPoint: entityContentRouteRef, + }), +); diff --git a/yarn.lock b/yarn.lock index 9417d52474..919ab12667 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7932,10 +7932,12 @@ __metadata: dependencies: "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" + "@backstage/config": "workspace:^" "@backstage/core-app-api": "workspace:^" "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" + "@backstage/plugin-catalog-react": "workspace:^" "@backstage/test-utils": "workspace:^" "@backstage/theme": "workspace:^" "@material-ui/core": ^4.9.13 @@ -7950,6 +7952,7 @@ __metadata: react-use: ^17.2.4 peerDependencies: react: ^16.13.1 || ^17.0.0 + react-router-dom: "*" languageName: unknown linkType: soft