fum 0.1.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.
@@ -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
+