Skip to content

Commit

Permalink
Separate Pup Api from Plugin Api. Add Secure Rest API.
Browse files Browse the repository at this point in the history
  • Loading branch information
Hexagon committed Apr 17, 2024
1 parent f2676ad commit 27d387c
Show file tree
Hide file tree
Showing 13 changed files with 430 additions and 165 deletions.
1 change: 1 addition & 0 deletions deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"@cross/deepmerge": "jsr:@cross/deepmerge@^1.0.0",
"@cross/env": "jsr:@cross/env@^1.0.0",
"@cross/fs": "jsr:@cross/fs@^0.0.9",
"@cross/jwt": "jsr:@cross/jwt@^0.4.1",
"@cross/runtime": "jsr:@cross/runtime@^1.0.0",
"@cross/service": "jsr:@cross/service@^1.0.0",
"@cross/test": "jsr:@cross/test@^0.0.9",
Expand Down
7 changes: 7 additions & 0 deletions docs/src/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ nav_order: 13

All notable changes to this project will be documented in this section.

## [1.0.0-rc.26] - Unreleased

- fix(core): Remove stray console.log
- fix(core): Fix working dir different from current dir
- change(plugins): Separate core api from plugin api
- feat(rest): Add rest API with JWT Bearer auth

## [1.0.0-rc.25] - 2023-04-17

- fix(plugins): Workaround for jsr bug affecting the web-interface plugin
Expand Down
2 changes: 1 addition & 1 deletion docs/src/examples/basic-webinterface/pup.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
// Use full uri to plugin, e.g. jsr:@pup/pup@$VERSION/plugins/web-interface
"url": "../../plugins/web-interface/mod.ts",
"options": {
"port": 5000
"port": 8002
}
}
]
Expand Down
22 changes: 10 additions & 12 deletions lib/cli/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,16 @@ async function main() {
console.error(`Could not change working directory to path of '${configFile}, exiting. Message: `, e.message)
exit(1)
}
// Change working directory to configured directory
} else if (checkedArgs.get("cwd")) {
// Change working directory of pup to whereever the configuration file is, change configFile to only contain file name
try {
const resolvedPath = path.parse(path.resolve(checkedArgs.get("cwd")!))
chdir(resolvedPath.dir)
} catch (e) {
console.error(`Could not change working directory to path specified by --cwd ${checkedArgs.get("cwd")}, exiting. Message: `, e.message)
return exit(1)
}
}
// Read or generate configuration
let configuration: Configuration
Expand All @@ -156,17 +166,6 @@ async function main() {
checkedArgs.get("name"),
)
}
// Change working directory to configured directory
if (checkedArgs.get("cwd")) {
// Change working directory of pup to whereever the configuration file is, change configFile to only contain file name
try {
const resolvedPath = path.parse(path.resolve(checkedArgs.get("cwd")!))
chdir(resolvedPath.dir)
} catch (e) {
console.error(`Could not change working directory to path specified by --cwd ${checkedArgs.get("cwd")}, exiting. Message: `, e.message)
return exit(1)
}
}
// Prepare for IPC
let ipcFile
if (useConfigFile) ipcFile = `${await toTempPath(configFile as string)}/.main.ipc`
Expand Down Expand Up @@ -411,7 +410,6 @@ async function main() {
/**
* Base argument: run
*/
console.log(baseArgument)
if (baseArgument === "run") {
/**
* Error handling: Pup already running
Expand Down
141 changes: 141 additions & 0 deletions lib/core/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/**
* Classes and interfaces related to the programmatic api of Pup
*
* This is a DRAFT for api version 1
*
* @file lib/core/api.ts
* @license MIT
*/

import type { EventEmitter } from "../common/eventemitter.ts"
import type { LogEventData } from "./logger.ts"
import type { Pup } from "./pup.ts"
import type { ProcessLoggerConfiguration } from "./configuration.ts"
import type { ProcessState } from "./process.ts"

export interface ApiPaths {
temporaryStorage?: string
persistentStorage?: string
configFilePath?: string
}

export interface ApiProcessData {
status: ApiProcessInformation
config: ApiProcessConfiguration
}

/**
* These interfaces are basically copies of the ones in pup core,
* but specific to plugins, to make any incompabilities between the
* plugin api and core apparent.
*/
interface ApiProcessInformation {
id: string
status: ProcessState
code?: number
signal?: string
pid?: number
started?: Date
exited?: Date
blocked?: boolean
restarts?: number
updated: Date
pendingRestartReason?: string
type: "cluster" | "process" | "worker"
}

interface ApiClusterConfiguration {
instances?: number
commonPort?: number
startPort?: number
strategy?: string
}

interface ApiProcessConfiguration {
id: string
cmd?: string
worker?: string[]
env?: Record<string, string>
cwd?: string
cluster?: ApiClusterConfiguration
pidFile?: string
watch?: string[]
autostart?: boolean
cron?: string
timeout?: number
overrun?: boolean
logger?: ProcessLoggerConfiguration
restart?: string
restartDelayMs?: number
restartLimit?: number
}

export interface ApiApplicationState {
pid: number
version: string
status: string
updated: string
started: string
memory: Deno.MemoryUsage
systemMemory: Deno.SystemMemoryInfo
loadAvg: number[]
osUptime: number
osRelease: string
denoVersion: { deno: string; v8: string; typescript: string }
type: string
processes: ApiProcessInformation[]
}

/**
* Exposes selected features of pup to Plugins and APIs
*/
export class PupApi {
public events: EventEmitter
public paths: ApiPaths
private _pup: Pup
constructor(pup: Pup) {
this.events = pup.events
this._pup = pup
this.paths = {
temporaryStorage: pup.temporaryStoragePath,
persistentStorage: pup.persistentStoragePath,
configFilePath: pup.configFilePath,
}
}
public allProcessStates(): ApiProcessData[] {
const statuses: ApiProcessData[] = this._pup.allProcesses().map((p) => {
return {
status: p.getStatus(),
config: p.getConfig(),
}
})
return statuses
}
public applicationState(): ApiApplicationState {
return this._pup.status.applicationState(this._pup.allProcesses())
}
public terminate(forceQuitMs: number) {
this._pup.terminate(forceQuitMs)
}
public start(id: string, reason: string) {
this._pup.start(id, reason)
}
public restart(id: string, reason: string) {
this._pup.restart(id, reason)
}
public stop(id: string, reason: string) {
this._pup.stop(id, reason)
}
public block(id: string, reason: string) {
this._pup.block(id, reason)
}
public unblock(id: string, reason: string) {
this._pup.unblock(id, reason)
}
public log(severity: "log" | "error" | "info" | "warn", plugin: string, message: string) {
this._pup.logger[severity](`plugin-${plugin}`, message)
}
public async getLogs(processId?: string, startTimeStamp?: number, endTimeStamp?: number, nRows?: number): Promise<LogEventData[]> {
return await this._pup.logger.getLogContents(processId, startTimeStamp, endTimeStamp, nRows)
}
}
137 changes: 4 additions & 133 deletions lib/core/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,8 @@
*/

import { Application } from "../../application.meta.ts"
import type { EventEmitter } from "../common/eventemitter.ts"
import type { PluginConfiguration, ProcessLoggerConfiguration } from "./configuration.ts"
import type { LogEventData } from "./logger.ts"
import type { ProcessState } from "./process.ts"
import { PupApi } from "./api.ts"
import type { PluginConfiguration } from "./configuration.ts"
import type { Pup } from "./pup.ts"

const SUPPORTED_API_VERSIONS = ["1"]
Expand All @@ -23,133 +21,6 @@ export interface PluginMetadata {
repository: string
}

/**
* These interfaces are basically copies of the ones in pup core,
* but specific to plugins, to make any incompabilities between the
* plugin api and core apparent.
*/
interface PluginProcessInformation {
id: string
status: ProcessState
code?: number
signal?: string
pid?: number
started?: Date
exited?: Date
blocked?: boolean
restarts?: number
updated: Date
pendingRestartReason?: string
type: "cluster" | "process" | "worker"
}

interface PluginClusterConfiguration {
instances?: number
commonPort?: number
startPort?: number
strategy?: string
}

interface PluginProcessConfiguration {
id: string
cmd?: string
worker?: string[]
env?: Record<string, string>
cwd?: string
cluster?: PluginClusterConfiguration
pidFile?: string
watch?: string[]
autostart?: boolean
cron?: string
timeout?: number
overrun?: boolean
logger?: ProcessLoggerConfiguration
restart?: string
restartDelayMs?: number
restartLimit?: number
}

export interface PluginProcessData {
status: PluginProcessInformation
config: PluginProcessConfiguration
}

export interface PluginApplicationState {
pid: number
version: string
status: string
updated: string
started: string
memory: Deno.MemoryUsage
systemMemory: Deno.SystemMemoryInfo
loadAvg: number[]
osUptime: number
osRelease: string
denoVersion: { deno: string; v8: string; typescript: string }
type: string
processes: PluginProcessInformation[]
}

export interface PluginPaths {
temporaryStorage?: string
persistentStorage?: string
configFilePath?: string
}

/**
* Exposes selected features of pup to Plugins
*/
export class PluginApi {
public events: EventEmitter
public paths: PluginPaths
private _pup: Pup
constructor(pup: Pup) {
this.events = pup.events
this._pup = pup
this.paths = {
temporaryStorage: pup.temporaryStoragePath,
persistentStorage: pup.persistentStoragePath,
configFilePath: pup.configFilePath,
}
}
public allProcessStates(): PluginProcessData[] {
const statuses: PluginProcessData[] = this._pup.allProcesses().map((p) => {
return {
status: p.getStatus(),
config: p.getConfig(),
}
})
return statuses
}
public applicationState(): PluginApplicationState {
return this._pup.status.applicationState(this._pup.allProcesses())
}
public terminate(forceQuitMs: number) {
this._pup.terminate(forceQuitMs)
}
public start(id: string, reason: string) {
this._pup.start(id, reason)
}
public restart(id: string, reason: string) {
this._pup.restart(id, reason)
}
public stop(id: string, reason: string) {
this._pup.stop(id, reason)
}
public block(id: string, reason: string) {
this._pup.block(id, reason)
}
public unblock(id: string, reason: string) {
this._pup.unblock(id, reason)
}
public log(severity: "log" | "error" | "info" | "warn", plugin: string, message: string) {
this._pup.logger[severity](`plugin-${plugin}`, message)
}
public async getLogs(processId?: string, startTimeStamp?: number, endTimeStamp?: number, nRows?: number): Promise<LogEventData[]> {
return await this._pup.logger.getLogContents(processId, startTimeStamp, endTimeStamp, nRows)
}
}

/**
* Internal representation of a plugin
*/
Expand All @@ -167,7 +38,7 @@ export class Plugin {
public async load() {
const url = this.config.url.replace("$VERSION", Application.version)
const { PupPlugin } = await import(url)
this.impl = new PupPlugin(new PluginApi(this.pup), this.config) as PluginImplementation
this.impl = new PupPlugin(new PupApi(this.pup), this.config) as PluginImplementation
}
public verify() {
if (!this.impl || this.impl.meta.name === "unset") {
Expand Down Expand Up @@ -217,7 +88,7 @@ export class PluginImplementation {
api: "unset",
repository: "unset",
}
constructor(_pup: PluginApi, _config: PluginConfiguration) {}
constructor(_pup: PupApi, _config: PluginConfiguration) {}
// Default implemetation of hook
public hook(_signal: string, _parameters: unknown): boolean {
return false
Expand Down
Loading

0 comments on commit 27d387c

Please sign in to comment.