mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
CloudWatch: Consume the grafana/aws-sdk (#31807)
* consume the grafana/aws-sdk * upgrade aws-sdk
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import ConfigEditor, { Props } from './ConfigEditor';
|
||||
import { AwsAuthType } from '@grafana/aws-sdk';
|
||||
import { ConfigEditor, Props } from './ConfigEditor';
|
||||
|
||||
jest.mock('app/features/plugins/datasource_srv', () => ({
|
||||
getDatasourceSrv: () => ({
|
||||
@@ -46,7 +47,7 @@ const setup = (propOverrides?: object) => {
|
||||
externalId: '',
|
||||
database: '',
|
||||
customMetricsNamespaces: '',
|
||||
authType: 'keys',
|
||||
authType: AwsAuthType.Keys,
|
||||
defaultRegion: 'us-east-2',
|
||||
timeField: '@timestamp',
|
||||
},
|
||||
|
||||
@@ -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 { 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 React, { FC, useEffect, useState } from 'react';
|
||||
import { Input, InlineField } from '@grafana/ui';
|
||||
import { DataSourcePluginOptionsEditorProps, onUpdateDatasourceJsonDataOption } from '@grafana/data';
|
||||
import { ConnectionConfig } from '@grafana/aws-sdk';
|
||||
|
||||
const authProviderOptions = [
|
||||
{ label: 'AWS SDK Default', value: 'default' },
|
||||
{ label: 'Access & secret key', value: 'keys' },
|
||||
{ label: 'Credentials file', value: 'credentials' },
|
||||
] as SelectableValue[];
|
||||
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||
import { store } from 'app/store/store';
|
||||
import { notifyApp } from 'app/core/actions';
|
||||
import { createWarningNotification } from 'app/core/copy/appNotification';
|
||||
|
||||
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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,12 +5,14 @@ import React, { memo } from 'react';
|
||||
import { AbsoluteTimeRange, QueryEditorProps } from '@grafana/data';
|
||||
import { InlineFormLabel } from '@grafana/ui';
|
||||
import { CloudWatchDatasource } from '../datasource';
|
||||
import { CloudWatchLogsQuery, CloudWatchQuery } from '../types';
|
||||
import { CloudWatchJsonData, CloudWatchLogsQuery, CloudWatchQuery } from '../types';
|
||||
import { CloudWatchLogsQueryField } from './LogsQueryField';
|
||||
import CloudWatchLink from './CloudWatchLink';
|
||||
import { css } from 'emotion';
|
||||
|
||||
type Props = QueryEditorProps<CloudWatchDatasource, CloudWatchQuery> & { allowCustomValue?: boolean };
|
||||
type Props = QueryEditorProps<CloudWatchDatasource, CloudWatchQuery, CloudWatchJsonData> & {
|
||||
allowCustomValue?: boolean;
|
||||
};
|
||||
|
||||
const labelClass = css`
|
||||
margin-left: 3px;
|
||||
|
||||
@@ -22,7 +22,7 @@ import syntax from '../syntax';
|
||||
|
||||
// Types
|
||||
import { AbsoluteTimeRange, AppEvents, ExploreQueryFieldProps, SelectableValue } from '@grafana/data';
|
||||
import { CloudWatchLogsQuery, CloudWatchQuery } from '../types';
|
||||
import { CloudWatchJsonData, CloudWatchLogsQuery, CloudWatchQuery } from '../types';
|
||||
import { CloudWatchDatasource } from '../datasource';
|
||||
import { LanguageMap, languages as prismLanguages } from 'prismjs';
|
||||
import { CloudWatchLanguageProvider } from '../language_provider';
|
||||
@@ -32,7 +32,8 @@ import { appEvents } from 'app/core/core';
|
||||
import { InputActionMeta } from '@grafana/ui/src/components/Select/types';
|
||||
import { getStatsGroups } from '../utils/query/getStatsGroups';
|
||||
|
||||
export interface CloudWatchLogsQueryFieldProps extends ExploreQueryFieldProps<CloudWatchDatasource, CloudWatchQuery> {
|
||||
export interface CloudWatchLogsQueryFieldProps
|
||||
extends ExploreQueryFieldProps<CloudWatchDatasource, CloudWatchQuery, CloudWatchJsonData> {
|
||||
absoluteRange: AbsoluteTimeRange;
|
||||
onLabelsRefresh?: () => void;
|
||||
ExtraFieldElement?: ReactNode;
|
||||
|
||||
@@ -7,11 +7,12 @@ import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
import { MetricsQueryEditor, normalizeQuery, Props } from './MetricsQueryEditor';
|
||||
import { CloudWatchDatasource } from '../datasource';
|
||||
import { CustomVariableModel, initialVariableModelState } from '../../../../features/variables/types';
|
||||
import { CloudWatchJsonData } from '../types';
|
||||
|
||||
const setup = () => {
|
||||
const instanceSettings = {
|
||||
jsonData: { defaultRegion: 'us-east-1' },
|
||||
} as DataSourceInstanceSettings;
|
||||
} as DataSourceInstanceSettings<CloudWatchJsonData>;
|
||||
|
||||
const templateSrv = new TemplateSrv();
|
||||
const variable: CustomVariableModel = {
|
||||
|
||||
@@ -4,11 +4,11 @@ import isEmpty from 'lodash/isEmpty';
|
||||
import { ExploreQueryFieldProps } from '@grafana/data';
|
||||
import { LegacyForms, ValidationEvents, EventsWithValidation, Icon } from '@grafana/ui';
|
||||
const { Input, Switch } = LegacyForms;
|
||||
import { CloudWatchQuery, CloudWatchMetricsQuery } from '../types';
|
||||
import { CloudWatchQuery, CloudWatchMetricsQuery, CloudWatchJsonData } from '../types';
|
||||
import { CloudWatchDatasource } from '../datasource';
|
||||
import { QueryField, Alias, MetricsQueryFieldsEditor } from './';
|
||||
|
||||
export type Props = ExploreQueryFieldProps<CloudWatchDatasource, CloudWatchQuery>;
|
||||
export type Props = ExploreQueryFieldProps<CloudWatchDatasource, CloudWatchQuery, CloudWatchJsonData>;
|
||||
|
||||
interface State {
|
||||
showMeta: boolean;
|
||||
|
||||
@@ -2,13 +2,13 @@ import React, { PureComponent } from 'react';
|
||||
import pick from 'lodash/pick';
|
||||
import { ExploreQueryFieldProps, ExploreMode } from '@grafana/data';
|
||||
import { Segment } from '@grafana/ui';
|
||||
import { CloudWatchQuery } from '../types';
|
||||
import { CloudWatchJsonData, CloudWatchQuery } from '../types';
|
||||
import { CloudWatchDatasource } from '../datasource';
|
||||
import { QueryInlineField } from './';
|
||||
import { MetricsQueryEditor } from './MetricsQueryEditor';
|
||||
import LogsQueryEditor from './LogsQueryEditor';
|
||||
|
||||
export type Props = ExploreQueryFieldProps<CloudWatchDatasource, CloudWatchQuery>;
|
||||
export type Props = ExploreQueryFieldProps<CloudWatchDatasource, CloudWatchQuery, CloudWatchJsonData>;
|
||||
|
||||
const apiModes = {
|
||||
Metrics: { label: 'CloudWatch Metrics', value: 'Metrics' },
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user