skewer 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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