Skip to content
This repository was archived by the owner on May 9, 2020. It is now read-only.

Replace urllib2 with requests, add option to disable SSL cert verification #39

Merged
merged 2 commits into from
Sep 10, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 15 additions & 25 deletions chef/api.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import six
import datetime
import logging
import os
import re
import socket
import subprocess
import threading
import six.moves.urllib.request
import six.moves.urllib.error
import six.moves.urllib.parse
import weakref
import six

import pkg_resources

import requests

from chef.auth import sign_request
from chef.exceptions import ChefServerError
from chef.rsa import Key
Expand All @@ -38,19 +37,6 @@ class UnknownRubyExpression(Exception):
"""Token exception for unprocessed Ruby expressions."""


class ChefRequest(six.moves.urllib.request.Request):
"""Workaround for using PUT/DELETE with urllib2."""
def __init__(self, *args, **kwargs):
self._method = kwargs.pop('method', None)
# Request is an old-style class, no super() allowed.
six.moves.urllib.request.Request.__init__(self, *args, **kwargs)

def get_method(self):
if self._method:
return self._method
return six.moves.urllib.request.Request.get_method(self)


class ChefAPI(object):
"""The ChefAPI object is a wrapper for a single Chef server.

Expand All @@ -70,7 +56,7 @@ class ChefAPI(object):
env_value_re = re.compile(r'ENV\[(.+)\]')
ruby_string_re = re.compile(r'^\s*(["\'])(.*?)\1\s*$')

def __init__(self, url, key, client, version='0.10.8', headers={}):
def __init__(self, url, key, client, version='0.10.8', headers={}, ssl_verify=True):
self.url = url.rstrip('/')
self.parsed_url = six.moves.urllib.parse.urlparse(self.url)
if not isinstance(key, Key):
Expand All @@ -83,6 +69,7 @@ def __init__(self, url, key, client, version='0.10.8', headers={}):
self.headers = dict((k.lower(), v) for k, v in six.iteritems(headers))
self.version_parsed = pkg_resources.parse_version(self.version)
self.platform = self.parsed_url.hostname == 'api.opscode.com'
self.ssl_verify = ssl_verify
if not api_stack_value():
self.set_default()

Expand All @@ -97,6 +84,7 @@ def from_config_file(cls, path):
log.debug('Unable to read config file "%s"', path)
return
url = key_path = client_name = None
ssl_verify = True
for line in open(path):
if not line.strip() or line.startswith('#'):
continue # Skip blanks and comments
Expand All @@ -107,6 +95,10 @@ def from_config_file(cls, path):
md = cls.ruby_string_re.search(value)
if md:
value = md.group(2)
elif key == 'ssl_verify_mode':
log.debug('Found ssl_verify_mode: %r', value)
ssl_verify = (value.strip() != ':verify_none')
log.debug('ssl_verify = %s', ssl_verify)
else:
# Not a string, don't even try
log.debug('Value for {0} does not look like a string: {1}'.format(key, value))
Expand Down Expand Up @@ -137,6 +129,7 @@ def _ruby_value(match):
if not os.path.isabs(key_path):
# Relative paths are relative to the config file
key_path = os.path.abspath(os.path.join(os.path.dirname(path), key_path))

if not (url and client_name and key_path):
# No URL, no chance this was valid, try running Ruby
log.debug('No Chef server config found, trying Ruby parse')
Expand Down Expand Up @@ -165,7 +158,7 @@ def _ruby_value(match):
return
if not client_name:
client_name = socket.getfqdn()
return cls(url, key_path, client_name)
return cls(url, key_path, client_name, ssl_verify=ssl_verify)

@staticmethod
def get_global():
Expand All @@ -192,11 +185,8 @@ def __exit__(self, type, value, traceback):
del api_stack_value()[-1]

def _request(self, method, url, data, headers):
# Testing hook, subclass and override for WSGI intercept
if six.PY3 and data:
data = data.encode()
request = ChefRequest(url, data, headers, method=method)
return six.moves.urllib.request.urlopen(request).read()
request = requests.api.request(method, url, headers=headers, data=data, verify=self.ssl_verify)
return request

def request(self, method, path, headers={}, data=None):
auth_headers = sign_request(key=self.key, http_method=method,
Expand Down Expand Up @@ -228,7 +218,7 @@ def api_request(self, method, path, headers={}, data=None):
headers['content-type'] = 'application/json'
data = json.dumps(data)
response = self.request(method, path, headers, data)
return json.loads(response.decode())
return response.json()

def __getitem__(self, path):
return self.api_request('GET', path)
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
'Programming Language :: Python',
],
zip_safe = False,
install_requires = ['six>=1.9.0'],
install_requires = ['six>=1.9.0','requests>=2.7.0'],
tests_require = ['unittest2', 'mock'],
test_suite = 'unittest2.collector',
)