skewer 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.idea/encodings.xml +5 -0
- data/.idea/misc.xml +8 -0
- data/.idea/modules.xml +9 -0
- data/.idea/skewer.iml +61 -0
- data/.idea/vcs.xml +7 -0
- data/.rvmrc +2 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +146 -0
- data/LICENSE +13 -0
- data/README.md +77 -0
- data/Rakefile +59 -0
- data/Vagrantfile +5 -0
- data/assets/Gemfile +5 -0
- data/assets/rubygems.sh +39 -0
- data/bin/skewer +101 -0
- data/features/aws.feature +45 -0
- data/features/bootstrapper.feature +14 -0
- data/features/configuration.feature +15 -0
- data/features/delete.feature +22 -0
- data/features/hooks.feature +15 -0
- data/features/interface.feature +61 -0
- data/features/provision.feature +38 -0
- data/features/rackspace.feature +34 -0
- data/features/step_definitions/cucumber_steps.rb +43 -0
- data/features/step_definitions/delete.rb +32 -0
- data/features/support/env.rb +6 -0
- data/features/support/puppetcode/manifests/classes/foobar.pp +4 -0
- data/features/support/puppetcode/manifests/classes/foobroken.pp +4 -0
- data/features/support/puppetcode/manifests/classes/role.pp +3 -0
- data/features/support/puppetcode/manifests/site.pp +2 -0
- data/features/support/puppetcode/modules/foo/manifests/bar.pp +3 -0
- data/features/support/puppetcode/modules/foo/manifests/broken.pp +8 -0
- data/features/support/puppetcode/modules/puppet/manifests/munge.pp +5 -0
- data/features/update.feature +29 -0
- data/lib/aws/node.rb +52 -0
- data/lib/aws/security_group.rb +37 -0
- data/lib/aws/service.rb +21 -0
- data/lib/bootstrapper.rb +112 -0
- data/lib/cli.rb +96 -0
- data/lib/config.rb +67 -0
- data/lib/cuke.rb +26 -0
- data/lib/ersatz/ersatz_node.rb +31 -0
- data/lib/ersatz/ssh_result.rb +15 -0
- data/lib/eucalyptus.rb +15 -0
- data/lib/hooks.rb +22 -0
- data/lib/node.erb +5 -0
- data/lib/node.rb +43 -0
- data/lib/parser.rb +92 -0
- data/lib/puppet.rb +53 -0
- data/lib/puppet_node.rb +26 -0
- data/lib/puppet_runtime_error.rb +6 -0
- data/lib/rackspace/images.rb +25 -0
- data/lib/rackspace/node.rb +60 -0
- data/lib/skewer.rb +13 -0
- data/lib/skewer/version.rb +3 -0
- data/lib/source.rb +54 -0
- data/lib/stub_node.rb +25 -0
- data/lib/util.rb +17 -0
- data/skewer.gemspec +30 -0
- data/spec/aws/node_spec.rb +70 -0
- data/spec/aws/security_group_spec.rb +20 -0
- data/spec/aws/service_spec.rb +31 -0
- data/spec/bootstrapper_spec.rb +116 -0
- data/spec/cli_spec.rb +71 -0
- data/spec/config_spec.rb +68 -0
- data/spec/cuke_spec.rb +46 -0
- data/spec/ersatz_node_spec.rb +9 -0
- data/spec/ersatz_ssh_result_spec.rb +16 -0
- data/spec/hooks_spec.rb +19 -0
- data/spec/logger_spec.rb +22 -0
- data/spec/parser_spec.rb +93 -0
- data/spec/puppet.rb +47 -0
- data/spec/puppet_node_spec.rb +31 -0
- data/spec/rackspace/images_spec.rb +37 -0
- data/spec/rackspace/node_spec.rb +30 -0
- data/spec/source_spec.rb +45 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/support/features/example.feature +9 -0
- data/spec/support/features/step_definitions/example.rb +8 -0
- data/spec/util_spec.rb +27 -0
- metadata +288 -0
data/lib/cuke.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
module Skewer
|
2
|
+
# Runs the Cucumber features for a given directory.
|
3
|
+
#
|
4
|
+
# Specifically, the tests related to infrastructure that skewer
|
5
|
+
# will map against to check if the node has been successfully
|
6
|
+
# built.
|
7
|
+
class Cuke
|
8
|
+
class CukeError < RuntimeError; end
|
9
|
+
|
10
|
+
def initialize(dir = nil)
|
11
|
+
raise "you must provide a valid directory for features to be executed within" if dir.nil? or dir.class != String or !directory_exists?(dir)
|
12
|
+
@dir = dir
|
13
|
+
end
|
14
|
+
|
15
|
+
def directory_exists?(dir)
|
16
|
+
File.directory?(dir)
|
17
|
+
end
|
18
|
+
|
19
|
+
def run
|
20
|
+
result = `cucumber #{@dir}`
|
21
|
+
parsed = result.match(/failed/)[0] rescue false
|
22
|
+
raise CukeError, "One of the cuke features failed!\n\n#{result}" if parsed
|
23
|
+
result
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'ersatz/ssh_result.rb'
|
2
|
+
|
3
|
+
module Skewer
|
4
|
+
# fakes a fog node
|
5
|
+
class ErsatzNode
|
6
|
+
attr_accessor :username, :dns_name
|
7
|
+
|
8
|
+
def initialize(hostname, user)
|
9
|
+
@dns_name = hostname
|
10
|
+
@username = user
|
11
|
+
end
|
12
|
+
|
13
|
+
def ssh(command)
|
14
|
+
full_ssh_command = "ssh -l #{@username} #{@dns_name} '#{command}'"
|
15
|
+
Skewer.logger.debug full_ssh_command
|
16
|
+
stdout = `#{full_ssh_command}`
|
17
|
+
result = ErsatzSSHResult.new(command, stdout, $?.exitstatus)
|
18
|
+
[result]
|
19
|
+
end
|
20
|
+
|
21
|
+
def scp(file, dest)
|
22
|
+
`scp #{file} #{@username}@#{@dns_name}:#{dest}`
|
23
|
+
end
|
24
|
+
|
25
|
+
def destroy
|
26
|
+
end
|
27
|
+
|
28
|
+
def wait_for
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'skewer'
|
2
|
+
|
3
|
+
module Skewer
|
4
|
+
# fakes a fog SSH result
|
5
|
+
class ErsatzSSHResult
|
6
|
+
attr_accessor :command, :stdout, :status
|
7
|
+
|
8
|
+
def initialize(command, stdout, status)
|
9
|
+
@command = command
|
10
|
+
@stdout = stdout
|
11
|
+
@status = status
|
12
|
+
Skewer.logger.debug self.stdout
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/eucalyptus.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'fog'
|
3
|
+
|
4
|
+
module Skewer
|
5
|
+
# instantiates a eucalyptus endpoint
|
6
|
+
class Eucalyptus
|
7
|
+
def initialize
|
8
|
+
compute = Fog::Compute.new({
|
9
|
+
:provider => 'AWS',
|
10
|
+
:endpoint => 'http://localhost:4567'
|
11
|
+
})
|
12
|
+
@node = compute.servers.bootstrap()
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/hooks.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
module Skewer
|
2
|
+
require 'config'
|
3
|
+
|
4
|
+
class Hooks
|
5
|
+
attr_writer :command
|
6
|
+
|
7
|
+
def initialize(host_name)
|
8
|
+
@command = SkewerConfig.instance.get('hook')
|
9
|
+
@host_name = host_name
|
10
|
+
end
|
11
|
+
|
12
|
+
def run
|
13
|
+
return_code = false
|
14
|
+
unless @command.nil?
|
15
|
+
Skewer.logger.debug "Running hooks ..."
|
16
|
+
`#{@command} #{@host_name}`
|
17
|
+
return_code = $? == 0 ? true : false
|
18
|
+
end
|
19
|
+
return_code
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/node.erb
ADDED
data/lib/node.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
module Skewer
|
2
|
+
# responsible for talking to remote machines
|
3
|
+
class Node
|
4
|
+
attr_reader :username
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@address = nil
|
8
|
+
@username = nil
|
9
|
+
@password = nil
|
10
|
+
@pubkey = nil
|
11
|
+
@ssh = Fog::SSH::Real.new(
|
12
|
+
@address,
|
13
|
+
@username,
|
14
|
+
{:password => @password}
|
15
|
+
)
|
16
|
+
install_pubkey(@ssh, @pubkey)
|
17
|
+
end
|
18
|
+
|
19
|
+
def install_pubkey(ssh, key)
|
20
|
+
key_file = File.read(key).gsub("\n",'')
|
21
|
+
ssh(['mkdir -p .ssh', 'chmod 0700 .ssh',"echo #{key_file} > .ssh/authorized_keys"])
|
22
|
+
end
|
23
|
+
|
24
|
+
def dns_name
|
25
|
+
@address
|
26
|
+
end
|
27
|
+
|
28
|
+
def wait_for(*blk)
|
29
|
+
end
|
30
|
+
|
31
|
+
def destroy
|
32
|
+
end
|
33
|
+
|
34
|
+
def ssh(commands)
|
35
|
+
results = @ssh.run(commands)
|
36
|
+
if results.is_a?(Array)
|
37
|
+
results.each {|result| Skewer.logger.debug result.stdout }
|
38
|
+
else
|
39
|
+
Skewer.logger.debug results.stdout
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/parser.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'fog'
|
2
|
+
require 'cli'
|
3
|
+
require 'aws/node'
|
4
|
+
require "rackspace/node"
|
5
|
+
|
6
|
+
module Skewer
|
7
|
+
class CLI
|
8
|
+
# Parses the CLI input and makes sure that it's clean.
|
9
|
+
class Parser
|
10
|
+
def initialize(type = nil, options = {})
|
11
|
+
# base case tests that we have input that we accept.
|
12
|
+
Fog.mock! if options[:mock]
|
13
|
+
validate_options(options, type)
|
14
|
+
|
15
|
+
if type == 'delete'
|
16
|
+
case options[:kind]
|
17
|
+
when :ec2
|
18
|
+
node = AWS::Node.find_by_name(options[:host])
|
19
|
+
when :rackspace
|
20
|
+
node = Rackspace::Node.find_by_ip(options[:host])
|
21
|
+
else
|
22
|
+
raise("#{options[:kind]} not found")
|
23
|
+
end
|
24
|
+
destroy_node(node, options)
|
25
|
+
else
|
26
|
+
Skewer::CLI.bootstrap_and_go(options)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def destroy_node(node, options)
|
31
|
+
if node
|
32
|
+
node.destroy
|
33
|
+
Skewer.logger.info("#{options[:host]} deleted.")
|
34
|
+
else
|
35
|
+
abort("#{options[:host]} not found.")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def validate_options(options, type)
|
40
|
+
abort(usage) if type.nil? and options.empty?
|
41
|
+
abort(usage) unless ['provision', 'update', 'delete'].include? type
|
42
|
+
abort("A key (--key KEY) must be provided if using EC2") if options[:kind] == :ec2 && !options[:key_name]
|
43
|
+
if type == 'provision'
|
44
|
+
unless options[:kind] && options[:image] && options[:role] && !options[:help]
|
45
|
+
abort(provision_usage)
|
46
|
+
end
|
47
|
+
elsif type == 'update'
|
48
|
+
unless options[:host] && options[:user] && !options[:help]
|
49
|
+
abort(update_usage)
|
50
|
+
end
|
51
|
+
elsif type == 'delete'
|
52
|
+
unless options[:kind] && options[:host] && !options[:help]
|
53
|
+
abort(delete_usage)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def usage
|
59
|
+
out = <<EOF
|
60
|
+
Usage: skewer COMMAND [options]
|
61
|
+
|
62
|
+
The available skewer commands are:
|
63
|
+
provision spawn a new VM via a cloud system and provision it with puppet code
|
64
|
+
update update the puppet code on a machine that you've already provisioned
|
65
|
+
delete deletes the given host from the provided cloud provider
|
66
|
+
EOF
|
67
|
+
out.strip
|
68
|
+
end
|
69
|
+
|
70
|
+
def provision_usage
|
71
|
+
out = <<EOF
|
72
|
+
Usage: skewer provision --cloud <which cloud> --image <AWS image> --role <puppet role class>
|
73
|
+
EOF
|
74
|
+
out.strip
|
75
|
+
end
|
76
|
+
|
77
|
+
def update_usage
|
78
|
+
out = <<EOF
|
79
|
+
Usage: skewer update --host <host> --user <user with sudo rights> --role <puppet role class>
|
80
|
+
EOF
|
81
|
+
out.strip
|
82
|
+
end
|
83
|
+
|
84
|
+
def delete_usage
|
85
|
+
out = <<EOF
|
86
|
+
Usage: skewer delete --cloud <which cloud> --host <host>
|
87
|
+
EOF
|
88
|
+
out.strip
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
data/lib/puppet.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'skewer'
|
2
|
+
|
3
|
+
module Skewer
|
4
|
+
require 'puppet_runtime_error'
|
5
|
+
# responsible for executing puppet
|
6
|
+
class Puppet
|
7
|
+
def arguments
|
8
|
+
[
|
9
|
+
"--modulepath modules",
|
10
|
+
"--vardir /var/lib/puppet"
|
11
|
+
].join(' ')
|
12
|
+
end
|
13
|
+
|
14
|
+
def bundle
|
15
|
+
"/var/lib/gems/1.8/bin/bundle"
|
16
|
+
end
|
17
|
+
|
18
|
+
def command_string(username, options)
|
19
|
+
@command_line = "cd infrastructure"
|
20
|
+
if username == 'root'
|
21
|
+
@command_line << " &&"
|
22
|
+
else
|
23
|
+
@command_line << " && sudo"
|
24
|
+
end
|
25
|
+
@command_line << " #{self.bundle} exec"
|
26
|
+
@command_line << " puppet apply"
|
27
|
+
@command_line << " manifests/site.pp"
|
28
|
+
@command_line << " --color false"
|
29
|
+
@command_line << " #{arguments}"
|
30
|
+
if options[:noop]
|
31
|
+
@command_line << " --noop"
|
32
|
+
end
|
33
|
+
@command_line
|
34
|
+
end
|
35
|
+
|
36
|
+
def run(node, options)
|
37
|
+
command = command_string(node.username, options)
|
38
|
+
result = node.ssh(command)[0]
|
39
|
+
if result.status != 0
|
40
|
+
Skewer.logger.debug result.stdout
|
41
|
+
raise PuppetRuntimeError, "Puppet failed"
|
42
|
+
else
|
43
|
+
Skewer.logger.debug "Puppet run succeeded"
|
44
|
+
end
|
45
|
+
result
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.run(node, options)
|
49
|
+
this = self.new
|
50
|
+
this.run(node, options)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/lib/puppet_node.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
module Skewer
|
2
|
+
# responsible for creating a node definition
|
3
|
+
class PuppetNode
|
4
|
+
attr_accessor :nodes, :puppet_repo
|
5
|
+
|
6
|
+
def initialize(nodes = { :default =>:noop})
|
7
|
+
@nodes = nodes
|
8
|
+
config = SkewerConfig.instance
|
9
|
+
@puppet_repo = config.get(:puppet_repo)
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_s
|
13
|
+
require 'erb'
|
14
|
+
lib_dir = File.dirname(__FILE__)
|
15
|
+
template = ERB.new(File.read(File.join(lib_dir, 'node.erb')))
|
16
|
+
template.result(binding)
|
17
|
+
end
|
18
|
+
|
19
|
+
def render
|
20
|
+
node_file_path = File.join(@puppet_repo, 'manifests','nodes.pp')
|
21
|
+
file = File.new(node_file_path, 'w+')
|
22
|
+
file << self.to_s
|
23
|
+
file.close
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Skewer
|
2
|
+
module Rackspace
|
3
|
+
class Images
|
4
|
+
attr_reader :supported
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@supported = {
|
8
|
+
'ubuntu1004' => { :id => 112, :name => "Ubuntu 10.04 LTS"},
|
9
|
+
'ubuntu1104' => { :id => 115, :name => "Ubuntu 11.04"},
|
10
|
+
'ubuntu1110' => { :id => 119, :name => "Ubuntu 11.10"},
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
# If provided a name for an image, give back the ID.
|
15
|
+
def get_id(name)
|
16
|
+
return @supported['ubuntu1004'][:id] if name.nil?
|
17
|
+
name = Integer(name) rescue name
|
18
|
+
return name if name.class == Fixnum
|
19
|
+
return @supported['ubuntu1004'][:id] if name.class != String
|
20
|
+
raise "An image with the name '#{name}' doesn't exist" if !@supported.has_key?(name)
|
21
|
+
@supported[name][:id]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'fog'
|
2
|
+
require 'rackspace/images'
|
3
|
+
|
4
|
+
module Skewer
|
5
|
+
module Rackspace
|
6
|
+
# Build out a Rackspace node using Fog.
|
7
|
+
class Node
|
8
|
+
attr_reader :node
|
9
|
+
|
10
|
+
# By default, boot an Ubuntu 10.04 LTS (lucid) server.
|
11
|
+
def initialize(flavor = 1, image = 112, name = 'my_server', instance = nil)
|
12
|
+
connection = self.class.find_service
|
13
|
+
|
14
|
+
# Get our SSH key to attach it to the server.
|
15
|
+
if instance
|
16
|
+
@node = instance
|
17
|
+
else
|
18
|
+
@node = build(connection, flavor, image, name)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.find_service
|
23
|
+
Fog::Compute.new(
|
24
|
+
:provider => 'Rackspace',
|
25
|
+
:rackspace_api_key => Fog.credentials[:rackspace_api_key],
|
26
|
+
:rackspace_username => Fog.credentials[:rackspace_username],
|
27
|
+
:rackspace_auth_url => "lon.auth.api.rackspacecloud.com")
|
28
|
+
end
|
29
|
+
|
30
|
+
def build(connection, flavor, image, name)
|
31
|
+
path = File.expand_path '~/.ssh/id_rsa.pub'
|
32
|
+
path = File.expand_path '~/.ssh/id_dsa.pub' if not File.exist? path
|
33
|
+
raise "Couldn't find a public key" if not File.exist? path
|
34
|
+
key = File.open(path, 'rb').read
|
35
|
+
|
36
|
+
images = Rackspace::Images.new
|
37
|
+
options = {
|
38
|
+
:flavor_id => flavor,
|
39
|
+
:image_id => images.get_id(image),
|
40
|
+
:name => name,
|
41
|
+
:public_key => key
|
42
|
+
}
|
43
|
+
connection.servers.bootstrap(options)
|
44
|
+
end
|
45
|
+
|
46
|
+
def destroy
|
47
|
+
@node.destroy if !@node.nil?
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.find_by_ip(ip_address, service = self.find_service())
|
51
|
+
node = service.servers.select { |server| server.public_ip_address == ip_address }
|
52
|
+
if node.size > 0
|
53
|
+
return self.new(nil, nil, nil, node[0])
|
54
|
+
else
|
55
|
+
return false
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|