1- import copy
1+ import six
2+
23import datetime
3- import itertools
44import logging
55import os
66import re
77import socket
88import subprocess
99import threading
10- import urllib2
11- import urlparse
10+ import six .moves .urllib .request
11+ import six .moves .urllib .error
12+ import six .moves .urllib .parse
13+
1214import weakref
1315
1416import pkg_resources
2830puts Chef::Config.configuration.to_json
2931""" .strip ()
3032
33+
3134def api_stack_value ():
3235 if not hasattr (api_stack , 'value' ):
3336 api_stack .value = []
3437 return api_stack .value
3538
3639
3740class UnknownRubyExpression (Exception ):
41+
3842 """Token exception for unprocessed Ruby expressions."""
3943
4044
41- class ChefRequest (urllib2 .Request ):
45+ class ChefRequest (six .moves .urllib .request .Request ):
46+
4247 """Workaround for using PUT/DELETE with urllib2."""
48+
4349 def __init__ (self , * args , ** kwargs ):
4450 self ._method = kwargs .pop ('method' , None )
4551 # Request is an old-style class, no super() allowed.
46- urllib2 .Request .__init__ (self , * args , ** kwargs )
52+ six . moves . urllib . request .Request .__init__ (self , * args , ** kwargs )
4753
4854 def get_method (self ):
4955 if self ._method :
5056 return self ._method
51- return urllib2 .Request .get_method (self )
57+ return six . moves . urllib . request .Request .get_method (self )
5258
5359
5460class ChefAPI (object ):
61+
5562 """The ChefAPI object is a wrapper for a single Chef server.
5663
5764 .. admonition:: The API stack
@@ -68,16 +75,19 @@ class ChefAPI(object):
6875
6976 ruby_value_re = re .compile (r'#\{([^}]+)\}' )
7077 env_value_re = re .compile (r'ENV\[(.+)\]' )
78+ ruby_string_re = re .compile (r'^\s*(["\'])(.*?)\1\s*$' )
7179
7280 def __init__ (self , url , key , client , version = '0.10.8' , headers = {}):
7381 self .url = url .rstrip ('/' )
74- self .parsed_url = urlparse .urlparse (self .url )
82+ self .parsed_url = six . moves . urllib . parse .urlparse (self .url )
7583 if not isinstance (key , Key ):
7684 key = Key (key )
85+ if not key .key :
86+ raise ValueError ("ChefAPI attribute 'key' was invalid." )
7787 self .key = key
7888 self .client = client
7989 self .version = version
80- self .headers = dict ((k .lower (), v ) for k , v in headers .iteritems ())
90+ self .headers = dict ((k .lower (), v ) for k , v in headers .items ())
8191 self .version_parsed = pkg_resources .parse_version (self .version )
8292 self .platform = self .parsed_url .hostname == 'api.opscode.com'
8393 if not api_stack_value ():
@@ -96,12 +106,19 @@ def from_config_file(cls, path):
96106 url = key_path = client_name = None
97107 for line in open (path ):
98108 if not line .strip () or line .startswith ('#' ):
99- continue # Skip blanks and comments
109+ continue # Skip blanks and comments
100110 parts = line .split (None , 1 )
101111 if len (parts ) != 2 :
102- continue # Not a simple key/value, we can't parse it anyway
112+ continue # Not a simple key/value, we can't parse it anyway
103113 key , value = parts
104- value = value .strip ().strip ('"\' ' )
114+ md = cls .ruby_string_re .search (value )
115+ if md :
116+ value = md .group (2 )
117+ else :
118+ # Not a string, don't even try
119+ log .debug ('Value for %s does not look like a string: %s' % (key , value ))
120+ continue
121+
105122 def _ruby_value (match ):
106123 expr = match .group (1 ).strip ()
107124 if expr == 'current_dir' :
@@ -117,25 +134,32 @@ def _ruby_value(match):
117134 except UnknownRubyExpression :
118135 continue
119136 if key == 'chef_server_url' :
137+ log .debug ('Found URL: %r' , value )
120138 url = value
121139 elif key == 'node_name' :
140+ log .debug ('Found client name: %r' , value )
122141 client_name = value
123142 elif key == 'client_key' :
143+ log .debug ('Found key path: %r' , value )
124144 key_path = value
125145 if not os .path .isabs (key_path ):
126146 # Relative paths are relative to the config file
127147 key_path = os .path .abspath (os .path .join (os .path .dirname (path ), key_path ))
128- if not url :
148+ if not ( url and client_name and key_path ) :
129149 # No URL, no chance this was valid, try running Ruby
130- log .debug ('No Chef server URL found, trying Ruby parse' )
150+ log .debug ('No Chef server config found, trying Ruby parse' )
151+ url = key_path = client_name = None
131152 proc = subprocess .Popen ('ruby' , stdin = subprocess .PIPE , stdout = subprocess .PIPE )
132153 script = config_ruby_script % path .replace ('\\ ' , '\\ \\ ' ).replace ("'" , "\\ '" )
133154 out , err = proc .communicate (script )
134155 if proc .returncode == 0 and out .strip ():
135156 data = json .loads (out )
157+ log .debug ('Ruby parse succeeded with %r' , data )
136158 url = data .get ('chef_server_url' )
137159 client_name = data .get ('node_name' )
138160 key_path = data .get ('client_key' )
161+ else :
162+ log .debug ('Ruby parse failed with exit code %s: %s' , proc .returncode , out .strip ())
139163 if not url :
140164 # Still no URL, can't use this config
141165 log .debug ('Still no Chef server URL found' )
@@ -177,39 +201,42 @@ def __exit__(self, type, value, traceback):
177201
178202 def _request (self , method , url , data , headers ):
179203 # Testing hook, subclass and override for WSGI intercept
204+ if six .PY3 and data :
205+ data = data .encode ()
180206 request = ChefRequest (url , data , headers , method = method )
181- return urllib2 .urlopen (request ).read ()
207+ return six . moves . urllib . request .urlopen (request ).read ()
182208
183209 def request (self , method , path , headers = {}, data = None ):
184210 auth_headers = sign_request (key = self .key , http_method = method ,
185- path = self .parsed_url .path + path .split ('?' , 1 )[0 ], body = data ,
186- host = self .parsed_url .netloc , timestamp = datetime .datetime .utcnow (),
187- user_id = self .client )
211+ path = self .parsed_url .path + path .split ('?' , 1 )[0 ], body = data ,
212+ host = self .parsed_url .netloc , timestamp = datetime .datetime .utcnow (),
213+ user_id = self .client )
188214 request_headers = {}
189215 request_headers .update (self .headers )
190- request_headers .update (dict ((k .lower (), v ) for k , v in headers .iteritems ()))
216+ request_headers .update (dict ((k .lower (), v ) for k , v in headers .items ()))
191217 request_headers ['x-chef-version' ] = self .version
192218 request_headers .update (auth_headers )
193219 try :
194- response = self ._request (method , self .url + path , data , dict ((k .capitalize (), v ) for k , v in request_headers .iteritems ()))
195- except urllib2 .HTTPError , e :
220+ response = self ._request (method , self .url + path , data , dict (
221+ (k .capitalize (), v ) for k , v in request_headers .items ()))
222+ except six .moves .urllib .error .HTTPError as e :
196223 e .content = e .read ()
197224 try :
198- e .content = json .loads (e .content )
225+ e .content = json .loads (e .content . decode () )
199226 raise ChefServerError .from_error (e .content ['error' ], code = e .code )
200227 except ValueError :
201228 pass
202229 raise e
203230 return response
204231
205- def api_request (self , method , path , headers = {}, data = None ):
206- headers = dict ((k .lower (), v ) for k , v in headers .iteritems ())
232+ def api_request (self , method , path , headers = {}, data = None ):
233+ headers = dict ((k .lower (), v ) for k , v in headers .items ())
207234 headers ['accept' ] = 'application/json'
208235 if data is not None :
209236 headers ['content-type' ] = 'application/json'
210237 data = json .dumps (data )
211238 response = self .request (method , path , headers , data )
212- return json .loads (response )
239+ return json .loads (response . decode () )
213240
214241 def __getitem__ (self , path ):
215242 return self .api_request ('GET' , path )
0 commit comments