fum 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,30 @@
1
+
2
+ module Fum
3
+ module Lang
4
+ class FumFile
5
+
6
+ require 'fum/lang/stage'
7
+
8
+ attr_accessor :name
9
+
10
+ def self.load(filename)
11
+ file = self.new
12
+ file.instance_eval(File.read(filename), filename)
13
+ file
14
+ end
15
+
16
+ def application_name(name)
17
+ @name = name
18
+ end
19
+
20
+ def stages
21
+ @stages ||= {}
22
+ end
23
+
24
+ def stage(*args, &block)
25
+ stage = Fum::Lang::Stage.new(*args, &block)
26
+ stages[stage.id] = stage
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,86 @@
1
+ module Fum
2
+ module Lang
3
+ class Stage
4
+
5
+ require 'fum/lang/zone'
6
+
7
+ attr_accessor :zones, :environment_name, :id, :swap_cnames, :template_name, :solution_stack_name, :version_label
8
+ attr_accessor :env_description
9
+
10
+ def initialize(id, &block)
11
+ @zones = []
12
+ @environment_name = id.to_s
13
+ @id = id.to_s
14
+ @swap_cnames = false
15
+
16
+ if block_given?
17
+ if block.arity == 1
18
+ yield self
19
+ else
20
+ instance_eval &block
21
+ end
22
+ end
23
+ end
24
+
25
+ def timestamp_name(prefix)
26
+ raise ArgumentError, "Prefix must be less than 15" unless prefix.length < 15 && prefix.length > 1
27
+ return "#{prefix}-#{Time.now.to_i.to_s(16)}"
28
+ end
29
+
30
+ def timestamp_name_matcher(prefix)
31
+ raise ArgumentError, "Prefix must be less than 15" unless prefix.length < 15 && prefix.length > 1
32
+ /#{prefix}-[a-z0-9]{8}/
33
+ end
34
+
35
+ def zone(*args, &block)
36
+ @zones << Zone.new(*args, &block)
37
+ end
38
+
39
+ def template(arg, &block)
40
+ @template_name = arg
41
+ end
42
+
43
+ def name(arg)
44
+ @environment_name = arg
45
+ end
46
+
47
+ def matcher(arg)
48
+ @matcher = arg
49
+ end
50
+
51
+ def version(arg)
52
+ @version_label = arg
53
+ end
54
+
55
+ def description(value)
56
+ @env_description = value
57
+ end
58
+
59
+ def solution_stack(name, &block)
60
+ @solution_stack_name = name
61
+ end
62
+
63
+ def cname_prefix(name, opts = {})
64
+ @cname_prefix = name
65
+ if opts[:swap]
66
+ @swap_cnames = true
67
+ end
68
+ end
69
+
70
+ # Returns true if environment matches the matcher for this stage
71
+ def matches?(env)
72
+ if @matcher
73
+ if @matcher.is_a?(Regexp)
74
+ return env.name =~ @matcher
75
+ else
76
+ return env.name == @matcher
77
+ end
78
+ else
79
+ return env.name == @environment_name
80
+ end
81
+ false
82
+ end
83
+
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,33 @@
1
+ module Fum
2
+ module Lang
3
+ class Zone
4
+
5
+ attr_accessor :name, :records
6
+
7
+ def initialize(name, &block)
8
+ @name = name
9
+ @records = []
10
+
11
+ if block_given?
12
+ if block.arity == 1
13
+ yield self
14
+ else
15
+ instance_eval &block
16
+ end
17
+ end
18
+ end
19
+
20
+ def elb_alias(name, opts = {})
21
+ @records << { :name => name, :type => 'A', :target => :elb, :options => opts}
22
+ end
23
+
24
+ def elb_cname(name, opts = {})
25
+ @records << { :name => name, :type => 'CNAME', :target =>:elb, :options => opts}
26
+ end
27
+
28
+ def cname(name, opts = {})
29
+ @records << { :name => name, :type => 'CNAME', :target =>:env, :options => opts}
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,172 @@
1
+ module Fum
2
+
3
+ #
4
+ # Analyzes environments and DNS zone info to determine which environments belong to a particular stage
5
+ # and the status of each environment.
6
+ #
7
+ class StageAnalyzer
8
+ include Fum::DNS
9
+ include Fum::Util
10
+
11
+ attr_accessor :env_map, :zones_map
12
+
13
+ def initialize(stage_decl)
14
+ @stage_decl = stage_decl
15
+ @zones_map = {}
16
+ @env_map = {}
17
+ end
18
+
19
+ def analyze(options)
20
+ beanstalk = Fog::AWS[:beanstalk]
21
+ build_zone_map(@stage_decl.zones, options)
22
+ analyze_zone_map(beanstalk.environments.select { |env| @stage_decl.matches?(env) }, options)
23
+ @env_map
24
+ end
25
+
26
+ # Return the Active environment or nil
27
+ def active
28
+ @env_map.values.select { |e| e[:state] == :active }.map { |e| e[:env] }.shift
29
+ end
30
+
31
+ # Return the inactive environments or empty array
32
+ def inactive
33
+ @env_map.values.select { |e| e[:state] == :inactive }.map { |e| e[:env] }
34
+ end
35
+
36
+ private
37
+
38
+ #
39
+ # Build Map
40
+ #
41
+ def build_zone_map(zone_decls, options)
42
+ zone_decls.each { |zone_decl|
43
+ puts "Looking up zone #{zone_decl.name}." if options[:verbose]
44
+ zone = dns.zones.all({:domain => zone_decl.name}).shift
45
+ die "Could not find zone #{zone_decl.name} in account." unless zone
46
+
47
+ puts "Obtaining all records for zone #{zone_decl.name}" if options[:verbose]
48
+ all_records = zone.records.all!
49
+
50
+ zone_decl.records.each { |record_decl|
51
+ fqdn = "#{record_decl[:name]}.#{zone_decl.name}."
52
+
53
+ existing = all_records.select { |r| r.name == fqdn && ['A', 'AAAA', 'CNAME'].include?(r.type)}
54
+
55
+ if existing.length > 1
56
+ # We do not currently handle this case, which would occur if AAAA records exist or weighted/latency records used.
57
+ die "Cannot update record #{fqdn} in zone #{zone} because more than one A, AAAA, or CNAME record already exists."
58
+ end
59
+
60
+ @zones_map[fqdn] = {
61
+ :zone_decl => zone_decl,
62
+ :record_decl => record_decl,
63
+ :record => existing.shift
64
+ }
65
+ }
66
+
67
+ }
68
+ end
69
+
70
+ def analyze_zone_map(environments, options)
71
+
72
+ puts "Analyzing environments." if options[:verbose]
73
+
74
+ environments.each { |e|
75
+ @env_map[e.id] = {
76
+ :env => e,
77
+ :elb => e.load_balancer,
78
+ :record_count => 0,
79
+ :dns_records => [],
80
+ :missing_dns_names => [],
81
+ :state => e.ready? ? :inactive : e.status.downcase.to_sym,
82
+ :environment => nil
83
+ }
84
+ }
85
+
86
+ @zones_map.each { |fqdn, entry|
87
+ next if entry[:record].nil?
88
+
89
+ if entry[:record].type != entry[:record_decl][:type]
90
+ entry[:error] = "incorrect record type #{entry[:record].type}"
91
+ entry[:environment] = nil
92
+ next
93
+ end
94
+
95
+ entry[:environment] = case entry[:record_decl][:type]
96
+ when "A"
97
+ environment_for_alias(entry[:record])
98
+ when "CNAME"
99
+ case entry[:record_decl][:target]
100
+ when :elb
101
+ environment_for_elb_cname(entry[:record])
102
+ when :env
103
+ environment_for_cname(entry[:record])
104
+ end
105
+ else
106
+ nil
107
+ end
108
+
109
+ if entry[:environment]
110
+ @env_map[entry[:environment].id][:record_count] += 1
111
+ end
112
+ }
113
+
114
+ zone_count = @zones_map.size
115
+
116
+ envs_with_records = @env_map.values.select { |e| e[:record_count] > 0 }
117
+
118
+ if envs_with_records.length == 1
119
+ record = envs_with_records.shift
120
+ if record[:record_count] == zone_count
121
+ record[:state] = :active
122
+ else
123
+ record[:state] = :degraded
124
+ end
125
+ else
126
+ # More than one record has DNS records, let's determine which ones
127
+ envs_with_records.each { |e|
128
+ e[:state] = :indeterminate
129
+ }
130
+ end
131
+
132
+ end
133
+
134
+ def environment_for_alias(record)
135
+ return nil unless record.alias_target
136
+ @env_map.each_value { |e|
137
+ next unless e[:env].ready? # Skip if environment not ready.
138
+ if e[:elb] && dns_names_equal(e[:elb].dns_name, record.alias_target['DNSName']) &&
139
+ e[:elb].hosted_zone_name_id == record.alias_target['HostedZoneId']
140
+ return e[:env]
141
+ end
142
+ }
143
+ nil
144
+ end
145
+
146
+ def environment_for_elb_cname(record)
147
+ dns_name = record.value.shift
148
+
149
+ @env_map.each_value { |e|
150
+ next unless e[:env].ready? # Skip if environment not ready.
151
+ if e[:elb] && dns_names_equal(e[:elb].dns_name, dns_name)
152
+ return e[:env]
153
+ end
154
+ }
155
+ nil
156
+ end
157
+
158
+ def environment_for_cname(record)
159
+ dns_name = record.value.shift
160
+
161
+ @env_map.each_value { |e|
162
+ next unless e[:env].ready? # Skip if environment not ready.
163
+ if dns_names_equal(e[:env].cname, dns_name)
164
+ return e[:env]
165
+ end
166
+ }
167
+ nil
168
+ end
169
+
170
+ end
171
+
172
+ end
@@ -0,0 +1,10 @@
1
+ module Fum
2
+ module Util
3
+
4
+ def die(msg)
5
+ puts msg
6
+ exit 1
7
+ end
8
+
9
+ end
10
+ end
@@ -0,0 +1,3 @@
1
+ module Fum
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,140 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fum
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - George Scott
14
+ - RumbleWare Inc.
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2012-04-06 00:00:00 Z
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: fog
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ hash: 25
30
+ segments:
31
+ - 1
32
+ - 3
33
+ - 1
34
+ version: 1.3.1
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: trollop
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ hash: 83
46
+ segments:
47
+ - 1
48
+ - 16
49
+ - 2
50
+ version: 1.16.2
51
+ type: :runtime
52
+ version_requirements: *id002
53
+ - !ruby/object:Gem::Dependency
54
+ name: json
55
+ prerelease: false
56
+ requirement: &id003 !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ hash: 5
62
+ segments:
63
+ - 1
64
+ - 6
65
+ - 5
66
+ version: 1.6.5
67
+ type: :runtime
68
+ version_requirements: *id003
69
+ description: fum helps you manage your AWS Elastic Beanstalk environments
70
+ email:
71
+ - foss@rumbleware.com
72
+ executables:
73
+ - fum
74
+ extensions: []
75
+
76
+ extra_rdoc_files: []
77
+
78
+ files:
79
+ - .gitignore
80
+ - Gemfile
81
+ - LICENSE
82
+ - README.rdoc
83
+ - Rakefile
84
+ - bin/fum
85
+ - examples/hello.fum
86
+ - fum.gemspec
87
+ - lib/fum.rb
88
+ - lib/fum/application.rb
89
+ - lib/fum/command.rb
90
+ - lib/fum/command_manager.rb
91
+ - lib/fum/commands/events.rb
92
+ - lib/fum/commands/launch.rb
93
+ - lib/fum/commands/list.rb
94
+ - lib/fum/commands/repair.rb
95
+ - lib/fum/commands/status.rb
96
+ - lib/fum/commands/tail.rb
97
+ - lib/fum/commands/template.rb
98
+ - lib/fum/commands/terminate.rb
99
+ - lib/fum/dns.rb
100
+ - lib/fum/lang/fum_file.rb
101
+ - lib/fum/lang/stage.rb
102
+ - lib/fum/lang/zone.rb
103
+ - lib/fum/stage_analyzer.rb
104
+ - lib/fum/util.rb
105
+ - lib/fum/version.rb
106
+ homepage: http://github.com/rumbleware/fum
107
+ licenses: []
108
+
109
+ post_install_message:
110
+ rdoc_options: []
111
+
112
+ require_paths:
113
+ - lib
114
+ required_ruby_version: !ruby/object:Gem::Requirement
115
+ none: false
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ hash: 3
120
+ segments:
121
+ - 0
122
+ version: "0"
123
+ required_rubygems_version: !ruby/object:Gem::Requirement
124
+ none: false
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ hash: 3
129
+ segments:
130
+ - 0
131
+ version: "0"
132
+ requirements: []
133
+
134
+ rubyforge_project: fum
135
+ rubygems_version: 1.8.15
136
+ signing_key:
137
+ specification_version: 3
138
+ summary: Management tool for AWS Elastic Beanstalk
139
+ test_files: []
140
+