mirror of https://github.com/apache/druid.git
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:
parent
1ef25a438f
commit
3b536eea7f
|
@ -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>
|
||||||
|
|
|
@ -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, '')}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in New Issue