Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions src/commands/engage/edit/environment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Args } from '@oclif/core';
import Command from '../../../lib/command.js';
import logger, { highlight } from '../../../lib/logger.js';
import Renderer from '../../../lib/engage/results/renderer.js';
import { editEnvironment } from '../../../lib/engage/services/edit-service.js';

export default class EngageEditEnvironmentCommand extends Command {
static override summary = 'Edit an environment with the specified name.';

static override aliases = [ 'central:edit:environment', 'central:edit:env', 'engage:edit:env' ];

static override description = `You must be authenticated to edit one or more resources.
Run ${highlight('"axway auth login"')} to authenticate.`;

static override args = {
name: Args.string({
description: 'Name of the environment.',
required: true
})
};

async run(): Promise<any> {
const log = logger('engage:edit:environment');
const { args, flags, account } = await this.parse(EngageEditEnvironmentCommand);
const renderer = new Renderer(console, flags.output);
try {
await editEnvironment({ account, name: args.name, useCache: flags.useCache, render: renderer, outputFormat: flags.output });
} catch (e: any) {
log('command error', e);
renderer.anyError(e);
process.exit(1);
}
}
}
26 changes: 26 additions & 0 deletions src/commands/engage/edit/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import Command from '../../../lib/command.js';
import { Flags } from '@oclif/core';
import { OutputTypes } from '../../../lib/engage/types.js';
import { commonFlags } from '../../../lib/engage/flags.js';
import { highlight } from '../../../lib/logger.js';

export default class EngageEdit extends Command {
static override summary = 'Edit and update resources by using the default editor.';

static override aliases = [ 'central:edit' ];

static override description = `You must be authenticated to edit one or more resources.
Run ${highlight('"axway auth login"')} to authenticate.`;

static override flags = {
...commonFlags,
output: Flags.string({
char: 'o',
description: `Additional output formats. One of: ${OutputTypes.yaml} | ${OutputTypes.json}`,
}),
};

async run(): Promise<any> {
return this.help();
}
}
8 changes: 6 additions & 2 deletions src/lib/engage/services/create-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import chalk from 'chalk';
import { ApiServerClient } from '../clients-external/apiserverclient.js';
import { DefinitionsManager } from '../results/DefinitionsManager.js';
import { AgentResourceCreateResult, AgentResourceKind, AgentTypes, ApiServerClientSingleResult, BundleType, CreateCommandParams, CreateCommandResult, CreateEnvironmentCommandParams, DataPlaneNames, EngageCommandParams, GenericResource } from '../types.js';
import { loadAndVerifySpecs, verifyFile } from '../utils/utils.js';
import { getLatestServedAPIVersion, loadAndVerifySpecs, verifyFile } from '../utils/utils.js';
import { askInput, askList } from '../utils/basic-prompts.js';
import { askAgentName, askEnvironmentName } from '../utils/agents/inputs.js';
import { createByResourceType, createNewAgentResource } from '../utils/agents/creators.js';
Expand Down Expand Up @@ -38,8 +38,12 @@ export async function createEnvironment(params: CreateEnvironmentCommandParams):
const defsManager = await new DefinitionsManager(client).init();
const sortedKindsMap = defsManager.getSortedKindsMap();
const resourceDef = Array.from(sortedKindsMap.values()).find(def => def.spec.kind === 'Environment');
let version = 'v1alpha1';
if (resourceDef) {
version = getLatestServedAPIVersion(resourceDef);
}
const resource: GenericResource = {
apiVersion: 'v1alpha1',
apiVersion: version,
kind: 'Environment',
group: 'management',
title: name,
Expand Down
63 changes: 63 additions & 0 deletions src/lib/engage/services/edit-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import chalk from 'chalk';
import { ApiServerClient } from '../clients-external/apiserverclient.js';
import { DefinitionsManager } from '../results/DefinitionsManager.js';
import { renderResponse } from '../results/resultsrenderer.js';
import { EditEnvironmentCommandParams, GenericResource, Kind } from '../types.js';
import TmpFile from '../utils/tmp-file.js';
import logger from '../../logger.js';
import { loadAndVerifySpecs } from '../utils/utils.js';

const log = logger('engage:services:edit-service');

export async function editEnvironment(params: EditEnvironmentCommandParams): Promise<void> {
const { account, region, useCache, name, render, outputFormat } = params;

const client = new ApiServerClient({ account, region, useCache });
const defsManager = await new DefinitionsManager(client).init();
const sortedKindsMap = defsManager.getSortedKindsMap();
const resourceDef = Array.from(sortedKindsMap.values()).find(def => def.spec.kind === 'Environment');
let commandIsSuccessful = true;
let file: TmpFile | undefined;
render.startSpin(`Fetching details of "environment/${name}.`);
try {
const response = await client.getResourceByName({ resourceDef, resourceName: name });
if (response.error) {
throw new Error(`Unable to retrieve Environment "${name}": ${response.error}`);
}
file = new TmpFile(response.data);
// stop spinner or it will interfere stdio of editor
render.stopSpin();
const { isUpdated } = await file.edit();
if (isUpdated) {
// intentionally taking just first doc even if user will provide more in the same file while editing.
const { docs } = await loadAndVerifySpecs(file.path, new Set([ Kind.Environment ]));
const response = await client.updateResource({ resourceDef, resource: docs[0] as GenericResource });
render.success(chalk`{greenBright "environment/${name}" has successfully been edited.}`);
// render result if output flag has been provided
if (outputFormat) {
renderResponse(console, response);
}
} else {
log('no changes has been made to file');
render.error('Edit cancelled, no changes made.');
file.delete();
commandIsSuccessful = false;
}
} catch (e: any) {
log('command error', e);
if (file) {
console.log(`A copy of your changes has been stored to "${file.path}".`);
}
render.anyError(e);
commandIsSuccessful = false;
} finally {
log(`command finished, success = ${commandIsSuccessful}`);
if (file && commandIsSuccessful) {
file.delete();
}
render.stopSpin();
if (!commandIsSuccessful) {
process.exit(1);
}
}
}
7 changes: 7 additions & 0 deletions src/lib/engage/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import Renderer from './results/renderer.js';
import { KeyValueMapToNameValueArray } from './utils/utils.js';

export const ABORT_TIMEOUT
Expand Down Expand Up @@ -477,6 +478,12 @@ export interface ProductizeCommandResult {
results: Map<string, ApiServerClientBulkResult>;
}

export interface EditEnvironmentCommandParams extends EngageCommandParams {
name: string;
render: Renderer;
outputFormat?: string;
}

export enum BundleType {
ALL_AGENTS = 'All Agents',
DISCOVERY = 'Discovery',
Expand Down
79 changes: 79 additions & 0 deletions src/lib/engage/utils/tmp-file.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { readFileSync, writeFileSync } from 'fs';
import tmp from 'tmp';
import { parseAsYaml } from '../results/resultsrenderer.js';
import { isWindows } from '../utils/utils.js';
import { editor } from '../utils/bash-commands.js';
import logger from '../../logger.js';

const log = logger('engage:utils:tmp-file');

export default class TmpFile {
private file: tmp.FileResultNoFd;
public path: string;

/**
* Init temporary file if "data" is provided - write data to file (as YAML at the moment)
* @param {object} data optional data to write while creating file
*/
constructor(data?: object) {
log('creating a new file');
// discardDescriptor = true is required for windows (fixes "file is open by another process" error).
this.file = tmp.fileSync({ discardDescriptor: true, prefix: 'axway-central-edit-', postfix: '.yaml' });
this.path = this.file.name;
log(`file created at: ${this.path}`);
// if data is provided write it to the file as YAML
if (data) {
this.write(parseAsYaml(data));
}
}

/**
* Delete tmp file
*/
delete(): void {
log(`removing: ${this.path}`);
this.file.removeCallback();
}

/**
* Write to file
* @param {string} data data to write
*/
write(data: string): void {
log(`writing to: ${this.path}`);
writeFileSync(this.path, data);
}

/**
* Read file as string (as is)
* @return {string} data from file
*/
read(): string {
log(`reading from: ${this.path}`);
return readFileSync(this.path, 'utf8');
}

/**
* Open file in editor and return promise with flags indicating if edit was successful or not
* (process killed, vim q! happened etc. ), and if file content has been changed or not.
* Using vim or "EDITOR" env on linux and only notepad on windows.
* @returns {object} represent result of editing:
* isComplete: editor process completed successfully
* isUpdated: content of the file changed
*/
async edit(): Promise<{ isComplete: boolean; isUpdated: boolean }> {
log(`editing: ${this.path}`);
const editorToUse = isWindows ? 'notepad' : process.env.EDITOR || 'vi';
const contentBeforeEdit = Buffer.from(this.read());
const editorExitCode = await editor(editorToUse, this.path);
const isUpdated = !contentBeforeEdit.equals(Buffer.from(this.read()));

if (editorExitCode === 0) {
log('file edit has been successful');
return { isComplete: true, isUpdated };
} else {
log(`file edit error, code: ${editorExitCode}`);
return { isComplete: false, isUpdated };
}
}
}
Loading