netconf-client
Version:
121 lines • 6.93 kB
JavaScript
import { firstValueFrom, NEVER, of, Subject, timer } from 'rxjs';
import { defaultIfEmpty, filter, map, switchMap, takeUntil, tap } from 'rxjs/operators';
import { Netconf, SSH_TIMEOUT } from "../lib/index.js";
import { catchMultipleEditError, setEditConfigStatus, setRpcConfigStatus, writeData } from "./output-operators.js";
import { Output } from "./output.js";
import { OperationType, parseArgs } from "./parse-args.js";
import { resolveXPath } from "./resolve-xpath.js";
/**
* Execute the requested netconf operation
*
* @param client - The netconf client
* @param cliOptions - The parsed command line arguments
* @returns An observable that emits when the operation is complete
*/
function execNetconfOperation(client, cliOptions) {
Output.debug(`Performing operation: ${cliOptions.operation.type}`);
switch (cliOptions.operation.type) {
case OperationType.HELLO:
return client.hello().pipe(writeData(cliOptions.resultFormat), switchMap(() => client.close()));
case OperationType.GET:
const getOptions = cliOptions.operation.options;
return client.getData(getOptions.xpath, getOptions.configFilter).pipe(map(data => {
let result;
if (getOptions.fullTree) {
result = data.result;
}
else {
// result = stripParents(data.result);
result = resolveXPath(data.result, getOptions.xpath);
}
const isRootResult = data.result === result;
data.result = result;
return [data, isRootResult];
}), writeData(cliOptions.resultFormat),
// writeData(cliOptions.resultFormat),
switchMap(() => client.close()));
case OperationType.RPC:
const rpcOptions = cliOptions.operation.options;
return client.rpc(rpcOptions.cmd, rpcOptions.values ?? {}).pipe(setRpcConfigStatus(), writeData(cliOptions.resultFormat), switchMap(() => client.close()));
case OperationType.MERGE:
const mergeOptions = cliOptions.operation.options;
return client.editConfigMerge(mergeOptions.xpath, mergeOptions.values ?? {}).pipe(catchMultipleEditError(), setEditConfigStatus(), writeData(cliOptions.resultFormat), switchMap(() => client.connectionState === 'uninitialized' ? of(void 0) : client.close()));
case OperationType.CREATE: {
const createOptions = cliOptions.operation.options;
const operation = createOptions.editConfigValues?.type === 'keyvalue'
? client.editConfigCreate(createOptions.xpath, createOptions.editConfigValues.values, createOptions.beforeKey)
: client.editConfigCreateListItems(createOptions.xpath, createOptions.editConfigValues.values);
return operation.pipe(catchMultipleEditError(), setEditConfigStatus(), writeData(cliOptions.resultFormat), switchMap(() => client.connectionState === 'uninitialized' ? of(void 0) : client.close()));
}
case OperationType.DELETE: {
const deleteOptions = cliOptions.operation.options;
const operation = deleteOptions.editConfigValues?.type === 'keyvalue'
? client.editConfigDelete(deleteOptions.xpath, deleteOptions.editConfigValues.values)
: client.editConfigDeleteListItems(deleteOptions.xpath, deleteOptions.editConfigValues.values);
return operation.pipe(catchMultipleEditError(), setEditConfigStatus(), writeData(cliOptions.resultFormat), switchMap(() => client.connectionState === 'uninitialized' ? of(void 0) : client.close()));
}
case OperationType.REPLACE: {
const replaceOptions = cliOptions.operation.options;
const operation = replaceOptions.editConfigValues?.type === 'keyvalue'
? client.editConfigReplace(replaceOptions.xpath, replaceOptions.editConfigValues.values)
: client.editConfigReplaceListItems(replaceOptions.xpath, replaceOptions.editConfigValues.values);
return operation.pipe(catchMultipleEditError(), setEditConfigStatus(), writeData(cliOptions.resultFormat), switchMap(() => client.connectionState === 'uninitialized' ? of(void 0) : client.close()));
}
case OperationType.SUBSCRIBE:
const subscribeOptions = cliOptions.operation.options;
const stop$ = new Subject();
const closed$ = new Subject();
process.on('SIGINT', async () => {
Output.info('\nStopping subscription');
stop$.next();
stop$.complete();
await firstValueFrom(timer(SSH_TIMEOUT).pipe(takeUntil(closed$), defaultIfEmpty(void 0)));
});
return client.subscription(subscribeOptions.type === 'stream' ? { stream: subscribeOptions.stream } : { xpath: subscribeOptions.xpath }, stop$).pipe(filter((data) => {
if (data?.result?.hasOwnProperty('ok')) {
// This is a RPC Reply with OK, we skip it
Output.info(`Started subscription. Ctrl+C to stop. PID=${process.pid}`);
return false;
}
return true;
}), switchMap(data => data ? of(data).pipe(writeData(cliOptions.resultFormat)) : of(data)), switchMap(data => data ? NEVER : client.close().pipe(tap(() => closed$.next()), tap(() => closed$.complete()))));
default:
throw new Error(`Invalid operation: ${cliOptions.operation.type}`);
}
}
async function main() {
const cliOptions = await parseArgs();
if (!cliOptions) {
return;
}
const showNamespaces = cliOptions.operation.type === OperationType.GET && cliOptions.operation.options.showNamespaces;
const allowMultipleEdit = (cliOptions.operation.type === OperationType.MERGE
|| cliOptions.operation.type === OperationType.CREATE
|| cliOptions.operation.type === OperationType.DELETE) && cliOptions.operation.options.allowMultiple;
const client = new Netconf({
host: cliOptions.host,
port: cliOptions.port,
user: cliOptions.user,
pass: cliOptions.pass,
ignoreAttrs: !showNamespaces,
readOnly: cliOptions.readOnly,
allowMultipleEdit,
namespace: cliOptions.namespaces,
debug: (msg, level) => Output.debug(msg, undefined, level),
});
await firstValueFrom(execNetconfOperation(client, cliOptions));
}
main().then(() => {
Output.debug('Exit (success)');
}).catch((error) => {
Output.error(`${error.message}`);
Output.printStackTrace(error);
Output.debug('Exit (error)');
process.exit(1);
});
// Export functions for testing
if (process.env.NODE_ENV === 'test') {
module.exports = { writeData, catchMultipleEditError, setEditConfigStatus };
}
//# sourceMappingURL=main.js.map