Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for snow app run from release channel #1951

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.""",
),
sfc-gh-gbloom marked this conversation as resolved.
Show resolved Hide resolved
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}."
)
sfc-gh-gbloom marked this conversation as resolved.
Show resolved Hide resolved

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
69 changes: 55 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,15 @@

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_CHANNEL,
DEFAULT_DIRECTIVE,
NAME_COL,
SPECIAL_COMMENT,
Expand Down Expand Up @@ -637,6 +640,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 +652,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_CHANNEL
)
if not same_identifiers(release_channel, current_release_channel):
raise UpgradeApplicationRestrictionError(
f"Application {name} is currently on release channel {current_release_channel}. Cannot upgrade to release channel {release_channel}."
)

upgrade_cursor = self._sql_executor.execute_query(
f"alter application {name} upgrade {using_clause}",
)
Expand All @@ -669,6 +692,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 +713,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 +759,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 +773,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 +792,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 +864,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 +903,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
Loading