Skip to content

Commit

Permalink
change error handling to be default our fault, add list of error code…
Browse files Browse the repository at this point in the history
…s to check for user fault
  • Loading branch information
sfc-gh-mchok committed Nov 27, 2024
1 parent 8d4e35b commit 52af246
Show file tree
Hide file tree
Showing 4 changed files with 192 additions and 70 deletions.
30 changes: 25 additions & 5 deletions src/snowflake/cli/_plugins/nativeapp/entities/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
from snowflake.cli._plugins.workspace.context import ActionContext
from snowflake.cli.api.cli_global_context import get_cli_context, span
from snowflake.cli.api.console.abc import AbstractConsole
from snowflake.cli.api.constants import ObjectType
from snowflake.cli.api.entities.common import (
EntityBase,
attach_spans_to_entity_actions,
Expand All @@ -83,6 +84,7 @@
from snowflake.cli.api.project.schemas.updatable_model import DiscriminatorField
from snowflake.cli.api.project.util import (
append_test_resource_suffix,
extract_schema,
identifier_for_url,
to_identifier,
unquote_identifier,
Expand Down Expand Up @@ -634,11 +636,29 @@ def _create_app(
self.console.step(f"Creating new application object {self.name} in account.")

if package.role != self.role:
get_snowflake_facade().grant_privileges_for_create_application(
package_role=package.role,
package_name=package.name,
stage_fqn=stage_fqn,
app_role=self.role,
get_snowflake_facade().grant_privileges_to_role(
privileges=["install", "develop"],
object_type=ObjectType.APPLICATION_PACKAGE,
object_identifier=package.name,
role_to_grant=self.role,
role_to_use=package.role,
)

stage_schema = extract_schema(stage_fqn)
get_snowflake_facade().grant_privileges_to_role(
privileges=["usage"],
object_type=ObjectType.SCHEMA,
object_identifier=f"{package.name}.{stage_schema}",
role_to_grant=self.role,
role_to_use=package.role,
)

get_snowflake_facade().grant_privileges_to_role(
privileges=["read"],
object_type=ObjectType.STAGE,
object_identifier=stage_fqn,
role_to_grant=self.role,
role_to_use=package.role,
)

return get_snowflake_facade().create_application(
Expand Down
74 changes: 74 additions & 0 deletions src/snowflake/cli/_plugins/nativeapp/sf_facade_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,40 @@
from click import ClickException
from snowflake.cli._plugins.nativeapp.sf_facade_constants import UseObjectType
from snowflake.cli.api.errno import (
APPLICATION_FILE_NOT_FOUND_ON_STAGE,
APPLICATION_INSTANCE_EMPTY_SETUP_SCRIPT,
APPLICATION_INSTANCE_FAILED_TO_RUN_SETUP_SCRIPT,
APPLICATION_INSTANCE_NO_ACTIVE_WAREHOUSE_FOR_CREATE_OR_UPGRADE,
APPLICATION_NO_LONGER_AVAILABLE,
APPLICATION_PACKAGE_CANNOT_SET_EXTERNAL_DISTRIBUTION_WITH_SPCS,
APPLICATION_PACKAGE_MANIFEST_CONTAINER_IMAGE_URL_BAD_VALUE,
APPLICATION_PACKAGE_MANIFEST_SPECIFIED_FILE_NOT_FOUND,
APPLICATION_PACKAGE_PATCH_DOES_NOT_EXIST,
CANNOT_GRANT_NON_MANIFEST_PRIVILEGE,
CANNOT_GRANT_OBJECT_NOT_IN_APP_PACKAGE,
CANNOT_GRANT_RESTRICTED_PRIVILEGE_TO_APP_PACKAGE_SHARE,
CANNOT_UPGRADE_FROM_LOOSE_FILES_TO_VERSION,
CANNOT_UPGRADE_FROM_VERSION_TO_LOOSE_FILES,
DOES_NOT_EXIST_OR_CANNOT_BE_PERFORMED,
DOES_NOT_EXIST_OR_NOT_AUTHORIZED,
DUPLICATE_COLUMN_NAME,
INSUFFICIENT_PRIVILEGES,
NATIVE_APPLICATION_MANIFEST_GENERIC_JSON_ERROR,
NATIVE_APPLICATION_MANIFEST_INVALID_SYNTAX,
NATIVE_APPLICATION_MANIFEST_UNEXPECTED_VALUE_FOR_PROPERTY,
NATIVE_APPLICATION_MANIFEST_UNRECOGNIZED_FIELD,
NO_INDIVIDUAL_PRIVS,
NO_REFERENCE_SET_FOR_DEFINITION,
NO_VERSIONS_AVAILABLE_FOR_ACCOUNT,
NOT_SUPPORTED_ON_DEV_MODE_APPLICATIONS,
OBJECT_ALREADY_EXISTS_IN_DOMAIN,
OBJECT_ALREADY_EXISTS_NO_PRIVILEGES,
ONLY_SUPPORTED_ON_DEV_MODE_APPLICATIONS,
ROLE_NOT_ASSIGNED,
SNOWSERVICES_IMAGE_MANIFEST_NOT_FOUND,
SNOWSERVICES_IMAGE_REPOSITORY_FAILS_TO_RETRIEVE_IMAGE_HASH_NEW,
SNOWSERVICES_IMAGE_REPOSITORY_IMAGE_IMPORT_TO_NATIVE_APP_FAIL,
VIEW_EXPANSION_FAILED,
)
from snowflake.connector import DatabaseError, Error, ProgrammingError

Expand All @@ -35,6 +64,51 @@
APPLICATION_NO_LONGER_AVAILABLE,
}

CREATE_OR_UPGRADE_APPLICATION_EXPECTED_USER_ERROR_CODES = {
APPLICATION_INSTANCE_FAILED_TO_RUN_SETUP_SCRIPT,
# user tried to do something they didn't have permission to
INSUFFICIENT_PRIVILEGES,
NATIVE_APPLICATION_MANIFEST_GENERIC_JSON_ERROR,
# user tried to create two objects with the same name
OBJECT_ALREADY_EXISTS_IN_DOMAIN,
APPLICATION_INSTANCE_NO_ACTIVE_WAREHOUSE_FOR_CREATE_OR_UPGRADE,
# user tried to access object that doesn't exist or operation that's not allowed
DOES_NOT_EXIST_OR_CANNOT_BE_PERFORMED,
# when setup script/manifest/readme isn't on the stage
APPLICATION_FILE_NOT_FOUND_ON_STAGE,
NATIVE_APPLICATION_MANIFEST_UNRECOGNIZED_FIELD,
# user tried to access an object they don't have permission to
OBJECT_ALREADY_EXISTS_NO_PRIVILEGES,
SNOWSERVICES_IMAGE_MANIFEST_NOT_FOUND,
# user tried to clone tables and it failed
VIEW_EXPANSION_FAILED,
# user tried to do something with a role that wasn't assigned to them
ROLE_NOT_ASSIGNED,
APPLICATION_PACKAGE_MANIFEST_SPECIFIED_FILE_NOT_FOUND,
# user tried to grant individual privilege on imported objects
NO_INDIVIDUAL_PRIVS,
SNOWSERVICES_IMAGE_REPOSITORY_IMAGE_IMPORT_TO_NATIVE_APP_FAIL,
# user tried to resolve a table/view from a name, but it was not found
DOES_NOT_EXIST_OR_NOT_AUTHORIZED,
APPLICATION_PACKAGE_PATCH_DOES_NOT_EXIST,
APPLICATION_PACKAGE_MANIFEST_CONTAINER_IMAGE_URL_BAD_VALUE,
SNOWSERVICES_IMAGE_REPOSITORY_FAILS_TO_RETRIEVE_IMAGE_HASH_NEW,
NATIVE_APPLICATION_MANIFEST_UNEXPECTED_VALUE_FOR_PROPERTY,
CANNOT_GRANT_NON_MANIFEST_PRIVILEGE,
NO_REFERENCE_SET_FOR_DEFINITION,
NATIVE_APPLICATION_MANIFEST_INVALID_SYNTAX,
CANNOT_GRANT_OBJECT_NOT_IN_APP_PACKAGE,
APPLICATION_PACKAGE_MANIFEST_SPECIFIED_FILE_NOT_FOUND,
# user tried installing from release directive and there are none available
NO_VERSIONS_AVAILABLE_FOR_ACCOUNT,
# user tried to create a table with duplicate column names
DUPLICATE_COLUMN_NAME,
APPLICATION_PACKAGE_MANIFEST_CONTAINER_IMAGE_URL_BAD_VALUE,
APPLICATION_INSTANCE_EMPTY_SETUP_SCRIPT,
APPLICATION_PACKAGE_CANNOT_SET_EXTERNAL_DISTRIBUTION_WITH_SPCS,
CANNOT_GRANT_RESTRICTED_PRIVILEGE_TO_APP_PACKAGE_SHARE,
}


def handle_unclassified_error(err: Error | Exception, context: str) -> NoReturn:
"""
Expand Down
110 changes: 49 additions & 61 deletions src/snowflake/cli/_plugins/nativeapp/sf_sql_facade.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
)
from snowflake.cli._plugins.nativeapp.sf_facade_constants import UseObjectType
from snowflake.cli._plugins.nativeapp.sf_facade_exceptions import (
CREATE_OR_UPGRADE_APPLICATION_EXPECTED_USER_ERROR_CODES,
UPGRADE_RESTRICTION_CODES,
CouldNotUseObjectError,
InsufficientPrivilegesError,
Expand All @@ -49,7 +50,6 @@
from snowflake.cli.api.identifiers import FQN
from snowflake.cli.api.metrics import CLICounterField
from snowflake.cli.api.project.util import (
extract_schema,
identifier_to_show_like_pattern,
is_valid_unquoted_identifier,
to_identifier,
Expand Down Expand Up @@ -155,7 +155,7 @@ def _use_schema_optional(self, schema_name: str | None):
"""
return self._use_object_optional(UseObjectType.SCHEMA, schema_name)

def _grant_privileges_to_role(
def grant_privileges_to_role(
self,
privileges: list[str],
object_type: ObjectType,
Expand All @@ -176,9 +176,15 @@ def _grant_privileges_to_role(
object_type_and_name = f"{object_type.value.sf_name} {object_identifier}"

with self._use_role_optional(role_to_use):
self._sql_executor.execute_query(
f"grant {comma_separated_privileges} on {object_type_and_name} to role {role_to_grant}"
)
try:
self._sql_executor.execute_query(
f"grant {comma_separated_privileges} on {object_type_and_name} to role {role_to_grant}"
)
except Exception as err:
handle_unclassified_error(
err,
f"Failed to grant {comma_separated_privileges} on {object_type_and_name} to role {role_to_grant}.",
)

def execute_user_script(
self,
Expand Down Expand Up @@ -588,6 +594,14 @@ def upgrade_application(
) -> list[tuple[str]]:
"""
Upgrades an application object using the provided clauses
@param name: Name of the application object
@param install_method: Method of installing the application
@param stage_fqn: FQN of the stage housing the application artifacts
@param role: Role to use when creating the application and provider-side objects
@param warehouse: Warehouse which is required to create an application object
@param debug_mode: Whether to enable debug mode; None means not explicitly enabled or disabled
@param should_authorize_event_sharing: Whether to enable event sharing; None means not explicitly enabled or disabled
"""
install_method.ensure_app_usable(
app_name=name,
Expand All @@ -612,10 +626,14 @@ def upgrade_application(
except ProgrammingError as err:
if err.errno in UPGRADE_RESTRICTION_CODES:
raise UpgradeApplicationRestrictionError(err.msg) from err
raise UserInputError(
f"Failed to upgrade application {name} with the following error message:\n"
f"{err.msg}"
) from err
elif (
err.errno in CREATE_OR_UPGRADE_APPLICATION_EXPECTED_USER_ERROR_CODES
):
raise UserInputError(
f"Failed to upgrade application {name} with the following error message:\n"
f"{err.msg}"
) from err
handle_unclassified_error(err, f"Failed to upgrade application {name}.")
except Exception as err:
handle_unclassified_error(err, f"Failed to upgrade application {name}.")

Expand Down Expand Up @@ -647,11 +665,10 @@ def upgrade_application(
raise UserInputError(
"Could not disable telemetry event sharing for the application because it contains mandatory events. Please set 'share_mandatory_events' to true in the application telemetry section of the project definition file."
) from err

raise UserInputError(
f"Failed to set AUTHORIZE_TELEMETRY_EVENT_SHARING when upgrading application {name} with the following error message:\n"
f"{err.msg}"
) from err
handle_unclassified_error(
err,
f"Failed to set AUTHORIZE_TELEMETRY_EVENT_SHARING when upgrading application {name}.",
)
except Exception as err:
handle_unclassified_error(
err,
Expand All @@ -660,48 +677,6 @@ def upgrade_application(

return upgrade_cursor.fetchall()

def grant_privileges_for_create_application(
self, package_role: str, package_name: str, stage_fqn: str, app_role: str
) -> None:
"""
Grants the required privileges to create an application to an
app role when the package role and the app role are not the same
"""
try:
self._grant_privileges_to_role(
privileges=["install", "develop"],
object_type=ObjectType.APPLICATION_PACKAGE,
object_identifier=package_name,
role_to_grant=app_role,
role_to_use=package_role,
)

stage_schema = extract_schema(stage_fqn)
self._grant_privileges_to_role(
privileges=["usage"],
object_type=ObjectType.SCHEMA,
object_identifier=f"{package_name}.{stage_schema}",
role_to_grant=app_role,
role_to_use=package_role,
)

self._grant_privileges_to_role(
privileges=["read"],
object_type=ObjectType.STAGE,
object_identifier=stage_fqn,
role_to_grant=app_role,
role_to_use=package_role,
)
except ProgrammingError as err:
raise UserInputError(
f"Failed to grant the required privileges to create an application with the following error message:\n"
f"{err.msg}"
) from err
except Exception as err:
handle_unclassified_error(
err, "Failed to grant the required privileges to create an application"
)

def create_application(
self,
name: str,
Expand All @@ -716,6 +691,15 @@ def create_application(
"""
Creates a new application object using an application package,
running the setup script of the application package
@param name: Name of the application object
@param package_name: Name of the application package to install the application from
@param install_method: Method of installing the application
@param stage_fqn: FQN of the stage housing the application artifacts
@param role: Role to use when creating the application and provider-side objects
@param warehouse: Warehouse which is required to create an application object
@param debug_mode: Whether to enable debug mode; None means not explicitly enabled or disabled
@param should_authorize_event_sharing: Whether to enable event sharing; None means not explicitly enabled or disabled
"""

# by default, applications are created in debug mode when possible;
Expand Down Expand Up @@ -753,13 +737,17 @@ def create_application(
raise UserInputError(
"The application package requires event sharing to be authorized. Please set 'share_mandatory_events' to true in the application telemetry section of the project definition file."
) from err

raise UserInputError(
f"Failed to create application {name} with the following error message:\n"
f"{err.msg}"
) from err
elif (
err.errno in CREATE_OR_UPGRADE_APPLICATION_EXPECTED_USER_ERROR_CODES
):
raise UserInputError(
f"Failed to create application {name} with the following error message:\n"
f"{err.msg}"
) from err
handle_unclassified_error(err, f"Failed to create application {name}.")
except Exception as err:
handle_unclassified_error(err, f"Failed to create application {name}.")

return create_cursor.fetchall()


Expand Down
48 changes: 44 additions & 4 deletions src/snowflake/cli/api/errno.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,58 @@

# General errors
NO_WAREHOUSE_SELECTED_IN_SESSION = 606
EMPTY_SQL_STATEMENT = 900

DOES_NOT_EXIST_OR_NOT_AUTHORIZED = 2003
DOES_NOT_EXIST_OR_CANNOT_BE_PERFORMED = 2043
INSUFFICIENT_PRIVILEGES = 3001
SQL_COMPILATION_ERROR = 1003
OBJECT_ALREADY_EXISTS_IN_DOMAIN = 1998
OBJECT_ALREADY_EXISTS = 2002
DOES_NOT_EXIST_OR_NOT_AUTHORIZED = 2003 # BASE_TABLE_OR_VIEW_NOT_FOUND
DUPLICATE_COLUMN_NAME = 2025
VIEW_EXPANSION_FAILED = 2037
DOES_NOT_EXIST_OR_CANNOT_BE_PERFORMED = (
2043 # OBJECT_DOES_NOT_EXIST_OR_CANNOT_PERFORM_OPERATION
)
INSUFFICIENT_PRIVILEGES = 3001 # NOT_AUTHORIZED
INVALID_OBJECT_TYPE_FOR_SPECIFIED_PRIVILEGE = 3008
ROLE_NOT_ASSIGNED = 3013
NO_INDIVIDUAL_PRIVS = 3028
OBJECT_ALREADY_EXISTS_NO_PRIVILEGES = 3041

# Native Apps
APPLICATION_PACKAGE_MANIFEST_SPECIFIED_FILE_NOT_FOUND = 93003
APPLICATION_FILE_NOT_FOUND_ON_STAGE = 93009
CANNOT_GRANT_OBJECT_NOT_IN_APP_PACKAGE = 93011
CANNOT_GRANT_RESTRICTED_PRIVILEGE_TO_APP_PACKAGE_SHARE = 93012
APPLICATION_PACKAGE_VERSION_ALREADY_EXISTS = 93030
APPLICATION_PACKAGE_VERSION_NAME_TOO_LONG = 93035
APPLICATION_PACKAGE_PATCH_DOES_NOT_EXIST = 93036
APPLICATION_PACKAGE_MAX_VERSIONS_HIT = 93037
CANNOT_UPGRADE_FROM_LOOSE_FILES_TO_VERSION = 93044
CANNOT_UPGRADE_FROM_VERSION_TO_LOOSE_FILES = 93045
ONLY_SUPPORTED_ON_DEV_MODE_APPLICATIONS = 93046
NO_VERSIONS_AVAILABLE_FOR_ACCOUNT = 93054
NOT_SUPPORTED_ON_DEV_MODE_APPLICATIONS = 93055
APPLICATION_NO_LONGER_AVAILABLE = 93079
APPLICATION_INSTANCE_FAILED_TO_RUN_SETUP_SCRIPT = 93082
APPLICATION_INSTANCE_NO_ACTIVE_WAREHOUSE_FOR_CREATE_OR_UPGRADE = 93083
APPLICATION_INSTANCE_EMPTY_SETUP_SCRIPT = 93084
APPLICATION_PACKAGE_CANNOT_DROP_VERSION_IF_IT_IS_IN_USE = 93088
APPLICATION_PACKAGE_MANIFEST_CONTAINER_IMAGE_URL_BAD_VALUE = 93148
CANNOT_GRANT_NON_MANIFEST_PRIVILEGE = 93118
APPLICATION_OWNS_EXTERNAL_OBJECTS = 93128
APPLICATION_PACKAGE_PATCH_ALREADY_EXISTS = 93168
APPLICATION_PACKAGE_CANNOT_SET_EXTERNAL_DISTRIBUTION_WITH_SPCS = 93197
NATIVE_APPLICATION_MANIFEST_UNRECOGNIZED_FIELD = 93301
NATIVE_APPLICATION_MANIFEST_UNEXPECTED_VALUE_FOR_PROPERTY = 93302
NATIVE_APPLICATION_MANIFEST_GENERIC_JSON_ERROR = 93303
NATIVE_APPLICATION_MANIFEST_INVALID_SYNTAX = 93300
APPLICATION_REQUIRES_TELEMETRY_SHARING = 93321
CANNOT_DISABLE_MANDATORY_TELEMETRY = 93329
APPLICATION_INSTANCE_FAILED_TO_RUN_SETUP_SCRIPT = 93082

ERR_JAVASCRIPT_EXECUTION = 100132
SNOWSERVICES_IMAGE_REPOSITORY_IMAGE_IMPORT_TO_NATIVE_APP_FAIL = 397007
SNOWSERVICES_IMAGE_MANIFEST_NOT_FOUND = 397012
SNOWSERVICES_IMAGE_REPOSITORY_FAILS_TO_RETRIEVE_IMAGE_HASH_NEW = 397013

NO_REFERENCE_SET_FOR_DEFINITION = 505019
NO_ACTIVE_REF_DEFINITION_WITH_REF_NAME_IN_APPLICATION = 505026

0 comments on commit 52af246

Please sign in to comment.