chef-vpc-toolkit 2.1.0 → 2.2.0

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