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,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
|