Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

do a bunch of stuff #185

Merged
merged 28 commits into from
Mar 27, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
257f9d6
move kwarg parsing to api backend instead of in pepper
mattp- Oct 3, 2018
4d822ee
we should be testing tornado as well as cherrypy
mattp- Mar 7, 2019
3c3ee23
test tornado and cherrypy
mattp- Mar 11, 2019
010f3fb
make verbose setting map to root logger
mattp- Mar 11, 2019
9596613
use https instead of git:// in requirements
mattp- Mar 11, 2019
68a00fb
tox.ini add missing indentation
mattp- Mar 11, 2019
ceb0f83
fix broken wheel test
mattp- Mar 12, 2019
02acbc1
fix broken poller tests
mattp- Mar 12, 2019
59c013a
improve /run handling, token handling with /run
mattp- Mar 12, 2019
f2a7177
Improve ret handling across cherrypy/tornado
mattp- Mar 12, 2019
cab58b2
add showlocals to tox.ini
mattp- Mar 12, 2019
f87815f
Revert "improve /run handling, token handling with /run"
mattp- Mar 12, 2019
c4790e7
don't pass an x-auth-header when /run
mattp- Mar 12, 2019
ee498f8
make outputter behave more like salt
mattp- Mar 12, 2019
a7df3b5
parameterize and improve /run | /login handling
mattp- Mar 12, 2019
f212cf1
pass along timeout kwarg in low
mattp- Mar 12, 2019
063ac82
raise logged exception failures as errors
mattp- Mar 12, 2019
89fd103
this needs xfail on tornado as well
mattp- Mar 12, 2019
6c293e8
let tox test multiple salt versions, both cherrypy and tornado backends
mattp- Mar 12, 2019
af444a4
Merge remote-tracking branch 'upstream/develop' into develop
mattp- Mar 12, 2019
79a92eb
we need to gate off suppliing the low-side timeout until a future rel…
mattp- Mar 12, 2019
53c1c7b
clean up travis
gtmanfred Mar 15, 2019
5a6589b
use TRAVIS_PYTHON_VERSION for tox
gtmanfred Mar 15, 2019
984b963
import yamlloader since it is also in 2017.7
gtmanfred Mar 15, 2019
efaaa06
run flake8 for py2 and py3
gtmanfred Mar 15, 2019
6fbbf9e
drop 2017.7 from tests
gtmanfred Mar 15, 2019
b37b47d
what a weird error to see from this
gtmanfred Mar 15, 2019
9332567
fix python 3.7
gtmanfred Mar 15, 2019
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
32 changes: 18 additions & 14 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,31 @@ services:

before_install:
- pyenv versions
- pyenv version-name
- env

install:
- pip install tox

python:
- '2.7'
- '3.4'
- '3.5'
- '3.6'
- '3.7-dev'

env:
- SALT=-v2018.3 BACKEND=-cherrypy CODECOV=py
- SALT=-v2018.3 BACKEND=-tornado CODECOV=py
- SALT=-v2019.2 BACKEND=-cherrypy CODECOV=py
- SALT=-v2019.2 BACKEND=-tornado CODECOV=py

matrix:
include:
- env: TOXENV=27,coverage CODECOV=py
python: 2.7
- env: TOXENV=34,coverage CODECOV=py
python: 3.4
- env: TOXENV=35,coverage CODECOV=py
python: 3.5
- env: TOXENV=36,coverage CODECOV=py
python: 3.6
- env: TOXENV=37,coverage CODECOV=py
python: 3.7-dev
- env: TOXENV=flake8
python: 3.6
env:

script:
- docker run -v $PWD:/pepper -ti --rm gtmanfred/pepper:latest tox -c /pepper/tox.ini -e "${CODECOV}${TOXENV}"
- PYTHON="${TRAVIS_PYTHON_VERSION%-dev}"
- docker run -v $PWD:/pepper -ti --rm gtmanfred/pepper:latest tox -c /pepper/tox.ini -e "${TRAVIS_PYTHON_VERSION%%.*}flake8,${CODECOV}${PYTHON//./}${BACKEND}${SALT}"

after_success:
- sudo chown $USER .tox/
Expand Down
94 changes: 59 additions & 35 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 @@ -569,12 +580,16 @@ def poll_for_returns(self, api, load):
},
}])

responded = set(jid_ret['return'][0].keys()) ^ set(ret_nodes)
inner_ret = jid_ret['return'][0]
# sometimes ret is nested in data
if 'data' in inner_ret:
inner_ret = inner_ret['data']

responded = set(inner_ret.keys()) ^ set(ret_nodes)

for node in responded:
yield None, "{{{}: {}}}".format(
node,
jid_ret['return'][0][node])
ret_nodes = list(jid_ret['return'][0].keys())
yield None, [{node: inner_ret[node]}]
ret_nodes = list(inner_ret.keys())

if set(ret_nodes) == set(nodes):
exit_code = 0
Expand All @@ -583,8 +598,9 @@ def poll_for_returns(self, api, load):
time.sleep(self.seconds_to_wait)

exit_code = exit_code if self.options.fail_if_minions_dont_respond else 0
yield exit_code, "{{Failed: {}}}".format(
list(set(ret_nodes) ^ set(nodes)))
failed = list(set(ret_nodes) ^ set(nodes))
if failed:
yield exit_code, [{'Failed': failed}]

def login(self, api):
login = api.token if self.options.userun else api.login
Expand Down Expand Up @@ -626,21 +642,23 @@ def low(self, api, load):
for i in load:
i['token'] = self.auth['token']

# having a defined salt_version means changes from https://github.com/saltstack/salt/pull/51979
# are available if backend is tornado, so safe to supply timeout
if self.options.timeout and api.salt_version:
for i in load:
if not i.get('client', '').startswith('wheel'):
i['timeout'] = self.options.timeout

return api.low(load, path=path)

def run(self):
'''
Parse all arguments and call salt-api
'''
# move logger instantiation to method?
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
# set up logging
rootLogger = logging.getLogger(name=None)
rootLogger.addHandler(logging.StreamHandler())
rootLogger.setLevel(max(logging.ERROR - (self.options.verbose * 10), 1))

api = pepper.Pepper(
self.parse_url(),
Expand All @@ -649,6 +667,12 @@ def run(self):

self.login(api)

load = self.parse_cmd(api)

for entry in load:
if not entry.get('client', '').startswith('wheel'):
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
26 changes: 25 additions & 1 deletion 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 @@ -217,7 +219,7 @@ def req(self, path, data=None):
req.add_header('Content-Length', clen)

# Add auth header to request
if self.auth and 'token' in self.auth and self.auth['token']:
if path != '/run' and self.auth and 'token' in self.auth and self.auth['token']:
req.add_header('X-Auth-Token', self.auth['token'])

# Send request
Expand All @@ -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()
38 changes: 29 additions & 9 deletions pepper/script.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,29 @@ def output(self):
def __call__(self):
try:
for exit_code, result in self.cli.run():
if HAS_SALT and not self.cli.options.userun and self.opts:
logger.info('Use Salt outputters')
for ret in json.loads(result)['return']:
if HAS_SALT and self.opts:
logger.debug('Use Salt outputters')
result = json.loads(result)

# unwrap ret in some cases
if 'return' in result:
result = result['return']

for ret in result:
if isinstance(ret, dict):
if self.cli.options.client == 'local':
if self.cli.options.client.startswith('local'):
for minionid, minionret in ret.items():
if isinstance(minionret, dict) and 'ret' in minionret:
# rest_tornado doesnt return full_return directly
# it will always be from get_event, so the output differs slightly
if isinstance(minionret, dict) and 'return' in minionret:
# version >= 2017.7
salt.output.display_output(
{minionid: minionret['return']},
self.cli.options.output or minionret.get('out', None) or 'nested',
opts=self.opts
)
# cherrypy returns with ret via full_return
elif isinstance(minionret, dict) and 'ret' in minionret:
# version >= 2017.7
salt.output.display_output(
{minionid: minionret['ret']},
Expand All @@ -70,9 +86,13 @@ def __call__(self):
opts=self.opts
)
elif 'data' in ret:
# unfold runners
outputter = ret.get('outputter', 'nested')
if isinstance(ret['data'], dict) and 'return' in ret['data']:
ret = ret['data']['return']
salt.output.display_output(
ret['data'],
self.cli.options.output or ret.get('outputter', 'nested'),
ret,
self.cli.options.output or outputter,
opts=self.opts
)
else:
Expand All @@ -84,7 +104,7 @@ def __call__(self):
else:
salt.output.display_output(
{self.cli.options.client: ret},
'nested',
self.cli.options.output or 'nested',
opts=self.opts,
)
else:
Expand All @@ -95,7 +115,7 @@ def __call__(self):
print(result)
if exit_code is not None:
if exit_code == 0:
return PepperRetcode().validate(self.cli.options, json.loads(result)['return'])
return PepperRetcode().validate(self.cli.options, result)
return exit_code
except (PepperException, PepperAuthException, PepperArgumentsException) as exc:
print('Pepper error: {0}'.format(exc), file=sys.stderr)
Expand Down
Loading