Add Nomad Backend plugin
Signed-off-by: josh <josh.timmons@hashicorp.com>
This commit is contained in:
@@ -54,6 +54,7 @@
|
||||
/plugins/gcalendar @backstage/maintainers @szubster @ptychu @kielosz @alexrybch
|
||||
/plugins/git-release-manager @backstage/maintainers @erikengervall
|
||||
/plugins/home @backstage/discoverability-maintainers
|
||||
/plugins/home-* @backstage/discoverability-maintainers
|
||||
/plugins/ilert @backstage/maintainers @yacut
|
||||
/plugins/jenkins @backstage/maintainers @timja
|
||||
/plugins/jenkins-backend @backstage/maintainers @timja
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"@backstage/plugin-kubernetes-backend": "workspace:^",
|
||||
"@backstage/plugin-lighthouse-backend": "workspace:^",
|
||||
"@backstage/plugin-linguist-backend": "workspace:^",
|
||||
"@backstage/plugin-nomad-backend": "workspace:^",
|
||||
"@backstage/plugin-permission-backend": "workspace:^",
|
||||
"@backstage/plugin-permission-common": "workspace:^",
|
||||
"@backstage/plugin-permission-node": "workspace:^",
|
||||
|
||||
@@ -65,6 +65,7 @@ import adr from './plugins/adr';
|
||||
import lighthouse from './plugins/lighthouse';
|
||||
import linguist from './plugins/linguist';
|
||||
import devTools from './plugins/devtools';
|
||||
import nomad from './plugins/nomad';
|
||||
import { PluginEnvironment } from './types';
|
||||
import { ServerPermissionClient } from '@backstage/plugin-permission-node';
|
||||
import { DefaultIdentityClient } from '@backstage/plugin-auth-node';
|
||||
@@ -172,6 +173,7 @@ async function main() {
|
||||
const lighthouseEnv = useHotMemoize(module, () => createEnv('lighthouse'));
|
||||
const linguistEnv = useHotMemoize(module, () => createEnv('linguist'));
|
||||
const devToolsEnv = useHotMemoize(module, () => createEnv('devtools'));
|
||||
const nomadEnv = useHotMemoize(module, () => createEnv('nomad'));
|
||||
|
||||
const apiRouter = Router();
|
||||
apiRouter.use('/catalog', await catalog(catalogEnv));
|
||||
@@ -198,6 +200,7 @@ async function main() {
|
||||
apiRouter.use('/adr', await adr(adrEnv));
|
||||
apiRouter.use('/linguist', await linguist(linguistEnv));
|
||||
apiRouter.use('/devtools', await devTools(devToolsEnv));
|
||||
apiRouter.use('/nomad', await nomad(nomadEnv));
|
||||
apiRouter.use(notFoundHandler());
|
||||
|
||||
await lighthouse(lighthouseEnv);
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2023 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 { createRouter } from '@backstage/plugin-nomad-backend';
|
||||
import { Router } from 'express';
|
||||
import { PluginEnvironment } from '../types';
|
||||
|
||||
export default async function createPlugin(
|
||||
env: PluginEnvironment,
|
||||
): Promise<Router> {
|
||||
// Here is where you will add all of the required initialization code that
|
||||
// your backend plugin needs to be able to start!
|
||||
|
||||
// The env contains a lot of goodies, but our router currently only
|
||||
// needs a logger
|
||||
return await createRouter({
|
||||
logger: env.logger,
|
||||
config: env.config,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
|
||||
@@ -0,0 +1,14 @@
|
||||
# nomad
|
||||
|
||||
Welcome to the nomad backend 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 [/nomad-backend](http://localhost:3000/nomad-backend).
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,69 @@
|
||||
{
|
||||
"name": "@backstage/plugin-nomad-backend",
|
||||
"version": "0.0.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "dist/index.cjs.js",
|
||||
"types": "dist/index.d.ts"
|
||||
},
|
||||
"backstage": {
|
||||
"role": "backend-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/backend-common": "workspace:^",
|
||||
"@backstage/config": "workspace:^",
|
||||
"@backstage/errors": "workspace:^",
|
||||
"@types/express": "*",
|
||||
"express": "^4.17.1",
|
||||
"express-promise-router": "^4.1.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"winston": "^3.2.1",
|
||||
"yn": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "workspace:^",
|
||||
"@types/supertest": "^2.0.8",
|
||||
"msw": "^1.0.0",
|
||||
"supertest": "^6.2.4"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"configSchema": {
|
||||
"$schema": "https://backstage.io/schema/config-v1",
|
||||
"title": "@backstage/nomad",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"nomad": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"addr": {
|
||||
"type": "string",
|
||||
"description": "The address of the Nomad API. See: https://developer.hashicorp.com/nomad/api-docs#addressing-and-ports",
|
||||
"visibility": "backend"
|
||||
},
|
||||
"token": {
|
||||
"type": "string",
|
||||
"description": "The token to call the Nomad API with. See: https://developer.hashicorp.com/nomad/api-docs#authentication",
|
||||
"visibility": "secret"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"addr"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright 2023 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.
|
||||
*/
|
||||
export * from './service/router';
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2023 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 { getRootLogger } from '@backstage/backend-common';
|
||||
import yn from 'yn';
|
||||
import { startStandaloneServer } from './service/standaloneServer';
|
||||
|
||||
const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7007;
|
||||
const enableCors = yn(process.env.PLUGIN_CORS, { default: false });
|
||||
const logger = getRootLogger();
|
||||
|
||||
startStandaloneServer({ port, enableCors, logger }).catch(err => {
|
||||
logger.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
logger.info('CTRL+C pressed; exiting.');
|
||||
process.exit(0);
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2023 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 { getVoidLogger } from '@backstage/backend-common';
|
||||
import express from 'express';
|
||||
import request from 'supertest';
|
||||
|
||||
import { createRouter } from './router';
|
||||
|
||||
describe('createRouter', () => {
|
||||
let app: express.Express;
|
||||
|
||||
beforeAll(async () => {
|
||||
const router = await createRouter({
|
||||
logger: getVoidLogger(),
|
||||
});
|
||||
app = express().use(router);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('GET /health', () => {
|
||||
it('returns ok', async () => {
|
||||
const response = await request(app).get('/health');
|
||||
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toEqual({ status: 'ok' });
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2023 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 { errorHandler, requestLoggingHandler } from '@backstage/backend-common';
|
||||
import { Config } from '@backstage/config';
|
||||
import { InputError } from '@backstage/errors';
|
||||
import express from 'express';
|
||||
import Router from 'express-promise-router';
|
||||
import { Logger } from 'winston';
|
||||
|
||||
export interface RouterOptions {
|
||||
logger: Logger;
|
||||
config: Config;
|
||||
}
|
||||
|
||||
export async function createRouter(
|
||||
options: RouterOptions,
|
||||
): Promise<express.Router> {
|
||||
const { config, logger } = options;
|
||||
|
||||
const router = Router();
|
||||
router.use(express.json());
|
||||
|
||||
router.get('/health', (_, response) => {
|
||||
logger.info('PONG!');
|
||||
response.json({ status: 'ok' });
|
||||
});
|
||||
|
||||
router.get('/v1/allocations', async (req, resp) => {
|
||||
const addr = config.getString('nomad.addr');
|
||||
const token = config.getOptionalString('nomad.token');
|
||||
|
||||
const namespace = (req.query.namespace as string) ?? '';
|
||||
if (!namespace || namespace === '') {
|
||||
throw new InputError(`Missing "namespace" query parameter`);
|
||||
}
|
||||
|
||||
const filter = (req.query.filter as string) ?? '';
|
||||
if (!filter || filter === '') {
|
||||
throw new InputError(`Missing "filter" query parameter`);
|
||||
}
|
||||
|
||||
logger.debug(
|
||||
`/v1/allocations request headers: namespace=${namespace} filter=${filter}`,
|
||||
);
|
||||
const allocationsResp = await fetch(
|
||||
`${addr}/v1/allocations?namespace=${encodeURIComponent(
|
||||
namespace,
|
||||
)}&filter=${encodeURIComponent(filter)}`,
|
||||
{
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'X-Nomad-Token': token || '',
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const allocationsBody = await allocationsResp.json();
|
||||
logger.debug(`/v1/allocations response: ${allocationsBody}`);
|
||||
|
||||
resp.json(allocationsBody);
|
||||
});
|
||||
|
||||
router.use(requestLoggingHandler());
|
||||
router.use(errorHandler());
|
||||
return router;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2023 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 { createServiceBuilder } from '@backstage/backend-common';
|
||||
import { Server } from 'http';
|
||||
import { Logger } from 'winston';
|
||||
import { createRouter } from './router';
|
||||
import { Config } from '@backstage/config';
|
||||
|
||||
export interface ServerOptions {
|
||||
config: Config;
|
||||
enableCors: boolean;
|
||||
logger: Logger;
|
||||
port: number;
|
||||
}
|
||||
|
||||
export async function startStandaloneServer(
|
||||
options: ServerOptions,
|
||||
): Promise<Server> {
|
||||
const logger = options.logger.child({ service: 'nomad' });
|
||||
logger.debug('Starting application server...');
|
||||
const router = await createRouter({
|
||||
logger,
|
||||
config: options.config,
|
||||
});
|
||||
|
||||
let service = createServiceBuilder(module)
|
||||
.setPort(options.port)
|
||||
.addRouter('/nomad', router);
|
||||
if (options.enableCors) {
|
||||
service = service.enableCors({ origin: 'http://localhost:3000' });
|
||||
}
|
||||
|
||||
return await service.start().catch(err => {
|
||||
logger.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
module.hot?.accept();
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright 2023 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.
|
||||
*/
|
||||
export {};
|
||||
@@ -51,27 +51,5 @@
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"configSchema": {
|
||||
"$schema": "https://backstage.io/schema/config-v1",
|
||||
"title": "@backstage/nomad",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"nomad": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"addr": {
|
||||
"type": "string",
|
||||
"description": "The address of the Nomad API. See: https://developer.hashicorp.com/nomad/api-docs#addressing-and-ports",
|
||||
"visibility": "frontend"
|
||||
},
|
||||
"token": {
|
||||
"type": "string",
|
||||
"description": "The token to call the Nomad API with. See: https://developer.hashicorp.com/nomad/api-docs#authentication",
|
||||
"visibility": "secret"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+12
-19
@@ -14,8 +14,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Config } from '@backstage/config';
|
||||
import { createApiRef } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
DiscoveryApi,
|
||||
FetchApi,
|
||||
createApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
|
||||
/** @public */
|
||||
export const nomadApiRef = createApiRef<NomadApi>({
|
||||
@@ -81,32 +84,22 @@ export class FetchError extends Error {
|
||||
|
||||
/** @public */
|
||||
export class NomadHttpApi implements NomadApi {
|
||||
static fromConfig(config: Config) {
|
||||
return new NomadHttpApi(
|
||||
config.getOptionalString('nomad.addr'),
|
||||
config.getOptionalString('nomad.token'),
|
||||
);
|
||||
static new(discoveryApi: DiscoveryApi, fetchApi: FetchApi) {
|
||||
return new NomadHttpApi(discoveryApi, fetchApi);
|
||||
}
|
||||
|
||||
constructor(
|
||||
private addr: string = 'http://127.0.0.1:4646',
|
||||
private token?: string,
|
||||
) {}
|
||||
constructor(private discoveryApi: DiscoveryApi, private fetchApi: FetchApi) {}
|
||||
|
||||
// TODO: pagination
|
||||
async listAllocations(
|
||||
options: ListAllocationsRequest,
|
||||
): Promise<ListAllocationsResponse> {
|
||||
const resp = await fetch(
|
||||
`${this.addr}/v1/allocations?namespace=${encodeURIComponent(
|
||||
const apiUrl = await this.discoveryApi.getBaseUrl('nomad');
|
||||
|
||||
const resp = await this.fetchApi.fetch(
|
||||
`${apiUrl}/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);
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ export const GroupListForEntity = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
// Issue call to nomad-backend
|
||||
try {
|
||||
const resp = await nomadApi.listAllocations({
|
||||
namespace,
|
||||
@@ -58,7 +59,7 @@ export const GroupListForEntity = () => {
|
||||
if (response.loading) {
|
||||
return <Progress />;
|
||||
}
|
||||
if (response.error || !response.value) {
|
||||
if (response.error) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {
|
||||
configApiRef,
|
||||
discoveryApiRef,
|
||||
fetchApiRef,
|
||||
createApiFactory,
|
||||
createPlugin,
|
||||
createRoutableExtension,
|
||||
@@ -33,8 +34,9 @@ export const nomadPlugin = createPlugin({
|
||||
apis: [
|
||||
createApiFactory({
|
||||
api: nomadApiRef,
|
||||
deps: { configApi: configApiRef },
|
||||
factory: ({ configApi }) => NomadHttpApi.fromConfig(configApi),
|
||||
deps: { discoveryApi: discoveryApiRef, fetchApi: fetchApiRef },
|
||||
factory: ({ discoveryApi, fetchApi }) =>
|
||||
NomadHttpApi.new(discoveryApi, fetchApi),
|
||||
}),
|
||||
],
|
||||
routes: {
|
||||
|
||||
@@ -7926,6 +7926,26 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/plugin-nomad-backend@workspace:^, @backstage/plugin-nomad-backend@workspace:plugins/nomad-backend":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/plugin-nomad-backend@workspace:plugins/nomad-backend"
|
||||
dependencies:
|
||||
"@backstage/backend-common": "workspace:^"
|
||||
"@backstage/cli": "workspace:^"
|
||||
"@backstage/config": "workspace:^"
|
||||
"@backstage/errors": "workspace:^"
|
||||
"@types/express": "*"
|
||||
"@types/supertest": ^2.0.8
|
||||
express: ^4.17.1
|
||||
express-promise-router: ^4.1.0
|
||||
msw: ^1.0.0
|
||||
node-fetch: ^2.6.7
|
||||
supertest: ^6.2.4
|
||||
winston: ^3.2.1
|
||||
yn: ^4.0.0
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/plugin-nomad@workspace:^, @backstage/plugin-nomad@workspace:plugins/nomad":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/plugin-nomad@workspace:plugins/nomad"
|
||||
@@ -24670,6 +24690,7 @@ __metadata:
|
||||
"@backstage/plugin-kubernetes-backend": "workspace:^"
|
||||
"@backstage/plugin-lighthouse-backend": "workspace:^"
|
||||
"@backstage/plugin-linguist-backend": "workspace:^"
|
||||
"@backstage/plugin-nomad-backend": "workspace:^"
|
||||
"@backstage/plugin-permission-backend": "workspace:^"
|
||||
"@backstage/plugin-permission-common": "workspace:^"
|
||||
"@backstage/plugin-permission-node": "workspace:^"
|
||||
|
||||
Reference in New Issue
Block a user