stem 0.2

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2010 Peter van Hardenberg
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,44 @@
1
+ #Stem
2
+ ##EC2 made easy.
3
+
4
+ ##Introduction
5
+
6
+ Stem is a thin, light-weight EC2 instance management library which abstracts the Amazon EC2 API and provides an intuitive interface for designing, launching, and managing running instances.
7
+
8
+ Stem is named after the model it encourages -- simple AMIs created on demand with many running copies derived from that.
9
+
10
+ ##Usage
11
+
12
+ You can use Stem to manage your instances either from the commandline or directly via the library. You should create an instance which will serve as your "stem" and be converted into an AMI. Once you have tested this instance, create a snapshot of the instance, then use it by name to launch new instances with their own individual configuration.
13
+
14
+ Here's a simple example from the command line. Begin by launching the example prototype instance.
15
+
16
+ $ bin/stem launch chrysanthemum/postgres-prototype/config.json chrysanthemum/postgres-prototype/userdata.sh
17
+
18
+ The config.json file specifies which AMI to start from, and what kind of EBS drive configuration to use. It is important that the drives are specified in the configuration file as any drives attached to the instance after launch will not become part of the eventual AMI you are creating
19
+
20
+ You can monitor the instance's fabrication process via
21
+
22
+ $ stem list
23
+
24
+ The instance you created will boot, install some packages on top of a stock Ubuntu 10.4 AMI, then (if everything goes according to plan) shut itself down and go into a "stopped" state that indicates success. If any part of the stem fabrication fails, the instance will remain running. Once the instance reaches stopped, type
25
+
26
+ $ stem create postgres-server <instance-id>
27
+
28
+ The AMI may take as long as half an hour to build, depending on how the gremlins in EC2 are behaving on any given day. You can check on their progress with
29
+
30
+ $ bin/amy
31
+
32
+ If the AMI fabrication reaches the state "failed" you will have to manually reissue the `create` command and hope that the gremlins are more forgiving the second time around.
33
+
34
+ Now that you have a simple postgres-server, you'll want to boot it up and create a database on it with some unique credentials! One of the simplest ways to solve this problem is to provide the instance with a templated userdata script which will perform per-instance configuration. I like mustache for this purpose.
35
+
36
+ $ mustache userdata.sh.yaml postgres-server/userdata.sh.mustache > postgres-server/userdata.sh
37
+ $ stem launch postgres-server/config.json postgres-server/userdata.sh
38
+
39
+ You can, of course, delete the produced userdata.sh once the instance is launched.
40
+
41
+ ##Inspiration and Thanks
42
+
43
+ Stem is almost entirely based on Orion Henry's Judo gem, and Blake Mizerany's work on variously patton, carealot, napkin, and several other experiments. Thanks also for feedback, testing and patches from Adam Wiggins, Mark McGranahan, Noah Zoschke, and Jason Dusek.
44
+
data/bin/stem ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift File.dirname(__FILE__) + "/../lib"
3
+ require 'stem'
4
+
5
+ # Help is the default.
6
+ ARGV << '-h' if ARGV.empty? && $stdin.tty?
7
+
8
+ # Process options
9
+ Stem::CLI.parse_options(ARGV) if $stdin.tty?
10
+
11
+ # Still here - run the command
12
+ Stem::CLI.dispatch_command(ARGV.shift, ARGV)
13
+
data/lib/stem/cli.rb ADDED
@@ -0,0 +1,91 @@
1
+ require 'optparse'
2
+
3
+ module Stem
4
+ Version = 0.2
5
+
6
+ module CLI
7
+ extend self
8
+
9
+ # Return a structure describing the options.
10
+ def parse_options(args)
11
+ opts = OptionParser.new do |opts|
12
+ opts.banner = "Usage: stem COMMAND ..."
13
+
14
+ opts.separator " "
15
+
16
+ opts.separator "Examples:"
17
+ opts.separator " $ stem launch prototype.config prototype-userdata.sh"
18
+ opts.separator " $ stem list"
19
+ opts.separator " $ stem create ami-name instance-id"
20
+
21
+ opts.separator " "
22
+ opts.separator "Options:"
23
+
24
+ opts.on("-v", "--version", "Print the version") do |v|
25
+ puts "Stem v#{Stem::Version}"
26
+ exit
27
+ end
28
+
29
+ opts.on_tail("-h", "--help", "Show this message") do
30
+ puts opts
31
+ exit
32
+ end
33
+ end
34
+
35
+ opts.separator ""
36
+
37
+ opts.parse!(args)
38
+ end
39
+
40
+ def dispatch_command command, arguments
41
+ case command
42
+ when "launch"
43
+ launch(*arguments)
44
+ when "create"
45
+ create(*arguments)
46
+ when "list"
47
+ list(*arguments)
48
+ when "describe"
49
+ describe(*arguments)
50
+ when "destroy"
51
+ destroy(*arguments)
52
+ when nil
53
+ puts "Please provide a command."
54
+ else
55
+ puts "Command \"#{command}\" not recognized."
56
+ end
57
+ end
58
+
59
+ def launch config_file = nil, userdata_file = nil
60
+ abort "No config file" unless config_file
61
+ userdata = File.new(userdata_file).read() if userdata_file
62
+ instance = Stem::Instance::launch(JSON.parse(File.new(config_file).read()), userdata)
63
+ puts "New instance ID: #{instance}"
64
+ end
65
+
66
+ def create name = nil, instance = nil
67
+ abort "Usage: create ami-name instance-to-capture" unless name && instance
68
+ image_id = Stem::Image::create(name, instance)
69
+ puts "New image ID: #{image_id}"
70
+ end
71
+
72
+ def describe what
73
+ require 'pp'
74
+ if (what[0..2] == "ami")
75
+ pp Stem::Image::describe(what)
76
+ elsif
77
+ pp Stem::Instance::describe(what)
78
+ end
79
+ end
80
+
81
+ def destroy instance = nil
82
+ abort "Usage: destroy instance-id" unless instance
83
+ Stem::Instance::destroy(instance)
84
+ end
85
+
86
+ def list *arguments
87
+ Stem::Instance::list(*arguments)
88
+ end
89
+ end
90
+ end
91
+
data/lib/stem/image.rb ADDED
@@ -0,0 +1,25 @@
1
+ module Stem
2
+ module Image
3
+ include Util
4
+ extend self
5
+
6
+ def create name, instance
7
+ description = {}
8
+ swirl.call("CreateImage", "InstanceId" => instance, "Name" => name, "Description" => "%%" + description.to_json)["imageId"]
9
+ end
10
+
11
+ def deregister image
12
+ swirl.call("DeregisterImage", "ImageId" => image)["return"]
13
+ end
14
+
15
+ def named name
16
+ i = swirl.call "DescribeImages", "Owner" => "self"
17
+ ami = i["imagesSet"].select {|m| m["name"] == name }.map { |m| m["imageId"] }.first
18
+ end
19
+
20
+ def describe image
21
+ swirl.call("DescribeImages", "ImageId" => image)["imagesSet"][0]
22
+ end
23
+ end
24
+ end
25
+
@@ -0,0 +1,111 @@
1
+ module Stem
2
+ module Instance
3
+ include Util
4
+ extend self
5
+
6
+ def launch config, userdata = nil
7
+ throw "No config provided" unless config
8
+
9
+ ami = nil
10
+ if config["ami"]
11
+ ami = config["ami"]
12
+ elsif config["ami-name"]
13
+ ami = Image::named(config["ami-name"])
14
+ throw "AMI named #{config["ami-name"]} was not found. (Does it need creating?)" unless ami
15
+ end
16
+ throw "No AMI specified." unless ami
17
+
18
+ opt = {
19
+ "MinCount" => "1",
20
+ "MaxCount" => "1",
21
+ "KeyName" => config["key_name"] || "default",
22
+ "InstanceType" => config["instance_type"] || "m1.small",
23
+ "ImageId" => ami
24
+ }
25
+ if config["availability_zone"]
26
+ opt.merge! "Placement.AvailabilityZone" => config["availability_zone"]
27
+ end
28
+
29
+ if config["volumes"]
30
+ devices = []
31
+ sizes = []
32
+ config["volumes"].each do |v|
33
+ puts "Adding a volume of #{v["size"]} to be mounted at #{v["device"]}."
34
+ devices << v["device"]
35
+ sizes << v["size"].to_s
36
+ end
37
+
38
+ opt.merge! "BlockDeviceMapping.#.Ebs.VolumeSize" => sizes,
39
+ "BlockDeviceMapping.#.DeviceName" => devices
40
+ end
41
+
42
+ if userdata
43
+ puts "Userdata provided, encoded and sent to the instance."
44
+ opt.merge!({ "UserData" => Base64.encode64(userdata)})
45
+ end
46
+
47
+ response = swirl.call "RunInstances", opt
48
+
49
+ puts "Success!"
50
+ response["instancesSet"].each do |i|
51
+ return i["instanceId"]
52
+ end
53
+ end
54
+ def restart instance_id
55
+ swirl.call "RebootInstances", "InstanceId" => instance_id
56
+ end
57
+
58
+ def destroy instance_id
59
+ swirl.call "TerminateInstances", "InstanceId" => instance_id
60
+ end
61
+
62
+ def stop instance_id
63
+ swirl.call "StopInstances", "InstanceId" => instance_id
64
+ end
65
+
66
+ def describe instance
67
+ throw "You must provide an instance ID to describe" unless instance
68
+ swirl.call("DescribeInstances", "InstanceId" => instance)["reservationSet"][0]["instancesSet"][0]
69
+ end
70
+
71
+ def list
72
+ instances = swirl.call("DescribeInstances")
73
+
74
+ lookup = {}
75
+ instances["reservationSet"].each do |r|
76
+ r["instancesSet"].each do |i|
77
+ lookup[i["imageId"]] = nil
78
+ end
79
+ end
80
+ amis = swirl.call("DescribeImages", "ImageId" => lookup.keys)["imagesSet"]
81
+
82
+ amis.each do |ami|
83
+ name = ami["name"] || ami["imageId"]
84
+ if !ami["description"] || ami["description"][0..1] != "%%"
85
+ # only truncate ugly names from other people (never truncate ours)
86
+ name.gsub!(/^(.{8}).+(.{8})/) { $1 + "..." + $2 }
87
+ name = "(foreign) " + name
88
+ end
89
+ lookup[ami["imageId"]] = name
90
+ end
91
+
92
+ puts "------------------------------------------"
93
+ puts "Instances"
94
+ puts "------------------------------------------"
95
+ instances["reservationSet"].each do |r|
96
+ r["instancesSet"].each do |i|
97
+ name = lookup[i["imageId"]]
98
+ puts "%-15s %-15s %-15s %s" % [ i["instanceId"], i["ipAddress"] || "no ip", i["instanceState"]["name"], name
99
+ end
100
+ end
101
+
102
+ puts "------------------------------------------"
103
+ puts "AMIs"
104
+ puts "------------------------------------------"
105
+ images = c.call "DescribeImages", "Owner" => "self"
106
+ images["imagesSet"].each do
107
+ puts "%-15s %s" % [ img["name"], img["imageId"] ]
108
+ end
109
+ end
110
+ end
111
+ end
data/lib/stem/ip.rb ADDED
@@ -0,0 +1,19 @@
1
+ module Stem
2
+ module Ip
3
+ include Util
4
+
5
+ extend self
6
+ def allocate
7
+ swirl.call("AllocateAddress")["publicIp"]
8
+ end
9
+
10
+ def associate ip, instance
11
+ result = swirl.call("AssociateAddress", "InstanceId" => instance, "PublicIp" => ip)["return"]
12
+ result == "true"
13
+ end
14
+
15
+ def release ip
16
+ result = swirl.call("ReleaseAddress", "PublicIp" => ip)
17
+ end
18
+ end
19
+ end
data/lib/stem/util.rb ADDED
@@ -0,0 +1,11 @@
1
+ module Stem
2
+ module Util
3
+ def swirl
4
+ @swirl ||= Swirl::EC2.new(
5
+ :aws_access_key_id => ENV['AWS_ACCESS_KEY_ID'],
6
+ :aws_secret_access_key => ENV['AWS_SECRET_ACCESS_KEY']
7
+ )
8
+ end
9
+ end
10
+ end
11
+
data/lib/stem.rb ADDED
@@ -0,0 +1,11 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+
3
+ require 'swirl'
4
+ require 'json'
5
+
6
+ require 'stem/cli'
7
+ require 'stem/util'
8
+ require 'stem/instance'
9
+ require 'stem/image'
10
+ require 'stem/ip'
11
+
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: stem
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 2
8
+ version: "0.2"
9
+ platform: ruby
10
+ authors:
11
+ - Peter van Hardenberg
12
+ autorequire:
13
+ bindir: bin
14
+ cert_chain: []
15
+
16
+ date: 2010-08-12 00:00:00 -07:00
17
+ default_executable:
18
+ dependencies:
19
+ - !ruby/object:Gem::Dependency
20
+ name: swirl
21
+ prerelease: false
22
+ requirement: &id001 !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "="
25
+ - !ruby/object:Gem::Version
26
+ segments:
27
+ - 1
28
+ - 5
29
+ - 2
30
+ version: 1.5.2
31
+ type: :runtime
32
+ version_requirements: *id001
33
+ description: minimalist EC2 instance management
34
+ email:
35
+ - pvh@heroku.com
36
+ executables:
37
+ - stem
38
+ extensions: []
39
+
40
+ extra_rdoc_files: []
41
+
42
+ files:
43
+ - LICENSE
44
+ - README.md
45
+ - lib/stem/cli.rb
46
+ - lib/stem/image.rb
47
+ - lib/stem/instance.rb
48
+ - lib/stem/ip.rb
49
+ - lib/stem/util.rb
50
+ - lib/stem.rb
51
+ has_rdoc: true
52
+ homepage: http://github.com/pvh/stem
53
+ licenses: []
54
+
55
+ post_install_message:
56
+ rdoc_options: []
57
+
58
+ require_paths:
59
+ - lib
60
+ required_ruby_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ segments:
65
+ - 0
66
+ version: "0"
67
+ required_rubygems_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ segments:
72
+ - 0
73
+ version: "0"
74
+ requirements: []
75
+
76
+ rubyforge_project:
77
+ rubygems_version: 1.3.6
78
+ signing_key:
79
+ specification_version: 2
80
+ summary: an EC2 instance management library designed to get out of your way and give you instances
81
+ test_files: []
82
+