1
- import copy
1
+ import six
2
+
2
3
import datetime
3
- import itertools
4
4
import logging
5
5
import os
6
6
import re
7
7
import socket
8
8
import subprocess
9
9
import 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
+
12
14
import weakref
13
15
14
16
import pkg_resources
28
30
puts Chef::Config.configuration.to_json
29
31
""" .strip ()
30
32
33
+
31
34
def api_stack_value ():
32
35
if not hasattr (api_stack , 'value' ):
33
36
api_stack .value = []
34
37
return api_stack .value
35
38
36
39
37
40
class UnknownRubyExpression (Exception ):
41
+
38
42
"""Token exception for unprocessed Ruby expressions."""
39
43
40
44
41
- class ChefRequest (urllib2 .Request ):
45
+ class ChefRequest (six .moves .urllib .request .Request ):
46
+
42
47
"""Workaround for using PUT/DELETE with urllib2."""
48
+
43
49
def __init__ (self , * args , ** kwargs ):
44
50
self ._method = kwargs .pop ('method' , None )
45
51
# 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 )
47
53
48
54
def get_method (self ):
49
55
if self ._method :
50
56
return self ._method
51
- return urllib2 .Request .get_method (self )
57
+ return six . moves . urllib . request .Request .get_method (self )
52
58
53
59
54
60
class ChefAPI (object ):
61
+
55
62
"""The ChefAPI object is a wrapper for a single Chef server.
56
63
57
64
.. admonition:: The API stack
@@ -68,16 +75,19 @@ class ChefAPI(object):
68
75
69
76
ruby_value_re = re .compile (r'#\{([^}]+)\}' )
70
77
env_value_re = re .compile (r'ENV\[(.+)\]' )
78
+ ruby_string_re = re .compile (r'^\s*(["\'])(.*?)\1\s*$' )
71
79
72
80
def __init__ (self , url , key , client , version = '0.10.8' , headers = {}):
73
81
self .url = url .rstrip ('/' )
74
- self .parsed_url = urlparse .urlparse (self .url )
82
+ self .parsed_url = six . moves . urllib . parse .urlparse (self .url )
75
83
if not isinstance (key , Key ):
76
84
key = Key (key )
85
+ if not key .key :
86
+ raise ValueError ("ChefAPI attribute 'key' was invalid." )
77
87
self .key = key
78
88
self .client = client
79
89
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 ())
81
91
self .version_parsed = pkg_resources .parse_version (self .version )
82
92
self .platform = self .parsed_url .hostname == 'api.opscode.com'
83
93
if not api_stack_value ():
@@ -96,12 +106,19 @@ def from_config_file(cls, path):
96
106
url = key_path = client_name = None
97
107
for line in open (path ):
98
108
if not line .strip () or line .startswith ('#' ):
99
- continue # Skip blanks and comments
109
+ continue # Skip blanks and comments
100
110
parts = line .split (None , 1 )
101
111
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
103
113
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
+
105
122
def _ruby_value (match ):
106
123
expr = match .group (1 ).strip ()
107
124
if expr == 'current_dir' :
@@ -117,25 +134,32 @@ def _ruby_value(match):
117
134
except UnknownRubyExpression :
118
135
continue
119
136
if key == 'chef_server_url' :
137
+ log .debug ('Found URL: %r' , value )
120
138
url = value
121
139
elif key == 'node_name' :
140
+ log .debug ('Found client name: %r' , value )
122
141
client_name = value
123
142
elif key == 'client_key' :
143
+ log .debug ('Found key path: %r' , value )
124
144
key_path = value
125
145
if not os .path .isabs (key_path ):
126
146
# Relative paths are relative to the config file
127
147
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 ) :
129
149
# 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
131
152
proc = subprocess .Popen ('ruby' , stdin = subprocess .PIPE , stdout = subprocess .PIPE )
132
153
script = config_ruby_script % path .replace ('\\ ' , '\\ \\ ' ).replace ("'" , "\\ '" )
133
154
out , err = proc .communicate (script )
134
155
if proc .returncode == 0 and out .strip ():
135
156
data = json .loads (out )
157
+ log .debug ('Ruby parse succeeded with %r' , data )
136
158
url = data .get ('chef_server_url' )
137
159
client_name = data .get ('node_name' )
138
160
key_path = data .get ('client_key' )
161
+ else :
162
+ log .debug ('Ruby parse failed with exit code %s: %s' , proc .returncode , out .strip ())
139
163
if not url :
140
164
# Still no URL, can't use this config
141
165
log .debug ('Still no Chef server URL found' )
@@ -177,39 +201,42 @@ def __exit__(self, type, value, traceback):
177
201
178
202
def _request (self , method , url , data , headers ):
179
203
# Testing hook, subclass and override for WSGI intercept
204
+ if six .PY3 and data :
205
+ data = data .encode ()
180
206
request = ChefRequest (url , data , headers , method = method )
181
- return urllib2 .urlopen (request ).read ()
207
+ return six . moves . urllib . request .urlopen (request ).read ()
182
208
183
209
def request (self , method , path , headers = {}, data = None ):
184
210
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 )
188
214
request_headers = {}
189
215
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 ()))
191
217
request_headers ['x-chef-version' ] = self .version
192
218
request_headers .update (auth_headers )
193
219
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 :
196
223
e .content = e .read ()
197
224
try :
198
- e .content = json .loads (e .content )
225
+ e .content = json .loads (e .content . decode () )
199
226
raise ChefServerError .from_error (e .content ['error' ], code = e .code )
200
227
except ValueError :
201
228
pass
202
229
raise e
203
230
return response
204
231
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 ())
207
234
headers ['accept' ] = 'application/json'
208
235
if data is not None :
209
236
headers ['content-type' ] = 'application/json'
210
237
data = json .dumps (data )
211
238
response = self .request (method , path , headers , data )
212
- return json .loads (response )
239
+ return json .loads (response . decode () )
213
240
214
241
def __getitem__ (self , path ):
215
242
return self .api_request ('GET' , path )
0 commit comments