Skip to content

Commit

Permalink
Append SNOWFLAKE_CLI_RESOURCE_SUFFIX value to app and package name
Browse files Browse the repository at this point in the history
  • Loading branch information
sfc-gh-fcampbell committed Aug 13, 2024
1 parent ffd30d4 commit 4a0c31a
Show file tree
Hide file tree
Showing 5 changed files with 237 additions and 13 deletions.
1 change: 1 addition & 0 deletions RELEASE-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
* Currently only supports SQL scripts: `post_deploy: [{sql_script: script.sql}]`
* Added `snow spcs service execute-job` command, which supports creating and executing a job service in the current schema.
* Added `snow app events` command to fetch logs and traces from local and customer app installations
* Added support for `SNOWFLAKE_CLI_RESOURCE_SUFFIX` environment variable to be used as a suffix for Native App package and app names

## Fixes and improvements
* Fixed problem with whitespaces in `snow connection add` command
Expand Down
25 changes: 18 additions & 7 deletions src/snowflake/cli/_plugins/nativeapp/project_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,18 @@
default_app_package,
default_application,
default_role,
resource_suffix,
)
from snowflake.cli.api.project.schemas.native_app.application import (
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
from snowflake.cli.api.project.util import extract_schema, to_identifier
from snowflake.cli.api.project.util import (
append_to_identifier,
extract_schema,
to_identifier,
)
from snowflake.connector import DictCursor


Expand Down Expand Up @@ -129,12 +134,15 @@ def project_identifier(self) -> str:
# sometimes strip out double quotes, so we try to get them back here.
return to_identifier(self.definition.name)

@cached_property
@property
def package_name(self) -> str:
suffix = resource_suffix()
if self.definition.package and self.definition.package.name:
return to_identifier(self.definition.package.name)
return append_to_identifier(
to_identifier(self.definition.package.name), suffix
)
else:
return to_identifier(default_app_package(self.project_identifier))
return to_identifier(default_app_package(self.project_identifier, suffix))

@cached_property
def package_role(self) -> str:
Expand All @@ -150,12 +158,15 @@ def package_distribution(self) -> str:
else:
return "internal"

@cached_property
@property
def app_name(self) -> str:
suffix = resource_suffix()
if self.definition.application and self.definition.application.name:
return to_identifier(self.definition.application.name)
return append_to_identifier(
to_identifier(self.definition.application.name), suffix
)
else:
return to_identifier(default_application(self.project_identifier))
return to_identifier(default_application(self.project_identifier, suffix))

@cached_property
def app_role(self) -> str:
Expand Down
18 changes: 12 additions & 6 deletions src/snowflake/cli/api/project/definition.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

from __future__ import annotations

import os
from pathlib import Path
from typing import List, Optional

Expand Down Expand Up @@ -102,16 +103,21 @@ def generate_local_override_yml(
return project.update_from_dict(local)


def default_app_package(project_name: str):
user = clean_identifier(get_env_username() or DEFAULT_USERNAME)
return append_to_identifier(to_identifier(project_name), f"_pkg_{user}")
def resource_suffix() -> str:
"""A suffix that should be added to account-level resources."""
return os.environ.get("SNOWFLAKE_CLI_RESOURCE_SUFFIX", "")


def default_app_package(project_name: str, suffix: str):
suffix = suffix or clean_identifier(get_env_username() or DEFAULT_USERNAME)
return append_to_identifier(to_identifier(project_name), f"_pkg_{suffix}")


def default_role():
conn = get_cli_context().connection
return conn.role


def default_application(project_name: str):
user = clean_identifier(get_env_username() or DEFAULT_USERNAME)
return append_to_identifier(to_identifier(project_name), f"_{user}")
def default_application(project_name: str, suffix: str):
suffix = suffix or clean_identifier(get_env_username() or DEFAULT_USERNAME)
return append_to_identifier(to_identifier(project_name), f"_{suffix}")
94 changes: 94 additions & 0 deletions tests_integration/nativeapp/test_deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,100 @@ def test_nativeapp_deploy(
assert result.exit_code == 0


@pytest.mark.integration
@enable_definition_v2_feature_flag
@pytest.mark.parametrize("test_project", ["napp_init_v1", "napp_init_v2"])
def test_nativeapp_deploy_with_resource_suffix(
test_project,
project_directory,
runner,
snowflake_session,
print_paths_as_posix,
):
project_name = "myapp"
suffix = "_some_suffix"
test_env_with_suffix = TEST_ENV | dict(SNOWFLAKE_CLI_RESOURCE_SUFFIX=suffix)
with project_directory(test_project):
result = runner.invoke_with_connection(
["app", "deploy"],
env=test_env_with_suffix,
)
assert result.exit_code == 0

try:
# package exist
package_name = f"{project_name}_pkg_{USER_NAME}{suffix}".upper()
assert contains_row_with(
row_from_snowflake_session(
snowflake_session.execute_string(
f"show application packages like '{package_name}'",
)
),
dict(name=package_name),
)

# make sure we always delete the app
result = runner.invoke_with_connection_json(
["app", "teardown"],
env=test_env_with_suffix,
)
assert result.exit_code == 0
finally:
# teardown is idempotent, so we can execute it again with no ill effects
result = runner.invoke_with_connection_json(
["app", "teardown", "--force"],
env=test_env_with_suffix,
)
assert result.exit_code == 0


@pytest.mark.integration
@enable_definition_v2_feature_flag
@pytest.mark.parametrize("test_project", ["napp_init_v1", "napp_init_v2"])
def test_nativeapp_deploy_with_resource_suffix_quoted(
test_project,
project_directory,
runner,
snowflake_session,
print_paths_as_posix,
):
project_name = "myapp"
suffix = "_must.be.quoted!!!"
test_env_with_quoted_suffix = TEST_ENV | dict(SNOWFLAKE_CLI_RESOURCE_SUFFIX=suffix)
with project_directory(test_project):
result = runner.invoke_with_connection(
["app", "deploy"],
env=test_env_with_quoted_suffix,
)
assert result.exit_code == 0

try:
# package exist
package_name = f"{project_name}_pkg_{USER_NAME}{suffix}"
assert contains_row_with(
row_from_snowflake_session(
snowflake_session.execute_string(
f"show application packages like '{package_name}'",
)
),
dict(name=package_name),
)

# make sure we always delete the app
result = runner.invoke_with_connection_json(
["app", "teardown"],
env=test_env_with_quoted_suffix,
)
assert result.exit_code == 0
finally:
# teardown is idempotent, so we can execute it again with no ill effects
result = runner.invoke_with_connection_json(
["app", "teardown", "--force"],
env=test_env_with_quoted_suffix,
)
assert result.exit_code == 0


@pytest.mark.integration
@enable_definition_v2_feature_flag
@pytest.mark.parametrize(
Expand Down
112 changes: 112 additions & 0 deletions tests_integration/nativeapp/test_init_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,118 @@ def test_nativeapp_init_run_without_modifications(
assert result.exit_code == 0


@pytest.mark.integration
@enable_definition_v2_feature_flag
@pytest.mark.parametrize("test_project", ["napp_init_v1", "napp_init_v2"])
def test_nativeapp_init_run_with_resource_suffix(
test_project,
project_directory,
runner,
snowflake_session,
):
project_name = "myapp"
suffix = "_some_suffix"
test_env_with_suffix = TEST_ENV | dict(SNOWFLAKE_CLI_RESOURCE_SUFFIX=suffix)
with project_directory(test_project):
result = runner.invoke_with_connection_json(
["app", "run"],
env=test_env_with_suffix,
)
assert result.exit_code == 0

try:
# app + package exist
package_name = f"{project_name}_pkg_{USER_NAME}{suffix}".upper()
app_name = f"{project_name}_{USER_NAME}{suffix}".upper()
assert contains_row_with(
row_from_snowflake_session(
snowflake_session.execute_string(
f"show application packages like '{package_name}'",
)
),
dict(name=package_name),
)
assert contains_row_with(
row_from_snowflake_session(
snowflake_session.execute_string(
f"show applications like '{app_name}'",
)
),
dict(name=app_name),
)

# make sure we always delete the app
result = runner.invoke_with_connection_json(
["app", "teardown"],
env=test_env_with_suffix,
)
assert result.exit_code == 0

finally:
# teardown is idempotent, so we can execute it again with no ill effects
result = runner.invoke_with_connection_json(
["app", "teardown", "--force"],
env=test_env_with_suffix,
)
assert result.exit_code == 0


@pytest.mark.integration
@enable_definition_v2_feature_flag
@pytest.mark.parametrize("test_project", ["napp_init_v1", "napp_init_v2"])
def test_nativeapp_init_run_with_resource_suffix_quoted(
test_project,
project_directory,
runner,
snowflake_session,
):
project_name = "myapp"
suffix = "_must.be.quoted!!!"
test_env_with_quoted_suffix = TEST_ENV | dict(SNOWFLAKE_CLI_RESOURCE_SUFFIX=suffix)
with project_directory(test_project):
result = runner.invoke_with_connection_json(
["app", "run"],
env=test_env_with_quoted_suffix,
)
assert result.exit_code == 0

try:
# app + package exist
package_name = f"{project_name}_pkg_{USER_NAME}{suffix}"
app_name = f"{project_name}_{USER_NAME}{suffix}"
assert contains_row_with(
row_from_snowflake_session(
snowflake_session.execute_string(
f"show application packages like '{package_name}'",
)
),
dict(name=package_name),
)
assert contains_row_with(
row_from_snowflake_session(
snowflake_session.execute_string(
f"show applications like '{app_name}'",
)
),
dict(name=app_name),
)

# make sure we always delete the app
result = runner.invoke_with_connection_json(
["app", "teardown"],
env=test_env_with_quoted_suffix,
)
assert result.exit_code == 0

finally:
# teardown is idempotent, so we can execute it again with no ill effects
result = runner.invoke_with_connection_json(
["app", "teardown", "--force"],
env=test_env_with_quoted_suffix,
)
assert result.exit_code == 0


# Tests a simple flow of an existing project, but executing snow app run and teardown, all with distribution=internal
@pytest.mark.integration
@enable_definition_v2_feature_flag
Expand Down

0 comments on commit 4a0c31a

Please sign in to comment.