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

Snow 1105629 git integration tests #865

Merged
merged 34 commits into from
Mar 7, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
a061e22
git copy: get
sfc-gh-pczajka Feb 27, 2024
f3c10d9
git copy: copy
sfc-gh-pczajka Feb 27, 2024
d9029e1
unit tests
sfc-gh-pczajka Feb 27, 2024
6f11594
Merge branch 'git-repository' into SNOW-1105689-git-copy
sfc-gh-pczajka Mar 4, 2024
db62c6c
Setting up api integration
sfc-gh-pczajka Feb 28, 2024
eaf972d
test git flow - in progress
sfc-gh-pczajka Feb 28, 2024
e7661b6
copy: fix error handling
sfc-gh-pczajka Feb 28, 2024
a9b6236
Add unit test for checking error messages
sfc-gh-pczajka Mar 1, 2024
228fb3e
finish flow test
sfc-gh-pczajka Mar 1, 2024
601cd75
fix get command
sfc-gh-pczajka Mar 4, 2024
5be18d8
switch to smaller repo
sfc-gh-pczajka Mar 4, 2024
20e362f
refactor tests
sfc-gh-pczajka Mar 4, 2024
adbef0c
update unit tests
sfc-gh-pczajka Mar 4, 2024
712b07b
remove snapshots for Windows run
sfc-gh-pczajka Mar 4, 2024
9c59659
target help message: a directory; create target if not exists
sfc-gh-pczajka Mar 5, 2024
d11c38e
Use SecurePath
sfc-gh-pczajka Mar 5, 2024
0ef4dcf
fix path type
sfc-gh-pczajka Mar 5, 2024
33d9556
Merge branch 'git-repository' into SNOW-1105689-git-copy
sfc-gh-pczajka Mar 5, 2024
5af7287
review fixes
sfc-gh-pczajka Mar 5, 2024
2f7eb04
Refactor stage.get
sfc-gh-pczajka Mar 5, 2024
c3de383
assert in callback
sfc-gh-pczajka Mar 5, 2024
cdf311d
fix source error
sfc-gh-pczajka Mar 5, 2024
287511c
update help message
sfc-gh-pczajka Mar 5, 2024
2317d29
Review fixes
sfc-gh-pczajka Mar 6, 2024
7265e17
rename test assert
sfc-gh-pczajka Mar 6, 2024
c2172f8
small fix
sfc-gh-pczajka Mar 6, 2024
2e9d7a4
Merge branch 'SNOW-1105689-git-copy' into SNOW-1105629-git-integratio…
sfc-gh-pczajka Mar 6, 2024
a1a79ef
fix merge issues
sfc-gh-pczajka Mar 6, 2024
dd6c1ae
change repo to snowcli; refactor copy tests
sfc-gh-pczajka Mar 6, 2024
d51002b
review fixes
sfc-gh-pczajka Mar 7, 2024
9082ea0
Merge branch 'git-repository' into SNOW-1105629-git-integration-tests
sfc-gh-pczajka Mar 7, 2024
a43d2ba
Merge branch 'git-repository' into SNOW-1105629-git-integration-tests
sfc-gh-pczajka Mar 7, 2024
c8fc64c
rename function
sfc-gh-pczajka Mar 7, 2024
adc7450
remove debug
sfc-gh-pczajka Mar 7, 2024
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
4 changes: 4 additions & 0 deletions src/snowflake/cli/api/utils/path_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,7 @@ def path_resolver(path_to_file: str) -> str:
if 0 < return_value <= BUFFER_SIZE:
return buffer.value
return path_to_file


def is_stage_path(path: str) -> bool:
return path.startswith("@") or path.startswith("snow://")
49 changes: 46 additions & 3 deletions src/snowflake/cli/plugins/git/commands.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import logging
from pathlib import Path

import typer
from click import ClickException
from snowflake.cli.api.commands.flags import identifier_argument
from snowflake.cli.api.commands.snow_typer import SnowTyper
from snowflake.cli.api.output.types import CommandResult, QueryResult
from snowflake.cli.api.utils.path_utils import is_stage_path
from snowflake.cli.plugins.git.manager import GitManager

app = SnowTyper(
Expand All @@ -14,6 +17,9 @@
log = logging.getLogger(__name__)

RepoNameArgument = identifier_argument(sf_object="git repository", example="my_repo")
RepoPathArgument = typer.Argument(
help="Path to git repository stage with scope provided. For example: @my_repo/branches/main"
)


@app.command(
Expand Down Expand Up @@ -46,11 +52,10 @@ def list_tags(
requires_connection=True,
)
def list_files(
repository_path: str = typer.Argument(
help="Path to git repository stage with scope provided. For example: @my_repo/branches/main"
),
repository_path: str = RepoPathArgument,
**options,
) -> CommandResult:
_assert_repository_path_is_stage("REPOSITORY_PATH", path=repository_path)
return QueryResult(GitManager().show_files(repo_path=repository_path))


Expand All @@ -64,3 +69,41 @@ def fetch(
**options,
) -> CommandResult:
return QueryResult(GitManager().fetch(repo_name=repository_name))


@app.command(
"copy",
help="Copies all files from given state of repository to local directory or stage.",
requires_connection=True,
)
def copy(
repository_path: str = RepoPathArgument,
destination_path: str = typer.Argument(
help="Target path for copy operation. Should be stage or local file path.",
),
parallel: int = typer.Option(
4,
help="Number of parallel threads to use when downloading files.",
),
**options,
):
_assert_repository_path_is_stage("REPOSITORY_PATH", path=repository_path)
is_copy = is_stage_path(destination_path)
if is_copy:
cursor = GitManager().copy(
repo_path=repository_path, destination_path=destination_path
)
else:
target = Path(destination_path).resolve()
cursor = GitManager().get(
repo_path=repository_path, target_path=target, parallel=parallel
)
return QueryResult(cursor)


def _assert_repository_path_is_stage(argument_name, path):
if not is_stage_path(path):
raise ClickException(
f"{argument_name} should be a path to git repository stage with scope provided."
" For example: @my_repo/branches/main"
)
33 changes: 27 additions & 6 deletions src/snowflake/cli/plugins/git/manager.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,40 @@
from snowflake.cli.api.sql_execution import SqlExecutionMixin
from pathlib import Path

from snowflake.cli.plugins.object.stage.manager import StageManager
from snowflake.connector.cursor import SnowflakeCursor

class GitManager(SqlExecutionMixin):
def show_branches(self, repo_name: str):

class GitManager(StageManager):
def show_branches(self, repo_name: str) -> SnowflakeCursor:
query = f"show git branches in {repo_name}"
return self._execute_query(query)

def show_tags(self, repo_name: str):
def show_tags(self, repo_name: str) -> SnowflakeCursor:
query = f"show git tags in {repo_name}"
return self._execute_query(query)

def show_files(self, repo_path: str):
def show_files(self, repo_path: str) -> SnowflakeCursor:
query = f"ls {repo_path}"
return self._execute_query(query)

def fetch(self, repo_name: str):
def fetch(self, repo_name: str) -> SnowflakeCursor:
query = f"alter git repository {repo_name} fetch"
return self._execute_query(query)

def _get_standard_stage_directory_path(self, path):
if not path.endswith("/"):
path += "/"
return self.get_standard_stage_name(path)

def get(self, repo_path: str, target_path: Path, parallel: int) -> SnowflakeCursor:
return super().get(
stage_name=self._get_standard_stage_directory_path(repo_path),
dest_path=target_path,
parallel=parallel,
)

def copy(self, repo_path: str, destination_path: str) -> SnowflakeCursor:
source = self._get_standard_stage_directory_path(repo_path)
destination = self._get_standard_stage_directory_path(destination_path)
query = f"copy files into {destination} from {source}"
return self._execute_query(query)
9 changes: 3 additions & 6 deletions src/snowflake/cli/plugins/object/stage/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
QueryResult,
SingleQueryResult,
)
from snowflake.cli.api.utils.path_utils import is_stage_path
from snowflake.cli.plugins.object.stage.diff import DiffResult
from snowflake.cli.plugins.object.stage.manager import StageManager

Expand All @@ -31,10 +32,6 @@ def stage_list(stage_name: str = StageNameArgument, **options) -> CommandResult:
return QueryResult(cursor)


def _is_stage_path(path: str):
return path.startswith("@") or path.startswith("snow://")


@app.command("copy", requires_connection=True)
def copy(
source_path: str = typer.Argument(
Expand All @@ -57,8 +54,8 @@ def copy(
Copies all files from target path to target directory. This works for both uploading
to and downloading files from the stage.
"""
is_get = _is_stage_path(source_path)
is_put = _is_stage_path(destination_path)
is_get = is_stage_path(source_path)
is_put = is_stage_path(destination_path)

if is_get and is_put:
raise click.ClickException(
Expand Down
84 changes: 80 additions & 4 deletions tests/__snapshots__/test_help_messages.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -753,6 +753,80 @@
╰──────────────────────────────────────────────────────────────────────────────╯


'''
# ---
# name: test_help_messages[git.copy]
'''

Usage: default git copy [OPTIONS] REPOSITORY_PATH DESTINATION_PATH

Copies all files from given state of repository to local directory or stage.

╭─ Arguments ──────────────────────────────────────────────────────────────────╮
│ * repository_path TEXT Path to git repository stage with scope │
│ provided. For example: │
│ @my_repo/branches/main │
│ [default: None] │
│ [required] │
│ * destination_path TEXT Target path for copy operation. Should be │
│ stage or local file path. │
│ [default: None] │
│ [required] │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Options ────────────────────────────────────────────────────────────────────╮
│ --parallel INTEGER Number of parallel threads to use when │
│ downloading files. │
│ [default: 4] │
│ --help -h Show this message and exit. │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Connection configuration ───────────────────────────────────────────────────╮
│ --connection,--environment -c TEXT Name of the connection, as defined │
│ in your `config.toml`. Default: │
│ `default`. │
│ --account,--accountname TEXT Name assigned to your Snowflake │
│ account. Overrides the value │
│ specified for the connection. │
│ --user,--username TEXT Username to connect to Snowflake. │
│ Overrides the value specified for │
│ the connection. │
│ --password TEXT Snowflake password. Overrides the │
│ value specified for the │
│ connection. │
│ --authenticator TEXT Snowflake authenticator. Overrides │
│ the value specified for the │
│ connection. │
│ --private-key-path TEXT Snowflake private key path. │
│ Overrides the value specified for │
│ the connection. │
│ --database,--dbname TEXT Database to use. Overrides the │
│ value specified for the │
│ connection. │
│ --schema,--schemaname TEXT Database schema to use. Overrides │
│ the value specified for the │
│ connection. │
│ --role,--rolename TEXT Role to use. Overrides the value │
│ specified for the connection. │
│ --warehouse TEXT Warehouse to use. Overrides the │
│ value specified for the │
│ connection. │
│ --temporary-connection -x Uses connection defined with │
│ command line parameters, instead │
│ of one defined in config │
│ --mfa-passcode TEXT Token to use for multi-factor │
│ authentication (MFA) │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Global configuration ───────────────────────────────────────────────────────╮
│ --format [TABLE|JSON] Specifies the output format. │
│ [default: TABLE] │
│ --verbose -v Displays log entries for log levels `info` │
│ and higher. │
│ --debug Displays log entries for log levels `debug` │
│ and higher; debug logs contains additional │
│ information. │
│ --silent Turns off intermediate output to console. │
╰──────────────────────────────────────────────────────────────────────────────╯


'''
# ---
# name: test_help_messages[git.fetch]
Expand Down Expand Up @@ -1031,10 +1105,12 @@
│ --help -h Show this message and exit. │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Commands ───────────────────────────────────────────────────────────────────╮
│ fetch Fetch changes from origin to snowflake repository. │
│ list-branches List all branches in the repository. │
│ list-files List files from given state of git repository. │
│ list-tags List all tags in the repository. │
│ copy Copies all files from given state of repository to local │
│ directory or stage. │
│ fetch Fetch changes from origin to snowflake repository. │
│ list-branches List all branches in the repository. │
│ list-files List files from given state of git repository. │
│ list-tags List all tags in the repository. │
╰──────────────────────────────────────────────────────────────────────────────╯


Expand Down
47 changes: 47 additions & 0 deletions tests/git/test_git_commands.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from pathlib import Path
from unittest import mock

import pytest
Expand Down Expand Up @@ -44,6 +45,11 @@ def test_list_files(mock_connector, runner, mock_ctx):
assert result.exit_code == 0, result.output
assert ctx.get_query() == "ls @repo_name/branches/main"

# detect whether repo_path is a stage path
result = runner.invoke(["git", "list-files", "not_a_stage_path"])
assert result.exit_code == 1
_assert_error_message(result.output)


@mock.patch("snowflake.connector.connect")
def test_fetch(mock_connector, runner, mock_ctx):
Expand All @@ -53,3 +59,44 @@ def test_fetch(mock_connector, runner, mock_ctx):

assert result.exit_code == 0, result.output
assert ctx.get_query() == "alter git repository repo_name fetch"


@mock.patch("snowflake.connector.connect")
def test_copy(mock_connector, runner, mock_ctx):
ctx = mock_ctx()
mock_connector.return_value = ctx
local_path = Path("local/path")
result = runner.invoke(["git", "copy", "@repo_name/branches/main", str(local_path)])

# paths in generated SQL should end with '/'
assert result.exit_code == 0, result.output
assert (
ctx.get_query()
== f"get @repo_name/branches/main/ file://{local_path.resolve()}/ parallel=4"
)

ctx = mock_ctx()
mock_connector.return_value = ctx
result = runner.invoke(
["git", "copy", "@repo_name/branches/main", "@stage_path/dir_in_stage"]
)

# paths in generated SQL should end with '/'
assert result.exit_code == 0, result.output
assert (
ctx.get_query()
== "copy files into @stage_path/dir_in_stage/ from @repo_name/branches/main/"
)

# detect whether repo_path is a stage path
result = runner.invoke(["git", "list-files", "not_a_stage_path"])
assert result.exit_code == 1
_assert_error_message(result.output)


def _assert_error_message(output):
sfc-gh-pjob marked this conversation as resolved.
Show resolved Hide resolved
assert "Error" in output
assert (
"REPOSITORY_PATH should be a path to git repository stage with scope" in output
)
assert "provided. For example: @my_repo/branches/main" in output
Loading
Loading