Skip to content

Commit

Permalink
Add support for release channels in snow app version create (#1946)
Browse files Browse the repository at this point in the history
  • Loading branch information
sfc-gh-melnacouzi authored Dec 11, 2024
1 parent 8f559e3 commit 8c3c85d
Show file tree
Hide file tree
Showing 10 changed files with 484 additions and 141 deletions.
3 changes: 3 additions & 0 deletions RELEASE-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,13 @@
* `snow app release-directive list`
* `snow app release-directive set`
* `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.

## Fixes and improvements
* Fixed crashes with older x86_64 Intel CPUs.
* Fixed inability to add patches to lowercase quoted versions
* Fixes label being set to blank instead of None when not provided.

# v3.2.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ def action_version_create(
force: bool,
*args,
**kwargs,
):
) -> VersionInfo:
"""
Create a version and/or patch for a new or existing application package.
Always performs a deploy action before creating version or patch.
Expand Down Expand Up @@ -453,12 +453,14 @@ def action_version_create(
# Define a new version in the application package
if not self.get_existing_version_info(resolved_version):
self.add_new_version(version=resolved_version, label=resolved_label)
return # A new version created automatically has patch 0, we do not need to further increment the patch.
# A new version created automatically has patch 0, we do not need to further increment the patch.
return VersionInfo(resolved_version, 0, resolved_label)

# Add a new patch to an existing (old) version
self.add_new_patch_to_version(
patch = self.add_new_patch_to_version(
version=resolved_version, patch=resolved_patch, label=resolved_label
)
return VersionInfo(resolved_version, patch, resolved_label)

def action_version_drop(
self,
Expand Down Expand Up @@ -537,14 +539,9 @@ def action_version_drop(
raise typer.Exit(1)

# Drop the version
sql_executor = get_sql_executor()
with sql_executor.use_role(self.role):
try:
sql_executor.execute_query(
f"alter application package {self.name} drop version {version}"
)
except ProgrammingError as err:
raise err # e.g. version is referenced in a release directive(s)
get_snowflake_facade().drop_version_from_package(
package_name=self.name, version=version, role=self.role
)

console.message(
f"Version {version} in application package {self.name} dropped successfully."
Expand Down Expand Up @@ -846,9 +843,10 @@ def add_new_version(self, version: str, label: str | None = None) -> None:

def add_new_patch_to_version(
self, version: str, patch: int | None = None, label: str | None = None
):
) -> int:
"""
Add a new patch, optionally a custom one, to an existing version in an application package.
Returns the patch number of the newly created patch.
"""
console = self._workspace_ctx.console

Expand All @@ -868,6 +866,7 @@ def add_new_patch_to_version(
console.message(
f"Patch {new_patch}{with_label_prompt} created for version {version} defined in application package {self.name}."
)
return new_patch

def check_index_changes_in_git_repo(
self, policy: PolicyBase, interactive: bool
Expand Down Expand Up @@ -1134,28 +1133,30 @@ def resolve_version_info(
bundle_map: BundleMap | None,
policy: PolicyBase,
interactive: bool,
):
) -> VersionInfo:
"""Determine version name, patch number, and label from CLI provided values and manifest.yml version entry.
@param [Optional] version: version name as specified in the command
@param [Optional] patch: patch number as specified in the command
@param [Optional] label: version/patch label as specified in the command
@param [Optional] bundle_map: bundle_map if a deploy_root is prepared. _bundle() is performed otherwise.
@param policy: CLI policy
@param interactive: True if command is run in interactive mode, otherwise False
@return VersionInfo: version_name, patch_number, label resolved from CLI and manifest.yml
"""
console = self._workspace_ctx.console

resolved_version = None
resolved_patch = None
resolved_label = ""
resolved_label = None

# If version is specified in CLI, no version information from manifest.yml is used (except for comment, we can't control comment as of now).
if version is not None:
console.message(
"Ignoring version information from the application manifest since a version was explicitly specified with the command."
)
resolved_patch = patch
resolved_label = label if label is not None else ""
resolved_label = label
resolved_version = version

# When version is not set by CLI, version name is read from manifest.yml. patch and label from CLI will be used, if provided.
Expand Down
71 changes: 59 additions & 12 deletions src/snowflake/cli/_plugins/nativeapp/sf_sql_facade.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
NO_WAREHOUSE_SELECTED_IN_SESSION,
RELEASE_DIRECTIVE_DOES_NOT_EXIST,
RELEASE_DIRECTIVES_VERSION_PATCH_NOT_FOUND,
SQL_COMPILATION_ERROR,
VERSION_DOES_NOT_EXIST,
VERSION_NOT_ADDED_TO_RELEASE_CHANNEL,
)
Expand Down Expand Up @@ -264,27 +265,62 @@ def create_version_in_package(
@param [Optional] label: Label for this version, visible to consumers.
"""

# Make the version a valid identifier, adding quotes if necessary
version = to_identifier(version)
package_name = to_identifier(package_name)

available_release_channels = self.show_release_channels(package_name, role)

# Label must be a string literal
with_label_cause = (
f"\nlabel={to_string_literal(label)}" if label is not None else ""
with_label_clause = (
f"label={to_string_literal(label)}" if label is not None else ""
)
add_version_query = dedent(
f"""\
alter application package {package_name}
add version {version}
using @{stage_fqn}{with_label_cause}
"""

action = "register" if available_release_channels else "add"

query = dedent(
_strip_empty_lines(
f"""\
alter application package {package_name}
{action} version {version}
using @{stage_fqn}
{with_label_clause}
"""
)
)

with self._use_role_optional(role):
try:
self._sql_executor.execute_query(query)
except Exception as err:
handle_unclassified_error(
err,
f"Failed to {action} version {version} to application package {package_name}.",
)

def drop_version_from_package(
self, package_name: str, version: str, role: str | None = None
):
"""
Drops a version from an existing application package.
@param package_name: Name of the application package to alter.
@param version: Version name to drop.
@param [Optional] role: Switch to this role while executing drop version.
"""

version = to_identifier(version)
package_name = to_identifier(package_name)

release_channels = self.show_release_channels(package_name, role)
action = "deregister" if release_channels else "drop"

query = f"alter application package {package_name} {action} version {version}"
with self._use_role_optional(role):
try:
self._sql_executor.execute_query(add_version_query)
self._sql_executor.execute_query(query)
except Exception as err:
handle_unclassified_error(
err,
f"Failed to add version {version} to application package {package_name}.",
f"Failed to {action} version {version} from application package {package_name}.",
)

def add_patch_to_package_version(
Expand Down Expand Up @@ -1085,6 +1121,10 @@ def show_release_channels(
cursor_class=DictCursor,
)
except ProgrammingError as err:
# TODO: Temporary check for syntax until UI Parameter is available in production
if err.errno == SQL_COMPILATION_ERROR:
# Release not out yet and param not out yet
return []
handle_unclassified_error(
err,
f"Failed to show release channels for application package {package_name}.",
Expand All @@ -1095,8 +1135,15 @@ def show_release_channels(
def _strip_empty_lines(text: str) -> str:
"""
Strips empty lines from the input string.
Preserves the new line at the end of the string if it exists.
"""
return "\n".join(line for line in text.splitlines() if line.strip())
all_lines = text.splitlines()

# join all non-empty lines, but preserve the new line at the end if it exists
last_line = all_lines[-1]
other_lines = [line for line in all_lines[:-1] if line.strip()]

return "\n".join(other_lines) + "\n" + last_line


def _handle_release_directive_version_error(
Expand Down
26 changes: 23 additions & 3 deletions src/snowflake/cli/_plugins/nativeapp/version/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from typing import Optional

import typer
from snowflake.cli._plugins.nativeapp.artifacts import VersionInfo
from snowflake.cli._plugins.nativeapp.common_flags import ForceOption, InteractiveOption
from snowflake.cli._plugins.nativeapp.v2_conversions.compat import (
force_project_definition_v2,
Expand All @@ -29,7 +30,14 @@
)
from snowflake.cli.api.commands.snow_typer import SnowTyperFactory
from snowflake.cli.api.entities.common import EntityActions
from snowflake.cli.api.output.types import CommandResult, MessageResult, QueryResult
from snowflake.cli.api.output.formats import OutputFormat
from snowflake.cli.api.output.types import (
CommandResult,
MessageResult,
ObjectResult,
QueryResult,
)
from snowflake.cli.api.project.util import to_identifier

app = SnowTyperFactory(
name="version",
Expand Down Expand Up @@ -78,7 +86,7 @@ def create(
project_root=cli_context.project_root,
)
package_id = options["package_entity_id"]
ws.perform_action(
result: VersionInfo = ws.perform_action(
package_id,
EntityActions.VERSION_CREATE,
version=version,
Expand All @@ -88,7 +96,19 @@ def create(
interactive=interactive,
skip_git_check=skip_git_check,
)
return MessageResult(f"Version create is now complete.")

message = "Version create is now complete."
if cli_context.output_format == OutputFormat.JSON:
return ObjectResult(
{
"message": message,
"version": to_identifier(result.version_name),
"patch": result.patch_number,
"label": result.label,
}
)
else:
return MessageResult(message)


@app.command("list", requires_connection=True)
Expand Down
Loading

0 comments on commit 8c3c85d

Please sign in to comment.