Web console: expose props for S3 (#9432)

* expose props for S3

* added env inputs

* add scarry warning

* use .password

* put the warning front and center

* Update web-console/src/views/load-data-view/load-data-view.tsx

Co-Authored-By: Suneet Saldanha <44787917+suneet-s@users.noreply.github.com>

* let prettier rewrap the text

Co-authored-by: Suneet Saldanha <44787917+suneet-s@users.noreply.github.com>
This commit is contained in:
Vadim Ogievetsky 2020-03-18 15:32:12 -07:00 committed by GitHub
parent 1ef25a438f
commit 3b536eea7f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 161 additions and 28 deletions

View File

@ -29,6 +29,7 @@ exports[`auto-form snapshot matches snapshot 1`] = `
autocomplete="off" autocomplete="off"
class="bp3-input" class="bp3-input"
min="0" min="0"
placeholder=""
style="padding-right: 10px;" style="padding-right: 10px;"
type="text" type="text"
value="" value=""
@ -194,6 +195,7 @@ exports[`auto-form snapshot matches snapshot 1`] = `
> >
<input <input
class="bp3-input" class="bp3-input"
placeholder=""
style="padding-right: 10px;" style="padding-right: 10px;"
type="text" type="text"
value="" value=""
@ -300,6 +302,7 @@ exports[`auto-form snapshot matches snapshot 1`] = `
> >
<textarea <textarea
class="bp3-input bp3-fill" class="bp3-input bp3-fill"
placeholder=""
/> />
</div> </div>
</div> </div>

View File

@ -47,7 +47,7 @@ export interface Field<M> {
defaultValue?: any; defaultValue?: any;
emptyValue?: any; emptyValue?: any;
suggestions?: Functor<M, Suggestion[]>; suggestions?: Functor<M, Suggestion[]>;
placeholder?: string; placeholder?: Functor<M, string>;
min?: number; min?: number;
zeroMeansUndefined?: boolean; zeroMeansUndefined?: boolean;
disabled?: Functor<M, boolean>; disabled?: Functor<M, boolean>;
@ -56,13 +56,14 @@ export interface Field<M> {
adjustment?: (model: M) => M; adjustment?: (model: M) => M;
} }
export interface AutoFormProps<T> { export interface AutoFormProps<M> {
fields: Field<T>[]; fields: Field<M>[];
model: T | undefined; model: M | undefined;
onChange: (newModel: T) => void; onChange: (newModel: M) => void;
onFinalize?: () => void; onFinalize?: () => void;
showCustom?: (model: T) => boolean; showCustom?: (model: M) => boolean;
large?: boolean; large?: boolean;
globalAdjustment?: (model: M) => M;
} }
export class AutoForm<T extends Record<string, any>> extends React.PureComponent<AutoFormProps<T>> { export class AutoForm<T extends Record<string, any>> extends React.PureComponent<AutoFormProps<T>> {
@ -111,11 +112,15 @@ export class AutoForm<T extends Record<string, any>> extends React.PureComponent
newModel = deepSet(model, field.name, newValue); newModel = deepSet(model, field.name, newValue);
} }
if (field.adjustment) {
newModel = field.adjustment(newModel);
}
this.modelChange(newModel); this.modelChange(newModel);
}; };
private modelChange = (newModel: T) => { private modelChange = (newModel: T) => {
const { fields, onChange, model } = this.props; const { globalAdjustment, fields, onChange, model } = this.props;
// Delete things that are not defined now (but were defined prior to the change) // Delete things that are not defined now (but were defined prior to the change)
for (const someField of fields) { for (const someField of fields) {
@ -127,11 +132,9 @@ export class AutoForm<T extends Record<string, any>> extends React.PureComponent
} }
} }
// Perform any adjustments if needed // Perform any global adjustments if needed
for (const someField of fields) { if (globalAdjustment) {
if (someField.adjustment) { newModel = globalAdjustment(newModel);
newModel = someField.adjustment(newModel);
}
} }
onChange(newModel); onChange(newModel);
@ -162,7 +165,7 @@ export class AutoForm<T extends Record<string, any>> extends React.PureComponent
fill fill
large={large} large={large}
disabled={AutoForm.evaluateFunctor(field.disabled, model, false)} disabled={AutoForm.evaluateFunctor(field.disabled, model, false)}
placeholder={field.placeholder} placeholder={AutoForm.evaluateFunctor(field.placeholder, model, '')}
intent={ intent={
AutoForm.evaluateFunctor(field.required, model, false) && modelValue == null AutoForm.evaluateFunctor(field.required, model, false) && modelValue == null
? AutoForm.REQUIRED_INTENT ? AutoForm.REQUIRED_INTENT
@ -203,14 +206,14 @@ export class AutoForm<T extends Record<string, any>> extends React.PureComponent
<SuggestibleInput <SuggestibleInput
value={modelValue != null ? modelValue : field.defaultValue || ''} value={modelValue != null ? modelValue : field.defaultValue || ''}
onValueChange={v => { onValueChange={v => {
if (sanitize) v = sanitize(v); if (sanitize && typeof v === 'string') v = sanitize(v);
this.fieldChange(field, v); this.fieldChange(field, v);
}} }}
onBlur={() => { onBlur={() => {
if (modelValue === '') this.fieldChange(field, undefined); if (modelValue === '') this.fieldChange(field, undefined);
}} }}
onFinalize={onFinalize} onFinalize={onFinalize}
placeholder={field.placeholder} placeholder={AutoForm.evaluateFunctor(field.placeholder, model, '')}
suggestions={AutoForm.evaluateFunctor(field.suggestions, model, undefined)} suggestions={AutoForm.evaluateFunctor(field.suggestions, model, undefined)}
large={large} large={large}
disabled={AutoForm.evaluateFunctor(field.disabled, model, false)} disabled={AutoForm.evaluateFunctor(field.disabled, model, false)}
@ -268,7 +271,7 @@ export class AutoForm<T extends Record<string, any>> extends React.PureComponent
<JsonInput <JsonInput
value={deepGet(model as any, field.name)} value={deepGet(model as any, field.name)}
onChange={(v: any) => this.fieldChange(field, v)} onChange={(v: any) => this.fieldChange(field, v)}
placeholder={field.placeholder} placeholder={AutoForm.evaluateFunctor(field.placeholder, model, '')}
/> />
); );
} }
@ -282,7 +285,7 @@ export class AutoForm<T extends Record<string, any>> extends React.PureComponent
onChange={(v: any) => { onChange={(v: any) => {
this.fieldChange(field, v); this.fieldChange(field, v);
}} }}
placeholder={field.placeholder} placeholder={AutoForm.evaluateFunctor(field.placeholder, model, '')}
large={large} large={large}
disabled={AutoForm.evaluateFunctor(field.disabled, model, false)} disabled={AutoForm.evaluateFunctor(field.disabled, model, false)}
intent={ intent={
@ -304,7 +307,7 @@ export class AutoForm<T extends Record<string, any>> extends React.PureComponent
onValueChange={(v: any) => { onValueChange={(v: any) => {
this.fieldChange(field, v); this.fieldChange(field, v);
}} }}
placeholder={field.placeholder} placeholder={AutoForm.evaluateFunctor(field.placeholder, model, '')}
/> />
); );
} }

View File

@ -35,10 +35,10 @@ export interface SuggestionGroup {
suggestions: string[]; suggestions: string[];
} }
export type Suggestion = string | SuggestionGroup; export type Suggestion = undefined | string | SuggestionGroup;
export interface SuggestibleInputProps extends HTMLInputProps { export interface SuggestibleInputProps extends HTMLInputProps {
onValueChange: (newValue: string) => void; onValueChange: (newValue: undefined | string) => void;
onFinalize?: () => void; onFinalize?: () => void;
suggestions?: Suggestion[]; suggestions?: Suggestion[];
large?: boolean; large?: boolean;
@ -53,7 +53,7 @@ export class SuggestibleInput extends React.PureComponent<SuggestibleInputProps>
// this.state = {}; // this.state = {};
} }
public handleSuggestionSelect(suggestion: string) { public handleSuggestionSelect(suggestion: undefined | string) {
const { onValueChange, onFinalize } = this.props; const { onValueChange, onFinalize } = this.props;
onValueChange(suggestion); onValueChange(suggestion);
if (onFinalize) onFinalize(); if (onFinalize) onFinalize();
@ -66,7 +66,15 @@ export class SuggestibleInput extends React.PureComponent<SuggestibleInputProps>
return ( return (
<Menu> <Menu>
{suggestions.map(suggestion => { {suggestions.map(suggestion => {
if (typeof suggestion === 'string') { if (typeof suggestion === 'undefined') {
return (
<MenuItem
key="__undefined__"
text="(none)"
onClick={() => this.handleSuggestionSelect(suggestion)}
/>
);
} else if (typeof suggestion === 'string') {
return ( return (
<MenuItem <MenuItem
key={suggestion} key={suggestion}

View File

@ -107,6 +107,7 @@ exports[`compaction dialog matches snapshot 1`] = `
autocomplete="off" autocomplete="off"
class="bp3-input" class="bp3-input"
min="0" min="0"
placeholder=""
style="padding-right: 10px;" style="padding-right: 10px;"
type="text" type="text"
value="419430400" value="419430400"
@ -215,6 +216,7 @@ exports[`compaction dialog matches snapshot 1`] = `
> >
<input <input
class="bp3-input" class="bp3-input"
placeholder=""
style="padding-right: 10px;" style="padding-right: 10px;"
type="text" type="text"
value="P1D" value="P1D"
@ -275,6 +277,7 @@ exports[`compaction dialog matches snapshot 1`] = `
autocomplete="off" autocomplete="off"
class="bp3-input" class="bp3-input"
min="0" min="0"
placeholder=""
style="padding-right: 10px;" style="padding-right: 10px;"
type="text" type="text"
value="5000000" value="5000000"
@ -522,6 +525,7 @@ exports[`compaction dialog matches snapshot 1`] = `
autocomplete="off" autocomplete="off"
class="bp3-input" class="bp3-input"
min="0" min="0"
placeholder=""
style="padding-right: 10px;" style="padding-right: 10px;"
type="text" type="text"
value="25" value="25"

View File

@ -1226,6 +1226,109 @@ export function getIoConfigFormFields(ingestionComboType: IngestionComboType): F
</> </>
), ),
}, },
{
name: 'inputSource.properties.accessKeyId.type',
label: 'Access key ID type',
type: 'string',
suggestions: [undefined, 'environment', 'default'],
placeholder: '(none)',
info: (
<>
<p>S3 access key type.</p>
<p>Setting this will override the default configuration provided in the config.</p>
<p>
The access key can be pulled from an environment variable or inlined in the
ingestion spec (default).
</p>
<p>
Note: Inlining the access key into the ingestion spec is dangerous as it might
appear in server log files and can be seen by anyone accessing this console.
</p>
</>
),
adjustment: (ioConfig: IoConfig) => {
return deepSet(
ioConfig,
'inputSource.properties.secretAccessKey.type',
deepGet(ioConfig, 'inputSource.properties.accessKeyId.type'),
);
},
},
{
name: 'inputSource.properties.accessKeyId.variable',
label: 'Access key ID environment variable',
type: 'string',
placeholder: '(environment variable name)',
defined: (ioConfig: IoConfig) =>
deepGet(ioConfig, 'inputSource.properties.accessKeyId.type') === 'environment',
info: <p>The environment variable containing the S3 access key for this S3 bucket.</p>,
},
{
name: 'inputSource.properties.accessKeyId.password',
label: 'Access key ID value',
type: 'string',
placeholder: '(access key)',
defined: (ioConfig: IoConfig) =>
deepGet(ioConfig, 'inputSource.properties.accessKeyId.type') === 'default',
info: (
<>
<p>S3 access key for this S3 bucket.</p>
<p>
Note: Inlining the access key into the ingestion spec is dangerous as it might
appear in server log files and can be seen by anyone accessing this console.
</p>
</>
),
},
{
name: 'inputSource.properties.secretAccessKey.type',
label: 'Secret key type',
type: 'string',
suggestions: [undefined, 'environment', 'default'],
placeholder: '(none)',
info: (
<>
<p>S3 secret key type.</p>
<p>Setting this will override the default configuration provided in the config.</p>
<p>
The secret key can be pulled from an environment variable or inlined in the
ingestion spec (default).
</p>
<p>
Note: Inlining the secret key into the ingestion spec is dangerous as it might
appear in server log files and can be seen by anyone accessing this console.
</p>
</>
),
},
{
name: 'inputSource.properties.secretAccessKey.variable',
label: 'Secret key value',
type: 'string',
placeholder: '(environment variable name)',
defined: (ioConfig: IoConfig) =>
deepGet(ioConfig, 'inputSource.properties.secretAccessKey.type') === 'environment',
info: <p>The environment variable containing the S3 secret key for this S3 bucket.</p>,
},
{
name: 'inputSource.properties.secretAccessKey.password',
label: 'Secret key value',
type: 'string',
placeholder: '(secret key)',
defined: (ioConfig: IoConfig) =>
deepGet(ioConfig, 'inputSource.properties.secretAccessKey.type') === 'default',
info: (
<>
<p>S3 secret key for this S3 bucket.</p>
<p>
Note: Inlining the access key into the ingestion spec is dangerous as it might
appear in server log files and can be seen by anyone accessing this console.
</p>
</>
),
},
]; ];
case 'index_parallel:azure': case 'index_parallel:azure':
@ -2036,7 +2139,6 @@ export function getPartitionRelatedTuningSpecFormFields(
optimal number of partitions per time chunk and one for generating segments. optimal number of partitions per time chunk and one for generating segments.
</p> </p>
), ),
adjustment: adjustTuningConfig,
}, },
{ {
name: 'partitionsSpec.type', name: 'partitionsSpec.type',

View File

@ -70,6 +70,7 @@ import { NUMERIC_TIME_FORMATS, possibleDruidFormatForValues } from '../../utils/
import { updateSchemaWithSample } from '../../utils/druid-type'; import { updateSchemaWithSample } from '../../utils/druid-type';
import { import {
adjustIngestionSpec, adjustIngestionSpec,
adjustTuningConfig,
DimensionMode, DimensionMode,
DimensionSpec, DimensionSpec,
DimensionsSpec, DimensionsSpec,
@ -1075,11 +1076,22 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
<LearnMore href={getIngestionDocLink(spec)} /> <LearnMore href={getIngestionDocLink(spec)} />
</Callout> </Callout>
{ingestionComboType ? ( {ingestionComboType ? (
<>
<AutoForm <AutoForm
fields={getIoConfigFormFields(ingestionComboType)} fields={getIoConfigFormFields(ingestionComboType)}
model={ioConfig} model={ioConfig}
onChange={c => this.updateSpecPreview(deepSet(spec, 'spec.ioConfig', c))} onChange={c => this.updateSpecPreview(deepSet(spec, 'spec.ioConfig', c))}
/> />
{deepGet(spec, 'spec.ioConfig.inputSource.properties.secretAccessKey.password') && (
<FormGroup>
<Callout intent={Intent.WARNING}>
This key will be visible to anyone accessing this console and may appear in
server logs. For production scenarios, use of a more secure secret key type is
strongly recommended.
</Callout>
</FormGroup>
)}
</>
) : ( ) : (
<FormGroup label="IO Config"> <FormGroup label="IO Config">
<JsonInput <JsonInput
@ -2747,6 +2759,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
<AutoForm <AutoForm
fields={getPartitionRelatedTuningSpecFormFields(getSpecType(spec) || 'index_parallel')} fields={getPartitionRelatedTuningSpecFormFields(getSpecType(spec) || 'index_parallel')}
model={tuningConfig} model={tuningConfig}
globalAdjustment={adjustTuningConfig}
onChange={t => this.updateSpec(deepSet(spec, 'spec.tuningConfig', t))} onChange={t => this.updateSpec(deepSet(spec, 'spec.tuningConfig', t))}
/> />
</div> </div>