UI/console update (#20590)
This commit is contained in:
parent
e58f3816a4
commit
722c578ff4
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:improvement
|
||||||
|
ui: Update Web CLI with examples and a new `kv-get` command for reading kv v2 data and metadata
|
||||||
|
```
|
|
@ -1,8 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) HashiCorp, Inc.
|
|
||||||
* SPDX-License-Identifier: MPL-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
import Component from '@ember/component';
|
|
||||||
|
|
||||||
export default Component.extend({});
|
|
|
@ -8,15 +8,17 @@ import { alias, or } from '@ember/object/computed';
|
||||||
import Component from '@ember/component';
|
import Component from '@ember/component';
|
||||||
import { getOwner } from '@ember/application';
|
import { getOwner } from '@ember/application';
|
||||||
import { schedule } from '@ember/runloop';
|
import { schedule } from '@ember/runloop';
|
||||||
|
import { camelize } from '@ember/string';
|
||||||
import { task } from 'ember-concurrency';
|
import { task } from 'ember-concurrency';
|
||||||
import ControlGroupError from 'vault/lib/control-group-error';
|
import ControlGroupError from 'vault/lib/control-group-error';
|
||||||
import {
|
import {
|
||||||
parseCommand,
|
parseCommand,
|
||||||
extractDataAndFlags,
|
|
||||||
logFromResponse,
|
logFromResponse,
|
||||||
logFromError,
|
logFromError,
|
||||||
logErrorFromInput,
|
formattedErrorFromInput,
|
||||||
executeUICommand,
|
executeUICommand,
|
||||||
|
extractFlagsFromStrings,
|
||||||
|
extractDataFromStrings,
|
||||||
} from 'vault/lib/console-helpers';
|
} from 'vault/lib/console-helpers';
|
||||||
|
|
||||||
export default Component.extend({
|
export default Component.extend({
|
||||||
|
@ -64,29 +66,25 @@ export default Component.extend({
|
||||||
|
|
||||||
// parse to verify it's valid
|
// parse to verify it's valid
|
||||||
try {
|
try {
|
||||||
serviceArgs = parseCommand(command, shouldThrow);
|
serviceArgs = parseCommand(command);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
if (shouldThrow) {
|
||||||
this.logAndOutput(command, { type: 'help' });
|
this.logAndOutput(command, { type: 'help' });
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
// we have a invalid command but don't want to throw
|
|
||||||
if (serviceArgs === false) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [method, flagArray, path, dataArray] = serviceArgs;
|
const { method, flagArray, path, dataArray } = serviceArgs;
|
||||||
|
const flags = extractFlagsFromStrings(flagArray, method);
|
||||||
|
const data = extractDataFromStrings(dataArray);
|
||||||
|
|
||||||
if (dataArray || flagArray) {
|
const inputError = formattedErrorFromInput(path, method, flags, dataArray);
|
||||||
var { data, flags } = extractDataAndFlags(method, dataArray, flagArray);
|
|
||||||
}
|
|
||||||
|
|
||||||
const inputError = logErrorFromInput(path, method, flags, dataArray);
|
|
||||||
if (inputError) {
|
if (inputError) {
|
||||||
this.logAndOutput(command, inputError);
|
this.logAndOutput(command, inputError);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const resp = yield service[method].call(service, path, data, flags.wrapTTL);
|
const resp = yield service[camelize(method)].call(service, path, data, flags);
|
||||||
this.logAndOutput(command, logFromResponse(resp, path, method, flags));
|
this.logAndOutput(command, logFromResponse(resp, path, method, flags));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof ControlGroupError) {
|
if (error instanceof ControlGroupError) {
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
</:footer>
|
</:footer>
|
||||||
</Hds::SideNav>
|
</Hds::SideNav>
|
||||||
</Frame.Sidebar>
|
</Frame.Sidebar>
|
||||||
<Frame.Main id="app-main-content">
|
<Frame.Main id="app-main-content" class={{if this.console.isOpen "main--console-open"}}>
|
||||||
{{! outlet for app content }}
|
{{! outlet for app content }}
|
||||||
<div id="modal-wormhole"></div>
|
<div id="modal-wormhole"></div>
|
||||||
<LinkStatus @status={{this.currentCluster.cluster.hcpLinkStatus}} />
|
<LinkStatus @status={{this.currentCluster.cluster.hcpLinkStatus}} />
|
||||||
|
|
|
@ -4,20 +4,57 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import keys from 'vault/lib/keycodes';
|
import keys from 'vault/lib/keycodes';
|
||||||
import argTokenizer from './arg-tokenizer';
|
import AdapterError from '@ember-data/adapter/error';
|
||||||
import { parse } from 'shell-quote';
|
import { parse } from 'shell-quote';
|
||||||
|
|
||||||
const supportedCommands = ['read', 'write', 'list', 'delete'];
|
import argTokenizer from './arg-tokenizer';
|
||||||
|
import { StringMap } from 'vault/vault/app-types';
|
||||||
|
|
||||||
|
// Add new commands to `log-help` component for visibility
|
||||||
|
const supportedCommands = ['read', 'write', 'list', 'delete', 'kv-get'];
|
||||||
const uiCommands = ['api', 'clearall', 'clear', 'fullscreen', 'refresh'];
|
const uiCommands = ['api', 'clearall', 'clear', 'fullscreen', 'refresh'];
|
||||||
|
|
||||||
export function extractDataAndFlags(method, data, flags) {
|
interface DataObj {
|
||||||
return data.concat(flags).reduce(
|
[key: string]: string | string[];
|
||||||
(accumulator, val) => {
|
}
|
||||||
// will be "key=value" or "-flag=value" or "foo=bar=baz"
|
|
||||||
|
export function extractDataFromStrings(dataArray: string[]): DataObj {
|
||||||
|
if (!dataArray) return {};
|
||||||
|
return dataArray.reduce((accumulator: DataObj, val: string) => {
|
||||||
|
// will be "key=value" or "foo=bar=baz"
|
||||||
// split on the first =
|
// split on the first =
|
||||||
// default to value of empty string
|
// default to value of empty string
|
||||||
const [item, value = ''] = val.split(/=(.+)?/);
|
const [item = '', value = ''] = val.split(/=(.+)?/);
|
||||||
if (item.startsWith('-')) {
|
if (!item) return accumulator;
|
||||||
|
|
||||||
|
// if it exists in data already, then we have multiple
|
||||||
|
// foo=bar in the list and need to make it an array
|
||||||
|
const existingValue = accumulator[item];
|
||||||
|
if (existingValue) {
|
||||||
|
accumulator[item] = Array.isArray(existingValue) ? [...existingValue, value] : [existingValue, value];
|
||||||
|
return accumulator;
|
||||||
|
}
|
||||||
|
accumulator[item] = value;
|
||||||
|
return accumulator;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Flags {
|
||||||
|
field?: string;
|
||||||
|
format?: string;
|
||||||
|
force?: boolean;
|
||||||
|
wrapTTL?: boolean;
|
||||||
|
[key: string]: string | boolean | undefined;
|
||||||
|
}
|
||||||
|
export function extractFlagsFromStrings(flagArray: string[], method: string): Flags {
|
||||||
|
if (!flagArray) return {};
|
||||||
|
return flagArray.reduce((accumulator: Flags, val: string) => {
|
||||||
|
// val will be "-flag=value" or "--force"
|
||||||
|
// split on the first =
|
||||||
|
// default to value or true
|
||||||
|
const [item, value] = val.split(/=(.+)?/);
|
||||||
|
if (!item) return accumulator;
|
||||||
|
|
||||||
let flagName = item.replace(/^-/, '');
|
let flagName = item.replace(/^-/, '');
|
||||||
if (flagName === 'wrap-ttl') {
|
if (flagName === 'wrap-ttl') {
|
||||||
flagName = 'wrapTTL';
|
flagName = 'wrapTTL';
|
||||||
|
@ -26,44 +63,48 @@ export function extractDataAndFlags(method, data, flags) {
|
||||||
flagName = 'force';
|
flagName = 'force';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
accumulator.flags[flagName] = value || true;
|
accumulator[flagName] = value || true;
|
||||||
return accumulator;
|
return accumulator;
|
||||||
}
|
}, {});
|
||||||
// if it exists in data already, then we have multiple
|
|
||||||
// foo=bar in the list and need to make it an array
|
|
||||||
if (accumulator.data[item]) {
|
|
||||||
accumulator.data[item] = [].concat(accumulator.data[item], value);
|
|
||||||
return accumulator;
|
|
||||||
}
|
|
||||||
accumulator.data[item] = value;
|
|
||||||
return accumulator;
|
|
||||||
},
|
|
||||||
{ data: {}, flags: {} }
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function executeUICommand(command, logAndOutput, commandFns) {
|
interface CommandFns {
|
||||||
|
[key: string]: CallableFunction;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function executeUICommand(
|
||||||
|
command: string,
|
||||||
|
logAndOutput: CallableFunction,
|
||||||
|
commandFns: CommandFns
|
||||||
|
): boolean {
|
||||||
const cmd = command.startsWith('api') ? 'api' : command;
|
const cmd = command.startsWith('api') ? 'api' : command;
|
||||||
const isUICommand = uiCommands.includes(cmd);
|
const isUICommand = uiCommands.includes(cmd);
|
||||||
if (isUICommand) {
|
if (isUICommand) {
|
||||||
logAndOutput(command);
|
logAndOutput(command);
|
||||||
}
|
}
|
||||||
if (typeof commandFns[cmd] === 'function') {
|
const execCommand = commandFns[cmd];
|
||||||
commandFns[cmd]();
|
if (execCommand && typeof execCommand === 'function') {
|
||||||
|
execCommand();
|
||||||
}
|
}
|
||||||
return isUICommand;
|
return isUICommand;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseCommand(command, shouldThrow) {
|
interface ParsedCommand {
|
||||||
const args = argTokenizer(parse(command));
|
method: string;
|
||||||
|
path: string;
|
||||||
|
flagArray: string[];
|
||||||
|
dataArray: string[];
|
||||||
|
}
|
||||||
|
export function parseCommand(command: string): ParsedCommand {
|
||||||
|
const args: string[] = argTokenizer(parse(command));
|
||||||
if (args[0] === 'vault') {
|
if (args[0] === 'vault') {
|
||||||
args.shift();
|
args.shift();
|
||||||
}
|
}
|
||||||
|
|
||||||
const [method, ...rest] = args;
|
const [method = '', ...rest] = args;
|
||||||
let path;
|
let path = '';
|
||||||
const flags = [];
|
const flags: string[] = [];
|
||||||
const data = [];
|
const data: string[] = [];
|
||||||
|
|
||||||
rest.forEach((arg) => {
|
rest.forEach((arg) => {
|
||||||
if (arg.startsWith('-')) {
|
if (arg.startsWith('-')) {
|
||||||
|
@ -86,24 +127,28 @@ export function parseCommand(command, shouldThrow) {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!supportedCommands.includes(method)) {
|
if (!supportedCommands.includes(method)) {
|
||||||
if (shouldThrow) {
|
|
||||||
throw new Error('invalid command');
|
throw new Error('invalid command');
|
||||||
}
|
}
|
||||||
return false;
|
return { method, flagArray: flags, path, dataArray: data };
|
||||||
}
|
|
||||||
return [method, flags, path, data];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function logFromResponse(response, path, method, flags) {
|
interface LogResponse {
|
||||||
|
auth?: StringMap;
|
||||||
|
data?: StringMap;
|
||||||
|
wrap_info?: StringMap;
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function logFromResponse(response: LogResponse, path: string, method: string, flags: Flags) {
|
||||||
const { format, field } = flags;
|
const { format, field } = flags;
|
||||||
let secret = response && (response.auth || response.data || response.wrap_info);
|
const respData: StringMap | undefined = response && (response.auth || response.data || response.wrap_info);
|
||||||
if (!secret) {
|
const secret: StringMap | LogResponse = respData || response;
|
||||||
|
|
||||||
|
if (!respData) {
|
||||||
if (method === 'write') {
|
if (method === 'write') {
|
||||||
return { type: 'success', content: `Success! Data written to: ${path}` };
|
return { type: 'success', content: `Success! Data written to: ${path}` };
|
||||||
} else if (method === 'delete') {
|
} else if (method === 'delete') {
|
||||||
return { type: 'success', content: `Success! Data deleted (if it existed) at: ${path}` };
|
return { type: 'success', content: `Success! Data deleted (if it existed) at: ${path}` };
|
||||||
} else {
|
|
||||||
secret = response;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,11 +188,17 @@ export function logFromResponse(response, path, method, flags) {
|
||||||
return { type: 'object', content: secret };
|
return { type: 'object', content: secret };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function logFromError(error, vaultPath, method) {
|
interface CustomError extends AdapterError {
|
||||||
|
httpStatus: number;
|
||||||
|
path: string;
|
||||||
|
errors: string[];
|
||||||
|
}
|
||||||
|
export function logFromError(error: CustomError, vaultPath: string, method: string) {
|
||||||
let content;
|
let content;
|
||||||
const { httpStatus, path } = error;
|
const { httpStatus, path } = error;
|
||||||
const verbClause = {
|
const verbClause = {
|
||||||
read: 'reading from',
|
read: 'reading from',
|
||||||
|
'kv-get': 'reading secret',
|
||||||
write: 'writing to',
|
write: 'writing to',
|
||||||
list: 'listing',
|
list: 'listing',
|
||||||
delete: 'deleting at',
|
delete: 'deleting at',
|
||||||
|
@ -162,7 +213,11 @@ export function logFromError(error, vaultPath, method) {
|
||||||
return { type: 'error', content };
|
return { type: 'error', content };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function shiftCommandIndex(keyCode, history, index) {
|
interface CommandLog {
|
||||||
|
type: string;
|
||||||
|
content?: string;
|
||||||
|
}
|
||||||
|
export function shiftCommandIndex(keyCode: number, history: CommandLog[], index: number) {
|
||||||
let newInputValue;
|
let newInputValue;
|
||||||
const commandHistoryLength = history.length;
|
const commandHistoryLength = history.length;
|
||||||
|
|
||||||
|
@ -186,17 +241,18 @@ export function shiftCommandIndex(keyCode, history, index) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newInputValue !== '') {
|
if (newInputValue !== '') {
|
||||||
newInputValue = history.objectAt(index).content;
|
newInputValue = history.objectAt(index)?.content;
|
||||||
}
|
}
|
||||||
|
|
||||||
return [index, newInputValue];
|
return [index, newInputValue];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function logErrorFromInput(path, method, flags, dataArray) {
|
export function formattedErrorFromInput(path: string, method: string, flags: Flags, dataArray: string[]) {
|
||||||
if (path === undefined) {
|
if (path === undefined) {
|
||||||
return { type: 'error', content: 'A path is required to make a request.' };
|
return { type: 'error', content: 'A path is required to make a request.' };
|
||||||
}
|
}
|
||||||
if (method === 'write' && !flags.force && dataArray.length === 0) {
|
if (method === 'write' && !flags.force && dataArray.length === 0) {
|
||||||
return { type: 'error', content: 'Must supply data or use -force' };
|
return { type: 'error', content: 'Must supply data or use -force' };
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
|
@ -84,11 +84,22 @@ export default Service.extend({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
read(path, data, wrapTTL) {
|
kvGet(path, data, flags = {}) {
|
||||||
|
const { wrapTTL, metadata } = flags;
|
||||||
|
// Split on first / to find backend and secret path
|
||||||
|
const pathSegment = metadata ? 'metadata' : 'data';
|
||||||
|
const [backend, secretPath] = path.split(/\/(.+)?/);
|
||||||
|
const kvPath = `${backend}/${pathSegment}/${secretPath}`;
|
||||||
|
return this.ajax('read', sanitizePath(kvPath), { wrapTTL });
|
||||||
|
},
|
||||||
|
|
||||||
|
read(path, data, flags) {
|
||||||
|
const wrapTTL = flags?.wrapTTL;
|
||||||
return this.ajax('read', sanitizePath(path), { wrapTTL });
|
return this.ajax('read', sanitizePath(path), { wrapTTL });
|
||||||
},
|
},
|
||||||
|
|
||||||
write(path, data, wrapTTL) {
|
write(path, data, flags) {
|
||||||
|
const wrapTTL = flags?.wrapTTL;
|
||||||
return this.ajax('write', sanitizePath(path), { data, wrapTTL });
|
return this.ajax('write', sanitizePath(path), { data, wrapTTL });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -96,7 +107,8 @@ export default Service.extend({
|
||||||
return this.ajax('delete', sanitizePath(path));
|
return this.ajax('delete', sanitizePath(path));
|
||||||
},
|
},
|
||||||
|
|
||||||
list(path, data, wrapTTL) {
|
list(path, data, flags) {
|
||||||
|
const wrapTTL = flags?.wrapTTL;
|
||||||
const listPath = ensureTrailingSlash(sanitizePath(path));
|
const listPath = ensureTrailingSlash(sanitizePath(path));
|
||||||
return this.ajax('list', listPath, {
|
return this.ajax('list', listPath, {
|
||||||
data: {
|
data: {
|
||||||
|
|
|
@ -125,6 +125,10 @@ $console-close-height: 35px;
|
||||||
min-height: 400px;
|
min-height: 400px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.main--console-open {
|
||||||
|
padding-bottom: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
.panel-open .console-ui-panel.fullscreen {
|
.panel-open .console-ui-panel.fullscreen {
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
Commands:
|
Commands:
|
||||||
read Read data and retrieves secrets
|
read Read data and retrieves secrets
|
||||||
|
kv-get Read data for kv v2 secret engines. Use -metadata flag to read metadata
|
||||||
write Write data, configuration, and secrets
|
write Write data, configuration, and secrets
|
||||||
delete Delete secrets and configuration
|
delete Delete secrets and configuration
|
||||||
list List data or secrets
|
list List data or secrets
|
||||||
|
|
|
@ -5,10 +5,18 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="console-ui-panel-content">
|
<div class="console-ui-panel-content">
|
||||||
<div class="content has-bottom-margin-l">
|
<div class="content has-bottom-margin-l">
|
||||||
<p class="has-text-grey is-font-mono">
|
<p class="has-text-grey is-font-mono has-bottom-margin-s">
|
||||||
The Vault Browser CLI provides an easy way to execute the most common CLI commands, such as write, read, delete, and
|
The Vault Browser CLI provides an easy way to execute common Vault CLI commands, such as write, read, delete, and list.
|
||||||
list.
|
It does not include kv v2 write or put commands. For guidance, type `help`.
|
||||||
</p>
|
</p>
|
||||||
|
<p class="has-text-grey is-font-mono has-bottom-margin-s">Examples:</p>
|
||||||
|
<p class="has-text-grey is-font-mono">→ Write secrets to kv v1: write <mount>/my-secret foo=bar</p>
|
||||||
|
<p class="has-text-grey is-font-mono">→ List kv v1 secret keys: list <mount>/</p>
|
||||||
|
<p class="has-text-grey is-font-mono">→ Read a kv v1 secret: read <mount>/my-secret</p>
|
||||||
|
<p class="has-text-grey is-font-mono">→ Mount a kv v2 secret engine: write sys/mounts/<mount> type=kv
|
||||||
|
options=version=2</p>
|
||||||
|
<p class="has-text-grey is-font-mono">→ Read a kv v2 secret: kv-get <mount>/secret-path</p>
|
||||||
|
<p class="has-text-grey is-font-mono">→ Read a kv v2 secret's metadata: kv-get <mount>/secret-path -metadata</p>
|
||||||
</div>
|
</div>
|
||||||
<Console::OutputLog @outputLog={{this.cliLog}} />
|
<Console::OutputLog @outputLog={{this.cliLog}} />
|
||||||
<Console::CommandInput
|
<Console::CommandInput
|
||||||
|
|
|
@ -10,8 +10,11 @@
|
||||||
<ToolbarFilters>
|
<ToolbarFilters>
|
||||||
<div class="field is-marginless">
|
<div class="field is-marginless">
|
||||||
<p class="control has-icons-left">
|
<p class="control has-icons-left">
|
||||||
|
<label for="swagger-result-filter" class="sr-only">Filter operations by path</label>
|
||||||
<input
|
<input
|
||||||
oninput={{queue (action "updateFilter") (action "proxyEvent")}}
|
id="swagger-result-filter"
|
||||||
|
{{on "input" (action "proxyEvent")}}
|
||||||
|
{{on "change" (action "updateFilter")}}
|
||||||
value={{@initialFilter}}
|
value={{@initialFilter}}
|
||||||
disabled={{this.swaggerLoading}}
|
disabled={{this.swaggerLoading}}
|
||||||
class="filter input"
|
class="filter input"
|
||||||
|
|
|
@ -92,6 +92,7 @@
|
||||||
"@types/ember__utils": "^4.0.2",
|
"@types/ember__utils": "^4.0.2",
|
||||||
"@types/qunit": "^2.19.3",
|
"@types/qunit": "^2.19.3",
|
||||||
"@types/rsvp": "^4.0.4",
|
"@types/rsvp": "^4.0.4",
|
||||||
|
"@types/shell-quote": "^1.7.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.19.0",
|
"@typescript-eslint/eslint-plugin": "^5.19.0",
|
||||||
"@typescript-eslint/parser": "^5.19.0",
|
"@typescript-eslint/parser": "^5.19.0",
|
||||||
"asn1js": "^2.2.0",
|
"asn1js": "^2.2.0",
|
||||||
|
|
|
@ -6,10 +6,11 @@
|
||||||
import { module, test } from 'qunit';
|
import { module, test } from 'qunit';
|
||||||
import {
|
import {
|
||||||
parseCommand,
|
parseCommand,
|
||||||
extractDataAndFlags,
|
|
||||||
logFromResponse,
|
logFromResponse,
|
||||||
logFromError,
|
logFromError,
|
||||||
logErrorFromInput,
|
formattedErrorFromInput,
|
||||||
|
extractFlagsFromStrings,
|
||||||
|
extractDataFromStrings,
|
||||||
} from 'vault/lib/console-helpers';
|
} from 'vault/lib/console-helpers';
|
||||||
|
|
||||||
module('Unit | Lib | console helpers', function () {
|
module('Unit | Lib | console helpers', function () {
|
||||||
|
@ -20,16 +21,16 @@ module('Unit | Lib | console helpers', function () {
|
||||||
access_key=AKIAJWVN5Z4FOFT7NLNA \
|
access_key=AKIAJWVN5Z4FOFT7NLNA \
|
||||||
secret_key=R4nm063hgMVo4BTT5xOs5nHLeLXA6lar7ZJ3Nt0i \
|
secret_key=R4nm063hgMVo4BTT5xOs5nHLeLXA6lar7ZJ3Nt0i \
|
||||||
region=us-east-1`,
|
region=us-east-1`,
|
||||||
expected: [
|
expected: {
|
||||||
'write',
|
method: 'write',
|
||||||
[],
|
flagArray: [],
|
||||||
'aws/config/root',
|
path: 'aws/config/root',
|
||||||
[
|
dataArray: [
|
||||||
'access_key=AKIAJWVN5Z4FOFT7NLNA',
|
'access_key=AKIAJWVN5Z4FOFT7NLNA',
|
||||||
'secret_key=R4nm063hgMVo4BTT5xOs5nHLeLXA6lar7ZJ3Nt0i',
|
'secret_key=R4nm063hgMVo4BTT5xOs5nHLeLXA6lar7ZJ3Nt0i',
|
||||||
'region=us-east-1',
|
'region=us-east-1',
|
||||||
],
|
],
|
||||||
],
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'write with space in a value',
|
name: 'write with space in a value',
|
||||||
|
@ -43,11 +44,11 @@ module('Unit | Lib | console helpers', function () {
|
||||||
insecure_tls=true \
|
insecure_tls=true \
|
||||||
starttls=false
|
starttls=false
|
||||||
`,
|
`,
|
||||||
expected: [
|
expected: {
|
||||||
'write',
|
method: 'write',
|
||||||
[],
|
flagArray: [],
|
||||||
'auth/ldap/config',
|
path: 'auth/ldap/config',
|
||||||
[
|
dataArray: [
|
||||||
'url=ldap://ldap.example.com:3268',
|
'url=ldap://ldap.example.com:3268',
|
||||||
'binddn=CN=ServiceViewDev,OU=Service Accounts,DC=example,DC=com',
|
'binddn=CN=ServiceViewDev,OU=Service Accounts,DC=example,DC=com',
|
||||||
'bindpass=xxxxxxxxxxxxxxxxxxxxxxxxxx',
|
'bindpass=xxxxxxxxxxxxxxxxxxxxxxxxxx',
|
||||||
|
@ -56,7 +57,7 @@ module('Unit | Lib | console helpers', function () {
|
||||||
'insecure_tls=true',
|
'insecure_tls=true',
|
||||||
'starttls=false',
|
'starttls=false',
|
||||||
],
|
],
|
||||||
],
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'write with double quotes',
|
name: 'write with double quotes',
|
||||||
|
@ -64,7 +65,7 @@ module('Unit | Lib | console helpers', function () {
|
||||||
auth/token/create \
|
auth/token/create \
|
||||||
policies="foo"
|
policies="foo"
|
||||||
`,
|
`,
|
||||||
expected: ['write', [], 'auth/token/create', ['policies=foo']],
|
expected: { method: 'write', flagArray: [], path: 'auth/token/create', dataArray: ['policies=foo'] },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'write with single quotes',
|
name: 'write with single quotes',
|
||||||
|
@ -72,7 +73,7 @@ module('Unit | Lib | console helpers', function () {
|
||||||
auth/token/create \
|
auth/token/create \
|
||||||
policies='foo'
|
policies='foo'
|
||||||
`,
|
`,
|
||||||
expected: ['write', [], 'auth/token/create', ['policies=foo']],
|
expected: { method: 'write', flagArray: [], path: 'auth/token/create', dataArray: ['policies=foo'] },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'write with unmatched quotes',
|
name: 'write with unmatched quotes',
|
||||||
|
@ -80,30 +81,35 @@ module('Unit | Lib | console helpers', function () {
|
||||||
auth/token/create \
|
auth/token/create \
|
||||||
policies="'foo"
|
policies="'foo"
|
||||||
`,
|
`,
|
||||||
expected: ['write', [], 'auth/token/create', ["policies='foo"]],
|
expected: { method: 'write', flagArray: [], path: 'auth/token/create', dataArray: ["policies='foo"] },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'write with shell characters',
|
name: 'write with shell characters',
|
||||||
/* eslint-disable no-useless-escape */
|
/* eslint-disable no-useless-escape */
|
||||||
command: `vault write database/roles/api-prod db_name=apiprod creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" default_ttl=1h max_ttl=24h
|
command: `vault write database/roles/api-prod db_name=apiprod creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" default_ttl=1h max_ttl=24h
|
||||||
`,
|
`,
|
||||||
expected: [
|
expected: {
|
||||||
'write',
|
method: 'write',
|
||||||
[],
|
flagArray: [],
|
||||||
'database/roles/api-prod',
|
path: 'database/roles/api-prod',
|
||||||
[
|
dataArray: [
|
||||||
'db_name=apiprod',
|
'db_name=apiprod',
|
||||||
`creation_statements=CREATE ROLE {{name}} WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT ON ALL TABLES IN SCHEMA public TO {{name}};`,
|
`creation_statements=CREATE ROLE {{name}} WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT ON ALL TABLES IN SCHEMA public TO {{name}};`,
|
||||||
'default_ttl=1h',
|
'default_ttl=1h',
|
||||||
'max_ttl=24h',
|
'max_ttl=24h',
|
||||||
],
|
],
|
||||||
],
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name: 'read with field',
|
name: 'read with field',
|
||||||
command: `vault read -field=access_key aws/creds/my-role`,
|
command: `vault read -field=access_key aws/creds/my-role`,
|
||||||
expected: ['read', ['-field=access_key'], 'aws/creds/my-role', []],
|
expected: {
|
||||||
|
method: 'read',
|
||||||
|
flagArray: ['-field=access_key'],
|
||||||
|
path: 'aws/creds/my-role',
|
||||||
|
dataArray: [],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -115,16 +121,14 @@ module('Unit | Lib | console helpers', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('#parseCommand: invalid commands', function (assert) {
|
test('#parseCommand: invalid commands', function (assert) {
|
||||||
|
assert.expect(1);
|
||||||
const command = 'vault kv get foo';
|
const command = 'vault kv get foo';
|
||||||
const result = parseCommand(command);
|
|
||||||
assert.false(result, 'parseCommand returns false by default');
|
|
||||||
|
|
||||||
assert.throws(
|
assert.throws(
|
||||||
() => {
|
() => {
|
||||||
parseCommand(command, true);
|
parseCommand(command);
|
||||||
},
|
},
|
||||||
/invalid command/,
|
/invalid command/,
|
||||||
'throws on invalid command when `shouldThrow` is true'
|
'throws on invalid command'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -132,14 +136,12 @@ module('Unit | Lib | console helpers', function () {
|
||||||
{
|
{
|
||||||
method: 'read',
|
method: 'read',
|
||||||
name: 'data fields',
|
name: 'data fields',
|
||||||
input: [
|
dataInput: [
|
||||||
[
|
|
||||||
'access_key=AKIAJWVN5Z4FOFT7NLNA',
|
'access_key=AKIAJWVN5Z4FOFT7NLNA',
|
||||||
'secret_key=R4nm063hgMVo4BTT5xOs5nHLeLXA6lar7ZJ3Nt0i',
|
'secret_key=R4nm063hgMVo4BTT5xOs5nHLeLXA6lar7ZJ3Nt0i',
|
||||||
'region=us-east-1',
|
'region=us-east-1',
|
||||||
],
|
],
|
||||||
[],
|
flagInput: [],
|
||||||
],
|
|
||||||
expected: {
|
expected: {
|
||||||
data: {
|
data: {
|
||||||
access_key: 'AKIAJWVN5Z4FOFT7NLNA',
|
access_key: 'AKIAJWVN5Z4FOFT7NLNA',
|
||||||
|
@ -152,7 +154,8 @@ module('Unit | Lib | console helpers', function () {
|
||||||
{
|
{
|
||||||
method: 'read',
|
method: 'read',
|
||||||
name: 'repeated data and a flag',
|
name: 'repeated data and a flag',
|
||||||
input: [['allowed_domains=example.com', 'allowed_domains=foo.example.com'], ['-wrap-ttl=2h']],
|
dataInput: ['allowed_domains=example.com', 'allowed_domains=foo.example.com'],
|
||||||
|
flagInput: ['-wrap-ttl=2h'],
|
||||||
expected: {
|
expected: {
|
||||||
data: {
|
data: {
|
||||||
allowed_domains: ['example.com', 'foo.example.com'],
|
allowed_domains: ['example.com', 'foo.example.com'],
|
||||||
|
@ -162,10 +165,27 @@ module('Unit | Lib | console helpers', function () {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
method: 'read',
|
||||||
|
name: 'triple data',
|
||||||
|
dataInput: [
|
||||||
|
'allowed_domains=example.com',
|
||||||
|
'allowed_domains=foo.example.com',
|
||||||
|
'allowed_domains=dev.example.com',
|
||||||
|
],
|
||||||
|
flagInput: [],
|
||||||
|
expected: {
|
||||||
|
data: {
|
||||||
|
allowed_domains: ['example.com', 'foo.example.com', 'dev.example.com'],
|
||||||
|
},
|
||||||
|
flags: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
method: 'read',
|
method: 'read',
|
||||||
name: 'data with more than one equals sign',
|
name: 'data with more than one equals sign',
|
||||||
input: [['foo=bar=baz', 'foo=baz=bop', 'some=value=val'], []],
|
dataInput: ['foo=bar=baz', 'foo=baz=bop', 'some=value=val'],
|
||||||
|
flagInput: [],
|
||||||
expected: {
|
expected: {
|
||||||
data: {
|
data: {
|
||||||
foo: ['bar=baz', 'baz=bop'],
|
foo: ['bar=baz', 'baz=bop'],
|
||||||
|
@ -177,7 +197,8 @@ module('Unit | Lib | console helpers', function () {
|
||||||
{
|
{
|
||||||
method: 'read',
|
method: 'read',
|
||||||
name: 'data with empty values',
|
name: 'data with empty values',
|
||||||
input: [[`foo=`, 'some=thing'], []],
|
dataInput: [`foo=`, 'some=thing'],
|
||||||
|
flagInput: [],
|
||||||
expected: {
|
expected: {
|
||||||
data: {
|
data: {
|
||||||
foo: '',
|
foo: '',
|
||||||
|
@ -189,7 +210,8 @@ module('Unit | Lib | console helpers', function () {
|
||||||
{
|
{
|
||||||
method: 'write',
|
method: 'write',
|
||||||
name: 'write with force flag',
|
name: 'write with force flag',
|
||||||
input: [[], ['-force']],
|
dataInput: [],
|
||||||
|
flagInput: ['-force'],
|
||||||
expected: {
|
expected: {
|
||||||
data: {},
|
data: {},
|
||||||
flags: {
|
flags: {
|
||||||
|
@ -200,7 +222,8 @@ module('Unit | Lib | console helpers', function () {
|
||||||
{
|
{
|
||||||
method: 'write',
|
method: 'write',
|
||||||
name: 'write with force short flag',
|
name: 'write with force short flag',
|
||||||
input: [[], ['-f']],
|
dataInput: [],
|
||||||
|
flagInput: ['-f'],
|
||||||
expected: {
|
expected: {
|
||||||
data: {},
|
data: {},
|
||||||
flags: {
|
flags: {
|
||||||
|
@ -211,7 +234,8 @@ module('Unit | Lib | console helpers', function () {
|
||||||
{
|
{
|
||||||
method: 'write',
|
method: 'write',
|
||||||
name: 'write with GNU style force flag',
|
name: 'write with GNU style force flag',
|
||||||
input: [[], ['--force']],
|
dataInput: [],
|
||||||
|
flagInput: ['--force'],
|
||||||
expected: {
|
expected: {
|
||||||
data: {},
|
data: {},
|
||||||
flags: {
|
flags: {
|
||||||
|
@ -222,9 +246,12 @@ module('Unit | Lib | console helpers', function () {
|
||||||
];
|
];
|
||||||
|
|
||||||
testExtractCases.forEach(function (testCase) {
|
testExtractCases.forEach(function (testCase) {
|
||||||
test(`#extractDataAndFlags: ${testCase.name}`, function (assert) {
|
test(`#extractDataFromStrings: ${testCase.name}`, function (assert) {
|
||||||
const { data, flags } = extractDataAndFlags(testCase.method, ...testCase.input);
|
const data = extractDataFromStrings(testCase.dataInput);
|
||||||
assert.deepEqual(data, testCase.expected.data, 'has expected data');
|
assert.deepEqual(data, testCase.expected.data, 'has expected data');
|
||||||
|
});
|
||||||
|
test(`#extractFlagsFromStrings: ${testCase.name}`, function (assert) {
|
||||||
|
const flags = extractFlagsFromStrings(testCase.flagInput, testCase.method);
|
||||||
assert.deepEqual(flags, testCase.expected.flags, 'has expected flags');
|
assert.deepEqual(flags, testCase.expected.flags, 'has expected flags');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -469,8 +496,8 @@ module('Unit | Lib | console helpers', function () {
|
||||||
];
|
];
|
||||||
|
|
||||||
testCommandCases.forEach(function (testCase) {
|
testCommandCases.forEach(function (testCase) {
|
||||||
test(`#logErrorFromInput: ${testCase.name}`, function (assert) {
|
test(`#formattedErrorFromInput: ${testCase.name}`, function (assert) {
|
||||||
const data = logErrorFromInput(...testCase.args);
|
const data = formattedErrorFromInput(...testCase.args);
|
||||||
|
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
data,
|
data,
|
||||||
|
|
|
@ -38,7 +38,7 @@ module('Unit | Service | console', function (hooks) {
|
||||||
|
|
||||||
{
|
{
|
||||||
method: 'read',
|
method: 'read',
|
||||||
args: ['/secrets/foo/bar', {}, '30m'],
|
args: ['/secrets/foo/bar', {}, { wrapTTL: '30m' }],
|
||||||
expectedURL: 'secrets/foo/bar',
|
expectedURL: 'secrets/foo/bar',
|
||||||
expectedVerb: 'GET',
|
expectedVerb: 'GET',
|
||||||
expectedOptions: { data: undefined, wrapTTL: '30m' },
|
expectedOptions: { data: undefined, wrapTTL: '30m' },
|
||||||
|
@ -65,7 +65,7 @@ module('Unit | Service | console', function (hooks) {
|
||||||
|
|
||||||
{
|
{
|
||||||
method: 'list',
|
method: 'list',
|
||||||
args: ['secret/mounts', {}, '1h'],
|
args: ['secret/mounts', {}, { wrapTTL: '1h' }],
|
||||||
expectedURL: 'secret/mounts/',
|
expectedURL: 'secret/mounts/',
|
||||||
expectedVerb: 'GET',
|
expectedVerb: 'GET',
|
||||||
expectedOptions: { data: { list: true }, wrapTTL: '1h' },
|
expectedOptions: { data: { list: true }, wrapTTL: '1h' },
|
||||||
|
@ -102,4 +102,58 @@ module('Unit | Service | console', function (hooks) {
|
||||||
assert.deepEqual(options, testCase.expectedOptions, `${testCase.method}: uses the correct options`);
|
assert.deepEqual(options, testCase.expectedOptions, `${testCase.method}: uses the correct options`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const kvTestCases = [
|
||||||
|
{
|
||||||
|
method: 'kvGet',
|
||||||
|
args: ['kv/foo'],
|
||||||
|
expectedURL: 'kv/data/foo',
|
||||||
|
expectedVerb: 'GET',
|
||||||
|
expectedOptions: { data: undefined, wrapTTL: undefined },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: 'kvGet',
|
||||||
|
args: ['kv/foo', {}, { metadata: true }],
|
||||||
|
expectedURL: 'kv/metadata/foo',
|
||||||
|
expectedVerb: 'GET',
|
||||||
|
expectedOptions: { data: undefined, wrapTTL: undefined },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: 'kvGet',
|
||||||
|
args: ['kv/foo', {}, { wrapTTL: '10m' }],
|
||||||
|
expectedURL: 'kv/data/foo',
|
||||||
|
expectedVerb: 'GET',
|
||||||
|
expectedOptions: { data: undefined, wrapTTL: '10m' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: 'kvGet',
|
||||||
|
args: ['kv/foo', {}, { metadata: true, wrapTTL: '10m' }],
|
||||||
|
expectedURL: 'kv/metadata/foo',
|
||||||
|
expectedVerb: 'GET',
|
||||||
|
expectedOptions: { data: undefined, wrapTTL: '10m' },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
test('it reads kv secret and metadata', function (assert) {
|
||||||
|
assert.expect(12);
|
||||||
|
const ajax = sinon.stub();
|
||||||
|
const uiConsole = this.owner.factoryFor('service:console').create({
|
||||||
|
adapter() {
|
||||||
|
return {
|
||||||
|
buildURL(url) {
|
||||||
|
return url;
|
||||||
|
},
|
||||||
|
ajax,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
kvTestCases.forEach((testCase) => {
|
||||||
|
uiConsole[testCase.method](...testCase.args);
|
||||||
|
const [url, verb, options] = ajax.lastCall.args;
|
||||||
|
assert.strictEqual(url, testCase.expectedURL, `${testCase.method}: uses correct url`);
|
||||||
|
assert.strictEqual(verb, testCase.expectedVerb, `${testCase.method}: uses the correct verb`);
|
||||||
|
assert.deepEqual(options, testCase.expectedOptions, `${testCase.method}: uses the correct options`);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -5648,6 +5648,13 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@types/shell-quote@npm:^1.7.1":
|
||||||
|
version: 1.7.1
|
||||||
|
resolution: "@types/shell-quote@npm:1.7.1"
|
||||||
|
checksum: 51e58326b8c6dcb72846b94cebe3dc4c84f3514469a8e52bd29c52c601e784b427b851d7477acbeef47bfcccf25d2a5768684d27e7fc95fdd003393c1bbb7bc3
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@types/symlink-or-copy@npm:^1.2.0":
|
"@types/symlink-or-copy@npm:^1.2.0":
|
||||||
version: 1.2.0
|
version: 1.2.0
|
||||||
resolution: "@types/symlink-or-copy@npm:1.2.0"
|
resolution: "@types/symlink-or-copy@npm:1.2.0"
|
||||||
|
@ -24224,6 +24231,7 @@ __metadata:
|
||||||
"@types/ember__utils": ^4.0.2
|
"@types/ember__utils": ^4.0.2
|
||||||
"@types/qunit": ^2.19.3
|
"@types/qunit": ^2.19.3
|
||||||
"@types/rsvp": ^4.0.4
|
"@types/rsvp": ^4.0.4
|
||||||
|
"@types/shell-quote": ^1.7.1
|
||||||
"@typescript-eslint/eslint-plugin": ^5.19.0
|
"@typescript-eslint/eslint-plugin": ^5.19.0
|
||||||
"@typescript-eslint/parser": ^5.19.0
|
"@typescript-eslint/parser": ^5.19.0
|
||||||
asn1js: ^2.2.0
|
asn1js: ^2.2.0
|
||||||
|
|
Loading…
Reference in New Issue