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 optional deploy files argument #933

Merged
merged 82 commits into from
May 6, 2024

Conversation

sfc-gh-gbloom
Copy link
Contributor

@sfc-gh-gbloom sfc-gh-gbloom commented Mar 25, 2024

Pre-review checklist

  • I've confirmed that instructions included in README.md are still correct after my changes in the codebase.
  • I've added or updated automated unit tests to verify correctness of my new code.
  • I've added or updated integration tests to verify correctness of my new code.
  • I've confirmed that my changes are working by executing CLI's commands manually.
  • I've confirmed that my changes are up-to-date with the target branch.
  • I've described my changes in the release notes.
  • I've described my changes in the section below.

Changes description

Following up on #921, this PR introduces the following changes:

  1. The option to provide specific files and directories when running snow app deploy
  2. The --prune/--no-prune option
  3. The -r/--recursive option
  4. Keep track of files copied/symlinked in the build step
  5. Fix a bug in build_md5_map()

When determining the deploy path from source path, we use the mapping of the files created in the build step. Since we symlink entire directories, nested files will not exist in the mapping. In such cases, we traverse the parents chain until a mapped directory is found.

Usage

snow app deploy file1 dir1 dir2/file2 -r

If no files are specified, all files will be synced to stage (existing behavior).

The file paths are assumed to be relative to the project root.

Absolute paths, directories and patterns are currently not supported.

Pruning

When pruning is enabled, files that exist only on the stage will be removed.

When deploying all changes, --prune is the default value (existing behavior).
When deploying specific files, --no-prune is the default value and the user has to explicitly specify --prune to avoid accidentally removing files.

Recursive

Same as cp command, when specifying specific directories to deploy, the -r flag must be provided. When a directory is deployed, all of its content will be synced recursively.

When deploying all changes, -r is implied (existing behavior).

@sfc-gh-gbloom sfc-gh-gbloom marked this pull request as ready for review March 26, 2024 17:26
@sfc-gh-gbloom sfc-gh-gbloom requested review from a team and sfc-gh-bgoel as code owners March 26, 2024 17:26
src/snowflake/cli/api/exceptions.py Outdated Show resolved Hide resolved
src/snowflake/cli/plugins/nativeapp/commands.py Outdated Show resolved Hide resolved
src/snowflake/cli/plugins/object/stage/diff.py Outdated Show resolved Hide resolved
src/snowflake/cli/plugins/nativeapp/manager.py Outdated Show resolved Hide resolved
src/snowflake/cli/plugins/object/stage/diff.py Outdated Show resolved Hide resolved
src/snowflake/cli/plugins/object/stage/diff.py Outdated Show resolved Hide resolved
src/snowflake/cli/plugins/object/stage/diff.py Outdated Show resolved Hide resolved
@sfc-gh-bdufour
Copy link
Contributor

An additional comment: your description states that the deploy root is CWD, but that's inaccurate, it's the project root that is current. Deploy root is typically a subdirectory of the project root. I still believe that falling back to look for files relative to the project root is a valuable addition to this command.

symlink_or_copy(source_path, dest_path / source_path.name)
dest_child_path = dest_path / source_path.name
symlink_or_copy(source_path, dest_child_path)
mapped_files[source_path.resolve()] = dest_child_path
Copy link
Contributor

Choose a reason for hiding this comment

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

Might be related to the discussion above, but why:

  1. Is source_path being resolved?
  2. Is only source_path being resolved but not dest_child_path?

Also, can you clarify the following:
If the user specified a glob pattern, then source_path will be the directories or files that qualify the source pattern. In case of directories, even though their contents are symlinked as well, mapped_files will only contain a kv pair of the directory to the dest, and not its contents.

Copy link
Contributor Author

@sfc-gh-gbloom sfc-gh-gbloom Apr 29, 2024

Choose a reason for hiding this comment

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

  1. Both src/dest parts in ArtifactDeploymentMap are resolved, mostly to canonicalize windows paths: C:\Users\RUNNER~1\ -> C:\Users\runneradmin\
  2. dest_child_path is also resolved (dest_path is resolved a few lines above)

Yes, mapped_files won't contain the contents of symlinked directories. It's only the files that we actually copied to the deploy directory.
You can see the logic in project_path_to_deploy_path() for cases where we look for a file under a symlinked directory.



def project_path_to_deploy_path(
project_path: Path, mapped_files: ArtifactDeploymentMap
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: is this project_path or source_path? project_path can be misleading to only mean the path to the project, i.e. root of the project.

Copy link
Contributor

Choose a reason for hiding this comment

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

Agreed, I think source_path is a better name.

if prune:
diff.only_on_stage = [i for i in diff.only_on_stage if i in paths_to_sync]
else:
files_not_removed = [i for i in diff.only_on_stage if i in paths_to_sync]
Copy link
Contributor

Choose a reason for hiding this comment

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

Lines 167 and 169 rely on the same list comprehension. Consider assigning it to a placeholder/variable before checking for prune, to reduce duplicating code.

from snowflake.cli.api.constants import DEFAULT_SIZE_LIMIT_MB
from snowflake.cli.api.project.schemas.native_app.path_mapping import PathMapping
from snowflake.cli.api.secure_path import SecurePath
from yaml import safe_load

# Map from source directories and files in the project directory to their path in the deploy directory. Both paths are absolute.
Copy link
Contributor

Choose a reason for hiding this comment

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

In an ideal world, these would be relative to their respective roots, but it's documented well and if we need them we can always use relative_to.



def project_path_to_deploy_path(
project_path: Path, mapped_files: ArtifactDeploymentMap
Copy link
Contributor

Choose a reason for hiding this comment

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

Agreed, I think source_path is a better name.

src/snowflake/cli/plugins/nativeapp/artifacts.py Outdated Show resolved Hide resolved
src/snowflake/cli/plugins/nativeapp/commands.py Outdated Show resolved Hide resolved
src/snowflake/cli/plugins/nativeapp/commands.py Outdated Show resolved Hide resolved
src/snowflake/cli/plugins/nativeapp/manager.py Outdated Show resolved Hide resolved
Copy link
Contributor

@sfc-gh-mheavin sfc-gh-mheavin left a comment

Choose a reason for hiding this comment

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

Left some thoughts for your consideration

RELEASE-NOTES.md Outdated
@@ -6,6 +6,7 @@

## New additions
* `snow sql` command supports now client-side templating of queries.
* Added the option to specify specific files and directories to sync when running `snow app deploy`.
Copy link
Contributor

Choose a reason for hiding this comment

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

Might be helpful to include the name of the option

def source_path_to_deploy_path(
source_path: Path, mapped_files: ArtifactDeploymentMap
) -> Path:
"""Given a source path and the files created during bundle, returns the absolute deploy destination path."""
Copy link
Contributor

Choose a reason for hiding this comment

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

Generally better to start with what it does.

Returns the absolute path where the specified application bundle was deployed. ??? (just guessing here)

"""
Prepares a local folder (deploy_root) with configured app artifacts.
This folder can then be uploaded to a stage.
Returns a mapping of the copied source files, pointing to where they were copied to.
Copy link
Contributor

Choose a reason for hiding this comment

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

copied. (remove "to")

Also, is it a mapping or a map that gets returned? From the code, it looks like it returns a Map.

@@ -229,17 +230,44 @@ def app_teardown(
@app.command("deploy", requires_connection=True)
@with_project_definition("native_app")
def app_deploy(
prune: Optional[bool] = typer.Option(
default=None,
help=f"""If set, matched files that do not exist locally will also be deleted from the stage.""",
Copy link
Contributor

Choose a reason for hiding this comment

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

Whether to delete specified files from the stage if they don't exist locally. If set, the command deletes files that exist in the stage, but not in the local filesystem.

None,
"--recursive",
"-r",
help=f"""Controls whether the specified directories should be deployed including all of their contents. If false, only the immediate files will be deployed, ignoring any sub-directories.""",
Copy link
Contributor

Choose a reason for hiding this comment

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

Whether to traverse and deploy files from subdirectories. If set, the command deploys all files and subdirectories; otherwise, only files in the current directory are deployed.

files: Optional[List[Path]] = typer.Argument(
default=None,
show_default=False,
help=f"""Paths of the files, relative to the project root, to be uploaded to a stage. [default: sync all local changes to the stage]""",
Copy link
Contributor

Choose a reason for hiding this comment

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

Paths, relative to the the project root, of files you want to upload to a stage. If unspecified, the command syncs all local changes to the stage.

**options,
) -> CommandResult:
"""
Creates an application package in your Snowflake account and syncs the local changes to the stage without creating or updating the application.
Running this command with no arguments at all is a shorthand for "snow app deploy --prune --recursive".
Copy link
Contributor

Choose a reason for hiding this comment

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

... at all, as in snow app deploy, ...

I think we use `` for commands. The scripts then convert it to the proper RST code format.

@@ -100,6 +106,20 @@ def ensure_correct_owner(row: dict, role: str, obj_name: str) -> None:
raise UnexpectedOwnerError(obj_name, role, actual_owner)


def _get_paths_to_sync(paths_to_sync: List[Path], deploy_root: Path) -> List[str]:
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this an internal function (so the doc string is not customer-visible)?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, not visible to the customer using the CLI. But visible to the public in general since this is open source.
Regardless, good idea to still have some information on what this function does.

@sfc-gh-cgorrie sfc-gh-cgorrie mentioned this pull request Apr 30, 2024
7 tasks
@sfc-gh-gbloom sfc-gh-gbloom enabled auto-merge (squash) May 2, 2024 19:25
help=f"""Whether to delete specified files from the stage if they don't exist locally. If set, the command deletes files that exist in the stage, but not in the local filesystem.""",
),
recursive: Optional[bool] = typer.Option(
None,
Copy link
Contributor

Choose a reason for hiding this comment

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

Personally I would use False as default for boolean value

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I needed it to be None here because there is the special flow of running snow app deploy with no arguments, in which case the default value is True

@sfc-gh-gbloom sfc-gh-gbloom merged commit 17138a6 into main May 6, 2024
11 checks passed
@sfc-gh-gbloom sfc-gh-gbloom deleted the gbloom-SNOW-1238239-deploy-files-argument branch May 6, 2024 15:07
sfc-gh-sichen pushed a commit that referenced this pull request Oct 17, 2024
* add deploy files argument

* add --prune flag

* add --recursive flag
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants