rivet 1.4.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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)