Skip to content

Commit

Permalink
Merge branch 'devel' into make_cloud_providers_dynamic_28722
Browse files Browse the repository at this point in the history
  • Loading branch information
djyasin authored Oct 16, 2024
2 parents 2f4a4f5 + 764dcbf commit 6d70934
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 14 deletions.
7 changes: 1 addition & 6 deletions .github/actions/run_awx_devel/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@ outputs:
ip:
description: The IP of the tools_awx_1 container
value: ${{ steps.data.outputs.ip }}
admin-token:
description: OAuth token for admin user
value: ${{ steps.data.outputs.admin_token }}
runs:
using: composite
steps:
Expand Down Expand Up @@ -62,6 +59,4 @@ runs:
shell: bash
run: |
AWX_IP=$(docker inspect -f '{{.NetworkSettings.Networks.awx.IPAddress}}' tools_awx_1)
ADMIN_TOKEN=$(docker exec -i tools_awx_1 awx-manage create_oauth2_token --user admin)
echo "ip=$AWX_IP" >> $GITHUB_OUTPUT
echo "admin_token=$ADMIN_TOKEN" >> $GITHUB_OUTPUT
echo "ip=$AWX_IP" >> $GITHUB_OUTPUT
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,8 @@ jobs:
echo "::remove-matcher owner=python::" # Disable annoying annotations from setup-python
echo '[general]' > ~/.tower_cli.cfg
echo 'host = https://${{ steps.awx.outputs.ip }}:8043' >> ~/.tower_cli.cfg
echo 'oauth_token = ${{ steps.awx.outputs.admin-token }}' >> ~/.tower_cli.cfg
echo 'username = admin' >> ~/.tower_cli.cfg
echo 'password = password' >> ~/.tower_cli.cfg
echo 'verify_ssl = false' >> ~/.tower_cli.cfg
TARGETS="$(ls awx_collection/tests/integration/targets | grep '${{ matrix.target-regex.regex }}' | tr '\n' ' ')"
make COLLECTION_VERSION=100.100.100-git COLLECTION_TEST_TARGET="--requirements $TARGETS" test_collection_integration
Expand Down
32 changes: 25 additions & 7 deletions awxkit/awxkit/api/pages/base.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import collections
import logging
import typing

from requests.auth import HTTPBasicAuth

Expand All @@ -12,6 +13,11 @@
log = logging.getLogger(__name__)


class AuthUrls(typing.TypedDict):
access_token: str
personal_token: str


class Base(Page):
def silent_delete(self):
"""Delete the object. If it's already deleted, ignore the error"""
Expand Down Expand Up @@ -141,30 +147,27 @@ def load_authtoken(self, username='', password=''):

load_default_authtoken = load_authtoken

def get_oauth2_token(self, username='', password='', client_id=None, description='AWX CLI', client_secret=None, scope='write'):
default_cred = config.credentials.default
username = username or default_cred.username
password = password or default_cred.password
def _request_token(self, auth_urls, username, password, client_id, description, client_secret, scope):
req = collections.namedtuple('req', 'headers')({})
if client_id and client_secret:
HTTPBasicAuth(client_id, client_secret)(req)
req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
resp = self.connection.post(
f"{config.api_base_path}o/token/",
auth_urls["access_token"],
data={"grant_type": "password", "username": username, "password": password, "scope": scope},
headers=req.headers,
)
elif client_id:
req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
resp = self.connection.post(
f"{config.api_base_path}o/token/",
auth_urls["access_token"],
data={"grant_type": "password", "username": username, "password": password, "client_id": client_id, "scope": scope},
headers=req.headers,
)
else:
HTTPBasicAuth(username, password)(req)
resp = self.connection.post(
'{0}v2/users/{1}/personal_tokens/'.format(config.api_base_path, username),
auth_urls['personal_token'],
json={"description": description, "application": None, "scope": scope},
headers=req.headers,
)
Expand All @@ -177,6 +180,21 @@ def get_oauth2_token(self, username='', password='', client_id=None, description
else:
raise exception_from_status_code(resp.status_code)

def get_oauth2_token(self, username='', password='', client_id=None, description='AWX CLI', client_secret=None, scope='write'):
default_cred = config.credentials.default
username = username or default_cred.username
password = password or default_cred.password
# Try gateway first, fallback to controller
urls: AuthUrls = {"access_token": "/o/token/", "personal_token": f"{config.gateway_base_path}v1/tokens/"}
try:
return self._request_token(urls, username, password, client_id, description, client_secret, scope)
except exc.NotFound:
urls = {
"access_token": f"{config.api_base_path}o/token/",
"personal_token": f"{config.api_base_path}v2/users/{username}/personal_tokens/",
}
return self._request_token(urls, username, password, client_id, description, client_secret, scope)

def load_session(self, username='', password=''):
default_cred = config.credentials.default
self.connection.login(
Expand Down
1 change: 1 addition & 0 deletions awxkit/awxkit/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,4 @@ def getvalue(self, name):
config.prevent_teardown = to_bool(os.getenv('AWXKIT_PREVENT_TEARDOWN', False))
config.use_sessions = to_bool(os.getenv('AWXKIT_SESSIONS', False))
config.api_base_path = os.getenv('AWXKIT_API_BASE_PATH', '/api/')
config.gateway_base_path = os.getenv('AWXKIT_GATEWAY_BASE_PATH', '/api/gateway/')
59 changes: 59 additions & 0 deletions awxkit/test/api/pages/test_base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from http.client import NOT_FOUND
import pytest
from pytest_mock import MockerFixture
from requests import Response

from awxkit.api.pages import Base
from awxkit.config import config


@pytest.fixture(autouse=True)
def setup_config(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(config, "credentials", {"default": {"username": "foo", "password": "bar"}}, raising=False)
monkeypatch.setattr(config, "base_url", "", raising=False)


@pytest.fixture
def response(mocker):
r = mocker.Mock()
r.status_code = NOT_FOUND
r.json.return_value = {
"token": "my_personal_token",
"access_token": "my_token",
}
return r


@pytest.mark.parametrize(
("auth_creds", "url", "token"),
[
({"client_id": "foo", "client_secret": "bar"}, "/o/token/", "my_token"),
({"client_id": "foo"}, "/o/token/", "my_token"),
({}, "/api/gateway/v1/tokens/", "my_personal_token"),
],
)
def test_get_oauth2_token_from_gateway(mocker: MockerFixture, response: Response, auth_creds, url, token):
post = mocker.patch("requests.Session.post", return_value=response)
base = Base()
ret = base.get_oauth2_token(**auth_creds)
assert post.call_count == 1
assert post.call_args.args[0] == url
assert ret == token


@pytest.mark.parametrize(
("auth_creds", "url", "token"),
[
({"client_id": "foo", "client_secret": "bar"}, "/api/o/token/", "my_token"),
({"client_id": "foo"}, "/api/o/token/", "my_token"),
({}, "/api/v2/users/foo/personal_tokens/", "my_personal_token"),
],
)
def test_get_oauth2_token_from_controller(mocker: MockerFixture, response: Response, auth_creds, url, token):
type(response).ok = mocker.PropertyMock(side_effect=[False, True])
post = mocker.patch("requests.Session.post", return_value=response)
base = Base()
ret = base.get_oauth2_token(**auth_creds)
assert post.call_count == 2
assert post.call_args.args[0] == url
assert ret == token

0 comments on commit 6d70934

Please sign in to comment.