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.
- data/.gitignore +5 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/Guardfile +14 -0
- data/README.markdown +182 -0
- data/Rakefile +11 -0
- data/bin/.gitignore +16 -0
- data/bin/migrant +7 -0
- data/features/basic_configuration.feature +47 -0
- data/features/step_definitions/configuration_steps.rb +60 -0
- data/features/support/env.rb +2 -0
- data/lib/migrant/bootstrappers/base.rb +38 -0
- data/lib/migrant/bootstrappers/ruby_local_192.rb +33 -0
- data/lib/migrant/bootstrappers.rb +2 -0
- data/lib/migrant/boxes.rb +55 -0
- data/lib/migrant/cli.rb +39 -0
- data/lib/migrant/clouds/aws.rb +28 -0
- data/lib/migrant/clouds/base.rb +100 -0
- data/lib/migrant/clouds/rackspace.rb +25 -0
- data/lib/migrant/clouds.rb +3 -0
- data/lib/migrant/environment.rb +127 -0
- data/lib/migrant/provisioners/base.rb +33 -0
- data/lib/migrant/provisioners/chef_solo.rb +57 -0
- data/lib/migrant/provisioners.rb +2 -0
- data/lib/migrant/ui.rb +24 -0
- data/lib/migrant/util/template_renderer.rb +15 -0
- data/lib/migrant/util.rb +5 -0
- data/lib/migrant/version.rb +3 -0
- data/lib/migrant.rb +15 -0
- data/migrant.gemspec +34 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/unit/aws_cloud_spec.rb +6 -0
- data/spec/unit/boxes_spec.rb +62 -0
- data/spec/unit/cloud_spec.rb +7 -0
- data/spec/unit/environment_spec.rb +45 -0
- data/spec/unit/rackspace_cloud_spec.rb +6 -0
- data/templates/chef/dna.json.erb +3 -0
- data/templates/chef/solo.rb.erb +5 -0
- metadata +225 -0
@@ -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
|
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
|
data/lib/migrant/util.rb
ADDED
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
|
data/spec/spec_helper.rb
ADDED
@@ -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,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
|