Skip to content

Commit

Permalink
move kwarg parsing to api backend instead of in pepper
Browse files Browse the repository at this point in the history
rather than attempting to deconstruct args locally to fully reproduce
the proper low state to pass to the api, we instead leave the kwargs as
is. The reason for this is the RunnerClient will deconstruct and
deserialize nested yaml args. We could equivalently do that locally but
it seems pepper tries to be a no-dependency dist so we just hand off the
work instead.

This PR partially depends on release of saltstack/salt#50124
to determine if a release is neon or newer; however, we can safely assume that if
the salt_version header is not provided in a request it is a salt-api
older than that.

Fixes saltstack#57
  • Loading branch information
mattp- committed Oct 19, 2018
1 parent fcf7ec3 commit 061e6e7
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 25 deletions.
61 changes: 36 additions & 25 deletions pepper/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,7 @@ def parse_login(self):

return ret

def parse_cmd(self):
def parse_cmd(self, api):
'''
Extract the low data for a command from the passed CLI params
'''
Expand Down Expand Up @@ -505,26 +505,37 @@ def parse_cmd(self):
low['arg'] = args
elif client.startswith('runner'):
low['fun'] = args.pop(0)
for arg in args:
if '=' in arg:
key, value = arg.split('=', 1)
try:
low[key] = json.loads(value)
except JSONDecodeError:
low[key] = value
else:
low.setdefault('arg', []).append(arg)
# post https://github.com/saltstack/salt/pull/50124, kwargs can be
# passed as is in foo=bar form, splitting and deserializing will
# happen in salt-api. additionally, the presence of salt-version header
# means we are neon or newer, so don't need a finer grained check
if api.salt_version:
low['arg'] = args
else:
for arg in args:
if '=' in arg:
key, value = arg.split('=', 1)
try:
low[key] = json.loads(value)
except JSONDecodeError:
low[key] = value
else:
low.setdefault('arg', []).append(arg)
elif client.startswith('wheel'):
low['fun'] = args.pop(0)
for arg in args:
if '=' in arg:
key, value = arg.split('=', 1)
try:
low[key] = json.loads(value)
except JSONDecodeError:
low[key] = value
else:
low.setdefault('arg', []).append(arg)
# see above comment in runner arg handling
if api.salt_version:
low['arg'] = args
else:
for arg in args:
if '=' in arg:
key, value = arg.split('=', 1)
try:
low[key] = json.loads(value)
except JSONDecodeError:
low[key] = value
else:
low.setdefault('arg', []).append(arg)
elif client.startswith('ssh'):
if len(args) < 2:
self.parser.error("Command or target not specified")
Expand Down Expand Up @@ -636,19 +647,19 @@ def run(self):
logger.addHandler(logging.StreamHandler())
logger.setLevel(max(logging.ERROR - (self.options.verbose * 10), 1))

load = self.parse_cmd()

for entry in load:
if entry.get('client', '').startswith('local'):
entry['full_return'] = True

api = pepper.Pepper(
self.parse_url(),
debug_http=self.options.debug_http,
ignore_ssl_errors=self.options.ignore_ssl_certificate_errors)

self.login(api)

load = self.parse_cmd(api)

for entry in load:
if entry.get('client', '').startswith('local'):
entry['full_return'] = True

if self.options.fail_if_minions_dont_respond:
for exit_code, ret in self.poll_for_returns(api, load): # pragma: no cover
yield exit_code, json.dumps(ret, sort_keys=True, indent=4)
Expand Down
24 changes: 24 additions & 0 deletions pepper/libpepper.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
'''
import json
import logging
import re
import ssl

from pepper.exceptions import PepperException
Expand Down Expand Up @@ -79,6 +80,7 @@ def __init__(self, api_url='https://localhost:8000', debug_http=False, ignore_ss
self.debug_http = int(debug_http)
self._ssl_verify = not ignore_ssl_errors
self.auth = {}
self.salt_version = None

def req_stream(self, path):
'''
Expand Down Expand Up @@ -231,6 +233,10 @@ def req(self, path, data=None):
if (self.debug_http):
logger.debug('Response: %s', content)
ret = json.loads(content)

if not self.salt_version and 'x-salt-version' in f.headers:
self._parse_salt_version(f.headers['x-salt-version'])

except (HTTPError, URLError) as exc:
logger.debug('Error with request', exc_info=True)
status = getattr(exc, 'code', None)
Expand Down Expand Up @@ -285,6 +291,10 @@ def req_requests(self, path, data=None):
if resp.status_code == 500:
# TODO should be resp.raise_from_status
raise PepperException('Server error.')

if not self.salt_version and 'x-salt-version' in resp.headers:
self._parse_salt_version(resp.headers['x-salt-version'])

return resp.json()

def low(self, lowstate, path='/'):
Expand Down Expand Up @@ -479,3 +489,17 @@ def _construct_url(self, path):

relative_path = path.lstrip('/')
return urlparse.urljoin(self.api_url, relative_path)

def _parse_salt_version(self, version):
# borrow from salt.version
git_describe_regex = re.compile(
r'(?:[^\d]+)?(?P<major>[\d]{1,4})'
r'\.(?P<minor>[\d]{1,2})'
r'(?:\.(?P<bugfix>[\d]{0,2}))?'
r'(?:\.(?P<mbugfix>[\d]{0,2}))?'
r'(?:(?P<pre_type>rc|a|b|alpha|beta|nb)(?P<pre_num>[\d]{1}))?'
r'(?:(?:.*)-(?P<noc>(?:[\d]+|n/a))-(?P<sha>[a-z0-9]{8}))?'
)
match = git_describe_regex.match(version)
if match:
self.salt_version = match.groups()

0 comments on commit 061e6e7

Please sign in to comment.