maws 0.8.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.
Files changed (50) hide show
  1. data/bin/maws +10 -0
  2. data/lib/maws/chunk_source.rb +41 -0
  3. data/lib/maws/command.rb +62 -0
  4. data/lib/maws/command_loader.rb +28 -0
  5. data/lib/maws/command_options_parser.rb +37 -0
  6. data/lib/maws/commands/configure.rb +287 -0
  7. data/lib/maws/commands/console.rb +38 -0
  8. data/lib/maws/commands/create.rb +25 -0
  9. data/lib/maws/commands/describe.rb +15 -0
  10. data/lib/maws/commands/destroy.rb +11 -0
  11. data/lib/maws/commands/elb-add.rb +17 -0
  12. data/lib/maws/commands/elb-describe.rb +23 -0
  13. data/lib/maws/commands/elb-disable-zones.rb +17 -0
  14. data/lib/maws/commands/elb-enable-zones.rb +18 -0
  15. data/lib/maws/commands/elb-remove.rb +16 -0
  16. data/lib/maws/commands/set-prefix.rb +24 -0
  17. data/lib/maws/commands/set-security-groups.rb +442 -0
  18. data/lib/maws/commands/start.rb +11 -0
  19. data/lib/maws/commands/status.rb +25 -0
  20. data/lib/maws/commands/stop.rb +11 -0
  21. data/lib/maws/commands/teardown.rb +11 -0
  22. data/lib/maws/commands/volumes-cleanup.rb +22 -0
  23. data/lib/maws/commands/volumes-status.rb +43 -0
  24. data/lib/maws/commands/wait.rb +61 -0
  25. data/lib/maws/connection.rb +121 -0
  26. data/lib/maws/core_ext/object.rb +5 -0
  27. data/lib/maws/description/ebs.rb +40 -0
  28. data/lib/maws/description/ec2.rb +72 -0
  29. data/lib/maws/description/elb.rb +52 -0
  30. data/lib/maws/description/rds.rb +47 -0
  31. data/lib/maws/description.rb +78 -0
  32. data/lib/maws/instance/ebs.rb +45 -0
  33. data/lib/maws/instance/ec2.rb +144 -0
  34. data/lib/maws/instance/elb.rb +92 -0
  35. data/lib/maws/instance/rds.rb +84 -0
  36. data/lib/maws/instance.rb +167 -0
  37. data/lib/maws/instance_collection.rb +98 -0
  38. data/lib/maws/instance_display.rb +84 -0
  39. data/lib/maws/instance_matcher.rb +27 -0
  40. data/lib/maws/loader.rb +173 -0
  41. data/lib/maws/logger.rb +66 -0
  42. data/lib/maws/mash.rb +9 -0
  43. data/lib/maws/maws.rb +102 -0
  44. data/lib/maws/profile_loader.rb +92 -0
  45. data/lib/maws/specification.rb +127 -0
  46. data/lib/maws/ssh.rb +7 -0
  47. data/lib/maws/trollop.rb +782 -0
  48. data/lib/maws/volumes_command.rb +29 -0
  49. data/lib/maws.rb +25 -0
  50. metadata +115 -0
data/lib/maws/maws.rb ADDED
@@ -0,0 +1,102 @@
1
+ require 'maws/instance_collection'
2
+ require 'maws/specification'
3
+ require 'maws/connection'
4
+
5
+
6
+ class Maws
7
+ attr_reader :instances, :config, :command, :connection, :descriptions
8
+
9
+ def initialize(config, command)
10
+ @config = config
11
+ @command = command
12
+
13
+ @connection = Connection.new(@config)
14
+
15
+ @instances = InstanceCollection.new
16
+ @specification = Specification.new(@config, @config.command_line.selection || "")
17
+ end
18
+
19
+ def services
20
+ @specification.services
21
+ end
22
+
23
+ def specified_roles
24
+ @specification.roles
25
+ end
26
+
27
+ def specified_zones
28
+ @specification.zones
29
+ end
30
+
31
+ def run!
32
+ connect
33
+ info "\n"
34
+ info "REGION: #{@config.region}"
35
+ info "AVAILABLE ZONES: #{@connection.available_zones.join(', ')}"
36
+ build_instances
37
+
38
+ info "\n"
39
+ info "QUERYING SERVICES: #{instances.services.join(', ')}"
40
+ info "TOTAL #{@config.profile.name.upcase} ON AWS: #{instances.aws.count} "
41
+ info "TOTAL SELECTED: #{instances.specified.count}"
42
+ info "TOTAL SELECTED ON AWS: #{instances.specified.aws.count}"
43
+
44
+ @command.run!
45
+ end
46
+
47
+ def connect(services = nil)
48
+ services ||= @specification.services
49
+ @connection.connect(services)
50
+
51
+ @config.available_zones = @connection.available_zones
52
+ @config.specified_zones = @specification.zones
53
+ end
54
+
55
+ def resync_instances
56
+ @descriptions = @connection.descriptions(services)
57
+ @descriptions.each {|description|
58
+ instance = @instances.matching(:aws_id => description.aws_id).first
59
+ instance.description = description if instance
60
+ }
61
+ end
62
+
63
+ def build_instances
64
+ @descriptions = @connection.descriptions(services)
65
+
66
+ create_from_descriptions
67
+ create_from_specification
68
+ end
69
+
70
+ def create_from_descriptions
71
+ @descriptions.map { |description|
72
+ instance = description.create_instance(self, @config)
73
+ @instances.add(instance)
74
+ instance.groups << "aws"
75
+ }
76
+ end
77
+
78
+ def create_from_specification
79
+ @specification.existing_instances = @instances.matching(:groups => 'aws')
80
+
81
+ prefix = @config.prefix
82
+ @specification.role_indexes.each {|role_name, indexes|
83
+ @specification.zones_for_role(role_name).each {|zone|
84
+ indexes.each do |index|
85
+ instance = find_or_create_specified(prefix, zone, role_name, index)
86
+ instance.groups << "specified"
87
+ end
88
+ }
89
+ }
90
+ end
91
+
92
+ def find_or_create_specified(prefix, zone, role_name, index)
93
+ found = @instances.matching({:zone => zone, :role => role_name, :index => index, :prefix => prefix})
94
+ if !found.empty?
95
+ found.first
96
+ else
97
+ instance = Instance.create(self, @config, prefix, zone, role_name, index)
98
+ @instances.add(instance)
99
+ instance
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,92 @@
1
+ class ProfileLoader
2
+ RESERVED_ROLE_NAMES = %w(roles lanes name zones aliases settings security_rules)
3
+
4
+ def initialize(config)
5
+ @config = config
6
+ end
7
+
8
+ def load
9
+ # assumes profile file is known to exist already
10
+ load_profile
11
+ load_roles
12
+ load_security_rules
13
+
14
+ exit_on_missing_roles
15
+ create_combined
16
+ end
17
+
18
+ private
19
+
20
+ def load_profile
21
+ profile_name = @config.command_line.profile_name
22
+ profile_path = @config.config.available_profiles[profile_name]
23
+
24
+ @config.profile = mash(YAML.load_file(profile_path))
25
+ @config.profile.name = profile_name
26
+
27
+ @config.available_roles = @config.profile.keys - RESERVED_ROLE_NAMES
28
+ sort_available_roles_by_appearance_in_profile(profile_path)
29
+ end
30
+
31
+ def sort_available_roles_by_appearance_in_profile(profile_path)
32
+ # hacky, but does the job
33
+ profile_text = File.read(profile_path)
34
+ @config.available_roles = @config.available_roles.sort_by {|role_name| profile_text.index(role_name+":")}
35
+ end
36
+
37
+ def load_roles
38
+ roles_file_name = @config.profile.roles
39
+ roles_file_path = File.join(@config.config.paths.roles, roles_file_name) + ".yml"
40
+ Loader.config_file_must_exist!('roles file', roles_file_path)
41
+
42
+ @config.roles = mash(YAML.load_file(roles_file_path))
43
+ @config.roles.name = roles_file_name
44
+ end
45
+
46
+ def load_security_rules
47
+ security_rules_file_name = @config.profile.security_rules
48
+
49
+ if security_rules_file_name.blank?
50
+ else
51
+ security_rules_file_path = File.join(@config.config.paths.security_rules, security_rules_file_name) + ".yml"
52
+ Loader.config_file_must_exist!('security rules file', security_rules_file_path)
53
+
54
+ @config.security_rules = mash(YAML.load_file(security_rules_file_path))
55
+ end
56
+ end
57
+
58
+ def exit_on_missing_roles
59
+ profile_config_role_names = @config.profile.keys - RESERVED_ROLE_NAMES
60
+ roles_config_role_names = @config.roles.keys - RESERVED_ROLE_NAMES
61
+
62
+ unknown_roles_in_profile_config = profile_config_role_names - roles_config_role_names
63
+
64
+ unless unknown_roles_in_profile_config.empty?
65
+ error "Undefined roles [%s] in profile '%s'" % [unknown_roles_in_profile_config.join(', '), @config.profile.name]
66
+ exit(1)
67
+ end
68
+ end
69
+
70
+ def create_combined
71
+ @config.combined = mash
72
+ # combine roles
73
+ @config.available_roles.each {|role_name|
74
+ @config.combined[role_name] = @config.roles[role_name].deep_merge(@config.profile[role_name])
75
+
76
+ # now combine configurations - these are arrays that need to be unioned
77
+ @config.combined[role_name].configurations = []
78
+ # first collect all configurations with the same name for this role
79
+ grouped_configurations = ((@config.roles[role_name].configurations || []) +
80
+ (@config.profile[role_name].configurations || [])).
81
+ group_by {|c| c.name }
82
+ # now merge all for the same name
83
+ grouped_configurations.each {|name, configurations|
84
+ merged_configuration = configurations.inject(configurations.first) { |merged, configuration| merged.deep_merge(configuration)}
85
+ @config.combined[role_name].configurations << merged_configuration
86
+ }
87
+ }
88
+
89
+ # combine settings
90
+ @config.combined.settings = @config.roles.settings.deep_merge(@config.profile.settings)
91
+ end
92
+ end
@@ -0,0 +1,127 @@
1
+ class Specification
2
+ LARGEST_INDEX = 999
3
+
4
+ attr_accessor :existing_instances
5
+
6
+ def initialize(config, spec)
7
+ @config = config
8
+ @spec = spec
9
+ end
10
+
11
+ # [:ec2, :rds, :elb]
12
+ def services
13
+ roles.map { |role_name|
14
+ @config.roles[role_name].service
15
+ }.compact.uniq.map{|service| service.to_sym}
16
+ end
17
+
18
+ # ['a', 'b', 'd']
19
+ def zones
20
+ found = @config.available_zones.find_all {|az|
21
+ @spec.match(%r{\b#{az}\b})
22
+ }
23
+ found.empty? ? @config.available_zones : found
24
+ end
25
+
26
+ def zones_for_role(role)
27
+ if @config.combined[role].scope != 'zone'
28
+ [nil]
29
+ else
30
+ zones
31
+ end
32
+ end
33
+
34
+ # {'app' => [1,2,3,4], 'web' => [3], 'db' => [4,5,6,7,8,9,10,11,12]}
35
+ def role_indexes
36
+ indexes = {}
37
+ roles.each {|role_name|
38
+ indexes[role_name] = indexes_range_for_role(role_name)
39
+ }
40
+
41
+ indexes.delete_if {|role, index_range| index_range.nil?}
42
+ indexes.each {|role, index_range| indexes[role] = resolve_index_range(role, index_range)}
43
+
44
+ indexes
45
+ end
46
+
47
+ # ('app', [nil, *]) => [1,2,3,4,5,6,7]
48
+ def resolve_index_range(role, range)
49
+ lower = range[0]
50
+ upper = range[1]
51
+
52
+ lower = 1 if lower.nil?
53
+
54
+ # replace upper
55
+ if upper == '*'
56
+ # max existing index
57
+ max_existing = @existing_instances.matching(:role => role).map {|i| i.index}.max.to_i
58
+ max_profile = @config.profile[role].count
59
+
60
+ upper = [max_profile, max_existing].max
61
+ end
62
+
63
+ if upper.nil?
64
+ # max index in profile
65
+ upper = @config.profile[role].count
66
+ end
67
+
68
+ lower.upto(upper).to_a
69
+ end
70
+
71
+ def indexes_range_for_role(role)
72
+ # 'app' => [nil, nil, nil] => [nil, nil]
73
+ # 'app-' => [nil, nil, nil] => [nil, nil]
74
+ # 'app-*' => ["*", nil, nil] => [nil, '*']
75
+ # 'app-3' => ["3", nil, nil] => [3,3]
76
+ # 'app-3-' => ["3", '-', nil] => [3, nil]
77
+ # 'app-3-*' => ["3", '-', "*"] => [3, '*']
78
+ # 'app-1-3' => ["1", '-', "3"] => [1, 3]
79
+
80
+ # '*' => [nil, '*']
81
+ # '' => [nil, nil]
82
+
83
+
84
+ md = @spec.match(%r{\b#{role}(?=\b)-?(?![^\*\d\s])(\d+|\*)?(-)?(\d+|\*)?})
85
+
86
+ # no match, either badly formed specification
87
+ # or '*' or '' (star and blank) specs
88
+ if md.nil?
89
+ anyzone = zones.join('')
90
+ index = if @spec.match(%r{^[#{anyzone}\s]*\*[#{anyzone}\s]*$}) # single '*' with zones
91
+ [nil, '*']
92
+ elsif @spec.match(%r{^[#{anyzone}\s]*$}) # blank
93
+ [nil, nil]
94
+ else
95
+ nil
96
+ end
97
+ return index
98
+ end
99
+
100
+ lower, separator, upper = md[1], md[2], md[3]
101
+
102
+ return nil unless lower.nil? || lower == '*' || lower =~ /^\d+$/
103
+ return nil unless upper.nil? || upper == '*' || upper =~ /^\d+$/
104
+
105
+
106
+
107
+ # convert to integers
108
+ lower = lower.to_i unless lower == '*' or lower.nil?
109
+ upper = upper.to_i unless upper == '*' or upper.nil?
110
+
111
+ # single star is upper bound, not lower bound
112
+ lower, upper = upper, lower if lower == '*'
113
+
114
+ # lower digit without separator and without upper digit means single index
115
+ if lower and separator.nil? and upper.nil?
116
+ upper = lower
117
+ end
118
+
119
+ [lower, upper]
120
+ end
121
+
122
+ def roles
123
+ roles = @config.available_roles.find_all { |role_name| @spec.include?(role_name) }
124
+ roles = @config.available_roles if roles.blank? # use all if none are specified
125
+ roles
126
+ end
127
+ end
data/lib/maws/ssh.rb ADDED
@@ -0,0 +1,7 @@
1
+ require 'net/ssh'
2
+
3
+ class Net::SSH::Authentication::KeyManager
4
+ def use_agent?
5
+ false
6
+ end
7
+ end