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

move create/alter app upgrade calls to sql facade to reclassify errors cause by setup script execution #1870

Merged
merged 29 commits into from
Dec 3, 2024

Conversation

sfc-gh-mchok
Copy link
Collaborator

@sfc-gh-mchok sfc-gh-mchok commented Nov 19, 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 on MacOS.
  • I've confirmed that my changes are working by executing CLI's commands manually on Windows.
  • 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

  • Move sql calls that execute setup scripts (CREATE APPLICATION and ALTER APPLICATION UPGRADE) to the SQL Facade, and reclassify all ProgrammingErrors coming out of these calls as UserInputErrors
  • Update tests that use these calls to mock the sql facade

@sfc-gh-mchok sfc-gh-mchok changed the title Mchok create or alter app to sqlfacade move create/alter app upgrade calls to sql facade to reclassify errors cause by setup script execution Nov 20, 2024
@sfc-gh-mchok sfc-gh-mchok marked this pull request as ready for review November 20, 2024 21:11
@sfc-gh-mchok sfc-gh-mchok requested review from a team as code owners November 20, 2024 21:12
src/snowflake/cli/_plugins/nativeapp/sf_sql_facade.py Outdated Show resolved Hide resolved
*mock_sql_facade_create_application.mock_calls,
] == expected

mock_console.warning.assert_called_once_with(
Copy link
Contributor

Choose a reason for hiding this comment

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

why is application creation a warning? or has it always been the case?

Copy link
Collaborator Author

@sfc-gh-mchok sfc-gh-mchok Nov 21, 2024

Choose a reason for hiding this comment

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

I believe it's always been the case if we look here, but we've always had the mock return None (so that the warning isn't called), which I don't think reflects reality, so I updated the tests for that

Comment on lines 611 to 613
DEFAULT_UPGRADE_SUCCESS_MESSAGE
if is_upgrade
else DEFAULT_CREATE_SUCCESS_MESSAGE
Copy link
Contributor

Choose a reason for hiding this comment

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

for simplicity, you can have the same message since this is within the same test. We already check the mocks for upgrade/create, so it's hard to mess this up.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

For sure, updated here: 17f761b

Copy link
Contributor

@sfc-gh-pjafari sfc-gh-pjafari left a comment

Choose a reason for hiding this comment

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

stil reading :D

@sfc-gh-mchok sfc-gh-mchok force-pushed the mchok-create-or-alter-app-to-sqlfacade branch from 23d0bfe to f1f00f0 Compare November 25, 2024 15:43
Comment on lines 674 to 680
with sql_executor.use_role(self.role):
event_sharing = EventSharingHandler(
telemetry_definition=model.telemetry,
telemetry_definition=self._entity_model.telemetry,
deploy_root=package.deploy_root,
install_method=install_method,
console=console,
console=self._workspace_ctx.console,
)
Copy link
Contributor

Choose a reason for hiding this comment

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

So about this use_role block :D
instantiating an EventSharingHandler shouldn't need to be in a use_role block. If it needs to be done with a specific role, pass the role in.

Looking at implementation of get_existing_app_info, it seems that it could be moved as a whole to the facade. Just make sure you pass an optional role in, similar to other methods in the facade.

For _upgrade_app and _create_app, you're already doing role handling in them.

It seems that this use_role is trivial now upon changes above and can be removed.

Comment on lines 63 to 71
# Reasons why an `alter application ... upgrade` might fail
UPGRADE_RESTRICTION_CODES = {
CANNOT_UPGRADE_FROM_LOOSE_FILES_TO_VERSION,
CANNOT_UPGRADE_FROM_VERSION_TO_LOOSE_FILES,
ONLY_SUPPORTED_ON_DEV_MODE_APPLICATIONS,
NOT_SUPPORTED_ON_DEV_MODE_APPLICATIONS,
APPLICATION_NO_LONGER_AVAILABLE,
}

Copy link
Contributor

@sfc-gh-pjafari sfc-gh-pjafari Nov 26, 2024

Choose a reason for hiding this comment

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

You can move these to sf_facade_exceptions file :)

Comment on lines 555 to 603
try:
using_clause = install_method.using_clause(stage_fqn)
upgrade_cursor = self._sql_executor.execute_query(
f"alter application {name} upgrade {using_clause}",
)

# if debug_mode is present (controlled), ensure it is up-to-date
if install_method.is_dev_mode:
if debug_mode is not None:
self._sql_executor.execute_query(
f"alter application {name} set debug_mode = {debug_mode}"
)

# Only update event sharing if the current value is different as the one we want to set
if should_authorize_event_sharing is not None:
current_authorize_event_sharing = (
self.get_app_properties(name, role)
.get(AUTHORIZE_TELEMETRY_COL, "false")
.lower()
== "true"
)
if (
current_authorize_event_sharing
!= should_authorize_event_sharing
):
self._log.info(
"Setting telemetry sharing authorization to %s",
should_authorize_event_sharing,
)
self._sql_executor.execute_query(
f"alter application {name} set AUTHORIZE_TELEMETRY_EVENT_SHARING = {str(should_authorize_event_sharing).upper()}"
)
except ProgrammingError as err:
if err.errno in UPGRADE_RESTRICTION_CODES:
raise UpgradeApplicationRestrictionError(err.msg) from err
elif err.errno == CANNOT_DISABLE_MANDATORY_TELEMETRY:
get_cli_context().metrics.set_counter(
CLICounterField.EVENT_SHARING_ERROR, 1
)
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 upgrade application {name} with the following error message:\n"
f"{err.msg}"
) from err
except Exception as err:
handle_unclassified_error(err, f"Failed to upgrade application {name}.")
Copy link
Contributor

Choose a reason for hiding this comment

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

In create case, it makes sense that you have all the error handling in one block since you send one query with the clause.
Here however, you can separate your sql calls in try blocks of their own to make the error handling more readable.
You can also move the telemetry upgrade block into its own private method here, like _update_telemetry_authorization if you want

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I extracted the logic without creating a helper, I think it looks alright as it is right now, but might be weird with the error raising duplication, but i made the error messages more specialized. Let me know what you think

cc @sfc-gh-melnacouzi

Copy link
Contributor

Choose a reason for hiding this comment

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

  • separate error handling makes sense if there are no shared errors -might look ugly though if there is shared error handling that we need to repeat.
  • separating telemetry authorization is optional given that it is not used somewhere else yet.
    I think you can make the call on this @sfc-gh-mchok based on which one is more readable.

Comment on lines 679 to 680
# 3. If existing application is found, perform a few validations and upgrade the application object.
create_or_upgrade_result = None
Copy link
Contributor

Choose a reason for hiding this comment

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

if we remove the self._upgrade_app() and _create_app() functions (since they do not do much), would this logic become clearer?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think it's ok as it is now - since create and upgrade do different things, e.g. create needs permission grants and upgrade has specific error handling. Either way I don't have strong feelings about it :D

"""

if package_role != role:
self._grant_privileges_for_create_application(
Copy link
Contributor

Choose a reason for hiding this comment

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

i don't think this belongs here tbh - I feel this should be called outside of this function, since it's supposed to "create_application" with specific parameters (not grant stuff)

Comment on lines +768 to +769
sql_executor = get_sql_executor()
with sql_executor.use_role(self.role):
Copy link
Collaborator Author

@sfc-gh-mchok sfc-gh-mchok Nov 29, 2024

Choose a reason for hiding this comment

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

Just adding this line at the top and indenting the rest of the function, since the rest of the calls are to the SQL Facade now we don't have a top-level use role. I believe this doesn't need to use the app warehouse from my testing so I did not add it, but please let me know if I am mistaken

Comment on lines +65 to +80
APPLICATION = ObjectNames("application", "application", "applications")
APPLICATION_PACKAGE = ObjectNames(
"application-package", "application package", "application packages"
)

def __str__(self):
"""This makes using this Enum easier in formatted string"""
return self.value.cli_name


OBJECT_TO_NAMES = {o.value.cli_name: o.value for o in ObjectType}
SUPPORTED_OBJECTS = sorted(OBJECT_TO_NAMES.keys())
UNSUPPORTED_OBJECTS = {
ObjectType.APPLICATION.value.cli_name,
ObjectType.APPLICATION_PACKAGE.value.cli_name,
}
SUPPORTED_OBJECTS = sorted(OBJECT_TO_NAMES.keys() - UNSUPPORTED_OBJECTS)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I wanted to add application and application package as constants to support the new sql facade operations with a cleaner API but I did not want to have them be supported in commands like object list/drop/etc., unsure if there's a better way of handling this

return sql_executor.show_specific_object(
"applications", self.name, name_col=NAME_COL
)
return get_snowflake_facade().get_existing_app_info(self.name, self.role)
Copy link
Contributor

Choose a reason for hiding this comment

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

i am confused, why do we use facade version sometimes, and sometimes use the application one (self.get_existing_app_info)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

My mistake, I forgot to refactor the other places where we call that method, I just replaced them all now

@@ -503,6 +553,198 @@ def show_release_directives(
)
return cursor.fetchall()

def get_existing_app_info(self, name: str, role: str) -> dict | None:
Copy link
Contributor

Choose a reason for hiding this comment

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

why is this different than show_specific_object?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The goal was to also move show_specific_object calls to the facade eventually, this is one of those instances, see #1870 (comment) for initial context

@sfc-gh-melnacouzi sfc-gh-melnacouzi enabled auto-merge (squash) December 3, 2024 03:41
@sfc-gh-melnacouzi sfc-gh-melnacouzi merged commit 6475e23 into main Dec 3, 2024
20 checks passed
@sfc-gh-melnacouzi sfc-gh-melnacouzi deleted the mchok-create-or-alter-app-to-sqlfacade branch December 3, 2024 15:30
sfc-gh-gbloom pushed a commit that referenced this pull request Dec 4, 2024
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.

4 participants