chef-vpc-toolkit 2.1.0 → 2.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 CHANGED
@@ -1,3 +1,17 @@
1
+ * Thu Feb 6 2011 Dan Prince <dan.prince@rackspace.com> - 2.2.0
2
+ - Support command execution with 'rake ssh'.
3
+ - The 'chef:tail_logs' task now supports the SERVER_NAME variable.
4
+ - Support for Ubuntu 10.10 maverick.
5
+ - Add support for connecting to servers which require SSL client auth.
6
+ - Implement 'group:add_server' task.
7
+ - Implement 'group:delete_server' task.
8
+ - Implement 'group:list REMOTE=true' task.
9
+ - Implement 'group:join' task.
10
+ - Implement 'group:join' task.
11
+ - Update the 'chef:install' task to support the SERVER_NAME variable.
12
+ - The 'chef:sync_repos' task is now 'chef:push_repos'.
13
+ - Implement a 'chef:pull_repos' task.
14
+
1
15
  * Tue Jan 11 2011 Dan Prince <dan.prince@rackspace.com> - 2.1.0
2
16
  - Implement 'rake vpn' tasks.
3
17
  - Set permission to 0600 on tmp/server_group files.
data/COPYING CHANGED
@@ -1,7 +1,7 @@
1
1
  Unless otherwise noted, all files are released under the MIT license,
2
2
  exceptions contain licensing information in them.
3
3
 
4
- Copyright (C) 2010 Rackspace US, Inc.
4
+ Copyright (C) 2010-2011 Rackspace US, Inc.
5
5
 
6
6
  Permission is hereby granted, free of charge, to any person obtaining a copy
7
7
  of this software and associated documentation files (the "Software"), to deal
data/README.rdoc CHANGED
@@ -39,32 +39,54 @@ To install the gem:
39
39
 
40
40
  Example commands:
41
41
 
42
- - Create a new cloud server group, upload cookbooks, install chef
42
+ - Create a new server group, upload cookbooks, install chef
43
43
  on all the nodes, sync share data and cookbooks.
44
44
 
45
45
  $ rake create
46
46
 
47
- - List your currently running cloud server groups.
47
+ - List your currently running server groups.
48
48
 
49
49
  $ rake group:list
50
50
 
51
- - SSH into the current (most recently created) cloud server group
51
+ - SSH into the current (most recently created) server group
52
52
 
53
53
  $ rake ssh
54
54
 
55
- - SSH into a cloud server group with an ID of 3
55
+ - SSH into a server group with an ID of 3
56
56
 
57
57
  $ rake ssh GROUP_ID=3
58
58
 
59
- - Delete the cloud server group with an ID of 3
59
+ - Delete the server group with an ID of 3
60
60
 
61
61
  $ rake group:delete GROUP_ID=3
62
62
 
63
- - Rebuild/Re-Chef the 'db1' server in the most recently created cloud
64
- server group
63
+ - Rebuild/Re-Chef the 'db1' server in the most recently created server group
65
64
 
66
65
  $ rake rechef SERVER_NAME=db1
67
66
 
67
+ - Connect to a group as a VPN client. Currently supports and requires
68
+ Network Manager Openvpn, nmcli, and nm-applet (Linux only).
69
+
70
+ $ rake vpn
71
+
72
+ - Disconnect from group as a VPN client
73
+
74
+ $ rake vpn:disconnect
75
+
76
+ == Bash Automation Script
77
+ The following is an example bash script to spin up a group and run commands via SSH. Useful for CI automation in Hudson, etc.
78
+
79
+ #!/bin/bash
80
+
81
+ chef-vpc-toolkit -v
82
+ trap "rake group:delete" INT TERM EXIT # cleanup the group on exit
83
+
84
+ rake create
85
+ VPN_GW=$(rake group:show | grep "VPN gateway IP" | sed -e "s|VPN gateway IP: ||")
86
+ ssh root@$VPN_GW bash <<-EOF_BASH
87
+ # do stuff here
88
+ EOF_BASH
89
+
68
90
  == Author
69
91
 
70
92
  Dan Prince <dan.prince@rackspace.com>
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.1.0
1
+ 2.2.0
@@ -4,12 +4,12 @@
4
4
  "description": "test description",
5
5
  "servers": {
6
6
  "login": {
7
- "image_id": "51",
7
+ "image_id": "69",
8
8
  "flavor_id": "2",
9
9
  "openvpn_server": "true"
10
10
  },
11
11
  "client1": {
12
- "image_id": "51",
12
+ "image_id": "69",
13
13
  "flavor_id": "2"
14
14
  }
15
15
  }
@@ -1,7 +1,12 @@
1
1
  require 'chef-vpc-toolkit/util'
2
2
  require 'chef-vpc-toolkit/chef_installer'
3
- require 'chef-vpc-toolkit/cloud_servers_vpc'
4
- require 'chef-vpc-toolkit/http_util'
5
3
  require 'chef-vpc-toolkit/ssh_util'
6
4
  require 'chef-vpc-toolkit/version'
5
+ require 'chef-vpc-toolkit/xml_util'
7
6
  require 'chef-vpc-toolkit/vpn_network_manager'
7
+ require 'chef-vpc-toolkit/cloud-servers-vpc/connection'
8
+ require 'chef-vpc-toolkit/cloud-servers-vpc/client'
9
+ require 'chef-vpc-toolkit/cloud-servers-vpc/server'
10
+ require 'chef-vpc-toolkit/cloud-servers-vpc/server_group'
11
+ require 'chef-vpc-toolkit/cloud-servers-vpc/ssh_public_key'
12
+ require 'chef-vpc-toolkit/cloud-servers-vpc/vpn_network_interface'
@@ -30,6 +30,7 @@ fi
30
30
 
31
31
  sed -e "s|localhost|$SERVER_NAME|g" -i /etc/chef/client.rb
32
32
  sed -e "s|^chef_server_url.*|chef_server_url \"http://$SERVER_NAME:4000\"|g" -i /etc/chef/client.rb
33
+ sed -e "s|^log_location.*|log_location \"\/var/log/chef/client.log\"|g" -i /etc/chef/client.rb
33
34
 
34
35
  local CHEF_CLIENT_CONF=/etc/default/chef-client
35
36
  [ -d /etc/sysconfig/ ] && CHEF_CLIENT_CONF=/etc/sysconfig/chef-client
@@ -5,8 +5,8 @@ local INSTALL_TYPE=${1:-"CLIENT"} # CLIENT/SERVER
5
5
  [[ "$INSTALL_TYPE" == "CLIENT" ]] || { echo "Chef server installations are not yet supported on Fedora."; exit 1; }
6
6
 
7
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
8
+ #gem update --system
9
+ #gem update
10
10
  gem install json -v 1.1.4 --no-rdoc --no-ri &> /dev/null || \
11
11
  { echo "Failed to install JSON gem on $HOSTNAME."; exit 1; }
12
12
  gem install ohai -v 0.5.6 --no-rdoc --no-ri &> /dev/null || \
@@ -34,7 +34,7 @@ validation_key "/etc/chef/validation.pem"
34
34
  client_key "/etc/chef/client.pem"
35
35
  EOF_CAT
36
36
 
37
- CHEF_GEM_DIR=$(gem contents chef | sed -e "s|\(.*chef-0.9.8\).*|\1|" | head -n 1)
37
+ CHEF_GEM_DIR=$(gem contents chef 2>/dev/null | sed -e "s|\(.*chef-0.9.8\).*|\1|" | head -n 1)
38
38
  cp $CHEF_GEM_DIR/distro/redhat/etc/init.d/chef-client /etc/init.d/
39
39
  cp $CHEF_GEM_DIR/distro/redhat/etc/logrotate.d/chef-client /etc/logrotate.d/
40
40
  chmod 755 /etc/init.d/chef-client
@@ -1,6 +1,7 @@
1
1
  function install_chef {
2
2
 
3
3
  CODENAME=$(cat /etc/*release | grep CODENAME | sed -e "s|^.*=\([^$]*\)$|\1|")
4
+ [[ "$CODENAME" == "maverick" ]] && CODENAME="lucid"
4
5
  local INSTALL_TYPE=${1:-"CLIENT"} # CLIENT/SERVER
5
6
 
6
7
  [ -f /etc/apt/sources.list.d/opscode.list ] || echo "deb http://apt.opscode.com $CODENAME main" > /etc/apt/sources.list.d/opscode.list
@@ -228,10 +228,17 @@ def self.tail_log(gateway_ip, server_name, log_file="/var/log/chef/client.log",
228
228
  %x{ssh -o "StrictHostKeyChecking no" root@#{gateway_ip} ssh #{server_name} tail -n #{num_lines} #{log_file}}
229
229
  end
230
230
 
231
+
232
+ def self.pull_cookbook_repos(options, local_dir="#{CHEF_VPC_PROJECT}/cookbook-repos/", remote_directory="/root/cookbook-repos")
233
+ $stdout.printf "Pulling remote Chef cookbook repositories..."
234
+ system("rsync -azL root@#{options['ssh_gateway_ip']}:#{remote_directory}/* '#{local_dir}'")
235
+ puts "OK"
236
+ end
237
+
231
238
  def self.rsync_cookbook_repos(options, local_dir="#{CHEF_VPC_PROJECT}/cookbook-repos/", remote_directory="/root/cookbook-repos")
232
239
 
233
240
  if File.exists?(local_dir) then
234
- $stdout.printf "Syncing local Chef cookbook repositories..."
241
+ $stdout.printf "Pushing local Chef cookbook repositories..."
235
242
  configs=Util.load_configs
236
243
  %x{ssh -o "StrictHostKeyChecking no" root@#{options['ssh_gateway_ip']} bash <<-"EOF_SSH"
237
244
  mkdir -p #{remote_directory}
@@ -0,0 +1,186 @@
1
+ module ChefVPCToolkit
2
+
3
+ module CloudServersVPC
4
+
5
+ class Client
6
+
7
+ @@data_dir=File.join(CHEF_VPC_PROJECT, "tmp", "clients")
8
+
9
+ def self.data_dir
10
+ @@data_dir
11
+ end
12
+
13
+ def self.data_dir=(dir)
14
+ @@data_dir=dir
15
+ end
16
+
17
+ attr_accessor :id
18
+ attr_accessor :name
19
+ attr_accessor :description
20
+ attr_accessor :status
21
+ attr_accessor :server_group_id
22
+
23
+ def initialize(options={})
24
+ @id=options[:id].to_i
25
+ @name=options[:name]
26
+ @description=options[:description]
27
+ @status=options[:status] or @status = "Pending"
28
+ @server_group_id=options[:server_group_id]
29
+
30
+ @vpn_network_interfaces=[]
31
+ end
32
+
33
+ def vpn_network_interfaces
34
+ @vpn_network_interfaces
35
+ end
36
+
37
+ def cache_to_disk
38
+ FileUtils.mkdir_p(@@data_dir)
39
+ File.open(File.join(@@data_dir, "#{@server_group_id}.xml"), 'w') do |f|
40
+ f.chmod(0600)
41
+ f.write(self.to_xml)
42
+ end
43
+ end
44
+
45
+ def delete
46
+ client_xml_file=File.join(@@data_dir, "#{@server_group_id}.xml")
47
+ if File.exists?(client_xml_file) then
48
+ File.delete(client_xml_file)
49
+ end
50
+ end
51
+
52
+ def self.from_xml(xml)
53
+
54
+ client=nil
55
+ dom = REXML::Document.new(xml)
56
+ REXML::XPath.each(dom, "/client") do |client|
57
+
58
+ client=Client.new(
59
+ :id => XMLUtil.element_text(client,"id").to_i,
60
+ :name => XMLUtil.element_text(client, "name"),
61
+ :description => XMLUtil.element_text(client,"description"),
62
+ :status => XMLUtil.element_text(client,"status"),
63
+ :server_group_id => XMLUtil.element_text(client, "server-group-id").to_i
64
+ )
65
+ REXML::XPath.each(dom, "//vpn-network-interface") do |vni|
66
+ vni = VpnNetworkInterface.new(
67
+ :id => XMLUtil.element_text(vni, "id"),
68
+ :vpn_ip_addr => XMLUtil.element_text(vni, "vpn-ip-addr"),
69
+ :ptp_ip_addr => XMLUtil.element_text(vni, "ptp-ip-addr"),
70
+ :client_key => XMLUtil.element_text(vni, "client-key"),
71
+ :client_cert => XMLUtil.element_text(vni, "client-cert"),
72
+ :ca_cert => XMLUtil.element_text(vni, "ca-cert")
73
+ )
74
+ client.vpn_network_interfaces << vni
75
+ end
76
+ end
77
+
78
+ client
79
+
80
+ end
81
+
82
+ def to_xml
83
+
84
+ xml = Builder::XmlMarkup.new
85
+ xml.tag! "client" do |sg|
86
+ sg.id(@id)
87
+ sg.name(@name)
88
+ sg.description(@description)
89
+ sg.status(@status)
90
+ sg.tag! "server-group-id", @server_group_id
91
+ sg.tag! "vpn-network-interfaces", {"type" => "array"} do |interfaces|
92
+ @vpn_network_interfaces.each do |vni|
93
+ interfaces.tag! "vpn-network-interface" do |xml_vni|
94
+ xml_vni.id(vni.id)
95
+ xml_vni.tag! "vpn-ip-addr", vni.vpn_ip_addr
96
+ xml_vni.tag! "ptp-ip-addr", vni.ptp_ip_addr
97
+ xml_vni.tag! "client-key", vni.client_key
98
+ xml_vni.tag! "client-cert", vni.client_cert
99
+ xml_vni.tag! "ca-cert", vni.ca_cert
100
+ end
101
+ end
102
+ end
103
+
104
+ end
105
+ xml.target!
106
+
107
+ end
108
+
109
+
110
+ # Poll the server group until it is online.
111
+ # :timeout - max number of seconds to wait before raising an exception.
112
+ # Defaults to 1500
113
+ def poll_until_online(options={})
114
+
115
+ timeout=options[:timeout] or timeout = ENV['VPN_CLIENT_TIMEOUT']
116
+ if timeout.nil? or timeout.empty? then
117
+ timeout=300 # defaults to 5 minutes
118
+ end
119
+
120
+ online = false
121
+ count=0
122
+ until online or (count*5) >= timeout.to_i do
123
+ count+=1
124
+ begin
125
+ client=Client.fetch(:id => @id, :source => "remote")
126
+
127
+ if client.status == "Online" then
128
+ online = true
129
+ else
130
+ yield client if block_given?
131
+ sleep 5
132
+ end
133
+ rescue EOFError
134
+ end
135
+ end
136
+ if (count*20) >= timeout.to_i then
137
+ raise "Timeout waiting for client to come online."
138
+ end
139
+
140
+ end
141
+
142
+ def self.create(server_group, client_name)
143
+
144
+ xml = Builder::XmlMarkup.new
145
+ xml.client do |client|
146
+ client.name(client_name)
147
+ client.description("Toolkit Client: #{client_name}")
148
+ client.tag! "server-group-id", server_group.id
149
+ end
150
+
151
+ xml=Connection.post("/clients.xml", xml.target!)
152
+ client=Client.from_xml(xml)
153
+ client.cache_to_disk
154
+ client
155
+
156
+ end
157
+
158
+ # Fetch a server group. The following options are available:
159
+ #
160
+ # :id - The ID of the group containing the client to fetch.
161
+ # :source - valid options are 'remote' and 'cache'
162
+ #
163
+ def self.fetch(options = {})
164
+
165
+ source = options[:source] or source = "remote"
166
+
167
+ if source == "remote" then
168
+ id=options[:id] or raise "Please specify a Client ID."
169
+ xml=Connection.get("/clients/#{id}.xml")
170
+ Client.from_xml(xml)
171
+ elsif source == "cache" then
172
+ id=options[:id] or id = ENV['GROUP_ID']
173
+ client_xml_file=File.join(@@data_dir, "#{id}.xml")
174
+ raise "No client files exist." if not File.exists?(client_xml_file)
175
+ Client.from_xml(IO.read(client_xml_file))
176
+ else
177
+ raise "Invalid fetch :source specified."
178
+ end
179
+
180
+ end
181
+
182
+ end
183
+
184
+ end
185
+
186
+ end
@@ -0,0 +1,146 @@
1
+
2
+ require 'uri'
3
+ require 'net/http'
4
+ require 'net/https'
5
+ require 'openssl'
6
+ require 'openssl/x509'
7
+
8
+ module ChefVPCToolkit
9
+
10
+ module CloudServersVPC
11
+
12
+ class Connection
13
+
14
+ MULTI_PART_BOUNDARY="jtZ!pZ1973um"
15
+
16
+ @@http=nil
17
+ @@auth_user=nil
18
+ @@auth_password=nil
19
+
20
+ def self.init_connection
21
+
22
+ configs=Util.load_configs
23
+
24
+ base_url = configs["cloud_servers_vpc_url"]
25
+ @@auth_user = configs["cloud_servers_vpc_username"]
26
+ @@auth_password = configs["cloud_servers_vpc_password"]
27
+
28
+ ssl_key = configs["ssl_key"]
29
+ ssl_cert = configs["ssl_cert"]
30
+ ssl_ca_cert = configs["ssl_ca_cert"]
31
+
32
+ url=URI.parse(base_url)
33
+ @@http = Net::HTTP.new(url.host,url.port)
34
+
35
+ if base_url =~ /^https/
36
+ @@http.use_ssl = true
37
+ if ssl_ca_cert then
38
+ @@http.verify_mode = OpenSSL::SSL::VERIFY_PEER
39
+ else
40
+ @@http.verify_mode = OpenSSL::SSL::VERIFY_NONE
41
+ end
42
+
43
+ if ssl_key then
44
+ pkey_data=IO.read(ssl_key)
45
+ if pkey_data.start_with?("-----BEGIN RSA PRIVATE KEY-----")
46
+ @@http.key=OpenSSL::PKey::RSA.new(pkey_data)
47
+ else
48
+ @@http.key=OpenSSL::PKey::DSA.new(pkey_data)
49
+ end
50
+ end
51
+ @@http.cert=OpenSSL::X509::Certificate.new(IO.read(ssl_cert)) if ssl_cert
52
+ @@http.ca_file=ssl_ca_cert if ssl_ca_cert
53
+ end
54
+
55
+ end
56
+
57
+ def self.file_upload(url_path, file_data={}, post_data={})
58
+ init_connection if @@http.nil?
59
+ req = Net::HTTP::Post.new(url_path)
60
+
61
+ post_arr=[]
62
+ post_data.each_pair do |key, value|
63
+ post_arr << "--#{MULTI_PART_BOUNDARY}\r\n"
64
+ post_arr << "Content-Disposition: form-data; name=\"#{key}\"\r\n"
65
+ post_arr << "\r\n"
66
+ post_arr << value
67
+ post_arr << "\r\n"
68
+ end
69
+
70
+ file_data.each_pair do |name, file|
71
+ post_arr << "--#{MULTI_PART_BOUNDARY}\r\n"
72
+ post_arr << "Content-Disposition: form-data; name=\"#{name}\"; filename=\"#{File.basename(file)}\"\r\n"
73
+ post_arr << "Content-Type: text/plain\r\n"
74
+ post_arr << "\r\n"
75
+ post_arr << File.read(file)
76
+ post_arr << "\r\n--#{MULTI_PART_BOUNDARY}--\r\n"
77
+ end
78
+ post_arr << "--#{MULTI_PART_BOUNDARY}--\r\n\r\n"
79
+
80
+ req.body=post_arr.join
81
+
82
+ req.basic_auth @@auth_user, @@auth_password if @@auth_user and @@auth_password
83
+ req["Content-Type"] = "multipart/form-data, boundary=#{MULTI_PART_BOUNDARY}"
84
+
85
+ response = @@http.request(req)
86
+ case response
87
+ when Net::HTTPSuccess
88
+ return response.body
89
+ else
90
+ puts response.body
91
+ response.error!
92
+ end
93
+ end
94
+
95
+ def self.post(url_path, post_data)
96
+ init_connection if @@http.nil?
97
+ req = Net::HTTP::Post.new(url_path)
98
+ if post_data.kind_of?(String) then
99
+ req.body=post_data
100
+ elsif post_data.kind_of?(Hash) then
101
+ req.form_data=post_data
102
+ else
103
+ raise "Invalid post data type."
104
+ end
105
+ req.basic_auth @@auth_user, @@auth_password if @@auth_user and @@auth_password
106
+ response = @@http.request(req)
107
+ case response
108
+ when Net::HTTPSuccess
109
+ return response.body
110
+ else
111
+ puts response.body
112
+ response.error!
113
+ end
114
+ end
115
+
116
+ def self.get(url_path)
117
+ init_connection if @@http.nil?
118
+ req = Net::HTTP::Get.new(url_path)
119
+ req.basic_auth @@auth_user, @@auth_password if @@auth_user and @@auth_password
120
+ response = @@http.request(req)
121
+ case response
122
+ when Net::HTTPSuccess
123
+ return response.body
124
+ else
125
+ response.error!
126
+ end
127
+ end
128
+
129
+ def self.delete(url_path)
130
+ init_connection if @@http.nil?
131
+ req = Net::HTTP::Delete.new(url_path)
132
+ req.basic_auth @@auth_user, @@auth_password if @@auth_user and @@auth_password
133
+ response = @@http.request(req)
134
+ case response
135
+ when Net::HTTPSuccess
136
+ return response.body
137
+ else
138
+ response.error!
139
+ end
140
+ end
141
+
142
+ end
143
+
144
+ end
145
+
146
+ end