| # |
| # Licensed to the Apache Software Foundation (ASF) under one |
| # or more contributor license agreements. See the NOTICE file |
| # distributed with this work for additional information |
| # regarding copyright ownership. The ASF licenses this file |
| # to you under the Apache License, Version 2.0 (the |
| # "License"); you may not use this file except in compliance |
| # with the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, |
| # software distributed under the License is distributed on an |
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| # KIND, either express or implied. See the License for the |
| # specific language governing permissions and limitations |
| # under the License. |
| # |
| import json |
| import os |
| import random |
| import requests |
| import string |
| import subprocess |
| import sys |
| from typing import Callable |
| |
| CLI_PYTHONPATH = f'{os.path.dirname(os.path.abspath(__file__))}/../../../client/python' |
| ROLE_ARN = 'arn:aws:iam::123456789012:role/my-role' |
| POLARIS_HOST = os.getenv('POLARIS_HOST', 'localhost') |
| POLARIS_URL = f'http://{POLARIS_HOST}:8181/api/catalog/v1/oauth/tokens' |
| |
| def get_salt(length=8) -> str: |
| characters = string.ascii_letters + string.digits |
| return ''.join(random.choice(characters) for _ in range(length)) |
| |
| |
| def root_cli(*args): |
| return cli(os.getenv('REGTEST_ROOT_BEARER_TOKEN'))(*args) |
| |
| |
| def cli(access_token): |
| def cli_inner(*args) -> Callable[[], str]: |
| def f() -> str: |
| result = subprocess.run([ |
| 'bash', |
| f'{CLI_PYTHONPATH}/../../polaris', |
| '--access-token', |
| access_token, |
| '--host', |
| POLARIS_HOST, |
| *args], |
| capture_output=True, |
| text=True |
| ) |
| print(result) |
| if result.returncode != 0: |
| raise Exception(result.stderr) |
| return result.stdout |
| return f |
| return cli_inner |
| |
| |
| def check_output(f, checker: Callable[[str], bool]): |
| assert checker(f()) |
| |
| |
| def check_exception(f, exception_str): |
| throws = True |
| try: |
| f() |
| throws = False |
| except Exception as e: |
| assert exception_str in str(e) |
| assert throws |
| |
| |
| def get_token(client_id, client_secret): |
| data = { |
| 'grant_type': 'client_credentials', |
| 'client_id': client_id, |
| 'client_secret': client_secret, |
| 'scope': 'PRINCIPAL_ROLE:ALL' |
| } |
| |
| response = requests.post(POLARIS_URL, data=data) |
| |
| if response.status_code != 200 or 'access_token' not in response.json(): |
| raise Exception("Failed to retrieve token") |
| |
| return response.json()['access_token'] |
| |
| |
| def test_quickstart_flow(): |
| """ |
| Basic CLI test - create a catalog, create a principal, and grant the principal access to the catalog. |
| """ |
| |
| SALT = get_salt() |
| sys.path.insert(0, CLI_PYTHONPATH) |
| try: |
| |
| # Create a catalog: |
| check_output(root_cli( |
| 'catalogs', |
| 'create', |
| '--storage-type', |
| 's3', |
| '--role-arn', |
| ROLE_ARN, |
| '--default-base-location', |
| f's3://fake-location-{SALT}', |
| '--property', |
| 'polaris.config.namespace-custom-location.enabled=true', |
| f'test_cli_catalog_{SALT}'), checker=lambda s: s == '') |
| check_output(root_cli('catalogs', 'list'), |
| checker=lambda s: f'test_cli_catalog_{SALT}' in s) |
| check_output(root_cli('catalogs', 'get', f'test_cli_catalog_{SALT}'), |
| checker=lambda s: 's3://fake-location' in s) |
| |
| # Create a new user: |
| credentials = root_cli('principals', 'create', f'test_cli_user_{SALT}')() |
| check_output(root_cli('principals', 'list'), checker=lambda s: f'test_cli_user_{SALT}' in s) |
| credentials = json.loads(credentials) |
| assert 'clientId' in credentials |
| assert 'clientSecret' in credentials |
| user_token = get_token(credentials['clientId'], credentials['clientSecret']) |
| |
| # User initially has no catalog access: |
| check_exception(cli(user_token)('catalogs', 'get', f'test_cli_catalog_{SALT}'), |
| exception_str='not authorized') |
| |
| # Grant user access: |
| check_output( |
| root_cli('principal-roles', 'create', f'test_cli_p_role_{SALT}'), |
| checker=lambda s: s == '') |
| check_output(root_cli( |
| 'catalog-roles', 'create', '--catalog', f'test_cli_catalog_{SALT}', f'test_cli_c_role_{SALT}'), |
| checker=lambda s: s == '') |
| check_output(root_cli( |
| 'principal-roles', 'grant', '--principal', f'test_cli_user_{SALT}', f'test_cli_p_role_{SALT}'), |
| checker=lambda s: s == '') |
| check_output(root_cli( |
| 'catalog-roles', |
| 'grant', |
| '--catalog', |
| f'test_cli_catalog_{SALT}', |
| '--principal-role', |
| f'test_cli_p_role_{SALT}', |
| f'test_cli_c_role_{SALT}' |
| ), checker=lambda s: s == '') |
| check_output(root_cli( |
| 'privileges', |
| 'catalog', |
| 'grant', |
| '--catalog', |
| f'test_cli_catalog_{SALT}', |
| '--catalog-role', |
| f'test_cli_c_role_{SALT}', |
| 'CATALOG_MANAGE_CONTENT' |
| ), checker=lambda s: s == '') |
| |
| # User now has catalog access: |
| check_output(cli(user_token)('catalogs', 'get', f'test_cli_catalog_{SALT}'), |
| checker=lambda s: 's3://fake-location' in s) |
| check_output(cli(user_token)( |
| 'namespaces', |
| 'create', |
| '--catalog', |
| f'test_cli_catalog_{SALT}', |
| f'test_cli_namespace_{SALT}', |
| '--property', |
| 'foo=bar', |
| '--location', |
| f's3://fake-location-{SALT}/custom-namespace-location/' |
| ), checker=lambda s: s == '') |
| check_output(cli(user_token)('namespaces', 'list', '--catalog', f'test_cli_catalog_{SALT}'), |
| checker=lambda s: f'test_cli_namespace_{SALT}' in s) |
| check_output(cli(user_token)( |
| 'namespaces', |
| 'get', |
| '--catalog', |
| f'test_cli_catalog_{SALT}', |
| f'test_cli_namespace_{SALT}' |
| ), checker=lambda s: f's3://fake-location-{SALT}/custom-namespace-location/' in s and '"foo": "bar"' in s) |
| check_output(cli(user_token)( |
| 'namespaces', |
| 'delete', |
| '--catalog', |
| f'test_cli_catalog_{SALT}', |
| f'test_cli_namespace_{SALT}' |
| ), checker=lambda s: s == '') |
| check_output(cli(user_token)('namespaces', 'list', '--catalog', f'test_cli_catalog_{SALT}'), |
| checker=lambda s: f'test_cli_namespace_{SALT}' not in s) |
| |
| finally: |
| sys.path.pop(0) |
| pass |
| |
| |
| def test_catalog_storage_config(): |
| """ |
| Tests specific to storage configs of catalogs across different cloud object stores |
| """ |
| |
| SALT = get_salt() |
| sys.path.insert(0, CLI_PYTHONPATH) |
| try: |
| # Create S3 catalog |
| check_output(root_cli( |
| 'catalogs', |
| 'create', |
| '--storage-type', |
| 's3', |
| '--role-arn', |
| ROLE_ARN, |
| '--default-base-location', |
| f's3://fake-location-{SALT}', |
| '--external-id', |
| 'custom-external-id-123', |
| f'test_cli_catalog_aws_{SALT}'), checker=lambda s: s == '') |
| check_output(root_cli('catalogs', 'get', f'test_cli_catalog_aws_{SALT}'), |
| checker=lambda s: 's3://fake-location' in s and |
| 'custom-external-id-123' in s) |
| |
| # Create Azure catalog |
| check_output(root_cli( |
| 'catalogs', |
| 'create', |
| '--storage-type', |
| 'azure', |
| '--default-base-location', |
| f'abfss://fake-container-{SALT}@acct1.dfs.core.windows.net', |
| '--tenant-id', |
| 'tenant123.onmicrosoft.com', |
| '--multi-tenant-app-name', |
| 'mtapp123', |
| '--consent-url', |
| 'https://fake-consent-url', |
| f'test_cli_catalog_azure_{SALT}'), checker=lambda s: s == '') |
| check_output(root_cli('catalogs', 'get', f'test_cli_catalog_azure_{SALT}'), |
| checker=lambda s: 'abfss://fake-container' in s and |
| 'tenant123.onmicrosoft.com' in s and |
| 'mtapp123' in s and |
| 'https://fake-consent-url' in s) |
| |
| # Create GCS catalog |
| # TODO: Once catalog capabilities are formalized in an API, condition this |
| # GCP scenario on whether the catalog under test has GCP root credentials available. |
| # In the meantime, since the credentials aren't actually used, just initialized, |
| # the following lines can be uncommented if running locally while using |
| # DEVSHELL_CLIENT_PORT to create a nonfunctional fake GCP credential supplier: |
| # |
| # DEVSHELL_CLIENT_PORT=0 ./gradlew runApp |
| # check_output(root_cli( |
| # 'catalogs', |
| # 'create', |
| # '--storage-type', |
| # 'gcs', |
| # '--default-base-location', |
| # f'gs://fake-location-{SALT}', |
| # '--service-account', |
| # 'service-acct-123', |
| # f'test_cli_catalog_gcp_{SALT}'), checker=lambda s: s == '') |
| # check_output(root_cli('catalogs', 'get', f'test_cli_catalog_gcp_{SALT}'), |
| # checker=lambda s: 'gs://fake-location' in s and |
| # 'service-acct-123' in s) |
| |
| finally: |
| sys.path.pop(0) |
| pass |
| |
| |
| def test_update_catalog(): |
| """ |
| Test updating properties on a catalog |
| """ |
| SALT = get_salt() |
| sys.path.insert(0, CLI_PYTHONPATH) |
| try: |
| |
| # Create a catalog: |
| check_output(root_cli( |
| 'catalogs', |
| 'create', |
| '--storage-type', |
| 's3', |
| '--role-arn', |
| ROLE_ARN, |
| '--default-base-location', |
| f's3://fake-location-{SALT}', |
| f'test_cli_catalog_{SALT}'), checker=lambda s: s == '') |
| check_output(root_cli('catalogs', 'get', f'test_cli_catalog_{SALT}'), |
| checker=lambda s: 's3://fake-location' in s) |
| |
| # Update by adding a property |
| check_output(root_cli( |
| 'catalogs', |
| 'update', |
| f'test_cli_catalog_{SALT}', |
| '--set-property', |
| 'foo=bar' |
| ), checker=lambda s: s == '') |
| |
| # Make sure the base location didn't get clobbered, and the new property is present. |
| check_output(root_cli('catalogs', 'get', f'test_cli_catalog_{SALT}'), |
| checker=lambda s: 's3://fake-location' in s and '"foo": "bar"' in s) |
| |
| # Update by changing default-base-location |
| check_output(root_cli( |
| 'catalogs', |
| 'update', |
| f'test_cli_catalog_{SALT}', |
| '--default-base-location', |
| f's3://new-location-{SALT}' |
| ), checker=lambda s: s == '') |
| |
| # Check for new location, and make sure the custom property is still there. |
| check_output(root_cli('catalogs', 'get', f'test_cli_catalog_{SALT}'), |
| checker=lambda s: 's3://new-location' in s and '"foo": "bar"' in s) |
| |
| # Update by adding a second property |
| check_output(root_cli( |
| 'catalogs', |
| 'update', |
| f'test_cli_catalog_{SALT}', |
| '--set-property', |
| 'prop2=222' |
| ), checker=lambda s: s == '') |
| |
| # Make sure both custom properties are present even though we only added the second one |
| check_output(root_cli('catalogs', 'get', f'test_cli_catalog_{SALT}'), |
| checker=lambda s: '"prop2": "222"' in s and '"foo": "bar"' in s) |
| |
| # Update by modifying a property and adding a property at the same time |
| check_output(root_cli( |
| 'catalogs', |
| 'update', |
| f'test_cli_catalog_{SALT}', |
| '--set-property', |
| 'prop2=two', |
| '--set-property', |
| 'prop3=333' |
| ), checker=lambda s: s == '') |
| |
| # Check all three custom properties |
| check_output(root_cli('catalogs', 'get', f'test_cli_catalog_{SALT}'), |
| checker=lambda s: '"prop2": "two"' in s and '"foo": "bar"' in s and '"prop3": "333"' in s) |
| |
| # Update one property that will be removed in the same command, one that will not be removed, |
| # add one new property, remove one of the properties that is not updated, and remove a |
| # nonexistent property |
| check_output(root_cli( |
| 'catalogs', |
| 'update', |
| f'test_cli_catalog_{SALT}', |
| '--set-property', |
| 'prop2=2222', |
| '--set-property', |
| 'prop3=3333', |
| '--set-property', |
| 'prop4=4444', |
| '--remove-property', |
| 'foo', |
| '--remove-property', |
| 'prop2', |
| '--remove-property', |
| 'nonexistent' |
| ), checker=lambda s: s == '') |
| |
| # One updated property and one new property remain after removals |
| check_output(root_cli('catalogs', 'get', f'test_cli_catalog_{SALT}'), |
| checker=lambda s: 'foo' not in s and 'prop2' not in s and |
| '"prop3": "3333"' in s and '"prop4": "4444"' in s) |
| |
| # Update to add a property whose value is a key-value list |
| check_output(root_cli( |
| 'catalogs', |
| 'update', |
| f'test_cli_catalog_{SALT}', |
| '--set-property', |
| 'listprop=k1=v1,k2=v2' |
| ), checker=lambda s: s == '') |
| |
| # Previous properties still exist, and the new property is parsed properly |
| check_output(root_cli('catalogs', 'get', f'test_cli_catalog_{SALT}'), |
| checker=lambda s: '"prop3": "3333"' in s and '"prop4": "4444"' in s and |
| '"listprop": "k1=v1,k2=v2"' in s) |
| |
| # Update to set a region |
| check_output(root_cli( |
| 'catalogs', |
| 'update', |
| f'test_cli_catalog_{SALT}', |
| '--region', |
| 'new-test-region' |
| ), checker=lambda s: s == '') |
| |
| # Original fake-location should still be present in storage_config_info.allowed_locations |
| check_output(root_cli('catalogs', 'get', f'test_cli_catalog_{SALT}'), |
| checker=lambda s: 's3://fake-location-' in s and '"region": "new-test-region"' in s) |
| |
| # Update to add an allowed location |
| check_output(root_cli( |
| 'catalogs', |
| 'update', |
| f'test_cli_catalog_{SALT}', |
| '--allowed-location', |
| f's3://extra-allowed-location-{SALT}' |
| ), checker=lambda s: s == '') |
| |
| # All allowed locations present and region still present |
| check_output(root_cli('catalogs', 'get', f'test_cli_catalog_{SALT}'), |
| checker=lambda s: 's3://fake-location-' in s and |
| 's3://extra-allowed-location-' in s and |
| '"region": "new-test-region"' in s) |
| |
| # Add allowed location and change region at same time |
| check_output(root_cli( |
| 'catalogs', |
| 'update', |
| f'test_cli_catalog_{SALT}', |
| '--allowed-location', |
| f's3://fourth-allowed-location-{SALT}', |
| '--region', |
| 'us-east-2' |
| ), checker=lambda s: s == '') |
| |
| # All allowed locations present and new region set |
| check_output(root_cli('catalogs', 'get', f'test_cli_catalog_{SALT}'), |
| checker=lambda s: 's3://fake-location-' in s and |
| 's3://extra-allowed-location-' in s and |
| 's3://fourth-allowed-location-' in s and |
| '"region": "us-east-2"' in s) |
| |
| finally: |
| sys.path.pop(0) |
| pass |
| |
| def test_update_principal(): |
| """ |
| Test updating properties on a principal |
| """ |
| SALT = get_salt() |
| sys.path.insert(0, CLI_PYTHONPATH) |
| try: |
| |
| # Create a principal: |
| credentials = root_cli('principals', 'create', f'test_cli_user_{SALT}', '--property', 'foo=bar')() |
| check_output(root_cli('principals', 'get', f'test_cli_user_{SALT}'), |
| checker=lambda s: '"foo": "bar"' in s) |
| |
| # Update by adding a second property |
| check_output(root_cli( |
| 'principals', |
| 'update', |
| f'test_cli_user_{SALT}', |
| '--set-property', |
| 'prop2=222' |
| ), checker=lambda s: s == '') |
| |
| # Make sure both custom properties are present even though we only added the second one |
| check_output(root_cli('principals', 'get', f'test_cli_user_{SALT}'), |
| checker=lambda s: '"prop2": "222"' in s and '"foo": "bar"' in s) |
| |
| # Update by modifying a property and adding a property at the same time |
| check_output(root_cli( |
| 'principals', |
| 'update', |
| f'test_cli_user_{SALT}', |
| '--set-property', |
| 'prop2=two', |
| '--set-property', |
| 'prop3=333' |
| ), checker=lambda s: s == '') |
| |
| # Check all three custom properties |
| check_output(root_cli('principals', 'get', f'test_cli_user_{SALT}'), |
| checker=lambda s: '"prop2": "two"' in s and '"foo": "bar"' in s and '"prop3": "333"' in s) |
| |
| # Update one property that will be removed in the same command, one that will not be removed, |
| # add one new property, remove one of the properties that is not updated, and remove a |
| # nonexistent property |
| check_output(root_cli( |
| 'principals', |
| 'update', |
| f'test_cli_user_{SALT}', |
| '--set-property', |
| 'prop2=2222', |
| '--set-property', |
| 'prop3=3333', |
| '--set-property', |
| 'prop4=4444', |
| '--remove-property', |
| 'foo', |
| '--remove-property', |
| 'prop2', |
| '--remove-property', |
| 'nonexistent' |
| ), checker=lambda s: s == '') |
| |
| # One updated property and one new property remain after removals |
| check_output(root_cli('principals', 'get', f'test_cli_user_{SALT}'), |
| checker=lambda s: 'foo' not in s and 'prop2' not in s and |
| '"prop3": "3333"' in s and '"prop4": "4444"' in s) |
| |
| finally: |
| sys.path.pop(0) |
| pass |
| |
| def test_update_principal_role(): |
| """ |
| Test updating properties on a principal_role |
| """ |
| SALT = get_salt() |
| sys.path.insert(0, CLI_PYTHONPATH) |
| try: |
| |
| # Create a principal_role: |
| credentials = root_cli('principal-roles', 'create', f'test_cli_p_role_{SALT}', '--property', 'foo=bar')() |
| check_output(root_cli('principal-roles', 'get', f'test_cli_p_role_{SALT}'), |
| checker=lambda s: '"foo": "bar"' in s) |
| |
| # Update by adding a second property |
| check_output(root_cli( |
| 'principal-roles', |
| 'update', |
| f'test_cli_p_role_{SALT}', |
| '--set-property', |
| 'prop2=222' |
| ), checker=lambda s: s == '') |
| |
| # Make sure both custom properties are present even though we only added the second one |
| check_output(root_cli('principal-roles', 'get', f'test_cli_p_role_{SALT}'), |
| checker=lambda s: '"prop2": "222"' in s and '"foo": "bar"' in s) |
| |
| # Update by modifying a property and adding a property at the same time |
| check_output(root_cli( |
| 'principal-roles', |
| 'update', |
| f'test_cli_p_role_{SALT}', |
| '--set-property', |
| 'prop2=two', |
| '--set-property', |
| 'prop3=333' |
| ), checker=lambda s: s == '') |
| |
| # Check all three custom properties |
| check_output(root_cli('principal-roles', 'get', f'test_cli_p_role_{SALT}'), |
| checker=lambda s: '"prop2": "two"' in s and '"foo": "bar"' in s and '"prop3": "333"' in s) |
| |
| # Update one property that will be removed in the same command, one that will not be removed, |
| # add one new property, remove one of the properties that is not updated, and remove a |
| # nonexistent property |
| check_output(root_cli( |
| 'principal-roles', |
| 'update', |
| f'test_cli_p_role_{SALT}', |
| '--set-property', |
| 'prop2=2222', |
| '--set-property', |
| 'prop3=3333', |
| '--set-property', |
| 'prop4=4444', |
| '--remove-property', |
| 'foo', |
| '--remove-property', |
| 'prop2', |
| '--remove-property', |
| 'nonexistent' |
| ), checker=lambda s: s == '') |
| |
| # One updated property and one new property remain after removals |
| check_output(root_cli('principal-roles', 'get', f'test_cli_p_role_{SALT}'), |
| checker=lambda s: 'foo' not in s and 'prop2' not in s and |
| '"prop3": "3333"' in s and '"prop4": "4444"' in s) |
| |
| finally: |
| sys.path.pop(0) |
| pass |
| |
| def test_update_catalog_role(): |
| """ |
| Test updating properties on a catalog_role |
| """ |
| SALT = get_salt() |
| sys.path.insert(0, CLI_PYTHONPATH) |
| try: |
| |
| # Create a catalog: |
| check_output(root_cli( |
| 'catalogs', |
| 'create', |
| '--storage-type', |
| 's3', |
| '--role-arn', |
| ROLE_ARN, |
| '--default-base-location', |
| f's3://fake-location-{SALT}', |
| f'test_cli_catalog_{SALT}'), checker=lambda s: s == '') |
| |
| # Create a catalog_role: |
| credentials = root_cli( |
| 'catalog-roles', |
| 'create', |
| f'test_cli_c_role_{SALT}', |
| '--catalog', |
| f'test_cli_catalog_{SALT}', |
| '--property', |
| 'foo=bar')() |
| check_output(root_cli( |
| 'catalog-roles', |
| 'get', |
| f'test_cli_c_role_{SALT}', |
| '--catalog', |
| f'test_cli_catalog_{SALT}', |
| ), checker=lambda s: '"foo": "bar"' in s) |
| |
| # Update by adding a second property |
| check_output(root_cli( |
| 'catalog-roles', |
| 'update', |
| f'test_cli_c_role_{SALT}', |
| '--catalog', |
| f'test_cli_catalog_{SALT}', |
| '--set-property', |
| 'prop2=222' |
| ), checker=lambda s: s == '') |
| |
| # Make sure both custom properties are present even though we only added the second one |
| check_output(root_cli( |
| 'catalog-roles', |
| 'get', |
| f'test_cli_c_role_{SALT}', |
| '--catalog', |
| f'test_cli_catalog_{SALT}', |
| ), checker=lambda s: '"prop2": "222"' in s and '"foo": "bar"' in s) |
| |
| # Update by modifying a property and adding a property at the same time |
| check_output(root_cli( |
| 'catalog-roles', |
| 'update', |
| f'test_cli_c_role_{SALT}', |
| '--catalog', |
| f'test_cli_catalog_{SALT}', |
| '--set-property', |
| 'prop2=two', |
| '--set-property', |
| 'prop3=333' |
| ), checker=lambda s: s == '') |
| |
| # Check all three custom properties |
| check_output(root_cli( |
| 'catalog-roles', |
| 'get', |
| f'test_cli_c_role_{SALT}', |
| '--catalog', |
| f'test_cli_catalog_{SALT}', |
| ), checker=lambda s: '"prop2": "two"' in s and '"foo": "bar"' in s and '"prop3": "333"' in s) |
| |
| # Update one property that will be removed in the same command, one that will not be removed, |
| # add one new property, remove one of the properties that is not updated, and remove a |
| # nonexistent property |
| check_output(root_cli( |
| 'catalog-roles', |
| 'update', |
| f'test_cli_c_role_{SALT}', |
| '--catalog', |
| f'test_cli_catalog_{SALT}', |
| '--set-property', |
| 'prop2=2222', |
| '--set-property', |
| 'prop3=3333', |
| '--set-property', |
| 'prop4=4444', |
| '--remove-property', |
| 'foo', |
| '--remove-property', |
| 'prop2', |
| '--remove-property', |
| 'nonexistent' |
| ), checker=lambda s: s == '') |
| |
| # One updated property and one new property remain after removals |
| check_output(root_cli( |
| 'catalog-roles', |
| 'get', |
| f'test_cli_c_role_{SALT}', |
| '--catalog', |
| f'test_cli_catalog_{SALT}', |
| ), checker=lambda s: 'foo' not in s and 'prop2' not in s and |
| '"prop3": "3333"' in s and '"prop4": "4444"' in s) |
| |
| finally: |
| sys.path.pop(0) |
| pass |
| |
| def test_nested_namespace(): |
| """ |
| Test creating and managing deeply nested namespaces. |
| """ |
| |
| SALT = get_salt() |
| sys.path.insert(0, CLI_PYTHONPATH) |
| try: |
| |
| # Create a catalog: |
| check_output(root_cli( |
| 'catalogs', |
| 'create', |
| '--storage-type', |
| 's3', |
| '--role-arn', |
| ROLE_ARN, |
| '--default-base-location', |
| f's3://fake-location-{SALT}', |
| f'test_cli_catalog_{SALT}'), checker=lambda s: s == '') |
| check_output(root_cli('catalogs', 'list'), |
| checker=lambda s: f'test_cli_catalog_{SALT}' in s) |
| check_output(root_cli('catalogs', 'get', f'test_cli_catalog_{SALT}'), |
| checker=lambda s: 's3://fake-location' in s) |
| |
| # Create some namespaces: |
| check_output(root_cli( |
| 'namespaces', |
| 'create', |
| '--catalog', |
| f'test_cli_catalog_{SALT}', |
| f'a_{SALT}' |
| ), checker=lambda s: s == '') |
| check_output(root_cli( |
| 'namespaces', |
| 'create', |
| '--catalog', |
| f'test_cli_catalog_{SALT}', |
| f'a_{SALT}.b_{SALT}' |
| ), checker=lambda s: s == '') |
| check_output(root_cli( |
| 'namespaces', |
| 'create', |
| '--catalog', |
| f'test_cli_catalog_{SALT}', |
| f'a_{SALT}.b_{SALT}.c_{SALT}' |
| ), checker=lambda s: s == '') |
| |
| # List namespaces: |
| check_output(root_cli('namespaces', 'list', '--catalog', f'test_cli_catalog_{SALT}'), |
| checker=lambda s: f'a_{SALT}' in s and f'b_{SALT}' not in s) |
| check_output(root_cli( |
| 'namespaces', |
| 'list', |
| '--catalog', |
| f'test_cli_catalog_{SALT}', |
| '--parent', |
| f'a_{SALT}' |
| ), checker=lambda s: f'a_{SALT}.b_{SALT}' in s) |
| |
| # a.b.c exists, and a.b can't be deleted while non-empty |
| check_output(root_cli( |
| 'namespaces', |
| 'get', |
| '--catalog', |
| f'test_cli_catalog_{SALT}', |
| f'a_{SALT}.b_{SALT}.c_{SALT}' |
| ), checker=lambda s: f'"a_{SALT}", "b_{SALT}", "c_{SALT}"' in s) |
| check_exception(root_cli( |
| 'namespaces', |
| 'delete', |
| '--catalog', |
| f'test_cli_catalog_{SALT}', |
| f'a_{SALT}.b_{SALT}' |
| ), exception_str='not empty') |
| finally: |
| sys.path.pop(0) |
| pass |
| |
| |
| def test_list_privileges(): |
| """ |
| Test creating and managing deeply nested namespaces. |
| """ |
| |
| SALT = get_salt() |
| sys.path.insert(0, CLI_PYTHONPATH) |
| try: |
| |
| # Create a catalog, namespace, and principal: |
| check_output(root_cli( |
| 'catalogs', |
| 'create', |
| '--storage-type', |
| 's3', |
| '--role-arn', |
| ROLE_ARN, |
| '--default-base-location', |
| f's3://fake-location-{SALT}', |
| f'test_cli_catalog_{SALT}'), checker=lambda s: s == '') |
| check_output(root_cli( |
| 'namespaces', |
| 'create', |
| '--catalog', |
| f'test_cli_catalog_{SALT}', |
| f'a_{SALT}' |
| ), checker=lambda s: s == '') |
| check_output(root_cli( |
| 'principals', |
| 'create', |
| f'test_cli_user_{SALT}' |
| ), checker=lambda s: s != '') |
| |
| # Grant the principal some privileges: |
| check_output( |
| root_cli('principal-roles', 'create', f'test_cli_p_role_{SALT}'), |
| checker=lambda s: s == '') |
| check_output(root_cli( |
| 'catalog-roles', 'create', '--catalog', f'test_cli_catalog_{SALT}', f'test_cli_c_role_{SALT}'), |
| checker=lambda s: s == '') |
| check_output(root_cli( |
| 'principal-roles', 'grant', '--principal', f'test_cli_user_{SALT}', f'test_cli_p_role_{SALT}'), |
| checker=lambda s: s == '') |
| check_output(root_cli( |
| 'catalog-roles', |
| 'grant', |
| '--catalog', |
| f'test_cli_catalog_{SALT}', |
| '--principal-role', |
| f'test_cli_p_role_{SALT}', |
| f'test_cli_c_role_{SALT}' |
| ), checker=lambda s: s == '') |
| check_output(root_cli( |
| 'privileges', |
| 'catalog', |
| 'grant', |
| '--catalog', |
| f'test_cli_catalog_{SALT}', |
| '--catalog-role', |
| f'test_cli_c_role_{SALT}', |
| 'TABLE_READ_DATA' |
| ), checker=lambda s: s == '') |
| check_output(root_cli( |
| 'privileges', |
| 'namespace', |
| 'grant', |
| '--catalog', |
| f'test_cli_catalog_{SALT}', |
| '--catalog-role', |
| f'test_cli_c_role_{SALT}', |
| '--namespace', |
| f'a_{SALT}', |
| 'TABLE_WRITE_DATA' |
| ), checker=lambda s: s == '') |
| check_output(root_cli( |
| 'privileges', |
| 'namespace', |
| 'grant', |
| '--catalog', |
| f'test_cli_catalog_{SALT}', |
| '--catalog-role', |
| f'test_cli_c_role_{SALT}', |
| '--namespace', |
| f'a_{SALT}', |
| 'TABLE_LIST' |
| ), checker=lambda s: s == '') |
| |
| # List privileges: |
| check_output(root_cli( |
| 'privileges', |
| 'list', |
| '--catalog', |
| f'test_cli_catalog_{SALT}', |
| '--catalog-role', |
| f'test_cli_c_role_{SALT}' |
| ), checker=lambda s: len(s.strip().split('\n')) == 3) |
| |
| finally: |
| sys.path.pop(0) |
| pass |
| |
| |
| def test_invalid_commands(): |
| sys.path.insert(0, CLI_PYTHONPATH) |
| try: |
| check_exception(root_cli('catalogs', 'create', 'test_catalog'), exception_str='--storage-type') |
| check_exception(root_cli( |
| 'catalogs', |
| 'create', |
| 'test_catalog', |
| '--storage-type', |
| 'not-real!' |
| ), exception_str='--storage-type') |
| finally: |
| sys.path.pop(0) |