migrant-boxes 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+