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 { getOwner } from '@ember/application';
|
||||
import { schedule } from '@ember/runloop';
|
||||
import { camelize } from '@ember/string';
|
||||
import { task } from 'ember-concurrency';
|
||||
import ControlGroupError from 'vault/lib/control-group-error';
|
||||
import {
|
||||
parseCommand,
|
||||
extractDataAndFlags,
|
||||
logFromResponse,
|
||||
logFromError,
|
||||
logErrorFromInput,
|
||||
formattedErrorFromInput,
|
||||
executeUICommand,
|
||||
extractFlagsFromStrings,
|
||||
extractDataFromStrings,
|
||||
} from 'vault/lib/console-helpers';
|
||||
|
||||
export default Component.extend({
|
||||
|
@ -64,29 +66,25 @@ export default Component.extend({
|
|||
|
||||
// parse to verify it's valid
|
||||
try {
|
||||
serviceArgs = parseCommand(command, shouldThrow);
|
||||
serviceArgs = parseCommand(command);
|
||||
} catch (e) {
|
||||
this.logAndOutput(command, { type: 'help' });
|
||||
return;
|
||||
}
|
||||
// we have a invalid command but don't want to throw
|
||||
if (serviceArgs === false) {
|
||||
if (shouldThrow) {
|
||||
this.logAndOutput(command, { type: 'help' });
|
||||
}
|
||||
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) {
|
||||
var { data, flags } = extractDataAndFlags(method, dataArray, flagArray);
|
||||
}
|
||||
|
||||
const inputError = logErrorFromInput(path, method, flags, dataArray);
|
||||
const inputError = formattedErrorFromInput(path, method, flags, dataArray);
|
||||
if (inputError) {
|
||||
this.logAndOutput(command, inputError);
|
||||
return;
|
||||
}
|
||||
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));
|
||||
} catch (error) {
|
||||
if (error instanceof ControlGroupError) {
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
</:footer>
|
||||
</Hds::SideNav>
|
||||
</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 }}
|
||||
<div id="modal-wormhole"></div>
|
||||
<LinkStatus @status={{this.currentCluster.cluster.hcpLinkStatus}} />
|
||||
|
|
|
@ -4,66 +4,107 @@
|
|||
*/
|
||||
|
||||
import keys from 'vault/lib/keycodes';
|
||||
import argTokenizer from './arg-tokenizer';
|
||||
import AdapterError from '@ember-data/adapter/error';
|
||||
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'];
|
||||
|
||||
export function extractDataAndFlags(method, data, flags) {
|
||||
return data.concat(flags).reduce(
|
||||
(accumulator, val) => {
|
||||
// will be "key=value" or "-flag=value" or "foo=bar=baz"
|
||||
// split on the first =
|
||||
// default to value of empty string
|
||||
const [item, value = ''] = val.split(/=(.+)?/);
|
||||
if (item.startsWith('-')) {
|
||||
let flagName = item.replace(/^-/, '');
|
||||
if (flagName === 'wrap-ttl') {
|
||||
flagName = 'wrapTTL';
|
||||
} else if (method === 'write') {
|
||||
if (flagName === 'f' || flagName === '-force') {
|
||||
flagName = 'force';
|
||||
}
|
||||
}
|
||||
accumulator.flags[flagName] = value || true;
|
||||
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: {} }
|
||||
);
|
||||
interface DataObj {
|
||||
[key: string]: string | string[];
|
||||
}
|
||||
|
||||
export function executeUICommand(command, logAndOutput, commandFns) {
|
||||
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 =
|
||||
// default to value of empty string
|
||||
const [item = '', value = ''] = val.split(/=(.+)?/);
|
||||
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(/^-/, '');
|
||||
if (flagName === 'wrap-ttl') {
|
||||
flagName = 'wrapTTL';
|
||||
} else if (method === 'write') {
|
||||
if (flagName === 'f' || flagName === '-force') {
|
||||
flagName = 'force';
|
||||
}
|
||||
}
|
||||
accumulator[flagName] = value || true;
|
||||
return accumulator;
|
||||
}, {});
|
||||
}
|
||||
|
||||
interface CommandFns {
|
||||
[key: string]: CallableFunction;
|
||||
}
|
||||
|
||||
export function executeUICommand(
|
||||
command: string,
|
||||
logAndOutput: CallableFunction,
|
||||
commandFns: CommandFns
|
||||
): boolean {
|
||||
const cmd = command.startsWith('api') ? 'api' : command;
|
||||
const isUICommand = uiCommands.includes(cmd);
|
||||
if (isUICommand) {
|
||||
logAndOutput(command);
|
||||
}
|
||||
if (typeof commandFns[cmd] === 'function') {
|
||||
commandFns[cmd]();
|
||||
const execCommand = commandFns[cmd];
|
||||
if (execCommand && typeof execCommand === 'function') {
|
||||
execCommand();
|
||||
}
|
||||
return isUICommand;
|
||||
}
|
||||
|
||||
export function parseCommand(command, shouldThrow) {
|
||||
const args = argTokenizer(parse(command));
|
||||
interface ParsedCommand {
|
||||
method: string;
|
||||
path: string;
|
||||
flagArray: string[];
|
||||
dataArray: string[];
|
||||
}
|
||||
export function parseCommand(command: string): ParsedCommand {
|
||||
const args: string[] = argTokenizer(parse(command));
|
||||
if (args[0] === 'vault') {
|
||||
args.shift();
|
||||
}
|
||||
|
||||
const [method, ...rest] = args;
|
||||
let path;
|
||||
const flags = [];
|
||||
const data = [];
|
||||
const [method = '', ...rest] = args;
|
||||
let path = '';
|
||||
const flags: string[] = [];
|
||||
const data: string[] = [];
|
||||
|
||||
rest.forEach((arg) => {
|
||||
if (arg.startsWith('-')) {
|
||||
|
@ -86,24 +127,28 @@ export function parseCommand(command, shouldThrow) {
|
|||
});
|
||||
|
||||
if (!supportedCommands.includes(method)) {
|
||||
if (shouldThrow) {
|
||||
throw new Error('invalid command');
|
||||
}
|
||||
return false;
|
||||
throw new Error('invalid command');
|
||||
}
|
||||
return [method, flags, path, data];
|
||||
return { method, flagArray: flags, path, dataArray: 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;
|
||||
let secret = response && (response.auth || response.data || response.wrap_info);
|
||||
if (!secret) {
|
||||
const respData: StringMap | undefined = response && (response.auth || response.data || response.wrap_info);
|
||||
const secret: StringMap | LogResponse = respData || response;
|
||||
|
||||
if (!respData) {
|
||||
if (method === 'write') {
|
||||
return { type: 'success', content: `Success! Data written to: ${path}` };
|
||||
} else if (method === 'delete') {
|
||||
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 };
|
||||
}
|
||||
|
||||
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;
|
||||
const { httpStatus, path } = error;
|
||||
const verbClause = {
|
||||
read: 'reading from',
|
||||
'kv-get': 'reading secret',
|
||||
write: 'writing to',
|
||||
list: 'listing',
|
||||
delete: 'deleting at',
|
||||
|
@ -162,7 +213,11 @@ export function logFromError(error, vaultPath, method) {
|
|||
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;
|
||||
const commandHistoryLength = history.length;
|
||||
|
||||
|
@ -186,17 +241,18 @@ export function shiftCommandIndex(keyCode, history, index) {
|
|||
}
|
||||
|
||||
if (newInputValue !== '') {
|
||||
newInputValue = history.objectAt(index).content;
|
||||
newInputValue = history.objectAt(index)?.content;
|
||||
}
|
||||
|
||||
return [index, newInputValue];
|
||||
}
|
||||
|
||||
export function logErrorFromInput(path, method, flags, dataArray) {
|
||||
export function formattedErrorFromInput(path: string, method: string, flags: Flags, dataArray: string[]) {
|
||||
if (path === undefined) {
|
||||
return { type: 'error', content: 'A path is required to make a request.' };
|
||||
}
|
||||
if (method === 'write' && !flags.force && dataArray.length === 0) {
|
||||
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 });
|
||||
},
|
||||
|
||||
write(path, data, wrapTTL) {
|
||||
write(path, data, flags) {
|
||||
const wrapTTL = flags?.wrapTTL;
|
||||
return this.ajax('write', sanitizePath(path), { data, wrapTTL });
|
||||
},
|
||||
|
||||
|
@ -96,7 +107,8 @@ export default Service.extend({
|
|||
return this.ajax('delete', sanitizePath(path));
|
||||
},
|
||||
|
||||
list(path, data, wrapTTL) {
|
||||
list(path, data, flags) {
|
||||
const wrapTTL = flags?.wrapTTL;
|
||||
const listPath = ensureTrailingSlash(sanitizePath(path));
|
||||
return this.ajax('list', listPath, {
|
||||
data: {
|
||||
|
|
|
@ -125,6 +125,10 @@ $console-close-height: 35px;
|
|||
min-height: 400px;
|
||||
}
|
||||
|
||||
.main--console-open {
|
||||
padding-bottom: 400px;
|
||||
}
|
||||
|
||||
.panel-open .console-ui-panel.fullscreen {
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
Commands:
|
||||
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
|
||||
delete Delete secrets and configuration
|
||||
list List data or secrets
|
||||
|
|
|
@ -5,10 +5,18 @@
|
|||
</div>
|
||||
<div class="console-ui-panel-content">
|
||||
<div class="content has-bottom-margin-l">
|
||||
<p class="has-text-grey is-font-mono">
|
||||
The Vault Browser CLI provides an easy way to execute the most common CLI commands, such as write, read, delete, and
|
||||
list.
|
||||
<p class="has-text-grey is-font-mono has-bottom-margin-s">
|
||||
The Vault Browser CLI provides an easy way to execute common Vault CLI commands, such as write, read, delete, and list.
|
||||
It does not include kv v2 write or put commands. For guidance, type `help`.
|
||||
</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>
|
||||
<Console::OutputLog @outputLog={{this.cliLog}} />
|
||||
<Console::CommandInput
|
||||
|
|
|
@ -10,8 +10,11 @@
|
|||
<ToolbarFilters>
|
||||
<div class="field is-marginless">
|
||||
<p class="control has-icons-left">
|
||||
<label for="swagger-result-filter" class="sr-only">Filter operations by path</label>
|
||||
<input
|
||||
oninput={{queue (action "updateFilter") (action "proxyEvent")}}
|
||||
id="swagger-result-filter"
|
||||
{{on "input" (action "proxyEvent")}}
|
||||
{{on "change" (action "updateFilter")}}
|
||||
value={{@initialFilter}}
|
||||
disabled={{this.swaggerLoading}}
|
||||
class="filter input"
|
||||
|
|
|
@ -92,6 +92,7 @@
|
|||
"@types/ember__utils": "^4.0.2",
|
||||
"@types/qunit": "^2.19.3",
|
||||
"@types/rsvp": "^4.0.4",
|
||||
"@types/shell-quote": "^1.7.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.19.0",
|
||||
"@typescript-eslint/parser": "^5.19.0",
|
||||
"asn1js": "^2.2.0",
|
||||
|
|
|
@ -6,10 +6,11 @@
|
|||
import { module, test } from 'qunit';
|
||||
import {
|
||||
parseCommand,
|
||||
extractDataAndFlags,
|
||||
logFromResponse,
|
||||
logFromError,
|
||||
logErrorFromInput,
|
||||
formattedErrorFromInput,
|
||||
extractFlagsFromStrings,
|
||||
extractDataFromStrings,
|
||||
} from 'vault/lib/console-helpers';
|
||||
|
||||
module('Unit | Lib | console helpers', function () {
|
||||
|
@ -20,16 +21,16 @@ module('Unit | Lib | console helpers', function () {
|
|||
access_key=AKIAJWVN5Z4FOFT7NLNA \
|
||||
secret_key=R4nm063hgMVo4BTT5xOs5nHLeLXA6lar7ZJ3Nt0i \
|
||||
region=us-east-1`,
|
||||
expected: [
|
||||
'write',
|
||||
[],
|
||||
'aws/config/root',
|
||||
[
|
||||
expected: {
|
||||
method: 'write',
|
||||
flagArray: [],
|
||||
path: 'aws/config/root',
|
||||
dataArray: [
|
||||
'access_key=AKIAJWVN5Z4FOFT7NLNA',
|
||||
'secret_key=R4nm063hgMVo4BTT5xOs5nHLeLXA6lar7ZJ3Nt0i',
|
||||
'region=us-east-1',
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'write with space in a value',
|
||||
|
@ -43,11 +44,11 @@ module('Unit | Lib | console helpers', function () {
|
|||
insecure_tls=true \
|
||||
starttls=false
|
||||
`,
|
||||
expected: [
|
||||
'write',
|
||||
[],
|
||||
'auth/ldap/config',
|
||||
[
|
||||
expected: {
|
||||
method: 'write',
|
||||
flagArray: [],
|
||||
path: 'auth/ldap/config',
|
||||
dataArray: [
|
||||
'url=ldap://ldap.example.com:3268',
|
||||
'binddn=CN=ServiceViewDev,OU=Service Accounts,DC=example,DC=com',
|
||||
'bindpass=xxxxxxxxxxxxxxxxxxxxxxxxxx',
|
||||
|
@ -56,7 +57,7 @@ module('Unit | Lib | console helpers', function () {
|
|||
'insecure_tls=true',
|
||||
'starttls=false',
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'write with double quotes',
|
||||
|
@ -64,7 +65,7 @@ module('Unit | Lib | console helpers', function () {
|
|||
auth/token/create \
|
||||
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',
|
||||
|
@ -72,7 +73,7 @@ module('Unit | Lib | console helpers', function () {
|
|||
auth/token/create \
|
||||
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',
|
||||
|
@ -80,30 +81,35 @@ module('Unit | Lib | console helpers', function () {
|
|||
auth/token/create \
|
||||
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',
|
||||
/* 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
|
||||
`,
|
||||
expected: [
|
||||
'write',
|
||||
[],
|
||||
'database/roles/api-prod',
|
||||
[
|
||||
expected: {
|
||||
method: 'write',
|
||||
flagArray: [],
|
||||
path: 'database/roles/api-prod',
|
||||
dataArray: [
|
||||
'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',
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: 'read with field',
|
||||
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) {
|
||||
assert.expect(1);
|
||||
const command = 'vault kv get foo';
|
||||
const result = parseCommand(command);
|
||||
assert.false(result, 'parseCommand returns false by default');
|
||||
|
||||
assert.throws(
|
||||
() => {
|
||||
parseCommand(command, true);
|
||||
parseCommand(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',
|
||||
name: 'data fields',
|
||||
input: [
|
||||
[
|
||||
'access_key=AKIAJWVN5Z4FOFT7NLNA',
|
||||
'secret_key=R4nm063hgMVo4BTT5xOs5nHLeLXA6lar7ZJ3Nt0i',
|
||||
'region=us-east-1',
|
||||
],
|
||||
[],
|
||||
dataInput: [
|
||||
'access_key=AKIAJWVN5Z4FOFT7NLNA',
|
||||
'secret_key=R4nm063hgMVo4BTT5xOs5nHLeLXA6lar7ZJ3Nt0i',
|
||||
'region=us-east-1',
|
||||
],
|
||||
flagInput: [],
|
||||
expected: {
|
||||
data: {
|
||||
access_key: 'AKIAJWVN5Z4FOFT7NLNA',
|
||||
|
@ -152,7 +154,8 @@ module('Unit | Lib | console helpers', function () {
|
|||
{
|
||||
method: 'read',
|
||||
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: {
|
||||
data: {
|
||||
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',
|
||||
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: {
|
||||
data: {
|
||||
foo: ['bar=baz', 'baz=bop'],
|
||||
|
@ -177,7 +197,8 @@ module('Unit | Lib | console helpers', function () {
|
|||
{
|
||||
method: 'read',
|
||||
name: 'data with empty values',
|
||||
input: [[`foo=`, 'some=thing'], []],
|
||||
dataInput: [`foo=`, 'some=thing'],
|
||||
flagInput: [],
|
||||
expected: {
|
||||
data: {
|
||||
foo: '',
|
||||
|
@ -189,7 +210,8 @@ module('Unit | Lib | console helpers', function () {
|
|||
{
|
||||
method: 'write',
|
||||
name: 'write with force flag',
|
||||
input: [[], ['-force']],
|
||||
dataInput: [],
|
||||
flagInput: ['-force'],
|
||||
expected: {
|
||||
data: {},
|
||||
flags: {
|
||||
|
@ -200,7 +222,8 @@ module('Unit | Lib | console helpers', function () {
|
|||
{
|
||||
method: 'write',
|
||||
name: 'write with force short flag',
|
||||
input: [[], ['-f']],
|
||||
dataInput: [],
|
||||
flagInput: ['-f'],
|
||||
expected: {
|
||||
data: {},
|
||||
flags: {
|
||||
|
@ -211,7 +234,8 @@ module('Unit | Lib | console helpers', function () {
|
|||
{
|
||||
method: 'write',
|
||||
name: 'write with GNU style force flag',
|
||||
input: [[], ['--force']],
|
||||
dataInput: [],
|
||||
flagInput: ['--force'],
|
||||
expected: {
|
||||
data: {},
|
||||
flags: {
|
||||
|
@ -222,9 +246,12 @@ module('Unit | Lib | console helpers', function () {
|
|||
];
|
||||
|
||||
testExtractCases.forEach(function (testCase) {
|
||||
test(`#extractDataAndFlags: ${testCase.name}`, function (assert) {
|
||||
const { data, flags } = extractDataAndFlags(testCase.method, ...testCase.input);
|
||||
test(`#extractDataFromStrings: ${testCase.name}`, function (assert) {
|
||||
const data = extractDataFromStrings(testCase.dataInput);
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
@ -469,8 +496,8 @@ module('Unit | Lib | console helpers', function () {
|
|||
];
|
||||
|
||||
testCommandCases.forEach(function (testCase) {
|
||||
test(`#logErrorFromInput: ${testCase.name}`, function (assert) {
|
||||
const data = logErrorFromInput(...testCase.args);
|
||||
test(`#formattedErrorFromInput: ${testCase.name}`, function (assert) {
|
||||
const data = formattedErrorFromInput(...testCase.args);
|
||||
|
||||
assert.deepEqual(
|
||||
data,
|
||||
|
|
|
@ -38,7 +38,7 @@ module('Unit | Service | console', function (hooks) {
|
|||
|
||||
{
|
||||
method: 'read',
|
||||
args: ['/secrets/foo/bar', {}, '30m'],
|
||||
args: ['/secrets/foo/bar', {}, { wrapTTL: '30m' }],
|
||||
expectedURL: 'secrets/foo/bar',
|
||||
expectedVerb: 'GET',
|
||||
expectedOptions: { data: undefined, wrapTTL: '30m' },
|
||||
|
@ -65,7 +65,7 @@ module('Unit | Service | console', function (hooks) {
|
|||
|
||||
{
|
||||
method: 'list',
|
||||
args: ['secret/mounts', {}, '1h'],
|
||||
args: ['secret/mounts', {}, { wrapTTL: '1h' }],
|
||||
expectedURL: 'secret/mounts/',
|
||||
expectedVerb: 'GET',
|
||||
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`);
|
||||
});
|
||||
});
|
||||
|
||||
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
|
||||
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":
|
||||
version: 1.2.0
|
||||
resolution: "@types/symlink-or-copy@npm:1.2.0"
|
||||
|
@ -24224,6 +24231,7 @@ __metadata:
|
|||
"@types/ember__utils": ^4.0.2
|
||||
"@types/qunit": ^2.19.3
|
||||
"@types/rsvp": ^4.0.4
|
||||
"@types/shell-quote": ^1.7.1
|
||||
"@typescript-eslint/eslint-plugin": ^5.19.0
|
||||
"@typescript-eslint/parser": ^5.19.0
|
||||
asn1js: ^2.2.0
|
||||
|
|
Loading…
Reference in New Issue