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,4 @@
1
+ class foobar {
2
+ include puppet::munge
3
+ include foo::bar
4
+ }
@@ -0,0 +1,4 @@
1
+ class foobroken {
2
+ include puppet::munge
3
+ include foo::broken
4
+ }
@@ -0,0 +1,3 @@
1
+ class my_role_class {
2
+ notice('this is a dirty great role class that actually does nothing')
3
+ }
@@ -0,0 +1,2 @@
1
+ import "nodes.pp"
2
+ import "classes/*.pp"
@@ -0,0 +1,3 @@
1
+ class foo::bar {
2
+ notice "foo to the bar to the baz"
3
+ }
@@ -0,0 +1,8 @@
1
+ class foo::broken {
2
+ file {
3
+ "broken file":
4
+ path => "/this/path/should/not/exist",
5
+ mode => "0999";:w
6
+
7
+ }
8
+ }
@@ -0,0 +1,5 @@
1
+ class puppet::munge {
2
+ group {
3
+ 'puppet': ensure => present
4
+ }
5
+ }
@@ -0,0 +1,29 @@
1
+ Feature: updating a node
2
+ In order to run my puppet code again
3
+ As a someone who wants to update config on the machine
4
+ I want to run the update command
5
+
6
+ Scenario: run the command without args
7
+ When I run `./bin/skewer update`
8
+ Then the exit status should not be 0
9
+
10
+ @announce-stdout @announce-stderr
11
+ Scenario: pass in a hostname user and role on passing puppet code
12
+ Given I have access to the internet
13
+ And I have puppet code in "/tmp/skewer_test_code"
14
+ When I run `./bin/skewer update --host default --user vagrant --role foobar --puppetcode /tmp/skewer_test_code/`
15
+ Then the exit status should be 0
16
+ And the stdout should contain "Using Puppet Code from /tmp/skewer_test_code/"
17
+ And the stdout should contain "Puppet run succeeded"
18
+
19
+
20
+ @announce-stdout @announce-stderr
21
+ Scenario: pass in a hostname user and role on broken puppet code
22
+ Given I have access to the internet
23
+ And I have puppet code in "/tmp/skewer_test_code"
24
+ When I run `./bin/skewer update --host default --user vagrant --role foobroken --puppetcode /tmp/skewer_test_code/`
25
+ Then the exit status should be 0
26
+ And the stdout should contain "Using Puppet Code from /tmp/skewer_test_code/"
27
+ And the stdout should contain "Puppet failed"
28
+
29
+
data/lib/aws/node.rb ADDED
@@ -0,0 +1,52 @@
1
+ require 'skewer'
2
+ require 'aws/service'
3
+
4
+ module Skewer
5
+ module AWS
6
+ # Build out an AWS node using Fog.
7
+ class Node
8
+ attr_reader :node
9
+
10
+ def initialize(aws_id, group_names, options = {})
11
+ if options[:aws_node]
12
+ @node = options[:aws_node]
13
+ else
14
+ @service = self.class.find_service(options)
15
+ node_options = {
16
+ :image_id => aws_id,
17
+ :flavor_id => SkewerConfig.get('flavor_id'),
18
+ :username => SkewerConfig.get('aws_username'),
19
+ :groups => group_names
20
+ }
21
+
22
+ if options[:key_name]
23
+ node_options[:key_name] = options[:key_name]
24
+ end
25
+
26
+ if SkewerConfig.instance.get('key_name')
27
+ node_options[:key_name] = SkewerConfig.get('key_name')
28
+ end
29
+
30
+ @node = @service.servers.bootstrap(node_options)
31
+ end
32
+ end
33
+
34
+ def self.find_service(options)
35
+ options[:service] ? options[:service] : AWS::Service.new.service
36
+ end
37
+
38
+ def destroy
39
+ @node.destroy
40
+ end
41
+
42
+ def self.find_by_name(dns_name, service = self.find_service({}))
43
+ node = service.servers.select { |server| server.dns_name == dns_name }[0]
44
+ if node
45
+ return self.new(nil, nil, {:aws_node => node})
46
+ else
47
+ return false
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,37 @@
1
+ module Skewer
2
+ module AWS
3
+ # Security group permissions for our AWS service.
4
+ class SecurityGroup
5
+ attr_reader :service, :group
6
+
7
+ def initialize(name, desc, ports)
8
+ @service ||= SkewerConfig.get 'aws_service'
9
+ groups = @service.security_groups
10
+ group = groups.select { |group| group.name == name }[0]
11
+
12
+ if group.nil? == true
13
+ group = @service.create_security_group(name, desc)
14
+ group = groups.get(name)
15
+ end
16
+ @group = group
17
+
18
+ if ports.length >= 1
19
+ ensure_port_ranges(group, ports)
20
+ end
21
+ end
22
+
23
+
24
+ def ensure_port_ranges(group, ports)
25
+ ports.each do |port|
26
+
27
+ description = port[:description]
28
+ range = port[:range]
29
+
30
+ group.revoke_port_range(range)
31
+ group.authorize_port_range(range, {:name => description})
32
+ # TODO: get the port range options in there
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,21 @@
1
+ module Skewer
2
+ module AWS
3
+ # The AWS service which is used to interface through to the AWS
4
+ # cloud using Fog.
5
+ class Service
6
+ attr_reader :service
7
+
8
+ def initialize
9
+ region = SkewerConfig.get('region')
10
+ @service = Fog::Compute.new({
11
+ :provider => 'AWS',
12
+ :region => region})
13
+ SkewerConfig.set 'aws_service', @service
14
+ end
15
+
16
+ def self.service
17
+ self.new.service
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,112 @@
1
+ require 'config'
2
+ require 'util'
3
+ require 'skewer'
4
+
5
+ module Skewer
6
+ # puts all of puppet's dependencies on
7
+ class Bootstrapper
8
+ MAX_CACHE = 3600
9
+ attr_writer :mock
10
+
11
+ def initialize(node,options)
12
+ @node = node
13
+ @options = options
14
+ @util = Util.new
15
+ @mock = false
16
+ end
17
+
18
+ def add_ssh_hostkey
19
+ location = @util.get_location(@node)
20
+ system "ssh -o 'StrictHostKeyChecking no' -o 'PasswordAuthentication no' no_such_user@#{location} >/dev/null 2>&1"
21
+ end
22
+
23
+ def execute(file_name)
24
+ file = File.join(File.dirname(__FILE__), '..', 'assets', file_name)
25
+ raise "#{file} does not exist" unless File.exists? file
26
+ @node.scp file, '/var/tmp/.'
27
+ result = @node.ssh "sudo bash /var/tmp/#{file_name}"
28
+ puts result.inspect
29
+ result
30
+ end
31
+
32
+ def install_gems
33
+ Skewer.logger.debug "Installing Gems"
34
+ assets = File.join(File.dirname(__FILE__), '..', 'assets')
35
+ @node.scp File.join(File.expand_path(assets), 'Gemfile'), 'infrastructure'
36
+ command = ". /etc/profile.d/rubygems.sh && cd infrastructure && bundle install"
37
+ result = @node.ssh(command)
38
+ end
39
+
40
+ def add_key_to_agent(executor = Kernel, homedir = ENV['HOME'])
41
+ config = SkewerConfig.instance
42
+ key_name = config.get('key_name')
43
+ key_path = File.join(homedir, '.ssh', "#{key_name}.pem")
44
+ Skewer.logger.debug "****Looking for #{key_path}"
45
+ if File.exists?(key_path)
46
+ executor.system("ssh-add #{key_path}")
47
+ end
48
+ end
49
+
50
+ def sync_source()
51
+ require 'source'
52
+ require 'puppet_node'
53
+ config = SkewerConfig.instance
54
+ source_dir = config.get(:puppet_repo)
55
+ Skewer.logger.debug "Using Puppet Code from #{source_dir}"
56
+ role = @options[:role]
57
+ if role
58
+ PuppetNode.new({:default => role.to_sym}).render
59
+ end
60
+ # TODO: if there's no role, it should look it up from an external source
61
+ if @mock
62
+ Skewer.logger.debug "Mock: would normally rsync now"
63
+ else
64
+ Source.new(source_dir).rsync(@node)
65
+ end
66
+
67
+ end
68
+
69
+ def lock_file
70
+ File.join('/tmp', 'skewer-' + Util.new.get_location(@node))
71
+ end
72
+
73
+ def lock_file_expired?(lock_file)
74
+ now = Time.now
75
+ lock_file_time = File.stat(lock_file).mtime
76
+ age = now - lock_file_time
77
+ age > MAX_CACHE
78
+ end
79
+
80
+ def destroy_lock_file()
81
+ FileUtils.rm_f(self.lock_file)
82
+ end
83
+
84
+ def should_i_run?
85
+ lock_file = lock_file()
86
+ if File.exists?(lock_file)
87
+ if lock_file_expired?(lock_file)
88
+ destroy_lock_file
89
+ return true
90
+ else
91
+ return false
92
+ end
93
+ else
94
+ FileUtils.touch(lock_file)
95
+ true
96
+ end
97
+ end
98
+
99
+ def prepare_node
100
+ add_ssh_hostkey
101
+ execute('rubygems.sh')
102
+ add_key_to_agent
103
+ end
104
+
105
+ def go
106
+ i_should_run = should_i_run?
107
+ prepare_node() if i_should_run
108
+ sync_source
109
+ install_gems if i_should_run
110
+ end
111
+ end
112
+ end
data/lib/cli.rb ADDED
@@ -0,0 +1,96 @@
1
+ require 'rubygems'
2
+ require 'fog'
3
+
4
+ require 'skewer'
5
+ require 'bootstrapper'
6
+ require 'util'
7
+ require 'hooks'
8
+
9
+ module Skewer
10
+ # this is responsible for composing all the other components. or should be.
11
+ class CLI
12
+ attr_reader :bootstrapper, :node
13
+
14
+ def initialize(options)
15
+ @options = options
16
+ @config = SkewerConfig.instance
17
+ @config.slurp_options(options)
18
+ @util = Util.new
19
+ @config.set(:logger, Skewer.logger)
20
+ end
21
+
22
+ def select_node(kind)
23
+ Skewer.logger.debug "Evaluating cloud #{kind}"
24
+ image = @options[:image]
25
+ case kind
26
+ when :ec2
27
+ require 'aws/security_group'
28
+ require 'aws/node'
29
+ require 'aws/service'
30
+ Skewer.logger.debug 'Launching an EC2 node'
31
+ aws_group = @options[:group]
32
+ group = aws_group ? aws_group : 'default'
33
+ node = AWS::Node.new(image, [group]).node
34
+ when :rackspace
35
+ require 'rackspace/node'
36
+ Skewer.logger.debug 'Launching a Rackspace node'
37
+ node = Rackspace::Node.new(@options[:flavor_id], image, 'default').node
38
+ when :linode
39
+ raise "not implemented"
40
+ when :eucalyptus
41
+ Skewer.logger.debug 'Using the EC2 API'
42
+ require 'eucalyptus'
43
+ node = Eucalyptus.new
44
+ when :vagrant
45
+ Skewer.logger.debug 'Launching a local vagrant node'
46
+ require 'ersatz/ersatz_node.rb'
47
+ node = ErsatzNode.new('default', 'vagrant')
48
+ when :stub
49
+ Skewer.logger.debug "Launching stubbed node for testing"
50
+ require 'stub_node'
51
+ node = StubNode.new
52
+ when :ersatz
53
+ require 'ersatz/ersatz_node.rb'
54
+ node = ErsatzNode.new(@config.get('host'), @config.get('user'))
55
+ else
56
+ raise "I don't know that cloud"
57
+ end
58
+ node
59
+ end
60
+
61
+ def destroy
62
+ @node.destroy unless @options[:keep]
63
+ end
64
+
65
+ def bootstrap
66
+ node = select_node(@options[:kind])
67
+ @node = node
68
+ @bootstrapper = Bootstrapper.new(node, @options)
69
+ end
70
+
71
+ def go
72
+ require 'puppet'
73
+ require 'cuke'
74
+ begin
75
+ node = @node
76
+ node.wait_for { ready? }
77
+ @bootstrapper.go
78
+ Puppet.run(node, @options)
79
+ location = @util.get_location(node)
80
+ Hooks.new(location).run
81
+ Skewer.logger.debug "Node ready\n open http://#{location} or \n ssh -l #{node.username} #{location}"
82
+ Cuke.new(@config.get(:cuke_dir)).run if @config.get(:cuke_dir)
83
+ rescue Exception => exception
84
+ Skewer.logger.debug exception
85
+ ensure
86
+ destroy
87
+ end
88
+ end
89
+
90
+ def self.bootstrap_and_go(options)
91
+ skewer = self.new(options)
92
+ skewer.bootstrap
93
+ skewer.go
94
+ end
95
+ end
96
+ end
data/lib/config.rb ADDED
@@ -0,0 +1,67 @@
1
+ require 'singleton'
2
+
3
+ module Skewer
4
+ # responsible for all configuration, once I move all the options in
5
+ class SkewerConfig
6
+ include Singleton
7
+ attr_accessor :aws_service, :puppet_repo, :region, :flavor_id, :aws_username, :flavor_id
8
+
9
+ def initialize
10
+ reset
11
+ read_config_files
12
+ end
13
+
14
+ def reset
15
+ @puppet_repo = '../infrastructure'
16
+ @region = 'us-east-1'
17
+ @flavor_id = 'm1.large'
18
+ @aws_username = 'ubuntu'
19
+ end
20
+
21
+ def read_config_file(config_file)
22
+ if File.exists?(config_file)
23
+ Skewer.logger.debug "reading #{config_file}"
24
+ config = File.read(config_file)
25
+ parse(config)
26
+ Skewer.logger.debug self.inspect
27
+ end
28
+ end
29
+
30
+ def read_config_files
31
+ read_config_file(File.join(ENV['HOME'], '.skewer.json'))
32
+ read_config_file('.skewer.json')
33
+ end
34
+
35
+ def parse(config)
36
+ require 'json'
37
+ configz = JSON.parse(config)
38
+ configz.each { |key,value| set(key,value) }
39
+ end
40
+
41
+ def set(attribute, value)
42
+ self.instance_variable_set "@#{attribute}", value
43
+ end
44
+
45
+ def self.set(key,value)
46
+ instance = self.instance
47
+ instance.set(key,value)
48
+ end
49
+
50
+ def get(attribute)
51
+ if attribute.class == Symbol
52
+ attribute = attribute.to_s
53
+ end
54
+ self.instance_variable_get "@" + attribute
55
+ end
56
+
57
+ def self.get(key)
58
+ self.instance.get(key)
59
+ end
60
+
61
+ def slurp_options(options)
62
+ options.each_pair do |key, value|
63
+ self.set(key, value)
64
+ end
65
+ end
66
+ end
67
+ end