fum 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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,44 @@
|
|
1
|
+
module Fum
|
2
|
+
module CommandManager
|
3
|
+
|
4
|
+
module ClassMethods
|
5
|
+
|
6
|
+
@@commands_map = Hash.new
|
7
|
+
|
8
|
+
def command(name, opts={})
|
9
|
+
require "fum/commands/#{name}"
|
10
|
+
class_name = name.to_s.capitalize
|
11
|
+
@@commands_map[name] = Fum::Commands::const_get(class_name)
|
12
|
+
end
|
13
|
+
|
14
|
+
def create_commands(*args)
|
15
|
+
map = {}
|
16
|
+
@@commands_map.each { |key, value|
|
17
|
+
map[key] = value.new(*args)
|
18
|
+
}
|
19
|
+
map
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.included(includer)
|
24
|
+
includer.extend ClassMethods
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize
|
28
|
+
@commands = self.class.create_commands(self)
|
29
|
+
end
|
30
|
+
|
31
|
+
def commands
|
32
|
+
@commands
|
33
|
+
end
|
34
|
+
|
35
|
+
def parse_command_options(name)
|
36
|
+
@commands[name].parse_options
|
37
|
+
end
|
38
|
+
|
39
|
+
def run_command(name, options = {})
|
40
|
+
@commands[name].execute(options)
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Fum
|
2
|
+
module Commands
|
3
|
+
class Events < Fum::Command
|
4
|
+
|
5
|
+
def parse_options
|
6
|
+
Trollop::options do
|
7
|
+
banner "usage: events [options], where options are:"
|
8
|
+
opt :number, "Number of events to show", :type => :integer, :default => 100
|
9
|
+
opt :application, "Show events only for the specified application", :type => :string
|
10
|
+
opt :environment, "Show events associated with the specified environment name or id", :type => :string
|
11
|
+
opt :severity, "Limit events to severity or higher", :type => :string
|
12
|
+
opt :version_label, "Version label", :type => :string, :long => "version"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def execute(options)
|
17
|
+
beanstalk = Fog::AWS[:beanstalk]
|
18
|
+
|
19
|
+
filters = {
|
20
|
+
'MaxRecords' => options[:number],
|
21
|
+
'ApplicationName' => options[:application],
|
22
|
+
'VersionLabel' => options[:version_label]
|
23
|
+
}
|
24
|
+
filters['Severity'] = options[:severity].upcase unless options[:severity].nil?
|
25
|
+
|
26
|
+
if options[:environment] && options[:environment].match(/e-[a-zA-Z0-9]{10}/)
|
27
|
+
filters['EnvironmentId'] = options[:environment]
|
28
|
+
else
|
29
|
+
filters['EnvironmentName'] = options[:environment]
|
30
|
+
end
|
31
|
+
|
32
|
+
filters = filters.delete_if { |key, value| value.nil? }
|
33
|
+
|
34
|
+
events = beanstalk.events.all(filters).reverse
|
35
|
+
|
36
|
+
# Beanstalk seems to return an extra item
|
37
|
+
events.shift
|
38
|
+
|
39
|
+
events.each { |event|
|
40
|
+
app = event.application_name
|
41
|
+
if event.environment_name
|
42
|
+
app += "(#{event.environment_name})"
|
43
|
+
elsif event.version_label
|
44
|
+
app += "(#{event.version_label})"
|
45
|
+
elsif event.template_name
|
46
|
+
app += "(#{event.template_name})"
|
47
|
+
end
|
48
|
+
|
49
|
+
puts "#{event.date.strftime('%b %d %H:%M:%S')} #{app} #{event.severity} \"#{event.message}\""
|
50
|
+
}
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
module Fum
|
2
|
+
module Commands
|
3
|
+
class Launch < Fum::Command
|
4
|
+
include Fum::DNS
|
5
|
+
|
6
|
+
def parse_options
|
7
|
+
options = Trollop::options do
|
8
|
+
banner "usage launch [options] <environment-id>, where options are:"
|
9
|
+
opt :version_label, "Launch the specified version", :long => "version", :type => :string
|
10
|
+
opt :create, "Create application if it does not exist"
|
11
|
+
opt :no_dns, "Do not change any DNS records", :default => false
|
12
|
+
opt :no_wait, "Launch environment, but do not wait for ready", :default => false, :short => 'w'
|
13
|
+
end
|
14
|
+
if ARGV.empty?
|
15
|
+
die "Stage not specified. Please specify a stage to launch"
|
16
|
+
end
|
17
|
+
options[:stage_name] = ARGV.shift
|
18
|
+
options
|
19
|
+
end
|
20
|
+
|
21
|
+
def execute(options)
|
22
|
+
app = @application.main_decl
|
23
|
+
|
24
|
+
stage_name = options[:stage_name]
|
25
|
+
stage_decl = stage(@application.main_decl, stage_name)
|
26
|
+
|
27
|
+
if options[:verbose]
|
28
|
+
puts "Verifying application '#{app.name}' exists in AWS."
|
29
|
+
end
|
30
|
+
|
31
|
+
beanstalk = Fog::AWS[:beanstalk]
|
32
|
+
|
33
|
+
beanstalk_app = beanstalk.applications.get(app.name)
|
34
|
+
|
35
|
+
if beanstalk_app.nil?
|
36
|
+
if options[:create]
|
37
|
+
beanstalk_app = beanstalk.applications.create(:name => app.name)
|
38
|
+
beanstalk.versions.create({
|
39
|
+
:application_name => app.name,
|
40
|
+
:label => 'Sample'
|
41
|
+
})
|
42
|
+
else
|
43
|
+
die "Could not find app '#{app.name}' in AWS account."
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
env_opt = {
|
49
|
+
:application_name => app.name,
|
50
|
+
:name => stage_decl.environment_name
|
51
|
+
}
|
52
|
+
env_opt[:template_name] = stage_decl.template_name unless stage_decl.template_name.nil?
|
53
|
+
env_opt[:solution_stack_name] = stage_decl.solution_stack_name unless stage_decl.solution_stack_name.nil?
|
54
|
+
env_opt[:description] = stage_decl.env_description unless stage_decl.env_description.nil?
|
55
|
+
|
56
|
+
set_version(stage_decl, env_opt, options)
|
57
|
+
|
58
|
+
if options[:verbose] || options[:noop]
|
59
|
+
puts "Launching an environment named '#{stage_decl.environment_name}'"
|
60
|
+
end
|
61
|
+
|
62
|
+
begin
|
63
|
+
new_env = beanstalk.environments.create(env_opt) unless options[:noop]
|
64
|
+
rescue Exception => e
|
65
|
+
die "Problem creating environment: #{e.to_s}"
|
66
|
+
end
|
67
|
+
|
68
|
+
puts "Launched environment '#{stage_decl.environment_name}'."
|
69
|
+
|
70
|
+
puts "Waiting for environment to become ready..."
|
71
|
+
|
72
|
+
new_env.wait_for { ready? } unless options[:noop]
|
73
|
+
|
74
|
+
puts "New environment is ready."
|
75
|
+
|
76
|
+
puts "Waiting for environment to become healthy..."
|
77
|
+
|
78
|
+
new_env.wait_for { healthy? } unless options[:noop]
|
79
|
+
|
80
|
+
puts "New environment is healthy."
|
81
|
+
|
82
|
+
update_zones(stage_decl, new_env, options) unless options[:no_dns]
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
def set_version(stage_decl, env_opt, options)
|
87
|
+
version_label = stage_decl.version_label
|
88
|
+
return nil if version_label.nil?
|
89
|
+
|
90
|
+
if version_label.is_a?(String)
|
91
|
+
env_opt[:version_label] = version_label
|
92
|
+
puts "Using verison label #{version_label}"
|
93
|
+
elsif version_label.is_a?(Hash) && version_label.has_key?(:from_stage)
|
94
|
+
from_stage = version_label[:from_stage]
|
95
|
+
from_stage_decl = stage(@application.main_decl, from_stage)
|
96
|
+
analyzer = StageAnalyzer.new(from_stage_decl)
|
97
|
+
analyzer.analyze(options)
|
98
|
+
active = analyzer.active
|
99
|
+
die "Cannot determine version to launch. No active environment for stage '#{from_stage}' specified." if active.nil?
|
100
|
+
env_opt[:version_label] = active.version_label
|
101
|
+
puts "Using version #{active.version_label} from stage #{from_stage}."
|
102
|
+
else
|
103
|
+
"Unknown version label #{version_label.inspect}"
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module Fum
|
2
|
+
module Commands
|
3
|
+
class List < Fum::Command
|
4
|
+
include Fum::DNS
|
5
|
+
|
6
|
+
def parse_options
|
7
|
+
opts = Trollop::options do
|
8
|
+
banner "usage: list [options] <environment-id>, where options are:"
|
9
|
+
end
|
10
|
+
if ARGV.empty?
|
11
|
+
die "Please specify a beanstalk type to list!"
|
12
|
+
end
|
13
|
+
|
14
|
+
object = ARGV.shift
|
15
|
+
|
16
|
+
opts[:type] = case object
|
17
|
+
when "stacks"
|
18
|
+
:stacks
|
19
|
+
when "app", "apps", "applications"
|
20
|
+
:applications
|
21
|
+
when "env", "envs", "environments"
|
22
|
+
:environments
|
23
|
+
when "stages"
|
24
|
+
:stages
|
25
|
+
when "templates"
|
26
|
+
:templates
|
27
|
+
when "versions"
|
28
|
+
:versions
|
29
|
+
else
|
30
|
+
die "Unknown object to list #{object}."
|
31
|
+
end
|
32
|
+
|
33
|
+
opts
|
34
|
+
end
|
35
|
+
|
36
|
+
#
|
37
|
+
#
|
38
|
+
# * :type
|
39
|
+
def execute(options)
|
40
|
+
beanstalk = Fog::AWS[:beanstalk]
|
41
|
+
|
42
|
+
case options[:type]
|
43
|
+
when :stacks
|
44
|
+
beanstalk.solution_stacks.each { |stack|
|
45
|
+
puts stack["SolutionStackName"]
|
46
|
+
}
|
47
|
+
when :applications
|
48
|
+
beanstalk.applications.each { |app|
|
49
|
+
puts app.name
|
50
|
+
}
|
51
|
+
when :environments
|
52
|
+
list_environments(options)
|
53
|
+
when :stages
|
54
|
+
@application.main_decl.stages.each_value { |stage|
|
55
|
+
puts stage.id
|
56
|
+
}
|
57
|
+
when :templates
|
58
|
+
beanstalk.templates.each { |template|
|
59
|
+
puts template.name
|
60
|
+
}
|
61
|
+
when :versions
|
62
|
+
beanstalk.versions.each { |version|
|
63
|
+
puts version.label
|
64
|
+
}
|
65
|
+
else
|
66
|
+
die "Unknown object to list #{object}."
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
def list_environments(options)
|
72
|
+
beanstalk = Fog::AWS[:beanstalk]
|
73
|
+
environments = beanstalk.environments
|
74
|
+
|
75
|
+
environments.each { |env|
|
76
|
+
puts env.name
|
77
|
+
}
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Fum
|
2
|
+
module Commands
|
3
|
+
class Repair < Fum::Command
|
4
|
+
include Fum::DNS
|
5
|
+
|
6
|
+
def parse_options
|
7
|
+
opts = Trollop::options do
|
8
|
+
banner "usage: repair [options] <stage-id>, where options are:"
|
9
|
+
opt :environment, "Environment name to repair", :type => :string
|
10
|
+
end
|
11
|
+
if ARGV.empty?
|
12
|
+
die "Please specify a stage name type to repair."
|
13
|
+
end
|
14
|
+
opts[:stage_name] = ARGV.shift
|
15
|
+
|
16
|
+
opts
|
17
|
+
end
|
18
|
+
|
19
|
+
#
|
20
|
+
#
|
21
|
+
# * :type
|
22
|
+
def execute(options)
|
23
|
+
stage_name = options[:stage_name]
|
24
|
+
stage_decl = stage(@application.main_decl, stage_name)
|
25
|
+
|
26
|
+
analyzer = StageAnalyzer.new(stage_decl)
|
27
|
+
|
28
|
+
analyzer.analyze(options)
|
29
|
+
|
30
|
+
degraded = analyzer.env_map.each_value.select { |env| env[:state] == :degraded }
|
31
|
+
|
32
|
+
if degraded.length == 1
|
33
|
+
degraded = degraded.shift
|
34
|
+
puts "Repairing environment #{degraded[:env].name}."
|
35
|
+
update_zones(stage_decl, degraded[:env], options)
|
36
|
+
puts "Repair complete."
|
37
|
+
else
|
38
|
+
puts "Nothing to repair."
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Fum
|
2
|
+
module Commands
|
3
|
+
class Status < Fum::Command
|
4
|
+
include Fum::DNS
|
5
|
+
|
6
|
+
def parse_options
|
7
|
+
opts = Trollop::options do
|
8
|
+
banner "usage: status [options] <stage-name>, where options are:"
|
9
|
+
end
|
10
|
+
if ARGV.empty?
|
11
|
+
die "Please specify a stage name."
|
12
|
+
end
|
13
|
+
|
14
|
+
opts[:stage_name] = ARGV.shift
|
15
|
+
|
16
|
+
opts
|
17
|
+
end
|
18
|
+
|
19
|
+
#
|
20
|
+
#
|
21
|
+
# * :type
|
22
|
+
def execute(options)
|
23
|
+
|
24
|
+
stage_name = options[:stage_name]
|
25
|
+
stage_decl = stage(@application.main_decl, stage_name)
|
26
|
+
|
27
|
+
analyzer = StageAnalyzer.new(stage_decl)
|
28
|
+
analyzer.analyze(options)
|
29
|
+
|
30
|
+
envs = analyzer.env_map.values
|
31
|
+
|
32
|
+
unless envs.empty?
|
33
|
+
envs.each { |env|
|
34
|
+
puts "#{env[:env].name} (#{env[:state]})"
|
35
|
+
}
|
36
|
+
else
|
37
|
+
puts "No environments found for stage #{stage_name}."
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Fum
|
2
|
+
module Commands
|
3
|
+
class Tail < Fum::Command
|
4
|
+
|
5
|
+
def parse_options
|
6
|
+
Trollop::options do
|
7
|
+
banner "usage: tail [options], where options are:"
|
8
|
+
opt :application, "Show events only for the specified application", :type => :string
|
9
|
+
opt :environment, "Show events associated with the specified environment name or id", :type => :string
|
10
|
+
opt :directory, "Output log files to the directory specified.", :type => :string
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def execute(options)
|
15
|
+
beanstalk = Fog::AWS[:beanstalk]
|
16
|
+
|
17
|
+
die "Directory #{options[:directory]} does not exist" if options[:directory] && !File.directory?(options[:directory])
|
18
|
+
|
19
|
+
if options[:environment].nil?
|
20
|
+
# Use the currently active environment
|
21
|
+
|
22
|
+
end
|
23
|
+
request_opts = {
|
24
|
+
'ApplicationName' => options[:application],
|
25
|
+
'EnvironmentName' => options[:environment],
|
26
|
+
'InfoType' => 'tail'
|
27
|
+
}
|
28
|
+
|
29
|
+
if options[:environment] && options[:environment].match(/e-[a-zA-Z0-9]{10}/)
|
30
|
+
request_opts['EnvironmentId'] = options[:environment]
|
31
|
+
else
|
32
|
+
request_opts['EnvironmentName'] = options[:environment]
|
33
|
+
end
|
34
|
+
|
35
|
+
puts "Requesting logs for environment #{options[:environment]}" if options[:verbose]
|
36
|
+
beanstalk.request_environment_info(request_opts)
|
37
|
+
|
38
|
+
begin
|
39
|
+
files = Timeout::timeout(360) do
|
40
|
+
# Sleep a few seconds
|
41
|
+
sleep(5)
|
42
|
+
begin
|
43
|
+
puts "Retrieving logs for environment #{options[:environment]}" if options[:verbose]
|
44
|
+
info = beanstalk.retrieve_environment_info(request_opts).body["RetrieveEnvironmentInfoResult"]["EnvironmentInfo"]
|
45
|
+
puts "Logs not yet available, will try again in 10 seconds." if options[:verbose] && info.length == 0
|
46
|
+
end while info.length == 0 && sleep(10)
|
47
|
+
|
48
|
+
info = info.sort_by { |i| i['SampleTimestamp'] }.reverse
|
49
|
+
instance_ids = []
|
50
|
+
info = info.select { |i|
|
51
|
+
seen = instance_ids.include?(i['Ec2InstanceId'])
|
52
|
+
instance_ids << i['Ec2InstanceId']
|
53
|
+
!seen
|
54
|
+
}
|
55
|
+
end
|
56
|
+
rescue Timeout::Error
|
57
|
+
die "Operation timed out, could not retrieve environment info."
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
require 'excon'
|
62
|
+
files.each { |file|
|
63
|
+
contents = Excon.get(file['Message']).body
|
64
|
+
if options[:directory]
|
65
|
+
filename = "#{file['Ec2InstanceId']}-#{file['SampleTimestamp'].strftime('%Y%m%dT%H%M')}.txt"
|
66
|
+
file = File.join(File.expand_path(options[:directory]),filename)
|
67
|
+
if File.exists?(file)
|
68
|
+
puts "File already exists #{file}, will not overwrite."
|
69
|
+
next
|
70
|
+
end
|
71
|
+
File.open(file, 'w') { |f|f.write(contents) }
|
72
|
+
puts "Created file #{file}"
|
73
|
+
else
|
74
|
+
puts "##########################################"
|
75
|
+
puts "# InstanceID #{file['Ec2InstanceId']}"
|
76
|
+
puts "# Timestamp #{file['SampleTimestamp']}"
|
77
|
+
puts "##########################################"
|
78
|
+
puts contents
|
79
|
+
end
|
80
|
+
|
81
|
+
}
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|