rivet 1.4.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,35 +1,46 @@
1
+ # encoding: UTF-8
2
+
1
3
  module Rivet
2
4
  class Autoscale
3
5
 
4
- REQUIRED_FIELDS = [:min_size, :max_size, :launch_configuration, :availability_zones]
5
-
6
- attr_reader :min_size, :max_size, :name, :launch_configuration
7
- attr_reader :availability_zones, :tags
8
-
9
- def initialize(name, definition)
10
- @name = name
11
- @min_size = definition['min_size']
12
- @max_size = definition['max_size']
13
-
14
- if definition.has_key? 'tags'
15
- definition['tags'].each do |t|
16
- unless t.has_key? 'propagate_at_launch'
17
- t['propagate_at_launch'] = true
18
- end
6
+ OPTIONS = [
7
+ :availability_zones,
8
+ :default_cooldown,
9
+ :desired_capacity,
10
+ :health_check_grace_period,
11
+ :health_check_type,
12
+ :launch_configuration,
13
+ :load_balancers,
14
+ :max_size,
15
+ :min_size,
16
+ :placement_group,
17
+ :subnets,
18
+ :tags,
19
+ :termination_policies
20
+ ].each { |a| attr_reader a }
21
+
22
+ REQUIRED_OPTIONS = [
23
+ :availability_zones,
24
+ :launch_configuration,
25
+ :max_size,
26
+ :min_size
27
+ ]
28
+
29
+ attr_reader :name
30
+
31
+ def initialize(config)
32
+ @name = config.name
33
+ @remote_group = AwsAutoscaleWrapper.new(@name)
34
+ @launch_config = LaunchConfig.new(config)
35
+
36
+ OPTIONS.each do |o|
37
+ if config.respond_to?(o)
38
+ instance_variable_set("@#{o}", config.send(o))
19
39
  end
20
- @tags = definition['tags']
21
- else
22
- @tags = []
23
- end
24
- @launch_config = LaunchConfig.new(definition)
25
-
26
- # Normalizing zones to match what the SDK expects, E.G. "<region><zone>"
27
- @availability_zones = definition['availability_zones'].map! do |zone|
28
- definition['region'] + zone
29
40
  end
30
41
 
31
- # The launch_configuration attr exists for convinence since that is what
32
- # the aws SDK refers to the launch configuration name as for autoscaling
42
+ # The launch_configuration attr exists because that is what
43
+ # the aws SDK refers to the launch configuration name as.
33
44
  @launch_configuration = @launch_config.identity
34
45
  end
35
46
 
@@ -46,110 +57,87 @@ module Rivet
46
57
  end
47
58
 
48
59
  def show_differences(level = 'info')
49
-
50
- Rivet::Log.write(level, "Remote and local defintions match") unless differences?
51
-
60
+ Rivet::Log.write(level, 'Remote and local match') unless differences?
52
61
  differences.each_pair do |attr, values|
53
62
  Rivet::Log.write(level, "#{attr}:")
54
- Rivet::Log.write(level, " remote: #{values['remote']}")
55
- Rivet::Log.write(level, " local: #{values['local']}")
63
+ Rivet::Log.write(level, " remote: #{values[:remote]}")
64
+ Rivet::Log.write(level, " local: #{values[:local]}")
56
65
  end
66
+ Rivet::Log.write('debug', @launch_config.user_data)
57
67
  end
58
68
 
59
69
  def sync
60
70
  if differences?
61
- Rivet::Log.info("Syncing autoscale group changes to AWS for #{@name}")
71
+ Rivet::Log.info "Syncing autoscale group changes to AWS for #{@name}"
62
72
  autoscale = AWS::AutoScaling.new
63
73
  group = autoscale.groups[@name]
64
74
 
65
75
  @launch_config.save
66
76
  create(options) unless group.exists?
67
77
 
68
- Rivet::Log.debug("Updating autoscaling group with the follow options")
69
- Rivet::Log.debug(options.inspect)
78
+ Rivet::Log.debug 'Updating autoscaling group with the follow options'
79
+ Rivet::Log.debug options.inspect
70
80
 
81
+ # It's easier to just delete all the tags if there are changes and apply
82
+ # new ones, than ferret out exactly which ones should be removed.
83
+ if differences.has_key? :tags
84
+ group.delete_all_tags
85
+ end
71
86
  group.update(options)
87
+
72
88
  else
73
- Rivet::Log.info("No autoscale differences to sync to AWS for #{@name}.")
89
+ Rivet::Log.info "No autoscale differences to sync to AWS for #{@name}."
74
90
  end
75
91
  end
76
92
 
77
93
  protected
78
94
 
79
- def get_update_options
80
- options = {}
81
- differences.each_pair do |attribute, values|
82
- options[attribute.to_sym] = values['local']
83
- end
84
-
85
- REQUIRED_FIELDS.each do |field|
86
- unless options.has_key? field
87
- options[field] = self.send(field)
88
- end
89
- end
90
- options
91
- end
92
-
93
95
  def get_differences
94
- remote = get_remote
95
96
  differences = {}
96
- [:min_size, :max_size, :launch_configuration, :tags].each do |a|
97
- if remote[a.to_s] != self.send(a)
98
- differences[a.to_s] = { 'remote' => remote[a.to_s], 'local' => self.send(a) }
99
- end
100
- end
101
97
 
102
- if remote['availability_zones'] != availability_zones
103
- differences['availability_zones'] = { 'remote' => remote['availability_zones'], 'local' => availability_zones }
104
- end if remote.has_key? 'availability_zones'
98
+ OPTIONS.each do |o|
99
+ remote_value = @remote_group.send(o)
100
+ local_value = send(o)
105
101
 
102
+ if (remote_value != local_value)
103
+ differences[o] = { :local => send(o), :remote => @remote_group.send(o) }
104
+ end
105
+ end
106
106
  differences
107
107
  end
108
108
 
109
- def get_remote
110
- autoscale = AWS::AutoScaling.new
111
- remote_group = autoscale.groups[@name]
112
- if remote_group.exists?
109
+ def get_update_options
110
+ options = {}
113
111
 
114
- remote_hash = [:min_size, :max_size].inject({}) do |accum, attr|
115
- accum[attr.to_s] = remote_group.send attr
116
- accum
117
- end
112
+ OPTIONS.each do |field|
113
+ local_value = self.send(field)
114
+ options[field] = local_value unless local_value.nil?
115
+ end
118
116
 
119
- # {:resource_id=>"venus", :propagate_at_launch=>true, :value=>"Venus", :key=>"Name", :resource_type=>"auto-scaling-group"}
120
- remote_hash['tags'] = remote_group.tags.to_a.inject([]) do |tags, current|
121
- tags << normalize_tag(current)
117
+ REQUIRED_OPTIONS.each do |field|
118
+ unless options.has_key? field
119
+ options[field] = self.send(field)
122
120
  end
123
-
124
- remote_hash['launch_configuration'] = remote_group.launch_configuration_name
125
-
126
- # Normalize their AWS::Core::Data::List to a sorted array
127
- remote_hash['availability_zones'] = remote_group.availability_zone_names.to_a.sort
128
-
129
- remote_hash
130
- else
131
- {}
132
121
  end
122
+ options
133
123
  end
134
124
 
135
125
  def create(options)
126
+
127
+ # When creating an autoscaling group passing empty arrays for subnets
128
+ # or some other fields can cause it to barf. Remove them first.
129
+ options.delete_if { |k, v| v.respond_to?(:'empty?') && v.empty? }
130
+
131
+ Rivet::Log.debug "Creating Autoscaling group #{@name} with the following options"
132
+ Rivet::Log.debug options
133
+
136
134
  autoscale = AWS::AutoScaling.new
137
135
  if autoscale.groups[@name].exists?
138
- raise "Cannot create AutoScaling #{@name} group it already exists!"
136
+ fail "Cannot create AutoScaling #{@name} group it already exists!"
139
137
  else
140
138
  autoscale.groups.create(@name, options)
141
139
  end
142
140
  end
143
141
 
144
- def normalize_tag(tag)
145
- normalized_tag = {}
146
- tag.each_pair do |k, v|
147
- unless (k == :resource_id || k == :resource_type)
148
- normalized_tag[k.to_s] = v
149
- end
150
- end
151
- normalized_tag
152
- end
153
-
154
142
  end
155
143
  end
@@ -0,0 +1,82 @@
1
+ # encoding: UTF-8
2
+
3
+ module Rivet
4
+ class AwsAutoscaleWrapper
5
+
6
+ OPTIONS = [
7
+ :availability_zones,
8
+ :default_cooldown,
9
+ :desired_capacity,
10
+ :health_check_grace_period,
11
+ :health_check_type,
12
+ :launch_configuration,
13
+ :load_balancers,
14
+ :max_size,
15
+ :min_size,
16
+ :placement_group,
17
+ :subnets,
18
+ :tags,
19
+ :termination_policies
20
+ ].each { |a| attr_reader a }
21
+
22
+ attr_reader :name
23
+
24
+ def initialize(name)
25
+ Rivet::Log.debug "Initializing AWS Autoscale Wrapper for #{name}"
26
+ @name = name
27
+ @group = AWS::AutoScaling.new.groups[@name]
28
+
29
+ if @group.exists?
30
+ OPTIONS.each do |o|
31
+ normalize_method = "normalize_#{o}".to_sym
32
+ if respond_to?(normalize_method)
33
+ Rivet::Log.debug "Calling #{normalize_method} in AWS autoscale wrapper"
34
+ value = send(normalize_method)
35
+ else
36
+ value = @group.send(o)
37
+ end
38
+ instance_variable_set("@#{o}", value)
39
+ end
40
+ end
41
+
42
+ end
43
+
44
+ def normalize_launch_configuration
45
+ @group.launch_configuration_name
46
+ end
47
+
48
+ def normalize_load_balancers
49
+ @group.load_balancer_names.to_a.sort
50
+ end
51
+
52
+ def normalize_availability_zones
53
+ @group.availability_zone_names.to_a.sort
54
+ end
55
+
56
+ def normalize_tags
57
+ @group.tags.to_a.inject([]) do |normalized_tags, current|
58
+ normalized_tags << normalize_tag(current)
59
+ end
60
+ end
61
+
62
+ def normalize_subnets
63
+ @group.subnets.empty? ? nil : @group.subnets.map(&:id).sort
64
+ end
65
+
66
+ def normalize_termination_policies
67
+ @group.termination_policies.to_a.sort
68
+ end
69
+
70
+ protected
71
+
72
+ def normalize_tag(tag)
73
+ normalized_tag = {}
74
+ tag.each_pair do |k, v|
75
+ unless (k == :resource_id || k == :resource_type)
76
+ normalized_tag[k] = v
77
+ end
78
+ end
79
+ normalized_tag
80
+ end
81
+ end
82
+ end
@@ -1,9 +1,11 @@
1
+ # encoding: UTF-8
2
+
1
3
  module Rivet
2
4
  module AwsUtils
3
5
 
4
6
  def self.verify_security_groups(groups)
5
- return false if groups.nil?
6
- Rivet::Log.info("Verifying security groups: #{groups.join(",")}")
7
+ return false if groups.nil? || groups.all?{|g| g.match(/\Asg-[0-9a-f]{8}\z/) }
8
+ Rivet::Log.info "Verifying security groups: #{groups.join(",")}"
7
9
 
8
10
  security_groups_collection = AWS::EC2.new.security_groups
9
11
  filtered_groups = []
@@ -13,7 +15,7 @@ module Rivet
13
15
 
14
16
  groups.each do |g|
15
17
  unless filtered_groups.include?(g)
16
- Rivet::Log.debug("Creating security group #{g}")
18
+ Rivet::Log.debug "Creating security group #{g}"
17
19
  security_groups_collection.create g
18
20
  end
19
21
  end
@@ -27,7 +29,7 @@ module Rivet
27
29
  option_matcher = /(\w.*)=(\S.*)\s*/
28
30
  aws_config = {}
29
31
 
30
- File.open(ENV['AWS_CONFIG_FILE'],"r").each_line do |line|
32
+ File.open(ENV['AWS_CONFIG_FILE'], 'r').each_line do |line|
31
33
 
32
34
  if line =~ profile_matcher
33
35
  current_profile = line.match(profile_matcher)[2]
@@ -39,13 +41,13 @@ module Rivet
39
41
 
40
42
  # Normalize the option name so it can be used with the AWS SDK
41
43
  if results[1] =~ /^\S*aws_/
42
- option = results[1].gsub("aws_",'').to_sym
44
+ option = results[1].gsub('aws_', '').to_sym
43
45
  else
44
46
  option = results[1].to_sym
45
47
  end
46
48
 
47
49
  value = results[2]
48
- aws_config[current_profile].merge!({ option => value})
50
+ aws_config[current_profile].merge!({ option => value })
49
51
  end
50
52
 
51
53
  end
@@ -54,12 +56,12 @@ module Rivet
54
56
  end
55
57
 
56
58
  def self.set_aws_credentials(profile)
57
- Rivet::Log.info("Settings AWS credentials to #{profile} profile")
59
+ Rivet::Log.info "Settings AWS credentials to #{profile} profile"
58
60
  settings = config_parser
59
61
  aws_creds = nil
60
62
 
61
63
  if settings && settings.has_key?(profile)
62
- aws_creds = [:access_key_id,:secret_access_key,:region].inject({})do |accum,option|
64
+ aws_creds = [:access_key_id, :secret_access_key, :region].inject({})do |accum, option|
63
65
  if settings[profile].has_key?(option)
64
66
  accum[option] = settings[profile][option]
65
67
  end
@@ -1,25 +1,11 @@
1
+ # encoding: UTF-8
2
+
1
3
  module Rivet
2
4
  class Bootstrap
3
- TEMPLATE_SUB_DIR = "bootstrap"
4
-
5
- attr_reader :gems, :run_list, :template, :environment, :region, :name, :elastic_ip
6
- attr_reader :template_path, :chef_command, :chef_organization, :chef_username
7
-
8
- def initialize(bootstrap_definition = {})
9
- ivars = [
10
- 'gems', 'run_list', 'template', 'environment', 'region', 'name', 'elastic_ip',
11
- 'config_dir', 'chef_command', 'chef_organization', 'chef_username']
12
-
13
- ivars.each do |i|
14
- if bootstrap_definition.has_key? i
15
- instance_variable_set("@#{i}", bootstrap_definition[i])
16
- end
17
- end unless bootstrap_definition.nil?
5
+ attr_reader :config
18
6
 
19
- @config_dir ||= '.'
20
- @template ||= 'default.erb'
21
-
22
- set_calculated_attrs
7
+ def initialize(config)
8
+ @config = config.bootstrap
23
9
  end
24
10
 
25
11
  def user_data
@@ -28,46 +14,15 @@ module Rivet
28
14
 
29
15
  protected
30
16
 
31
- def set_calculated_attrs
32
- @template_path = File.join(@config_dir, TEMPLATE_SUB_DIR)
33
- @secret_file = File.join(@config_dir, "encrypted_data_bag_secret_#{@environment}")
34
- @validation_key = File.new(File.join(@config_dir, "#{@chef_organization}-validator.pem")).read
35
- end
36
-
37
17
  def generate_user_data
38
- config_content = "log_level :info\n"
39
- config_content << "log_location STDOUT\n"
40
- config_content << "environment '#{environment}'\n"
41
- config_content << "chef_server_url 'https://api.opscode.com/organizations/#{chef_organization}'\n"
42
- config_content << "validation_client_name '#{chef_organization}-validator'\n"
43
-
44
- knife_content = "chef_username = '#{chef_username}'\n"
45
- knife_content << "chef_organization = '#{chef_organization}'\n"
46
- knife_content << "\n"
47
- knife_content << "environment '#{environment}'\n"
48
- knife_content << "log_level :info\n"
49
- knife_content << "log_location STDOUT\n"
50
- knife_content << "node_name \"\#{chef_username}\"\n"
51
- knife_content << "client_key \"~/.chef/\#{chef_username}.pem\"\n"
52
- knife_content << "chef_server_url \"https://api.opscode.com/organizations/\#{chef_organization}\"\n"
53
- knife_content << "cache_type 'BasicFile'\n"
54
- knife_content << "puts \"Using \#{environment} environment...\"\n"
55
-
56
- install_gems = ''
57
-
58
- gems.each do |g|
59
- if g.size > 1
60
- install_gems << "gem install #{g[0]} -v #{g[1]} --no-rdoc --no-ri\n"
61
- else
62
- install_gems << "gem install #{g[0]} --no-rdoc --no-ri\n"
63
- end
64
- end unless gems.nil?
65
-
66
- first_boot = { :run_list => @run_list.flatten }.to_json unless @run_list.nil?
67
-
68
- template = ERB.new File.new(File.join(@template_path, @template)).read
69
- template.result(binding)
18
+ if config.respond_to?(:template)
19
+ Rivet::Log.debug "Rendering #{config.template}"
20
+ template = ERB.new(File.read(config.template))
21
+ template.result(config.instance_eval { binding })
22
+ else
23
+ Rivet::Log.debug 'No template provided, Rendering empty user-data'
24
+ ''
25
+ end
70
26
  end
71
-
72
27
  end
73
28
  end
data/lib/rivet/client.rb CHANGED
@@ -1,35 +1,41 @@
1
+ # encoding: UTF-8
2
+
1
3
  module Rivet
2
4
  class Client
3
- def initialize
4
- end
5
-
6
5
  def run(options)
7
- AwsUtils.set_aws_credentials(options[:profile])
8
- Rivet::Log.level(options[:log_level])
6
+ AwsUtils.set_aws_credentials options.profile
7
+ Rivet::Log.level options.log_level
9
8
 
10
- unless Dir.exists?(options[:definitions_directory])
11
- Rivet::Utils.die("The autoscale definitions directory doesn't exist")
9
+ Rivet::Log.info "Using autoscale config path #{options.config_path}"
10
+
11
+ unless Dir.exists?(options.config_path)
12
+ Rivet::Utils.die 'The autoscale config path does not exist'
12
13
  end
13
14
 
14
- group_def = Rivet::Utils.get_definition(
15
- options[:group],
16
- options[:definitions_directory])
15
+ # Get config object for autoscaling group
16
+ config = Rivet::Utils.get_config(
17
+ options.group,
18
+ options.config_path)
17
19
 
18
- unless group_def
19
- Rivet::Utils.die "The #{options[:group]} definition doesn't exist"
20
+ unless config
21
+ Rivet::Utils.list_groups(options.config_path)
22
+ Rivet::Utils.die "The #{options.group} autoscale definition doesn't exist"
20
23
  end
21
24
 
22
- Rivet::Log.info("Checking #{options[:group]} autoscaling definition")
23
- autoscale_def = Rivet::Autoscale.new(options[:group], group_def)
24
- autoscale_def.show_differences
25
+ config.validate
26
+
27
+ config = ConfigProxy.new(config)
25
28
 
26
- if options[:sync]
27
- autoscale_def.sync
29
+ Rivet::Log.info "Checking #{options.group} autoscaling definition"
30
+
31
+ autoscale_group = Rivet::Autoscale.new(config)
32
+ autoscale_group.show_differences
33
+
34
+ if options.sync
35
+ autoscale_group.sync
28
36
  else
29
- Rivet::Log.info("use the -s [--sync] flag to sync changes")
37
+ Rivet::Log.info 'use the -s [--sync] flag to sync changes'
30
38
  end
31
-
32
39
  end
33
-
34
40
  end
35
41
  end
@@ -0,0 +1,87 @@
1
+ # encoding: UTF-8
2
+
3
+ module Rivet
4
+ class Config < OpenState
5
+ attr_reader :name
6
+ attr_accessor :bootstrap
7
+
8
+ def self.from_file(dsl_file, load_path='.')
9
+ name = File.basename(dsl_file, '.rb')
10
+ data = Proc.new { eval(File.read(dsl_file)) }
11
+ new(name, load_path, &data)
12
+ end
13
+
14
+ def initialize(name, load_path='.', &block)
15
+ super()
16
+ @name = name
17
+ @path = load_path
18
+ @bootstrap = OpenState.new
19
+ @required_fields = {
20
+ :min_size => nil,
21
+ :max_size => nil,
22
+ :availability_zones => nil,
23
+ :default_cooldown => 300,
24
+ :desired_capacity => 0,
25
+ :health_check_grace_period => 0,
26
+ :health_check_type => :ec2,
27
+ :load_balancers => [],
28
+ :tags => [],
29
+ :termination_policies => ['Default']
30
+ }
31
+ instance_eval(&block) if block
32
+ end
33
+
34
+ def path(*args)
35
+ if args.size < 1
36
+ @path
37
+ else
38
+ File.join(@path, *args)
39
+ end
40
+ end
41
+
42
+ def normalize_availability_zones
43
+ availability_zones.map { |zone| region + zone }.sort
44
+ end
45
+
46
+ def normalize_security_groups
47
+ security_groups.sort
48
+ end
49
+
50
+ def normalize_load_balancers
51
+ load_balancers.sort
52
+ end
53
+
54
+ def normalize_subnets
55
+ subnets.sort
56
+ end
57
+
58
+ def normalize_tags
59
+ normalized_tags = []
60
+ tags.each do |t|
61
+ normalized_hash = {}
62
+
63
+ if t.has_key? :propagate_at_launch
64
+ normalized_hash[:propagate_at_launch] = t[:propagate_at_launch]
65
+ else
66
+ normalized_hash[:propagate_at_launch] = true
67
+ end
68
+
69
+ [:value, :key].each do |k|
70
+ if t.has_key? k
71
+ normalized_hash[k] = t[k]
72
+ else
73
+ normalized_hash[k] = nil
74
+ end
75
+ end
76
+ normalized_tags << normalized_hash
77
+ end
78
+ normalized_tags
79
+ end
80
+
81
+ protected
82
+
83
+ def import(import_path)
84
+ lambda { eval(File.read(import_path)) }.call
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,28 @@
1
+ # encoding: UTF-8
2
+
3
+ module Rivet
4
+ class ConfigProxy < BasicObject
5
+
6
+ def initialize(config)
7
+ @config = config
8
+ end
9
+
10
+ def send(m, *args)
11
+ if @config.respond_to?("normalize_#{m}".to_sym)
12
+ @config.send("normalize_#{m}".to_sym)
13
+ else
14
+ super
15
+ end
16
+ end
17
+
18
+ def method_missing(m, *args, &block)
19
+ if @config.respond_to?("normalize_#{m}".to_sym)
20
+ @config.send("normalize_#{m}".to_sym)
21
+ elsif @config.respond_to? m
22
+ @config.send(m, *args, &block)
23
+ else
24
+ super
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,3 +1,5 @@
1
+ # encoding: UTF-8
2
+
1
3
  class Hash
2
4
  # Returns a new hash with +self+ and +other_hash+ merged recursively.
3
5
  #
@@ -14,7 +16,7 @@ class Hash
14
16
 
15
17
  # Same as +deep_merge+, but modifies +self+.
16
18
  def deep_merge!(other_hash, &block)
17
- other_hash.each_pair do |k,v|
19
+ other_hash.each_pair do |k, v|
18
20
  tv = self[k]
19
21
  if tv.is_a?(Hash) && v.is_a?(Hash)
20
22
  self[k] = tv.deep_merge(v, &block)