skewer 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.
Files changed (81) hide show
  1. data/.idea/encodings.xml +5 -0
  2. data/.idea/misc.xml +8 -0
  3. data/.idea/modules.xml +9 -0
  4. data/.idea/skewer.iml +61 -0
  5. data/.idea/vcs.xml +7 -0
  6. data/.rvmrc +2 -0
  7. data/Gemfile +3 -0
  8. data/Gemfile.lock +146 -0
  9. data/LICENSE +13 -0
  10. data/README.md +77 -0
  11. data/Rakefile +59 -0
  12. data/Vagrantfile +5 -0
  13. data/assets/Gemfile +5 -0
  14. data/assets/rubygems.sh +39 -0
  15. data/bin/skewer +101 -0
  16. data/features/aws.feature +45 -0
  17. data/features/bootstrapper.feature +14 -0
  18. data/features/configuration.feature +15 -0
  19. data/features/delete.feature +22 -0
  20. data/features/hooks.feature +15 -0
  21. data/features/interface.feature +61 -0
  22. data/features/provision.feature +38 -0
  23. data/features/rackspace.feature +34 -0
  24. data/features/step_definitions/cucumber_steps.rb +43 -0
  25. data/features/step_definitions/delete.rb +32 -0
  26. data/features/support/env.rb +6 -0
  27. data/features/support/puppetcode/manifests/classes/foobar.pp +4 -0
  28. data/features/support/puppetcode/manifests/classes/foobroken.pp +4 -0
  29. data/features/support/puppetcode/manifests/classes/role.pp +3 -0
  30. data/features/support/puppetcode/manifests/site.pp +2 -0
  31. data/features/support/puppetcode/modules/foo/manifests/bar.pp +3 -0
  32. data/features/support/puppetcode/modules/foo/manifests/broken.pp +8 -0
  33. data/features/support/puppetcode/modules/puppet/manifests/munge.pp +5 -0
  34. data/features/update.feature +29 -0
  35. data/lib/aws/node.rb +52 -0
  36. data/lib/aws/security_group.rb +37 -0
  37. data/lib/aws/service.rb +21 -0
  38. data/lib/bootstrapper.rb +112 -0
  39. data/lib/cli.rb +96 -0
  40. data/lib/config.rb +67 -0
  41. data/lib/cuke.rb +26 -0
  42. data/lib/ersatz/ersatz_node.rb +31 -0
  43. data/lib/ersatz/ssh_result.rb +15 -0
  44. data/lib/eucalyptus.rb +15 -0
  45. data/lib/hooks.rb +22 -0
  46. data/lib/node.erb +5 -0
  47. data/lib/node.rb +43 -0
  48. data/lib/parser.rb +92 -0
  49. data/lib/puppet.rb +53 -0
  50. data/lib/puppet_node.rb +26 -0
  51. data/lib/puppet_runtime_error.rb +6 -0
  52. data/lib/rackspace/images.rb +25 -0
  53. data/lib/rackspace/node.rb +60 -0
  54. data/lib/skewer.rb +13 -0
  55. data/lib/skewer/version.rb +3 -0
  56. data/lib/source.rb +54 -0
  57. data/lib/stub_node.rb +25 -0
  58. data/lib/util.rb +17 -0
  59. data/skewer.gemspec +30 -0
  60. data/spec/aws/node_spec.rb +70 -0
  61. data/spec/aws/security_group_spec.rb +20 -0
  62. data/spec/aws/service_spec.rb +31 -0
  63. data/spec/bootstrapper_spec.rb +116 -0
  64. data/spec/cli_spec.rb +71 -0
  65. data/spec/config_spec.rb +68 -0
  66. data/spec/cuke_spec.rb +46 -0
  67. data/spec/ersatz_node_spec.rb +9 -0
  68. data/spec/ersatz_ssh_result_spec.rb +16 -0
  69. data/spec/hooks_spec.rb +19 -0
  70. data/spec/logger_spec.rb +22 -0
  71. data/spec/parser_spec.rb +93 -0
  72. data/spec/puppet.rb +47 -0
  73. data/spec/puppet_node_spec.rb +31 -0
  74. data/spec/rackspace/images_spec.rb +37 -0
  75. data/spec/rackspace/node_spec.rb +30 -0
  76. data/spec/source_spec.rb +45 -0
  77. data/spec/spec_helper.rb +10 -0
  78. data/spec/support/features/example.feature +9 -0
  79. data/spec/support/features/step_definitions/example.rb +8 -0
  80. data/spec/util_spec.rb +27 -0
  81. metadata +288 -0
@@ -0,0 +1,3 @@
1
+ module Skewer
2
+ VERSION = "0.1.0"
3
+ end
data/lib/source.rb ADDED
@@ -0,0 +1,54 @@
1
+ require 'util'
2
+
3
+ module Skewer
4
+ # responsible for moving source to remote nodes
5
+ class Source
6
+ def initialize(path = nil)
7
+ @util = Util.new
8
+ raise "I can't see the path #{path}" unless File.exists?(path)
9
+ @path = path.sub(/\/$/, '')
10
+ end
11
+
12
+ def excludes
13
+ '--exclude Gemfile --exclude Gemfile.lock --exclude ' + ['.git'].join(' ')
14
+ end
15
+
16
+ def create_destination(node)
17
+ begin
18
+ node.ssh('mkdir -p infrastructure')
19
+ rescue
20
+ location = @util.get_location(node)
21
+ raise "Couldn't SSH to #{location} with #{node.username}"
22
+ end
23
+ end
24
+
25
+ def rsync_command(node)
26
+ location = @util.get_location(node)
27
+ "rsync #{self.excludes} --delete -arpze ssh #{@path}/. #{node.username}@#{location}:infrastructure/."
28
+ end
29
+
30
+ def mock_rsync(command)
31
+ Skewer.logger.debug "MOCK: #{command}"
32
+ end
33
+
34
+ def real_rsync(node, command)
35
+ location = @util.get_location(node)
36
+ raise "Failed to rsync to #{location} with #{node.username}" unless system(command)
37
+ end
38
+
39
+ def rsync(node)
40
+ Skewer.logger.debug rsync_command(node)
41
+ location = @util.get_location(node)
42
+ Skewer.logger.debug "Copying code to #{location} ..."
43
+ create_destination(node)
44
+ command = self.rsync_command(node)
45
+
46
+ if node.class.to_s =~ /StubNode/
47
+ mock_rsync(command)
48
+ else
49
+ real_rsync(node, command)
50
+ end
51
+ Skewer.logger.debug " Done."
52
+ end
53
+ end
54
+ end
data/lib/stub_node.rb ADDED
@@ -0,0 +1,25 @@
1
+ module Skewer
2
+ # test stub for pretending to be a real node
3
+ class StubNode
4
+ attr_reader :dns_name, :username, :public_ip_address
5
+
6
+ def initialize
7
+ @dns_name = 'com.doodoo'
8
+ @public_ip_address = '192.168.0.1'
9
+ @username = 'imabirdbrain'
10
+ @debug = false
11
+ end
12
+
13
+ def announce(return_type)
14
+ Skewer.logger.debug "#{self.class} will return #{return_type}" if @debug
15
+ return_type
16
+ end
17
+
18
+ def method_missing(name, *args)
19
+ require 'ersatz/ssh_result.rb'
20
+ Skewer.logger.debug "#{self.class}.#{name} called with #{args.join(',')}" if @debug
21
+ return announce([ErsatzSSHResult.new('foo', 'success', 0)]) if name == :ssh
22
+ announce true
23
+ end
24
+ end
25
+ end
data/lib/util.rb ADDED
@@ -0,0 +1,17 @@
1
+ module Skewer
2
+ # A utility class to handle common cloud operations that we'll
3
+ # encounter amongst different providers.
4
+ class Util
5
+ # Get the location (dns name, or IP) for a given Fog server. Cloud
6
+ # provider agnostic.
7
+ def get_location(server = nil)
8
+ # Negative case.
9
+ return nil if server.nil?
10
+ return nil if not server.respond_to?(:dns_name) and not server.respond_to?(:public_ip_address)
11
+
12
+ # Positive case.
13
+ return server.dns_name if server.respond_to? :dns_name
14
+ server.public_ip_address if server.respond_to? :public_ip_address
15
+ end
16
+ end
17
+ end
data/skewer.gemspec ADDED
@@ -0,0 +1,30 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "skewer/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "skewer"
7
+ s.version = Skewer::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Julian Simpson", "Kushal Pisavadia"]
10
+ s.email = ["julian@build-doctor.com"]
11
+ s.homepage = "http://www.build-doctor.com/skewer"
12
+ s.summary = "skewer-#{s.version}"
13
+ s.description = "Runs masterless puppet code on cloud machines"
14
+
15
+ s.rubyforge_project = "skewer"
16
+
17
+ s.add_runtime_dependency 'fog', '~> 1.1.2'
18
+
19
+ s.add_development_dependency 'aruba'
20
+ s.add_development_dependency 'jeweler'
21
+ s.add_development_dependency 'metric_fu'
22
+ s.add_development_dependency 'rcov', '~> 1.0.0'
23
+ s.add_development_dependency 'rspec'
24
+ s.add_development_dependency 'vagrant', '~> 0.9.0'
25
+
26
+ s.files = `git ls-files`.split("\n").reject {|path| path =~ /\.gitignore$/ }
27
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
28
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
29
+ s.require_paths = ["lib"]
30
+ end
@@ -0,0 +1,70 @@
1
+ require 'rspec'
2
+ require 'fog'
3
+ require 'aws/node'
4
+ require 'config'
5
+
6
+ describe Skewer::AWS::Node do
7
+ it "should have an optional SSH key" do
8
+ lambda {
9
+ Fog.mock!
10
+ Skewer::AWS::Node.new('ami-123456', ['default']).node
11
+ }.should raise_exception Fog::Errors::MockNotImplemented
12
+
13
+ begin
14
+ Skewer::AWS::Node.new('ami-123456', ['default'], {:key_name => 'FOO_BAR_KEY'})
15
+ rescue Fog::Compute::AWS::NotFound => e
16
+ e.message.should == "The key pair 'FOO_BAR_KEY' does not exist"
17
+ end
18
+ end
19
+
20
+ it "should get the SSH key via config" do
21
+ Skewer::SkewerConfig.set('key_name', 'FRONT_DOOR')
22
+ begin
23
+ Skewer::AWS::Node.new('ami-123456', ['default']).inspect
24
+ rescue Fog::Compute::AWS::NotFound => e
25
+ e.message.should == "The key pair 'FRONT_DOOR' does not exist"
26
+ end
27
+ end
28
+
29
+ it "should be able to find the node by name" do
30
+ options = {}
31
+ service = stub('aws_service')
32
+ servers = stub('aws_servers')
33
+ service.should_receive(:servers).and_return(servers)
34
+ servers.should_receive(:select).and_return([])
35
+ Skewer::AWS::Node.find_by_name('foobar', service)
36
+ end
37
+
38
+ it "should be able in inject the AWS service" do
39
+ options = {}
40
+ service = stub('aws_service')
41
+ servers = stub('aws_servers')
42
+
43
+ service.should_receive(:servers).and_return(servers)
44
+ servers.should_receive(:bootstrap)
45
+
46
+ options[:service] = service
47
+ Skewer::AWS::Node.new('ami-goo', ['default'], options)
48
+ end
49
+
50
+ it "should have a delete method" do
51
+ options = {}
52
+ service = stub('aws_service')
53
+ servers = stub('aws_servers')
54
+ node = stub('aws_node')
55
+
56
+ service.should_receive(:servers).and_return(servers)
57
+ servers.should_receive(:bootstrap).and_return(node)
58
+ node.should_receive(:delete).and_return(true)
59
+
60
+ options[:service] = service
61
+ Skewer::AWS::Node.new('ami-goo', ['default'], options).node.delete.should == true
62
+ end
63
+
64
+ it "should have a static find_service method" do
65
+ options = {}
66
+ service = stub('aws_service')
67
+ options[:service] = service
68
+ Skewer::AWS::Node.find_service(options).should == service
69
+ end
70
+ end
@@ -0,0 +1,20 @@
1
+ require 'aws/security_group'
2
+ require 'fog'
3
+
4
+ describe Skewer::AWS::SecurityGroup do
5
+ Fog.mock!
6
+ it "should use an existing AWS service if it exists" do
7
+ service = Skewer::AWS::Service.service
8
+ group = Skewer::AWS::SecurityGroup.new('foo','bar', {})
9
+ group.service.should == service
10
+ end
11
+
12
+ it "should not open any extra ports if we don't ask" do
13
+ Skewer::AWS::SecurityGroup.new('foo1','bar1', {}).group.ip_permissions.should == []
14
+ end
15
+
16
+ it "should open SSH by default" do
17
+ default_group = Skewer::AWS::Service.service.security_groups.select {|group| group.name == 'default'}[0]
18
+ default_group.ip_permissions.select {|dg| dg['fromPort'] == 22 && dg['toPort'] == 22 }.length.should == 1
19
+ end
20
+ end
@@ -0,0 +1,31 @@
1
+ require 'config'
2
+ require 'aws/service'
3
+ require 'fog'
4
+
5
+ describe Skewer::AWS::Service do
6
+ it "should return an AWS instance" do
7
+ Fog.mock!
8
+ Skewer::AWS::Service.service.class.should == Fog::Compute::AWS::Mock
9
+ end
10
+
11
+ it "should register itself for reuse" do
12
+ service = Skewer::AWS::Service.service
13
+ Skewer::SkewerConfig.get('aws_service').should == service
14
+ end
15
+
16
+ it "should take zone param from SkewerConfig, and raise exception if not supported" do
17
+ Skewer::SkewerConfig.set 'region', 'unknown region'
18
+
19
+ lambda {
20
+ service = Skewer::AWS::Service.service
21
+ }.should raise_exception(ArgumentError, 'Unknown region: "unknown region"')
22
+ end
23
+
24
+ it "should take zone param from SkewerConfig" do
25
+ Fog.mock!
26
+ Skewer::SkewerConfig.set 'region', 'eu-west-1'
27
+ service = Skewer::AWS::Service.service
28
+ service.nil?.should == false
29
+ service.class.should == Fog::Compute::AWS::Mock
30
+ end
31
+ end
@@ -0,0 +1,116 @@
1
+ require 'bootstrapper'
2
+ require 'source'
3
+ require 'config'
4
+
5
+ describe Skewer::Bootstrapper do
6
+ it "should barf if the file doesn't exist" do
7
+ lambda {
8
+ Skewer::Bootstrapper.new(nil, {}).execute('oberon.sh')
9
+ }.should raise_exception RuntimeError
10
+ end
11
+
12
+ it "should copy the rubygems profile.d script over" do
13
+ node = mock('node')
14
+ node.should_receive(:scp)
15
+ node.should_receive(:ssh).with('sudo bash /var/tmp/rubygems.sh')
16
+ Skewer::Bootstrapper.new(node, {}).execute('rubygems.sh')
17
+ end
18
+
19
+ it "should install rubygems on the remote machine" do
20
+ node = mock('node')
21
+ node.should_receive(:scp).with(File.expand_path("./lib/../assets/Gemfile"), "infrastructure")
22
+ node.should_receive(:ssh).with('. /etc/profile.d/rubygems.sh && cd infrastructure && bundle install')
23
+ Skewer::Bootstrapper.new(node, {}).install_gems
24
+ end
25
+
26
+ it "should make a node file if it can't find one" do
27
+ FileUtils.mkdir_p('target/manifests')
28
+ s = Skewer::Source.new('target')
29
+ node = stub('node')
30
+ node.should_receive(:username).at_least(1).times
31
+ node.should_receive(:dns_name).at_least(1).times
32
+ node.should_receive(:ssh).at_least(1).times
33
+ Skewer::SkewerConfig.instance.set(:puppet_repo, 'target')
34
+ lambda {
35
+ Skewer::Bootstrapper.new(node, {:role => 'foo'}).sync_source
36
+ }.should raise_exception RuntimeError
37
+ Skewer::SkewerConfig.instance.set(:puppet_repo, '../infrastructure')
38
+ File.exist?('target/manifests/nodes.pp').should == true
39
+ FileUtils
40
+ end
41
+
42
+ it "should make an attempt to load the key so that we can rsync the code over" do
43
+ kernel = stub('kernel')
44
+ node = stub('node')
45
+ kernel.should_receive(:system).and_return(true)
46
+ faux_homedir = 'tmp/foo/.ssh'
47
+
48
+ config = Skewer::SkewerConfig.instance
49
+
50
+ config.set(:key_name, 'my_great_test_key' )
51
+ FileUtils.mkdir_p(faux_homedir)
52
+ FileUtils.touch("#{faux_homedir}/my_great_test_key.pem")
53
+
54
+ bootstrapper = Skewer::Bootstrapper.new(node, {})
55
+ bootstrapper.add_key_to_agent(kernel, 'tmp/foo')
56
+ Skewer::SkewerConfig.set(:key_name, nil)
57
+
58
+
59
+ end
60
+
61
+ it "should always run if there is no lock file" do
62
+ node = stub('node')
63
+ node.should_receive(:dns_name).at_least(1).times.and_return('foo.bar.com')
64
+
65
+ bootstrapper = Skewer::Bootstrapper.new(node, {:role => 'foo'})
66
+ bootstrapper.destroy_lock_file
67
+
68
+ bootstrapper.should_i_run?.should == true
69
+ File.unlink(bootstrapper.lock_file)
70
+ bootstrapper.should_i_run?.should == true
71
+
72
+ end
73
+
74
+ it "should let me know if it has just run" do
75
+ node = stub('node')
76
+ node.should_receive(:dns_name).at_least(1).times.and_return('foo.bar.com')
77
+
78
+ bootstrapper = Skewer::Bootstrapper.new(node, {:role => 'foo'})
79
+ bootstrapper.destroy_lock_file
80
+
81
+ bootstrapper.should_i_run?.should == true
82
+ bootstrapper.should_i_run?.should == false
83
+ bootstrapper.should_i_run?.should == false
84
+ end
85
+
86
+ it "should not bother bootstrapping again if it has bootstrapped recently" do
87
+ node = stub('node')
88
+ node.should_receive(:scp).at_least(1).times
89
+ node.should_receive(:ssh).at_least(1).times
90
+ node.should_receive(:dns_name).at_least(1).times.and_return('foo.bar.com')
91
+ bootstrapper = Skewer::Bootstrapper.new(node, {:role => 'foo'})
92
+ bootstrapper.destroy_lock_file
93
+ bootstrapper.mock = true
94
+ bootstrapper.go
95
+ sleep 1
96
+ bootstrapper.go
97
+ end
98
+
99
+ it "should allow mocking out of the source step" do
100
+ node = stub('node')
101
+ node.should_not_receive(:username)
102
+ bootstrapper = Skewer::Bootstrapper.new(node, {:role => 'foo'})
103
+ bootstrapper.mock = true
104
+ bootstrapper.sync_source
105
+ end
106
+
107
+ it "should actually run the source step" do
108
+ node = stub('node')
109
+ node.should_receive(:username).at_least(3).times
110
+ node.should_receive(:ssh)
111
+ bootstrapper = Skewer::Bootstrapper.new(node, {:role => 'foo'})
112
+ lambda {
113
+ bootstrapper.sync_source
114
+ }.should raise_exception RuntimeError
115
+ end
116
+ end
data/spec/cli_spec.rb ADDED
@@ -0,0 +1,71 @@
1
+ require 'cli'
2
+
3
+ describe Skewer::CLI do
4
+ before(:each) do
5
+ config = Skewer::SkewerConfig.instance
6
+ config.reset
7
+ end
8
+
9
+ Fog.mock!
10
+ it "shouldn't barf if you instantiate it wthout options" do
11
+ lambda {
12
+ Skewer::CLI.new({:kind => :nil})
13
+ }.should_not raise_exception
14
+ end
15
+
16
+ it "should blow up if you ask for Linode as it's not done" do
17
+ lambda {
18
+ Skewer::CLI.new({:kind => :nil}).select_node(:linode)
19
+ }.should raise_exception RuntimeError
20
+ end
21
+
22
+ it "should return a node of type eucalytus if you ask for one" do
23
+ lambda {
24
+ Skewer::CLI.new({:kind => :nil}).select_node(:eucalyptus)
25
+ }.should raise_exception Fog::Errors::MockNotImplemented
26
+ end
27
+
28
+ it "should return a node of type local if you ask for vagrant" do
29
+ Skewer::CLI.new({:kind => :nil}).select_node(:vagrant).class.should == Skewer::ErsatzNode
30
+ end
31
+
32
+ it "should return a node of type AWS if you ask for one" do
33
+ lambda {
34
+ Skewer::CLI.new({:kind => :nil}).select_node(:ec2)
35
+ }.should raise_exception Fog::Errors::MockNotImplemented
36
+ end
37
+
38
+ it "should be able to parse the options as they're given" do
39
+ config = Skewer::SkewerConfig.instance
40
+ config.get(:region).should == 'us-east-1'
41
+ cli = Skewer::CLI.new({:kind => :nil, :region => 'eu-west-1'})
42
+ config.get(:region).should == 'eu-west-1'
43
+ end
44
+
45
+ it "should be able to pass the region through to the AWS service" do
46
+ config = Skewer::SkewerConfig.instance
47
+ config.get(:region).should == 'us-east-1'
48
+ cli = Skewer::CLI.new({:kind => :nil, :region => 'eu-west-1'})
49
+ config.get(:region).should == 'eu-west-1'
50
+ lambda {
51
+ cli.select_node(:ec2)
52
+ }.should raise_exception Fog::Errors::MockNotImplemented
53
+ end
54
+
55
+ it "should be able to pass the flavor_id through to the AWS service" do
56
+ config = Skewer::SkewerConfig.instance
57
+ config.get(:flavor_id).should == 'm1.large'
58
+ cli = Skewer::CLI.new({:kind => :nil, :flavor_id => 'm1.small'})
59
+ config.get(:flavor_id).should == 'm1.small'
60
+ lambda {
61
+ cli.select_node(:ec2)
62
+ }.should raise_exception Fog::Errors::MockNotImplemented
63
+ end
64
+
65
+ it "should be able to provide a cuke directory" do
66
+ config = Skewer::SkewerConfig.instance
67
+ config.get(:cuke_dir).should == nil
68
+ cli = Skewer::CLI.new({:kind => :nil, :cuke_dir => 'spec'})
69
+ config.get(:cuke_dir).should == 'spec'
70
+ end
71
+ end