fum 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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