chef-workflow 0.1.1 → 0.2.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.
@@ -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