mirror of https://github.com/grafana/grafana
CloudWatch: Consume the grafana/aws-sdk (#31807)
* consume the grafana/aws-sdk * upgrade aws-sdkpull/31870/head
parent
d2aaaeb497
commit
f135d75a22
@ -1,339 +1,63 @@ |
||||
import React, { PureComponent } from 'react'; |
||||
import { InlineFormLabel, LegacyForms, Button } from '@grafana/ui'; |
||||
const { Select, Input } = LegacyForms; |
||||
import { |
||||
AppEvents, |
||||
SelectableValue, |
||||
DataSourcePluginOptionsEditorProps, |
||||
onUpdateDatasourceJsonDataOptionSelect, |
||||
onUpdateDatasourceResetOption, |
||||
onUpdateDatasourceJsonDataOption, |
||||
onUpdateDatasourceSecureJsonDataOption, |
||||
} from '@grafana/data'; |
||||
import React, { FC, useEffect, useState } from 'react'; |
||||
import { Input, InlineField } from '@grafana/ui'; |
||||
import { DataSourcePluginOptionsEditorProps, onUpdateDatasourceJsonDataOption } from '@grafana/data'; |
||||
import { ConnectionConfig } from '@grafana/aws-sdk'; |
||||
|
||||
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; |
||||
import { CloudWatchDatasource } from '../datasource'; |
||||
import { CloudWatchJsonData, CloudWatchSecureJsonData } from '../types'; |
||||
import { CancelablePromise, makePromiseCancelable } from 'app/core/utils/CancelablePromise'; |
||||
import { appEvents } from 'app/core/core'; |
||||
import { store } from 'app/store/store'; |
||||
import { notifyApp } from 'app/core/actions'; |
||||
import { createWarningNotification } from 'app/core/copy/appNotification'; |
||||
|
||||
const authProviderOptions = [ |
||||
{ label: 'AWS SDK Default', value: 'default' }, |
||||
{ label: 'Access & secret key', value: 'keys' }, |
||||
{ label: 'Credentials file', value: 'credentials' }, |
||||
] as SelectableValue[]; |
||||
import { CloudWatchJsonData, CloudWatchSecureJsonData } from '../types'; |
||||
import { CloudWatchDatasource } from '../datasource'; |
||||
|
||||
export type Props = DataSourcePluginOptionsEditorProps<CloudWatchJsonData, CloudWatchSecureJsonData>; |
||||
|
||||
export interface State { |
||||
regions: SelectableValue[]; |
||||
} |
||||
export const ConfigEditor: FC<Props> = (props: Props) => { |
||||
const [datasource, setDatasource] = useState<CloudWatchDatasource>(); |
||||
|
||||
export class ConfigEditor extends PureComponent<Props, State> { |
||||
constructor(props: Props) { |
||||
super(props); |
||||
const addWarning = (message: string) => { |
||||
store.dispatch(notifyApp(createWarningNotification('CloudWatch Authentication', message))); |
||||
}; |
||||
|
||||
this.state = { |
||||
regions: [], |
||||
}; |
||||
} |
||||
useEffect(() => { |
||||
getDatasourceSrv() |
||||
.loadDatasource(props.options.name) |
||||
.then((datasource: CloudWatchDatasource) => setDatasource(datasource)); |
||||
|
||||
loadRegionsPromise: CancelablePromise<any> | null = null; |
||||
|
||||
componentDidMount() { |
||||
this.loadRegionsPromise = makePromiseCancelable(this.loadRegions()); |
||||
this.loadRegionsPromise.promise.catch(({ isCanceled }) => { |
||||
if (isCanceled) { |
||||
console.warn('Cloud Watch ConfigEditor has unmounted, initialization was canceled'); |
||||
} |
||||
}); |
||||
|
||||
if (this.props.options.jsonData.authType === 'arn') { |
||||
appEvents.emit(AppEvents.alertWarning, [ |
||||
'Since grafana 7.3 authentication type "arn" is deprecated, falling back to default SDK provider', |
||||
]); |
||||
if (props.options.jsonData.authType === 'arn') { |
||||
addWarning('Since grafana 7.3 authentication type "arn" is deprecated, falling back to default SDK provider'); |
||||
} else if ( |
||||
this.props.options.jsonData.authType === 'credentials' && |
||||
!this.props.options.jsonData.profile && |
||||
!this.props.options.jsonData.database |
||||
props.options.jsonData.authType === 'credentials' && |
||||
!props.options.jsonData.profile && |
||||
!props.options.jsonData.database |
||||
) { |
||||
appEvents.emit(AppEvents.alertWarning, [ |
||||
addWarning( |
||||
'As of grafana 7.3 authentication type "credentials" should be used only for shared file credentials. \ |
||||
If you don\'t have a credentials file, switch to the default SDK provider for extracting credentials \ |
||||
from environment variables or IAM roles', |
||||
]); |
||||
} |
||||
} |
||||
|
||||
componentWillUnmount() { |
||||
if (this.loadRegionsPromise) { |
||||
this.loadRegionsPromise.cancel(); |
||||
} |
||||
} |
||||
|
||||
async loadRegions() { |
||||
await getDatasourceSrv() |
||||
.loadDatasource(this.props.options.name) |
||||
.then((ds: CloudWatchDatasource) => ds.getRegions()) |
||||
.then( |
||||
(regions: any) => { |
||||
this.setState({ |
||||
regions: regions.map((region: any) => { |
||||
return { |
||||
value: region.value, |
||||
label: region.text, |
||||
}; |
||||
}), |
||||
}); |
||||
}, |
||||
(err: any) => { |
||||
const regions = [ |
||||
'af-south-1', |
||||
'ap-east-1', |
||||
'ap-northeast-1', |
||||
'ap-northeast-2', |
||||
'ap-northeast-3', |
||||
'ap-south-1', |
||||
'ap-southeast-1', |
||||
'ap-southeast-2', |
||||
'ca-central-1', |
||||
'cn-north-1', |
||||
'cn-northwest-1', |
||||
'eu-central-1', |
||||
'eu-north-1', |
||||
'eu-south-1', |
||||
'eu-west-1', |
||||
'eu-west-2', |
||||
'eu-west-3', |
||||
'me-south-1', |
||||
'sa-east-1', |
||||
'us-east-1', |
||||
'us-east-2', |
||||
'us-gov-east-1', |
||||
'us-gov-west-1', |
||||
'us-iso-east-1', |
||||
'us-isob-east-1', |
||||
'us-west-1', |
||||
'us-west-2', |
||||
]; |
||||
|
||||
this.setState({ |
||||
regions: regions.map((region: string) => ({ |
||||
value: region, |
||||
label: region, |
||||
})), |
||||
}); |
||||
|
||||
// expected to fail when creating new datasource
|
||||
// console.error('failed to get latest regions', err);
|
||||
} |
||||
If you don\'t have a credentials file, switch to the default SDK provider for extracting credentials \ |
||||
from environment variables or IAM roles' |
||||
); |
||||
} |
||||
|
||||
render() { |
||||
const { regions } = this.state; |
||||
const { options } = this.props; |
||||
const secureJsonData = (options.secureJsonData || {}) as CloudWatchSecureJsonData; |
||||
let profile = options.jsonData.profile; |
||||
if (profile === undefined) { |
||||
profile = options.database; |
||||
} |
||||
|
||||
return ( |
||||
<> |
||||
<h3 className="page-heading">CloudWatch Details</h3> |
||||
<div className="gf-form-group"> |
||||
<div className="gf-form-inline"> |
||||
<div className="gf-form"> |
||||
<InlineFormLabel |
||||
className="width-14" |
||||
tooltip="Specify which AWS credentials chain to use. AWS SDK Default is the recommended option for EKS, ECS, or if you've attached an IAM role to your EC2 instance." |
||||
> |
||||
Authentication Provider |
||||
</InlineFormLabel> |
||||
<Select |
||||
className="width-30" |
||||
value={authProviderOptions.find((authProvider) => authProvider.value === options.jsonData.authType)} |
||||
options={authProviderOptions} |
||||
defaultValue={options.jsonData.authType} |
||||
onChange={(option) => { |
||||
onUpdateDatasourceJsonDataOptionSelect(this.props, 'authType')(option); |
||||
}} |
||||
/> |
||||
</div> |
||||
</div> |
||||
{options.jsonData.authType === 'credentials' && ( |
||||
<div className="gf-form-inline"> |
||||
<div className="gf-form"> |
||||
<InlineFormLabel |
||||
className="width-14" |
||||
tooltip="Credentials profile name, as specified in ~/.aws/credentials, leave blank for default." |
||||
> |
||||
Credentials Profile Name |
||||
</InlineFormLabel> |
||||
<div className="width-30"> |
||||
<Input |
||||
className="width-30" |
||||
placeholder="default" |
||||
value={profile} |
||||
onChange={onUpdateDatasourceJsonDataOption(this.props, 'profile')} |
||||
/> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
)} |
||||
{options.jsonData.authType === 'keys' && ( |
||||
<div> |
||||
{options.secureJsonFields?.accessKey ? ( |
||||
<div className="gf-form-inline"> |
||||
<div className="gf-form"> |
||||
<InlineFormLabel className="width-14">Access Key ID</InlineFormLabel> |
||||
<Input className="width-25" placeholder="Configured" disabled={true} /> |
||||
</div> |
||||
<div className="gf-form"> |
||||
<div className="max-width-30 gf-form-inline"> |
||||
<Button |
||||
variant="secondary" |
||||
type="button" |
||||
onClick={onUpdateDatasourceResetOption(this.props, 'accessKey')} |
||||
> |
||||
Reset |
||||
</Button> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
) : ( |
||||
<div className="gf-form-inline"> |
||||
<div className="gf-form"> |
||||
<InlineFormLabel className="width-14">Access Key ID</InlineFormLabel> |
||||
<div className="width-30"> |
||||
<Input |
||||
className="width-30" |
||||
value={secureJsonData.accessKey || ''} |
||||
onChange={onUpdateDatasourceSecureJsonDataOption(this.props, 'accessKey')} |
||||
/> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
)} |
||||
{options.secureJsonFields?.secretKey ? ( |
||||
<div className="gf-form-inline"> |
||||
<div className="gf-form"> |
||||
<InlineFormLabel className="width-14">Secret Access Key</InlineFormLabel> |
||||
<Input className="width-25" placeholder="Configured" disabled={true} /> |
||||
</div> |
||||
<div className="gf-form"> |
||||
<div className="max-width-30 gf-form-inline"> |
||||
<Button |
||||
variant="secondary" |
||||
type="button" |
||||
onClick={onUpdateDatasourceResetOption(this.props, 'secretKey')} |
||||
> |
||||
Reset |
||||
</Button> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
) : ( |
||||
<div className="gf-form-inline"> |
||||
<div className="gf-form"> |
||||
<InlineFormLabel className="width-14">Secret Access Key</InlineFormLabel> |
||||
<div className="width-30"> |
||||
<Input |
||||
className="width-30" |
||||
value={secureJsonData.secretKey || ''} |
||||
onChange={onUpdateDatasourceSecureJsonDataOption(this.props, 'secretKey')} |
||||
/> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
)} |
||||
</div> |
||||
)} |
||||
<div className="gf-form-inline"> |
||||
<div className="gf-form"> |
||||
<InlineFormLabel |
||||
className="width-14" |
||||
tooltip="Optionally, specify the ARN of a role to assume. Specifying a role here will ensure that the selected authentication provider is used to assume the specified role rather than using the credentials directly. Leave blank if you don't need to assume a role at all" |
||||
> |
||||
Assume Role ARN |
||||
</InlineFormLabel> |
||||
<div className="width-30"> |
||||
<Input |
||||
className="width-30" |
||||
placeholder="arn:aws:iam:*" |
||||
value={options.jsonData.assumeRoleArn || ''} |
||||
onChange={onUpdateDatasourceJsonDataOption(this.props, 'assumeRoleArn')} |
||||
/> |
||||
</div> |
||||
</div> |
||||
<div className="gf-form-inline"> |
||||
<div className="gf-form"> |
||||
<InlineFormLabel |
||||
className="width-14" |
||||
tooltip="If you are assuming a role in another account, that has been created with an external ID, specify the external ID here." |
||||
> |
||||
External ID |
||||
</InlineFormLabel> |
||||
<div className="width-30"> |
||||
<Input |
||||
className="width-30" |
||||
placeholder="External ID" |
||||
value={options.jsonData.externalId || ''} |
||||
onChange={onUpdateDatasourceJsonDataOption(this.props, 'externalId')} |
||||
/> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div className="gf-form-inline"> |
||||
<div className="gf-form"> |
||||
<InlineFormLabel |
||||
className="width-14" |
||||
tooltip="Specify the region, such as for US West (Oregon) use ` us-west-2 ` as the region." |
||||
> |
||||
Default Region |
||||
</InlineFormLabel> |
||||
<Select |
||||
className="width-30" |
||||
value={regions.find((region) => region.value === options.jsonData.defaultRegion)} |
||||
options={regions} |
||||
defaultValue={options.jsonData.defaultRegion} |
||||
onChange={onUpdateDatasourceJsonDataOptionSelect(this.props, 'defaultRegion')} |
||||
/> |
||||
</div> |
||||
</div> |
||||
<div className="gf-form-inline"> |
||||
<div className="gf-form"> |
||||
<InlineFormLabel className="width-14" tooltip="Namespaces of Custom Metrics."> |
||||
Custom Metrics |
||||
</InlineFormLabel> |
||||
<Input |
||||
className="width-30" |
||||
placeholder="Namespace1,Namespace2" |
||||
value={options.jsonData.customMetricsNamespaces || ''} |
||||
onChange={onUpdateDatasourceJsonDataOption(this.props, 'customMetricsNamespaces')} |
||||
/> |
||||
</div> |
||||
</div> |
||||
<div className="gf-form-inline"> |
||||
<div className="gf-form"> |
||||
<InlineFormLabel className="width-14" tooltip="Optionally, specify a custom endpoint for the service."> |
||||
Endpoint |
||||
</InlineFormLabel> |
||||
<div className="width-30"> |
||||
<Input |
||||
className="width-30" |
||||
placeholder={'https://{service}.{region}.amazonaws.com'} |
||||
value={options.jsonData.endpoint || ''} |
||||
onChange={onUpdateDatasourceJsonDataOption(this.props, 'endpoint')} |
||||
/> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</> |
||||
); |
||||
} |
||||
} |
||||
|
||||
export default ConfigEditor; |
||||
}, []); |
||||
|
||||
return ( |
||||
<> |
||||
<ConnectionConfig |
||||
{...props} |
||||
loadRegions={ |
||||
datasource && |
||||
(() => datasource!.getRegions().then((r) => r.filter((r) => r.value !== 'default').map((v) => v.value))) |
||||
} |
||||
> |
||||
<InlineField label="Namespaces of Custom Metrics" labelWidth={28} tooltip="Namespaces of Custom Metrics."> |
||||
<Input |
||||
width={60} |
||||
placeholder="Namespace1,Namespace2" |
||||
value={props.options.jsonData.customMetricsNamespaces || ''} |
||||
onChange={onUpdateDatasourceJsonDataOption(props, 'customMetricsNamespaces')} |
||||
/> |
||||
</InlineField> |
||||
</ConnectionConfig> |
||||
</> |
||||
); |
||||
}; |
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue