Skip to content

Commit

Permalink
Add tenant name/id header when scoped to tenant (CASMPET-6319)
Browse files Browse the repository at this point in the history
  • Loading branch information
Brad Klein authored and erl-hpe committed May 8, 2023
1 parent f42542b commit 49d3d90
Show file tree
Hide file tree
Showing 9 changed files with 101 additions and 27 deletions.
21 changes: 19 additions & 2 deletions cray/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ def cli(ctx, *args, **kwargs):
"--hostname", default=None, no_global=True,
help='Hostname of cray system.'
)
@option(
"--tenant", default=None, no_global=True,
help='Tenant name to scope requests for.'
)
@option(
"--no-auth", is_flag=True,
help='Do not attempt to authenticate.'
Expand All @@ -103,7 +107,7 @@ def cli(ctx, *args, **kwargs):
"--overwrite", is_flag=True,
help="Overwrite existing configuration if it exists"
)
def init(ctx, hostname, no_auth, overwrite, **kwargs):
def init(ctx, hostname, no_auth, overwrite, tenant, **kwargs):
""" Initialize/reinitialize the Cray CLI """
# pylint: disable=line-too-long
config_dir = ctx.obj.get('config_dir')
Expand Down Expand Up @@ -137,9 +141,21 @@ def init(ctx, hostname, no_auth, overwrite, **kwargs):
if not re.match("^http(s)?://", hostname):
hostname = f'https://{hostname}'

if tenant is None:
tenant = ctx.obj.get(
'config',
{}
).get(
'core.tenant',
click.prompt('Tenant Name (leave blank for global scope):',
default="",
type=str)
)

initialize_dirs(config_dir) # No error if directories already exist
config = Config(config_dir, configuration, raise_err=False)
config.set_deep('core.hostname', hostname)
config.set_deep('core.tenant', tenant)
config.save()
config.set_active()
ctx.obj['config'] = config
Expand All @@ -153,7 +169,8 @@ def init(ctx, hostname, no_auth, overwrite, **kwargs):
echo(
ctx.forward(
login, hostname=hostname, username=username,
password=password, rsa_token=rsa_token, **kwargs
password=password, rsa_token=rsa_token,
tenant=tenant, **kwargs
), level=LOG_FORCE, ctx=ctx
)
return "Initialization complete."
Expand Down
2 changes: 1 addition & 1 deletion cray/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ class Config(NestedDict):
Use ctx.obj.config instead.
"""

_CORE_KEYS = ['hostname', 'quiet', 'format']
_CORE_KEYS = ['hostname', 'tenant', 'quiet', 'format']

def __init__(self, path, config, raise_err=False):
# pylint: disable=super-init-not-called
Expand Down
25 changes: 14 additions & 11 deletions cray/rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
from cray.errors import InsecureError
from cray.errors import UnauthorizedError
from cray.utils import get_hostname
from cray.utils import get_headers


def make_url(route, url=None, default_scheme='https', ctx=None):
Expand Down Expand Up @@ -90,34 +91,36 @@ def request(method, route, callback=None, **kwargs):

try:
url = make_url(route)
headers = get_headers(ctx=ctx)
echo(f'REQUEST: {method} to {url}', ctx=ctx, level=LOG_DEBUG)
echo(f'OPTIONS: {opts}', ctx=ctx, level=LOG_RAW)
# TODO: Find solution for this.
with warnings.catch_warnings():
warnings.filterwarnings("ignore", category=InsecureRequestWarning)
response = requester.request(method, url, **opts)
response = requester.request(method, url, **opts, headers=headers)
if not response.ok:
_log_request_error(response.text, ctx)
raise BadResponseError(response, ctx=ctx)
except InsecureTransportError as err: # pragma: NO COVER
_log_request_error(err, ctx)
# pylint: disable=raise-missing-from
except InsecureTransportError as err:
# pragma: NO COVER
_log_request_error(err, ctx) # pylint: disable=raise-missing-from
raise InsecureError(ctx=ctx)
except InvalidGrantError as err: # pragma: NO COVER
_log_request_error(err, ctx)
# pylint: disable=raise-missing-from
except InvalidGrantError as err:
# pragma: NO COVER
_log_request_error(err, ctx) # pylint: disable=raise-missing-from
raise UnauthorizedError(ctx=ctx)
except requests.exceptions.HTTPError as err: # pragma: NO COVER
except requests.exceptions.HTTPError as err:
# pragma: NO COVER
_log_request_error(err, ctx)
if err.response.status_code == 401:
# pylint: disable=raise-missing-from
raise UnauthorizedError(ctx=ctx)
raise click.UsageError(str(err))
except requests.exceptions.Timeout as err: # pragma: NO COVER
except requests.exceptions.Timeout as err:
# pragma: NO COVER
_log_request_error(err, ctx)
raise click.UsageError('Timed out trying to connect to cray', ctx=ctx)
except click.ClickException:
# Don't log click specific exceptions
except click.ClickException: # Don't log click specific exceptions
raise
except Exception as err:
_log_request_error(err, ctx)
Expand Down
7 changes: 5 additions & 2 deletions cray/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
from cray.tests.utils import new_username
from cray.tests.utils import new_configname
from cray.tests.utils import new_hostname
from cray.tests.utils import new_tenant
from cray.tests.utils import create_config_file


Expand Down Expand Up @@ -95,11 +96,13 @@ def cli_runner(request):
'configname': 'default',
'username': new_username(),
'hostname': new_hostname(),
'tenant': '',
}
config = {
'configname': new_configname(),
'username': new_username(),
'hostname': new_hostname(),
'tenant': new_tenant(),
}
opts = {
'default': default,
Expand All @@ -120,11 +123,11 @@ def cli_runner(request):
initialize_dirs(os.path.join(os.getcwd(), '.config/cray/'))
create_config_file(
default['configname'], default['hostname'],
default['username']
default['tenant'], default['username']
)
create_config_file(
config['configname'], config['hostname'],
config['username']
config['tenant'], config['username']
)

yield runner, cli.cli, opts
Expand Down
38 changes: 33 additions & 5 deletions cray/tests/test_functional/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@ def test_cray_init_no_hostname(cli_runner):
config = opts['default']
hostname = config['hostname']
configname = config['configname']
tenant = config['tenant']
result = runner.invoke(
cli, ['init', '--no-auth'], input=f'{hostname}\n'
cli, ['init', '--no-auth', '--tenant', tenant], input=f'{hostname}\n'
)
assert result.exit_code == 0
assert "Initialization complete." in result.output
Expand All @@ -54,6 +55,29 @@ def test_cray_init_no_hostname(cli_runner):
assert data['core']['hostname'] == hostname


@pytest.mark.parametrize(
'cli_runner', [{'is_init': True}], indirect=['cli_runner']
)
def test_cray_init_no_tenant(cli_runner):
""" Test `cray init --configuration {config}`
for validating a new configuration is created. """
runner, cli, opts = cli_runner
config = opts['default']
hostname = config['hostname']
configname = config['configname']
tenant = config['tenant']
result = runner.invoke(
cli, ['init', '--no-auth', '--hostname', hostname], input=f'{tenant}\n'
)
assert result.exit_code == 0
assert "Initialization complete." in result.output
filep = f'.config/cray/configurations/{configname}'
assert os.path.isfile(filep)
with open(filep, encoding='utf-8') as f:
data = toml.load(f)
assert data['core']['tenant'] == tenant


@pytest.mark.parametrize(
'cli_runner', [{'is_init': True}], indirect=['cli_runner']
)
Expand All @@ -62,8 +86,9 @@ def test_cray_init(cli_runner):
runner, cli, opts = cli_runner
config = opts['default']
hostname = config['hostname']
tenant = config['tenant']
configname = config['configname']
result = runner.invoke(cli, ['init', '--hostname', hostname, '--no-auth'])
result = runner.invoke(cli, ['init', '--hostname', hostname, '--no-auth', '--tenant', tenant])

assert result.exit_code == 0
assert "Initialization complete." in result.output
Expand All @@ -72,6 +97,7 @@ def test_cray_init(cli_runner):
with open(filep, encoding='utf-8') as f:
data = toml.load(f)
assert data['core']['hostname'] == hostname
assert data['core']['tenant'] == tenant


@pytest.mark.parametrize(
Expand All @@ -82,7 +108,8 @@ def test_cray_init_verify_no_auth(cli_runner, rest_mock):
runner, cli, opts = cli_runner
config = opts['default']
hostname = config['hostname']
result = runner.invoke(cli, ['init', '--hostname', hostname, '--no-auth'])
tenant = config['tenant']
result = runner.invoke(cli, ['init', '--hostname', hostname, '--no-auth', '--tenant', tenant])
assert result.exit_code == 0
result = runner.invoke(cli, ['uas', 'list'])
print(result.output)
Expand All @@ -98,11 +125,12 @@ def test_cray_init_w_config(cli_runner):
runner, cli, opts = cli_runner
config = opts['config']
hostname = config['hostname']
tenant = config['tenant']
configname = config['configname']
result = runner.invoke(
cli,
['init', '--hostname', hostname, '--no-auth', '--configuration',
configname]
['init', '--hostname', hostname, '--tenant', tenant, '--no-auth',
'--configuration', configname]
)

assert result.exit_code == 0
Expand Down
3 changes: 2 additions & 1 deletion cray/tests/test_modules/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,10 +349,11 @@ def test_cray_config_get_shallow(cli_runner):
runner, cli, opts = cli_runner
config = opts['default']
hostname = config['hostname']
tenant = config['tenant']
result = runner.invoke(cli, ['config', 'get', 'core'])
assert result.exit_code == 0
data = json.loads(result.output)
assert data == {'hostname': hostname}
assert data == {'hostname': hostname, 'tenant': tenant}


def test_cray_config_get_missing_param(cli_runner):
Expand Down
7 changes: 4 additions & 3 deletions cray/tests/test_unit/test_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,12 +125,13 @@ def test_generator_danger_tag_default_confirm(cli_runner, rest_mock, pets):
runner, cli, opts = cli_runner
config = opts['default']
hostname = config['hostname']
petId = "1"
result = runner.invoke(cli, ['pets', 'pet', 'delete', petId], input='y')
orderId = "1"
result = runner.invoke(cli, ['pets', 'store', 'order', 'delete', orderId], 'y')
data = json.loads(strip_confirmation(result.output))
print(result.output)
assert result.exit_code == 0
data = json.loads(strip_confirmation(result.output))
assert data['url'] == f'{hostname}/v2/pet/{petId}'
assert data['url'] == f'{hostname}/v2/store/order/{orderId}'
assert data['method'].lower() == 'delete'


Expand Down
8 changes: 6 additions & 2 deletions cray/tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ def new_hostname():
return f"https://{_uuid().replace('-', '')}"


def new_tenant():
return f"vcluster-{_uuid().replace('-', '')}"


def new_configname():
return _uuid().replace('-', '')

Expand All @@ -50,9 +54,9 @@ def new_random_string():
return _uuid().replace('-', '')


def create_config_file(filename, hostname, username):
def create_config_file(filename, hostname, tenant, username):
data = {
'core': {'hostname': hostname},
'core': {'hostname': hostname, 'tenant': tenant},
'auth': {'login': {'username': username}}
}
path = '.config/cray/configurations'
Expand Down
17 changes: 17 additions & 0 deletions cray/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,23 @@ def get_hostname(ctx=None):
return hostname


def get_tenant(ctx=None):
""" Get the tenant requests are scoped to (None if not scoped to a tenant) """
ctx = ctx or click.get_current_context()
config = ctx.obj['config']
tenant = config.get('core.tenant', None)
return tenant

def get_headers(ctx=None):
""" Get the headers which may or may not contain a tenant """
headers={}
ctx = ctx or click.get_current_context()
tenant = get_tenant(ctx=ctx)
if tenant:
headers={"Cray-Tenant-Name":tenant}
return headers


def hostname_to_name(hostname=None, ctx=None):
""" Convert hostname to name value for saving as filename"""
hostname = hostname or get_hostname(ctx=ctx)
Expand Down

0 comments on commit 49d3d90

Please sign in to comment.