diff --git a/looker_deployer/cli.py b/looker_deployer/cli.py index 6c9b8d9..015b5c8 100644 --- a/looker_deployer/cli.py +++ b/looker_deployer/cli.py @@ -107,8 +107,11 @@ def setup_content_subparser(subparsers): export_subparser.add_argument("--env", required=True, help="What environment to deploy to") export_subparser.add_argument("--ini", default=loc, help="ini file to parse for credentials") export_subparser.add_argument("--debug", action="store_true", help="set logger to debug for more verbosity") - export_subparser.add_argument("--folders", nargs="+", required=True, help="What folders to export content from") export_subparser.add_argument("--local-target", required=True, help="Local directory to store content") + export_content_group = export_subparser.add_argument_group() + export_content_group.add_argument("--folders", nargs="+", help="Folders to fully export") + export_content_group.add_argument("--dashboards", nargs="+", help="Dashboards to export") + export_content_group.add_argument("--looks", nargs="+", help="Looks to export") export_subparser.set_defaults(func=deploy_content_export.main) import_subparser.add_argument("--env", required=True, help="What environment to deploy to") @@ -116,10 +119,10 @@ def setup_content_subparser(subparsers): import_subparser.add_argument("--debug", action="store_true", help="set logger to debug for more verbosity") import_subparser.add_argument("--recursive", action="store_true", help="Should folders deploy recursively") import_subparser.add_argument("--target-folder", help="override the default target folder with a custom path") - content_group = import_subparser.add_mutually_exclusive_group(required=True) - content_group.add_argument("--folders", nargs="+", help="Folders to fully deploy") - content_group.add_argument("--dashboards", nargs="+", help="Dashboards to deploy") - content_group.add_argument("--looks", nargs="+", help="Looks to deploy") + import_content_group = import_subparser.add_mutually_exclusive_group(required=True) + import_content_group.add_argument("--folders", nargs="+", help="Folders to fully deploy") + import_content_group.add_argument("--dashboards", nargs="+", help="Dashboards to deploy") + import_content_group.add_argument("--looks", nargs="+", help="Looks to deploy") import_subparser.set_defaults(func=deploy_content.main) diff --git a/looker_deployer/commands/deploy_content_export.py b/looker_deployer/commands/deploy_content_export.py index 1f48176..06377cf 100644 --- a/looker_deployer/commands/deploy_content_export.py +++ b/looker_deployer/commands/deploy_content_export.py @@ -58,6 +58,40 @@ def export_spaces(folder_id, env, ini, path, debug=False): subprocess.run(gzr_command) +def export_content(content_type, content_id, env, ini, path, debug=False): + host, port, client_id, client_secret, verify_ssl = get_gzr_creds(ini, env) + + gzr_command = [ + "gzr", + content_type, + "cat", + content_id, + "--host", + host, + "--port", + port, + "--client-id", + client_id, + "--client-secret", + client_secret + ] + + # config parser returns a string - easier to parse that than convert to a bool + if verify_ssl == "False": + gzr_command.append("--no-verify-ssl") + if debug: + gzr_command.append("--debug") + + # if we're running on windows we need to appropriately call the command-line arg" + if os.name == "nt": + win_exec = ["cmd.exe", "/c"] + gzr_command = win_exec + gzr_command + + filename = Path(path) / f"{content_type}_{content_id}.json" + with open(filename, "w") as outfile: + subprocess.run(gzr_command, stdout=outfile) + + def recurse_folders(folder_id, folder_list, sdk, debug=False): space = sdk.space(str(folder_id)) folder_list.append(space.name) @@ -72,8 +106,10 @@ def recurse_folders(folder_id, folder_list, sdk, debug=False): return folder_list -def send_export(folder_ids, local_target, env, ini, sdk, debug=False): - for fid in folder_ids: +def send_export( + sdk, env, ini, local_target, folders=None, dashboards=None, looks=None, debug=False +): + for fid in folders or []: # generate the list of folders folder_list = [] @@ -90,6 +126,28 @@ def send_export(folder_ids, local_target, env, ini, sdk, debug=False): # export the folder export_spaces(fid, env, ini, str(path), debug) + for did in dashboards or []: + + logger.debug("dashboard_list", extra={"dashboards": dashboards}) + + # create the target directory + path = Path(local_target) + path.mkdir(parents=True, exist_ok=True) + + # export the dashboard + export_content("dashboard", did, env, ini, str(path), debug) + + for lid in looks or []: + + logger.debug("look_list", extra={"looks": looks}) + + # create the target directory + path = Path(local_target) + path.mkdir(parents=True, exist_ok=True) + + # export the look + export_content("look", lid, env, ini, str(path), debug) + def main(args): @@ -100,7 +158,16 @@ def main(args): logger.info( "Exporting content", - extra={"env": args.env, "folders": args.folders, "local_target": args.local_target} + extra={"env": args.env, "folders": args.folders, "dashboards": args.dashboards, "looks": args.looks, "local_target": args.local_target} ) sdk = get_client(args.ini, args.env) - send_export(args.folders, args.local_target, args.env, args.ini, sdk, args.debug) + send_export( + sdk, + args.env, + args.ini, + args.local_target, + args.folders, + args.dashboards, + args.looks, + args.debug + ) diff --git a/setup.py b/setup.py index 05dba40..0038013 100644 --- a/setup.py +++ b/setup.py @@ -31,6 +31,6 @@ name=NAME, packages=["looker_deployer", "looker_deployer/commands", "looker_deployer/utils"], entry_points={"console_scripts": ["ldeploy=looker_deployer.cli:main"]}, - python_requires=">=3.6.0, <3.10", + python_requires=">=3.6.0, <3.11", version=VERSION ) diff --git a/tests/test_deploy_content_export.py b/tests/test_deploy_content_export.py index 9045533..4270d73 100644 --- a/tests/test_deploy_content_export.py +++ b/tests/test_deploy_content_export.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from unittest.mock import patch, mock_open +from pathlib import Path import subprocess from looker_sdk import methods from looker_deployer.commands import deploy_content_export @@ -150,5 +152,31 @@ def test_send_export(mocker): mocker.patch("pathlib.Path.mkdir") mocker.patch("looker_deployer.commands.deploy_content_export.export_spaces") - deploy_content_export.send_export("1", "./foo/bar", "env", "ini", "sdk", False) + deploy_content_export.send_export("sdk", "env", "ini", "./foo/bar", folders="1", debug=False) deploy_content_export.export_spaces.assert_called_with("1", "env", "ini", "foo/bar/Shared/bosh", False) + + +def test_export_dashboard(mocker): + mocker.patch("looker_deployer.commands.deploy_content_export.get_gzr_creds") + deploy_content_export.get_gzr_creds.return_value = ("foobar.com", "1234", "abc", "xyz", "True") + + fake_file_path = Path("foo/bar/dashboard_1.json") + with patch('looker_deployer.commands.deploy_content_export.open', mock_open()) as mocked_file: + mocker.patch("subprocess.run") + deploy_content_export.export_content("dashboard", "1", "env", "ini", "foo/bar", False) + + # assert "foo/bar/dashboard_1.json" opened and on write mode 'w' + mocked_file.assert_called_once_with(fake_file_path, 'w') + + +def test_export_look(mocker): + mocker.patch("looker_deployer.commands.deploy_content_export.get_gzr_creds") + deploy_content_export.get_gzr_creds.return_value = ("foobar.com", "1234", "abc", "xyz", "True") + + fake_file_path = Path("foo/bar/look_1.json") + with patch('looker_deployer.commands.deploy_content_export.open', mock_open()) as mocked_file: + mocker.patch("subprocess.run") + deploy_content_export.export_content("look", "1", "env", "ini", "foo/bar", False) + + # assert "foo/bar/look_1.json" opened and on write mode 'w' + mocked_file.assert_called_once_with(fake_file_path, 'w')