Skip to content

Instantly share code, notes, and snippets.

@ezeeetm
Created July 15, 2015 20:10
Show Gist options
  • Save ezeeetm/913f3f06c4429c6f4ed5 to your computer and use it in GitHub Desktop.
Save ezeeetm/913f3f06c4429c6f4ed5 to your computer and use it in GitHub Desktop.
AWS Instance Scheduler
#########################################################################################################################
# Script name: instanceScheduler.ps1
# Version: v0.1
# Date: 08Nov2014
# Author: Eric Miller
# Purpose: start/stop ec2 instances on a schedule, with retries and SNS notifications on WARNING/FATAL events
# Usage: ./instanceScheduler.ps1
# Dependencies: aws.ps1, logging.ps1
# Notes:
# In order to give the retries sufficient time to process,
# do not schedule starts/stops closer together than $maxRetries * $retryIntervals seconds
# http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-ItemType-InstanceStateType.html
# Valid values: 0 (pending) | 16 (running) | 32 (shutting-down) | 48 (terminated) | 64 (stopping) | 80 (stopped)
#########################################################################################################################
# Initialize logging and AWS session, see dependencies above
# aws.ps1 uses specific IAM role for this script, redact access/secret keys or omit this file in commits
. .\logging.ps1
. .\aws.ps1
# config
$scheduleFile = '.\schedule.txt'
$debug = $false #toggle $true/$false to turn console debugging on/off
$skipDays = @("Saturday","Sunday")
$chkInterval = 15 #secs
$retryInterval = 60 #secs
$maxRetries = 5
$pendingChanges = @()
# get instances/schedules from $scheduleFile
function getSchedule
{
$schedule = Get-Content $scheduleFile
$instances = @()
foreach ($line in $schedule)
{
if ($line.StartsWith("#") -or ($line.length -eq 0)){continue} # skip comments and blank lines
try
{
$line = $line.split(",")
$instance = New-Object PSObject
Add-Member -InputObject $instance -MemberType NoteProperty -Name id -Value $line[0].Trim().ToLower()
Add-Member -InputObject $instance -MemberType NoteProperty -Name startTime -Value $line[1].Trim()
Add-Member -InputObject $instance -MemberType NoteProperty -Name stopTime -Value $line[2].Trim()
Add-Member -InputObject $instance -MemberType NoteProperty -Name currState -Value $null
Add-Member -InputObject $instance -MemberType NoteProperty -Name desState -Value $null
Add-Member -InputObject $instance -MemberType NoteProperty -Name retryCredits -Value $null
$instances += $instance
}
catch
{
log FATAL "check the schedule file for correct syntax, script terminating"
exit
}
}
return $instances
}
# main loop
while ($true)
{
# load new schedule whenever script starts or schedule file is changed
if(($((gci $scheduleFile).LastWriteTime) -gt $schedLastWrite) -or (!$schedLastWrite))
{
$instanceList = getSchedule
$schedLastWrite = (gci $scheduleFile).LastWriteTime
log INFO "script started or schedule modified, reading new schedule"
if ($debug)
{
write-host "DEBUG: schedLastWrite- $schedLastWrite`n" -foregroundcolor yellow
write-host "DEBUG: instances from getSchedule function:" -foregroundcolor yellow
foreach ($instance in $instanceList){write-host $instance -foregroundcolor yellow}
}
}
# if today is not a skip day, rock on
$dow = (Get-Date).DayOfWeek.ToString()
if (!$skipDays.contains($dow))
{
# add instance objects with a start or stop scheduled for this minute to $pendingChanges array
$now = Get-Date -format "HHmm"
foreach($instance in $instanceList)
{
if($instance.startTime -eq $now)
{
log INFO "added to pending: $($instance.id) / desired state *running* @ $($instance.startTime) / $maxRetries retries"
$instance.desState = "running"
$instance.retryCredits = $maxRetries
$pendingChanges += $instance
}
if($instance.stopTime -eq $now)
{
log INFO "added to pending: $($instance.id) / desired state *stopped* @ $($instance.stopTime) / $maxRetries retries"
$instance.desState = "stopped"
$instance.retryCredits = $maxRetries
$pendingChanges += $instance
}
}
}
# refresh instance states and remove any whose current state matches desired state
if ($pendingChanges)
{
foreach ($instance in $pendingChanges)
{
$currInstance = Get-EC2Instance $instance.id
$instance.currState = $currInstance.Instances.state.name.Value
if ($instance.currState -eq $instance.desState)
{
log INFO "removed from pending: $($instance.id), desired state *$($instance.desState)* achieved"
$pendingChanges = $pendingChanges -ne $instance
}
}
}
# if $pendingChanges still isn't empty, do stuff
if ($pendingChanges)
{
foreach($instance in $pendingChanges)
{
if(($instance.desState -eq "stopped") -and ($instance.currState -eq "running")) #do nothing for pending, shutting-down, terminated, or stopping
{
log INFO "stopping $($instance.id)"
try {$r = stop-ec2instance $instance.id} catch{log WARNING "problem with API call to stop $($instance.id)"}
log INFO "ec2 response: instance = $($r.InstanceId), currentState = $($r.CurrentState.Name.Value), previousState = $($r.PreviousState.Name.Value)"
}
if(($instance.desState -eq "running") -and ($instance.currState -eq "stopped")) #do nothing for pending, shutting-down, terminated, or stopping
{
log INFO "starting $($instance.id)"
try {$r = start-ec2instance $instance.id} catch {log WARNING "problem with API call to start $($instance.id)"}
log INFO "ec2 response: instance = $($r.InstanceId), currentState = $($r.CurrentState.Name.Value), previousState = $($r.PreviousState.Name.Value)"
}
# decrement $retryCredits.
# If instances hit $maxRetries, drop and send SNS notification (in logging)
$instance.retryCredits -= 1
if ($instance.retryCredits -eq 0)
{
log WARNING "removed from pending: ($instance.id), $maxRetries failed retries"
$pendingChanges = $pendingChanges -ne $instance
}
}
start-sleep -s $retryInterval
}
start-sleep -s $chkInterval
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment