Created
February 3, 2020 06:26
-
-
Save faizanakram99/bd05a1b846d4db1e39d8cfc788bd0b86 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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