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

open source awxkit #4451

Merged
merged 2 commits into from
Aug 9, 2019
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
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,7 @@ test:
. $(VENV_BASE)/awx/bin/activate; \
fi; \
PYTHONDONTWRITEBYTECODE=1 py.test -p no:cacheprovider -n auto $(TEST_DIRS)
cd awxkit && $(VENV_BASE)/awx/bin/tox -re py3
awx-manage check_migrations --dry-run --check -n 'vNNN_missing_migration_file'

test_unit:
Expand Down
100 changes: 100 additions & 0 deletions awxkit/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
report.xml
report.pylama
*,cover
.hypothesis/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# IPython Notebook
.ipynb_checkpoints

# pyenv
.python-version

# celery beat schedule file
celerybeat-schedule

# dotenv
.env

# virtualenv
venv/
ENV/

# Spyder project settings
.spyderproject

# Rope project settings
.ropeproject

# vim
*.swp

# mac OS
*.DS_Store

# pytest
*.pytest_cache
4 changes: 4 additions & 0 deletions awxkit/MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
include requirements.txt
include setup.py
recursive-include awxkit *.py *.yml *.md
recursive-include test *.py *.yml *.md
4 changes: 4 additions & 0 deletions awxkit/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
awxkit
======

Python library that backs the provided `awx` command line client.
4 changes: 4 additions & 0 deletions awxkit/awxkit/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from .api import pages, client, resources # NOQA
from .config import config # NOQA
from . import awx # NOQA
from .ws import WSClient # NOQA
2 changes: 2 additions & 0 deletions awxkit/awxkit/api/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .pages import * # NOQA
from .client import * # NOQA
121 changes: 121 additions & 0 deletions awxkit/awxkit/api/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import logging

import requests

from awxkit import exceptions as exc
from awxkit.config import config


log = logging.getLogger(__name__)


class ConnectionException(exc.Common):

pass


class Token_Auth(requests.auth.AuthBase):
def __init__(self, token, auth_type='Token'):
self.token = token
self.auth_type = auth_type

def __call__(self, request):
request.headers['Authorization'] = '{0.auth_type} {0.token}'.format(self)
return request


def log_elapsed(r, *args, **kwargs): # requests hook to display API elapsed time
log.debug('"{0.request.method} {0.url}" elapsed: {0.elapsed}'.format(r))


class Connection(object):
"""A requests.Session wrapper for establishing connection w/ AWX instance"""

def __init__(self, server, verify=False):
self.server = server
self.verify = verify

if not self.verify:
requests.packages.urllib3.disable_warnings()

self.session = requests.Session()
self.uses_session_cookie = False

def get_session_requirements(self, next='/api/'):
self.get('/api/') # this causes a cookie w/ the CSRF token to be set
return dict(next=next)

def login(self, username=None, password=None, token=None, **kwargs):
if username and password:
_next = kwargs.get('next')
if _next:
headers = self.session.headers.copy()
self.post('/api/login/', headers=headers,
data=dict(username=username, password=password, next=_next))
self.session_id = self.session.cookies.get('sessionid')
self.uses_session_cookie = True
else:
self.session.auth = (username, password)
elif token:
self.session.auth = Token_Auth(token, auth_type=kwargs.get('auth_type', 'Token'))
else:
self.session.auth = None

def logout(self):
if self.uses_session_cookie:
self.session.cookies.pop('sessionid', None)
else:
self.session.auth = None

def request(self, relative_endpoint, method='get', json=None, data=None, query_parameters=None, headers=None):
"""Core requests.Session wrapper that returns requests.Response objects"""
session_request_method = getattr(self.session, method, None)
if not session_request_method:
raise ConnectionException(message="Unknown request method: {0}".format(method))

use_endpoint = relative_endpoint
if self.server.endswith('/') and use_endpoint.startswith('/'):
raise RuntimeError('AWX URL given with trailing slash, remove slash.')
url = '{0.server}{1}'.format(self, use_endpoint)

kwargs = dict(verify=self.verify, params=query_parameters, json=json, data=data,
hooks=dict(response=log_elapsed))

if headers is not None:
kwargs['headers'] = headers

if method in ('post', 'put', 'patch', 'delete'):
kwargs.setdefault('headers', {})['X-CSRFToken'] = self.session.cookies.get('csrftoken')
kwargs['headers']['Referer'] = url

for attempt in range(1, config.client_connection_attempts + 1):
try:
response = session_request_method(url, **kwargs)
break
except requests.exceptions.ConnectionError as err:
if attempt == config.client_connection_attempts:
raise err
log.exception('Failed to reach url: {0}. Retrying.'.format(url))

return response

def delete(self, relative_endpoint):
return self.request(relative_endpoint, method='delete')

def get(self, relative_endpoint, query_parameters=None, headers=None):
return self.request(relative_endpoint, method='get', query_parameters=query_parameters, headers=headers)

def head(self, relative_endpoint):
return self.request(relative_endpoint, method='head')

def options(self, relative_endpoint):
return self.request(relative_endpoint, method='options')

def patch(self, relative_endpoint, json):
return self.request(relative_endpoint, method='patch', json=json)

def post(self, relative_endpoint, json=None, data=None, headers=None):
return self.request(relative_endpoint, method='post', json=json, data=data, headers=headers)

def put(self, relative_endpoint, json):
return self.request(relative_endpoint, method='put', json=json)
7 changes: 7 additions & 0 deletions awxkit/awxkit/api/mixins/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from .has_create import * # NOQA
from .has_instance_groups import HasInstanceGroups # NOQA
from .has_notifications import HasNotifications # NOQA
from .has_status import HasStatus # NOQA
from .has_survey import HasSurvey # NOQA
from .has_variables import HasVariables # NOQA
from .has_copy import HasCopy # NOQA
15 changes: 15 additions & 0 deletions awxkit/awxkit/api/mixins/has_copy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from awxkit.api.pages import Page
from awxkit.utils import random_title


class HasCopy(object):

def can_copy(self):
return self.get_related('copy').can_copy

def copy(self, name=''):
"""Return a copy of current page"""
payload = {"name": name or "Copy - " + random_title()}
endpoint = self.json.related['copy']
page = Page(self.connection, endpoint=endpoint)
return page.post(payload)
Loading