Created
October 21, 2022 07:01
-
-
Save NWYLZW/95aaae0f65fae3ee5e89aa86b06a99c9 to your computer and use it in GitHub Desktop.
jms
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { JMS } from './jms' | |
const JMS_HOST = 'xxxxx' | |
const JMS_PORT = xxxx | |
const JMS_KEY_ID = 'xxxxxx' | |
const JMS_SECRET = 'xxxxxxxx' | |
;(async () => { | |
try { | |
const api = JMS.createApi({ | |
HOST: JMS_HOST, | |
PORT: JMS_PORT, | |
KEY_ID: JMS_KEY_ID, | |
SECRET: JMS_SECRET | |
}) | |
const { assets, user } = await api.getAssetsAndSysUser('xxxxxxxxxxx') | |
const { data } = await api.doCommand(assets, user, 'ls -l') | |
console.log(data) | |
} catch (e) { | |
console.error(e) | |
} | |
})() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { RequestOptions } from 'http' | |
import http from 'node:http' | |
import httpSignature from 'http-signature' | |
export namespace JMS { | |
export interface Assets { | |
id: string | |
ip: string | |
hostname: string | |
} | |
export interface SysUser { | |
id: string | |
name: string | |
username: string | |
} | |
export interface Pagination<T> { | |
results?: T[] | |
} | |
export let questerDebug = { | |
v: false, | |
toogle: () => questerDebug.v = !questerDebug.v | |
} | |
const debug = (...args: any[]) => questerDebug.v && console.debug(...args) | |
export interface JMSOptions { | |
HOST: string | |
PORT: number | |
KEY_ID: string | |
SECRET: string | |
} | |
export const createApi = (options: JMSOptions) => { | |
const { | |
HOST, | |
PORT, | |
KEY_ID, | |
SECRET, | |
} = options | |
const quester = { | |
basePath: '/api/v1', | |
options: { | |
host: HOST, | |
port: PORT, | |
headers: { | |
'Accept': 'application/json', | |
'X-JMS-ORG': '00000000-0000-0000-0000-000000000002' | |
} | |
}, | |
inner(options: RequestOptions, body?: string) { | |
return new Promise<string>((resolve, reject) => { | |
const req = http.request({ | |
...quester.options, | |
...options, | |
path: quester.basePath + options.path, | |
headers: { | |
...quester.options.headers, | |
...options.headers, | |
...(body ? { 'Content-Length': Buffer.byteLength(body) } : {}) | |
} | |
}, res => { | |
// @ts-ignore | |
debug(req._header, res.headers) | |
const chunks: Buffer[] = [] | |
res.on('data', chunk => chunks.push(chunk)) | |
res.on('end', () => { | |
if (res.statusCode >= 200 && res.statusCode < 300) { | |
resolve(Buffer.concat(chunks).toString()) | |
} else { | |
reject(`[${res.statusCode}] "${Buffer.concat(chunks).toString()}"`) | |
} | |
}) | |
}) | |
req.on('error', error => { | |
reject(error) | |
}) | |
httpSignature.sign(req, { | |
keyId: KEY_ID, | |
key: SECRET, | |
headers: ['(request-target)', 'accept', 'date'], | |
algorithm: 'hmac-sha256', | |
}) | |
if (body) { | |
req.write(body) | |
} | |
req.end() | |
}) | |
}, | |
get(path: string, query?: Record<string, string>, options: Omit<RequestOptions, 'method'> = {}) { | |
return this.inner({ | |
...options, | |
path: `${path}${query ? '?' + new URLSearchParams(query).toString() : ''}`, | |
method: 'GET', | |
headers: { | |
...options.headers, | |
...quester.options.headers, | |
} | |
}) | |
}, | |
post(path: string, body?: Record<string, any>, options: Omit<RequestOptions, 'method'> = {}) { | |
return this.inner({ | |
...options, | |
path, | |
method: 'POST', | |
headers: { | |
...options.headers, | |
...quester.options.headers, | |
'Content-Type': 'application/json', | |
}, | |
}, JSON.stringify(body)) | |
} | |
} | |
const assets = { | |
basePath: '/perms/users/assets/', | |
get(keywords?: string) { | |
return quester.get(this.basePath, { | |
offset: '0', | |
limit: '15', | |
display: '1', | |
search: keywords, | |
}).then<Pagination<Assets>>(JSON.parse) | |
}, | |
systemUsers(id: string) { | |
return quester.get(`${this.basePath}${id}/system-users/`).then<SysUser[]>(JSON.parse) | |
}, | |
} | |
const ops = { | |
basePath: '/ops/', | |
execute(assetsIds: string[], sysUId: string, command: string) { | |
return quester.post(`${this.basePath}command-executions/`, { | |
hosts: assetsIds, | |
run_as: sysUId, | |
command, | |
}).then<{ log_url: string }>(JSON.parse) | |
} | |
} | |
async function getAssetsAndSysUser(keywords?: string) { | |
const { results: [ asset ] = [] } = await assets.get(keywords) | |
if (!asset) | |
throw new Error(`Not found assets: ${keywords}`) | |
const [ user ] = await assets.systemUsers(asset.id) | |
if (!user) | |
throw new Error(`Not found system user: ${asset.id}`) | |
return { assets: asset, user } | |
} | |
async function doCommand(assets: Assets, u: SysUser, command: string) { | |
const { log_url } = await ops.execute([assets.id], u.id, command) | |
debug(`http://${HOST}:${PORT}${log_url}`) | |
return quester.get(log_url.replace('/api/v1', '')).then<{ | |
data: string | |
}>(JSON.parse) | |
} | |
return { | |
assets, ops, | |
getAssetsAndSysUser, doCommand | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment