diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx
index ca6d09e318..c4450c8261 100644
--- a/packages/app/src/components/catalog/EntityPage.tsx
+++ b/packages/app/src/components/catalog/EntityPage.tsx
@@ -112,6 +112,7 @@ import {
EntityOwnershipCard,
EntityUserProfileCard,
} from '@backstage/plugin-org';
+import { EntityNomadContent, isNomadAvailable } from '@backstage/plugin-nomad';
import {
EntityPagerDutyCard,
isPagerDutyAvailable,
@@ -501,6 +502,10 @@ const serviceEntityPage = (
+
+
+
+
{pullRequestsContent}
diff --git a/plugins/nomad/src/Router.tsx b/plugins/nomad/src/Router.tsx
index d65fda777a..7615d6ad99 100644
--- a/plugins/nomad/src/Router.tsx
+++ b/plugins/nomad/src/Router.tsx
@@ -21,16 +21,15 @@ import { MissingAnnotationEmptyState } from '@backstage/core-components';
import { Route, Routes } from 'react-router-dom';
import { GroupListForEntity } from './components/GroupList/GroupListForEntity';
+/** @public */
+export const NOMAD_NAMESPACE_ANNOTATION = 'nomad.io/namespace';
+
/** @public */
export const NOMAD_GROUP_ANNOTATION = 'nomad.io/group';
-/** @public */
-export const NOMAD_TASK_ANNOTATION = 'nomad.io/task';
-
/** @public */
export const isNomadAvailable = (entity: Entity) =>
- Boolean(entity.metadata.annotations?.[NOMAD_GROUP_ANNOTATION]) ||
- Boolean(entity.metadata.annotations?.[NOMAD_TASK_ANNOTATION]);
+ Boolean(entity.metadata.annotations?.[NOMAD_GROUP_ANNOTATION]);
/** @public */
export const EmbeddedRouter = () => {
diff --git a/plugins/nomad/src/api.ts b/plugins/nomad/src/api.ts
index ca5113524b..e9a967e2ae 100644
--- a/plugins/nomad/src/api.ts
+++ b/plugins/nomad/src/api.ts
@@ -25,7 +25,7 @@ export const nomadApiRef = createApiRef({
/** @public */
export type NomadApi = {
/**
- * listAllocations is for listing all allocations matching some part of 'prefix'.
+ * listAllocations is for listing all allocations matching some part of 'filter'.
*
* See: https://developer.hashicorp.com/nomad/api-docs/allocations#list-allocations
*/
@@ -36,8 +36,8 @@ export type NomadApi = {
/** @public */
export interface ListAllocationsRequest {
- namespace?: string;
- filter?: string;
+ namespace: string;
+ filter: string;
}
/** @public */
@@ -47,45 +47,71 @@ export interface ListAllocationsResponse {
/** @public */
export interface Allocation {
- id: string;
- clientStatus: string;
- createTime: number;
- namespace: string;
- name: string;
- modifyTime: number;
- nodeID: string;
- taskGroup: string;
+ ClientStatus: string;
+ CreateTime: number;
+ DeploymentStatus: DeploymentStatus;
+ ID: string;
+ ModifyTime: number;
+ Name: string;
+ Namespace: string;
+ NodeID: string;
+ TaskGroup: string;
}
/** @public */
export interface DeploymentStatus {
- healthy: boolean;
- timestamp: string;
+ Healthy: boolean;
+ Timestamp: string;
+}
+
+/** @public */
+export class FetchError extends Error {
+ get name(): string {
+ return this.constructor.name;
+ }
+
+ static async forResponse(resp: Response): Promise {
+ return new FetchError(
+ `Request failed with status code ${
+ resp.status
+ }.\nReason: ${await resp.text()}`,
+ );
+ }
}
/** @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'),
+ config.getOptionalString('nomad.addr'),
+ config.getOptionalString('nomad.token'),
);
}
constructor(
- private nomadAddr?: string,
- private nomadRegion?: string,
- private nomadNamespace?: string,
- private nomadToken?: string,
+ private addr: string = 'http://127.0.0.1:4646',
+ private token?: string,
) {}
+ // TODO: pagination
async listAllocations(
options: ListAllocationsRequest,
): Promise {
+ const resp = await fetch(
+ `${this.addr}/v1/allocations?namespace=${encodeURIComponent(
+ options.namespace,
+ )}&filter=${encodeURIComponent(options.filter)}`,
+ {
+ method: 'GET',
+ headers: {
+ 'X-Nomad-Token': this.token || '',
+ },
+ },
+ );
+ if (!resp.ok) throw await FetchError.forResponse(resp);
+
return Promise.resolve({
- allocations: [],
+ allocations: await resp.json(),
});
}
}
diff --git a/plugins/nomad/src/components/GroupList/GroupListForEntity.tsx b/plugins/nomad/src/components/GroupList/GroupListForEntity.tsx
index 101d894a81..b6323dcf87 100644
--- a/plugins/nomad/src/components/GroupList/GroupListForEntity.tsx
+++ b/plugins/nomad/src/components/GroupList/GroupListForEntity.tsx
@@ -14,18 +14,20 @@
* limitations under the License.
*/
-import { Table } from '@backstage/core-components';
+import { Progress } from '@backstage/core-components';
import React from 'react';
+import { useGroupForEntity } from '../../hooks/useGroupForEntity';
+import { GroupListTable } from './GroupListTable';
export const GroupListForEntity = () => {
- return (
-
- );
+ const { value, loading, error } = useGroupForEntity();
+
+ if (loading) {
+ return ;
+ }
+ if (error || !value) {
+ return null;
+ }
+
+ return ;
};
diff --git a/plugins/nomad/src/components/GroupList/GroupListTable.tsx b/plugins/nomad/src/components/GroupList/GroupListTable.tsx
new file mode 100644
index 0000000000..b427946805
--- /dev/null
+++ b/plugins/nomad/src/components/GroupList/GroupListTable.tsx
@@ -0,0 +1,47 @@
+/*
+ * 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, TableColumn } from '@backstage/core-components';
+import React from 'react';
+import { Allocation } from '../../api';
+
+const columns: TableColumn[] = [
+ {
+ title: 'Allocation ID',
+ field: 'ID',
+ },
+ {
+ title: 'Name',
+ field: 'Name',
+ },
+];
+
+export const GroupListTable = ({
+ allocations,
+}: {
+ allocations: Allocation[];
+}) => {
+ return (
+
+ );
+};
diff --git a/plugins/nomad/src/hooks/useGroupForEntity.tsx b/plugins/nomad/src/hooks/useGroupForEntity.tsx
index 22a853cbb1..fac3bff598 100644
--- a/plugins/nomad/src/hooks/useGroupForEntity.tsx
+++ b/plugins/nomad/src/hooks/useGroupForEntity.tsx
@@ -15,12 +15,35 @@
*/
import { useEntity } from '@backstage/plugin-catalog-react';
-import { NOMAD_GROUP_ANNOTATION } from '../Router';
+import { NOMAD_GROUP_ANNOTATION, NOMAD_NAMESPACE_ANNOTATION } from '../Router';
+import { errorApiRef, useApi } from '@backstage/core-plugin-api';
+import { nomadApiRef } from '../api';
+import useAsync from 'react-use/lib/useAsync';
/**
* Get the entity's group and query it from the Nomad API.
*/
export const useGroupForEntity = () => {
const { entity } = useEntity();
+
+ const namespace =
+ entity.metadata.annotations?.[NOMAD_NAMESPACE_ANNOTATION] ?? 'default';
const group = entity.metadata.annotations?.[NOMAD_GROUP_ANNOTATION] ?? '';
+
+ const nomadApi = useApi(nomadApiRef);
+ const errorApi = useApi(errorApiRef);
+
+ const response = useAsync(
+ () =>
+ nomadApi.listAllocations({
+ namespace,
+ filter: `TaskGroup == "${group}"`,
+ }),
+ [group],
+ );
+
+ if (response.error) {
+ errorApi.post(response.error);
+ }
+ return response;
};
diff --git a/plugins/nomad/src/index.ts b/plugins/nomad/src/index.ts
index 662b52cd97..59ae5efaee 100644
--- a/plugins/nomad/src/index.ts
+++ b/plugins/nomad/src/index.ts
@@ -13,4 +13,5 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-export { nomadPlugin, NomadPage } from './plugin';
+export { nomadPlugin, NomadPage, EntityNomadContent } from './plugin';
+export { isNomadAvailable, EmbeddedRouter } from './Router';