CloudWatch: fix variable query migration with json template variables (#51207) (#51346)

(cherry picked from commit 8ba8e1df83)
This commit is contained in:
Isabella Siu
2022-06-23 16:48:10 -04:00
committed by GitHub
parent d1c45321f7
commit cfc67c0bbd
4 changed files with 50 additions and 17 deletions
@@ -500,8 +500,7 @@ describe('datasource', () => {
describe('convertMultiFiltersFormat', () => { describe('convertMultiFiltersFormat', () => {
const ds = setupMockedDataSource({ variables: [labelsVariable, dimensionVariable], mockGetVariableName: false }); const ds = setupMockedDataSource({ variables: [labelsVariable, dimensionVariable], mockGetVariableName: false });
it('converts keys and values correctly', () => { it('converts keys and values correctly', () => {
// the json in this line doesn't matter, but it makes sure that old queries will be parsed const filters = { $dimension: ['b'], a: ['$labels', 'bar'] };
const filters = { $dimension: ['b'], a: ['${labels:json}', 'bar'] };
const result = ds.datasource.convertMultiFilterFormat(filters); const result = ds.datasource.convertMultiFilterFormat(filters);
expect(result).toStrictEqual({ expect(result).toStrictEqual({
env: ['b'], env: ['b'],
@@ -237,7 +237,7 @@ export class CloudWatchDatasource
options, options,
this.timeSrv.timeRange(), this.timeSrv.timeRange(),
this.replace.bind(this), this.replace.bind(this),
this.getVariableValue.bind(this), this.expandVariableToArray.bind(this),
this.getActualRegion.bind(this), this.getActualRegion.bind(this),
this.tracingDataSourceUid this.tracingDataSourceUid
); );
@@ -650,7 +650,7 @@ export class CloudWatchDatasource
if (Array.isArray(anyQuery[fieldName])) { if (Array.isArray(anyQuery[fieldName])) {
anyQuery[fieldName] = anyQuery[fieldName].flatMap((val: string) => { anyQuery[fieldName] = anyQuery[fieldName].flatMap((val: string) => {
if (fieldName === 'logGroupNames') { if (fieldName === 'logGroupNames') {
return this.getVariableValue(val, options.scopedVars || {}); return this.expandVariableToArray(val, options.scopedVars || {});
} }
return this.replace(val, options.scopedVars, true, fieldName); return this.replace(val, options.scopedVars, true, fieldName);
}); });
@@ -851,22 +851,20 @@ export class CloudWatchDatasource
return { ...result, [key]: null }; return { ...result, [key]: null };
} }
const newValues = this.getVariableValue(value, scopedVars); const newValues = this.expandVariableToArray(value, scopedVars);
return { ...result, [key]: newValues }; return { ...result, [key]: newValues };
}, {}); }, {});
} }
// get the value for a given template variable // get the value for a given template variable
getVariableValue(value: string, scopedVars: ScopedVars): string[] { expandVariableToArray(value: string, scopedVars: ScopedVars): string[] {
const variableName = this.templateSrv.getVariableName(value); const variableName = this.templateSrv.getVariableName(value);
const valueVar = this.templateSrv.getVariables().find(({ name }) => { const valueVar = this.templateSrv.getVariables().find(({ name }) => {
return name === variableName; return name === variableName;
}); });
if (variableName && valueVar) { if (variableName && valueVar) {
if ((valueVar as unknown as VariableWithMultiSupport).multi) { if ((valueVar as unknown as VariableWithMultiSupport).multi) {
// rebuild the variable name to handle old migrated queries return this.templateSrv.replace(value, scopedVars, 'pipe').split('|');
const values = this.templateSrv.replace('$' + variableName, scopedVars, 'pipe').split('|');
return values;
} }
return [this.templateSrv.replace(value, scopedVars)]; return [this.templateSrv.replace(value, scopedVars)];
} }
@@ -881,7 +879,7 @@ export class CloudWatchDatasource
} }
const initialVal: string[] = []; const initialVal: string[] = [];
const newValues = values.reduce((result, value) => { const newValues = values.reduce((result, value) => {
const vals = this.getVariableValue(value, {}); const vals = this.expandVariableToArray(value, {});
return [...result, ...vals]; return [...result, ...vals];
}, initialVal); }, initialVal);
return { ...result, [key]: newValues }; return { ...result, [key]: newValues };
@@ -55,6 +55,12 @@ describe('variableQueryMigrations', () => {
expect(query.dimensionKey).toBe('DBInstanceIdentifier'); expect(query.dimensionKey).toBe('DBInstanceIdentifier');
expect(query.dimensionFilters).toStrictEqual({ InstanceId: '$instance_id' }); expect(query.dimensionFilters).toStrictEqual({ InstanceId: '$instance_id' });
}); });
it('should migrate json template variables', () => {
const query = migrateVariableQuery(
'dimension_values(us-east-1,AWS/RDS,CPUUtilization,DBInstanceIdentifier,{"role":${role:json},"pop":${pop:json}})'
);
expect(query.dimensionFilters).toStrictEqual({ role: '$role', pop: '$pop' });
});
}); });
}); });
}); });
@@ -68,6 +74,12 @@ describe('variableQueryMigrations', () => {
expect(query.resourceType).toBe('elasticloadbalancing:loadbalancer'); expect(query.resourceType).toBe('elasticloadbalancing:loadbalancer');
expect(query.tags).toStrictEqual({ 'elasticbeanstalk:environment-name': ['myApp-dev', 'myApp-prod'] }); expect(query.tags).toStrictEqual({ 'elasticbeanstalk:environment-name': ['myApp-dev', 'myApp-prod'] });
}); });
it('should migrate json template variables', () => {
const query = migrateVariableQuery(
'resource_arns(eu-west-1,elasticloadbalancing:loadbalancer,{"elasticbeanstalk:environment-name":[${jsonVar:json},"test-$singleVar"]})'
);
expect(query.tags).toStrictEqual({ 'elasticbeanstalk:environment-name': ['$jsonVar', 'test-$singleVar'] });
});
it('should parse a empty array for tags', () => { it('should parse a empty array for tags', () => {
const query = migrateVariableQuery('resource_arns(eu-west-1,elasticloadbalancing:loadbalancer, [])'); const query = migrateVariableQuery('resource_arns(eu-west-1,elasticloadbalancing:loadbalancer, [])');
expect(query.tags).toStrictEqual({}); expect(query.tags).toStrictEqual({});
@@ -81,6 +93,10 @@ describe('variableQueryMigrations', () => {
expect(query.attributeName).toBe('rds:db'); expect(query.attributeName).toBe('rds:db');
expect(query.ec2Filters).toStrictEqual({ environment: ['$environment'] }); expect(query.ec2Filters).toStrictEqual({ environment: ['$environment'] });
}); });
it('should migrate json template variables', () => {
const query = migrateVariableQuery('ec2_instance_attribute(us-east-1,rds:db,{"environment":${env:json}})');
expect(query.ec2Filters).toStrictEqual({ environment: ['$env'] });
});
it('should parse an empty array for filters', () => { it('should parse an empty array for filters', () => {
const query = migrateVariableQuery('ec2_instance_attribute(us-east-1,rds:db,[])'); const query = migrateVariableQuery('ec2_instance_attribute(us-east-1,rds:db,[])');
expect(query.ec2Filters).toStrictEqual({}); expect(query.ec2Filters).toStrictEqual({});
@@ -1,11 +1,29 @@
import { omit } from 'lodash'; import { omit } from 'lodash';
import { VariableQuery, VariableQueryType, OldVariableQuery } from '../types'; import { Dimensions, VariableQuery, VariableQueryType, OldVariableQuery, MultiFilters } from '../types';
const jsonVariable = /\${(\w+):json}/g;
function isVariableQuery(rawQuery: string | VariableQuery | OldVariableQuery): rawQuery is VariableQuery { function isVariableQuery(rawQuery: string | VariableQuery | OldVariableQuery): rawQuery is VariableQuery {
return typeof rawQuery !== 'string' && typeof rawQuery.ec2Filters !== 'string' && typeof rawQuery.tags !== 'string'; return typeof rawQuery !== 'string' && typeof rawQuery.ec2Filters !== 'string' && typeof rawQuery.tags !== 'string';
} }
function migrateMultiFilters(oldFilters: string): MultiFilters {
const tempFilters = oldFilters.replace(jsonVariable, '"$$$1"');
const parsedFilters: Dimensions = JSON.parse(tempFilters);
const newFilters: MultiFilters = {};
// if the old filter was {key:value} transform it to {key:[value]}
Object.keys(parsedFilters).forEach((key) => {
const value = parsedFilters[key];
if (typeof value === 'string') {
newFilters[key] = [value];
} else if (value !== undefined) {
newFilters[key] = value;
}
});
return newFilters;
}
export function migrateVariableQuery(rawQuery: string | VariableQuery | OldVariableQuery): VariableQuery { export function migrateVariableQuery(rawQuery: string | VariableQuery | OldVariableQuery): VariableQuery {
if (isVariableQuery(rawQuery)) { if (isVariableQuery(rawQuery)) {
return rawQuery; return rawQuery;
@@ -19,22 +37,23 @@ export function migrateVariableQuery(rawQuery: string | VariableQuery | OldVaria
newQuery.tags = {}; newQuery.tags = {};
if (rawQuery.dimensionFilters !== '' && rawQuery.ec2Filters !== '[]') { if (rawQuery.dimensionFilters !== '' && rawQuery.ec2Filters !== '[]') {
const tempFilters = rawQuery.dimensionFilters.replace(jsonVariable, '"$$$1"');
try { try {
newQuery.dimensionFilters = JSON.parse(rawQuery.dimensionFilters); newQuery.dimensionFilters = JSON.parse(tempFilters);
} catch { } catch {
throw new Error(`unable to migrate poorly formed filters: ${rawQuery.dimensionFilters}`); throw new Error(`unable to migrate poorly formed filters: ${rawQuery.dimensionFilters}`);
} }
} }
if (rawQuery.ec2Filters !== '' && rawQuery.ec2Filters !== '[]') { if (rawQuery.ec2Filters !== '' && rawQuery.ec2Filters !== '[]') {
try { try {
newQuery.ec2Filters = JSON.parse(rawQuery.ec2Filters); newQuery.ec2Filters = migrateMultiFilters(rawQuery.ec2Filters);
} catch { } catch {
throw new Error(`unable to migrate poorly formed filters: ${rawQuery.ec2Filters}`); throw new Error(`unable to migrate poorly formed filters: ${rawQuery.ec2Filters}`);
} }
} }
if (rawQuery.tags !== '' && rawQuery.tags !== '[]') { if (rawQuery.tags !== '' && rawQuery.tags !== '[]') {
try { try {
newQuery.tags = JSON.parse(rawQuery.tags); newQuery.tags = migrateMultiFilters(rawQuery.tags);
} catch { } catch {
throw new Error(`unable to migrate poorly formed filters: ${rawQuery.tags}`); throw new Error(`unable to migrate poorly formed filters: ${rawQuery.tags}`);
} }
@@ -94,8 +113,9 @@ export function migrateVariableQuery(rawQuery: string | VariableQuery | OldVaria
newQuery.dimensionKey = dimensionValuesQuery[4]; newQuery.dimensionKey = dimensionValuesQuery[4];
newQuery.dimensionFilters = {}; newQuery.dimensionFilters = {};
if (!!dimensionValuesQuery[6] && dimensionValuesQuery[6] !== '[]') { if (!!dimensionValuesQuery[6] && dimensionValuesQuery[6] !== '[]') {
const tempFilters = dimensionValuesQuery[6].replace(jsonVariable, '"$$$1"');
try { try {
newQuery.dimensionFilters = JSON.parse(dimensionValuesQuery[6]); newQuery.dimensionFilters = JSON.parse(tempFilters);
} catch { } catch {
throw new Error(`unable to migrate poorly formed filters: ${dimensionValuesQuery[6]}`); throw new Error(`unable to migrate poorly formed filters: ${dimensionValuesQuery[6]}`);
} }
@@ -118,7 +138,7 @@ export function migrateVariableQuery(rawQuery: string | VariableQuery | OldVaria
newQuery.attributeName = ec2InstanceAttributeQuery[2]; newQuery.attributeName = ec2InstanceAttributeQuery[2];
if (ec2InstanceAttributeQuery[3] && ec2InstanceAttributeQuery[3] !== '[]') { if (ec2InstanceAttributeQuery[3] && ec2InstanceAttributeQuery[3] !== '[]') {
try { try {
newQuery.ec2Filters = JSON.parse(ec2InstanceAttributeQuery[3]); newQuery.ec2Filters = migrateMultiFilters(ec2InstanceAttributeQuery[3]);
} catch { } catch {
throw new Error(`unable to migrate poorly formed filters: ${ec2InstanceAttributeQuery[3]}`); throw new Error(`unable to migrate poorly formed filters: ${ec2InstanceAttributeQuery[3]}`);
} }
@@ -133,7 +153,7 @@ export function migrateVariableQuery(rawQuery: string | VariableQuery | OldVaria
newQuery.resourceType = resourceARNsQuery[2]; newQuery.resourceType = resourceARNsQuery[2];
if (resourceARNsQuery[3] && resourceARNsQuery[3] !== '[]') { if (resourceARNsQuery[3] && resourceARNsQuery[3] !== '[]') {
try { try {
newQuery.tags = JSON.parse(resourceARNsQuery[3]); newQuery.tags = migrateMultiFilters(resourceARNsQuery[3]);
} catch { } catch {
throw new Error(`unable to migrate poorly formed filters: ${resourceARNsQuery[3]}`); throw new Error(`unable to migrate poorly formed filters: ${resourceARNsQuery[3]}`);
} }