Skip to content

Commit

Permalink
Release v2.8.1 - version update and cherry picks (#1550)
Browse files Browse the repository at this point in the history
* Fix git execute with upper letter in directory (#1494)

* Stage execute fix (#1525)

Added more tests for stage execute

* Use IDENTIFIER for string literals (#1409)

* Fix git setup with FQN repo names (#1530)

* Snow 1644844 git setup api integration naming fix (#1532)

* git setup: proposed object name should be unique

* add unit tests

* self review

* fix tests

* existing secret can be FQN

* update release notes

* Fetch existing integration in single query

* fix unit tests

* fix release notes

* fix diag_log_path default value (#1533)

* fix diag_log_path default value

* fix tests

* Add unit test checking whether default value got overridden

* Snow 1650685 fix snowpark package (#1541)

* bump version and release notes

* update snapshots

---------

Co-authored-by: Jorge Vasquez Rojas <jorge.vasquezrojas@snowflake.com>
Co-authored-by: Adam Stus <adam.stus@snowflake.com>
Co-authored-by: Tomasz Urbaszek <tomasz.urbaszek@snowflake.com>
  • Loading branch information
4 people committed Sep 10, 2024
1 parent ff742a5 commit b1231dc
Show file tree
Hide file tree
Showing 45 changed files with 957 additions and 332 deletions.
14 changes: 14 additions & 0 deletions RELEASE-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,20 @@
## Fixes and improvements


# v2.8.1
## Backward incompatibility

## Deprecations

## New additions

## Fixes and improvements
* Fixed git execute not working with upper case in directory name.
* Fixed `snow git setup` command behaviour for fully qualified repository names.
* Fixed `snow git setup` command behaviour in case API integration or secret with default name already exists.
* Fixed `snow snowpark package create` creating empty zip when package name contained capital letters.


# v2.8.0
## Backward incompatibility

Expand Down
2 changes: 1 addition & 1 deletion src/snowflake/cli/__about__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@

from __future__ import annotations

VERSION = "2.8.0rc1"
VERSION = "2.8.1"
30 changes: 26 additions & 4 deletions src/snowflake/cli/api/commands/flags.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from snowflake.cli.api.commands.typer_pre_execute import register_pre_execute_command
from snowflake.cli.api.console import cli_console
from snowflake.cli.api.exceptions import MissingConfiguration
from snowflake.cli.api.identifiers import FQN
from snowflake.cli.api.output.formats import OutputFormat
from snowflake.cli.api.project.definition_manager import DefinitionManager
from snowflake.cli.api.rendering.jinja import CONTEXT_KEY
Expand Down Expand Up @@ -350,13 +351,23 @@ def _password_callback(value: str):
rich_help_panel=_CONNECTION_SECTION,
)

# Set default via callback to avoid including tempdir path in generated docs (snow --docs).
# Use constant instead of None, as None is removed from telemetry data.
_DIAG_LOG_DEFAULT_VALUE = "<temporary_directory>"


def _diag_log_path_callback(path: str):
if path == _DIAG_LOG_DEFAULT_VALUE:
path = tempfile.gettempdir()
cli_context_manager.connection_context.set_diag_log_path(Path(path))
return path


DiagLogPathOption: Path = typer.Option(
tempfile.gettempdir(),
"--diag-log-path",
help="Diagnostic report path",
callback=_callback(
lambda: cli_context_manager.connection_context.set_diag_log_path
),
callback=_diag_log_path_callback,
show_default=False,
rich_help_panel=_CONNECTION_SECTION,
exists=True,
Expand Down Expand Up @@ -514,11 +525,15 @@ def experimental_option(
)


def identifier_argument(sf_object: str, example: str) -> typer.Argument:
def identifier_argument(
sf_object: str, example: str, callback: Callable | None = None
) -> typer.Argument:
return typer.Argument(
...,
help=f"Identifier of the {sf_object}. For example: {example}",
show_default=False,
click_type=IdentifierType(),
callback=callback,
)


Expand Down Expand Up @@ -638,3 +653,10 @@ def parse_key_value_variables(variables: Optional[List[str]]) -> List[Variable]:
key, value = p.split("=", 1)
result.append(Variable(key.strip(), value.strip()))
return result


class IdentifierType(click.ParamType):
name = "TEXT"

def convert(self, value, param, ctx):
return FQN.from_string(value)
21 changes: 17 additions & 4 deletions src/snowflake/cli/api/identifiers.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,17 @@ class FQN:
fqn = FQN.from_string("my_name").set_database("db").set_schema("foo")
"""

def __init__(self, database: str | None, schema: str | None, name: str):
def __init__(
self,
database: str | None,
schema: str | None,
name: str,
signature: str | None = None,
):
self._database = database
self._schema = schema
self._name = name
self.signature = signature

@property
def database(self) -> str | None:
Expand Down Expand Up @@ -72,6 +79,8 @@ def url_identifier(self) -> str:

@property
def sql_identifier(self) -> str:
if self.signature:
return f"IDENTIFIER('{self.identifier}'){self.signature}"
return f"IDENTIFIER('{self.identifier}')"

def __str__(self):
Expand All @@ -98,9 +107,13 @@ def from_string(cls, identifier: str) -> "FQN":
else:
database = None
schema = result.group("first_qualifier")
if signature := result.group("signature"):
unqualified_name = unqualified_name + signature
return cls(name=unqualified_name, schema=schema, database=database)

signature = None
if result.group("signature"):
signature = result.group("signature")
return cls(
name=unqualified_name, schema=schema, database=database, signature=signature
)

@classmethod
def from_stage(cls, stage: str) -> "FQN":
Expand Down
8 changes: 4 additions & 4 deletions src/snowflake/cli/api/sql_execution.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,23 +147,23 @@ def use_warehouse(self, new_wh: str):
self.use(object_type=ObjectType.WAREHOUSE, name=prev_wh)

def create_password_secret(
self, name: str, username: str, password: str
self, name: FQN, username: str, password: str
) -> SnowflakeCursor:
return self._execute_query(
f"""
create secret {name}
create secret {name.sql_identifier}
type = password
username = '{username}'
password = '{password}'
"""
)

def create_api_integration(
self, name: str, api_provider: str, allowed_prefix: str, secret: Optional[str]
self, name: FQN, api_provider: str, allowed_prefix: str, secret: Optional[str]
) -> SnowflakeCursor:
return self._execute_query(
f"""
create api integration {name}
create api integration {name.sql_identifier}
api_provider = {api_provider}
api_allowed_prefixes = ('{allowed_prefix}')
allowed_authentication_secrets = ({secret if secret else ''})
Expand Down
87 changes: 68 additions & 19 deletions src/snowflake/cli/plugins/git/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import logging
from os import path
from pathlib import Path
from typing import List, Optional
from typing import Dict, List, Optional

import typer
from click import ClickException
Expand All @@ -41,6 +41,7 @@
)
from snowflake.cli.plugins.object.manager import ObjectManager
from snowflake.cli.plugins.stage.manager import OnErrorType
from snowflake.connector import DictCursor

app = SnowTyperFactory(
name="git",
Expand Down Expand Up @@ -82,10 +83,12 @@ def _repo_path_argument_callback(path):
scope_option=scope_option(help_example="`list --in database my_db`"),
)

from snowflake.cli.api.identifiers import FQN

def _assure_repository_does_not_exist(om: ObjectManager, repository_name: str) -> None:

def _assure_repository_does_not_exist(om: ObjectManager, repository_name: FQN) -> None:
if om.object_exists(
object_type=ObjectType.GIT_REPOSITORY.value.cli_name, name=repository_name
object_type=ObjectType.GIT_REPOSITORY.value.cli_name, fqn=repository_name
):
raise ClickException(f"Repository '{repository_name}' already exists")

Expand All @@ -95,9 +98,27 @@ def _validate_origin_url(url: str) -> None:
raise ClickException("Url address should start with 'https'")


def _unique_new_object_name(
om: ObjectManager, object_type: ObjectType, proposed_fqn: FQN
) -> str:
existing_objects: List[Dict] = om.show(
object_type=object_type.value.cli_name,
like=f"{proposed_fqn.name}%",
cursor_class=DictCursor,
).fetchall()
existing_names = set(o["name"].upper() for o in existing_objects)

result = proposed_fqn.name
i = 1
while result.upper() in existing_names:
result = proposed_fqn.name + str(i)
i += 1
return result


@app.command("setup", requires_connection=True)
def setup(
repository_name: str = RepoNameArgument,
repository_name: FQN = RepoNameArgument,
**options,
) -> CommandResult:
"""
Expand All @@ -123,12 +144,29 @@ def setup(
should_create_secret = False
secret_name = None
if secret_needed:
secret_name = f"{repository_name}_secret"
secret_name = typer.prompt(
"Secret identifier (will be created if not exists)", default=secret_name
default_secret_name = (
FQN.from_string(f"{repository_name.name}_secret")
.set_schema(repository_name.schema)
.set_database(repository_name.database)
)
default_secret_name.set_name(
_unique_new_object_name(
om, object_type=ObjectType.SECRET, proposed_fqn=default_secret_name
),
)
secret_name = FQN.from_string(
typer.prompt(
"Secret identifier (will be created if not exists)",
default=default_secret_name.name,
)
)
if not secret_name.database:
secret_name.set_database(repository_name.database)
if not secret_name.schema:
secret_name.set_schema(repository_name.schema)

if om.object_exists(
object_type=ObjectType.SECRET.value.cli_name, name=secret_name
object_type=ObjectType.SECRET.value.cli_name, fqn=secret_name
):
cli_console.step(f"Using existing secret '{secret_name}'")
else:
Expand All @@ -137,10 +175,17 @@ def setup(
secret_username = typer.prompt("username")
secret_password = typer.prompt("password/token", hide_input=True)

api_integration = f"{repository_name}_api_integration"
api_integration = typer.prompt(
"API integration identifier (will be created if not exists)",
default=api_integration,
# API integration is an account-level object
api_integration = FQN.from_string(f"{repository_name.name}_api_integration")
api_integration.set_name(
typer.prompt(
"API integration identifier (will be created if not exists)",
default=_unique_new_object_name(
om,
object_type=ObjectType.INTEGRATION,
proposed_fqn=api_integration,
),
)
)

if should_create_secret:
Expand All @@ -150,7 +195,7 @@ def setup(
cli_console.step(f"Secret '{secret_name}' successfully created.")

if not om.object_exists(
object_type=ObjectType.INTEGRATION.value.cli_name, name=api_integration
object_type=ObjectType.INTEGRATION.value.cli_name, fqn=api_integration
):
manager.create_api_integration(
name=api_integration,
Expand All @@ -177,7 +222,7 @@ def setup(
requires_connection=True,
)
def list_branches(
repository_name: str = RepoNameArgument,
repository_name: FQN = RepoNameArgument,
like=like_option(
help_example='`list-branches --like "%_test"` lists all branches that end with "_test"'
),
Expand All @@ -186,15 +231,17 @@ def list_branches(
"""
List all branches in the repository.
"""
return QueryResult(GitManager().show_branches(repo_name=repository_name, like=like))
return QueryResult(
GitManager().show_branches(repo_name=repository_name.identifier, like=like)
)


@app.command(
"list-tags",
requires_connection=True,
)
def list_tags(
repository_name: str = RepoNameArgument,
repository_name: FQN = RepoNameArgument,
like=like_option(
help_example='`list-tags --like "v2.0%"` lists all tags that start with "v2.0"'
),
Expand All @@ -203,7 +250,9 @@ def list_tags(
"""
List all tags in the repository.
"""
return QueryResult(GitManager().show_tags(repo_name=repository_name, like=like))
return QueryResult(
GitManager().show_tags(repo_name=repository_name.identifier, like=like)
)


@app.command(
Expand All @@ -228,13 +277,13 @@ def list_files(
requires_connection=True,
)
def fetch(
repository_name: str = RepoNameArgument,
repository_name: FQN = RepoNameArgument,
**options,
) -> CommandResult:
"""
Fetch changes from origin to Snowflake repository.
"""
return QueryResult(GitManager().fetch(repo_name=repository_name))
return QueryResult(GitManager().fetch(fqn=repository_name))


@app.command(
Expand Down
Loading

0 comments on commit b1231dc

Please sign in to comment.