From 3519b10a70223af58d68da85a33009f1f95012d8 Mon Sep 17 00:00:00 2001 From: Michel El Nacouzi Date: Wed, 7 Aug 2024 08:59:16 -0400 Subject: [PATCH] Add package post-deploy scripts in project definition file (#1399) --- RELEASE-NOTES.md | 4 + .../api/project/schemas/entities/common.py | 4 +- .../project/schemas/native_app/application.py | 4 +- .../api/project/schemas/native_app/package.py | 17 +- .../cli/plugins/nativeapp/manager.py | 83 ++++++- .../cli/plugins/nativeapp/project_model.py | 16 +- .../cli/plugins/nativeapp/run_processor.py | 8 +- .../v2_conversions/v2_to_v1_decorator.py | 28 +-- .../nativeapp/version/version_processor.py | 4 + tests/nativeapp/test_manager.py | 146 +++++++++++++ ..._deploy.py => test_post_deploy_for_app.py} | 20 +- .../nativeapp/test_post_deploy_for_package.py | 175 +++++++++++++++ tests/nativeapp/test_v2_to_v1.py | 8 +- .../test_version_create_processor.py | 189 ++++++++++------ tests/nativeapp/utils.py | 4 + tests/project/__snapshots__/test_config.ambr | 4 + .../package/001-shared.sql | 6 +- .../package/002-shared.sql | 8 +- .../package/001-shared.sql | 5 + .../package/002-shared.sql | 12 ++ .../integration_v2/package/001-shared.sql | 6 +- .../integration_v2/package/002-shared.sql | 8 +- ...{post_deploy1.sql => app_post_deploy1.sql} | 0 ...{post_deploy2.sql => app_post_deploy2.sql} | 0 .../scripts/package_post_deploy1.sql | 4 + .../scripts/package_post_deploy2.sql | 1 + .../projects/napp_post_deploy/snowflake.yml | 10 +- .../snowflake.yml | 3 + tests_integration/nativeapp/test_init_run.py | 88 -------- .../nativeapp/test_post_deploy.py | 202 ++++++++++++++++++ ...{post_deploy1.sql => app_post_deploy1.sql} | 2 +- .../scripts/app_post_deploy2.sql | 3 + .../scripts/package_post_deploy1.sql | 5 + .../scripts/package_post_deploy2.sql | 3 + .../scripts/post_deploy2.sql | 3 - .../snowflake.yml | 11 +- ...{post_deploy1.sql => app_post_deploy1.sql} | 2 +- .../scripts/app_post_deploy2.sql | 3 + .../scripts/package_post_deploy1.sql | 5 + .../scripts/package_post_deploy2.sql | 3 + .../scripts/post_deploy2.sql | 3 - .../snowflake.yml | 11 +- 42 files changed, 886 insertions(+), 235 deletions(-) rename tests/nativeapp/{test_post_deploy.py => test_post_deploy_for_app.py} (92%) create mode 100644 tests/nativeapp/test_post_deploy_for_package.py create mode 100644 tests/test_data/projects/integration_templated_v2/package/001-shared.sql create mode 100644 tests/test_data/projects/integration_templated_v2/package/002-shared.sql rename tests/test_data/projects/napp_post_deploy/scripts/{post_deploy1.sql => app_post_deploy1.sql} (100%) rename tests/test_data/projects/napp_post_deploy/scripts/{post_deploy2.sql => app_post_deploy2.sql} (100%) create mode 100644 tests/test_data/projects/napp_post_deploy/scripts/package_post_deploy1.sql create mode 100644 tests/test_data/projects/napp_post_deploy/scripts/package_post_deploy2.sql create mode 100644 tests_integration/nativeapp/test_post_deploy.py rename tests_integration/test_data/projects/napp_application_post_deploy_v1/scripts/{post_deploy1.sql => app_post_deploy1.sql} (66%) create mode 100644 tests_integration/test_data/projects/napp_application_post_deploy_v1/scripts/app_post_deploy2.sql create mode 100644 tests_integration/test_data/projects/napp_application_post_deploy_v1/scripts/package_post_deploy1.sql create mode 100644 tests_integration/test_data/projects/napp_application_post_deploy_v1/scripts/package_post_deploy2.sql delete mode 100644 tests_integration/test_data/projects/napp_application_post_deploy_v1/scripts/post_deploy2.sql rename tests_integration/test_data/projects/napp_application_post_deploy_v2/scripts/{post_deploy1.sql => app_post_deploy1.sql} (66%) create mode 100644 tests_integration/test_data/projects/napp_application_post_deploy_v2/scripts/app_post_deploy2.sql create mode 100644 tests_integration/test_data/projects/napp_application_post_deploy_v2/scripts/package_post_deploy1.sql create mode 100644 tests_integration/test_data/projects/napp_application_post_deploy_v2/scripts/package_post_deploy2.sql delete mode 100644 tests_integration/test_data/projects/napp_application_post_deploy_v2/scripts/post_deploy2.sql diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 436549b9e9..cee763ffe8 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -18,9 +18,13 @@ ## Backward incompatibility ## Deprecations + * Added deprecation warning for `native_app.package.scripts` in project definition file. ## New additions * Added support for project definition file defaults in templates +* Added support for `native_app.package.post_deploy` scripts in project definition file. + * These scripts will execute whenever a Native App Package is created or updated. + * Currently only supports SQL scripts: `post_deploy: [{sql_script: script.sql}]` ## Fixes and improvements diff --git a/src/snowflake/cli/api/project/schemas/entities/common.py b/src/snowflake/cli/api/project/schemas/entities/common.py index 262f345808..1c4effa0f6 100644 --- a/src/snowflake/cli/api/project/schemas/entities/common.py +++ b/src/snowflake/cli/api/project/schemas/entities/common.py @@ -19,7 +19,7 @@ from pydantic import Field from snowflake.cli.api.project.schemas.native_app.application import ( - ApplicationPostDeployHook, + PostDeployHook, ) from snowflake.cli.api.project.schemas.updatable_model import ( IdentifierField, @@ -35,7 +35,7 @@ class MetaField(UpdatableModel): title="Role to use when creating the entity object", default=None, ) - post_deploy: Optional[List[ApplicationPostDeployHook]] = Field( + post_deploy: Optional[List[PostDeployHook]] = Field( title="Actions that will be executed after the application object is created/upgraded", default=None, ) diff --git a/src/snowflake/cli/api/project/schemas/native_app/application.py b/src/snowflake/cli/api/project/schemas/native_app/application.py index 84f05066ad..6124698cf9 100644 --- a/src/snowflake/cli/api/project/schemas/native_app/application.py +++ b/src/snowflake/cli/api/project/schemas/native_app/application.py @@ -28,7 +28,7 @@ class SqlScriptHookType(UpdatableModel): # Currently sql_script is the only supported hook type. Change to a Union once other hook types are added -ApplicationPostDeployHook = SqlScriptHookType +PostDeployHook = SqlScriptHookType class Application(UpdatableModel): @@ -48,7 +48,7 @@ class Application(UpdatableModel): title="When set, forces debug_mode on/off for the deployed application object", default=None, ) - post_deploy: Optional[List[ApplicationPostDeployHook]] = Field( + post_deploy: Optional[List[PostDeployHook]] = Field( title="Actions that will be executed after the application object is created/upgraded", default=None, ) diff --git a/src/snowflake/cli/api/project/schemas/native_app/package.py b/src/snowflake/cli/api/project/schemas/native_app/package.py index e696ed4ded..603507eddc 100644 --- a/src/snowflake/cli/api/project/schemas/native_app/package.py +++ b/src/snowflake/cli/api/project/schemas/native_app/package.py @@ -16,7 +16,8 @@ from typing import List, Literal, Optional -from pydantic import Field, field_validator +from pydantic import Field, field_validator, model_validator +from snowflake.cli.api.project.schemas.native_app.application import PostDeployHook from snowflake.cli.api.project.schemas.updatable_model import ( IdentifierField, UpdatableModel, @@ -44,6 +45,10 @@ class Package(UpdatableModel): title="Distribution of the application package created by the Snowflake CLI", default="internal", ) + post_deploy: Optional[List[PostDeployHook]] = Field( + title="Actions that will be executed after the application package object is created/updated", + default=None, + ) @field_validator("scripts") @classmethod @@ -54,6 +59,16 @@ def validate_scripts(cls, input_list): ) return input_list + @model_validator(mode="after") + @classmethod + def validate_no_scripts_and_post_deploy(cls, value: Package): + if value.scripts and value.post_deploy: + raise ValueError( + "package.scripts and package.post_deploy fields cannot be used together. " + "We recommend using package.post_deploy for all post package deploy scripts" + ) + return value + class PackageV11(Package): # Templated defaults only supported in v1.1+ diff --git a/src/snowflake/cli/plugins/nativeapp/manager.py b/src/snowflake/cli/plugins/nativeapp/manager.py index 546e1c5062..7a759fe727 100644 --- a/src/snowflake/cli/plugins/nativeapp/manager.py +++ b/src/snowflake/cli/plugins/nativeapp/manager.py @@ -25,6 +25,7 @@ import jinja2 from click import ClickException +from snowflake.cli.api.cli_global_context import get_cli_context from snowflake.cli.api.console import cli_console as cc from snowflake.cli.api.errno import ( DOES_NOT_EXIST_OR_CANNOT_BE_PERFORMED, @@ -33,7 +34,7 @@ ) from snowflake.cli.api.exceptions import SnowflakeSQLExecutionError from snowflake.cli.api.project.schemas.native_app.application import ( - ApplicationPostDeployHook, + PostDeployHook, ) from snowflake.cli.api.project.schemas.native_app.native_app import NativeApp from snowflake.cli.api.project.schemas.native_app.path_mapping import PathMapping @@ -41,6 +42,9 @@ identifier_for_url, unquote_identifier, ) +from snowflake.cli.api.rendering.sql_templates import ( + get_sql_cli_jinja_env, +) from snowflake.cli.api.sql_execution import SqlExecutionMixin from snowflake.cli.plugins.connection.util import make_snowsight_url from snowflake.cli.plugins.nativeapp.artifacts import ( @@ -279,9 +283,13 @@ def app_role(self) -> str: return self.na_project.app_role @property - def app_post_deploy_hooks(self) -> Optional[List[ApplicationPostDeployHook]]: + def app_post_deploy_hooks(self) -> Optional[List[PostDeployHook]]: return self.na_project.app_post_deploy_hooks + @property + def package_post_deploy_hooks(self) -> Optional[List[PostDeployHook]]: + return self.na_project.package_post_deploy_hooks + @property def debug_mode(self) -> bool: return self.na_project.debug_mode @@ -603,6 +611,12 @@ def _apply_package_scripts(self) -> None: Assuming the application package exists and we are using the correct role, applies all package scripts in-order to the application package. """ + + if self.package_scripts: + cc.warning( + "WARNING: native_app.package.scripts is deprecated. Please migrate to using native_app.package.post_deploy." + ) + env = jinja2.Environment( loader=jinja2.loaders.FileSystemLoader(self.project_root), keep_trailing_newline=True, @@ -624,6 +638,67 @@ def _apply_package_scripts(self) -> None: err, role=self.package_role, warehouse=self.package_warehouse ) + def _execute_sql_script( + self, script_content: str, database_name: Optional[str] = None + ) -> None: + """ + Executing the provided SQL script content. + This assumes that a relevant warehouse is already active. + If database_name is passed in, it will be used first. + """ + try: + if database_name is not None: + self._execute_query(f"use database {database_name}") + + self._execute_queries(script_content) + except ProgrammingError as err: + generic_sql_error_handler(err) + + def _execute_post_deploy_hooks( + self, + post_deploy_hooks: Optional[List[PostDeployHook]], + deployed_object_type: str, + database_name: str, + ) -> None: + """ + Executes post-deploy hooks for the given object type. + While executing SQL post deploy hooks, it first switches to the database provided in the input. + All post deploy scripts templates will first be expanded using the global template context. + """ + if not post_deploy_hooks: + return + + with cc.phase(f"Executing {deployed_object_type} post-deploy actions"): + sql_scripts_paths = [] + for hook in post_deploy_hooks: + if hook.sql_script: + sql_scripts_paths.append(hook.sql_script) + else: + raise ValueError( + f"Unsupported {deployed_object_type} post-deploy hook type: {hook}" + ) + + env = get_sql_cli_jinja_env( + loader=jinja2.loaders.FileSystemLoader(self.project_root) + ) + scripts_content_list = self._expand_script_templates( + env, get_cli_context().template_context, sql_scripts_paths + ) + + for index, sql_script_path in enumerate(sql_scripts_paths): + cc.step(f"Executing SQL script: {sql_script_path}") + self._execute_sql_script(scripts_content_list[index], database_name) + + def execute_package_post_deploy_hooks(self) -> None: + self._execute_post_deploy_hooks( + self.package_post_deploy_hooks, "application package", self.package_name + ) + + def execute_app_post_deploy_hooks(self) -> None: + self._execute_post_deploy_hooks( + self.app_post_deploy_hooks, "application", self.app_name + ) + def deploy( self, bundle_map: BundleMap, @@ -655,6 +730,10 @@ def deploy( print_diff=print_diff, ) + # 4. Execute post-deploy hooks + with self.use_package_warehouse(): + self.execute_package_post_deploy_hooks() + if validate: self.validate(use_scratch_stage=False) diff --git a/src/snowflake/cli/plugins/nativeapp/project_model.py b/src/snowflake/cli/plugins/nativeapp/project_model.py index 7b90f0dcde..7696eb76f2 100644 --- a/src/snowflake/cli/plugins/nativeapp/project_model.py +++ b/src/snowflake/cli/plugins/nativeapp/project_model.py @@ -25,7 +25,7 @@ default_role, ) from snowflake.cli.api.project.schemas.native_app.application import ( - ApplicationPostDeployHook, + PostDeployHook, ) from snowflake.cli.api.project.schemas.native_app.native_app import NativeApp from snowflake.cli.api.project.schemas.native_app.path_mapping import PathMapping @@ -162,15 +162,25 @@ def app_role(self) -> str: return self._default_role @cached_property - def app_post_deploy_hooks(self) -> Optional[List[ApplicationPostDeployHook]]: + def app_post_deploy_hooks(self) -> Optional[List[PostDeployHook]]: """ - List of application post deploy hooks. + List of application instance post deploy hooks. """ if self.definition.application and self.definition.application.post_deploy: return self.definition.application.post_deploy else: return None + @cached_property + def package_post_deploy_hooks(self) -> Optional[List[PostDeployHook]]: + """ + List of application package post deploy hooks. + """ + if self.definition.package and self.definition.package.post_deploy: + return self.definition.package.post_deploy + else: + return None + @cached_property def _default_role(self) -> str: role = default_role() diff --git a/src/snowflake/cli/plugins/nativeapp/run_processor.py b/src/snowflake/cli/plugins/nativeapp/run_processor.py index 2bc0d01055..9c4e6817b7 100644 --- a/src/snowflake/cli/plugins/nativeapp/run_processor.py +++ b/src/snowflake/cli/plugins/nativeapp/run_processor.py @@ -18,7 +18,6 @@ from textwrap import dedent from typing import Optional -import jinja2 import typer from click import UsageError from snowflake.cli.api.cli_global_context import cli_context @@ -37,9 +36,6 @@ identifier_to_show_like_pattern, unquote_identifier, ) -from snowflake.cli.api.rendering.sql_templates import ( - get_sql_cli_jinja_env, -) from snowflake.cli.api.utils.cursor import find_all_rows from snowflake.cli.plugins.nativeapp.artifacts import BundleMap from snowflake.cli.plugins.nativeapp.constants import ( @@ -309,7 +305,7 @@ def create_or_upgrade_app( ) # hooks always executed after a create or upgrade - self._execute_post_deploy_hooks() + self.execute_app_post_deploy_hooks() return except ProgrammingError as err: @@ -356,7 +352,7 @@ def create_or_upgrade_app( ) # hooks always executed after a create or upgrade - self._execute_post_deploy_hooks() + self.execute_app_post_deploy_hooks() except ProgrammingError as err: generic_sql_error_handler(err) diff --git a/src/snowflake/cli/plugins/nativeapp/v2_conversions/v2_to_v1_decorator.py b/src/snowflake/cli/plugins/nativeapp/v2_conversions/v2_to_v1_decorator.py index 20f2ea9710..95352c9028 100644 --- a/src/snowflake/cli/plugins/nativeapp/v2_conversions/v2_to_v1_decorator.py +++ b/src/snowflake/cli/plugins/nativeapp/v2_conversions/v2_to_v1_decorator.py @@ -16,7 +16,7 @@ from functools import wraps from pathlib import Path -from typing import Any, Dict, List, Optional, Union +from typing import Any, Dict, Optional, Union from click import ClickException from snowflake.cli.api.cli_global_context import cli_context, cli_context_manager @@ -26,10 +26,6 @@ from snowflake.cli.api.project.schemas.entities.application_package_entity import ( ApplicationPackageEntity, ) -from snowflake.cli.api.project.schemas.native_app.application import ( - ApplicationPostDeployHook, - SqlScriptHookType, -) from snowflake.cli.api.project.schemas.native_app.path_mapping import PathMapping from snowflake.cli.api.project.schemas.project_definition import ( DefinitionV11, @@ -50,14 +46,6 @@ def _convert_v2_artifact_to_v1_dict( return str(v2_artifact) -def _convert_v2_post_deploy_hook_to_v1_scripts( - v2_post_deploy_hook: ApplicationPostDeployHook, -) -> List[str]: - if isinstance(v2_post_deploy_hook, SqlScriptHookType): - return v2_post_deploy_hook.sql_script - raise ValueError(f"Unsupported post deploy hook type: {v2_post_deploy_hook}") - - def _pdf_v2_to_v1(v2_definition: DefinitionV20) -> DefinitionV11: pdfv1: Dict[str, Any] = {"definition_version": "1.1", "native_app": {}} @@ -103,10 +91,16 @@ def _pdf_v2_to_v1(v2_definition: DefinitionV20) -> DefinitionV11: "distribution" ] = app_package_definition.distribution if app_package_definition.meta and app_package_definition.meta.post_deploy: - pdfv1["native_app"]["package"]["scripts"] = [ - _convert_v2_post_deploy_hook_to_v1_scripts(s) - for s in app_package_definition.meta.post_deploy - ] + pdfv1["native_app"]["package"][ + "post_deploy" + ] = app_package_definition.meta.post_deploy + if app_package_definition.meta: + if app_package_definition.meta.role: + pdfv1["native_app"]["package"]["role"] = app_package_definition.meta.role + if app_package_definition.meta.warehouse: + pdfv1["native_app"]["package"][ + "warehouse" + ] = app_package_definition.meta.warehouse # Application if app_definition: diff --git a/src/snowflake/cli/plugins/nativeapp/version/version_processor.py b/src/snowflake/cli/plugins/nativeapp/version/version_processor.py index 608ae2e955..b17e180a9a 100644 --- a/src/snowflake/cli/plugins/nativeapp/version/version_processor.py +++ b/src/snowflake/cli/plugins/nativeapp/version/version_processor.py @@ -215,6 +215,8 @@ def process( is_interactive=is_interactive, ) + # TODO: consider using self.deploy() instead + try: self.create_app_package() except ApplicationPackageAlreadyExistsError as e: @@ -234,6 +236,8 @@ def process( recursive=True, stage_fqn=self.stage_fqn, ) + with self.use_package_warehouse(): + self.execute_package_post_deploy_hooks() # Warn if the version exists in a release directive(s) existing_release_directives = ( diff --git a/tests/nativeapp/test_manager.py b/tests/nativeapp/test_manager.py index 08ea0ac2c6..83fe0774e7 100644 --- a/tests/nativeapp/test_manager.py +++ b/tests/nativeapp/test_manager.py @@ -1453,3 +1453,149 @@ def test_get_events_no_event_table(mock_account_event_table, temp_dir, mock_curs native_app_manager = _get_na_manager() with pytest.raises(NoEventTableForAccount): native_app_manager.get_events() + + +@mock.patch( + NATIVEAPP_MANAGER_ACCOUNT_EVENT_TABLE, + return_value="db.schema.event_table", + new_callable=mock.PropertyMock, +) +@mock.patch(NATIVEAPP_MANAGER_EXECUTE) +def test_stream_events(mock_execute, mock_account_event_table, temp_dir, mock_cursor): + create_named_file( + file_name="snowflake.yml", + dir_name=temp_dir, + contents=[mock_snowflake_yml_file], + ) + + events = [ + [dict(TIMESTAMP=datetime(2024, 1, 1), VALUE="test")] * 10, + [dict(TIMESTAMP=datetime(2024, 1, 2), VALUE="test")] * 10, + ] + side_effects, expected = mock_execute_helper( + [ + ( + mock_cursor(events[0], []), + mock.call( + dedent( + f"""\ + select * from ( + select timestamp, value::varchar value + from db.schema.event_table + where resource_attributes:"snow.database.name" = 'MYAPP' + + + + + order by timestamp desc + limit {len(events[0])} + ) order by timestamp asc + + """ + ), + cursor_class=DictCursor, + ), + ), + ( + mock_cursor(events[1], []), + mock.call( + dedent( + """\ + select * from ( + select timestamp, value::varchar value + from db.schema.event_table + where resource_attributes:"snow.database.name" = 'MYAPP' + and timestamp >= '2024-01-01 00:00:00' + + + + order by timestamp desc + + ) order by timestamp asc + + """ + ), + cursor_class=DictCursor, + ), + ), + ] + ) + mock_execute.side_effect = side_effects + + native_app_manager = _get_na_manager() + stream = native_app_manager.stream_events(interval_seconds=0, last=len(events[0])) + for i in range(len(events[0])): + # Exhaust the initial set of events + assert next(stream) == events[0][i] + assert mock_execute.call_count == 1 + + for i in range(len(events[1])): + # Then it'll make another query which returns the second set of events + assert next(stream) == events[1][i] + assert mock_execute.call_count == 2 + assert mock_execute.mock_calls == expected + + try: + stream.throw(KeyboardInterrupt) + except StopIteration: + pass + else: + pytest.fail("stream_events didn't end when receiving a KeyboardInterrupt") + + +@mock.patch.object(NativeAppManager, "validate") +@mock.patch.object(NativeAppManager, "execute_package_post_deploy_hooks") +@mock.patch.object(NativeAppManager, "sync_deploy_root_with_stage") +@mock.patch.object(NativeAppManager, "_apply_package_scripts") +@mock.patch.object(NativeAppManager, "create_app_package") +@mock.patch.object(NativeAppManager, "use_role") +@mock.patch.object(NativeAppManager, "use_package_warehouse") +def test_deploy_with_package_post_deploy_hook( + mock_use_package_warehouse, + mock_use_role, + mock_create_app_package, + mock_apply_package_scripts, + mock_sync_deploy_root_with_stage, + mock_execute_package_post_deploy_hooks, + mock_validate, + temp_dir, +): + # Setup + mock_diff_result = DiffResult(different=[StagePath("setup.sql")]) + mock_sync_deploy_root_with_stage.return_value = mock_diff_result + + current_working_directory = os.getcwd() + create_named_file( + file_name="snowflake.yml", + dir_name=current_working_directory, + contents=[mock_snowflake_yml_file], + ) + + # Create NativeAppManager instance + manager = _get_na_manager(temp_dir) + + mock_bundle_map = mock.Mock(spec=BundleMap) + # Test with default parameters + result = manager.deploy( + bundle_map=mock_bundle_map, + prune=True, + recursive=True, + ) + + # Assertions + mock_create_app_package.assert_called_once() + mock_use_package_warehouse.assert_called_once() + mock_use_role.assert_called_once_with(manager.package_role) + mock_apply_package_scripts.assert_called_once() + mock_sync_deploy_root_with_stage.assert_called_once_with( + bundle_map=mock_bundle_map, + role=manager.package_role, + prune=True, + recursive=True, + stage_fqn=manager.stage_fqn, + local_paths_to_sync=None, + print_diff=True, + ) + mock_execute_package_post_deploy_hooks.assert_called_once() + mock_validate.assert_called_once_with(use_scratch_stage=False) + assert result == mock_diff_result diff --git a/tests/nativeapp/test_post_deploy.py b/tests/nativeapp/test_post_deploy_for_app.py similarity index 92% rename from tests/nativeapp/test_post_deploy.py rename to tests/nativeapp/test_post_deploy_for_app.py index 024fcc9fd1..415ac486e6 100644 --- a/tests/nativeapp/test_post_deploy.py +++ b/tests/nativeapp/test_post_deploy_for_app.py @@ -21,22 +21,20 @@ from snowflake.cli.api.project.definition_manager import DefinitionManager from snowflake.cli.api.project.errors import SchemaValidationError from snowflake.cli.api.project.schemas.native_app.application import ( - ApplicationPostDeployHook, + PostDeployHook, ) from snowflake.cli.plugins.nativeapp.exceptions import MissingScriptError from snowflake.cli.plugins.nativeapp.run_processor import NativeAppRunProcessor from tests.nativeapp.patch_utils import mock_connection from tests.nativeapp.utils import ( + CLI_GLOBAL_TEMPLATE_CONTEXT, NATIVEAPP_MANAGER_EXECUTE, NATIVEAPP_MANAGER_EXECUTE_QUERIES, RUN_PROCESSOR_APP_POST_DEPLOY_HOOKS, ) from tests.testing_utils.fixtures import MockConnectionCtx -CLI_GLOBAL_TEMPLATE_CONTEXT = ( - "snowflake.cli.api.cli_global_context._CliGlobalContextAccess.template_context" -) MOCK_CONNECTION_DB = "tests.testing_utils.fixtures.MockConnectionCtx.database" MOCK_CONNECTION_WH = "tests.testing_utils.fixtures.MockConnectionCtx.warehouse" @@ -68,7 +66,7 @@ def test_sql_scripts( with project_directory("napp_post_deploy") as project_dir: processor = _get_run_processor(str(project_dir)) - processor._execute_post_deploy_hooks() # noqa SLF001 + processor.execute_app_post_deploy_hooks() assert mock_execute_query.mock_calls == [ mock.call("use database myapp_test_user"), @@ -115,7 +113,7 @@ def test_sql_scripts_with_no_warehouse_no_database( with project_directory("napp_post_deploy") as project_dir: processor = _get_run_processor(str(project_dir)) - processor._execute_post_deploy_hooks() # noqa SLF001 + processor.execute_app_post_deploy_hooks() # Verify no "use warehouse" # Verify "use database" applies to current application @@ -148,7 +146,9 @@ def test_missing_sql_script( processor = _get_run_processor(str(project_dir)) with pytest.raises(MissingScriptError) as err: - processor._execute_post_deploy_hooks() # noqa SLF001 + processor.execute_app_post_deploy_hooks() + + assert err.value.message == 'Script "scripts/missing.sql" does not exist' @mock.patch(RUN_PROCESSOR_APP_POST_DEPLOY_HOOKS, new_callable=mock.PropertyMock) @@ -167,7 +167,7 @@ def test_invalid_hook_type( processor = _get_run_processor(str(project_dir)) with pytest.raises(ValueError) as err: - processor._execute_post_deploy_hooks() # noqa SLF001 + processor.execute_app_post_deploy_hooks() assert "Unsupported application post-deploy hook type" in str(err) @@ -181,8 +181,8 @@ def test_invalid_hook_type( def test_post_deploy_hook_schema(args, expected_error): if expected_error: with pytest.raises(ValidationError) as err: - ApplicationPostDeployHook(**args) + PostDeployHook(**args) assert expected_error in str(SchemaValidationError(err.value)) else: - ApplicationPostDeployHook(**args) + PostDeployHook(**args) diff --git a/tests/nativeapp/test_post_deploy_for_package.py b/tests/nativeapp/test_post_deploy_for_package.py new file mode 100644 index 0000000000..c69f9b1c30 --- /dev/null +++ b/tests/nativeapp/test_post_deploy_for_package.py @@ -0,0 +1,175 @@ +# Copyright (c) 2024 Snowflake Inc. +# +# Licensed 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 os +from textwrap import dedent +from unittest import mock + +import pytest +from snowflake.cli._plugins.nativeapp.exceptions import MissingScriptError +from snowflake.cli._plugins.nativeapp.manager import NativeAppManager +from snowflake.cli.api.project.definition_manager import DefinitionManager +from snowflake.cli.api.project.errors import SchemaValidationError +from snowflake.connector import ProgrammingError + +from tests.nativeapp.patch_utils import mock_connection +from tests.nativeapp.utils import ( + CLI_GLOBAL_TEMPLATE_CONTEXT, + NATIVEAPP_MANAGER_EXECUTE, + NATIVEAPP_MANAGER_EXECUTE_QUERIES, +) +from tests.testing_utils.fixtures import MockConnectionCtx + + +@mock.patch(NATIVEAPP_MANAGER_EXECUTE) +@mock.patch(NATIVEAPP_MANAGER_EXECUTE_QUERIES) +@mock.patch(CLI_GLOBAL_TEMPLATE_CONTEXT, new_callable=mock.PropertyMock) +@mock.patch.dict(os.environ, {"USER": "test_user"}) +@mock_connection() +def test_package_post_deploy_scripts( + mock_conn, + mock_cli_ctx, + mock_execute_queries, + mock_execute_query, + project_directory, +): + mock_conn.return_value = MockConnectionCtx() + with project_directory("napp_post_deploy") as project_dir: + dm = DefinitionManager(project_dir) + manager = NativeAppManager( + project_definition=dm.project_definition.native_app, + project_root=dm.project_root, + ) + mock_cli_ctx.return_value = dm.template_context + + manager.execute_package_post_deploy_hooks() + + assert mock_execute_query.mock_calls == [ + mock.call("use database myapp_pkg_test_user"), + mock.call("use database myapp_pkg_test_user"), + ] + assert mock_execute_queries.mock_calls == [ + # Verify template variables were expanded correctly + mock.call( + dedent( + """\ + -- package post-deploy script (1/2) + + select myapp; + select package_bar; + """ + ) + ), + mock.call("-- package post-deploy script (2/2)\n"), + ] + + +@mock.patch(NATIVEAPP_MANAGER_EXECUTE) +@mock.patch(NATIVEAPP_MANAGER_EXECUTE_QUERIES) +@mock.patch(CLI_GLOBAL_TEMPLATE_CONTEXT, new_callable=mock.PropertyMock) +@mock.patch.dict(os.environ, {"USER": "test_user"}) +@mock_connection() +def test_package_post_deploy_scripts_with_no_scripts( + mock_conn, + mock_cli_ctx, + mock_execute_queries, + mock_execute_query, + project_directory, +): + mock_conn.return_value = MockConnectionCtx() + with project_directory("napp_project_1") as project_dir: + dm = DefinitionManager(project_dir) + manager = NativeAppManager( + project_definition=dm.project_definition.native_app, + project_root=dm.project_root, + ) + mock_cli_ctx.return_value = dm.template_context + + manager.execute_package_post_deploy_hooks() + + assert mock_execute_query.mock_calls == [] + assert mock_execute_queries.mock_calls == [] + + +@mock.patch(CLI_GLOBAL_TEMPLATE_CONTEXT, new_callable=mock.PropertyMock) +@mock.patch.dict(os.environ, {"USER": "test_user"}) +@mock_connection() +def test_package_post_deploy_scripts_with_non_existing_scripts( + mock_conn, + mock_cli_ctx, + project_directory, +): + mock_conn.return_value = MockConnectionCtx() + with project_directory("napp_post_deploy_missing_file") as project_dir: + dm = DefinitionManager(project_dir) + manager = NativeAppManager( + project_definition=dm.project_definition.native_app, + project_root=dm.project_root, + ) + mock_cli_ctx.return_value = dm.template_context + + with pytest.raises(MissingScriptError) as err: + manager.execute_package_post_deploy_hooks() + + assert ( + err.value.message + == 'Script "scripts/package_missing_script.sql" does not exist' + ) + + +@mock.patch(NATIVEAPP_MANAGER_EXECUTE) +@mock.patch(CLI_GLOBAL_TEMPLATE_CONTEXT, new_callable=mock.PropertyMock) +@mock.patch.dict(os.environ, {"USER": "test_user"}) +@mock_connection() +def test_package_post_deploy_scripts_with_sql_error( + mock_conn, + mock_cli_ctx, + mock_execute_query, + project_directory, +): + mock_conn.return_value = MockConnectionCtx() + with project_directory("napp_post_deploy") as project_dir: + dm = DefinitionManager(project_dir) + manager = NativeAppManager( + project_definition=dm.project_definition.native_app, + project_root=dm.project_root, + ) + mock_cli_ctx.return_value = dm.template_context + mock_execute_query.side_effect = ProgrammingError() + + with pytest.raises(ProgrammingError): + manager.execute_package_post_deploy_hooks() + + +@mock.patch.dict(os.environ, {"USER": "test_user"}) +def test_package_scripts_and_post_deploy_found( + project_directory, +): + with project_directory( + "napp_post_deploy", + {"native_app": {"package": {"scripts": ["scripts/package_post_deploy2.sql"]}}}, + ) as project_dir: + + with pytest.raises(SchemaValidationError) as err: + dm = DefinitionManager(project_dir) + NativeAppManager( + project_definition=dm.project_definition.native_app, + project_root=dm.project_root, + ) + + assert ( + "package.scripts and package.post_deploy fields cannot be used together" + in err.value.message + ) diff --git a/tests/nativeapp/test_v2_to_v1.py b/tests/nativeapp/test_v2_to_v1.py index 635a284c90..7d010058b5 100644 --- a/tests/nativeapp/test_v2_to_v1.py +++ b/tests/nativeapp/test_v2_to_v1.py @@ -125,9 +125,11 @@ "package": { "name": "pkg_name", "distribution": "external", - "scripts": [ - "scripts/script1.sql", - "scripts/script2.sql", + "role": "pkg_role", + "warehouse": "pkg_wh", + "post_deploy": [ + {"sql_script": "scripts/script1.sql"}, + {"sql_script": "scripts/script2.sql"}, ], }, "application": { diff --git a/tests/nativeapp/test_version_create_processor.py b/tests/nativeapp/test_version_create_processor.py index 5928c7ffd6..1a6ce175c6 100644 --- a/tests/nativeapp/test_version_create_processor.py +++ b/tests/nativeapp/test_version_create_processor.py @@ -39,7 +39,6 @@ from tests.nativeapp.utils import ( FIND_VERSION_FROM_MANIFEST, NATIVEAPP_MANAGER_EXECUTE, - TYPER_CONFIRM, VERSION_MODULE, mock_execute_helper, mock_snowflake_yml_file, @@ -326,25 +325,33 @@ def test_process_no_version_exists_throws_bad_option_exception_two( # Test version create when there are no release directives matching the version AND no version exists for app pkg @mock.patch(FIND_VERSION_FROM_MANIFEST, return_value=("manifest_version", None)) @mock.patch(f"{VERSION_MODULE}.check_index_changes_in_git_repo", return_value=None) -@mock.patch( - f"{VERSION_MODULE}.{CREATE_PROCESSOR}.create_app_package", return_value=None +@mock.patch.object( + NativeAppVersionCreateProcessor, "create_app_package", return_value=None ) @mock.patch(NATIVEAPP_MANAGER_EXECUTE) -@mock.patch( - f"{VERSION_MODULE}.{CREATE_PROCESSOR}._apply_package_scripts", return_value=None -) -@mock.patch( - f"{VERSION_MODULE}.{CREATE_PROCESSOR}.sync_deploy_root_with_stage", +@mock.patch.object(NativeAppVersionCreateProcessor, "use_package_warehouse") +@mock.patch.object( + NativeAppVersionCreateProcessor, + "execute_package_post_deploy_hooks", return_value=None, ) -@mock.patch( - f"{VERSION_MODULE}.{CREATE_PROCESSOR}.get_existing_release_directive_info_for_version", +@mock.patch.object( + NativeAppVersionCreateProcessor, "_apply_package_scripts", return_value=None +) +@mock.patch.object( + NativeAppVersionCreateProcessor, "sync_deploy_root_with_stage", return_value=None +) +@mock.patch.object( + NativeAppVersionCreateProcessor, + "get_existing_release_directive_info_for_version", return_value=None, ) -@mock.patch( - f"{VERSION_MODULE}.{CREATE_PROCESSOR}.get_existing_version_info", return_value=None +@mock.patch.object( + NativeAppVersionCreateProcessor, "get_existing_version_info", return_value=None +) +@mock.patch.object( + NativeAppVersionCreateProcessor, "add_new_version", return_value=None ) -@mock.patch(f"{VERSION_MODULE}.{CREATE_PROCESSOR}.add_new_version", return_value=None) @pytest.mark.parametrize( "policy_param", [allow_always_policy, ask_always_policy, deny_always_policy] ) @@ -354,6 +361,8 @@ def test_process_no_existing_release_directives_or_versions( mock_rd, mock_sync, mock_apply_package_scripts, + mock_execute_package_post_deploy_hooks, + mock_use_package_warehouse, mock_execute, mock_create_app_pkg, mock_check_git, @@ -398,6 +407,8 @@ def test_process_no_existing_release_directives_or_versions( mock_rd.assert_called_once() mock_create_app_pkg.assert_called_once() mock_apply_package_scripts.assert_called_once() + mock_use_package_warehouse.assert_called_once(), + mock_execute_package_post_deploy_hooks.assert_called_once(), mock_sync.assert_called_once() mock_existing_version_info.assert_called_once() mock_add_new_version.assert_called_once() @@ -408,25 +419,31 @@ def test_process_no_existing_release_directives_or_versions( "snowflake.cli.plugins.nativeapp.artifacts.find_version_info_in_manifest_file" ) @mock.patch(f"{VERSION_MODULE}.check_index_changes_in_git_repo", return_value=None) -@mock.patch( - f"{VERSION_MODULE}.{CREATE_PROCESSOR}.create_app_package", return_value=None +@mock.patch.object( + NativeAppVersionCreateProcessor, "create_app_package", return_value=None ) @mock.patch(NATIVEAPP_MANAGER_EXECUTE) -@mock.patch( - f"{VERSION_MODULE}.{CREATE_PROCESSOR}._apply_package_scripts", return_value=None -) -@mock.patch( - f"{VERSION_MODULE}.{CREATE_PROCESSOR}.sync_deploy_root_with_stage", +@mock.patch.object(NativeAppVersionCreateProcessor, "use_package_warehouse") +@mock.patch.object( + NativeAppVersionCreateProcessor, + "execute_package_post_deploy_hooks", return_value=None, ) -@mock.patch( - f"{VERSION_MODULE}.{CREATE_PROCESSOR}.get_existing_release_directive_info_for_version", +@mock.patch.object( + NativeAppVersionCreateProcessor, "_apply_package_scripts", return_value=None +) +@mock.patch.object( + NativeAppVersionCreateProcessor, "sync_deploy_root_with_stage", return_value=None +) +@mock.patch.object( + NativeAppVersionCreateProcessor, + "get_existing_release_directive_info_for_version", return_value=None, ) -@mock.patch(f"{VERSION_MODULE}.{CREATE_PROCESSOR}.get_existing_version_info") -@mock.patch(f"{VERSION_MODULE}.{CREATE_PROCESSOR}.add_new_version") -@mock.patch( - f"{VERSION_MODULE}.{CREATE_PROCESSOR}.add_new_patch_to_version", return_value=None +@mock.patch.object(NativeAppVersionCreateProcessor, "get_existing_version_info") +@mock.patch.object(NativeAppVersionCreateProcessor, "add_new_version") +@mock.patch.object( + NativeAppVersionCreateProcessor, "add_new_patch_to_version", return_value=None ) @pytest.mark.parametrize( "policy_param", [allow_always_policy, ask_always_policy, deny_always_policy] @@ -438,6 +455,8 @@ def test_process_no_existing_release_directives_w_existing_version( mock_rd, mock_sync, mock_apply_package_scripts, + mock_execute_package_post_deploy_hooks, + mock_use_package_warehouse, mock_execute, mock_create_app_pkg, mock_check_git, @@ -488,6 +507,8 @@ def test_process_no_existing_release_directives_w_existing_version( mock_rd.assert_called_once() mock_create_app_pkg.assert_called_once() mock_apply_package_scripts.assert_called_once() + mock_use_package_warehouse.assert_called_once(), + mock_execute_package_post_deploy_hooks.assert_called_once() mock_sync.assert_called_once() assert mock_existing_version_info.call_count == 2 mock_add_new_version.assert_not_called() @@ -498,25 +519,29 @@ def test_process_no_existing_release_directives_w_existing_version( # Test version create when there are release directives matching the version AND no version exists for app pkg AND --force is False AND interactive mode is False AND --interactive is True AND user does not want to proceed # Test version create when there are release directives matching the version AND no version exists for app pkg AND --force is False AND interactive mode is True AND user does not want to proceed @mock.patch(f"{VERSION_MODULE}.check_index_changes_in_git_repo", return_value=None) -@mock.patch( - f"{VERSION_MODULE}.{CREATE_PROCESSOR}.create_app_package", return_value=None +@mock.patch.object( + NativeAppVersionCreateProcessor, "create_app_package", return_value=None ) @mock.patch(NATIVEAPP_MANAGER_EXECUTE) -@mock.patch( - f"{VERSION_MODULE}.{CREATE_PROCESSOR}._apply_package_scripts", return_value=None -) -@mock.patch( - f"{VERSION_MODULE}.{CREATE_PROCESSOR}.sync_deploy_root_with_stage", +@mock.patch.object(NativeAppVersionCreateProcessor, "use_package_warehouse") +@mock.patch.object( + NativeAppVersionCreateProcessor, + "execute_package_post_deploy_hooks", return_value=None, ) -@mock.patch( - f"{VERSION_MODULE}.{CREATE_PROCESSOR}.get_existing_release_directive_info_for_version", - return_value=None, +@mock.patch.object( + NativeAppVersionCreateProcessor, "_apply_package_scripts", return_value=None ) -@mock.patch( - f"snowflake.cli.plugins.nativeapp.policy.{TYPER_CONFIRM}", return_value=False +@mock.patch.object( + NativeAppVersionCreateProcessor, "sync_deploy_root_with_stage", return_value=None ) -@mock.patch(f"{VERSION_MODULE}.{CREATE_PROCESSOR}.get_existing_version_info") +@mock.patch.object( + NativeAppVersionCreateProcessor, + "get_existing_release_directive_info_for_version", + return_value=None, +) +@mock.patch.object(typer, "confirm", return_value=False) +@mock.patch.object(NativeAppVersionCreateProcessor, "get_existing_version_info") @pytest.mark.parametrize( "policy_param, is_interactive_param, expected_code", [ @@ -531,6 +556,8 @@ def test_process_existing_release_directives_user_does_not_proceed( mock_rd, mock_sync, mock_apply_package_scripts, + mock_execute_package_post_deploy_hooks, + mock_use_package_warehouse, mock_execute, mock_create_app_pkg, mock_check_git, @@ -568,7 +595,7 @@ def test_process_existing_release_directives_user_does_not_proceed( processor = _get_version_create_processor() with pytest.raises(typer.Exit): - result = processor.process( + processor.process( bundle_map=mock_bundle_map, version=version, patch=12, @@ -576,12 +603,13 @@ def test_process_existing_release_directives_user_does_not_proceed( git_policy=allow_always_policy, is_interactive=is_interactive_param, ) - assert result.exit_code == expected_code assert mock_execute.mock_calls == expected mock_check_git.assert_called_once() mock_rd.assert_called_once() mock_create_app_pkg.assert_called_once() mock_apply_package_scripts.assert_called_once() + mock_use_package_warehouse.assert_called_once(), + mock_execute_package_post_deploy_hooks.assert_called_once(), mock_sync.assert_called_once() @@ -589,30 +617,34 @@ def test_process_existing_release_directives_user_does_not_proceed( # Test version create when there are release directives matching the version AND no version exists for app pkg AND --force is False AND interactive mode is False AND --interactive is True AND user wants to proceed # Test version create when there are release directives matching the version AND no version exists for app pkg AND --force is False AND interactive mode is True AND user wants to proceed @mock.patch(f"{VERSION_MODULE}.check_index_changes_in_git_repo", return_value=None) -@mock.patch( - f"{VERSION_MODULE}.{CREATE_PROCESSOR}.create_app_package", return_value=None +@mock.patch.object( + NativeAppVersionCreateProcessor, "create_app_package", return_value=None ) @mock.patch(NATIVEAPP_MANAGER_EXECUTE) -@mock.patch( - f"{VERSION_MODULE}.{CREATE_PROCESSOR}._apply_package_scripts", return_value=None -) -@mock.patch( - f"{VERSION_MODULE}.{CREATE_PROCESSOR}.sync_deploy_root_with_stage", +@mock.patch.object(NativeAppVersionCreateProcessor, "use_package_warehouse") +@mock.patch.object( + NativeAppVersionCreateProcessor, + "execute_package_post_deploy_hooks", return_value=None, ) -@mock.patch( - f"{VERSION_MODULE}.{CREATE_PROCESSOR}.get_existing_release_directive_info_for_version", - return_value=None, +@mock.patch.object( + NativeAppVersionCreateProcessor, "_apply_package_scripts", return_value=None ) -@mock.patch( - f"{VERSION_MODULE}.{CREATE_PROCESSOR}.get_existing_version_info", return_value=None +@mock.patch.object( + NativeAppVersionCreateProcessor, "sync_deploy_root_with_stage", return_value=None ) -@mock.patch( - f"{VERSION_MODULE}.{CREATE_PROCESSOR}.add_new_patch_to_version", return_value=None +@mock.patch.object( + NativeAppVersionCreateProcessor, + "get_existing_release_directive_info_for_version", + return_value=None, ) -@mock.patch( - f"snowflake.cli.plugins.nativeapp.policy.{TYPER_CONFIRM}", return_value=True +@mock.patch.object( + NativeAppVersionCreateProcessor, "get_existing_version_info", return_value=None +) +@mock.patch.object( + NativeAppVersionCreateProcessor, "add_new_patch_to_version", return_value=None ) +@mock.patch.object(typer, "confirm", return_value=True) @pytest.mark.parametrize( "policy_param, is_interactive_param", [ @@ -628,6 +660,8 @@ def test_process_existing_release_directives_w_existing_version_two( mock_rd, mock_sync, mock_apply_package_scripts, + mock_execute_package_post_deploy_hooks, + mock_use_package_warehouse, mock_execute, mock_create_app_pkg, mock_check_git, @@ -681,6 +715,8 @@ def test_process_existing_release_directives_w_existing_version_two( mock_rd.assert_called_once() mock_create_app_pkg.assert_called_once() mock_apply_package_scripts.assert_called_once() + mock_use_package_warehouse.assert_called_once(), + mock_execute_package_post_deploy_hooks.assert_called_once() mock_sync.assert_called_once() assert mock_existing_version_info.call_count == 2 mock_add_patch.assert_called_once() @@ -689,27 +725,36 @@ def test_process_existing_release_directives_w_existing_version_two( # Test version create when the app package doesn't have the magic CLI comment @mock.patch(FIND_VERSION_FROM_MANIFEST, return_value=("manifest_version", None)) @mock.patch(f"{VERSION_MODULE}.check_index_changes_in_git_repo", return_value=None) -@mock.patch( - f"{VERSION_MODULE}.{CREATE_PROCESSOR}.create_app_package", +@mock.patch.object( + NativeAppVersionCreateProcessor, + "create_app_package", side_effect=ApplicationPackageAlreadyExistsError(""), ) @mock.patch(NATIVEAPP_MANAGER_EXECUTE) -@mock.patch( - f"{VERSION_MODULE}.{CREATE_PROCESSOR}._apply_package_scripts", return_value=None -) -@mock.patch( - f"{VERSION_MODULE}.{CREATE_PROCESSOR}.sync_deploy_root_with_stage", +@mock.patch.object(NativeAppVersionCreateProcessor, "use_package_warehouse") +@mock.patch.object( + NativeAppVersionCreateProcessor, + "execute_package_post_deploy_hooks", return_value=None, ) -@mock.patch( - f"{VERSION_MODULE}.{CREATE_PROCESSOR}.get_existing_release_directive_info_for_version", +@mock.patch.object( + NativeAppVersionCreateProcessor, "_apply_package_scripts", return_value=None +) +@mock.patch.object( + NativeAppVersionCreateProcessor, "sync_deploy_root_with_stage", return_value=None +) +@mock.patch.object( + NativeAppVersionCreateProcessor, + "get_existing_release_directive_info_for_version", return_value=None, ) -@mock.patch( - f"{VERSION_MODULE}.{CREATE_PROCESSOR}.get_existing_version_info", return_value=None +@mock.patch.object( + NativeAppVersionCreateProcessor, "get_existing_version_info", return_value=None +) +@mock.patch.object( + NativeAppVersionCreateProcessor, "add_new_version", return_value=None ) -@mock.patch(f"{VERSION_MODULE}.{CREATE_PROCESSOR}.add_new_version", return_value=None) -@mock.patch(f"snowflake.cli.plugins.nativeapp.policy.{TYPER_CONFIRM}") +@mock.patch.object(typer, "confirm") @pytest.mark.parametrize( "policy_param", [allow_always_policy, ask_always_policy, deny_always_policy] ) @@ -721,6 +766,8 @@ def test_process_package_no_magic_comment( mock_rd, mock_sync, mock_apply_package_scripts, + mock_execute_package_post_deploy_hooks, + mock_use_package_warehouse, mock_execute, mock_create_app_pkg, mock_check_git, @@ -772,6 +819,8 @@ def test_process_package_no_magic_comment( mock_rd.assert_called_once() mock_create_app_pkg.assert_called_once() mock_apply_package_scripts.assert_called_once() + mock_execute_package_post_deploy_hooks.assert_called_once() + mock_use_package_warehouse.assert_called_once() mock_sync.assert_called_once() mock_existing_version_info.assert_called_once() mock_add_new_version.assert_called_once() diff --git a/tests/nativeapp/utils.py b/tests/nativeapp/utils.py index 2d7779d668..7e8e94be37 100644 --- a/tests/nativeapp/utils.py +++ b/tests/nativeapp/utils.py @@ -31,6 +31,10 @@ RUN_MODULE = "snowflake.cli.plugins.nativeapp.run_processor" VERSION_MODULE = "snowflake.cli.plugins.nativeapp.version.version_processor" +CLI_GLOBAL_TEMPLATE_CONTEXT = ( + "snowflake.cli.api.cli_global_context._CliGlobalContextAccess.template_context" +) + TEARDOWN_PROCESSOR = f"{TEARDOWN_MODULE}.NativeAppTeardownProcessor" NATIVEAPP_MANAGER = f"{NATIVEAPP_MODULE}.NativeAppManager" RUN_PROCESSOR = f"{RUN_MODULE}.NativeAppRunProcessor" diff --git a/tests/project/__snapshots__/test_config.ambr b/tests/project/__snapshots__/test_config.ambr index 36e892e977..e8a5534e3d 100644 --- a/tests/project/__snapshots__/test_config.ambr +++ b/tests/project/__snapshots__/test_config.ambr @@ -19,6 +19,7 @@ 'package': dict({ 'distribution': 'internal', 'name': None, + 'post_deploy': None, 'role': None, 'scripts': list([ 'package/001-shared.sql', @@ -53,6 +54,7 @@ 'package': dict({ 'distribution': 'external', 'name': None, + 'post_deploy': None, 'role': None, 'scripts': list([ 'package/001-shared.sql', @@ -136,6 +138,7 @@ 'package': dict({ 'distribution': 'internal', 'name': 'myapp_pkg_polly', + 'post_deploy': None, 'role': 'accountadmin', 'scripts': list([ '001-shared.sql', @@ -188,6 +191,7 @@ 'package': dict({ 'distribution': 'internal', 'name': 'myapp_pkg_polly', + 'post_deploy': None, 'role': 'accountadmin', 'scripts': list([ '001-shared.sql', diff --git a/tests/test_data/projects/integration_external_v2/package/001-shared.sql b/tests/test_data/projects/integration_external_v2/package/001-shared.sql index 5709c5d684..b6d7886daf 100644 --- a/tests/test_data/projects/integration_external_v2/package/001-shared.sql +++ b/tests/test_data/projects/integration_external_v2/package/001-shared.sql @@ -1,5 +1,5 @@ -- package script (1/2) -create schema if not exists {{ package_name }}.my_shared_content; -grant usage on schema {{ package_name }}.my_shared_content - to share in application package {{ package_name }}; +create schema if not exists &{ ctx.entities.pkg.name }.my_shared_content; +grant usage on schema &{ ctx.entities.pkg.name }.my_shared_content + to share in application package &{ ctx.entities.pkg.name }; diff --git a/tests/test_data/projects/integration_external_v2/package/002-shared.sql b/tests/test_data/projects/integration_external_v2/package/002-shared.sql index 6db8ddf20b..ac4eb9ae2d 100644 --- a/tests/test_data/projects/integration_external_v2/package/002-shared.sql +++ b/tests/test_data/projects/integration_external_v2/package/002-shared.sql @@ -1,12 +1,12 @@ -- package script (2/2) -create or replace table {{ package_name }}.my_shared_content.shared_table ( +create or replace table &{ ctx.entities.pkg.name }.my_shared_content.shared_table ( col1 number, col2 varchar ); -insert into {{ package_name }}.my_shared_content.shared_table (col1, col2) +insert into &{ ctx.entities.pkg.name }.my_shared_content.shared_table (col1, col2) values (1, 'hello'); -grant select on table {{ package_name }}.my_shared_content.shared_table - to share in application package {{ package_name }}; +grant select on table &{ ctx.entities.pkg.name }.my_shared_content.shared_table + to share in application package &{ ctx.entities.pkg.name }; diff --git a/tests/test_data/projects/integration_templated_v2/package/001-shared.sql b/tests/test_data/projects/integration_templated_v2/package/001-shared.sql new file mode 100644 index 0000000000..b6d7886daf --- /dev/null +++ b/tests/test_data/projects/integration_templated_v2/package/001-shared.sql @@ -0,0 +1,5 @@ +-- package script (1/2) + +create schema if not exists &{ ctx.entities.pkg.name }.my_shared_content; +grant usage on schema &{ ctx.entities.pkg.name }.my_shared_content + to share in application package &{ ctx.entities.pkg.name }; diff --git a/tests/test_data/projects/integration_templated_v2/package/002-shared.sql b/tests/test_data/projects/integration_templated_v2/package/002-shared.sql new file mode 100644 index 0000000000..ac4eb9ae2d --- /dev/null +++ b/tests/test_data/projects/integration_templated_v2/package/002-shared.sql @@ -0,0 +1,12 @@ +-- package script (2/2) + +create or replace table &{ ctx.entities.pkg.name }.my_shared_content.shared_table ( + col1 number, + col2 varchar +); + +insert into &{ ctx.entities.pkg.name }.my_shared_content.shared_table (col1, col2) + values (1, 'hello'); + +grant select on table &{ ctx.entities.pkg.name }.my_shared_content.shared_table + to share in application package &{ ctx.entities.pkg.name }; diff --git a/tests/test_data/projects/integration_v2/package/001-shared.sql b/tests/test_data/projects/integration_v2/package/001-shared.sql index 5709c5d684..b6d7886daf 100644 --- a/tests/test_data/projects/integration_v2/package/001-shared.sql +++ b/tests/test_data/projects/integration_v2/package/001-shared.sql @@ -1,5 +1,5 @@ -- package script (1/2) -create schema if not exists {{ package_name }}.my_shared_content; -grant usage on schema {{ package_name }}.my_shared_content - to share in application package {{ package_name }}; +create schema if not exists &{ ctx.entities.pkg.name }.my_shared_content; +grant usage on schema &{ ctx.entities.pkg.name }.my_shared_content + to share in application package &{ ctx.entities.pkg.name }; diff --git a/tests/test_data/projects/integration_v2/package/002-shared.sql b/tests/test_data/projects/integration_v2/package/002-shared.sql index 6db8ddf20b..ac4eb9ae2d 100644 --- a/tests/test_data/projects/integration_v2/package/002-shared.sql +++ b/tests/test_data/projects/integration_v2/package/002-shared.sql @@ -1,12 +1,12 @@ -- package script (2/2) -create or replace table {{ package_name }}.my_shared_content.shared_table ( +create or replace table &{ ctx.entities.pkg.name }.my_shared_content.shared_table ( col1 number, col2 varchar ); -insert into {{ package_name }}.my_shared_content.shared_table (col1, col2) +insert into &{ ctx.entities.pkg.name }.my_shared_content.shared_table (col1, col2) values (1, 'hello'); -grant select on table {{ package_name }}.my_shared_content.shared_table - to share in application package {{ package_name }}; +grant select on table &{ ctx.entities.pkg.name }.my_shared_content.shared_table + to share in application package &{ ctx.entities.pkg.name }; diff --git a/tests/test_data/projects/napp_post_deploy/scripts/post_deploy1.sql b/tests/test_data/projects/napp_post_deploy/scripts/app_post_deploy1.sql similarity index 100% rename from tests/test_data/projects/napp_post_deploy/scripts/post_deploy1.sql rename to tests/test_data/projects/napp_post_deploy/scripts/app_post_deploy1.sql diff --git a/tests/test_data/projects/napp_post_deploy/scripts/post_deploy2.sql b/tests/test_data/projects/napp_post_deploy/scripts/app_post_deploy2.sql similarity index 100% rename from tests/test_data/projects/napp_post_deploy/scripts/post_deploy2.sql rename to tests/test_data/projects/napp_post_deploy/scripts/app_post_deploy2.sql diff --git a/tests/test_data/projects/napp_post_deploy/scripts/package_post_deploy1.sql b/tests/test_data/projects/napp_post_deploy/scripts/package_post_deploy1.sql new file mode 100644 index 0000000000..535384debf --- /dev/null +++ b/tests/test_data/projects/napp_post_deploy/scripts/package_post_deploy1.sql @@ -0,0 +1,4 @@ +-- package post-deploy script (1/2) + +select &{ ctx.native_app.name }; +select &{ ctx.env.package_foo }; diff --git a/tests/test_data/projects/napp_post_deploy/scripts/package_post_deploy2.sql b/tests/test_data/projects/napp_post_deploy/scripts/package_post_deploy2.sql new file mode 100644 index 0000000000..a842f614d7 --- /dev/null +++ b/tests/test_data/projects/napp_post_deploy/scripts/package_post_deploy2.sql @@ -0,0 +1 @@ +-- package post-deploy script (2/2) diff --git a/tests/test_data/projects/napp_post_deploy/snowflake.yml b/tests/test_data/projects/napp_post_deploy/snowflake.yml index fe734b230c..82ef1a0405 100644 --- a/tests/test_data/projects/napp_post_deploy/snowflake.yml +++ b/tests/test_data/projects/napp_post_deploy/snowflake.yml @@ -8,8 +8,12 @@ native_app: application: post_deploy: - - sql_script: scripts/post_deploy1.sql - - sql_script: scripts/post_deploy2.sql - + - sql_script: scripts/app_post_deploy1.sql + - sql_script: scripts/app_post_deploy2.sql + package: + post_deploy: + - sql_script: scripts/package_post_deploy1.sql + - sql_script: scripts/package_post_deploy2.sql env: foo: bar + package_foo: package_bar diff --git a/tests/test_data/projects/napp_post_deploy_missing_file/snowflake.yml b/tests/test_data/projects/napp_post_deploy_missing_file/snowflake.yml index 4c6c069d4c..c5e1a608e2 100644 --- a/tests/test_data/projects/napp_post_deploy_missing_file/snowflake.yml +++ b/tests/test_data/projects/napp_post_deploy_missing_file/snowflake.yml @@ -9,3 +9,6 @@ native_app: application: post_deploy: - sql_script: scripts/missing.sql + package: + post_deploy: + - sql_script: scripts/package_missing_script.sql diff --git a/tests_integration/nativeapp/test_init_run.py b/tests_integration/nativeapp/test_init_run.py index 9f47f97633..8386b86c61 100644 --- a/tests_integration/nativeapp/test_init_run.py +++ b/tests_integration/nativeapp/test_init_run.py @@ -420,94 +420,6 @@ def test_nativeapp_init_from_repo_with_single_template( single_template_repo.close() -# Tests that application post-deploy scripts are executed by creating a post_deploy_log table and having each post-deploy script add a record to it -@pytest.mark.integration -@enable_definition_v2_feature_flag -@pytest.mark.parametrize("definition_version", ["v1", "v2"]) -@pytest.mark.parametrize("is_versioned", [True, False]) -@pytest.mark.parametrize("with_project_flag", [True, False]) -def test_nativeapp_app_post_deploy( - runner, - snowflake_session, - project_directory, - definition_version, - is_versioned, - with_project_flag, -): - version = "v1" - project_name = "myapp" - app_name = f"{project_name}_{USER_NAME}" - - with project_directory( - f"napp_application_post_deploy_{definition_version}" - ) as tmp_dir: - version_run_args = ["--version", version] if is_versioned else [] - project_args = ["--project", f"{tmp_dir}"] if with_project_flag else [] - - def run(): - """(maybe) create a version, then snow app run""" - if is_versioned: - result = runner.invoke_with_connection_json( - ["app", "version", "create", version] + project_args, - env=TEST_ENV, - ) - assert result.exit_code == 0 - - result = runner.invoke_with_connection_json( - ["app", "run"] + version_run_args + project_args, - env=TEST_ENV, - ) - assert result.exit_code == 0 - - if with_project_flag: - working_directory_changer = WorkingDirectoryChanger() - working_directory_changer.change_working_directory_to("app") - - try: - # First run, application is created (and maybe a version) - run() - - # Verify both scripts were executed - assert row_from_snowflake_session( - snowflake_session.execute_string( - f"select * from {app_name}.public.post_deploy_log", - ) - ) == [ - {"TEXT": "post-deploy-part-1"}, - {"TEXT": "post-deploy-part-2"}, - ] - - # Second run, application is upgraded - run() - - # Verify both scripts were executed - assert row_from_snowflake_session( - snowflake_session.execute_string( - f"select * from {app_name}.public.post_deploy_log", - ) - ) == [ - {"TEXT": "post-deploy-part-1"}, - {"TEXT": "post-deploy-part-2"}, - {"TEXT": "post-deploy-part-1"}, - {"TEXT": "post-deploy-part-2"}, - ] - - finally: - # need to drop the version before we can teardown - if is_versioned: - result = runner.invoke_with_connection_json( - ["app", "version", "drop", version, "--force"] + project_args, - env=TEST_ENV, - ) - assert result.exit_code == 0 - - result = runner.invoke_with_connection_json( - ["app", "teardown", "--force"] + project_args, - env=TEST_ENV, - ) - assert result.exit_code == 0 - - # Tests running an app whose package was dropped externally (requires dropping and recreating the app) @pytest.mark.integration @enable_definition_v2_feature_flag diff --git a/tests_integration/nativeapp/test_post_deploy.py b/tests_integration/nativeapp/test_post_deploy.py new file mode 100644 index 0000000000..18f38243c9 --- /dev/null +++ b/tests_integration/nativeapp/test_post_deploy.py @@ -0,0 +1,202 @@ +# Copyright (c) 2024 Snowflake Inc. +# +# Licensed 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. + +# Tests that application post-deploy scripts are executed by creating a post_deploy_log table and having each post-deploy script add a record to it +import uuid +import pytest + +from snowflake.cli.api.project.util import generate_user_env +from tests_integration.test_utils import ( + enable_definition_v2_feature_flag, + row_from_snowflake_session, +) +from tests_integration.testing_utils.working_directory_utils import ( + WorkingDirectoryChanger, +) + +USER_NAME = f"user_{uuid.uuid4().hex}" +TEST_ENV = generate_user_env(USER_NAME) + + +def run(runner, args): + result = runner.invoke_with_connection_json( + ["app", "run"] + args, + env=TEST_ENV, + ) + assert result.exit_code == 0 + + +def deploy(runner, args): + result = runner.invoke_with_connection_json( + ["app", "deploy"] + args, + env=TEST_ENV, + ) + assert result.exit_code == 0 + + +def teardown(runner, args): + result = runner.invoke_with_connection_json( + ["app", "teardown", "--force"] + args, + env=TEST_ENV, + ) + assert result.exit_code == 0 + + +def create_version(runner, version, args): + result = runner.invoke_with_connection_json( + ["app", "version", "create", version] + args, + env=TEST_ENV, + ) + assert result.exit_code == 0 + + +def drop_version(runner, version, args): + result = runner.invoke_with_connection_json( + ["app", "version", "drop", version, "--force"] + args, + env=TEST_ENV, + ) + assert result.exit_code == 0 + + +def verify_app_post_deploy_log(snowflake_session, app_name, expected_rows): + assert ( + row_from_snowflake_session( + snowflake_session.execute_string( + f"select * from {app_name}.app_schema.post_deploy_log", + ) + ) + == expected_rows + ) + + +def verify_pkg_post_deploy_log(snowflake_session, pkg_name, expected_rows): + assert ( + row_from_snowflake_session( + snowflake_session.execute_string( + f"select * from {pkg_name}.pkg_schema.post_deploy_log", + ) + ) + == expected_rows + ) + + +@pytest.mark.integration +@enable_definition_v2_feature_flag +@pytest.mark.parametrize( + "test_project", + ["napp_application_post_deploy_v1", "napp_application_post_deploy_v2"], +) +@pytest.mark.parametrize("is_versioned", [True, False]) +@pytest.mark.parametrize("with_project_flag", [True, False]) +def test_nativeapp_post_deploy( + runner, + snowflake_session, + project_directory, + test_project, + is_versioned, + with_project_flag, +): + version = "v1" + project_name = "myapp" + app_name = f"{project_name}_{USER_NAME}" + pkg_name = f"{project_name}_pkg_{USER_NAME}" + + with project_directory(test_project) as tmp_dir: + project_args = ["--project", f"{tmp_dir}"] if with_project_flag else [] + version_run_args = ["--version", version] if is_versioned else [] + + if with_project_flag: + working_directory_changer = WorkingDirectoryChanger() + working_directory_changer.change_working_directory_to("app") + + try: + # first run, application is created + if is_versioned: + create_version(runner, version, project_args) + run(runner, project_args + version_run_args) + + verify_app_post_deploy_log( + snowflake_session, + app_name, + [ + {"TEXT": "app-post-deploy-part-1"}, + {"TEXT": "app-post-deploy-part-2"}, + ], + ) + + verify_pkg_post_deploy_log( + snowflake_session, + pkg_name, + [ + {"TEXT": "package-post-deploy-part-1"}, + {"TEXT": "package-post-deploy-part-2"}, + ], + ) + + # Second run, application is upgraded + if is_versioned: + create_version(runner, version, project_args) + run(runner, project_args + version_run_args) + + verify_app_post_deploy_log( + snowflake_session, + app_name, + [ + {"TEXT": "app-post-deploy-part-1"}, + {"TEXT": "app-post-deploy-part-2"}, + {"TEXT": "app-post-deploy-part-1"}, + {"TEXT": "app-post-deploy-part-2"}, + ], + ) + verify_pkg_post_deploy_log( + snowflake_session, + pkg_name, + [ + {"TEXT": "package-post-deploy-part-1"}, + {"TEXT": "package-post-deploy-part-2"}, + {"TEXT": "package-post-deploy-part-1"}, + {"TEXT": "package-post-deploy-part-2"}, + ], + ) + + deploy(runner, project_args) + + verify_app_post_deploy_log( + snowflake_session, + app_name, + [ + {"TEXT": "app-post-deploy-part-1"}, + {"TEXT": "app-post-deploy-part-2"}, + {"TEXT": "app-post-deploy-part-1"}, + {"TEXT": "app-post-deploy-part-2"}, + ], + ) + verify_pkg_post_deploy_log( + snowflake_session, + pkg_name, + [ + {"TEXT": "package-post-deploy-part-1"}, + {"TEXT": "package-post-deploy-part-2"}, + {"TEXT": "package-post-deploy-part-1"}, + {"TEXT": "package-post-deploy-part-2"}, + {"TEXT": "package-post-deploy-part-1"}, + {"TEXT": "package-post-deploy-part-2"}, + ], + ) + + finally: + if is_versioned: + # need to drop the version before we can teardown + drop_version(runner, version, project_args) + teardown(runner, project_args) diff --git a/tests_integration/test_data/projects/napp_application_post_deploy_v1/scripts/post_deploy1.sql b/tests_integration/test_data/projects/napp_application_post_deploy_v1/scripts/app_post_deploy1.sql similarity index 66% rename from tests_integration/test_data/projects/napp_application_post_deploy_v1/scripts/post_deploy1.sql rename to tests_integration/test_data/projects/napp_application_post_deploy_v1/scripts/app_post_deploy1.sql index 697dbc16e8..6a6dbd5801 100644 --- a/tests_integration/test_data/projects/napp_application_post_deploy_v1/scripts/post_deploy1.sql +++ b/tests_integration/test_data/projects/napp_application_post_deploy_v1/scripts/app_post_deploy1.sql @@ -2,4 +2,4 @@ CREATE SCHEMA IF NOT EXISTS &{ ctx.env.schema }; CREATE TABLE IF NOT EXISTS &{ ctx.env.schema }.post_deploy_log (text VARCHAR); -INSERT INTO &{ ctx.env.schema }.post_deploy_log VALUES('post-deploy-part-1'); +INSERT INTO &{ ctx.env.schema }.post_deploy_log VALUES('app-post-deploy-part-1'); diff --git a/tests_integration/test_data/projects/napp_application_post_deploy_v1/scripts/app_post_deploy2.sql b/tests_integration/test_data/projects/napp_application_post_deploy_v1/scripts/app_post_deploy2.sql new file mode 100644 index 0000000000..9604936c49 --- /dev/null +++ b/tests_integration/test_data/projects/napp_application_post_deploy_v1/scripts/app_post_deploy2.sql @@ -0,0 +1,3 @@ +-- app post-deploy script (2/2) + +INSERT INTO &{ ctx.env.schema }.post_deploy_log VALUES('app-post-deploy-part-2'); diff --git a/tests_integration/test_data/projects/napp_application_post_deploy_v1/scripts/package_post_deploy1.sql b/tests_integration/test_data/projects/napp_application_post_deploy_v1/scripts/package_post_deploy1.sql new file mode 100644 index 0000000000..70ae384f9a --- /dev/null +++ b/tests_integration/test_data/projects/napp_application_post_deploy_v1/scripts/package_post_deploy1.sql @@ -0,0 +1,5 @@ +-- app post-deploy script (1/2) + +CREATE SCHEMA IF NOT EXISTS &{ ctx.env.pkg_schema }; +CREATE TABLE IF NOT EXISTS &{ ctx.env.pkg_schema }.post_deploy_log (text VARCHAR); +INSERT INTO &{ ctx.env.pkg_schema }.post_deploy_log VALUES('package-post-deploy-part-1'); diff --git a/tests_integration/test_data/projects/napp_application_post_deploy_v1/scripts/package_post_deploy2.sql b/tests_integration/test_data/projects/napp_application_post_deploy_v1/scripts/package_post_deploy2.sql new file mode 100644 index 0000000000..b6bec6fb25 --- /dev/null +++ b/tests_integration/test_data/projects/napp_application_post_deploy_v1/scripts/package_post_deploy2.sql @@ -0,0 +1,3 @@ +-- app post-deploy script (2/2) + +INSERT INTO &{ ctx.env.pkg_schema }.post_deploy_log VALUES('package-post-deploy-part-2'); diff --git a/tests_integration/test_data/projects/napp_application_post_deploy_v1/scripts/post_deploy2.sql b/tests_integration/test_data/projects/napp_application_post_deploy_v1/scripts/post_deploy2.sql deleted file mode 100644 index dfccefe9ee..0000000000 --- a/tests_integration/test_data/projects/napp_application_post_deploy_v1/scripts/post_deploy2.sql +++ /dev/null @@ -1,3 +0,0 @@ --- app post-deploy script (2/2) - -INSERT INTO &{ ctx.env.schema }.post_deploy_log VALUES('post-deploy-part-2'); diff --git a/tests_integration/test_data/projects/napp_application_post_deploy_v1/snowflake.yml b/tests_integration/test_data/projects/napp_application_post_deploy_v1/snowflake.yml index 18595ca446..ffcf423ddc 100644 --- a/tests_integration/test_data/projects/napp_application_post_deploy_v1/snowflake.yml +++ b/tests_integration/test_data/projects/napp_application_post_deploy_v1/snowflake.yml @@ -8,8 +8,13 @@ native_app: application: post_deploy: - - sql_script: scripts/post_deploy1.sql - - sql_script: scripts/post_deploy2.sql + - sql_script: scripts/app_post_deploy1.sql + - sql_script: scripts/app_post_deploy2.sql + package: + post_deploy: + - sql_script: scripts/package_post_deploy1.sql + - sql_script: scripts/package_post_deploy2.sql env: - schema: public + schema: app_schema + pkg_schema: pkg_schema diff --git a/tests_integration/test_data/projects/napp_application_post_deploy_v2/scripts/post_deploy1.sql b/tests_integration/test_data/projects/napp_application_post_deploy_v2/scripts/app_post_deploy1.sql similarity index 66% rename from tests_integration/test_data/projects/napp_application_post_deploy_v2/scripts/post_deploy1.sql rename to tests_integration/test_data/projects/napp_application_post_deploy_v2/scripts/app_post_deploy1.sql index 697dbc16e8..6a6dbd5801 100644 --- a/tests_integration/test_data/projects/napp_application_post_deploy_v2/scripts/post_deploy1.sql +++ b/tests_integration/test_data/projects/napp_application_post_deploy_v2/scripts/app_post_deploy1.sql @@ -2,4 +2,4 @@ CREATE SCHEMA IF NOT EXISTS &{ ctx.env.schema }; CREATE TABLE IF NOT EXISTS &{ ctx.env.schema }.post_deploy_log (text VARCHAR); -INSERT INTO &{ ctx.env.schema }.post_deploy_log VALUES('post-deploy-part-1'); +INSERT INTO &{ ctx.env.schema }.post_deploy_log VALUES('app-post-deploy-part-1'); diff --git a/tests_integration/test_data/projects/napp_application_post_deploy_v2/scripts/app_post_deploy2.sql b/tests_integration/test_data/projects/napp_application_post_deploy_v2/scripts/app_post_deploy2.sql new file mode 100644 index 0000000000..9604936c49 --- /dev/null +++ b/tests_integration/test_data/projects/napp_application_post_deploy_v2/scripts/app_post_deploy2.sql @@ -0,0 +1,3 @@ +-- app post-deploy script (2/2) + +INSERT INTO &{ ctx.env.schema }.post_deploy_log VALUES('app-post-deploy-part-2'); diff --git a/tests_integration/test_data/projects/napp_application_post_deploy_v2/scripts/package_post_deploy1.sql b/tests_integration/test_data/projects/napp_application_post_deploy_v2/scripts/package_post_deploy1.sql new file mode 100644 index 0000000000..70ae384f9a --- /dev/null +++ b/tests_integration/test_data/projects/napp_application_post_deploy_v2/scripts/package_post_deploy1.sql @@ -0,0 +1,5 @@ +-- app post-deploy script (1/2) + +CREATE SCHEMA IF NOT EXISTS &{ ctx.env.pkg_schema }; +CREATE TABLE IF NOT EXISTS &{ ctx.env.pkg_schema }.post_deploy_log (text VARCHAR); +INSERT INTO &{ ctx.env.pkg_schema }.post_deploy_log VALUES('package-post-deploy-part-1'); diff --git a/tests_integration/test_data/projects/napp_application_post_deploy_v2/scripts/package_post_deploy2.sql b/tests_integration/test_data/projects/napp_application_post_deploy_v2/scripts/package_post_deploy2.sql new file mode 100644 index 0000000000..b6bec6fb25 --- /dev/null +++ b/tests_integration/test_data/projects/napp_application_post_deploy_v2/scripts/package_post_deploy2.sql @@ -0,0 +1,3 @@ +-- app post-deploy script (2/2) + +INSERT INTO &{ ctx.env.pkg_schema }.post_deploy_log VALUES('package-post-deploy-part-2'); diff --git a/tests_integration/test_data/projects/napp_application_post_deploy_v2/scripts/post_deploy2.sql b/tests_integration/test_data/projects/napp_application_post_deploy_v2/scripts/post_deploy2.sql deleted file mode 100644 index dfccefe9ee..0000000000 --- a/tests_integration/test_data/projects/napp_application_post_deploy_v2/scripts/post_deploy2.sql +++ /dev/null @@ -1,3 +0,0 @@ --- app post-deploy script (2/2) - -INSERT INTO &{ ctx.env.schema }.post_deploy_log VALUES('post-deploy-part-2'); diff --git a/tests_integration/test_data/projects/napp_application_post_deploy_v2/snowflake.yml b/tests_integration/test_data/projects/napp_application_post_deploy_v2/snowflake.yml index 9a81c8a0f2..a50e734f77 100644 --- a/tests_integration/test_data/projects/napp_application_post_deploy_v2/snowflake.yml +++ b/tests_integration/test_data/projects/napp_application_post_deploy_v2/snowflake.yml @@ -8,6 +8,10 @@ entities: - src: app/* dest: ./ manifest: app/manifest.yml + meta: + post_deploy: + - sql_script: scripts/package_post_deploy1.sql + - sql_script: scripts/package_post_deploy2.sql app: type: application name: myapp_<% ctx.env.USER %> @@ -15,7 +19,8 @@ entities: target: pkg meta: post_deploy: - - sql_script: scripts/post_deploy1.sql - - sql_script: scripts/post_deploy2.sql + - sql_script: scripts/app_post_deploy1.sql + - sql_script: scripts/app_post_deploy2.sql env: - schema: public + schema: app_schema + pkg_schema: pkg_schema