Skip to content

Instantly share code, notes, and snippets.

@faizanakram99
Created February 3, 2020 06:26
Show Gist options
  • Save faizanakram99/bd05a1b846d4db1e39d8cfc788bd0b86 to your computer and use it in GitHub Desktop.
Save faizanakram99/bd05a1b846d4db1e39d8cfc788bd0b86 to your computer and use it in GitHub Desktop.
<?php
use Aws\Ec2\Ec2Client;
use Aws\ElasticBeanstalk\ElasticBeanstalkClient;
use Aws\ElasticBeanstalk\Exception\ElasticBeanstalkException;
use Aws\Rds\Exception\RdsException;
use Aws\Rds\RdsClient;
require __DIR__.'/vendor/autoload.php';
if (2 !== $argc || !is_numeric($argv[1])) {
die("Usage: {$argv[0]} [office hours, 0 or 1]\n");
}
function restructureTags($tags)
{
return array_column($tags, 'Value', 'Key');
}
function getDesiredEbInstanceType(string $environment, bool $isOfficeHours, string $officeHoursInstanceType): string
{
if ($isOfficeHours) {
return $officeHoursInstanceType;
}
if ('Production' === $environment) {
return 't2.small';
}
return 't2.micro';
}
function handleRdsException($rdsInstance, RdsException $exception)
{
$response = new SimpleXMLElement($exception->getResponse()->getBody());
echo "Failed switching {$rdsInstance['DBInstanceIdentifier']} to type {$rdsInstance['DesiredInstanceType']} due to: {$response->Error->Message}\n";
}
$isOfficeHours = (bool) intval($argv[1]);
$ec2Client = new Ec2Client($clientConfig = [
'region' => 'eu-central-1',
'version' => 'latest',
]);
$ebClient = new ElasticBeanstalkClient($clientConfig);
$rdsClient = new RdsClient($clientConfig);
$instances = array_column(
array_column(
$ec2Client->describeInstances()->get('Reservations'),
'Instances'
),
0
);
$instances = array_map(function ($instance) {
if (isset($instance['Tags'])) {
$instance['Tags'] = restructureTags($instance['Tags']);
if (!isset($instance['Tags']['OfficeHoursInstType'])) {
return $instance;
}
$instance['OfficeHoursInstType'] = $instance['Tags']['OfficeHoursInstType'];
}
return $instance;
}, $instances);
$taggedInstances = array_filter($instances, function ($instance) {
return !empty($instance['OfficeHoursInstType']) && 'terminated' !== $instance['State']['Name'];
});
$taggedInstances = array_map(function ($instance) use ($isOfficeHours) {
$instance['DesiredInstanceType'] = $isOfficeHours
? $instance['OfficeHoursInstType']
: (($instance['EbsOptimized'] ? 't3.' : 't2.').(stristr($instance['Tags']['Name'], 'freebsd') ? 'small' : 'micro'));
return $instance;
}, $taggedInstances);
foreach ($taggedInstances as $instance) {
if ($instance['InstanceType'] === $instance['DesiredInstanceType']) {
if ('stopped' === $instance['State']['Name']) {
echo "Starting instance {$instance['InstanceId']} (currently in state {$instance['State']['Name']})\n";
$result = $ec2Client->startInstances([
'InstanceIds' => [$instance['InstanceId']],
]);
}
} else {
if ('running' === $instance['State']['Name']) {
$environmentIdentifier = [];
if (isset($instance['Tags']['elasticbeanstalk:environment-id'])) {
$environments = $ebClient->describeEnvironments(['EnvironmentIds' => [$instance['Tags']['elasticbeanstalk:environment-id']]])->get('Environments');
if (!is_array($environments) || !count($environments)) {
continue;
}
$environmentIdentifier = array_intersect_key($environments[0], ['ApplicationName' => '', 'EnvironmentName' => '']);
$configuration = $ebClient->describeConfigurationSettings($environmentIdentifier)->get('ConfigurationSettings')[0];
if (!is_array($configuration)) {
continue;
}
$configuredInstanceType = array_values(array_filter($configuration['OptionSettings'], function ($setting) {
return 'aws:ec2:instances' === $setting['Namespace'] && 'InstanceTypes' === $setting['OptionName'];
}))[0]['Value'];
if (!strlen($configuredInstanceType)) {
continue;
}
$desiredInstanceType = getDesiredEbInstanceType(
$environmentIdentifier['EnvironmentName'],
$isOfficeHours,
$officeHoursInstanceType = implode(',', explode(' ', $instance['OfficeHoursInstType']))
);
echo "Updating instance type for app/env {$environmentIdentifier['ApplicationName']} / {$environmentIdentifier['EnvironmentName']} to {$desiredInstanceType}\n";
$optionSettings = [
'OptionSettings' => [
[
'Namespace' => 'aws:ec2:instances',
'OptionName' => 'InstanceTypes',
'Value' => $desiredInstanceType,
],
],
];
try {
$ebClient->updateEnvironment(
array_merge(
$environmentIdentifier,
$optionSettings
)
);
} catch (ElasticBeanstalkException $exception) {
echo $exception->getAwsErrorMessage();
}
} else {
echo "Stopping instance {$instance['InstanceId']} ({$instance['Tags']['Name']})\n";
$result = $ec2Client->stopInstances([
'InstanceIds' => [$instance['InstanceId']],
]);
}
} elseif ('stopped' === $instance['State']['Name']) {
echo "Changing instance {$instance['InstanceId']} to type {$instance['DesiredInstanceType']} and starting\n";
$result = $ec2Client->ModifyInstanceAttribute([
'InstanceId' => $instance['InstanceId'],
'InstanceType' => [
'Value' => $instance['DesiredInstanceType'],
],
]);
$result = $ec2Client->startInstances([
'InstanceIds' => [$instance['InstanceId']],
]);
} else {
echo "Awaiting state change for instance {$instance['InstanceId']}, currently in state {$instance['State']['Name']}\n";
}
}
}
$rdsInstances = $rdsClient->describeDBInstances()->get('DBInstances');
if (file_exists($cacheFile = __DIR__.'/.instance-type-tags.serialized') && filectime($cacheFile) > time() - 600) {
$instanceTypeTags = unserialize(file_get_contents($cacheFile));
} else {
$instanceTypeTags = [];
foreach ($rdsInstances as $rdsInstance) {
$tags = restructureTags($rdsClient->listTagsForResource(['ResourceName' => $rdsInstance['DBInstanceArn']])->get('TagList'));
if (empty($tags['OfficeHoursInstType'])) {
continue;
}
$instanceTypeTags[$rdsInstance['DBInstanceIdentifier']] = $tags['OfficeHoursInstType'];
}
@unlink($cacheFile);
file_put_contents($cacheFile, serialize($instanceTypeTags));
}
foreach ($rdsInstances as $rdsInstance) {
if (!isset($instanceTypeTags[$rdsInstance['DBInstanceIdentifier']])) {
continue;
}
$rdsInstance['DesiredInstanceType'] = $isOfficeHours ? $instanceTypeTags[$rdsInstance['DBInstanceIdentifier']] : 'db.t2.micro';
if ($rdsInstance['DesiredInstanceType'] === $rdsInstance['DBInstanceClass']) {
continue;
}
if ('modifying' === $rdsInstance['DBInstanceStatus']) {
echo "Changes are currently being applied to {$rdsInstance['DBInstanceIdentifier']}\n";
} elseif ('available' !== $rdsInstance['DBInstanceStatus']) {
echo "Skipping change to {$rdsInstance['DBInstanceIdentifier']} due to unavailability\n";
} else {
try {
$rdsClient->modifyDBInstance([
'DBInstanceIdentifier' => $rdsInstance['DBInstanceIdentifier'],
'ApplyImmediately' => true,
'DBInstanceClass' => $rdsInstance['DesiredInstanceType'],
]);
echo "Switching {$rdsInstance['DBInstanceIdentifier']} to type {$rdsInstance['DesiredInstanceType']}\n";
} catch (RdsException $exception) {
if (
!$isOfficeHours &&
'InvalidParameterCombination' === $exception->getAwsErrorCode()
) {
try {
$rdsClient->modifyDBInstance([
'DBInstanceIdentifier' => $rdsInstance['DBInstanceIdentifier'],
'ApplyImmediately' => true,
'DBInstanceClass' => 'db.t2.small',
]);
} catch (RdsException $exception) {
handleRdsException($rdsInstance, $exception);
}
} else {
handleRdsException($rdsInstance, $exception);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment