migrant-boxes 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ require 'migrant/clouds/base'
2
+ require 'migrant/clouds/aws'
3
+ require 'migrant/clouds/rackspace'
@@ -0,0 +1,127 @@
1
+ require 'fileutils'
2
+ require 'configuration'
3
+ require 'thor'
4
+ module Migrant
5
+
6
+ # Holds all the state for this run of Migrant
7
+ class Environment
8
+ DEFAULT_DATA_PATH = 'data'
9
+ DEFAULT_BOXES_PATH = File.join(DEFAULT_DATA_PATH,'migrant_boxes.yml')
10
+
11
+ def initialize(environment_name,config=nil)
12
+ @ui = Migrant::UI.new(::Thor::Base.shell.new)
13
+ @environment_name = environment_name
14
+ if config
15
+ @config = config
16
+ else
17
+ Kernel.load 'Migrantfile'
18
+ @config = Configuration.for('migrant')
19
+ end
20
+ if @environment_name.nil?
21
+ @ui.notice "Using default environment"
22
+ elsif @config.include?(@environment_name.to_sym)
23
+ @ui.notice "Using #{@environment_name} environment"
24
+ else
25
+ @ui.error "#{@environment_name} environment is not defined in the configuration file"
26
+ raise "Environment #{@environment_name} not found"
27
+ end
28
+
29
+ cloud_class = Migrant::Clouds::Base.registered(setting('provider.name'))
30
+ raise "Cannot find cloud '#{setting('provider.name')}'" if cloud_class.nil?
31
+ @cloud = cloud_class.new(self)
32
+ @bootstrapper = Migrant::Bootstrappers::Base.default.new(self)
33
+ @provisioner = Migrant::Provisioners::Base.registered(:chef_solo).new(self)
34
+ FileUtils.mkdir_p DEFAULT_DATA_PATH unless File.exists?(DEFAULT_DATA_PATH)
35
+ @boxes = Boxes.new(DEFAULT_BOXES_PATH).load
36
+ end
37
+
38
+ # Retrieve a setting by name. First try within the context of the current environment.
39
+ # If the property does not exist there, look in the default property definitions
40
+ def setting(name)
41
+ roots = []
42
+ if @environment_name
43
+ roots << @config.send(@environment_name)
44
+ end
45
+ roots << @config
46
+ roots.each do |root|
47
+ begin
48
+ setting_value = name.split('.').inject(root) { |node,prop| if node.include?(prop.to_sym) then node.send(prop) else raise "Undefined property #{prop} for #{node.name}" end}
49
+ return setting_value
50
+ rescue => e
51
+ # Fall through to next case
52
+ end
53
+ end
54
+ # If we get here the property does not exist
55
+ #XXX - should probably ask for a default, and if one is not provided, raise an error
56
+ nil
57
+ end
58
+
59
+ def up
60
+ connect
61
+ init_server
62
+ bootstrap
63
+ provision
64
+ end
65
+
66
+ def connect
67
+ ui.warn "Connecting to #{setting('provider.name')}"
68
+ @cloud.connect
69
+ end
70
+
71
+ def launch!
72
+ @cloud.bootstrap_server
73
+ @boxes.add(@environment_name,setting('provider.name'),@server.id)
74
+ @boxes.save
75
+
76
+ # persist metadata about server so we can load it later
77
+ @cloud.execute ['pwd']
78
+ end
79
+
80
+ # If the server exists, connect to it. If not, bootstrap it
81
+ def init_server
82
+ box = @boxes[@environment_name]
83
+ if box.nil?
84
+ launch!
85
+ else
86
+ @cloud.connect_to_server(box)
87
+ end
88
+ end
89
+
90
+ def info
91
+ box = @boxes[@environment_name]
92
+ if box.nil?
93
+ @ui.info "There are no boxes running"
94
+ else
95
+ connect
96
+ @cloud.connect_to_server(box)
97
+ @cloud.log_server_info
98
+ end
99
+ end
100
+
101
+ def bootstrap
102
+ @bootstrapper.run unless @bootstrapper.bootstrapped?
103
+ end
104
+
105
+ def provision
106
+ @provisioner.upload
107
+ @provisioner.prepare
108
+ @provisioner.run
109
+ end
110
+
111
+ def destroy
112
+ box = @boxes.first
113
+
114
+ if box.nil?
115
+ @ui.error "There are currently no boxes running"
116
+ else
117
+ if @cloud.destroy(box)
118
+ FileUtils.rm DEFAULT_BOXES_PATH
119
+ end
120
+ end
121
+ end
122
+
123
+ attr_accessor :server
124
+ attr_reader :ui
125
+ attr_reader :cloud
126
+ end
127
+ end
@@ -0,0 +1,33 @@
1
+ module Migrant
2
+ module Provisioners
3
+ class Base
4
+ def self.register(shortcut)
5
+ @@provisioners ||= {}
6
+ @@provisioners[shortcut] = self
7
+ end
8
+
9
+ def self.registered(shortcut)
10
+ @@provisioners[shortcut]
11
+ end
12
+
13
+ def initialize(env)
14
+ @environment = env
15
+ end
16
+
17
+ # Upload any necessary files
18
+ def upload
19
+ raise "invalid provisioner"
20
+ end
21
+
22
+ # Any pre-provisioning preparation
23
+ def prepare
24
+ raise "invalid provisioner"
25
+ end
26
+
27
+ # Run the provisioner
28
+ def run
29
+ raise "invalide provisioner"
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,57 @@
1
+ require 'vagrant'
2
+
3
+ module Migrant
4
+ module Provisioners
5
+ class ChefSolo < Base
6
+ include Util
7
+
8
+ register :chef_solo
9
+ def initialize(env)
10
+ super
11
+ end
12
+ def prepare
13
+ end
14
+ def upload
15
+ server = @environment.server
16
+ vagrant_environment = Vagrant::Environment.new
17
+ vagrant_provisioner_config = vagrant_environment.config.vm.provisioners.first.config
18
+ cookbook_paths = vagrant_provisioner_config.cookbooks_path
19
+ roles_path = vagrant_provisioner_config.roles_path
20
+
21
+ # Delete old stuff
22
+ server.ssh "rm -rf /tmp/migrant && mkdir -p /tmp/migrant"
23
+
24
+ @environment.ui.info "Uploading Cookbooks"
25
+ cookbook_dest_paths = []
26
+ cookbook_paths.each do |path|
27
+ dest_path = "/tmp/migrant/#{File.basename(path)}"
28
+ server.scp path, dest_path, :recursive => true
29
+ cookbook_dest_paths << dest_path
30
+ end
31
+ role_dest_paths = []
32
+ dest_path = "/tmp/migrant/#{File.basename(roles_path)}"
33
+ server.scp roles_path, dest_path, :recursive => true
34
+ role_dest_paths << dest_path
35
+
36
+ @environment.ui.info "Generating Chef Solo Config Files"
37
+ File.open('/tmp/migrant-solo.rb','w') do |f|
38
+ f.write(TemplateRenderer.render('chef/solo.rb', {
39
+ :cookbook_path => cookbook_dest_paths,
40
+ :role_path => role_dest_paths
41
+ }))
42
+ end
43
+ server.scp "/tmp/migrant-solo.rb","/tmp/migrant/solo.rb"
44
+ File.open('/tmp/migrant-dna.json','w') do |f|
45
+ f.write(TemplateRenderer.render('chef/dna.json', {
46
+ :run_list => vagrant_provisioner_config.run_list
47
+ }))
48
+ end
49
+ server.scp "/tmp/migrant-dna.json","/tmp/migrant/dna.json"
50
+ end
51
+
52
+ def run
53
+ @environment.cloud.execute(['sudo chef-solo -c /tmp/migrant/solo.rb -j /tmp/migrant/dna.json'])
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,2 @@
1
+ require 'migrant/provisioners/base'
2
+ require 'migrant/provisioners/chef_solo'
data/lib/migrant/ui.rb ADDED
@@ -0,0 +1,24 @@
1
+ module Migrant
2
+ # Handles displaying messages to the user.
3
+ class UI
4
+ # shell - a Thor::Shell instance
5
+ def initialize(shell)
6
+ @shell = shell
7
+ end
8
+ def info(msg)
9
+ @shell.say(msg,nil)
10
+ end
11
+ def warn(msg)
12
+ @shell.say(msg,:yellow)
13
+ end
14
+ def error(msg)
15
+ @shell.say(msg,:red)
16
+ end
17
+ def notice(msg)
18
+ @shell.say(msg,:green)
19
+ end
20
+ def yes?(msg,opts)
21
+ @shell.yes?(msg,opts)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,15 @@
1
+ require 'vagrant'
2
+
3
+ module Migrant
4
+ module Util
5
+ class TemplateRenderer < ::Vagrant::Util::TemplateRenderer
6
+ # Returns the full path to the template, taking into accoun the gem directory
7
+ # and adding the `.erb` extension to the end.
8
+ #
9
+ # @return [String]
10
+ def full_template_path
11
+ Migrant.source_root.join('templates', "#{template}.erb").to_s.squeeze("/")
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,5 @@
1
+ module Migrant
2
+ module Util
3
+ autoload :TemplateRenderer, 'migrant/util/template_renderer'
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ module Migrant
2
+ VERSION = "0.1.0"
3
+ end
data/lib/migrant.rb ADDED
@@ -0,0 +1,15 @@
1
+ module Migrant
2
+ autoload :CLI, 'migrant/cli'
3
+ autoload :Config, 'migrant/config'
4
+ autoload :Environment, 'migrant/environment'
5
+ autoload :UI, 'migrant/ui'
6
+ autoload :Boxes, 'migrant/boxes'
7
+ autoload :Util, 'migrant/util'
8
+
9
+ def self.source_root
10
+ @source_root ||= Pathname.new(File.expand_path('../../', __FILE__))
11
+ end
12
+ end
13
+ require 'migrant/clouds'
14
+ require 'migrant/provisioners'
15
+ require 'migrant/bootstrappers'
data/migrant.gemspec ADDED
@@ -0,0 +1,34 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "migrant/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "migrant-boxes"
7
+ s.version = Migrant::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Cameron Pope"]
10
+ s.email = ["cameron@theaboutbox.com"]
11
+ s.homepage = "http://github.com/theaboutbox/migrant"
12
+ s.summary = %q{Take your Vagrant to the Cloud}
13
+ s.description = %q{A simple an opinionated way to replicate a Vagrant setup on EC2 or Rackspace}
14
+
15
+ s.rubyforge_project = "migrant-boxes"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = 'migrant'
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_dependency 'thor'
23
+ s.add_dependency 'fog'
24
+ s.add_dependency 'vagrant'
25
+ s.add_dependency 'configuration'
26
+ s.add_development_dependency 'rspec'
27
+ s.add_development_dependency 'rake'
28
+ s.add_development_dependency 'pry'
29
+ s.add_development_dependency 'cucumber'
30
+ s.add_development_dependency 'guard-rspec'
31
+ s.add_development_dependency 'guard-cucumber'
32
+ s.add_development_dependency 'rb-fsevent' if RUBY_PLATFORM =~ /darwin/
33
+ s.add_development_dependency 'growl_notify' if RUBY_PLATFORM =~ /darwin/
34
+ end
@@ -0,0 +1,2 @@
1
+ $:.push('./lib')
2
+ require 'migrant'
@@ -0,0 +1,6 @@
1
+ require 'spec_helper'
2
+
3
+ describe Migrant::Clouds::AWS do
4
+ it "defaults to smallest instance"
5
+ it "defaults to Ubuntu lucid"
6
+ end
@@ -0,0 +1,62 @@
1
+ require 'spec_helper'
2
+ require 'tempfile'
3
+
4
+ describe Migrant::Boxes do
5
+ before do
6
+ tmpfile = Tempfile.new(['boxes','.yml'])
7
+ @path = tmpfile.path
8
+ tmpfile.close
9
+ tmpfile.unlink
10
+ end
11
+
12
+ after do
13
+ File.unlink @path
14
+ end
15
+
16
+ def box_yaml
17
+ YAML.load(File.read @path)
18
+ end
19
+
20
+ def boxes
21
+ Migrant::Boxes.new(@path).load
22
+ end
23
+
24
+ context 'default environment' do
25
+ context 'writing box information' do
26
+ before do
27
+ b = Migrant::Boxes.new(@path)
28
+ b.add(nil,'aws','i-abc123')
29
+ b.save
30
+ end
31
+
32
+ it 'writes a valid yaml file' do
33
+ expect { box_yaml }.not_to raise_error
34
+ end
35
+
36
+ it 'writes the provider' do
37
+ boxes[nil].provider.should == 'aws'
38
+ end
39
+
40
+ it 'writes the instance id' do
41
+ boxes[nil].id.should == 'i-abc123'
42
+ end
43
+
44
+ end
45
+ end
46
+ context 'named environment' do
47
+ before do
48
+ b = Migrant::Boxes.new(@path)
49
+ b.add(nil,'aws','i-abc123')
50
+ b.add('testing','rackspace','123456')
51
+ b.save
52
+ end
53
+
54
+ it 'writes the provider' do
55
+ boxes['testing'].provider.should == 'rackspace'
56
+ end
57
+
58
+ it 'writes an instance id' do
59
+ boxes['testing'].id.should == '123456'
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,7 @@
1
+ require 'spec_helper'
2
+
3
+ describe Migrant::Clouds::Base do
4
+ it "loads ssh key credentials for the current environment"
5
+ it "loads image information for current environment"
6
+ it "loads image id for current environment"
7
+ end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+
3
+ describe Migrant::Environment do
4
+ before do
5
+ Configuration.for('migrant') {
6
+ provider {
7
+ name 'rackspace'
8
+ flavor_id 1
9
+ }
10
+
11
+ ssh {
12
+ public_key 'test'
13
+ private_key 'test'
14
+ }
15
+
16
+ staging {
17
+ # Defaults
18
+ }
19
+
20
+ production {
21
+ provider {
22
+ flavor_id 2
23
+ }
24
+ }
25
+ }
26
+
27
+ @cfg = Configuration.for('migrant')
28
+ @staging_env = Migrant::Environment.new('staging',@cfg)
29
+ @production_env = Migrant::Environment.new('production',@cfg)
30
+ @default_env = Migrant::Environment.new(nil,@cfg)
31
+ end
32
+
33
+ it "retrieves properties when there is no environment set" do
34
+ @default_env.setting('provider.flavor_id').should == 1
35
+ end
36
+ it "retrieves configuration properties for the current environment" do
37
+ @production_env.setting('provider.flavor_id').should == 2
38
+ end
39
+ it "defaults properties that do not exist in the current environment" do
40
+ @staging_env.setting('provider.flavor_id').should == 1
41
+ end
42
+ it "provides a useful message for a non-existent environment" do
43
+ expect {foobar_env = Migrant::Environment.new('foobar',@cfg)}.to raise_error
44
+ end
45
+ end
@@ -0,0 +1,6 @@
1
+ require 'spec_helper'
2
+
3
+ describe Migrant::Clouds::Rackspace do
4
+ it "defaults image id to ubuntu lucid"
5
+ it "defaults flavor to smallest server"
6
+ end
@@ -0,0 +1,3 @@
1
+ {
2
+ "run_list":<%= run_list.inspect %>
3
+ }
@@ -0,0 +1,5 @@
1
+ file_cache_path "/tmp/migrant"
2
+ cookbook_path <%= cookbook_path.inspect %>
3
+ role_path <%= role_path.inspect %>
4
+ log_level :info
5
+