From 88ae9f24b971964b0cbf90ce3055d4ec56cf73b3 Mon Sep 17 00:00:00 2001 From: Patryk Czajka Date: Thu, 7 Mar 2024 14:24:14 +0100 Subject: [PATCH 01/13] initial version of setup command --- src/snowflake/cli/plugins/git/commands.py | 70 ++++++++++++++++++++++- src/snowflake/cli/plugins/git/manager.py | 35 ++++++++++++ 2 files changed, 104 insertions(+), 1 deletion(-) diff --git a/src/snowflake/cli/plugins/git/commands.py b/src/snowflake/cli/plugins/git/commands.py index 13be906546..ce7ca18365 100644 --- a/src/snowflake/cli/plugins/git/commands.py +++ b/src/snowflake/cli/plugins/git/commands.py @@ -1,10 +1,12 @@ import logging from pathlib import Path +from typing import Optional import typer from click import ClickException from snowflake.cli.api.commands.flags import identifier_argument from snowflake.cli.api.commands.snow_typer import SnowTyper +from snowflake.cli.api.console.console import cli_console from snowflake.cli.api.output.types import CommandResult, QueryResult from snowflake.cli.api.utils.path_utils import is_stage_path from snowflake.cli.plugins.git.manager import GitManager @@ -38,6 +40,72 @@ def _repo_path_argument_callback(path): ) +@app.command("setup", requires_connection=True) +def setup( + repository_name: str = RepoNameArgument, + **options, +) -> CommandResult: + """ + Sets up a git repository object. + + You will be prompted for: + + * url - address of repository to be used for git clone operation + + * secret - Snowflake secret containing authentication credentials. Not needed if origin repository does not require + authentication for RO operations (clone, fetch) + + * API integration - object allowing Snowflake to interact with git repository. + """ + + manager = GitManager() + + def _assure_repository_does_not_exist() -> None: + pass + + def _get_secret() -> Optional[str]: + secret_needed = typer.confirm("Use secret for authentication?") + if not secret_needed: + return None + + use_existing_secret = typer.confirm("Use existing secret?") + if use_existing_secret: + existing_secret = typer.prompt("Secret identifier") + return existing_secret + + cli_console.step("Creating new secret") + secret_name = f"{repository_name}_secret" + username = typer.prompt("username") + password = typer.prompt("password/token", hide_input=True) + manager.create_secret(username=username, password=password, name=secret_name) + cli_console.step(f"Secret '{secret_name}' successfully created") + return secret_name + + def _get_api_integration(secret: Optional[str], url: str) -> str: + use_existing_api = typer.confirm("Use existing api integration?") + if use_existing_api: + api_name = typer.prompt("API integration identifier") + return api_name + + api_name = f"{repository_name}_api_integration" + manager.create_api_integration(name=api_name, allowed_prefix=url, secret=secret) + cli_console.step(f"API integration '{api_name}' successfully created.") + return api_name + + _assure_repository_does_not_exist() + url = typer.prompt("Origin url") + secret = _get_secret() + api_integration = _get_api_integration(secret=secret, url=url) + return QueryResult( + manager.create( + repo_name=repository_name, + url=url, + api_integration=api_integration, + secret=secret, + ) + ) + + @app.command( "list-branches", requires_connection=True, @@ -61,7 +129,7 @@ def list_tags( **options, ) -> CommandResult: """ - List all tags in the repository. + List all tags in the repository.§ """ return QueryResult(GitManager().show_tags(repo_name=repository_name)) diff --git a/src/snowflake/cli/plugins/git/manager.py b/src/snowflake/cli/plugins/git/manager.py index 04e4816f4b..11d0e580ff 100644 --- a/src/snowflake/cli/plugins/git/manager.py +++ b/src/snowflake/cli/plugins/git/manager.py @@ -1,3 +1,5 @@ +from typing import Optional + from snowflake.cli.plugins.object.stage.manager import StageManager from snowflake.connector.cursor import SnowflakeCursor @@ -18,3 +20,36 @@ def show_files(self, repo_path: str) -> SnowflakeCursor: def fetch(self, repo_name: str) -> SnowflakeCursor: query = f"alter git repository {repo_name} fetch" return self._execute_query(query) + + def create( + self, repo_name: str, api_integration: str, url: str, secret: str + ) -> SnowflakeCursor: + query = ( + f"create git repository {repo_name}" + f" api_integration = {api_integration}" + f" origin = '{url}'" + ) + if secret is not None: + query += f" git_credentials = {secret}" + return self._execute_query(query) + + def create_secret(self, name: str, username: str, password: str) -> SnowflakeCursor: + query = ( + f"create secret {name}" + f" type = password" + f" username = '{username}'" + f" password = '{password}'" + ) + return self._execute_query(query) + + def create_api_integration( + self, name: str, allowed_prefix: str, secret: Optional[str] + ) -> SnowflakeCursor: + query = ( + f"create api integration {name}" + f" api_provider = git_https_api" + f" api_allowed_prefixes = ('{allowed_prefix}')" + f" allowed_authentication_secrets = ({secret if secret else ''})" + f" enabled = true" + ) + return self._execute_query(query) From 7f38b530de18a834d6e68455bb5115e6a49ab31c Mon Sep 17 00:00:00 2001 From: Patryk Czajka Date: Thu, 7 Mar 2024 14:41:22 +0100 Subject: [PATCH 02/13] Quickcheck whether repository already exists --- src/snowflake/cli/plugins/git/commands.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/snowflake/cli/plugins/git/commands.py b/src/snowflake/cli/plugins/git/commands.py index ce7ca18365..fe1ffee98a 100644 --- a/src/snowflake/cli/plugins/git/commands.py +++ b/src/snowflake/cli/plugins/git/commands.py @@ -7,9 +7,12 @@ from snowflake.cli.api.commands.flags import identifier_argument from snowflake.cli.api.commands.snow_typer import SnowTyper from snowflake.cli.api.console.console import cli_console +from snowflake.cli.api.constants import ObjectType from snowflake.cli.api.output.types import CommandResult, QueryResult from snowflake.cli.api.utils.path_utils import is_stage_path from snowflake.cli.plugins.git.manager import GitManager +from snowflake.cli.plugins.object.manager import ObjectManager +from snowflake.connector import ProgrammingError app = SnowTyper( name="git", @@ -61,7 +64,15 @@ def setup( manager = GitManager() def _assure_repository_does_not_exist() -> None: - pass + om = ObjectManager() + try: + om.describe( + object_type=ObjectType.GIT_REPOSITORY.value.cli_name, + name=repository_name, + ) + raise ClickException(f"Repository '{repository_name}' already exists") + except ProgrammingError: + pass def _get_secret() -> Optional[str]: secret_needed = typer.confirm("Use secret for authentication?") From e93fc7d2689f28798bff6841a660a9570f09e073 Mon Sep 17 00:00:00 2001 From: Patryk Czajka Date: Fri, 8 Mar 2024 10:01:15 +0100 Subject: [PATCH 03/13] unit tests --- tests/git/test_git_commands.py | 201 +++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) diff --git a/tests/git/test_git_commands.py b/tests/git/test_git_commands.py index fab949042b..91d7fbae10 100644 --- a/tests/git/test_git_commands.py +++ b/tests/git/test_git_commands.py @@ -2,6 +2,9 @@ from unittest import mock import pytest +from snowflake.connector import ProgrammingError + +EXAMPLE_URL = "https://github.com/an-example-repo.git" @pytest.mark.skip(reason="Command is hidden") @@ -99,6 +102,204 @@ def test_copy_not_a_stage_error(runner): _assert_invalid_repo_path_error_message(result.output) +@mock.patch("snowflake.connector.connect") +@mock.patch("snowflake.cli.plugins.snowpark.commands.ObjectManager.describe") +def test_setup_already_exists_error(mock_om_describe, mock_connector, runner, mock_ctx): + mock_om_describe.return_value = {"object_details": "something"} + ctx = mock_ctx() + mock_connector.return_value = ctx + + result = runner.invoke(["git", "setup", "repo_name"]) + assert result.exit_code == 1, result.output + assert "Error" in result.output + assert "Repository 'repo_name' already exists" in result.output + + +@mock.patch("snowflake.connector.connect") +@mock.patch("snowflake.cli.plugins.snowpark.commands.ObjectManager.describe") +def test_setup_no_secret_existing_api( + mock_om_describe, mock_connector, runner, mock_ctx +): + mock_om_describe.side_effect = ProgrammingError("does not exist or not authorized") + ctx = mock_ctx() + mock_connector.return_value = ctx + + communication = "\n".join([EXAMPLE_URL, "n", "y", "existing_api_integration", ""]) + result = runner.invoke(["git", "setup", "repo_name"], input=communication) + + assert result.exit_code == 0, result.output + assert result.output.startswith( + "\n".join( + [ + "Origin url: https://github.com/an-example-repo.git", + "Use secret for authentication? [y/N]: n", + "Use existing api integration? [y/N]: y", + "API integration identifier: existing_api_integration", + ] + ) + ) + assert ctx.get_query() == ( + "create git repository repo_name" + " api_integration = existing_api_integration" + " origin = 'https://github.com/an-example-repo.git'" + ) + + +@mock.patch("snowflake.connector.connect") +@mock.patch("snowflake.cli.plugins.snowpark.commands.ObjectManager.describe") +def test_setup_no_secret_create_api(mock_om_describe, mock_connector, runner, mock_ctx): + mock_om_describe.side_effect = ProgrammingError("does not exist or not authorized") + ctx = mock_ctx() + mock_connector.return_value = ctx + + communication = "\n".join([EXAMPLE_URL, "n", "n", "existing_api_integration", ""]) + result = runner.invoke(["git", "setup", "repo_name"], input=communication) + + assert result.exit_code == 0, result.output + assert result.output.startswith( + "\n".join( + [ + "Origin url: https://github.com/an-example-repo.git", + "Use secret for authentication? [y/N]: n", + "Use existing api integration? [y/N]: n", + "API integration 'repo_name_api_integration' successfully created.", + ] + ) + ) + assert ctx.get_query() == ( + "create api integration repo_name_api_integration" + " api_provider = git_https_api" + " api_allowed_prefixes = ('https://github.com/an-example-repo.git')" + " allowed_authentication_secrets = ()" + " enabled = true" + "\n" + "create git repository repo_name" + " api_integration = repo_name_api_integration" + " origin = 'https://github.com/an-example-repo.git'" + ) + + +@mock.patch("snowflake.connector.connect") +@mock.patch("snowflake.cli.plugins.snowpark.commands.ObjectManager.describe") +def test_setup_existing_secret_existing_api( + mock_om_describe, mock_connector, runner, mock_ctx +): + mock_om_describe.side_effect = ProgrammingError("does not exist or not authorized") + ctx = mock_ctx() + mock_connector.return_value = ctx + + communication = "\n".join( + [EXAMPLE_URL, "y", "y", "existing_secret", "y", "existing_api_integration", ""] + ) + result = runner.invoke(["git", "setup", "repo_name"], input=communication) + + assert result.exit_code == 0, result.output + assert result.output.startswith( + "\n".join( + [ + "Origin url: https://github.com/an-example-repo.git", + "Use secret for authentication? [y/N]: y", + "Use existing secret? [y/N]: y", + "Secret identifier: existing_secret", + "Use existing api integration? [y/N]: y", + "API integration identifier: existing_api_integration", + ] + ) + ) + assert ctx.get_query() == ( + "create git repository repo_name" + " api_integration = existing_api_integration" + " origin = 'https://github.com/an-example-repo.git'" + " git_credentials = existing_secret" + ) + + +@mock.patch("snowflake.connector.connect") +@mock.patch("snowflake.cli.plugins.snowpark.commands.ObjectManager.describe") +def test_setup_existing_secret_create_api( + mock_om_describe, mock_connector, runner, mock_ctx +): + mock_om_describe.side_effect = ProgrammingError("does not exist or not authorized") + ctx = mock_ctx() + mock_connector.return_value = ctx + + communication = "\n".join([EXAMPLE_URL, "y", "y", "existing_secret", "n", ""]) + result = runner.invoke(["git", "setup", "repo_name"], input=communication) + + assert result.exit_code == 0, result.output + assert result.output.startswith( + "\n".join( + [ + "Origin url: https://github.com/an-example-repo.git", + "Use secret for authentication? [y/N]: y", + "Use existing secret? [y/N]: y", + "Secret identifier: existing_secret", + "Use existing api integration? [y/N]: n", + "API integration 'repo_name_api_integration' successfully created.", + ] + ) + ) + assert ctx.get_query() == ( + "create api integration repo_name_api_integration" + " api_provider = git_https_api" + " api_allowed_prefixes = ('https://github.com/an-example-repo.git')" + " allowed_authentication_secrets = (existing_secret)" + " enabled = true" + "\n" + "create git repository repo_name" + " api_integration = repo_name_api_integration" + " origin = 'https://github.com/an-example-repo.git'" + " git_credentials = existing_secret" + ) + + +@mock.patch("snowflake.connector.connect") +@mock.patch("snowflake.cli.plugins.snowpark.commands.ObjectManager.describe") +def test_setup_create_secret_create_api( + mock_om_describe, mock_connector, runner, mock_ctx +): + mock_om_describe.side_effect = ProgrammingError("does not exist or not authorized") + ctx = mock_ctx() + mock_connector.return_value = ctx + + communication = "\n".join([EXAMPLE_URL, "y", "n", "john_doe", "admin123", "n", ""]) + result = runner.invoke(["git", "setup", "repo_name"], input=communication) + + assert result.exit_code == 0, result.output + assert result.output.startswith( + "\n".join( + [ + "Origin url: https://github.com/an-example-repo.git", + "Use secret for authentication? [y/N]: y", + "Use existing secret? [y/N]: n", + "Creating new secret", + "username: john_doe", + "password/token: ", + "Secret 'repo_name_secret' successfully created", + "Use existing api integration? [y/N]: n", + "API integration 'repo_name_api_integration' successfully created.", + ] + ) + ) + assert ctx.get_query() == ( + "create secret repo_name_secret" + " type = password" + " username = 'john_doe'" + " password = 'admin123'" + "\n" + "create api integration repo_name_api_integration" + " api_provider = git_https_api" + " api_allowed_prefixes = ('https://github.com/an-example-repo.git')" + " allowed_authentication_secrets = (repo_name_secret)" + " enabled = true" + "\n" + "create git repository repo_name" + " api_integration = repo_name_api_integration" + " origin = 'https://github.com/an-example-repo.git'" + " git_credentials = repo_name_secret" + ) + + def _assert_invalid_repo_path_error_message(output): assert "Error" in output assert ( From 45768599f1ecf52b4cf2ebe493c169dd7057f3a9 Mon Sep 17 00:00:00 2001 From: Patryk Czajka Date: Fri, 8 Mar 2024 10:15:13 +0100 Subject: [PATCH 04/13] integration tests --- tests_integration/test_git.py | 33 ++++++--------------------------- 1 file changed, 6 insertions(+), 27 deletions(-) diff --git a/tests_integration/test_git.py b/tests_integration/test_git.py index 5ae9e1a96a..b524e5f759 100644 --- a/tests_integration/test_git.py +++ b/tests_integration/test_git.py @@ -9,34 +9,13 @@ @pytest.fixture def sf_git_repository(runner, test_database): repo_name = "SNOWCLI_TESTING_REPO" - integration_name = "SNOW_GIT_TESTING_API_INTEGRATION" - - if not _integration_exists(runner, integration_name=integration_name): - result = runner.invoke_with_connection( - [ - "sql", - "-q", - f""" - CREATE API INTEGRATION {integration_name} - API_PROVIDER = git_https_api - API_ALLOWED_PREFIXES = ('https://github.com/snowflakedb/') - ALLOWED_AUTHENTICATION_SECRETS = () - ENABLED = true - """, - ] - ) - assert result.exit_code == 0 - + integration_name = "SNOWCLI_TESTING_REPO_API_INTEGRATION" + communication = ["https://github.com/snowflakedb/snowflake-cli.git", "n", "n"] + if _integration_exists(runner, integration_name): + communication[-1] = "y" + communication.append(integration_name) result = runner.invoke_with_connection( - [ - "sql", - "-q", - f""" - CREATE GIT REPOSITORY {repo_name} - API_INTEGRATION = {integration_name} - ORIGIN = 'https://github.com/snowflakedb/snowflake-cli.git' - """, - ] + ["git", "setup", repo_name], input="\n".join(communication) + "\n" ) assert result.exit_code == 0 return repo_name From adeac9d5fc5b8803b05e4a6c7033e3a45d6d26d1 Mon Sep 17 00:00:00 2001 From: Patryk Czajka Date: Fri, 8 Mar 2024 10:22:01 +0100 Subject: [PATCH 05/13] small fix; update help messages test --- src/snowflake/cli/plugins/git/commands.py | 2 +- tests/__snapshots__/test_help_messages.ambr | 73 +++++++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/src/snowflake/cli/plugins/git/commands.py b/src/snowflake/cli/plugins/git/commands.py index fe1ffee98a..fd7aabd58f 100644 --- a/src/snowflake/cli/plugins/git/commands.py +++ b/src/snowflake/cli/plugins/git/commands.py @@ -140,7 +140,7 @@ def list_tags( **options, ) -> CommandResult: """ - List all tags in the repository.§ + List all tags in the repository. """ return QueryResult(GitManager().show_tags(repo_name=repository_name)) diff --git a/tests/__snapshots__/test_help_messages.ambr b/tests/__snapshots__/test_help_messages.ambr index 4b5bcedb84..848ad50469 100644 --- a/tests/__snapshots__/test_help_messages.ambr +++ b/tests/__snapshots__/test_help_messages.ambr @@ -1127,6 +1127,78 @@ ╰──────────────────────────────────────────────────────────────────────────────╯ + ''' +# --- +# name: test_help_messages[git.setup] + ''' + + Usage: default git setup [OPTIONS] REPOSITORY_NAME + + Sets up a git repository object. + You will be prompted for: + * url - address of repository to be used for git clone operation + * secret - Snowflake secret containing authentication credentials. Not needed + if origin repository does not require authentication for RO operations (clone, + fetch) + * API integration - object allowing Snowflake to interact with git repository. + + ╭─ Arguments ──────────────────────────────────────────────────────────────────╮ + │ * repository_name TEXT Identifier of the git repository. For │ + │ example: my_repo │ + │ [default: None] │ + │ [required] │ + ╰──────────────────────────────────────────────────────────────────────────────╯ + ╭─ Options ────────────────────────────────────────────────────────────────────╮ + │ --help -h Show this message and exit. │ + ╰──────────────────────────────────────────────────────────────────────────────╯ + ╭─ Connection configuration ───────────────────────────────────────────────────╮ + │ --connection,--environment -c TEXT Name of the connection, as defined │ + │ in your `config.toml`. Default: │ + │ `default`. │ + │ --account,--accountname TEXT Name assigned to your Snowflake │ + │ account. Overrides the value │ + │ specified for the connection. │ + │ --user,--username TEXT Username to connect to Snowflake. │ + │ Overrides the value specified for │ + │ the connection. │ + │ --password TEXT Snowflake password. Overrides the │ + │ value specified for the │ + │ connection. │ + │ --authenticator TEXT Snowflake authenticator. Overrides │ + │ the value specified for the │ + │ connection. │ + │ --private-key-path TEXT Snowflake private key path. │ + │ Overrides the value specified for │ + │ the connection. │ + │ --database,--dbname TEXT Database to use. Overrides the │ + │ value specified for the │ + │ connection. │ + │ --schema,--schemaname TEXT Database schema to use. Overrides │ + │ the value specified for the │ + │ connection. │ + │ --role,--rolename TEXT Role to use. Overrides the value │ + │ specified for the connection. │ + │ --warehouse TEXT Warehouse to use. Overrides the │ + │ value specified for the │ + │ connection. │ + │ --temporary-connection -x Uses connection defined with │ + │ command line parameters, instead │ + │ of one defined in config │ + │ --mfa-passcode TEXT Token to use for multi-factor │ + │ authentication (MFA) │ + ╰──────────────────────────────────────────────────────────────────────────────╯ + ╭─ Global configuration ───────────────────────────────────────────────────────╮ + │ --format [TABLE|JSON] Specifies the output format. │ + │ [default: TABLE] │ + │ --verbose -v Displays log entries for log levels `info` │ + │ and higher. │ + │ --debug Displays log entries for log levels `debug` │ + │ and higher; debug logs contains additional │ + │ information. │ + │ --silent Turns off intermediate output to console. │ + ╰──────────────────────────────────────────────────────────────────────────────╯ + + ''' # --- # name: test_help_messages[git] @@ -1146,6 +1218,7 @@ │ list-branches List all branches in the repository. │ │ list-files List files from given state of git repository. │ │ list-tags List all tags in the repository. │ + │ setup Sets up a git repository object. │ ╰──────────────────────────────────────────────────────────────────────────────╯ From 6d2beb522cf26e696b91a7a1c0c72b4efd8a6724 Mon Sep 17 00:00:00 2001 From: Patryk Czajka Date: Fri, 8 Mar 2024 10:32:50 +0100 Subject: [PATCH 06/13] check setup output in integration tests --- tests_integration/test_git.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests_integration/test_git.py b/tests_integration/test_git.py index b524e5f759..a10d979cc6 100644 --- a/tests_integration/test_git.py +++ b/tests_integration/test_git.py @@ -18,6 +18,7 @@ def sf_git_repository(runner, test_database): ["git", "setup", repo_name], input="\n".join(communication) + "\n" ) assert result.exit_code == 0 + assert f"Git Repository {repo_name} was successfully created." in result.output return repo_name From fa6fb1c10762ebdf81de8580572e22010c7c276b Mon Sep 17 00:00:00 2001 From: Patryk Czajka Date: Fri, 8 Mar 2024 12:16:00 +0100 Subject: [PATCH 07/13] review: refactor git/commands::setup --- src/snowflake/cli/plugins/git/commands.py | 79 +++++++++++------------ 1 file changed, 36 insertions(+), 43 deletions(-) diff --git a/src/snowflake/cli/plugins/git/commands.py b/src/snowflake/cli/plugins/git/commands.py index fd7aabd58f..bfb1a62f89 100644 --- a/src/snowflake/cli/plugins/git/commands.py +++ b/src/snowflake/cli/plugins/git/commands.py @@ -1,6 +1,5 @@ import logging from pathlib import Path -from typing import Optional import typer from click import ClickException @@ -43,6 +42,18 @@ def _repo_path_argument_callback(path): ) +def _assure_repository_does_not_exist(repository_name: str) -> None: + om = ObjectManager() + try: + om.describe( + object_type=ObjectType.GIT_REPOSITORY.value.cli_name, + name=repository_name, + ) + raise ClickException(f"Repository '{repository_name}' already exists") + except ProgrammingError: + pass + + @app.command("setup", requires_connection=True) def setup( repository_name: str = RepoNameArgument, @@ -60,53 +71,35 @@ def setup( * API integration - object allowing Snowflake to interact with git repository. """ - + _assure_repository_does_not_exist(repository_name) manager = GitManager() - def _assure_repository_does_not_exist() -> None: - om = ObjectManager() - try: - om.describe( - object_type=ObjectType.GIT_REPOSITORY.value.cli_name, - name=repository_name, - ) - raise ClickException(f"Repository '{repository_name}' already exists") - except ProgrammingError: - pass - - def _get_secret() -> Optional[str]: - secret_needed = typer.confirm("Use secret for authentication?") - if not secret_needed: - return None + url = typer.prompt("Origin url") + secret = None + secret_needed = typer.confirm("Use secret for authentication?") + if secret_needed: use_existing_secret = typer.confirm("Use existing secret?") if use_existing_secret: - existing_secret = typer.prompt("Secret identifier") - return existing_secret - - cli_console.step("Creating new secret") - secret_name = f"{repository_name}_secret" - username = typer.prompt("username") - password = typer.prompt("password/token", hide_input=True) - manager.create_secret(username=username, password=password, name=secret_name) - cli_console.step(f"Secret '{secret_name}' successfully created") - return secret_name - - def _get_api_integration(secret: Optional[str], url: str) -> str: - use_existing_api = typer.confirm("Use existing api integration?") - if use_existing_api: - api_name = typer.prompt("API integration identifier") - return api_name - - api_name = f"{repository_name}_api_integration" - manager.create_api_integration(name=api_name, allowed_prefix=url, secret=secret) - cli_console.step(f"API integration '{api_name}' successfully created.") - return api_name - - _assure_repository_does_not_exist() - url = typer.prompt("Origin url") - secret = _get_secret() - api_integration = _get_api_integration(secret=secret, url=url) + secret = typer.prompt("Secret identifier") + else: + cli_console.step("Creating new secret") + secret = f"{repository_name}_secret" + username = typer.prompt("username") + password = typer.prompt("password/token", hide_input=True) + manager.create_secret(username=username, password=password, name=secret) + cli_console.step(f"Secret '{secret}' successfully created") + + use_existing_api = typer.confirm("Use existing api integration?") + if use_existing_api: + api_integration = typer.prompt("API integration identifier") + else: + api_integration = f"{repository_name}_api_integration" + manager.create_api_integration( + name=api_integration, allowed_prefix=url, secret=secret + ) + cli_console.step(f"API integration '{api_integration}' successfully created.") + return QueryResult( manager.create( repo_name=repository_name, From 161800a6cd149dcbdb0cf303bd15a64c07c2ebd4 Mon Sep 17 00:00:00 2001 From: Patryk Czajka Date: Fri, 8 Mar 2024 12:19:32 +0100 Subject: [PATCH 08/13] review fixes --- src/snowflake/cli/api/sql_execution.py | 23 +++++++++++++++++++++++ src/snowflake/cli/plugins/git/commands.py | 9 +++++++-- src/snowflake/cli/plugins/git/manager.py | 23 ----------------------- 3 files changed, 30 insertions(+), 25 deletions(-) diff --git a/src/snowflake/cli/api/sql_execution.py b/src/snowflake/cli/api/sql_execution.py index 3b2a04fcba..ffb06617f2 100644 --- a/src/snowflake/cli/api/sql_execution.py +++ b/src/snowflake/cli/api/sql_execution.py @@ -82,6 +82,29 @@ def use_role(self, new_role: str): if is_different_role: self._execute_query(f"use role {prev_role}") + def create_password_secret( + self, name: str, username: str, password: str + ) -> SnowflakeCursor: + query = ( + f"create secret {name}" + f" type = password" + f" username = '{username}'" + f" password = '{password}'" + ) + return self._execute_query(query) + + def create_api_integration( + self, name: str, api_provider: str, allowed_prefix: str, secret: Optional[str] + ) -> SnowflakeCursor: + query = ( + f"create api integration {name}" + f" api_provider = {api_provider}" + f" api_allowed_prefixes = ('{allowed_prefix}')" + f" allowed_authentication_secrets = ({secret if secret else ''})" + f" enabled = true" + ) + return self._execute_query(query) + def _execute_schema_query(self, query: str, name: Optional[str] = None, **kwargs): """ Check that a database and schema are provided before executing the query. Useful for operating on schema level objects. diff --git a/src/snowflake/cli/plugins/git/commands.py b/src/snowflake/cli/plugins/git/commands.py index bfb1a62f89..aed16a82ea 100644 --- a/src/snowflake/cli/plugins/git/commands.py +++ b/src/snowflake/cli/plugins/git/commands.py @@ -87,7 +87,9 @@ def setup( secret = f"{repository_name}_secret" username = typer.prompt("username") password = typer.prompt("password/token", hide_input=True) - manager.create_secret(username=username, password=password, name=secret) + manager.create_password_secret( + username=username, password=password, name=secret + ) cli_console.step(f"Secret '{secret}' successfully created") use_existing_api = typer.confirm("Use existing api integration?") @@ -96,7 +98,10 @@ def setup( else: api_integration = f"{repository_name}_api_integration" manager.create_api_integration( - name=api_integration, allowed_prefix=url, secret=secret + name=api_integration, + api_provider="git_https_api", + allowed_prefix=url, + secret=secret, ) cli_console.step(f"API integration '{api_integration}' successfully created.") diff --git a/src/snowflake/cli/plugins/git/manager.py b/src/snowflake/cli/plugins/git/manager.py index 11d0e580ff..c1091a144c 100644 --- a/src/snowflake/cli/plugins/git/manager.py +++ b/src/snowflake/cli/plugins/git/manager.py @@ -1,5 +1,3 @@ -from typing import Optional - from snowflake.cli.plugins.object.stage.manager import StageManager from snowflake.connector.cursor import SnowflakeCursor @@ -32,24 +30,3 @@ def create( if secret is not None: query += f" git_credentials = {secret}" return self._execute_query(query) - - def create_secret(self, name: str, username: str, password: str) -> SnowflakeCursor: - query = ( - f"create secret {name}" - f" type = password" - f" username = '{username}'" - f" password = '{password}'" - ) - return self._execute_query(query) - - def create_api_integration( - self, name: str, allowed_prefix: str, secret: Optional[str] - ) -> SnowflakeCursor: - query = ( - f"create api integration {name}" - f" api_provider = git_https_api" - f" api_allowed_prefixes = ('{allowed_prefix}')" - f" allowed_authentication_secrets = ({secret if secret else ''})" - f" enabled = true" - ) - return self._execute_query(query) From 56d092aa79abf7ed881aa0bdd3e25623713ed4da Mon Sep 17 00:00:00 2001 From: Patryk Czajka Date: Fri, 8 Mar 2024 13:06:26 +0100 Subject: [PATCH 09/13] Update src/snowflake/cli/plugins/git/commands.py Co-authored-by: Tomasz Urbaszek --- src/snowflake/cli/plugins/git/commands.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/snowflake/cli/plugins/git/commands.py b/src/snowflake/cli/plugins/git/commands.py index aed16a82ea..ca2412af5d 100644 --- a/src/snowflake/cli/plugins/git/commands.py +++ b/src/snowflake/cli/plugins/git/commands.py @@ -43,9 +43,8 @@ def _repo_path_argument_callback(path): def _assure_repository_does_not_exist(repository_name: str) -> None: - om = ObjectManager() try: - om.describe( + ObjectManager().describe( object_type=ObjectType.GIT_REPOSITORY.value.cli_name, name=repository_name, ) From 9bd803ab04ab84e72e99910d88eb73f22f0f533e Mon Sep 17 00:00:00 2001 From: Patryk Czajka Date: Fri, 8 Mar 2024 16:05:41 +0100 Subject: [PATCH 10/13] refactor setup command --- src/snowflake/cli/plugins/git/commands.py | 72 ++++++++++++++--------- 1 file changed, 44 insertions(+), 28 deletions(-) diff --git a/src/snowflake/cli/plugins/git/commands.py b/src/snowflake/cli/plugins/git/commands.py index aed16a82ea..04ae06128b 100644 --- a/src/snowflake/cli/plugins/git/commands.py +++ b/src/snowflake/cli/plugins/git/commands.py @@ -42,16 +42,25 @@ def _repo_path_argument_callback(path): ) -def _assure_repository_does_not_exist(repository_name: str) -> None: - om = ObjectManager() +def _object_exists(object_type, identifier): try: - om.describe( - object_type=ObjectType.GIT_REPOSITORY.value.cli_name, - name=repository_name, + ObjectManager().describe( + object_type=object_type.value.cli_name, + name=identifier, ) - raise ClickException(f"Repository '{repository_name}' already exists") + return True except ProgrammingError: - pass + return False + + +def _assure_repository_does_not_exist(repository_name: str) -> None: + if _object_exists(ObjectType.GIT_REPOSITORY, repository_name): + raise ClickException(f"Repository '{repository_name}' already exists") + + +def _validate_origin_url(url: str) -> None: + if not url.startswith("https://"): + raise ClickException("Url address should start with 'https'") @app.command("setup", requires_connection=True) @@ -75,42 +84,49 @@ def setup( manager = GitManager() url = typer.prompt("Origin url") + _validate_origin_url(url) - secret = None + secret = {} secret_needed = typer.confirm("Use secret for authentication?") if secret_needed: - use_existing_secret = typer.confirm("Use existing secret?") - if use_existing_secret: - secret = typer.prompt("Secret identifier") - else: - cli_console.step("Creating new secret") - secret = f"{repository_name}_secret" - username = typer.prompt("username") - password = typer.prompt("password/token", hide_input=True) - manager.create_password_secret( - username=username, password=password, name=secret - ) - cli_console.step(f"Secret '{secret}' successfully created") - - use_existing_api = typer.confirm("Use existing api integration?") - if use_existing_api: - api_integration = typer.prompt("API integration identifier") - else: - api_integration = f"{repository_name}_api_integration" + secret_name = f"{repository_name}_secret" + secret_name = typer.prompt( + "Secret identifier (will be created if not exists)", default=secret_name + ) + secret = {"name": secret_name} + if not _object_exists(ObjectType.SECRET, secret_name): + cli_console.step(f"Secret '{secret_name}' will be created") + secret["username"] = typer.prompt("username") + secret["password"] = typer.prompt("password/token", hide_input=True) + + api_integration = f"{repository_name}_api_integration" + api_integration = typer.prompt( + "API integration identifier (will be created if not exists)", + default=api_integration, + ) + + if "username" in secret: + manager.create_password_secret(**secret) + secret_name = secret["name"] + cli_console.step(f"Secret '{secret_name}' successfully created") + + if not _object_exists(ObjectType.INTEGRATION, api_integration): manager.create_api_integration( name=api_integration, api_provider="git_https_api", allowed_prefix=url, - secret=secret, + secret=secret.get("name"), ) cli_console.step(f"API integration '{api_integration}' successfully created.") + else: + cli_console.step(f"Using existing API integration '{api_integration}'") return QueryResult( manager.create( repo_name=repository_name, url=url, api_integration=api_integration, - secret=secret, + secret=secret.get("name"), ) ) From 8277a0fd9494233047a1c0e1c244ef401eec070b Mon Sep 17 00:00:00 2001 From: Patryk Czajka Date: Fri, 8 Mar 2024 16:45:58 +0100 Subject: [PATCH 11/13] update unit tests --- src/snowflake/cli/plugins/git/commands.py | 8 ++- tests/git/test_git_commands.py | 80 ++++++++++++++++------- 2 files changed, 60 insertions(+), 28 deletions(-) diff --git a/src/snowflake/cli/plugins/git/commands.py b/src/snowflake/cli/plugins/git/commands.py index 04ae06128b..8afc6808be 100644 --- a/src/snowflake/cli/plugins/git/commands.py +++ b/src/snowflake/cli/plugins/git/commands.py @@ -94,7 +94,9 @@ def setup( "Secret identifier (will be created if not exists)", default=secret_name ) secret = {"name": secret_name} - if not _object_exists(ObjectType.SECRET, secret_name): + if _object_exists(ObjectType.SECRET, secret_name): + cli_console.step(f"Using existing secret '{secret_name}'") + else: cli_console.step(f"Secret '{secret_name}' will be created") secret["username"] = typer.prompt("username") secret["password"] = typer.prompt("password/token", hide_input=True) @@ -108,7 +110,7 @@ def setup( if "username" in secret: manager.create_password_secret(**secret) secret_name = secret["name"] - cli_console.step(f"Secret '{secret_name}' successfully created") + cli_console.step(f"Secret '{secret_name}' successfully created.") if not _object_exists(ObjectType.INTEGRATION, api_integration): manager.create_api_integration( @@ -119,7 +121,7 @@ def setup( ) cli_console.step(f"API integration '{api_integration}' successfully created.") else: - cli_console.step(f"Using existing API integration '{api_integration}'") + cli_console.step(f"Using existing API integration '{api_integration}'.") return QueryResult( manager.create( diff --git a/tests/git/test_git_commands.py b/tests/git/test_git_commands.py index 91d7fbae10..cb22eed7cd 100644 --- a/tests/git/test_git_commands.py +++ b/tests/git/test_git_commands.py @@ -115,16 +115,34 @@ def test_setup_already_exists_error(mock_om_describe, mock_connector, runner, mo assert "Repository 'repo_name' already exists" in result.output +@mock.patch("snowflake.connector.connect") +@mock.patch("snowflake.cli.plugins.snowpark.commands.ObjectManager.describe") +def test_setup_invalid_url_error(mock_om_describe, mock_connector, runner, mock_ctx): + mock_om_describe.side_effect = ProgrammingError("does not exist or not authorized") + ctx = mock_ctx() + mock_connector.return_value = ctx + communication = "http://invalid_url.git\ns" + result = runner.invoke(["git", "setup", "repo_name"], input=communication) + + assert result.exit_code == 1, result.output + assert "Error" in result.output + assert "Url address should start with 'https'" in result.output + + @mock.patch("snowflake.connector.connect") @mock.patch("snowflake.cli.plugins.snowpark.commands.ObjectManager.describe") def test_setup_no_secret_existing_api( mock_om_describe, mock_connector, runner, mock_ctx ): - mock_om_describe.side_effect = ProgrammingError("does not exist or not authorized") + mock_om_describe.side_effect = [ + ProgrammingError("does not exist or not authorized"), + None, + ] + mock_om_describe.return_value = [None, {"object_details": "something"}] ctx = mock_ctx() mock_connector.return_value = ctx - communication = "\n".join([EXAMPLE_URL, "n", "y", "existing_api_integration", ""]) + communication = "\n".join([EXAMPLE_URL, "n", "existing_api_integration", ""]) result = runner.invoke(["git", "setup", "repo_name"], input=communication) assert result.exit_code == 0, result.output @@ -133,8 +151,8 @@ def test_setup_no_secret_existing_api( [ "Origin url: https://github.com/an-example-repo.git", "Use secret for authentication? [y/N]: n", - "Use existing api integration? [y/N]: y", - "API integration identifier: existing_api_integration", + "API integration identifier (will be created if not exists) [repo_name_api_integration]: existing_api_integration", + "Using existing API integration 'existing_api_integration'.", ] ) ) @@ -152,7 +170,7 @@ def test_setup_no_secret_create_api(mock_om_describe, mock_connector, runner, mo ctx = mock_ctx() mock_connector.return_value = ctx - communication = "\n".join([EXAMPLE_URL, "n", "n", "existing_api_integration", ""]) + communication = "\n".join([EXAMPLE_URL, "n", "", ""]) result = runner.invoke(["git", "setup", "repo_name"], input=communication) assert result.exit_code == 0, result.output @@ -161,7 +179,7 @@ def test_setup_no_secret_create_api(mock_om_describe, mock_connector, runner, mo [ "Origin url: https://github.com/an-example-repo.git", "Use secret for authentication? [y/N]: n", - "Use existing api integration? [y/N]: n", + "API integration identifier (will be created if not exists) [repo_name_api_integration]: ", "API integration 'repo_name_api_integration' successfully created.", ] ) @@ -184,12 +202,17 @@ def test_setup_no_secret_create_api(mock_om_describe, mock_connector, runner, mo def test_setup_existing_secret_existing_api( mock_om_describe, mock_connector, runner, mock_ctx ): - mock_om_describe.side_effect = ProgrammingError("does not exist or not authorized") + mock_om_describe.side_effect = [ + ProgrammingError("does not exist or not authorized"), + None, + None, + ] + mock_om_describe.return_value = [None, "integration_details", "secret_details"] ctx = mock_ctx() mock_connector.return_value = ctx communication = "\n".join( - [EXAMPLE_URL, "y", "y", "existing_secret", "y", "existing_api_integration", ""] + [EXAMPLE_URL, "y", "existing_secret", "existing_api_integration", ""] ) result = runner.invoke(["git", "setup", "repo_name"], input=communication) @@ -199,10 +222,10 @@ def test_setup_existing_secret_existing_api( [ "Origin url: https://github.com/an-example-repo.git", "Use secret for authentication? [y/N]: y", - "Use existing secret? [y/N]: y", - "Secret identifier: existing_secret", - "Use existing api integration? [y/N]: y", - "API integration identifier: existing_api_integration", + "Secret identifier (will be created if not exists) [repo_name_secret]: existing_secret", + "Using existing secret 'existing_secret'", + "API integration identifier (will be created if not exists) [repo_name_api_integration]: existing_api_integration", + "Using existing API integration 'existing_api_integration'.", ] ) ) @@ -219,11 +242,16 @@ def test_setup_existing_secret_existing_api( def test_setup_existing_secret_create_api( mock_om_describe, mock_connector, runner, mock_ctx ): - mock_om_describe.side_effect = ProgrammingError("does not exist or not authorized") + mock_om_describe.side_effect = [ + ProgrammingError("does not exist or not authorized"), + None, + ProgrammingError("does not exist or not authorized"), + ] + mock_om_describe.return_value = [None, "secret_details", None] ctx = mock_ctx() mock_connector.return_value = ctx - communication = "\n".join([EXAMPLE_URL, "y", "y", "existing_secret", "n", ""]) + communication = "\n".join([EXAMPLE_URL, "y", "existing_secret", "", ""]) result = runner.invoke(["git", "setup", "repo_name"], input=communication) assert result.exit_code == 0, result.output @@ -232,9 +260,9 @@ def test_setup_existing_secret_create_api( [ "Origin url: https://github.com/an-example-repo.git", "Use secret for authentication? [y/N]: y", - "Use existing secret? [y/N]: y", - "Secret identifier: existing_secret", - "Use existing api integration? [y/N]: n", + "Secret identifier (will be created if not exists) [repo_name_secret]: existing_secret", + "Using existing secret 'existing_secret'", + "API integration identifier (will be created if not exists) [repo_name_api_integration]: ", "API integration 'repo_name_api_integration' successfully created.", ] ) @@ -262,7 +290,9 @@ def test_setup_create_secret_create_api( ctx = mock_ctx() mock_connector.return_value = ctx - communication = "\n".join([EXAMPLE_URL, "y", "n", "john_doe", "admin123", "n", ""]) + communication = "\n".join( + [EXAMPLE_URL, "y", "", "john_doe", "admin123", "new_integration", ""] + ) result = runner.invoke(["git", "setup", "repo_name"], input=communication) assert result.exit_code == 0, result.output @@ -271,13 +301,13 @@ def test_setup_create_secret_create_api( [ "Origin url: https://github.com/an-example-repo.git", "Use secret for authentication? [y/N]: y", - "Use existing secret? [y/N]: n", - "Creating new secret", + "Secret identifier (will be created if not exists) [repo_name_secret]: ", + "Secret 'repo_name_secret' will be created", "username: john_doe", "password/token: ", - "Secret 'repo_name_secret' successfully created", - "Use existing api integration? [y/N]: n", - "API integration 'repo_name_api_integration' successfully created.", + "API integration identifier (will be created if not exists) [repo_name_api_integration]: new_integration", + "Secret 'repo_name_secret' successfully created.", + "API integration 'new_integration' successfully created.", ] ) ) @@ -287,14 +317,14 @@ def test_setup_create_secret_create_api( " username = 'john_doe'" " password = 'admin123'" "\n" - "create api integration repo_name_api_integration" + "create api integration new_integration" " api_provider = git_https_api" " api_allowed_prefixes = ('https://github.com/an-example-repo.git')" " allowed_authentication_secrets = (repo_name_secret)" " enabled = true" "\n" "create git repository repo_name" - " api_integration = repo_name_api_integration" + " api_integration = new_integration" " origin = 'https://github.com/an-example-repo.git'" " git_credentials = repo_name_secret" ) From 74c733f7b539f6036a0a5534ca323b0c1395bee7 Mon Sep 17 00:00:00 2001 From: Patryk Czajka Date: Fri, 8 Mar 2024 16:49:03 +0100 Subject: [PATCH 12/13] update integration tests --- tests_integration/test_git.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests_integration/test_git.py b/tests_integration/test_git.py index a10d979cc6..5846c837e1 100644 --- a/tests_integration/test_git.py +++ b/tests_integration/test_git.py @@ -10,12 +10,11 @@ def sf_git_repository(runner, test_database): repo_name = "SNOWCLI_TESTING_REPO" integration_name = "SNOWCLI_TESTING_REPO_API_INTEGRATION" - communication = ["https://github.com/snowflakedb/snowflake-cli.git", "n", "n"] - if _integration_exists(runner, integration_name): - communication[-1] = "y" - communication.append(integration_name) + communication = "\n".join( + ["https://github.com/snowflakedb/snowflake-cli.git", "n", integration_name, ""] + ) result = runner.invoke_with_connection( - ["git", "setup", repo_name], input="\n".join(communication) + "\n" + ["git", "setup", repo_name], input=communication ) assert result.exit_code == 0 assert f"Git Repository {repo_name} was successfully created." in result.output From 75052e0650da76005016730655c2be74790ddae0 Mon Sep 17 00:00:00 2001 From: Patryk Czajka Date: Fri, 8 Mar 2024 17:01:39 +0100 Subject: [PATCH 13/13] multiline string --- src/snowflake/cli/api/sql_execution.py | 26 +++--- src/snowflake/cli/plugins/git/manager.py | 14 +-- tests/git/test_git_commands.py | 107 +++++++++++++---------- 3 files changed, 85 insertions(+), 62 deletions(-) diff --git a/src/snowflake/cli/api/sql_execution.py b/src/snowflake/cli/api/sql_execution.py index ffb06617f2..8512e26851 100644 --- a/src/snowflake/cli/api/sql_execution.py +++ b/src/snowflake/cli/api/sql_execution.py @@ -85,23 +85,27 @@ def use_role(self, new_role: str): def create_password_secret( self, name: str, username: str, password: str ) -> SnowflakeCursor: - query = ( - f"create secret {name}" - f" type = password" - f" username = '{username}'" - f" password = '{password}'" + query = dedent( + f""" + create secret {name} + type = password + username = '{username}' + password = '{password}' + """ ) return self._execute_query(query) def create_api_integration( self, name: str, api_provider: str, allowed_prefix: str, secret: Optional[str] ) -> SnowflakeCursor: - query = ( - f"create api integration {name}" - f" api_provider = {api_provider}" - f" api_allowed_prefixes = ('{allowed_prefix}')" - f" allowed_authentication_secrets = ({secret if secret else ''})" - f" enabled = true" + query = dedent( + f""" + create api integration {name} + api_provider = {api_provider} + api_allowed_prefixes = ('{allowed_prefix}') + allowed_authentication_secrets = ({secret if secret else ''}) + enabled = true + """ ) return self._execute_query(query) diff --git a/src/snowflake/cli/plugins/git/manager.py b/src/snowflake/cli/plugins/git/manager.py index c1091a144c..2c8e71cdc1 100644 --- a/src/snowflake/cli/plugins/git/manager.py +++ b/src/snowflake/cli/plugins/git/manager.py @@ -1,3 +1,5 @@ +from textwrap import dedent + from snowflake.cli.plugins.object.stage.manager import StageManager from snowflake.connector.cursor import SnowflakeCursor @@ -22,11 +24,13 @@ def fetch(self, repo_name: str) -> SnowflakeCursor: def create( self, repo_name: str, api_integration: str, url: str, secret: str ) -> SnowflakeCursor: - query = ( - f"create git repository {repo_name}" - f" api_integration = {api_integration}" - f" origin = '{url}'" + query = dedent( + f""" + create git repository {repo_name} + api_integration = {api_integration} + origin = '{url}' + """ ) if secret is not None: - query += f" git_credentials = {secret}" + query += f"git_credentials = {secret}\n" return self._execute_query(query) diff --git a/tests/git/test_git_commands.py b/tests/git/test_git_commands.py index cb22eed7cd..492cf48e9c 100644 --- a/tests/git/test_git_commands.py +++ b/tests/git/test_git_commands.py @@ -1,4 +1,5 @@ from pathlib import Path +from textwrap import dedent from unittest import mock import pytest @@ -156,10 +157,12 @@ def test_setup_no_secret_existing_api( ] ) ) - assert ctx.get_query() == ( - "create git repository repo_name" - " api_integration = existing_api_integration" - " origin = 'https://github.com/an-example-repo.git'" + assert ctx.get_query() == dedent( + """ + create git repository repo_name + api_integration = existing_api_integration + origin = 'https://github.com/an-example-repo.git' + """ ) @@ -184,16 +187,19 @@ def test_setup_no_secret_create_api(mock_om_describe, mock_connector, runner, mo ] ) ) - assert ctx.get_query() == ( - "create api integration repo_name_api_integration" - " api_provider = git_https_api" - " api_allowed_prefixes = ('https://github.com/an-example-repo.git')" - " allowed_authentication_secrets = ()" - " enabled = true" - "\n" - "create git repository repo_name" - " api_integration = repo_name_api_integration" - " origin = 'https://github.com/an-example-repo.git'" + assert ctx.get_query() == dedent( + """ + create api integration repo_name_api_integration + api_provider = git_https_api + api_allowed_prefixes = ('https://github.com/an-example-repo.git') + allowed_authentication_secrets = () + enabled = true + + + create git repository repo_name + api_integration = repo_name_api_integration + origin = 'https://github.com/an-example-repo.git' + """ ) @@ -229,11 +235,13 @@ def test_setup_existing_secret_existing_api( ] ) ) - assert ctx.get_query() == ( - "create git repository repo_name" - " api_integration = existing_api_integration" - " origin = 'https://github.com/an-example-repo.git'" - " git_credentials = existing_secret" + assert ctx.get_query() == dedent( + """ + create git repository repo_name + api_integration = existing_api_integration + origin = 'https://github.com/an-example-repo.git' + git_credentials = existing_secret + """ ) @@ -267,17 +275,20 @@ def test_setup_existing_secret_create_api( ] ) ) - assert ctx.get_query() == ( - "create api integration repo_name_api_integration" - " api_provider = git_https_api" - " api_allowed_prefixes = ('https://github.com/an-example-repo.git')" - " allowed_authentication_secrets = (existing_secret)" - " enabled = true" - "\n" - "create git repository repo_name" - " api_integration = repo_name_api_integration" - " origin = 'https://github.com/an-example-repo.git'" - " git_credentials = existing_secret" + assert ctx.get_query() == dedent( + """ + create api integration repo_name_api_integration + api_provider = git_https_api + api_allowed_prefixes = ('https://github.com/an-example-repo.git') + allowed_authentication_secrets = (existing_secret) + enabled = true + + + create git repository repo_name + api_integration = repo_name_api_integration + origin = 'https://github.com/an-example-repo.git' + git_credentials = existing_secret + """ ) @@ -311,22 +322,26 @@ def test_setup_create_secret_create_api( ] ) ) - assert ctx.get_query() == ( - "create secret repo_name_secret" - " type = password" - " username = 'john_doe'" - " password = 'admin123'" - "\n" - "create api integration new_integration" - " api_provider = git_https_api" - " api_allowed_prefixes = ('https://github.com/an-example-repo.git')" - " allowed_authentication_secrets = (repo_name_secret)" - " enabled = true" - "\n" - "create git repository repo_name" - " api_integration = new_integration" - " origin = 'https://github.com/an-example-repo.git'" - " git_credentials = repo_name_secret" + assert ctx.get_query() == dedent( + """ + create secret repo_name_secret + type = password + username = 'john_doe' + password = 'admin123' + + + create api integration new_integration + api_provider = git_https_api + api_allowed_prefixes = ('https://github.com/an-example-repo.git') + allowed_authentication_secrets = (repo_name_secret) + enabled = true + + + create git repository repo_name + api_integration = new_integration + origin = 'https://github.com/an-example-repo.git' + git_credentials = repo_name_secret + """ )