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 --from-stage option to version create #1973

Merged
merged 2 commits into from
Jan 9, 2025
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 set`
* `snow app release-directive unset`
* `snow app version create` now returns version, patch, and label in JSON format.
* Add `--from-stage` flag to `snow app version create` to allow version creation from the content of the stage without re-syncing to the stage.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity, what does "re-syncing to the stage" mean?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uploading files to the stage from local workspace.

* Add support for release channels:
* Add support for release channels feature in native app version creation/drop.
* Add ability to specify release channel when creating application instance from release directive: `snow app run --from-release-directive --channel=<channel>`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,7 @@ def action_version_create(
skip_git_check: bool,
interactive: bool,
force: bool,
from_stage: Optional[bool],
*args,
**kwargs,
) -> VersionInfo:
Expand Down Expand Up @@ -463,18 +464,28 @@ def action_version_create(
if git_policy.should_proceed():
self.check_index_changes_in_git_repo(policy=policy, interactive=interactive)

self._deploy(
action_ctx=action_ctx,
bundle_map=bundle_map,
prune=True,
recursive=True,
paths=[],
print_diff=True,
validate=True,
stage_fqn=self.stage_fqn,
interactive=interactive,
force=force,
)
# if user is asking to create the version from the current stage,
# then do not re-deploy the artifacts or touch the stage
if from_stage:
# verify package exists:
if not self.get_existing_app_pkg_info():
raise ClickException(
"Cannot create version from stage because the application package does not exist yet. "
"Try removing --from-stage flag or executing `snow app deploy` to deploy the application package first."
)
else:
self._deploy(
action_ctx=action_ctx,
bundle_map=bundle_map,
prune=True,
recursive=True,
paths=[],
print_diff=True,
validate=True,
stage_fqn=self.stage_fqn,
interactive=interactive,
force=force,
)

# Warn if the version exists in a release directive(s)
try:
Expand Down
7 changes: 7 additions & 0 deletions src/snowflake/cli/_plugins/nativeapp/version/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ def create(
help="When enabled, the Snowflake CLI skips checking if your project has any untracked or stages files in git. Default: unset.",
is_flag=True,
),
from_stage: bool = typer.Option(
False,
"--from-stage",
help="When enabled, the Snowflake CLI creates a version from the current application package stage without syncing to the stage first.",
is_flag=True,
),
interactive: bool = InteractiveOption,
force: Optional[bool] = ForceOption,
**options,
Expand All @@ -95,6 +101,7 @@ def create(
force=force,
interactive=interactive,
skip_git_check=skip_git_check,
from_stage=from_stage,
)

message = "Version create is now complete."
Expand Down
1 change: 1 addition & 0 deletions src/snowflake/cli/_plugins/workspace/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@ def version_create(
skip_git_check=skip_git_check,
interactive=interactive,
force=force,
from_stage=False,
)


Expand Down
7 changes: 7 additions & 0 deletions tests/__snapshots__/test_help_messages.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -2089,6 +2089,13 @@
| untracked or stages |
| files in git. Default: |
| unset. |
| --from-stage When enabled, the |

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious what is this file?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It contains snapshots from all the commands through unit tests - it is auto-generated, and helps verify that the command output changes (--help) are as expected.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

got it thanks

| Snowflake CLI creates |
| a version from the |
| current application |
| package stage without |
| syncing to the stage |
| first. |
| --interactive --no-interactive When enabled, this |
| option displays |
| prompts even if the |
Expand Down
63 changes: 63 additions & 0 deletions tests/nativeapp/test_version_create.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,12 @@

from tests.nativeapp.factories import ApplicationPackageEntityModelFactory, PdfV2Factory
from tests.nativeapp.utils import (
APP_PACKAGE_ENTITY_GET_EXISTING_APP_PKG_INFO,
APPLICATION_PACKAGE_ENTITY_MODULE,
SQL_EXECUTOR_EXECUTE,
SQL_FACADE,
SQL_FACADE_CREATE_VERSION,
SQL_FACADE_SHOW_RELEASE_DIRECTIVES,
mock_execute_helper,
mock_snowflake_yml_file_v2,
)
Expand All @@ -64,6 +66,7 @@ def _version_create(
skip_git_check: bool,
label: str | None = None,
console: AbstractConsole | None = None,
from_stage: bool = False,
):
dm = DefinitionManager()
pd = dm.project_definition
Expand All @@ -83,6 +86,7 @@ def _version_create(
force=force,
interactive=interactive,
skip_git_check=skip_git_check,
from_stage=from_stage,
)


Expand Down Expand Up @@ -931,3 +935,62 @@ def test_patch_from_manifest(
mock_console.warning.assert_called_with(
f"Cannot resolve version. Found patch: {manifest_patch} in manifest.yml which is different from provided patch {cli_patch}."
)


@mock.patch(SQL_FACADE_CREATE_VERSION)
@mock.patch(SQL_FACADE_SHOW_RELEASE_DIRECTIVES, return_value=[])
@mock.patch.object(ApplicationPackageEntity, "_deploy")
@mock.patch(
APP_PACKAGE_ENTITY_GET_EXISTING_APP_PKG_INFO, return_value=[{"name": "app_pkg"}]
)
@mock.patch.object(
ApplicationPackageEntity, "check_index_changes_in_git_repo", return_value=None
)
@mock.patch.object(
ApplicationPackageEntity, "get_existing_version_info", return_value=None
)
@mock.patch.object(ApplicationPackageEntity, "_bundle")
def test_action_version_create_from_stage(
mock_bundle,
mock_get_existing_version_info,
mock_check_git,
mock_get_existing_pkg_info,
mock_deploy,
mock_show_release_directives,
mock_create_version,
application_package_entity,
action_context,
):
pkg_model = application_package_entity._entity_model # noqa SLF001
pkg_model.meta.role = "package_role"

version = "v1"
result = application_package_entity.action_version_create(
action_ctx=action_context,
version=version,
patch=None,
label=None,
skip_git_check=False,
interactive=False,
force=False,
from_stage=True,
)

assert result == VersionInfo(version, 0, None)

mock_check_git.assert_called_once()
mock_show_release_directives.assert_called_once_with(
package_name=pkg_model.fqn.name, role=pkg_model.meta.role
)
mock_get_existing_version_info.assert_called_once_with(version)
mock_bundle.assert_called_once()
mock_create_version.assert_called_once_with(
package_name=pkg_model.fqn.name,
version=version,
stage_fqn=application_package_entity.stage_fqn,
role=pkg_model.meta.role,
label=None,
)

# Deploy should not be called with --from-stage
mock_deploy.assert_not_called()
82 changes: 82 additions & 0 deletions tests_integration/nativeapp/test_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -637,3 +637,85 @@ def test_version_create_with_json_result(runner, nativeapp_project_directory):
"label": None,
"message": "Version create is now complete.",
}


@pytest.mark.integration
def test_version_from_stage(runner, nativeapp_project_directory):
with nativeapp_project_directory("napp_init_v2"):
# Deploy:
result = runner.invoke_with_connection_json(["app", "deploy"])
assert result.exit_code == 0

# Add a file and make sure it shows up in the diff against the stage:
with open("app/TEST_UPDATE.md", "w") as f:
f.write("Hello world!")
result = runner.invoke_with_connection(["app", "diff"])
assert result.exit_code == 0
assert "TEST_UPDATE.md" in result.output

# Test version creation:
result = runner.invoke_with_connection_json(
[
"app",
"version",
"create",
"v1",
"--force",
"--skip-git-check",
"--from-stage",
]
)
assert result.exit_code == 0
assert result.json == {
"version": "v1",
"patch": 0,
"label": None,
"message": "Version create is now complete.",
}

# Make sure the file still hasn't been deployed yet:
result = runner.invoke_with_connection(["app", "diff"])
assert result.exit_code == 0
assert "TEST_UPDATE.md" in result.output

# Create patch:
result = runner.invoke_with_connection_json(
[
"app",
"version",
"create",
"v1",
"--force",
"--skip-git-check",
"--from-stage",
]
)
assert result.exit_code == 0
assert result.json == {
"version": "v1",
"patch": 1,
"label": None,
"message": "Version create is now complete.",
}

# Make sure the file still hasn't been deployed yet:
result = runner.invoke_with_connection(["app", "diff"])
assert result.exit_code == 0
assert "TEST_UPDATE.md" in result.output

# Create patch but don't use --from-stage:
result = runner.invoke_with_connection_json(
["app", "version", "create", "v1", "--force", "--skip-git-check"]
)
assert result.exit_code == 0
assert result.json == {
"version": "v1",
"patch": 2,
"label": None,
"message": "Version create is now complete.",
}

# Make sure the file has been actually deployed:
result = runner.invoke_with_connection(["app", "diff"])
assert result.exit_code == 0
assert "TEST_UPDATE.md" not in result.output
Loading