Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/eypp 1211.fog aws policies support #9

Open
wants to merge 9 commits into
base: pre-release
Choose a base branch
from
11 changes: 9 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,19 @@ matrix:
gemfile: gemfiles/Gemfile-edge
- rvm: 2.3.0
gemfile: Gemfile
- rvm: jruby-18mode
gemfile: gemfiles/Gemfile-ruby-1.8.7
- rvm: jruby-19mode
gemfile: gemfiles/Gemfile-ruby-1.9
- rvm: jruby-head
gemfile: Gemfile
allow_failures:
- rvm: jruby-head
gemfile: Gemfile
- rvm: 2.2.0
gemfile: gemfiles/Gemfile-edge
- rvm: 2.3.0
gemfile: gemfiles/Gemfile-edge
- rvm: 2.1.1
gemfile: gemfiles/Gemfile-edge
notifications:
email: false
irc:
Expand All @@ -47,6 +52,8 @@ notifications:
on_success: always
on_failure: always
use_notice: false
before_install:
- gem update bundler
env:
global:
secure: LlDKdKSRo3sEjQ55XesbOXhKZ3RrOtqoD1ZL8Wx39K3iVzeEV3Kc8HjDfEvo7R4pOc3BMTNJcputklVEPN0FkWGN7Py+OEtbHj3IZl0MX+KEWNk0gU+4+sgPrL1eXUQyMUSkCrBsKg08rPel4KMYUOXbtnLyUU9PDbBwm4LJYOc=
Expand Down
2 changes: 1 addition & 1 deletion fog-aws.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
spec.require_paths = ["lib"]

spec.add_development_dependency 'bundler', '~> 1.6'
spec.add_development_dependency 'bundler', '~> 1.15'
spec.add_development_dependency 'rake', '~> 10.0'
spec.add_development_dependency 'shindo', '~> 0.3'
spec.add_development_dependency 'rubyzip', '~> 0.9.9'
Expand Down
107 changes: 107 additions & 0 deletions lib/fog/aws.rb
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,113 @@ module AWS
service(:sts, 'STS')
service(:support, 'Support')

# Transforms hash keys according to the passed mapping or given block
#
# ==== Parameters
# * object<~Hash> - A hash to apply transformation to
# * mappings<~Hash> - A hash of mappings for keys. Keys of the mappings object should match desired keys of the
# object which will be transformed. If object contains values that are hashes or arrays of hashes which should
# be transformed as well mappings object's value should be configured with a specific form, for example:
# {
# :key => {
# 'Key' => {
# :nested_key => 'NestedKey'
# }
# }
# }
# This form will transform object's key :key to 'Key'
# and transform corresponding value: if it's a hash then its key :nested_key will be transformed into 'NestedKey'
# if it's an array of hashes, each hash element of the array will have it's key :nested_key transformed into 'NestedKey'
# * block<~Proc> - block which is applied if mappings object does not contain key. Block receives key as it's argument
# and should return new key as the one that will be used to replace the original one
# ==== Returns
# * object<~Hash> - hash containing transformed keys
#
def self.map_keys(object, mappings = nil, &block)
case object
when ::Hash
object.reduce({}) do |acc, (key, val)|
mapping = mappings[key] if mappings
new_key, new_value = begin
if mapping
case mapping
when ::Hash
mapped_key = mapping.keys[0]
[mapped_key, map_keys(val, mapping[mapped_key], &block)]
else
[mapping, map_keys(val, &block)]
end
else
mapped_value = map_keys(val, &block)
if block_given?
[block.call(key), mapped_value]
else
[key, mapped_value]
end
end
end
acc[new_key] = new_value
acc
end
when ::Array
object.map { |item| map_keys(item, mappings, &block) }
else
object
end
end

# Maps object keys to aws compatible: underscore keys are transformed in camel case strings
# with capitalized first word (aka :ab_cd transforms into AbCd). Already compatible keys remain the same
#
# ==== Parameters
# * object<~Hash> - A hash to apply transformation to
# * mappings<~Hash> - An optional hash of mappings for keys
# ==== Returns
# * object<~Hash> - hash containing transformed keys
#
def self.map_to_aws(object, mappings = nil)
map_keys(object, mappings) do |key|
words = key.to_s.split('_')
if words.length > 1
words.collect(&:capitalize).join
else
words[0].split(/(?=[A-Z])/).collect(&:capitalize).join
end
end
end

# Maps object keys from aws to ruby compatible form aka snake case symbols
#
# ==== Parameters
# * object<~Hash> - A hash to apply transformation to
# * mappings<~Hash> - An optional hash of mappings for keys
# ==== Returns
# * object<~Hash> - hash containing transformed keys
#
def self.map_from_aws(object, mappings = nil)
map_keys(object, mappings) { |key| key.to_s.split(/(?=[A-Z])/).join('_').downcase.to_sym }
end

# Helper function to invert mappings: recursively replaces mapping keys and values.
#
# ==== Parameters
# * mappings<~Hash> - mappings hash
# ==== Returns
# * object<~Hash> - inverted mappings
#
def self.invert_mappings(mappings)
mappings.reduce({}) do |acc, (key, val)|
mapped_key, mapped_value = case val
when ::Hash
[val.keys[0], val.values[0]]
else
[val, nil]
end
acc[mapped_key] = mapped_value ? { key => invert_mappings(mapped_value) } : key
acc
end
end

def self.indexed_param(key, values)
params = {}
unless key.include?('%d')
Expand Down
4 changes: 4 additions & 0 deletions lib/fog/aws/models/auto_scaling/group.rb
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ def enable_metrics_collection(granularity = '1Minute', metrics = {})
reload
end

def policies
service.policies.all('AutoScalingGroupName' => id)
end

def instances
Fog::AWS::AutoScaling::Instances.new(:service => service).load(attributes[:instances])
end
Expand Down
160 changes: 143 additions & 17 deletions lib/fog/aws/models/auto_scaling/policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,109 @@ module Fog
module AWS
class AutoScaling
class Policy < Fog::Model
identity :id, :aliases => 'PolicyName'
attribute :arn, :aliases => 'PolicyARN'
attribute :adjustment_type, :aliases => 'AdjustmentType'
attribute :alarms, :aliases => 'Alarms'
attribute :auto_scaling_group_name, :aliases => 'AutoScalingGroupName'
attribute :cooldown, :aliases => 'Cooldown'
attribute :min_adjustment_step, :aliases => 'MinAdjustmentStep'
attribute :scaling_adjustment, :aliases => 'ScalingAdjustment'
identity :id, :aliases => 'PolicyName'
attribute :arn, :aliases => 'PolicyARN'
attribute :type, :aliases => 'PolicyType'
attribute :adjustment_type, :aliases => 'AdjustmentType'
attribute :scaling_adjustment, :aliases => 'ScalingAdjustment'
attribute :step_adjustments, :aliases => 'StepAdjustments'
attribute :target_tracking_configuration, :aliases => 'TargetTrackingConfiguration'
attribute :alarms, :aliases => 'Alarms'
attribute :auto_scaling_group_name, :aliases => 'AutoScalingGroupName'
attribute :cooldown, :aliases => 'Cooldown'
attribute :estimated_instance_warmup, :aliases => 'EstimatedInstanceWarmup'
attribute :metric_aggregation_type, :aliases => 'MetricAggregationType'
attribute :min_adjustment_magnitude, :aliases => 'MinAdjustmentMagnitude'
attribute :min_adjustment_step, :aliases => 'MinAdjustmentStep'

STEP_ADJUSTMENTS_MAPPING = {
:metric_interval_lower_bound => 'MetricIntervalLowerBound',
:metric_interval_upper_bound => 'MetricIntervalUpperBound',
:scaling_adjustment => 'ScalingAdjustment'
}.freeze

TARGET_TRACKING_MAPPING = {
:customized_metric_specification => {
'CustomizedMetricSpecification' => {
:metric_name => 'MetricName',
:namespace => 'Namespace',
:statistics => 'Statistics',
:unit => 'Unit',
:dimensions => {
'Dimensions' => {
:name => 'Name',
:value => 'Value'
}
}
}
},
:disable_scale_in => 'DisableScaleIn',
:target_value => 'TargetValue',
:predefined_metric_specification => {
'PredefinedMetricSpecification' => {
:predefined_metric_type => 'PredefinedMetricType',
:resource_label => 'ResourceLabel'
}
}
}.freeze

# Returns attribute names specific for different policy types
#
# ==== Parameters
# * policy_type<~String> - type of the auto scaling policy
#
# ==== Returns
# * options<~Array> Array of string containing policy specific options
#
def self.preserve_options(policy_type)
case policy_type
when 'StepScaling'
%w(EstimatedInstanceWarmup PolicyType MinAdjustmentMagnitude MetricAggregationType AdjustmentType StepAdjustments)
when 'TargetTrackingScaling'
%w(EstimatedInstanceWarmup PolicyType TargetTrackingConfiguration)
else
%w(AdjustmentType ScalingAdjustment PolicyType Cooldown MinAdjustmentMagnitude MinAdjustmentStep)
end
end

def initialize(attributes)
attributes['AdjustmentType'] ||= 'ChangeInCapacity'
attributes['ScalingAdjustment'] ||= 1
super
case self.type
when 'StepScaling'
prepare_step_policy
when 'TargetTrackingScaling'
prepare_target_policy
else
prepare_simple_policy
end
end

def update(attributes)
requires :id
merge_attributes(attributes)
save
end

# TODO: implement #alarms
# TODO: implement #auto_scaling_group

def auto_scaling_group
service.groups.get(self.auto_scaling_group_name)
end

def save
requires :id
requires :adjustment_type
requires :auto_scaling_group_name
requires :scaling_adjustment
type_requirements

options = Hash[self.class.aliases.map { |key, value| [key, send(value)] }]
options.delete_if { |key, value| value.nil? }
if options['TargetTrackingConfiguration']
options['TargetTrackingConfiguration'] = Fog::AWS.map_to_aws(options['TargetTrackingConfiguration'], TARGET_TRACKING_MAPPING)
end
if options['StepAdjustments']
options['StepAdjustments'] = Fog::AWS.map_to_aws(options['StepAdjustments'], STEP_ADJUSTMENTS_MAPPING)
end
options_keys = self.class.preserve_options(self.type)
options.delete_if { |key, value| value.nil? || !options_keys.include?(key) }

service.put_scaling_policy(adjustment_type, auto_scaling_group_name, id, scaling_adjustment, options)
service.put_scaling_policy(auto_scaling_group_name, id, options)
reload
end

Expand All @@ -38,6 +113,57 @@ def destroy
requires :auto_scaling_group_name
service.delete_policy(auto_scaling_group_name, id)
end

def reload
requires :identity, :auto_scaling_group_name

data = begin
collection.get(identity, auto_scaling_group_name)
rescue Excon::Errors::SocketError
nil
end

return unless data

new_attributes = data.attributes
merge_attributes(new_attributes)
self
end

private

def prepare_simple_policy
self.adjustment_type ||= 'ChangeInCapacity'
self.scaling_adjustment ||= 1
end

def prepare_target_policy
# do we need default tracking configuration or should we just allow it to fail?
if target_tracking_configuration
self.target_tracking_configuration = Fog::AWS.map_from_aws(target_tracking_configuration, TARGET_TRACKING_MAPPING)
end
end

def prepare_step_policy
# do we need any default scaling steps or should we just allow it to fail?
self.adjustment_type ||= 'ChangeInCapacity'
if step_adjustments
self.step_adjustments = Fog::AWS.map_from_aws(step_adjustments, STEP_ADJUSTMENTS_MAPPING)
end
end

def type_requirements
requires :id
requires :auto_scaling_group_name
case self.type
when 'StepScaling'
requires :step_adjustments
when 'TargetTrackingScaling'
requires :target_tracking_configuration
else
requires :scaling_adjustment
end
end
end
end
end
Expand Down
6 changes: 6 additions & 0 deletions lib/fog/aws/models/cloud_watch/alarm.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ def initialize(attributes)
super
end

def update(attributes)
requires :id
merge_attributes(attributes)
save
end

def save
requires :id
requires :comparison_operator
Expand Down
Loading