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
data/Gemfile
ADDED
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.
|
data/README.rdoc
ADDED
@@ -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.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/fum
ADDED
data/examples/hello.fum
ADDED
data/fum.gemspec
ADDED
@@ -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
|
data/lib/fum.rb
ADDED
@@ -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
|
+
|
data/lib/fum/command.rb
ADDED
@@ -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
|