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.
- data/CHANGELOG.md +19 -0
- data/LICENSE.txt +2 -2
- data/README.md +31 -142
- data/bin/chef-workflow-bootstrap +6 -0
- data/chef-workflow.gemspec +4 -1
- data/lib/chef-workflow.rb +41 -39
- data/lib/chef-workflow/support/attr.rb +28 -26
- data/lib/chef-workflow/support/db.rb +26 -0
- data/lib/chef-workflow/support/db/basic.rb +225 -0
- data/lib/chef-workflow/support/db/group.rb +72 -0
- data/lib/chef-workflow/support/debug.rb +47 -45
- data/lib/chef-workflow/support/ec2.rb +136 -134
- data/lib/chef-workflow/support/general.rb +46 -54
- data/lib/chef-workflow/support/generic.rb +27 -23
- data/lib/chef-workflow/support/ip.rb +89 -103
- data/lib/chef-workflow/support/knife-plugin.rb +26 -24
- data/lib/chef-workflow/support/knife.rb +76 -102
- data/lib/chef-workflow/support/scheduler.rb +319 -324
- data/lib/chef-workflow/support/ssh.rb +100 -0
- data/lib/chef-workflow/support/vagrant.rb +34 -30
- data/lib/chef-workflow/support/vm.rb +25 -54
- data/lib/chef-workflow/support/vm/chef_server.rb +28 -19
- data/lib/chef-workflow/support/vm/ec2.rb +135 -106
- data/lib/chef-workflow/support/vm/helpers/knife.rb +26 -0
- data/lib/chef-workflow/support/vm/knife.rb +218 -189
- data/lib/chef-workflow/support/vm/vagrant.rb +90 -74
- data/lib/chef-workflow/version.rb +3 -5
- metadata +57 -4
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'chef-workflow/support/db'
|
2
|
+
|
3
|
+
module ChefWorkflow
|
4
|
+
class DatabaseSupport
|
5
|
+
class VMGroup
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
def initialize(table_name, box_nil)
|
9
|
+
raise "Must provide a table name!" unless table_name
|
10
|
+
@table_name = table_name
|
11
|
+
@box_nil = box_nil
|
12
|
+
@db = ChefWorkflow::DatabaseSupport.instance
|
13
|
+
create_table
|
14
|
+
end
|
15
|
+
|
16
|
+
def [](key)
|
17
|
+
rows = @db.execute("select value from #{@table_name} where name=? order by id", [key])
|
18
|
+
if rows.count == 0
|
19
|
+
@box_nil ? [] : nil
|
20
|
+
else
|
21
|
+
rows.to_a.map { |x| Marshal.load(x.first) }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def []=(key, value)
|
26
|
+
delete(key)
|
27
|
+
|
28
|
+
return value if value.empty?
|
29
|
+
|
30
|
+
values = value.map { |x| Marshal.dump(x) }
|
31
|
+
value_string = ("(?, ?)," * values.count).chop
|
32
|
+
|
33
|
+
@db.execute("insert into #{@table_name} (name, value) values #{value_string}", values.map { |x| [key, x] }.flatten)
|
34
|
+
end
|
35
|
+
|
36
|
+
def keys
|
37
|
+
@db.execute("select distinct name from #{@table_name}").map(&:first)
|
38
|
+
end
|
39
|
+
|
40
|
+
def delete(key)
|
41
|
+
@db.execute("delete from #{@table_name} where name=?", [key])
|
42
|
+
end
|
43
|
+
|
44
|
+
def has_key?(key)
|
45
|
+
@db.execute("select count(*) from #{@table_name} where name=?", [key]).first.first.to_i > 0
|
46
|
+
end
|
47
|
+
|
48
|
+
def each
|
49
|
+
# XXX very slow, but fuck it
|
50
|
+
keys.each do |key|
|
51
|
+
yield key, self[key]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def create_table
|
58
|
+
@db.execute <<-EOF
|
59
|
+
create table if not exists #{@table_name} (
|
60
|
+
id integer not null primary key autoincrement,
|
61
|
+
name varchar(255) not null,
|
62
|
+
value text not null
|
63
|
+
)
|
64
|
+
EOF
|
65
|
+
|
66
|
+
@db.execute <<-EOF
|
67
|
+
create index if not exists #{@table_name}_name_index on #{@table_name} (name)
|
68
|
+
EOF
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -1,51 +1,53 @@
|
|
1
|
-
|
2
|
-
# mixin to assist with adding debug messages.
|
3
|
-
#
|
4
|
-
module DebugSupport
|
5
|
-
|
6
|
-
CHEF_WORKFLOW_DEBUG_DEFAULT = 2
|
7
|
-
|
8
|
-
#
|
9
|
-
# Conditionally executes based on the level of debugging requested.
|
10
|
-
#
|
11
|
-
# `CHEF_WORKFLOW_DEBUG` in the environment is converted to an integer. This
|
12
|
-
# integer is compared to the first argument. If it is higher than the first
|
13
|
-
# argument, the block supplied will execute.
|
14
|
-
#
|
15
|
-
# Optionally, if there is a `else_block`, this block will be executed if the
|
16
|
-
# condition is *not* met. This allows a slightly more elegant (if less ugly)
|
17
|
-
# variant of dealing with the situation where if debugging is on, do one
|
18
|
-
# thing, and if not, do something else.
|
19
|
-
#
|
20
|
-
# Examples:
|
1
|
+
module ChefWorkflow
|
21
2
|
#
|
22
|
-
#
|
23
|
-
# $stderr.puts "Here's a debug message"
|
24
|
-
# end
|
3
|
+
# mixin to assist with adding debug messages.
|
25
4
|
#
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
5
|
+
module DebugSupport
|
6
|
+
|
7
|
+
CHEF_WORKFLOW_DEBUG_DEFAULT = 2
|
8
|
+
|
9
|
+
#
|
10
|
+
# Conditionally executes based on the level of debugging requested.
|
11
|
+
#
|
12
|
+
# `CHEF_WORKFLOW_DEBUG` in the environment is converted to an integer. This
|
13
|
+
# integer is compared to the first argument. If it is higher than the first
|
14
|
+
# argument, the block supplied will execute.
|
15
|
+
#
|
16
|
+
# Optionally, if there is a `else_block`, this block will be executed if the
|
17
|
+
# condition is *not* met. This allows a slightly more elegant (if less ugly)
|
18
|
+
# variant of dealing with the situation where if debugging is on, do one
|
19
|
+
# thing, and if not, do something else.
|
20
|
+
#
|
21
|
+
# Examples:
|
22
|
+
#
|
23
|
+
# if_debug(1) do
|
24
|
+
# $stderr.puts "Here's a debug message"
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# This will print "here's a debug message" to standard error if debugging is
|
28
|
+
# set to 1 or greater.
|
29
|
+
#
|
30
|
+
# do_thing = lambda { run_thing }
|
31
|
+
# if_debug(2, &do_thing) do
|
32
|
+
# $stderr.puts "Doing this thing"
|
33
|
+
# do_thing.call
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# If debugging is set to 2 or higher, "Doing this thing" will be printed to
|
37
|
+
# standard error and then `run_thing` will be executed. If lower than 2 or
|
38
|
+
# off, will just execute `run_thing`.
|
39
|
+
#
|
40
|
+
def if_debug(minimum=1, else_block=nil)
|
41
|
+
$CHEF_WORKFLOW_DEBUG ||=
|
42
|
+
ENV.has_key?("CHEF_WORKFLOW_DEBUG") ?
|
43
|
+
ENV["CHEF_WORKFLOW_DEBUG"].to_i :
|
44
|
+
CHEF_WORKFLOW_DEBUG_DEFAULT
|
44
45
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
46
|
+
if $CHEF_WORKFLOW_DEBUG >= minimum
|
47
|
+
yield if block_given?
|
48
|
+
elsif else_block
|
49
|
+
else_block.call
|
50
|
+
end
|
49
51
|
end
|
50
52
|
end
|
51
53
|
end
|
@@ -4,167 +4,169 @@ require 'chef-workflow/support/general'
|
|
4
4
|
require 'chef-workflow/support/debug'
|
5
5
|
require 'chef-workflow/support/attr'
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
7
|
+
module ChefWorkflow
|
8
|
+
class EC2Support
|
9
|
+
extend ChefWorkflow::AttrSupport
|
10
|
+
include ChefWorkflow::DebugSupport
|
11
|
+
|
12
|
+
fancy_attr :access_key_id
|
13
|
+
fancy_attr :secret_access_key
|
14
|
+
fancy_attr :ami
|
15
|
+
fancy_attr :instance_type
|
16
|
+
fancy_attr :region
|
17
|
+
fancy_attr :ssh_key
|
18
|
+
fancy_attr :security_groups
|
19
|
+
fancy_attr :security_group_open_ports
|
20
|
+
fancy_attr :provision_wait
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
self.security_groups :auto
|
24
|
+
self.security_group_open_ports [22, 4000]
|
25
|
+
self.provision_wait 300
|
26
|
+
end
|
26
27
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
28
|
+
def ec2_obj
|
29
|
+
args =
|
30
|
+
if access_key_id and secret_access_key
|
31
|
+
{
|
32
|
+
:access_key_id => access_key_id,
|
33
|
+
:secret_access_key => secret_access_key
|
34
|
+
}
|
35
|
+
else
|
36
|
+
{
|
37
|
+
:access_key_id => ENV["AWS_ACCESS_KEY_ID"],
|
38
|
+
:secret_access_key => ENV["AWS_SECRET_ACCESS_KEY"]
|
39
|
+
}
|
40
|
+
end
|
40
41
|
|
41
|
-
|
42
|
-
|
43
|
-
|
42
|
+
ec2 = AWS::EC2.new(args)
|
43
|
+
ec2.regions[region]
|
44
|
+
end
|
44
45
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
46
|
+
#
|
47
|
+
# Only used if security_groups is set to auto. Returns the filename to
|
48
|
+
# marshal the automatically created security groups to.
|
49
|
+
#
|
50
|
+
def security_group_setting_path
|
51
|
+
File.join(ChefWorkflow::GeneralSupport.workflow_dir, 'security-groups')
|
52
|
+
end
|
53
|
+
|
54
|
+
#
|
55
|
+
# Creates a security group and saves it to the security_group_setting_path.
|
56
|
+
#
|
57
|
+
def create_security_group
|
58
|
+
aws_ec2 = ec2_obj
|
52
59
|
|
53
|
-
|
54
|
-
# Creates a security group and saves it to the security_group_setting_path.
|
55
|
-
#
|
56
|
-
def create_security_group
|
57
|
-
aws_ec2 = ec2_obj
|
60
|
+
name = nil
|
58
61
|
|
59
|
-
|
62
|
+
loop do
|
63
|
+
name = 'chef-workflow-' + (0..rand(10).to_i).map { rand(0..9).to_s }.join("")
|
60
64
|
|
61
|
-
|
62
|
-
|
65
|
+
if_debug(3) do
|
66
|
+
$stderr.puts "Seeing if security group name #{name} is taken"
|
67
|
+
end
|
63
68
|
|
64
|
-
|
65
|
-
|
69
|
+
break unless aws_ec2.security_groups[name].exists?
|
70
|
+
sleep 0.3
|
66
71
|
end
|
67
72
|
|
68
|
-
|
69
|
-
sleep 0.3
|
70
|
-
end
|
73
|
+
group = aws_ec2.security_groups.create(name)
|
71
74
|
|
72
|
-
|
75
|
+
security_group_open_ports.each do |port|
|
76
|
+
group.authorize_ingress(:tcp, port)
|
77
|
+
group.authorize_ingress(:udp, port)
|
78
|
+
end
|
79
|
+
|
80
|
+
group.authorize_ingress(:tcp, (0..65535), group)
|
81
|
+
group.authorize_ingress(:udp, (0..65535), group)
|
73
82
|
|
74
|
-
|
75
|
-
|
76
|
-
|
83
|
+
# XXX I think the name should be enough, but maybe this'll cause a problem.
|
84
|
+
File.binwrite(security_group_setting_path, Marshal.dump([name]))
|
85
|
+
return [name]
|
77
86
|
end
|
78
87
|
|
79
|
-
|
80
|
-
|
88
|
+
#
|
89
|
+
# Loads any stored security groups. Returns nil if it can't.
|
90
|
+
#
|
91
|
+
def load_security_group
|
92
|
+
Marshal.load(File.binread(security_group_setting_path)) rescue nil
|
93
|
+
end
|
81
94
|
|
82
|
-
#
|
83
|
-
|
84
|
-
|
85
|
-
|
95
|
+
#
|
96
|
+
# Ensures security groups exist.
|
97
|
+
#
|
98
|
+
# If @security_groups is :auto, creates one and sets it up with the
|
99
|
+
# security_group_open_ports on TCP and UDP.
|
100
|
+
#
|
101
|
+
# If @security_groups is an array of group names or a single group name,
|
102
|
+
# asserts they exist. If they do not exist, it raises.
|
103
|
+
#
|
104
|
+
def assert_security_groups
|
105
|
+
aws_ec2 = ec2_obj
|
106
|
+
|
107
|
+
if security_groups == :auto
|
108
|
+
loaded_groups = load_security_group
|
109
|
+
|
110
|
+
# this will make it hit the second block everytime from now on (and
|
111
|
+
# bootstrap it recursively)
|
112
|
+
if loaded_groups
|
113
|
+
self.security_groups loaded_groups
|
114
|
+
assert_security_groups
|
115
|
+
else
|
116
|
+
self.security_groups create_security_group
|
117
|
+
end
|
118
|
+
else
|
119
|
+
self.security_groups = [security_groups] unless security_groups.kind_of?(Array)
|
86
120
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
Marshal.load(File.binread(security_group_setting_path)) rescue nil
|
92
|
-
end
|
121
|
+
self.security_groups.each do |group|
|
122
|
+
#
|
123
|
+
# just retry this until it works -- some stupid flexible proxy in aws-sdk will bark about a missing method otherwise.
|
124
|
+
#
|
93
125
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
# If @security_groups is an array of group names or a single group name,
|
101
|
-
# asserts they exist. If they do not exist, it raises.
|
102
|
-
#
|
103
|
-
def assert_security_groups
|
104
|
-
aws_ec2 = ec2_obj
|
105
|
-
|
106
|
-
if security_groups == :auto
|
107
|
-
loaded_groups = load_security_group
|
108
|
-
|
109
|
-
# this will make it hit the second block everytime from now on (and
|
110
|
-
# bootstrap it recursively)
|
111
|
-
if loaded_groups
|
112
|
-
self.security_groups loaded_groups
|
113
|
-
assert_security_groups
|
114
|
-
else
|
115
|
-
self.security_groups create_security_group
|
116
|
-
end
|
117
|
-
else
|
118
|
-
self.security_groups = [security_groups] unless security_groups.kind_of?(Array)
|
119
|
-
|
120
|
-
self.security_groups.each do |group|
|
121
|
-
#
|
122
|
-
# just retry this until it works -- some stupid flexible proxy in aws-sdk will bark about a missing method otherwise.
|
123
|
-
#
|
124
|
-
|
125
|
-
begin
|
126
|
-
aws_ec2.security_groups[group]
|
127
|
-
rescue
|
128
|
-
sleep 1
|
129
|
-
retry
|
130
|
-
end
|
126
|
+
begin
|
127
|
+
aws_ec2.security_groups[group]
|
128
|
+
rescue
|
129
|
+
sleep 1
|
130
|
+
retry
|
131
|
+
end
|
131
132
|
|
132
|
-
|
133
|
+
raise "EC2 security group #{group} does not exist and it should." unless aws_ec2.security_groups[group]
|
134
|
+
end
|
133
135
|
end
|
134
136
|
end
|
135
|
-
end
|
136
137
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
138
|
+
def find_secgroup_running_instances(group_name)
|
139
|
+
# exponential complexity with API calls? NO PROBLEM
|
140
|
+
ec2_obj.instances.select do |i|
|
141
|
+
i.status != :terminated &&
|
142
|
+
i.security_groups.find { |s| s.name == group_name }
|
143
|
+
end
|
142
144
|
end
|
143
|
-
end
|
144
145
|
|
145
|
-
|
146
|
-
|
147
|
-
|
146
|
+
def destroy_security_group
|
147
|
+
if File.exist?(security_group_setting_path)
|
148
|
+
group_name = Marshal.load(File.binread(security_group_setting_path)).first
|
148
149
|
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
150
|
+
until (instances = find_secgroup_running_instances(group_name)).empty?
|
151
|
+
if_debug(1) do
|
152
|
+
$stderr.puts "Trying to destroy security group #{group_name}, but instances are still bound to it."
|
153
|
+
$stderr.puts instances.map(&:id).inspect
|
154
|
+
$stderr.puts "Terminating instances, sleeping, and trying again."
|
155
|
+
end
|
156
|
+
|
157
|
+
instances.each do |i|
|
158
|
+
i.terminate rescue nil
|
159
|
+
end
|
160
|
+
|
161
|
+
sleep 10
|
158
162
|
end
|
159
163
|
|
160
|
-
|
164
|
+
ec2_obj.security_groups.find { |g| g.name == group_name }.delete
|
161
165
|
end
|
162
|
-
|
163
|
-
ec2_obj.security_groups.find { |g| g.name == group_name }.delete
|
164
166
|
end
|
165
|
-
end
|
166
167
|
|
167
|
-
|
168
|
+
include ChefWorkflow::GenericSupport
|
169
|
+
end
|
168
170
|
end
|
169
171
|
|
170
|
-
EC2Support.configure
|
172
|
+
ChefWorkflow::EC2Support.configure
|