1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532
|
package autoscaling
import (
"encoding/xml"
"fmt"
"github.com/docker/goamz/aws"
"log"
"net/http"
"net/http/httputil"
"net/url"
"strconv"
"time"
)
const debug = false
var timeNow = time.Now
// AutoScaling contains the details of the AWS region to perform operations against.
type AutoScaling struct {
aws.Auth
aws.Region
}
type xmlErrors struct {
RequestId string `xml:"RequestID"`
Errors []Error `xml:"Errors>Error"`
}
// Error contains pertinent information from the failed operation.
type Error struct {
// HTTP status code (200, 403, ...)
StatusCode int
// AutoScaling error code ("UnsupportedOperation", ...)
Code string
// The human-oriented error message
Message string
RequestId string `xml:"RequestID"`
}
func (err *Error) Error() string {
if err.Code == "" {
return err.Message
}
return fmt.Sprintf("%s (%s)", err.Message, err.Code)
}
// New creates a new AutoScaling
func New(auth aws.Auth, region aws.Region) *AutoScaling {
return &AutoScaling{auth, region}
}
func (as *AutoScaling) query(params map[string]string, resp interface{}) error {
params["Version"] = "2011-01-01"
params["Timestamp"] = timeNow().In(time.UTC).Format(time.RFC3339)
endpoint, err := url.Parse(as.Region.AutoScalingEndpoint)
if err != nil {
return err
}
sign(as.Auth, "GET", endpoint.Path, params, endpoint.Host)
endpoint.RawQuery = multimap(params).Encode()
if debug {
log.Printf("get { %v } -> {\n", endpoint.String())
}
r, err := http.Get(endpoint.String())
if err != nil {
return err
}
defer r.Body.Close()
if debug {
dump, _ := httputil.DumpResponse(r, true)
log.Printf("response:\n")
log.Printf("%v\n}\n", string(dump))
}
if r.StatusCode != 200 {
return buildError(r)
}
err = xml.NewDecoder(r.Body).Decode(resp)
return err
}
func multimap(p map[string]string) url.Values {
q := make(url.Values, len(p))
for k, v := range p {
q[k] = []string{v}
}
return q
}
func makeParams(action string) map[string]string {
params := make(map[string]string)
params["Action"] = action
return params
}
func addParamsList(params map[string]string, label string, ids []string) {
for i, id := range ids {
params[label+"."+strconv.Itoa(i+1)] = id
}
}
func buildError(r *http.Response) error {
errors := xmlErrors{}
xml.NewDecoder(r.Body).Decode(&errors)
var err Error
if len(errors.Errors) > 0 {
err = errors.Errors[0]
}
err.RequestId = errors.RequestId
err.StatusCode = r.StatusCode
if err.Message == "" {
err.Message = r.Status
}
return &err
}
// ----------------------------------------------------------------------------
// Auto Scaling base types and related functions.
type AutoScalingGroup struct {
AutoScalingGroupARN string `xml:"AutoScalingGroupARN"`
AutoScalingGroupName string `xml:"AutoScalingGroupName"`
AvailabilityZones []string `xml:"AvailabilityZones>member"`
CreatedTime string `xml:"CreatedTime"`
DefaultCooldown int64 `xml:"DefaultCooldown"`
DesiredCapacity int64 `xml:"DesiredCapacity"`
HealthCheckGracePeriod int64 `xml:"HealthCheckGracePeriod"`
HealthCheckType string `xml:"HealthCheckType"`
Instances []Instance `xml:"Instances>member"`
LaunchConfigurationName string `xml:"LaunchConfigurationName"`
LoadBalancerNames []string `xml:"LoadBalancerNames>member"`
MaxSize int64 `xml:"MaxSize"`
MinSize int64 `xml:"MinSize"`
TerminationPolicies []string `xml:"TerminationPolicies>member"`
VPCZoneIdentifier string `xml:"VPCZoneIdentifier"`
Tags []Tag `xml:"Tags>member"`
SuspendedProcesses []string `xml:"SuspendedProcesses>member"`
}
type Instance struct {
InstanceId string `xml:"InstanceId"`
HealthStatus string `xml:"HealthStatus"`
AvailabilityZone string `xml:"AvailabilityZone"`
LaunchConfigurationName string `xml:"LaunchConfigurationName"`
LifecycleState string `xml:"LifecycleState"`
}
type LaunchConfiguration struct {
AssociatePublicIpAddress bool `xml:"AssociatePublicIpAddress"`
CreatedTime string `xml:"CreatedTime"`
EbsOptimized bool `xml:"EbsOptimized"`
LaunchConfigurationARN string `xml:"LaunchConfigurationARN"`
LaunchConfigurationName string `xml:"LaunchConfigurationName"`
IamInstanceProfile string `xml:"IamInstanceProfile"`
ImageId string `xml:"ImageId"`
InstanceType string `xml:"InstanceType"`
KernelId string `xml:"KernelId"`
SecurityGroups []string `xml:"SecurityGroups>member"`
KeyName string `xml:"KeyName"`
UserData string `xml:"UserData"`
InstanceMonitoring string `xml:"InstanceMonitoring"`
}
type Tag struct {
Key string `xml:"Key"`
PropagateAtLaunch bool `xml:"PropagateAtLaunch"`
ResourceId string `xml:"ResourceId"`
ResourceType string `xml:"ResourceType"`
Value string `xml:"Value"`
}
// AutoScalingGroupsResp defines the basic response structure.
type AutoScalingGroupsResp struct {
RequestId string `xml:"ResponseMetadata>RequestId"`
AutoScalingGroups []AutoScalingGroup `xml:"DescribeAutoScalingGroupsResult>AutoScalingGroups>member"`
}
// LaunchConfigurationResp defines the basic response structure for launch configuration
// requests
type LaunchConfigurationResp struct {
RequestId string `xml:"ResponseMetadata>RequestId"`
LaunchConfigurations []LaunchConfiguration `xml:"DescribeLaunchConfigurationsResult>LaunchConfigurations>member"`
}
// SimpleResp is the basic response from most actions.
type SimpleResp struct {
XMLName xml.Name
RequestId string `xml:"ResponseMetadata>RequestId"`
}
// CreateLaunchConfigurationResp is returned from the CreateLaunchConfiguration request.
type CreateLaunchConfigurationResp struct {
LaunchConfiguration
RequestId string `xml:"ResponseMetadata>RequestId"`
}
// SetDesiredCapacityRequestParams contains the details for the SetDesiredCapacity action.
type SetDesiredCapacityRequestParams struct {
AutoScalingGroupName string
DesiredCapacity int64
HonorCooldown bool
}
// DescribeAutoScalingGroups returns details about the groups provided in the list. If the list is nil
// information is returned about all the groups in the region.
func (as *AutoScaling) DescribeAutoScalingGroups(groupnames []string) (
resp *AutoScalingGroupsResp, err error) {
params := makeParams("DescribeAutoScalingGroups")
addParamsList(params, "AutoScalingGroupNames.member", groupnames)
resp = &AutoScalingGroupsResp{}
err = as.query(params, resp)
if err != nil {
return nil, err
}
return resp, nil
}
// CreateAutoScalingGroup creates a new autoscaling group.
func (as *AutoScaling) CreateAutoScalingGroup(ag AutoScalingGroup) (
resp *AutoScalingGroupsResp, err error) {
resp = &AutoScalingGroupsResp{}
params := makeParams("CreateAutoScalingGroup")
params["AutoScalingGroupName"] = ag.AutoScalingGroupName
params["MaxSize"] = strconv.FormatInt(ag.MaxSize, 10)
params["MinSize"] = strconv.FormatInt(ag.MinSize, 10)
params["LaunchConfigurationName"] = ag.LaunchConfigurationName
addParamsList(params, "AvailabilityZones.member", ag.AvailabilityZones)
if len(ag.LoadBalancerNames) > 0 {
addParamsList(params, "LoadBalancerNames.member", ag.LoadBalancerNames)
}
if ag.DefaultCooldown > 0 {
params["DefaultCooldown"] = strconv.FormatInt(ag.DefaultCooldown, 10)
}
if ag.DesiredCapacity > 0 {
params["DesiredCapacity"] = strconv.FormatInt(ag.DesiredCapacity, 10)
}
if ag.HealthCheckGracePeriod > 0 {
params["HealthCheckGracePeriod"] = strconv.FormatInt(ag.HealthCheckGracePeriod, 10)
}
if ag.HealthCheckType == "ELB" {
params["HealthCheckType"] = ag.HealthCheckType
}
if len(ag.VPCZoneIdentifier) > 0 {
params["VPCZoneIdentifier"] = ag.VPCZoneIdentifier
}
if len(ag.TerminationPolicies) > 0 {
addParamsList(params, "TerminationPolicies.member", ag.TerminationPolicies)
}
// TODO(JP) : Implement Tags
//if len(ag.Tags) > 0 {
// addParamsList(params, "Tags", ag.Tags)
//}
err = as.query(params, resp)
if err != nil {
return nil, err
}
return resp, nil
}
// DescribeLaunchConfigurations returns details about the launch configurations supplied in
// the list. If the list is nil, information is returned about all launch configurations in the
// region.
func (as *AutoScaling) DescribeLaunchConfigurations(confnames []string) (
resp *LaunchConfigurationResp, err error) {
params := makeParams("DescribeLaunchConfigurations")
addParamsList(params, "LaunchConfigurationNames.member", confnames)
resp = &LaunchConfigurationResp{}
err = as.query(params, resp)
if err != nil {
return nil, err
}
return resp, nil
}
// CreateLaunchConfiguration creates a new launch configuration.
func (as *AutoScaling) CreateLaunchConfiguration(lc LaunchConfiguration) (
resp *CreateLaunchConfigurationResp, err error) {
resp = &CreateLaunchConfigurationResp{}
params := makeParams("CreateLaunchConfiguration")
params["LaunchConfigurationName"] = lc.LaunchConfigurationName
if len(lc.ImageId) > 0 {
params["ImageId"] = lc.ImageId
params["InstanceType"] = lc.InstanceType
}
if len(lc.IamInstanceProfile) > 0 {
params["IamInstanceProfile"] = lc.IamInstanceProfile
}
if lc.AssociatePublicIpAddress {
params["AssociatePublicIpAddress"] = "true"
}
if len(lc.SecurityGroups) > 0 {
addParamsList(params, "SecurityGroups.member", lc.SecurityGroups)
}
if len(lc.KeyName) > 0 {
params["KeyName"] = lc.KeyName
}
if len(lc.KernelId) > 0 {
params["KernelId"] = lc.KernelId
}
if lc.InstanceMonitoring == "false" {
params["InstanceMonitoring.Enabled"] = "false"
}
err = as.query(params, resp)
if err != nil {
return resp, err
}
return resp, nil
}
// SuspendProcesses suspends the processes for the autoscaling group. If no processes are
// provided, all processes are suspended.
//
// If you suspend either of the two primary processes (Launch or Terminate), this can prevent other
// process types from functioning properly.
func (as *AutoScaling) SuspendProcesses(ag AutoScalingGroup, processes []string) (
resp *SimpleResp, err error) {
resp = &SimpleResp{}
params := makeParams("SuspendProcesses")
params["AutoScalingGroupName"] = ag.AutoScalingGroupName
if len(processes) > 0 {
addParamsList(params, "ScalingProcesses.member", processes)
}
err = as.query(params, resp)
if err != nil {
return nil, err
}
return resp, nil
}
// ResumeProcesses resumes the scaling processes for the scaling group. If no processes are
// provided, all processes are resumed.
func (as *AutoScaling) ResumeProcesses(ag AutoScalingGroup, processes []string) (
resp *SimpleResp, err error) {
resp = &SimpleResp{}
params := makeParams("ResumeProcesses")
params["AutoScalingGroupName"] = ag.AutoScalingGroupName
if len(processes) > 0 {
addParamsList(params, "ScalingProcesses.member", processes)
}
err = as.query(params, resp)
if err != nil {
return nil, err
}
return resp, nil
}
// UpdateAutoScalingGroup updates the scaling group.
//
// To update an auto scaling group with a launch configuration that has the InstanceMonitoring
// flag set to False, you must first ensure that collection of group metrics is disabled.
// Otherwise calls to UpdateAutoScalingGroup will fail.
func (as *AutoScaling) UpdateAutoScalingGroup(ag AutoScalingGroup) (resp *SimpleResp, err error) {
resp = &SimpleResp{}
params := makeParams("UpdateAutoScalingGroup")
params["AutoScalingGroupName"] = ag.AutoScalingGroupName
addParamsList(params, "AvailabilityZones.member", ag.AvailabilityZones)
if ag.DefaultCooldown > 0 {
params["DefaultCooldown"] = strconv.FormatInt(ag.DefaultCooldown, 10)
}
params["DesiredCapacity"] = strconv.FormatInt(ag.DesiredCapacity, 10)
if ag.HealthCheckGracePeriod > 0 {
params["HealthCheckGracePeriod"] = strconv.FormatInt(ag.HealthCheckGracePeriod, 10)
}
if ag.HealthCheckType == "ELB" {
params["HealthCheckType"] = ag.HealthCheckType
}
params["LaunchConfigurationName"] = ag.LaunchConfigurationName
if ag.MaxSize > 0 {
params["MaxSize"] = strconv.FormatInt(ag.MaxSize, 10)
}
if ag.MinSize > 0 {
params["MinSize"] = strconv.FormatInt(ag.MinSize, 10)
}
if len(ag.TerminationPolicies) > 0 {
addParamsList(params, "TerminationPolicies.member", ag.TerminationPolicies)
}
if len(ag.VPCZoneIdentifier) > 0 {
params["VPCZoneIdentifier"] = ag.VPCZoneIdentifier
}
err = as.query(params, resp)
if err != nil {
return nil, err
}
return resp, nil
}
// SetDesiredCapacity changes the DesiredCapacity of an AutoScaling group.
func (as *AutoScaling) SetDesiredCapacity(rp SetDesiredCapacityRequestParams) (resp *SimpleResp, err error) {
resp = &SimpleResp{}
params := makeParams("SetDesiredCapacity")
params["AutoScalingGroupName"] = rp.AutoScalingGroupName
params["DesiredCapacity"] = strconv.FormatInt(rp.DesiredCapacity, 10)
if rp.HonorCooldown {
params["HonorCooldown"] = "true"
}
err = as.query(params, resp)
if err != nil {
return nil, err
}
return resp, nil
}
// ----------------------------------------------------------------------------
// Autoscaling scheduled actions types and methods
// ScheduledUpdateGroupAction contains the information to be used in a scheduled update to an
// AutoScalingGroup
type ScheduledUpdateGroupAction struct {
AutoScalingGroupName string `xml:"AutoScalingGroupName"`
DesiredCapacity int64 `xml:"DesiredCapacity"`
EndTime string `xml:"EndTime"`
MaxSize int64 `xml:"MaxSize"`
MinSize int64 `xml:"MinSize"`
Recurrence string `xml:"Recurrence"`
ScheduledActionARN string `xml:"ScheduledActionARN"`
ScheduledActionName string `xml:"ScheduledActionName"`
StartTime string `xml:"StartTime"`
}
// DescribeScheduledActionsResult contains the response from a DescribeScheduledActions.
type DescribeScheduledActionsResult struct {
NextToken string `xml:"NextToken"`
ScheduledUpdateGroupActions []ScheduledUpdateGroupAction `xml:"DescribeScheduledActions>ScheduledUpdateGroups>member"`
}
// ScheduledActionsRequestParams contains the items that can be specified when making
// a ScheduledActions request
type ScheduledActionsRequestParams struct {
AutoScalingGroupName string
EndTime string
MaxRecords int64
ScheduledActionNames []string
StartTime string
}
// PutScheduledActionRequestParams contains the details of the ScheduledAction to be added.
type PutScheduledActionRequestParams struct {
AutoScalingGroupName string
DesiredCapacity int64
EndTime string
MaxSize int64
MinSize int64
Recurrence string
ScheduledActionName string
StartTime string
}
// DeleteScheduledActionRequestParams contains the details of the scheduled action to delete.
type DeleteScheduledActionRequestParams struct {
AutoScalingGroupName string
ScheduledActionName string
}
// DescribeScheduledActions returns a list of the current scheduled actions. If the
// AutoScalingGroup name is provided it will list all the scheduled actions for that group.
func (as *AutoScaling) DescribeScheduledActions(rp ScheduledActionsRequestParams) (
resp *DescribeScheduledActionsResult, err error) {
resp = &DescribeScheduledActionsResult{}
params := makeParams("DescribeScheduledActions")
if rp.AutoScalingGroupName != "" {
params["AutoScalingGroupName"] = rp.AutoScalingGroupName
}
if rp.StartTime != "" {
params["StartTime"] = rp.StartTime
}
if rp.EndTime != "" {
params["EndTime"] = rp.EndTime
}
if rp.MaxRecords > 0 {
params["MaxRecords"] = strconv.FormatInt(rp.MaxRecords, 10)
}
if len(rp.ScheduledActionNames) > 0 {
addParamsList(params, "ScheduledActionNames.member", rp.ScheduledActionNames)
}
err = as.query(params, resp)
if err != nil {
return nil, err
}
return resp, nil
}
// PutScheduledUpdateGroupAction creates or updates a scheduled scaling action for an
// AutoScaling group. Scheduled actions can be made up to thirty days in advance. When updating
// a scheduled scaling action, if you leave a parameter unspecified, the corresponding value
// remains unchanged in the affected AutoScaling group.
//
// Auto Scaling supports the date and time expressed in "YYYY-MM-DDThh:mm:ssZ" format in UTC/GMT
// only.
func (as *AutoScaling) PutScheduledUpdateGroupAction(rp PutScheduledActionRequestParams) (
resp *SimpleResp, err error) {
resp = &SimpleResp{}
params := makeParams("PutScheduledUpdateGroupAction")
params["AutoScalingGroupName"] = rp.AutoScalingGroupName
params["ScheduledActionName"] = rp.ScheduledActionName
if len(rp.EndTime) > 0 {
params["EndTime"] = rp.EndTime
}
if len(rp.StartTime) > 0 {
params["StartTime"] = rp.StartTime
}
if rp.MaxSize > 0 {
params["MaxSize"] = strconv.FormatInt(rp.MaxSize, 10)
}
if rp.MinSize > 0 {
params["MinSize"] = strconv.FormatInt(rp.MinSize, 10)
}
if len(rp.Recurrence) > 0 {
params["Recurrence"] = rp.Recurrence
}
err = as.query(params, resp)
if err != nil {
return nil, err
}
return resp, nil
}
// DeleteScheduledAction deletes a scheduled action.
func (as *AutoScaling) DeleteScheduledAction(rp DeleteScheduledActionRequestParams) (
resp *SimpleResp, err error) {
resp = &SimpleResp{}
params := makeParams("DeleteScheduledAction")
params["AutoScalingGroupName"] = rp.AutoScalingGroupName
params["ScheduledActionName"] = rp.ScheduledActionName
err = as.query(params, resp)
if err != nil {
return nil, err
}
return resp, nil
}
|