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.
- data/.gitignore +5 -0
- data/Gemfile +7 -0
- data/LICENSE +15 -0
- data/README.rdoc +219 -0
- data/Rakefile +1 -0
- data/bin/fum +5 -0
- data/examples/hello.fum +6 -0
- data/fum.gemspec +27 -0
- data/lib/fum.rb +12 -0
- data/lib/fum/application.rb +117 -0
- data/lib/fum/command.rb +21 -0
- data/lib/fum/command_manager.rb +44 -0
- data/lib/fum/commands/events.rb +58 -0
- data/lib/fum/commands/launch.rb +110 -0
- data/lib/fum/commands/list.rb +83 -0
- data/lib/fum/commands/repair.rb +45 -0
- data/lib/fum/commands/status.rb +45 -0
- data/lib/fum/commands/tail.rb +89 -0
- data/lib/fum/commands/template.rb +301 -0
- data/lib/fum/commands/terminate.rb +54 -0
- data/lib/fum/dns.rb +135 -0
- data/lib/fum/lang/fum_file.rb +30 -0
- data/lib/fum/lang/stage.rb +86 -0
- data/lib/fum/lang/zone.rb +33 -0
- data/lib/fum/stage_analyzer.rb +172 -0
- data/lib/fum/util.rb +10 -0
- data/lib/fum/version.rb +3 -0
- metadata +140 -0
@@ -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
|
data/lib/fum/util.rb
ADDED
data/lib/fum/version.rb
ADDED
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
|
+
|