chef-vpc-toolkit 2.0.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 (46) hide show
  1. data/.gitignore +2 -0
  2. data/COPYING +26 -0
  3. data/README.rdoc +75 -0
  4. data/Rakefile +34 -0
  5. data/VERSION +1 -0
  6. data/bin/chef-vpc-toolkit +80 -0
  7. data/config/chef_installer.yml +20 -0
  8. data/config/databags.json.example +24 -0
  9. data/config/nodes.json +10 -0
  10. data/config/server_group.json +16 -0
  11. data/contrib/doc/ChefVPCToolkit.odt +0 -0
  12. data/contrib/doc/ChefVPCToolkit.pdf +0 -0
  13. data/contrib/etc/chef_vpc_toolkit.conf +10 -0
  14. data/contrib/rake/Rakefile +23 -0
  15. data/cookbook-repos/local/README +5 -0
  16. data/cookbook-repos/local/Rakefile +66 -0
  17. data/cookbook-repos/local/certificates/README +1 -0
  18. data/cookbook-repos/local/config/client.rb.example +21 -0
  19. data/cookbook-repos/local/config/knife.rb.example +10 -0
  20. data/cookbook-repos/local/config/rake.rb +60 -0
  21. data/cookbook-repos/local/config/server.rb.example +42 -0
  22. data/cookbook-repos/local/config/solo.rb.example +13 -0
  23. data/cookbook-repos/local/cookbooks/README +4 -0
  24. data/cookbook-repos/local/cookbooks/motd/README.rdoc +15 -0
  25. data/cookbook-repos/local/cookbooks/motd/attributes/motd.rb +1 -0
  26. data/cookbook-repos/local/cookbooks/motd/metadata.rb +6 -0
  27. data/cookbook-repos/local/cookbooks/motd/recipes/default.rb +13 -0
  28. data/cookbook-repos/local/cookbooks/motd/templates/default/motd.erb +1 -0
  29. data/cookbook-repos/local/roles/README +4 -0
  30. data/lib/chef-vpc-toolkit.rb +6 -0
  31. data/lib/chef-vpc-toolkit/chef-0.9.bash +232 -0
  32. data/lib/chef-vpc-toolkit/chef_bootstrap/centos.bash +47 -0
  33. data/lib/chef-vpc-toolkit/chef_bootstrap/fedora.bash +41 -0
  34. data/lib/chef-vpc-toolkit/chef_bootstrap/rhel.bash +38 -0
  35. data/lib/chef-vpc-toolkit/chef_bootstrap/ubuntu.bash +32 -0
  36. data/lib/chef-vpc-toolkit/chef_installer.rb +276 -0
  37. data/lib/chef-vpc-toolkit/cloud_files.bash +67 -0
  38. data/lib/chef-vpc-toolkit/cloud_servers_vpc.rb +285 -0
  39. data/lib/chef-vpc-toolkit/http_util.rb +117 -0
  40. data/lib/chef-vpc-toolkit/ssh_util.rb +22 -0
  41. data/lib/chef-vpc-toolkit/util.rb +56 -0
  42. data/lib/chef-vpc-toolkit/version.rb +8 -0
  43. data/rake/chef_vpc_toolkit.rake +284 -0
  44. data/test/cloud_servers_vpc_test.rb +174 -0
  45. data/test/test_helper.rb +25 -0
  46. metadata +153 -0
@@ -0,0 +1,41 @@
1
+ function install_chef {
2
+
3
+ local INSTALL_TYPE=${1:-"CLIENT"} # CLIENT/SERVER
4
+
5
+ [[ "$INSTALL_TYPE" == "CLIENT" ]] || { echo "Chef server installations are not yet supported on Fedora."; exit 1; }
6
+
7
+ yum install -q -y ruby ruby-devel gcc gcc-c++ automake autoconf rubygems make &> /dev/null || { echo "Failed to install ruby, ruby-devel, etc."; exit 1; }
8
+ gem update --system
9
+ gem update
10
+ gem install json -v 1.1.4 --no-rdoc --no-ri &> /dev/null || \
11
+ { echo "Failed to install JSON gem on $HOSTNAME."; exit 1; }
12
+ gem install ohai -v 0.5.6 --no-rdoc --no-ri &> /dev/null || \
13
+ { echo "Failed to install ohai gem on $HOSTNAME."; exit 1; }
14
+ gem install chef -v 0.9.8 --no-rdoc --no-ri &> /dev/null || \
15
+ { echo "Failed to install chef gem on $HOSTNAME."; exit 1; }
16
+
17
+ for DIR in /etc/chef /var/log/chef /var/cache/chef /var/lib/chef /var/run/chef; do
18
+ mkdir -p $DIR
19
+ done
20
+
21
+ cat > /etc/chef/client.rb <<-"EOF_CAT"
22
+ log_level :info
23
+ log_location STDOUT
24
+ ssl_verify_mode :verify_none
25
+ chef_server_url "http://localhost:4000"
26
+ file_cache_path "/var/cache/chef"
27
+ file_backup_path "/var/lib/chef/backup"
28
+ pid_file "/var/run/chef/client.pid"
29
+ cache_options({ :path => "/var/cache/chef/checksums", :skip_expires => true})
30
+ signing_ca_user "chef"
31
+ Mixlib::Log::Formatter.show_time = true
32
+ validation_client_name "chef-validator"
33
+ validation_key "/etc/chef/validation.pem"
34
+ client_key "/etc/chef/client.pem"
35
+ EOF_CAT
36
+
37
+ cp /usr/lib/ruby/gems/1.8/gems/chef-0.9.8/distro/redhat/etc/init.d/chef-client /etc/init.d/
38
+ cp /usr/lib/ruby/gems/1.8/gems/chef-0.9.8/distro/redhat/etc/logrotate.d/chef-client /etc/logrotate.d/
39
+ chmod 755 /etc/init.d/chef-client
40
+
41
+ }
@@ -0,0 +1,38 @@
1
+ function install_chef {
2
+
3
+ local INSTALL_TYPE=${1:-"CLIENT"} # CLIENT/SERVER
4
+
5
+ # cached RPMs from ELFF
6
+ local CDN_BASE="http://c2521002.cdn.cloudfiles.rackspacecloud.com"
7
+
8
+ local TARBALL="chef-client-0.9.8-rhel5-x86_64.tar.gz"
9
+ if [[ "$INSTALL_TYPE" == "SERVER" ]]; then
10
+ TARBALL="chef-server-0.9.8-rhel5-x86_64.tar.gz"
11
+ fi
12
+
13
+ rpm -q rsync &> /dev/null || yum install -y -q rsync
14
+ rpm -q wget &> /dev/null || yum install -y -q wget
15
+
16
+ if ! rpm -q rubygem-chef &> /dev/null; then
17
+
18
+ local CHEF_RPM_DIR=$(mktemp -d)
19
+
20
+ wget "$CDN_BASE/$TARBALL" -O "$CHEF_RPM_DIR/chef.tar.gz" &> /dev/null \
21
+ || { echo "Failed to download Chef RPM tarball."; exit 1; }
22
+ cd $CHEF_RPM_DIR
23
+
24
+ tar xzf chef.tar.gz || { echo "Failed to extract Chef tarball."; exit 1; }
25
+ rm chef.tar.gz
26
+ cd chef*
27
+ yum install -q -y --nogpgcheck */*.rpm
28
+ if [[ "$INSTALL_TYPE" == "SERVER" ]]; then
29
+ rpm -q rubygem-chef-server &> /dev/null || { echo "Failed to install chef."; exit 1; }
30
+ else
31
+ rpm -q rubygem-chef &> /dev/null || { echo "Failed to install chef."; exit 1; }
32
+ fi
33
+ cd /tmp
34
+ rm -Rf "$CHEF_RPM_DIR"
35
+
36
+ fi
37
+
38
+ }
@@ -0,0 +1,32 @@
1
+ function install_chef {
2
+
3
+ CODENAME=$(cat /etc/*release | grep CODENAME | sed -e "s|^.*=\([^$]*\)$|\1|")
4
+ local INSTALL_TYPE=${1:-"CLIENT"} # CLIENT/SERVER
5
+
6
+ [ -f /etc/apt/sources.list.d/opscode.list ] || echo "deb http://apt.opscode.com $CODENAME main" > /etc/apt/sources.list.d/opscode.list
7
+ wget -q -O- http://apt.opscode.com/packages@opscode.com.gpg.key | apt-key add - &> /dev/null || { echo "Failed to configure Apt repo."; exit 1; }
8
+
9
+ dpkg -L rsync &> /dev/null || apt-get install -y rsync &> /dev/null
10
+
11
+ if ! dpkg -L chef &> /dev/null; then
12
+
13
+ if [[ "$INSTALL_TYPE" == "SERVER" ]]; then
14
+
15
+ [[ "$CODENAME" == "lucid" ]] || { echo "Ubuntu 10.0.4 lucid is required for Chef server installations."; exit 1; }
16
+ apt-get update &> /dev/null || { echo "Failed to apt-get update."; exit 1; }
17
+ echo "chef-solr chef-solr/amqp_password password YA1B2C301234Z" | debconf-set-selections &> /dev/null || { echo "Failed to set debconf selections for chef-solr."; exit 1; }
18
+ echo "chef chef/chef_server_url string http://localhost:4000" | debconf-set-selections &> /dev/null || { echo "Failed to set debconf selections for chef."; exit 1; }
19
+ DEBIAN_FRONTEND=noninteractive apt-get install -y chef-server chef &> /dev/null || { echo "Failed to install the Chef Server via apt-get on $HOSTNAME."; exit 1; }
20
+ else
21
+ apt-get update &> /dev/null || { echo "Failed to apt-get update."; exit 1; }
22
+ echo "chef chef/chef_server_url string http://localhost:4000" | debconf-set-selections &> /dev/null || { echo "Failed to set debconf selections for chef."; exit 1; }
23
+ DEBIAN_FRONTEND=noninteractive apt-get install -y chef &> /dev/null || { echo "Failed to install Chef via apt-get on $HOSTNAME."; exit 1; }
24
+ fi
25
+
26
+ /etc/init.d/chef-client stop &> /dev/null
27
+ sleep 2
28
+ rm /var/log/chef/client.log
29
+
30
+ fi
31
+
32
+ }
@@ -0,0 +1,276 @@
1
+ require 'rubygems'
2
+ require 'json'
3
+ require 'yaml'
4
+
5
+ module ChefVPCToolkit
6
+
7
+ module ChefInstaller
8
+ CHEF_INSTALL_FUNCTIONS=File.dirname(__FILE__) + "/chef-0.9.bash"
9
+
10
+ def self.load_configs
11
+
12
+ config_file=CHEF_VPC_PROJECT + File::SEPARATOR + "config" + File::SEPARATOR + "chef_installer.yml"
13
+
14
+ if File.exists?(config_file) then
15
+ return YAML.load_file(config_file)
16
+ else
17
+ raise "The config/chef_installer.conf file is missing."
18
+ end
19
+
20
+ end
21
+
22
+ # validate the chef.json config file by parsing it
23
+ def self.validate_json(options)
24
+
25
+ Util.raise_if_nil_or_empty(options, "chef_json_file")
26
+ begin
27
+ JSON.parse(IO.read(options["chef_json_file"]))
28
+ rescue Exception => e
29
+ puts "Failed to parse Chef JSON config file:"
30
+ puts ""
31
+ raise
32
+ end
33
+
34
+ if not options["databags_json_file"].nil? and not options["databags_json_file"].empty?
35
+ begin
36
+ JSON.parse(IO.read(options["databags_json_file"]))
37
+ rescue Exception => e
38
+ puts "Failed to parse Databag JSON config file:"
39
+ puts ""
40
+ raise
41
+ end
42
+ end
43
+
44
+ end
45
+
46
+ def self.install_chef_server(options, machine_os_types)
47
+
48
+ Util.raise_if_nil_or_empty(options, "ssh_gateway_ip")
49
+ Util.raise_if_nil_or_empty(options, "chef_json_file")
50
+ Util.raise_if_nil_or_empty(options, "chef_server_name")
51
+
52
+ # should we install a Chef client on the server?
53
+ json=JSON.parse(IO.read(options["chef_json_file"]))
54
+ configure_client_script=""
55
+ start_client_script=""
56
+ if json.has_key?(options["chef_server_name"]) then
57
+ configure_client_script="configure_chef_client '#{options['chef_server_name']}' ''"
58
+ start_client_script="start_chef_client"
59
+ end
60
+ knife_add_nodes_script=""
61
+ json.each_pair do |node_name, node_json|
62
+ run_list=node_json['run_list'].inspect
63
+ node_json.delete("run_list")
64
+ attributes=node_json.to_json.to_s
65
+ knife_add_nodes_script+="knife_add_node '#{node_name}' '#{run_list}' '#{attributes}'\n"
66
+ end
67
+
68
+ cookbook_urls=""
69
+ if options["chef_cookbook_repos"] then
70
+ cookbook_urls=options["chef_cookbook_repos"].inject { |sum, c| sum + " " + c }
71
+ end
72
+ os_type=machine_os_types[options['chef_server_name']]
73
+
74
+ data=%x{
75
+ ssh root@#{options['ssh_gateway_ip']} bash <<-"EOF_GATEWAY"
76
+ ssh #{options['chef_server_name']} bash <<-"EOF_BASH"
77
+ echo "Installing Chef server on: $HOSTNAME"
78
+ EOF_BASH
79
+ EOF_GATEWAY
80
+ }
81
+ puts data
82
+
83
+ data=%x{
84
+ ssh root@#{options['ssh_gateway_ip']} bash <<-"EOF_GATEWAY"
85
+ ssh #{options['chef_server_name']} bash <<-"EOF_BASH"
86
+ #{IO.read(File.dirname(__FILE__) + "/cloud_files.bash")}
87
+ #{IO.read(File.dirname(__FILE__) + "/chef_bootstrap/#{os_type}.bash")}
88
+ #{IO.read(CHEF_INSTALL_FUNCTIONS)}
89
+ install_chef "SERVER"
90
+
91
+ mkdir -p /root/cookbook-repos
92
+
93
+ configure_chef_server
94
+ start_chef_server
95
+
96
+ #{configure_client_script}
97
+
98
+ configure_knife "#{options["knife_editor"]}"
99
+
100
+ knife_upload_cookbooks_and_roles
101
+
102
+ #{knife_add_nodes_script}
103
+
104
+ #{start_client_script}
105
+
106
+ EOF_BASH
107
+ EOF_GATEWAY
108
+ }
109
+ puts data
110
+
111
+ return client_validation_key(options)
112
+
113
+ end
114
+
115
+ def self.client_validation_key(options)
116
+
117
+ client_validation_key=%x{
118
+ ssh root@#{options['ssh_gateway_ip']} bash <<-"EOF_GATEWAY"
119
+ ssh #{options['chef_server_name']} bash <<-"EOF_BASH"
120
+ #{IO.read(CHEF_INSTALL_FUNCTIONS)}
121
+ print_client_validation_key
122
+ EOF_BASH
123
+ EOF_GATEWAY
124
+ }
125
+
126
+ raise "Client validation key is blank." if client_validation_key.nil? or client_validation_key.empty?
127
+
128
+ return client_validation_key
129
+
130
+ end
131
+
132
+ def self.install_chef_clients(options, client_validation_key, os_types)
133
+
134
+ # configure Chef clients on each node
135
+ json=JSON.parse(IO.read(options['chef_json_file']))
136
+ json.each_pair do |hostname, json_hash|
137
+ if hostname != options['chef_server_name']
138
+ install_chef_client(options, hostname, client_validation_key, os_types[hostname])
139
+ end
140
+ end
141
+
142
+ end
143
+
144
+ def self.install_chef_client(options, client_name, client_validation_key, os_type)
145
+
146
+ puts "Installing Chef client on: #{client_name}"
147
+
148
+ data=%x{
149
+ ssh root@#{options['ssh_gateway_ip']} bash <<-"EOF_GATEWAY"
150
+ ssh #{client_name} bash <<-"EOF_BASH"
151
+ #{IO.read(File.dirname(__FILE__) + "/cloud_files.bash")}
152
+ #{IO.read(File.dirname(__FILE__) + "/chef_bootstrap/#{os_type}.bash")}
153
+ #{IO.read(CHEF_INSTALL_FUNCTIONS)}
154
+ install_chef "CLIENT"
155
+ configure_chef_client '#{options['chef_server_name']}' '#{client_validation_key}'
156
+ start_chef_client
157
+ EOF_BASH
158
+ EOF_GATEWAY
159
+ }
160
+ puts data
161
+
162
+ end
163
+
164
+ def self.create_databags(options)
165
+
166
+ Util.raise_if_nil_or_empty(options, "ssh_gateway_ip")
167
+
168
+ if options["databags_json_file"].nil? or options["databags_json_file"].empty?
169
+ puts "No databag config file specified."
170
+ return
171
+ end
172
+ printf "Creating databags..."
173
+ STDOUT.flush
174
+
175
+ if not File.exists?(options["databags_json_file"]) then
176
+ raise "Databags json file is missing: #{options["databags_json_file"]}."
177
+ end
178
+
179
+ json=JSON.parse(IO.read(options["databags_json_file"]))
180
+
181
+ databag_cmds=""
182
+
183
+ json.each_pair do |bag_name, items_json|
184
+ databag_cmds+="knife data bag delete '#{bag_name}' -y &> /dev/null \n"
185
+ databag_cmds+="knife data bag create '#{bag_name}' -y \n"
186
+
187
+ items_json.each do |item_json|
188
+
189
+ item_id=item_json["id"]
190
+ raise "Databags json missing item ID." if item_id.nil? or item_id.empty?
191
+ databag_cmds+="knife_create_databag '#{bag_name}' '#{item_id}' '#{item_json.to_json.to_s}'\n"
192
+ end
193
+
194
+ data=%x{
195
+ ssh root@#{options['ssh_gateway_ip']} bash <<-"EOF_GATEWAY"
196
+ #{IO.read(CHEF_INSTALL_FUNCTIONS)}
197
+ #{databag_cmds}
198
+ EOF_GATEWAY
199
+ }
200
+ puts "OK."
201
+
202
+ end
203
+
204
+ end
205
+
206
+ def self.knife_readd_node(options, client_name)
207
+
208
+ Util.raise_if_nil_or_empty(options, "ssh_gateway_ip")
209
+ Util.raise_if_nil_or_empty(options, "chef_json_file")
210
+
211
+ json=JSON.parse(IO.read(options["chef_json_file"]))
212
+ node_json=json[client_name]
213
+ run_list=node_json['run_list'].inspect
214
+ node_json.delete("run_list")
215
+ attributes=node_json.to_json.to_s
216
+ data=%x{
217
+ ssh root@#{options['ssh_gateway_ip']} bash <<-"EOF_GATEWAY"
218
+ #{IO.read(CHEF_INSTALL_FUNCTIONS)}
219
+ knife_delete_node '#{client_name}'
220
+ knife_add_node '#{client_name}' '#{run_list}' '#{attributes}'
221
+ EOF_GATEWAY
222
+ }
223
+
224
+ end
225
+
226
+ def self.tail_log(gateway_ip, server_name, log_file="/var/log/chef/client.log", num_lines="100")
227
+ %x{ssh root@#{gateway_ip} ssh #{server_name} tail -n #{num_lines} #{log_file}}
228
+ end
229
+
230
+ def self.rsync_cookbook_repos(options, local_dir="#{CHEF_VPC_PROJECT}/cookbook-repos/", remote_directory="/root/cookbook-repos")
231
+
232
+ if File.exists?(local_dir) then
233
+ $stdout.printf "Syncing local Chef cookbook repositories..."
234
+ configs=Util.load_configs
235
+ %x{ssh root@#{options['ssh_gateway_ip']} bash <<-"EOF_SSH"
236
+ mkdir -p #{remote_directory}
237
+ if [ -f /usr/bin/yum ]; then
238
+ rpm -q rsync &> /dev/null || yum install -y -q rsync
239
+ else
240
+ dpkg -L rsync > /dev/null 2>&1 || apt-get install -y --quiet rsync > /dev/null 2>&1
241
+ fi
242
+ EOF_SSH
243
+ }
244
+ system("rsync -azL '#{local_dir}' root@#{options['ssh_gateway_ip']}:#{remote_directory}")
245
+ puts "OK"
246
+ end
247
+
248
+ cookbook_urls=""
249
+ if options["chef_cookbook_repos"] then
250
+ cookbook_urls=options["chef_cookbook_repos"].inject { |sum, c| sum + " " + c }
251
+ end
252
+
253
+ data=%x{
254
+ ssh root@#{options['ssh_gateway_ip']} bash <<-"EOF_SSH"
255
+ #{IO.read(File.dirname(__FILE__) + "/cloud_files.bash")}
256
+ #{IO.read(CHEF_INSTALL_FUNCTIONS)}
257
+
258
+ if [ -n "#{cookbook_urls}" ]; then
259
+ download_cookbook_repos "#{cookbook_urls}"
260
+ fi
261
+
262
+ if [ -f /root/.chef/knife.rb ]; then
263
+ echo -n "Uploading cookbooks and roles..."
264
+ knife_upload_cookbooks_and_roles
265
+ echo "OK"
266
+ fi
267
+
268
+ EOF_SSH
269
+ }
270
+ puts data
271
+
272
+ end
273
+
274
+ end
275
+
276
+ end
@@ -0,0 +1,67 @@
1
+ # Load the username and password into variables that are used by this
2
+ # bash API.
3
+ function install_curl {
4
+
5
+ if [ -f /usr/bin/yum ]; then
6
+ rpm -q curl &> /dev/null || yum install -y -q curl
7
+ elif [ -f /usr/bin/dpkg ]; then
8
+ dpkg -L curl > /dev/null 2>&1 || apt-get install -y --quiet curl > /dev/null 2>&1
9
+ else
10
+ echo "Failed to install curl. Unsupported platform."
11
+ exit 1
12
+ fi
13
+
14
+ }
15
+
16
+ function load_cloud_configs {
17
+
18
+ if [ -z "$RACKSPACE_CLOUD_API_USERNAME" ] || [ -z "$RACKSPACE_CLOUD_API_KEY" ]; then
19
+
20
+ if [ ! -f ~/.rackspace_cloud ]; then
21
+ echo "Missing .rackspace_cloud config file."
22
+ exit 1
23
+ fi
24
+
25
+ export RACKSPACE_CLOUD_API_USERNAME=$(cat ~/.rackspace_cloud | grep "userid" | sed -e "s|.*: \([^ \n\r]*\).*|\1|")
26
+ export RACKSPACE_CLOUD_API_KEY=$(cat ~/.rackspace_cloud | grep "api_key" | sed -e "s|.*: \([^ \n\r]*\).*|\1|")
27
+
28
+ if [ -z "$RACKSPACE_CLOUD_API_USERNAME" ] || [ -z "$RACKSPACE_CLOUD_API_KEY" ]; then
29
+ echo "Please define a 'userid' and 'api_key' in ~/.rackspace_cloud"
30
+ exit 1
31
+ fi
32
+
33
+ fi
34
+
35
+ }
36
+
37
+ # Download a private file from Rackspace Cloud Files using the secure
38
+ # X-Storage-Url.
39
+ function download_cloud_file {
40
+
41
+ if (( $# != 2 )); then
42
+ echo "Failed to download cloud file."
43
+ echo "usage: download_cloud_file <container_url> <output_file>"
44
+ exit 1
45
+ fi
46
+
47
+ load_cloud_configs
48
+ install_curl
49
+
50
+ local CONTAINER_URL=$1
51
+ local OUTPUT_FILE=$2
52
+
53
+ local AUTH_RESPONSE=$(curl -D - \
54
+ -H "X-Auth-Key: $RACKSPACE_CLOUD_API_KEY" \
55
+ -H "X-Auth-User: $RACKSPACE_CLOUD_API_USERNAME" \
56
+ "https://auth.api.rackspacecloud.com/v1.0" 2> /dev/null)
57
+
58
+ [[ $? == 0 ]] || { echo "Failed to authenticate."; exit 1; }
59
+
60
+ local AUTH_TOKEN=$(echo $AUTH_RESPONSE | \
61
+ sed -e "s|.* X-Auth-Token: \([^ \n\r]*\).*|\1|g")
62
+ local STORAGE_URL=$(echo $AUTH_RESPONSE | \
63
+ sed -e "s|.* X-Storage-Url: \([^ \n\r]*\).*|\1|g")
64
+
65
+ curl -s -X GET -H "X-Auth-Token: $AUTH_TOKEN" "$STORAGE_URL/$CONTAINER_URL" -o "$OUTPUT_FILE"
66
+
67
+ }