Skip to content

Commit

Permalink
Merge pull request #70 from Innablr/hotfix/assume-fail
Browse files Browse the repository at this point in the history
Fix incorrect config handling
  • Loading branch information
abukharov authored Jan 17, 2024
2 parents 4b09136 + 3ba66dc commit d58a4b7
Show file tree
Hide file tree
Showing 15 changed files with 104 additions and 123 deletions.
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ Main Revolver configuration is done in YAML. First line in the config file must
|-|-|-|
| region | Specifies the default AWS region | - |
| timezone | Specifies the default time zone | - |
| timezone_tag | Revolver will read this tag on individual resources to override account-wide timezone | Timezone |
| timezoneTag | Revolver will read this tag on individual resources to override account-wide timezone | Timezone |
| organization_role_name | Role to be assumed on the main account from organizations to get the accounts list from it | - |
| revolver_role_name | Revolver role name to be assumed on each client account | - |
| drivers | List of enabled drivers and their options (see Drivers) | - |
Expand All @@ -86,7 +86,7 @@ Main Revolver configuration is done in YAML. First line in the config file must
defaults:
region: ap-southeast-2
timezone: Australia/Melbourne
timezone_tag: Timezone
timezoneTag: Timezone
organization_role_name: AWSOrganizationsReadOnly
revolver_role_name: ssPowerCycle
drivers:
Expand Down Expand Up @@ -141,21 +141,21 @@ Main Revolver configuration is done in YAML. First line in the config file must
plugins:
- name: powercycle
tagging: strict
availability_tag: Schedule
availabilityTag: Schedule
- name: validateTags
tag: CostCentre
exclude_list:
- account_id: "111111111111"
settings:
name: helix-dev
timezone: Europe/Dublin
timezone_tag: TZ
timezoneTag: TZ
plugins:
powercycle:
active: true
configs:
- tagging: strict
availability_tag: Schedule
availabilityTag: Schedule
validateTags:
active: true
configs:
Expand Down Expand Up @@ -205,7 +205,7 @@ Starts AWS resources in the worktime and stops them after hours based on their t
|Option|Description|Allowed values|Default|
|-|-|-|-|
|tagging|Defines tagging format. See below|`strict`|`strict`|
|availability_tag|Name of the tag that contains the schedule|AWS tag name|Schedule|
|availabilityTag|Name of the tag that contains the schedule|AWS tag name|Schedule|

When an operation is performed on a resource a tag with a name `ReasonSchedule` (Schedule is replaced with the actual name of the schedule tag) will be set explaining the reason.

Expand All @@ -219,7 +219,7 @@ plugins:
active: true
configs:
- tagging: strict
availability_tag: Schedule
availabilityTag: Schedule
```

Powercycle plugin supports the following tagging standards:
Expand Down
8 changes: 4 additions & 4 deletions drivers/driverInterface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@ import { RevolverAction } from '../actions/actions';
export abstract class DriverInterface {
protected accountConfig: any;
protected driverConfig: any;
protected Id: string;
protected accountId: string;
protected logger: Logger<RevolverLogObject>;

constructor(accountConfig: any, driverConfig: any) {
this.accountConfig = accountConfig.settings;
this.driverConfig = driverConfig;
this.Id = accountConfig.Id;
this.accountId = accountConfig.accountId;
this.logger = logger.getSubLogger(
{ name: `${this.accountConfig.name}(${this.Id})` },
{ accountId: this.Id, accountName: this.accountConfig.name, driverName: this.name },
{ name: `${this.accountConfig.name}(${this.accountId})` },
{ accountId: this.accountId, accountName: this.accountConfig.name, driverName: this.name },
);
this.logger.debug(`Initialising driver ${this.name} for account ${this.accountConfig.name}`);
}
Expand Down
5 changes: 1 addition & 4 deletions drivers/ebs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,7 @@ class EBSDriver extends DriverInterface {

return ebsVolumes.map(
(xe) =>
new InstrumentedEBS(
xe,
`arn:aws:ec2:${this.accountConfig.region}:${this.accountConfig.Id}:volume/${xe.VolumeId}`,
),
new InstrumentedEBS(xe, `arn:aws:ec2:${this.accountConfig.region}:${this.accountId}:volume/${xe.VolumeId}`),
);
}
}
Expand Down
5 changes: 1 addition & 4 deletions drivers/ec2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,10 +234,7 @@ class Ec2Driver extends DriverInterface {

return ec2Instances.map(
(xi) =>
new InstrumentedEc2(
xi,
`arn:aws:ec2:${this.accountConfig.region}:${this.accountConfig.Id}:volume/${xi.InstanceId}`,
),
new InstrumentedEc2(xi, `arn:aws:ec2:${this.accountConfig.region}:${this.accountId}:volume/${xi.InstanceId}`),
);
}
}
Expand Down
4 changes: 2 additions & 2 deletions drivers/redshiftCluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ class RedshiftClusterDriver extends DriverInterface {
stopOneCluster(cluster: InstrumentedRedshiftCluster) {
let redshift: Redshift;
const logger = this.logger;
const tzTagName = this.accountConfig.timezone_tag || 'Timezone';
const tzTagName = this.accountConfig.timezoneTag || 'Timezone';
const tz = cluster.tag(tzTagName) || this.accountConfig.timezone || 'utc';
const locaTimeNow = dateTime.getTime(tz);
const snapshotId = `revolver-cluster-${cluster.resourceId}-${locaTimeNow.toFormat('yyyyLLddHHmmss')}`;
Expand Down Expand Up @@ -239,7 +239,7 @@ class RedshiftClusterDriver extends DriverInterface {
(cluster) =>
new InstrumentedRedshiftCluster(
cluster,
`arn:aws:redshift:${this.accountConfig.region}:${this.Id}:cluster:${cluster.ClusterIdentifier}`,
`arn:aws:redshift:${this.accountConfig.region}:${this.accountId}:cluster:${cluster.ClusterIdentifier}`,
),
),
);
Expand Down
2 changes: 1 addition & 1 deletion drivers/redshiftClusterSnapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ class RedshiftClusterSnapshotDriver extends DriverInterface {
(xs) =>
new InstrumentedRedshiftClusterSnapshot(
xs,
`arn:aws:redshift:${this.accountConfig.region}:${this.Id}:snapshot:${xs.ClusterIdentifier}/${xs.SnapshotIdentifier}`,
`arn:aws:redshift:${this.accountConfig.region}:${this.accountId}:snapshot:${xs.ClusterIdentifier}/${xs.SnapshotIdentifier}`,
),
),
)
Expand Down
2 changes: 1 addition & 1 deletion drivers/snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ class SnapshotDriver extends DriverInterface {
const ec2 = await new EC2({ credentials: creds, region: this.accountConfig.region });

const snapshots = await paginateAwsCall(ec2.describeSnapshots.bind(ec2), 'Snapshots', {
OwnerIds: [this.Id],
OwnerIds: [this.accountId],
});
logger.debug('Snapshots %d found', snapshots.length);

Expand Down
4 changes: 2 additions & 2 deletions lib/accountRevolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ export class AccountRevolver {
this.config = accountConfig;
this.logger = logger.getSubLogger(
{ name: 'accountRevolver' },
{ accountId: this.config.settings.Id, accountName: this.config.settings.name },
{ accountId: this.config.accountId, accountName: this.config.settings.name },
);
}

async initialise(): Promise<void> {
this.logger.info('Initialising revolver');
this.logger.info(`Initialising revolver for account ${this.config.settings.name}(${this.config.accountId})`);

const activePlugins = Object.keys(this.config.plugins)
.filter((xp) => this.supportedPlugins.indexOf(xp) > -1)
Expand Down
100 changes: 38 additions & 62 deletions lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,61 +3,28 @@ import { promises as fs } from 'fs';
import path = require('node:path');
import yaml from 'js-yaml';
import { Organizations, S3 } from 'aws-sdk';
import { paginateAwsCall, uniqueBy } from './common';
import { deepmerge } from 'deepmerge-ts';

class Settings {
private settings: { [key: string]: any };

constructor() {
this.settings = {};
}

store(settings: { [key: string]: any }) {
this.settings = settings;
}

get(key: string) {
return this.settings[key];
}
}

export const settings = new Settings();
import { paginateAwsCall } from './common';
import merge from 'ts-deepmerge';

export class RevolverConfig {
validateConfig(data: string) {
const config: any = yaml.load(data);
logger.debug('Read Revolver config: %j', config);
if (!Array.isArray(config.accounts.include_list)) {
throw new Error("Invalid configuration. 'include_list' key is either missing or not an array");
if (!Array.isArray(config.accounts.includeList)) {
throw new Error('Invalid configuration: "includeList" key is either missing or not an array');
}
if (!Array.isArray(config.accounts.exclude_list)) {
throw new Error("Invalid configuration. 'exclude_list' key is either missing or not an array");
if (!Array.isArray(config.accounts.excludeList)) {
throw new Error('Invalid configuration: "excludeList" key is either missing or not an array');
}
settings.store(config.settings);
// merge default settings and extract some info
config.organizations = config.organizations.map((r: any) => deepmerge({}, config.defaults, r));
config.accounts.include_list = config.accounts.include_list.map((r: any) => deepmerge(config.defaults, r));
config.accounts.exclude_list = config.accounts.exclude_list.map((r: any) => deepmerge(config.defaults, r));

config.defaults.settings.organizationRoleName = config.defaults.settings.organization_role_name;
config.defaults.settings.revolverRoleName = config.defaults.settings.revolver_role_name;

config.organizations.map((org: any) => {
org.Id = org.account_id;
org.settings.organizationRoleName = org.settings.organization_role_name;
org.settings.revolverRoleName = org.settings.revolver_role_name;
config.organizations.forEach((org: any) => {
org.settings = Object.assign({}, config.defaults.settings, org.settings);
});
config.accounts.include_list.map((acc: any) => {
acc.Id = acc.account_id;
acc.settings.revolverRoleName = acc.settings.revolver_role_name;
});
config.accounts.exclude_list.map((acc: any) => {
acc.Id = acc.account_id;
acc.settings.revolverRoleName = acc.settings.revolver_role_name;

config.accounts.includeList.forEach((account: any) => {
account.settings = Object.assign({}, config.defaults.settings, account.settings);
});

logger.debug('Final Revolver config: %j', config);
logger.debug('Read Revolver config: %j', config);
return config;
}

Expand All @@ -83,11 +50,14 @@ export class RevolverConfig {
const client = new Organizations({ credentials: cr, region: orgsRegion });
const accounts = await paginateAwsCall(client.listAccounts.bind(client), 'Accounts');
accounts.forEach((account) => {
account.accountId = account.Id;
delete account.Id;
account.settings = {
name: account.Name,
region: cr.settings.region,
timezone: cr.settings.timezone,
revolverRoleName: cr.settings.revolver_role_name,
timezoneTag: cr.settings.timezoneTag,
revolverRoleName: cr.settings.revolverRoleName,
};
});
return accounts;
Expand All @@ -99,22 +69,28 @@ export class RevolverConfig {
}

filterAccountsList(orgsAccountsList: any[], config: any) {
logger.info('%d Accounts found on the Organizations listed', orgsAccountsList.length);
logger.info('Getting accounts from include/exclude lists..');
logger.info('%d accounts found on include_list', config.accounts.include_list.length);
logger.info('%d accounts found on exclude_list', config.accounts.exclude_list.length);
const filteredAccountsList = config.accounts.include_list
// concat include_list
.concat(orgsAccountsList)
// delete exclude_list
.filter((xa: any) => !config.accounts.exclude_list.find((xi: any) => xi.Id === xa.Id))
// build assumeRoleArn string, extract account_id and revolver_role_name
.map((account: any) => {
account.settings.assumeRoleArn = `arn:aws:iam::${account.Id}:role/${account.settings.revolverRoleName}`;
return account;
});
logger.info(`${orgsAccountsList.length} Accounts found on the Organizations listed`);
logger.info(`${config.accounts.includeList.length} accounts found on include_list`);
logger.info(`${config.accounts.excludeList.length} accounts found on exclude_list`);
// exclude specified in includeList accounts from the org list
const orgWithoutIncludeList = orgsAccountsList.filter(
(xa: any) =>
!config.accounts.includeList.find(
(xi: any) => xi.accountId === xa.accountId && xi.settings.region === xa.settings.region,
),
);
const accountList = orgWithoutIncludeList.concat(config.accounts.includeList);
// exclude accounts specified in excludeList
const filteredAccountsList = accountList.filter(
(xa: any) => !config.accounts.excludeList.find((xi: any) => xi.accountId === xa.accountId),
);
// build assumeRoleArn string, extract account_id and revolver_role_name
const updatedAccountsList = filteredAccountsList.map((xa: any) => {
const account: any = merge.withOptions({ mergeArrays: false }, xa, config.defaults);
account.settings.assumeRoleArn = `arn:aws:iam::${account.accountId}:role/${account.settings.revolverRoleName}`;
return account;
});

// remove duplicated accounts
return uniqueBy(filteredAccountsList, (account: any) => JSON.stringify([account.Id, account.settings.region]));
return updatedAccountsList;
}
}
30 changes: 15 additions & 15 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@
"license": "MIT",
"dependencies": {
"aws-sdk": "^2.1472.0",
"deepmerge-ts": "^5.1.0",
"js-yaml": "^4.1.0",
"luxon": "^3.4.4",
"proxy-agent": "^6.3.1",
"ts-deepmerge": "^6.2.0",
"tslog": "^4.9.2"
},
"devDependencies": {
Expand Down
4 changes: 2 additions & 2 deletions plugins/pluginInterface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ export abstract class RevolverPlugin {

constructor(accountConfig: any, pluginName: string, pluginConfig: any) {
this.accountConfig = accountConfig.settings;
this.accountId = accountConfig.Id;
this.accountId = accountConfig.accountId;
this.pluginConfig = pluginConfig;
this.pluginConfig.name = pluginName;
this.logger = logger.getSubLogger(
{ name: this.accountConfig.name },
{ accountId: this.accountConfig.Id, accountName: this.accountConfig.name, pluginName },
{ accountId: this.accountId, accountName: this.accountConfig.name, pluginName },
);
this.logger.debug(`Initialising plugin ${this.name} for account ${this.accountConfig.name}`);
}
Expand Down
4 changes: 2 additions & 2 deletions plugins/powercycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ export default class PowerCyclePlugin extends RevolverPlugin {

constructor(accountConfig: any, pluginName: string, pluginConfig: any) {
super(accountConfig, pluginName, pluginConfig);
this.scheduleTagName = this.pluginConfig.availability_tag || 'Schedule';
this.timezoneTagName = this.accountConfig.timezone_tag || 'Timezone';
this.scheduleTagName = this.pluginConfig.availabilityTag || 'Schedule';
this.timezoneTagName = this.accountConfig.timezoneTag || 'Timezone';
this.warningTagName = `Warning${this.scheduleTagName}`;
this.reasonTagName = `Reason${this.scheduleTagName}`;
}
Expand Down
Loading

0 comments on commit d58a4b7

Please sign in to comment.