Skip to content

Commit

Permalink
feat: add generic list services endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
zcstarr committed Aug 10, 2019
1 parent 62d341e commit bce7140
Show file tree
Hide file tree
Showing 5 changed files with 263 additions and 5 deletions.
51 changes: 50 additions & 1 deletion openrpc.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,55 @@
}
}
},
{
"name": "listServices",
"params": [
{
"name": "filter",
"schema": {
"type": "string",
"enum": [
"all",
"running",
"available",
"installed"
]
},
"required": true
}
],
"result": {
"name": "listedServices",
"schema": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"version": {
"type": "string"
},
"enviornments": {
"type": "array",
"items": {
"type": "string"
}
},
"state": {
"type": "string",
"enum": [
"active",
"available",
"installed"
]
}
}
}
}
}
},
{
"name": "listInstalledServices",
"params": [],
Expand Down Expand Up @@ -211,4 +260,4 @@
}
},
"openrpc": "1.0.0-rc0"
}
}
15 changes: 13 additions & 2 deletions src/generated-types/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
export type StringWxzVcTo3 = string;
export type StringFek9G2ZM = "all" | "running" | "available" | "installed";
export type BooleanQg3XFxa5 = boolean;
export type ArrayFoEQPbEQ = StringWxzVcTo3[];
export type StringTLqBBstC = "active" | "available" | "installed";
export interface ObjectT9Jrtxk4 {
name?: StringWxzVcTo3;
version?: StringWxzVcTo3;
enviornments?: ArrayFoEQPbEQ;
state?: StringTLqBBstC;
[k: string]: any;
}
export type ArrayLRvfa67I = ObjectT9Jrtxk4[];
export interface ObjectVZsrKceH {
name?: StringWxzVcTo3;
version?: StringWxzVcTo3;
[k: string]: any;
}
export type ArrayKvcc3Slb = ObjectVZsrKceH[];
export type ArrayFoEQPbEQ = StringWxzVcTo3[];
export interface ObjectD8RkBGZG {
cmd: StringWxzVcTo3;
args: ArrayFoEQPbEQ;
Expand Down Expand Up @@ -42,6 +52,7 @@ export interface ObjectDLZvXzsu {
}
export type ArrayZUy9Ik8E = ObjectDLZvXzsu[];
export type InstallService = (serviceName: StringWxzVcTo3, version: StringWxzVcTo3) => Promise<BooleanQg3XFxa5>;
export type ListServices = (filter: StringFek9G2ZM) => Promise<ArrayLRvfa67I>;
export type ListInstalledServices = () => Promise<ArrayKvcc3Slb>;
export type ListRunningServices = () => Promise<ArrayZUy9Ik8E>;
export type StartService = (name: StringWxzVcTo3, version: StringWxzVcTo3, environment: StringWxzVcTo3) => Promise<ObjectDLZvXzsu>;
export type StartService = (name: StringWxzVcTo3, version: StringWxzVcTo3, environment: StringWxzVcTo3) => Promise<ObjectDLZvXzsu>;
16 changes: 16 additions & 0 deletions src/lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ export interface Service {
assets: string[];
}

export interface ServiceDesc {
name: string;
version: string;
environments: string[];
}

export interface Health {
port: string;
protocol: "udp" | "tcp";
Expand All @@ -42,6 +48,7 @@ export interface Services {
};
version: string;
}

export interface ServiceOS {
commands: Commands;
assets: string[];
Expand Down Expand Up @@ -110,6 +117,15 @@ export class Config {
version,
};
}

public getAvailableServices(os: string): ServiceDesc[] {
const services = this.config.services.filter((service) => service.os.hasOwnProperty(os) === true);
return services.map((service) => {
const { name, version } = service;
const environments = service.environments.map((env) => env.name);
return { name, environments, version };
});
}
/**
* Validates a service configuration against service runner schema
*
Expand Down
141 changes: 141 additions & 0 deletions src/methods/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import { methods, ServiceMethodMapping } from "./index";
import { Installer } from "../lib/installer";
import { Config, ServiceDesc, Service } from "../lib/config";
import { getOS } from "../lib/util";
import { Repo, IManifest } from "../lib/repo";
import { ServiceManager } from "../lib/serviceManager";
import _ from "lodash";
import { ENGINE_METHOD_DH } from "constants";
import { mockConfig } from "fixtures/src/util";
import { ActiveServiceSpec } from "../lib/service";
import { EventEmitter } from "events";
jest.mock("../lib/installer");
jest.mock("../lib/repo");
jest.mock("../lib/config");
jest.mock("../lib/ServiceManager");
describe("methods should reflect service runner api", () => {
let installer: Installer;
let serviceManager: ServiceManager;
let meths: ServiceMethodMapping;
type ServiceType = "available" | "running" | "installed";

const generateService = (id: number, serviceType: ServiceType) => {
return { name: `${id}`, environments: ["dev", "prod"], version: "foobar" };
};

const createTestSpecService = (name: string): ActiveServiceSpec => {

return {
args: {
start: [],
stop: [],
teardown: [],
},
commands: {
setup: [],
start: "",
stop: "",
teardown: "",
},
env: "dev",
name,
notifications: new EventEmitter(),
path: "",
rpcPort: "9999",
state: "running",
version: "foobar",
};
};

const createTestService = (name: string) => {
return {
assets: [],
commands: {
setup: [],
start: "",
stop: "",
teardown: "",
},
environments: [{
args: {
start: [],
stop: [],
teardown: [],
},
name: "dev",
}, {
name: "prod",
args: {
start: [],
stop: [],
teardown: [],
},
}],
name,
rpcPort: "9999",
version: "foobar",
};

};

beforeAll(() => {
installer = new Installer(new Config({}), getOS(), new Repo(""));
serviceManager = new ServiceManager(new Repo(""), new Config({}));
meths = methods(installer, serviceManager);

});

it("should install service", async () => {
const mock = jest.fn();
installer.install = mock;
meths.installService("foobar", "dev");
expect(mock.call.length).toBe(1);
});

it("should start service", async () => {
const mock = jest.fn().mockReturnValue(createTestService("1000"));
serviceManager.startService = mock;
await meths.startService("foobar", "1000", "dev");
expect(mock.call.length).toBe(1);
});

it("should list services", async () => {
let testServices = [1, 2, 3].map((id) => generateService(id, "available"));
const testSvc = _.cloneDeep(testServices);
serviceManager.config.getAvailableServices = jest.fn((x: string): ServiceDesc[] => testSvc);

let services = await meths.listServices("available");
let exServices = testServices.map((s) => Object.assign(s, { state: "available" }));
expect(_.isEqual(exServices, services));

installer.repo = new Repo("");
installer.repo.getManifest = jest.fn(async (): Promise<IManifest> => {
return {
version: "old",
lastModified: "test",
services: ["4", "5", "6"].map((name) => ({ name, version: "foobar", path: name })),
};
});
testServices = [4, 5, 6].map((id) => generateService(id, "installed"));
serviceManager.config.getService = jest.fn((name: string, os: string): Service => {
return createTestService(name);
});
services = await meths.listServices("installed");
exServices = testServices.map((s) => Object.assign(s, { state: "installed" }));

expect(_.isEqual(exServices, services));
serviceManager.listActiveServices = jest.fn((): ActiveServiceSpec[] => {
return [4, 5, 6].map((name) => {
return createTestSpecService(`${name}`);
});
});
exServices = testServices.map((s) => Object.assign(s, { state: "running", environments: ["dev"] }));
services = await meths.listServices("running");
expect(_.isEqual(exServices, services));

services = await meths.listServices("all");
const names = services.map((service) => (service.name));
expect(_.isEqual(names, [1, 2, 3, 4, 5, 6, 4, 5, 6].map((n) => `${n}`))).toBe(true);
});

});
45 changes: 43 additions & 2 deletions src/methods/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,20 @@
*/
import { Installer } from "../lib/installer";
import { ServiceManager } from "../lib/serviceManager";
import { ServiceDesc, Service } from "../lib/config";
import { makeLogger } from "../lib/logging";
import { InstallService, ListInstalledServices, ListRunningServices, StartService } from "../generated-types";
import { InstallService, ListInstalledServices, ListRunningServices, StartService, ListServices } from "../generated-types";
import { IMethodMapping } from "@open-rpc/server-js/build/router";
import { getOS } from "../lib/util";
const logger = makeLogger("ServiceRunner", "Routes");

export interface ServiceMethodMapping extends IMethodMapping {
installService: InstallService;
listServices: ListServices;
listInstalledServices: ListInstalledServices;
listRunningServices: ListRunningServices;
startService: StartService;
}

/**
* Returns the MethodMapping for the RPC Server essentially the routes.
*
Expand All @@ -25,6 +27,29 @@ export interface ServiceMethodMapping extends IMethodMapping {
*/
export const methods = (installer: Installer, serviceManager: ServiceManager): ServiceMethodMapping => {

const getAvailableServices = async (): Promise<ServiceDesc[]> => {
logger.debug("listing available services");
const services = serviceManager.config.getAvailableServices(getOS());
return services.map((s) => Object.assign(s, { state: "available" }));
};

const getInstalledServices = async (): Promise<ServiceDesc[]> => {
const mf = await installer.repo.getManifest();
if (mf.services === undefined) { return []; }
logger.debug("got services and returning");
return mf.services.map((service) => {
const svc = serviceManager.config.getService(service.name, getOS());
return { state: "installed", name: service.name, version: service.version, environments: svc.environments.map((env) => env.name) };
});
};

const getRunningServices = async (): Promise<ServiceDesc[]> => {
return serviceManager.listActiveServices().map((service) => {
const { name, version, env } = service;
return { name, state: "running", environments: [env], version };
});
};

return {

installService: async (name: string, version: string) => {
Expand All @@ -38,6 +63,22 @@ export const methods = (installer: Installer, serviceManager: ServiceManager): S
}
return true;
},
listServices: async (filter) => {
logger.debug("listing services");
switch (filter) {
case "all":
const avail: ServiceDesc[] = await getAvailableServices();
const install = await getInstalledServices();
const running = await getRunningServices();
return avail.concat(install, running);
case "available":
return getAvailableServices();
case "installed":
return getInstalledServices();
case "running":
return getRunningServices();
}
},
listInstalledServices: async () => {
logger.debug("listing installed services");
const mf = await installer.repo.getManifest();
Expand Down

0 comments on commit bce7140

Please sign in to comment.