Skip to content

Commit

Permalink
Add support for snow app run from release channel
Browse files Browse the repository at this point in the history
  • Loading branch information
sfc-gh-melnacouzi committed Dec 12, 2024
1 parent 5a3e36e commit 659fd6c
Show file tree
Hide file tree
Showing 10 changed files with 889 additions and 26 deletions.
1 change: 1 addition & 0 deletions RELEASE-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
* `snow app release-directive unset`
* Add support for release channels feature in native app version creation/drop.
* `snow app version create` now returns version, patch, and label in JSON format.
* Add ability to specify release channel when creating application instance from release directive: `snow app run --from-release-directive --channel=<channel>`

## Fixes and improvements
* Fixed crashes with older x86_64 Intel CPUs.
Expand Down
7 changes: 7 additions & 0 deletions src/snowflake/cli/_plugins/nativeapp/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,12 @@ def app_run(
The command fails if no release directive exists for your Snowflake account for a given application package, which is determined from the project definition file. Default: unset.""",
is_flag=True,
),
channel: str = typer.Option(
None,
show_default=False,
help=f"""The name of the release channel to use when creating or upgrading an application instance from a release directive.
Requires the `--from-release-directive` flag to be set. If unset, the default channel will be used.""",
),
interactive: bool = InteractiveOption,
force: Optional[bool] = ForceOption,
validate: bool = ValidateOption,
Expand Down Expand Up @@ -179,6 +185,7 @@ def app_run(
paths=[],
interactive=interactive,
force=force,
release_channel=channel,
)
app = ws.get_entity(app_id)
return MessageResult(
Expand Down
46 changes: 46 additions & 0 deletions src/snowflake/cli/_plugins/nativeapp/entities/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from snowflake.cli._plugins.nativeapp.constants import (
ALLOWED_SPECIAL_COMMENTS,
COMMENT_COL,
DEFAULT_CHANNEL,
OWNER_COL,
)
from snowflake.cli._plugins.nativeapp.entities.application_package import (
Expand Down Expand Up @@ -85,6 +86,8 @@
append_test_resource_suffix,
extract_schema,
identifier_for_url,
identifier_in_list,
same_identifiers,
to_identifier,
unquote_identifier,
)
Expand Down Expand Up @@ -329,6 +332,7 @@ def action_deploy(
prune: bool,
recursive: bool,
paths: List[Path],
release_channel: Optional[str] = None,
validate: bool = ValidateOption,
stage_fqn: Optional[str] = None,
interactive: bool = InteractiveOption,
Expand Down Expand Up @@ -356,15 +360,25 @@ def action_deploy(

# same-account release directive
if from_release_directive:
release_channel = _get_verified_release_channel(
package_entity, release_channel
)

self.create_or_upgrade_app(
package=package_entity,
stage_fqn=stage_fqn,
install_method=SameAccountInstallMethod.release_directive(),
release_channel=release_channel,
policy=policy,
interactive=interactive,
)
return

if release_channel:
raise UsageError(
f"Release channel is only supported when --from-release-directive is used."
)

# versioned dev
if version:
try:
Expand Down Expand Up @@ -603,6 +617,7 @@ def _upgrade_app(
event_sharing: EventSharingHandler,
policy: PolicyBase,
interactive: bool,
release_channel: Optional[str] = None,
) -> list[tuple[str]] | None:
self.console.step(f"Upgrading existing application object {self.name}.")

Expand All @@ -613,6 +628,7 @@ def _upgrade_app(
stage_fqn=stage_fqn,
debug_mode=self.debug,
should_authorize_event_sharing=event_sharing.should_authorize_event_sharing(),
release_channel=release_channel,
role=self.role,
warehouse=self.warehouse,
)
Expand All @@ -627,6 +643,7 @@ def _create_app(
install_method: SameAccountInstallMethod,
event_sharing: EventSharingHandler,
package: ApplicationPackageEntity,
release_channel: Optional[str] = None,
) -> list[tuple[str]]:
self.console.step(f"Creating new application object {self.name} in account.")

Expand Down Expand Up @@ -665,6 +682,7 @@ def _create_app(
should_authorize_event_sharing=event_sharing.should_authorize_event_sharing(),
role=self.role,
warehouse=self.warehouse,
release_channel=release_channel,
)

@span("update_app_object")
Expand All @@ -675,6 +693,7 @@ def create_or_upgrade_app(
install_method: SameAccountInstallMethod,
policy: PolicyBase,
interactive: bool,
release_channel: Optional[str] = None,
):
event_sharing = EventSharingHandler(
telemetry_definition=self.telemetry,
Expand All @@ -699,6 +718,7 @@ def create_or_upgrade_app(
event_sharing=event_sharing,
policy=policy,
interactive=interactive,
release_channel=release_channel,
)

# 3. If no existing application found, or we performed a drop before the upgrade, we proceed to create
Expand All @@ -708,6 +728,7 @@ def create_or_upgrade_app(
install_method=install_method,
event_sharing=event_sharing,
package=package,
release_channel=release_channel,
)

print_messages(self.console, create_or_upgrade_result)
Expand Down Expand Up @@ -1004,3 +1025,28 @@ def _application_objects_to_str(

def _application_object_to_str(obj: ApplicationOwnedObject) -> str:
return f"({obj['type']}) {obj['name']}"


def _get_verified_release_channel(
package_entity: ApplicationPackageEntity,
release_channel: Optional[str],
) -> Optional[str]:
release_channel = release_channel or DEFAULT_CHANNEL
available_release_channels = get_snowflake_facade().show_release_channels(
package_entity.name, role=package_entity.role
)
if available_release_channels:
release_channel_names = [c["name"] for c in available_release_channels]
if not identifier_in_list(release_channel, release_channel_names):
raise UsageError(
f"Release channel '{release_channel}' is not available for application package {package_entity.name}. Available release channels: ({', '.join(release_channel_names)})."
)
else:
if same_identifiers(release_channel, DEFAULT_CHANNEL):
return None
else:
raise UsageError(
f"Release channels are not enabled for application package {package_entity.name}."
)

return release_channel
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ def release_directive_unset(
show_default=False,
help="Name of the release directive",
),
channel: Optional[str] = typer.Option(
channel: str = typer.Option(
DEFAULT_CHANNEL,
help="Name of the release channel to use",
),
Expand Down
68 changes: 54 additions & 14 deletions src/snowflake/cli/_plugins/nativeapp/sf_sql_facade.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@

import logging
from contextlib import contextmanager
from functools import cache
from textwrap import dedent
from typing import Any, Dict, List

from snowflake.cli._plugins.connection.util import UIParameter, get_ui_parameter
from snowflake.cli._plugins.nativeapp.constants import (
AUTHORIZE_TELEMETRY_COL,
CHANNEL_COL,
DEFAULT_DIRECTIVE,
NAME_COL,
SPECIAL_COMMENT,
Expand Down Expand Up @@ -637,6 +639,7 @@ def upgrade_application(
warehouse: str,
debug_mode: bool | None,
should_authorize_event_sharing: bool | None,
release_channel: str | None = None,
) -> list[tuple[str]]:
"""
Upgrades an application object using the provided clauses
Expand All @@ -648,17 +651,36 @@ def upgrade_application(
@param warehouse: Warehouse which is required to create an application object
@param debug_mode: Whether to enable debug mode; None means not explicitly enabled or disabled
@param should_authorize_event_sharing: Whether to enable event sharing; None means not explicitly enabled or disabled
@param release_channel [Optional]: Release channel to use when upgrading the application
"""

name = to_identifier(name)
release_channel = to_identifier(release_channel) if release_channel else None

install_method.ensure_app_usable(
app_name=name,
app_role=role,
show_app_row=self.get_existing_app_info(name, role),
)

# If all the above checks are in order, proceed to upgrade

@cache # only cache within the scope of this method
def get_app_properties():
return self.get_app_properties(name, role)

with self._use_role_optional(role), self._use_warehouse_optional(warehouse):
try:
using_clause = install_method.using_clause(stage_fqn)
if release_channel:
current_release_channel = get_app_properties().get(
CHANNEL_COL, "DEFAULT"
)
if not same_identifiers(release_channel, current_release_channel):
raise UpgradeApplicationRestrictionError(
f"Cannot upgrade application {name} from release channel {release_channel} because application is already on a different channel."
)

upgrade_cursor = self._sql_executor.execute_query(
f"alter application {name} upgrade {using_clause}",
)
Expand All @@ -669,6 +691,9 @@ def upgrade_application(
self._sql_executor.execute_query(
f"alter application {name} set debug_mode = {debug_mode}"
)

except UpgradeApplicationRestrictionError as err:
raise err
except ProgrammingError as err:
if err.errno in UPGRADE_RESTRICTION_CODES:
raise UpgradeApplicationRestrictionError(err.msg) from err
Expand All @@ -687,7 +712,7 @@ def upgrade_application(
# Only update event sharing if the current value is different as the one we want to set
if should_authorize_event_sharing is not None:
current_authorize_event_sharing = (
self.get_app_properties(name, role)
get_app_properties()
.get(AUTHORIZE_TELEMETRY_COL, "false")
.lower()
== "true"
Expand Down Expand Up @@ -733,6 +758,7 @@ def create_application(
warehouse: str,
debug_mode: bool | None,
should_authorize_event_sharing: bool | None,
release_channel: str | None = None,
) -> list[tuple[str]]:
"""
Creates a new application object using an application package,
Expand All @@ -746,7 +772,11 @@ def create_application(
@param warehouse: Warehouse which is required to create an application object
@param debug_mode: Whether to enable debug mode; None means not explicitly enabled or disabled
@param should_authorize_event_sharing: Whether to enable event sharing; None means not explicitly enabled or disabled
@param release_channel [Optional]: Release channel to use when creating the application
"""
package_name = to_identifier(package_name)
name = to_identifier(name)
release_channel = to_identifier(release_channel) if release_channel else None

# by default, applications are created in debug mode when possible;
# this can be overridden in the project definition
Expand All @@ -761,18 +791,28 @@ def create_application(
"Setting AUTHORIZE_TELEMETRY_EVENT_SHARING to %s",
should_authorize_event_sharing,
)
authorize_telemetry_clause = f" AUTHORIZE_TELEMETRY_EVENT_SHARING = {str(should_authorize_event_sharing).upper()}"
authorize_telemetry_clause = f"AUTHORIZE_TELEMETRY_EVENT_SHARING = {str(should_authorize_event_sharing).upper()}"

using_clause = install_method.using_clause(stage_fqn)
release_channel_clause = (
f"using release channel {release_channel}" if release_channel else ""
)

with self._use_role_optional(role), self._use_warehouse_optional(warehouse):
try:
create_cursor = self._sql_executor.execute_query(
dedent(
f"""\
create application {name}
from application package {package_name} {using_clause} {debug_mode_clause}{authorize_telemetry_clause}
comment = {SPECIAL_COMMENT}
"""
_strip_empty_lines(
f"""\
create application {name}
from application package {package_name}
{using_clause}
{release_channel_clause}
{debug_mode_clause}
{authorize_telemetry_clause}
comment = {SPECIAL_COMMENT}
"""
)
),
)
except ProgrammingError as err:
Expand Down Expand Up @@ -823,10 +863,10 @@ def create_application_package(
dedent(
_strip_empty_lines(
f"""\
create application package {package_name}
comment = {SPECIAL_COMMENT}
distribution = {distribution}
{enable_release_channels_clause}
create application package {package_name}
comment = {SPECIAL_COMMENT}
distribution = {distribution}
{enable_release_channels_clause}
"""
)
)
Expand Down Expand Up @@ -862,9 +902,9 @@ def alter_application_package_properties(
self._sql_executor.execute_query(
dedent(
f"""\
alter application package {package_name}
set enable_release_channels = {str(enable_release_channels).lower()}
"""
alter application package {package_name}
set enable_release_channels = {str(enable_release_channels).lower()}
"""
)
)
except ProgrammingError as err:
Expand Down
12 changes: 12 additions & 0 deletions tests/__snapshots__/test_help_messages.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -983,6 +983,18 @@
| determined from the |
| project definition |
| file. Default: unset. |
| --channel TEXT The name of the |
| release channel to |
| use when creating or |
| upgrading an |
| application instance |
| from a release |
| directive. Requires |
| the |
| --from-release-direc… |
| flag to be set. If |
| unset, the default |
| channel will be used. |
| --interactive --no-interactive When enabled, this |
| option displays |
| prompts even if the |
Expand Down
18 changes: 12 additions & 6 deletions tests/nativeapp/test_event_sharing.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,14 +294,17 @@ def _setup_mocks_for_create_app(
mock.call(
name=DEFAULT_APP_ID,
package_name=DEFAULT_PKG_ID,
install_method=SameAccountInstallMethod.release_directive()
if is_prod
else SameAccountInstallMethod.unversioned_dev(),
install_method=(
SameAccountInstallMethod.release_directive()
if is_prod
else SameAccountInstallMethod.unversioned_dev()
),
stage_fqn=DEFAULT_STAGE_FQN,
debug_mode=None,
should_authorize_event_sharing=expected_authorize_telemetry_flag,
role="app_role",
warehouse="app_warehouse",
release_channel=None,
)
]

Expand Down Expand Up @@ -397,14 +400,17 @@ def _setup_mocks_for_upgrade_app(
mock_sql_facade_upgrade_application_expected = [
mock.call(
name=DEFAULT_APP_ID,
install_method=SameAccountInstallMethod.release_directive()
if is_prod
else SameAccountInstallMethod.unversioned_dev(),
install_method=(
SameAccountInstallMethod.release_directive()
if is_prod
else SameAccountInstallMethod.unversioned_dev()
),
stage_fqn=DEFAULT_STAGE_FQN,
debug_mode=None,
should_authorize_event_sharing=expected_authorize_telemetry_flag,
role="app_role",
warehouse="app_warehouse",
release_channel=None,
)
]
return [*mock_execute_query_expected, *mock_sql_facade_upgrade_application_expected]
Expand Down
Loading

0 comments on commit 659fd6c

Please sign in to comment.