chef-vpc-toolkit 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ }