chef-workflow 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,63 +1,55 @@
1
1
  require 'chef-workflow/support/generic'
2
2
  require 'chef-workflow/support/attr'
3
- require 'chef-workflow/support/vm/ec2'
4
- require 'chef-workflow/support/vm/vagrant'
5
-
6
- #
7
- # General configuration, typically global to all chef-workflow related things.
8
- # See `GenericSupport` for a rundown of usage.
9
- #
10
- class GeneralSupport
11
- # Standard chef-workflow dir.
12
- DEFAULT_CHEF_WORKFLOW_DIR = File.join(Dir.pwd, '.chef-workflow')
13
- # Location of the VM database.
14
- DEFAULT_CHEF_VM_FILE = File.join(DEFAULT_CHEF_WORKFLOW_DIR, 'vms')
15
- # Location of the chef-server prison file (vagrant only).
16
- DEFAULT_CHEF_SERVER_PRISON = File.join(DEFAULT_CHEF_WORKFLOW_DIR, 'chef-server')
17
-
18
- extend AttrSupport
19
-
20
- ##
21
- # :attr:
22
- #
23
- # configure the workflow directory
24
- fancy_attr :workflow_dir
25
-
26
- ##
27
- # :attr:
28
- #
29
- # configure the location of the vm file
30
- fancy_attr :vm_file
31
3
 
32
- ##
33
- # :attr:
4
+ module ChefWorkflow
34
5
  #
35
- # configure the location of the chef server prison.
36
- fancy_attr :chef_server_prison
37
-
38
- def initialize(opts={})
39
- @workflow_dir = opts[:workflow_dir] || DEFAULT_CHEF_WORKFLOW_DIR
40
- @vm_file = opts[:vm_file] || DEFAULT_CHEF_VM_FILE
41
- @chef_server_prison = opts[:chef_server_prison] || DEFAULT_CHEF_SERVER_PRISON
42
- machine_provisioner :vagrant
43
- end
44
-
45
- def machine_provisioner(*args)
46
- if args.count > 0
47
- @machine_provisioner = case args.first
48
- when :ec2
49
- VM::EC2Provisioner
50
- when :vagrant
51
- VM::VagrantProvisioner
52
- else
53
- args.first
54
- end
6
+ # General configuration, typically global to all chef-workflow related things.
7
+ # See `GenericSupport` for a rundown of usage.
8
+ #
9
+ class GeneralSupport
10
+ extend ChefWorkflow::AttrSupport
11
+ include ChefWorkflow::GenericSupport
12
+
13
+ # Standard chef-workflow dir.
14
+ DEFAULT_CHEF_WORKFLOW_DIR = File.join(Dir.pwd, '.chef-workflow')
15
+ # Location of the VM database.
16
+ DEFAULT_CHEF_VM_FILE = File.join(DEFAULT_CHEF_WORKFLOW_DIR, 'state.db')
17
+
18
+ ##
19
+ # :attr:
20
+ #
21
+ # configure the workflow directory
22
+ fancy_attr :workflow_dir
23
+
24
+ ##
25
+ # :attr:
26
+ #
27
+ # configure the location of the vm file
28
+ fancy_attr :vm_file
29
+
30
+ def initialize(opts={})
31
+ @workflow_dir = opts[:workflow_dir] || DEFAULT_CHEF_WORKFLOW_DIR
32
+ @vm_file = opts[:vm_file] || DEFAULT_CHEF_VM_FILE
33
+ machine_provisioner :vagrant
55
34
  end
56
35
 
57
- @machine_provisioner
36
+ def machine_provisioner(*args)
37
+ if args.count > 0
38
+ @machine_provisioner = case args.first
39
+ when :ec2
40
+ require 'chef-workflow/support/vm/ec2'
41
+ ChefWorkflow::VM::EC2Provisioner
42
+ when :vagrant
43
+ require 'chef-workflow/support/vm/vagrant'
44
+ ChefWorkflow::VM::VagrantProvisioner
45
+ else
46
+ args.first
47
+ end
48
+ end
49
+
50
+ @machine_provisioner
51
+ end
58
52
  end
59
-
60
- include GenericSupport
61
53
  end
62
54
 
63
- GeneralSupport.configure
55
+ ChefWorkflow::GeneralSupport.configure
@@ -1,30 +1,34 @@
1
- #
2
- # mixin for supplying a consistent interface to singleton configuration classes.
3
- #
1
+ require 'singleton'
2
+ require 'deprecated'
4
3
 
5
- module GenericSupport
6
- #--
7
- # it's cool; this isn't absolutely evil or anything.
8
- #++
9
- def self.included(klass)
10
- class << klass
11
- # The singleton object that is supplying the current configuration.
12
- # Always reference this when working with classes that use this
13
- # interface.
14
- attr_reader :singleton
4
+ module ChefWorkflow
5
+ #
6
+ # mixin for supplying a consistent interface to singleton configuration classes.
7
+ #
15
8
 
16
- # circular references, oh my
17
- attr_accessor :supported_class
9
+ module GenericSupport
10
+ def self.included(klass)
11
+ klass.instance_eval do
12
+ include Singleton
18
13
 
19
- #
20
- # Configure the singleton. Instance evals a block that you can set stuff on.
21
- #
22
- def configure(&block)
23
- @singleton ||= self.supported_class.new
24
- @singleton.instance_eval(&block) if block
14
+ def self.configure(&block)
15
+ instance.instance_eval(&block) if block
16
+ end
17
+
18
+ def self.method_missing(sym, *args)
19
+ instance.send(sym, *args)
20
+ end
21
+
22
+ def self.singleton
23
+ instance
24
+ end
25
+
26
+ class << self
27
+ include Deprecated
28
+ end
29
+
30
+ self.singleton_class.deprecated :singleton, "#{klass.name} class methods"
25
31
  end
26
32
  end
27
-
28
- klass.supported_class = klass
29
33
  end
30
34
  end
@@ -1,129 +1,115 @@
1
- require 'delegate'
2
1
  require 'fileutils'
3
- require 'chef-workflow/support/generic'
4
2
  require 'chef-workflow/support/attr'
3
+ require 'chef-workflow/support/db'
4
+ require 'chef-workflow/support/generic'
5
5
 
6
6
  ENV["TEST_CHEF_SUBNET"] ||= "10.10.10.0"
7
7
 
8
- #
9
- # IP allocation database. Uses `GenericSupport`.
10
- #
11
- class IPSupport < DelegateClass(Hash)
12
- extend AttrSupport
13
-
14
- ##
15
- # :attr:
16
- #
17
- # The subnet used for calculating assignable IP addresses. You really want to
18
- # set `TEST_CHEF_SUBNET` in your environment instead of changing this.
19
- #
20
- fancy_attr :subnet
21
-
22
- ##
23
- # :attr:
8
+ module ChefWorkflow
24
9
  #
25
- # The location of the ip database.
10
+ # IP allocation database. Uses `GenericSupport`.
26
11
  #
27
- fancy_attr :ip_file
12
+ class IPSupport
13
+ extend ChefWorkflow::AttrSupport
14
+ include ChefWorkflow::GenericSupport
28
15
 
29
- def initialize(subnet=ENV["TEST_CHEF_SUBNET"], ip_file=File.join(Dir.pwd, '.chef-workflow', 'ips'))
30
- @subnet = subnet
31
- reset
32
- @ip_file = ip_file
33
- super(@ip_assignment)
34
- end
16
+ ##
17
+ # :attr:
18
+ #
19
+ # The subnet used for calculating assignable IP addresses. You really want to
20
+ # set `TEST_CHEF_SUBNET` in your environment instead of changing this.
21
+ #
22
+ fancy_attr :subnet
35
23
 
36
- #
37
- # Resets (clears) the IP database.
38
- #
39
- def reset
40
- @ip_assignment = { }
41
- end
24
+ def initialize(subnet=ENV["TEST_CHEF_SUBNET"])
25
+ @subnet = subnet
26
+ @db = ChefWorkflow::DatabaseSupport.instance
27
+ create_table
28
+ end
42
29
 
43
- #
44
- # Loads the IP database from disk. Location is based on the `ip_file` accessor.
45
- #
46
- def load
47
- if File.exist?(ip_file)
48
- @ip_assignment = Marshal.load(File.binread(ip_file))
30
+ def create_table
31
+ @db.execute <<-EOF
32
+ create table if not exists ips (
33
+ id integer not null primary key autoincrement,
34
+ role_name varchar(255) not null,
35
+ ip_addr varchar(255) not null,
36
+ UNIQUE(role_name, ip_addr)
37
+ )
38
+ EOF
49
39
  end
50
- end
51
40
 
52
- #
53
- # Saves the IP database to disk. Location is based on the `ip_file` accessor.
54
- #
55
- def write
56
- FileUtils.mkdir_p(File.dirname(ip_file))
57
- File.binwrite(ip_file, Marshal.dump(@ip_assignment))
58
- end
41
+ #
42
+ # Gets the next unallocated IP, given an IP to start with.
43
+ #
44
+ def next_ip(arg)
45
+ octets = arg.split(/\./, 4).map(&:to_i)
46
+ octets[3] += 1
47
+ raise "out of ips!" if octets[3] > 255
48
+ return octets.map(&:to_s).join(".")
49
+ end
59
50
 
60
- #
61
- # Gets the next unallocated IP, given an IP to start with.
62
- #
63
- def next_ip(arg)
64
- octets = arg.split(/\./, 4).map(&:to_i)
65
- octets[3] += 1
66
- raise "out of ips!" if octets[3] > 255
67
- return octets.map(&:to_s).join(".")
68
- end
51
+ #
52
+ # Gets the next un-used IP. This basically calls `next_ip` with knowledge of
53
+ # the database.
54
+ #
55
+ def unused_ip
56
+ ip = next_ip(@subnet)
69
57
 
70
- #
71
- # Gets the next un-used IP. This basically calls `next_ip` with knowledge of
72
- # the database.
73
- #
74
- def unused_ip
75
- ip = next_ip(@subnet)
58
+ while ip_used?(ip)
59
+ ip = next_ip(ip)
60
+ end
76
61
 
77
- while ip_used?(ip)
78
- ip = next_ip(ip)
62
+ return ip
79
63
  end
80
64
 
81
- return ip
82
- end
65
+ #
66
+ # Predicate to determine if an IP is in use.
67
+ #
68
+ def ip_used?(ip)
69
+ @db.execute("select count(*) from ips where ip_addr=?", [ip]).first.first > 0 rescue nil
70
+ end
83
71
 
84
- #
85
- # Predicate to determine if an IP is in use.
86
- #
87
- def ip_used?(ip)
88
- @ip_assignment.values.flatten.include?(ip)
89
- end
72
+ #
73
+ # Appends an IP to a role.
74
+ #
75
+ def assign_role_ip(role, ip)
76
+ @db.execute("insert into ips (role_name, ip_addr) values (?, ?)", [role, ip])
77
+ end
90
78
 
91
- #
92
- # Appends an IP to a role.
93
- #
94
- def assign_role_ip(role, ip)
95
- @ip_assignment[role] ||= []
96
- @ip_assignment[role].push(ip)
97
- end
79
+ #
80
+ # Removes the role and all associated IPs.
81
+ #
82
+ def delete_role(role)
83
+ @db.execute("delete from ips where role_name=?", [role])
84
+ end
98
85
 
99
- #
100
- # Removes the role and all associated IPs.
101
- #
102
- def delete_role(role)
103
- @ip_assignment.delete(role)
104
- end
86
+ #
87
+ # Get all the known roles
88
+ #
89
+ def roles
90
+ @db.execute("select distinct role_name from ips").map(&:first);
91
+ end
105
92
 
106
- #
107
- # Gets all the IPs for a role, as an array of strings.
108
- #
109
- def get_role_ips(role)
110
- @ip_assignment[role] || []
111
- end
93
+ #
94
+ # Gets all the IPs for a role, as an array of strings.
95
+ #
96
+ def get_role_ips(role)
97
+ @db.execute("select ip_addr from ips where role_name=? order by id", [role]).map(&:first)
98
+ end
112
99
 
113
- #
114
- # Helper method for vagrant. Vagrant always occupies .1 of any subnet it
115
- # configures host-only networking on. This takes care of doing that.
116
- #
117
- def seed_vagrant_ips
118
- # vagrant requires that .1 be used by vagrant. ugh.
119
- dot_one_ip = @subnet.gsub(/\.\d+$/, '.1')
120
- unless ip_used?(dot_one_ip)
121
- assign_role_ip("vagrant-reserved", dot_one_ip)
100
+ #
101
+ # Helper method for vagrant. Vagrant always occupies .1 of any subnet it
102
+ # configures host-only networking on. This takes care of doing that.
103
+ #
104
+ def seed_vagrant_ips
105
+ # vagrant requires that .1 be used by vagrant. ugh.
106
+ dot_one_ip = @subnet.gsub(/\.\d+$/, '.1')
107
+ unless ip_used?(dot_one_ip)
108
+ assign_role_ip("vagrant-reserved", dot_one_ip)
109
+ end
122
110
  end
123
- end
124
111
 
125
- include GenericSupport
112
+ end
126
113
  end
127
114
 
128
- IPSupport.configure
129
- IPSupport.singleton.load
115
+ ChefWorkflow::IPSupport.configure
@@ -2,32 +2,34 @@ require 'chef/application/knife'
2
2
  require 'chef/knife'
3
3
  require 'stringio'
4
4
 
5
- #
6
- # Mixin to add methods to assist with creating knife plugins.
7
- #
8
- module KnifePluginSupport
9
-
10
- #
11
- # Given a class name for a plugin compatible with the Chef::Knife interface,
12
- # initializes it and makes it available for execution. It also overrides the
13
- # `ui` object to use `StringIO` objects, which allow you to choose when and
14
- # if you display the output of the commands by referencing
15
- # `obj.ui.stdout.string` and similar calls.
5
+ module ChefWorkflow
16
6
  #
17
- # The second argument is an array of arguments to the command, such as they
18
- # would be presented to a command line tool as `ARGV`.
7
+ # Mixin to add methods to assist with creating knife plugins.
19
8
  #
20
- def init_knife_plugin(klass, args)
21
- klass.options = Chef::Application::Knife.options.merge(klass.options)
22
- klass.load_deps
23
- cli = klass.new(args)
24
- cli.ui = Chef::Knife::UI.new(
25
- StringIO.new('', 'w'),
26
- StringIO.new('', 'w'),
27
- StringIO.new('', 'r'),
28
- cli.config
29
- )
9
+ module KnifePluginSupport
10
+
11
+ #
12
+ # Given a class name for a plugin compatible with the Chef::Knife interface,
13
+ # initializes it and makes it available for execution. It also overrides the
14
+ # `ui` object to use `StringIO` objects, which allow you to choose when and
15
+ # if you display the output of the commands by referencing
16
+ # `obj.ui.stdout.string` and similar calls.
17
+ #
18
+ # The second argument is an array of arguments to the command, such as they
19
+ # would be presented to a command line tool as `ARGV`.
20
+ #
21
+ def init_knife_plugin(klass, args)
22
+ klass.options = Chef::Application::Knife.options.merge(klass.options)
23
+ klass.load_deps
24
+ cli = klass.new(args)
25
+ cli.ui = Chef::Knife::UI.new(
26
+ StringIO.new('', 'w'),
27
+ StringIO.new('', 'w'),
28
+ StringIO.new('', 'r'),
29
+ cli.config
30
+ )
30
31
 
31
- return cli
32
+ return cli
33
+ end
32
34
  end
33
35
  end
@@ -3,120 +3,94 @@ require 'erb'
3
3
  require 'chef-workflow/support/generic'
4
4
  require 'chef-workflow/support/general'
5
5
  require 'chef-workflow/support/debug'
6
+ require 'chef-workflow/support/attr'
6
7
 
7
- #
8
- # Configuration class for chef tooling and SSH interaction. Uses `GenericSupport`.
9
- #
10
- class KnifeSupport
11
- include GenericSupport
12
- include DebugSupport
13
-
14
- # defaults, yo
15
- DEFAULTS = {
16
- :search_index_wait => 60,
17
- :cookbooks_path => File.join(Dir.pwd, 'cookbooks'),
18
- :chef_config_path => File.join(GeneralSupport.singleton.workflow_dir, 'chef'),
19
- :knife_config_path => File.join(GeneralSupport.singleton.workflow_dir, 'chef', 'knife.rb'),
20
- :roles_path => File.join(Dir.pwd, 'roles'),
21
- :environments_path => File.join(Dir.pwd, 'environments'),
22
- :data_bags_path => File.join(Dir.pwd, 'data_bags'),
23
- :ssh_user => "vagrant",
24
- :ssh_password => "vagrant",
25
- :ssh_identity_file => nil,
26
- :use_sudo => true,
27
- :test_environment => "vagrant",
28
- :test_recipes => []
29
- }
30
-
31
- DEFAULTS[:knife_config_template] = <<-EOF
32
- log_level :info
33
- log_location STDOUT
34
- node_name 'test-user'
35
- client_key File.join('<%= KnifeSupport.singleton.chef_config_path %>', 'admin.pem')
36
- validation_client_name 'chef-validator'
37
- validation_key File.join('<%= KnifeSupport.singleton.chef_config_path %>', 'validation.pem')
38
- chef_server_url 'http://<%= IPSupport.singleton.get_role_ips("chef-server").first %>:4000'
39
- environment '<%= KnifeSupport.singleton.test_environment %>'
40
- cache_type 'BasicFile'
41
- cache_options( :path => File.join('<%= KnifeSupport.singleton.chef_config_path %>', 'checksums' ))
42
- cookbook_path [ '<%= KnifeSupport.singleton.cookbooks_path %>' ]
43
- EOF
44
-
8
+ module ChefWorkflow
45
9
  #
46
- # Helper method to allow extensions to add attributes to this class. Could
47
- # probably be replaced by `AttrSupport`. Takes an attribute name and a
48
- # default which will be set initially, intended to be overridden by the user
49
- # if necessary.
10
+ # Configuration class for chef tooling and SSH interaction. Uses `GenericSupport`.
50
11
  #
51
- def self.add_attribute(attr_name, default)
52
- KnifeSupport.configure
12
+ class KnifeSupport
13
+ include ChefWorkflow::DebugSupport
14
+ extend ChefWorkflow::AttrSupport
15
+ include ChefWorkflow::GenericSupport
53
16
 
54
- DEFAULTS[attr_name] = default # a little inelegant, but it works.
17
+ # defaults, yo
18
+ DEFAULTS = {
19
+ :search_index_wait => 60,
20
+ :cookbooks_path => File.join(Dir.pwd, 'cookbooks'),
21
+ :chef_config_path => File.join(ChefWorkflow::GeneralSupport.workflow_dir, 'chef'),
22
+ :knife_config_path => File.join(ChefWorkflow::GeneralSupport.workflow_dir, 'chef', 'knife.rb'),
23
+ :roles_path => File.join(Dir.pwd, 'roles'),
24
+ :environments_path => File.join(Dir.pwd, 'environments'),
25
+ :data_bags_path => File.join(Dir.pwd, 'data_bags'),
26
+ :ssh_user => "vagrant",
27
+ :ssh_password => "vagrant",
28
+ :ssh_identity_file => nil,
29
+ :use_sudo => true,
30
+ :test_environment => "vagrant",
31
+ :test_recipes => []
32
+ }
55
33
 
56
- # HACK: no good way to hook this right now, revisit later.
57
- str = ""
58
- if attr_name.to_s == "knife_config_path"
59
- str = <<-EOF
60
- def #{attr_name}=(arg)
61
- @#{attr_name} = arg
62
- ENV["CHEF_CONFIG"] = arg
63
- end
34
+ DEFAULTS[:knife_config_template] = <<-EOF
35
+ log_level :info
36
+ log_location STDOUT
37
+ node_name 'test-user'
38
+ client_key File.join('<%= KnifeSupport.chef_config_path %>', 'admin.pem')
39
+ validation_client_name 'chef-validator'
40
+ validation_key File.join('<%= KnifeSupport.chef_config_path %>', 'validation.pem')
41
+ chef_server_url 'http://<%= IPSupport.get_role_ips("chef-server").first %>:4000'
42
+ environment '<%= KnifeSupport.test_environment %>'
43
+ cache_type 'BasicFile'
44
+ cache_options( :path => File.join('<%= KnifeSupport.chef_config_path %>', 'checksums' ))
45
+ cookbook_path [ '<%= KnifeSupport.cookbooks_path %>' ]
46
+ EOF
64
47
 
65
- def #{attr_name}(arg=nil)
66
- if arg
67
- @#{attr_name} = arg
68
- ENV["CHEF_CONFIG"] = arg
69
- end
70
-
71
- @#{attr_name}
72
- end
73
- EOF
74
- else
75
- str = <<-EOF
76
- def #{attr_name}=(arg)
77
- @#{attr_name} = arg
78
- end
48
+ attr_reader :attributes
79
49
 
80
- def #{attr_name}(arg=nil)
81
- if arg
82
- @#{attr_name} = arg
83
- end
84
-
85
- @#{attr_name}
86
- end
87
- EOF
50
+ def initialize
51
+ @attributes = { }
52
+
53
+ DEFAULTS.each do |key, value|
54
+ add_attribute(key, value)
55
+ end
56
+ end
57
+
58
+ #
59
+ # Helper method to allow extensions to add attributes to this class.
60
+ # Takes an attribute name and a default which will be set initially,
61
+ # intended to be overridden by the user if necessary.
62
+ #
63
+ #--
64
+ # FIXME Move all the user-configurable stuff to its own support system
65
+ #++
66
+ def add_attribute(attr_name, default)
67
+ @attributes[attr_name] = default
88
68
  end
89
69
 
90
- KnifeSupport.singleton.instance_eval str
91
- KnifeSupport.singleton.send(attr_name, default)
92
- end
93
-
94
- DEFAULTS.each { |key, value| add_attribute(key, value) }
70
+ def method_missing(sym, *args)
71
+ attr_name = sym.to_s.sub(/=$/, '').to_sym
72
+ assignment = sym.to_s.end_with?("=")
95
73
 
96
- def initialize(options={})
97
- DEFAULTS.each do |key, value|
98
- instance_variable_set(
99
- "@#{key}",
100
- options.has_key?(key) ? options[key] : DEFAULTS[key]
101
- )
74
+ if @attributes.has_key?(attr_name)
75
+ if assignment or args.count > 0
76
+ @attributes[attr_name] = args[0]
77
+ else
78
+ @attributes[attr_name]
79
+ end
80
+ else
81
+ raise "Attribute #{attr_name} does not exist"
82
+ end
102
83
  end
103
- end
104
84
 
105
- def method_missing(sym, *args)
106
- if_debug(2) do
107
- $stderr.puts "#{self.class.name}'s #{sym} method was referenced while trying to configure #{self.class.name}"
108
- $stderr.puts "#{self.class.name} has not been configured to support this feature."
109
- $stderr.puts "This is probably due to it being dynamically added from a rake task, and you're running the test suite."
110
- $stderr.puts "It's probably harmless. Expect a better solution than this debug message soon."
85
+ #
86
+ # Writes out a knife.rb based on the settings in this configuration. Uses the
87
+ # `knife_config_path` and `chef_config_path` to determine where to write it.
88
+ #
89
+ def build_knife_config
90
+ FileUtils.mkdir_p(chef_config_path)
91
+ File.binwrite(knife_config_path, ERB.new(knife_config_template).result(binding))
111
92
  end
112
93
  end
113
-
114
- #
115
- # Writes out a knife.rb based on the settings in this configuration. Uses the
116
- # `knife_config_path` and `chef_config_path` to determine where to write it.
117
- #
118
- def build_knife_config
119
- FileUtils.mkdir_p(chef_config_path)
120
- File.binwrite(knife_config_path, ERB.new(knife_config_template).result(binding))
121
- end
122
94
  end
95
+
96
+ ChefWorkflow::KnifeSupport.configure