fum 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ .idea
4
+ Gemfile.lock
5
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in fum.gemspec
4
+ gemspec
5
+
6
+ #gem "fog", :git => "git://github.com/fog/fog.git"
7
+ gem "fog", :path => "~/ruby/fog"
data/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ Copyright (c) 2012 George Scott, RumbleWare Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
4
+ documentation files (the "Software"), to deal in the Software without restriction, including without limitation
5
+ the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
6
+ and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
7
+
8
+ The above copyright notice and this permission notice shall be included in all copies or substantial
9
+ portions of the Software.
10
+
11
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
12
+ TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
13
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
14
+ CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
15
+ IN THE SOFTWARE.
@@ -0,0 +1,219 @@
1
+
2
+ fum is a tool for managing Amazon Web Services Elastic beanstalk lifecycle stages. fum assists with the following:
3
+
4
+ * Managing DNS changes associated with deployments
5
+ * Application version management and deployment
6
+ * Maintaing multiple lifecycle stages (test/staging/production) of an application.
7
+
8
+ == Getting started
9
+
10
+ fum is a Ruby DSL (Domain Specific Language) similar to rake, chef, and other tools. You declare application lifecycle
11
+ stages using the fum language, and then use the fum command to manage those stages.
12
+
13
+ Installing fum is simple:
14
+
15
+ sudo gem install fum
16
+
17
+ Create a file named +hello.fum+ with the following:
18
+
19
+ application_name 'HelloWorld'
20
+
21
+ stage :hello do
22
+ solution_stack "32bit Amazon Linux running Tomcat 7"
23
+ end
24
+
25
+ Tell fum about your credentials by creating a file named +.fum+ in your home directory with your AWS credentials (Note,
26
+ we recommend running these examples only in a development account until you are comfortable using fum):
27
+
28
+ access_key_id: <YOUR_ACCESS_KEY>
29
+ secret_access_key: <YOUR_SECRET_KEY>
30
+
31
+ Now, let's go ahead and launch it:
32
+
33
+ fum hello.fum launch --create hello
34
+
35
+ This command tells fum to launch a new environment using the "hello" stage definition you have provided in +hello.fum+.
36
+ By default fum will not create new applications or versions, but if you specify the +--create+ flag, then fum will
37
+ create the application and a sample version for you if none exists. In this example, fum will create an application
38
+ named "HelloWorld". It will also create an environment named "hello" corresponding to the stage you specified.
39
+
40
+ == Adding DNS support.
41
+
42
+ The previous examle was simple, and possibly a time saver, but is not particularly interesting. If you are developing
43
+ an app on elastic beanstalk, you most likely don't want your end users to access your app using
44
+ "example.elasticbeantalk.com" as the URL. Instead, you most likely want them to access your app using your own
45
+ hostname such as "www.example.com" or even "example.com" directly. The most flexible way to accomplish this is to use
46
+ AWS Route 53 which supports alias records that map a domain name to an elastic load balancer. The example below shows
47
+ how one can map a few different records types to an AWS Route 53 zone name 'example.com':
48
+
49
+ stage :hello do
50
+ solution_stack "32bit Amazon Linux running Tomcat 7"
51
+
52
+ zone 'example.com'
53
+ elb_alias :apex # Create an alias entry to the load balancer at the zone apex 'myapp.com.'
54
+ elb_alias 'www' # Create an alias entry to the load balancer at 'www.myapp.com.'
55
+ elb_cname 'www2' # Create a CNAME to the load balancer rather than an alias record.
56
+ cname 'www3' # Create a CNAME to the elastic beanstalk CNAME (useful when using swap cname feature)
57
+ end
58
+
59
+ end
60
+
61
+ Each stage declaration can have multiple zones and each zone can declare multiple records to create/update when a stage
62
+ is launched. The +elb_alias+ directive provides a domain name that should be mapped, you can specify +:apex+ if you
63
+ would like to map an alias to the zone apex, in this case 'example.com'. The +elb_cname+ creates a CNAME record to
64
+ the load balancer's DNS name, while the +cname+ record creates a CNAME record pointing to the elastic beanstalk
65
+ environment's cname.
66
+
67
+ To run this configuration create a zone named 'example.com' using the AWS Managmenent Console and run the following
68
+ command:
69
+
70
+ fum hello_r53.fum launch hello
71
+
72
+ When you run the command, you will notice a few differences in the output from before. First, the newly created
73
+ environment is
74
+
75
+ As an alternative to using AWS Route 53, you can use elastic beanstalk's cname prefix and swapping features to manage
76
+ your environments. This is configured as follows:
77
+
78
+ stage :hello do
79
+ solution_stack "32bit Amazon Linux running Tomcat 7"
80
+
81
+ cname_prefix "example", :swap => true
82
+ end
83
+
84
+ The +cname_prefix+ declaration tells fum what prefix to request for the beanstalk environment when it is launched.
85
+ By default, fum does not specify a prefix, and lets AWS assign a name dynamically. If you specify a cname prefix,
86
+ then you must ensure that the name is unique across all AWS customers. You can also specify an option to +cname_prefix+
87
+ to turn on cname swapping. By specifying +:swap => true+ as an option, a newly launched environment will always try
88
+ to take over the CNAME from any currently running environments.
89
+
90
+ Let's try it (Note, if you want to run this sample, you will most likely need to pick a cname prefix other than
91
+ "example" as it likely not available.):
92
+
93
+ fum hello_cname.fum launch hello
94
+
95
+ Login to the AWS Management Console and observe your environment is launched correctly and has the correct CNAME, take
96
+ note of the environment name created, and then launch another:
97
+
98
+ fum hello_cname.fum launch hello
99
+
100
+ Afer the command is complete, login to the AWS Management Console and observe the newly created environemnt now
101
+ is using the specified CNAME, while the previously created environment is using a dynamically assigned CNAME.
102
+
103
+
104
+ == Multiple environments per stage
105
+
106
+ Stages typically have only one elastic beanstalk environment active at any given time. However, fum uses multiple
107
+ environments per stage combined with DNS to mask lifecycle changes from the end users of the application. When
108
+ deploying a new version of your application, or updaing an environment configuration, fum manages the environments
109
+ and DNS records to provide a seamless rollout for your users.
110
+
111
+ stage :multi
112
+ solution_stack "32bit Amazon Linux running Tomcat 7"
113
+ name timestamp_name('multi')
114
+ matcher timestamp_name_matcher('multi')
115
+ description "Environment multi launched on #{Time.now}."
116
+
117
+ end
118
+
119
+ The name directive tells fum what name to give an environment upon launch. You can provide a string as an argument
120
+ to the +name+ directive, but then fum will only be able to launch.
121
+
122
+ Launch two instances:
123
+
124
+ fum multi.fum launch multi
125
+
126
+ And the launch another:
127
+
128
+ fum multi.fum launch multi
129
+
130
+ You can login to the AWS Managment Console to view them, but you can also use the fum list command as follow:
131
+
132
+ fum multi.fum status multi
133
+
134
+ You will notice that the status command displays the name of each environment, followed by
135
+
136
+ fum multi.fm terminate --all multi
137
+
138
+ This will terminate all environments that match the
139
+
140
+ == Configuration templates
141
+
142
+ stage :production
143
+ template "production-config"
144
+ end
145
+
146
+ stage :latest
147
+ template "latest-config"
148
+ end
149
+
150
+ == Version promotion
151
+
152
+ Up to this point, we haven't talked about how fum handles application versions. You can specify what version a
153
+ particular stage uses using the +version+ directive as follows:
154
+
155
+ stage :last_release
156
+ version "last-release-label"
157
+ end
158
+
159
+ If you run:
160
+
161
+ fum launch last_release
162
+
163
+ It will launch an environment.
164
+ In a typical application environment, you will
165
+
166
+ # An environment that whose version is automatically updated via a build server.
167
+ stage :build do
168
+ template 'build-config'
169
+
170
+ zone 'example.com'
171
+ elb_alas 'build' # build.myapp.com will point to this environment.
172
+ end
173
+ end
174
+
175
+ stage :staging do
176
+ template 'staging-config'
177
+
178
+ version :from_stage => :build
179
+
180
+ zone 'example.com'
181
+ elb_alias 'staging'
182
+ end
183
+ end
184
+
185
+ stage :production do
186
+ template 'produciton-config'
187
+
188
+ version :from_stage => :staging
189
+
190
+ zone 'example.com'
191
+ elb_alias :apex
192
+ elb_alias 'www'
193
+ end
194
+ end
195
+
196
+ Let's launch it:
197
+
198
+
199
+ In this example, the "build" stage is
200
+
201
+ == Utility commands
202
+
203
+ Fum also provides a number of utility commands to aid in manging elastic beanstalk.
204
+
205
+ The *list* command allows you to list various beanstalk components. For example:
206
+
207
+ fum list stacks # List all solutions stacks
208
+ fum list env # Lists all environments
209
+
210
+ The *tail* command allows you to tail the event log
211
+
212
+ fum tail
213
+
214
+ The *template* command allows for manipulating configuration templates
215
+
216
+ == Why is the tool called "fum"?
217
+
218
+ Because fum is the {final word}[http://en.wikipedia.org/wiki/Fee-fi-fo-fum] in managing your
219
+ {AWS Elastic Beanstalk}[http://aws.amazon.com/elasticbeanstalk/] infrastructure.
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/fum ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'fum'
4
+
5
+ Fum::Application.new.start
@@ -0,0 +1,6 @@
1
+
2
+ application_name 'HelloWorld'
3
+
4
+ stage :hello do
5
+ solution_stack "32bit Amazon Linux running Tomcat 7"
6
+ end
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "fum/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "fum"
7
+ s.version = Fum::VERSION
8
+ s.authors = ["George Scott", "RumbleWare Inc."]
9
+ s.email = ["foss@rumbleware.com"]
10
+ s.homepage = "http://github.com/rumbleware/fum"
11
+ s.summary = "Management tool for AWS Elastic Beanstalk"
12
+ s.description = "fum helps you manage your AWS Elastic Beanstalk environments"
13
+
14
+ s.rubyforge_project = "fum"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ # specify any dependencies here; for example:
22
+ # s.add_development_dependency "rspec"
23
+ s.add_runtime_dependency "fog", "~>1.3.1"
24
+ s.add_runtime_dependency "trollop", "~>1.16.2"
25
+ s.add_runtime_dependency "json", "~>1.6.5"
26
+ #s.add_runtime_dependency "highline", "~>1.6.0"
27
+ end
@@ -0,0 +1,12 @@
1
+
2
+ require 'fum/version'
3
+
4
+ require 'fum/util'
5
+ require 'fum/command_manager'
6
+ require 'fum/command'
7
+ require 'fum/dns'
8
+ require 'fum/stage_analyzer'
9
+
10
+ require 'fum/application'
11
+
12
+ require 'pp'
@@ -0,0 +1,117 @@
1
+
2
+ require 'rubygems'
3
+ require 'fum/lang/fum_file'
4
+ require 'trollop'
5
+ require 'fog'
6
+
7
+
8
+ module Fum
9
+
10
+ def die(msg)
11
+ puts msg
12
+ exit 1
13
+ end
14
+
15
+ class Application
16
+ include CommandManager
17
+
18
+ attr_accessor :main_decl
19
+
20
+ command :events
21
+ command :launch
22
+ command :list
23
+ command :repair
24
+ command :status
25
+ command :tail
26
+ command :template
27
+ command :terminate
28
+
29
+ def start
30
+ global_options = process_command_line
31
+
32
+ @beanstalk = Fog::AWS[:beanstalk]
33
+
34
+ command = commands[@command_name.to_sym]
35
+
36
+ if global_options[:verbose]
37
+ puts "Loading definition file #{@filename}"
38
+ end
39
+
40
+ @main_decl = load_file(@filename)
41
+
42
+ if command
43
+ options = parse_command_options(@command_name.to_sym)
44
+ options.merge!(global_options)
45
+ run_command(@command_name.to_sym, options)
46
+ else
47
+ parser.die "Unknown command '#{@command_name}'", nil
48
+ end
49
+
50
+ end
51
+
52
+ def process_command_line
53
+ #global_options = Trollop::options do
54
+
55
+ sub_commands = commands.keys.map { |c| c.to_s }
56
+
57
+ parser = Trollop::Parser.new do
58
+ version "fum #{VERSION}"
59
+ banner <<-EOS
60
+ usage: fum <filename> [options] <command> [command-options]
61
+
62
+ Commands are (use --help after command to see additional usage):
63
+ launch
64
+ list
65
+
66
+ where [options] are:
67
+ EOS
68
+
69
+ opt :credentials, "File containing AWS credentials to use", :type => :string
70
+ opt :noop, "Do nothing, print steps to be executed.", :default => false
71
+ opt :verbose, "Print verbose output.", :default => false
72
+ stop_on sub_commands #commands.keys.map { |c| c.to_s }
73
+ end
74
+
75
+ global_options = Trollop::with_standard_exception_handling parser do
76
+ o = parser.parse ARGV
77
+ raise Trollop::HelpNeeded if ARGV.empty?
78
+ @filename = ARGV.shift
79
+ raise Trollop::HelpNeeded if ARGV.empty?
80
+ @command_name = ARGV.shift
81
+ o
82
+ end
83
+
84
+ config = load_credentials
85
+
86
+ Fog.credentials = {
87
+ :aws_access_key_id => config[:access_key_id],
88
+ :aws_secret_access_key => config[:secret_access_key]
89
+ }
90
+
91
+ global_options
92
+ end
93
+
94
+
95
+ def load_file(filename)
96
+ Fum::Lang::FumFile.load(filename)
97
+ end
98
+
99
+ def load_credentials
100
+ require 'yaml'
101
+
102
+ config_file = File.expand_path('~/.fum')
103
+
104
+ options = YAML.load(File.read(config_file))
105
+
106
+ # Convert to symbols
107
+ options.each { |key, value|
108
+ options.delete(key)
109
+ options[key.to_sym] = value
110
+ }
111
+ options
112
+ end
113
+
114
+ end
115
+
116
+ end
117
+
@@ -0,0 +1,21 @@
1
+ module Fum
2
+
3
+ class Command
4
+ include Fum::Util
5
+
6
+ def initialize(application)
7
+ @application = application
8
+ end
9
+
10
+ def parse_options
11
+ {}
12
+ end
13
+
14
+ # Return the stage declaration given app and name.
15
+ def stage(app, name)
16
+ app.stages[name.to_s] || die("Unknown stage '#{name}'")
17
+ end
18
+
19
+ end
20
+
21
+ end