From 570b198071ea56e80d643869b6e018982b50df8b Mon Sep 17 00:00:00 2001 From: Abby Shen Date: Fri, 13 Dec 2024 14:01:53 -0800 Subject: [PATCH 1/9] spcs service events&metrics --- .../cli/_plugins/spcs/services/commands.py | 147 ++++++++++++++++++ .../cli/_plugins/spcs/services/manager.py | 143 ++++++++++++++++- 2 files changed, 289 insertions(+), 1 deletion(-) diff --git a/src/snowflake/cli/_plugins/spcs/services/commands.py b/src/snowflake/cli/_plugins/spcs/services/commands.py index a5d9fdcba0..825b950a89 100644 --- a/src/snowflake/cli/_plugins/spcs/services/commands.py +++ b/src/snowflake/cli/_plugins/spcs/services/commands.py @@ -44,6 +44,7 @@ from snowflake.cli.api.feature_flags import FeatureFlag from snowflake.cli.api.identifiers import FQN from snowflake.cli.api.output.types import ( + CollectionResult, CommandResult, MessageResult, QueryJsonValueResult, @@ -297,6 +298,152 @@ def logs( return StreamResult(cast(Generator[CommandResult, None, None], stream)) +@app.command(requires_connection=True) +def events( + name: FQN = ServiceNameArgument, + container_name: str = typer.Option( + ..., + "--container-name", + help="Name of the container.", + show_default=False, + ), + instance_id: str = typer.Option( + ..., + "--instance-id", + help="ID of the service instance, starting with 0.", + show_default=False, + ), + since: str = typer.Option( + default="", + help="Fetch events that are newer than this time ago, in Snowflake interval syntax.", + ), + until: str = typer.Option( + default="", + help="Fetch events that are older than this time ago, in Snowflake interval syntax.", + ), + first: int = typer.Option( + default=-1, + show_default=False, + help="Fetch only the first N events. Cannot be used with --last.", + ), + last: int = typer.Option( + default=-1, + show_default=False, + help="Fetch only the last N events. Cannot be used with --first.", + ), + **options, +): + """Fetches events for this app from the event table configured in Snowflake.""" + if first >= 0 and last >= 0: + raise IncompatibleParametersError(["--first", "--last"]) + + manager = ServiceManager() + column_names = [ + "TIMESTAMP", + "START_TIMESTAMP", + "OBSERVED_TIMESTAMP", + "TRACE", + "RESOURCE", + "RESOURCE_ATTRIBUTES", + "SCOPE", + "SCOPE_ATTRIBUTES", + "RECORD_TYPE", + "RECORD", + "RECORD_ATTRIBUTES", + "VALUE", + "EXEMPLARS", + ] + + events = manager.get_events( + service_name=name.identifier, + container_name=container_name, + instance_id=instance_id, + since=since, + until=until, + first=first, + last=last, + ) + transformed_events = [dict(zip(column_names, event)) for event in events] + + if not transformed_events: + return MessageResult("No events found.") + + return CollectionResult(transformed_events) + + +@app.command(requires_connection=True) +def metrics( + name: FQN = ServiceNameArgument, + container_name: str = typer.Option( + ..., + "--container-name", + help="Name of the container.", + show_default=False, + ), + instance_id: str = typer.Option( + ..., + "--instance-id", + help="ID of the service instance, starting with 0.", + show_default=False, + ), + since: str = typer.Option( + default="", + help="Fetch events that are newer than this time ago, in Snowflake interval syntax.", + ), + until: str = typer.Option( + default="", + help="Fetch events that are older than this time ago, in Snowflake interval syntax.", + ), + first: int = typer.Option( + default=-1, + show_default=False, + help="Fetch only the first N events. Cannot be used with --last.", + ), + last: int = typer.Option( + default=-1, + show_default=False, + help="Fetch only the last N events. Cannot be used with --first.", + ), + **options, +): + """Fetches events for this app from the event table configured in Snowflake.""" + if first >= 0 and last >= 0: + raise IncompatibleParametersError(["--first", "--last"]) + + manager = ServiceManager() + column_names = [ + "TIMESTAMP", + "START_TIMESTAMP", + "OBSERVED_TIMESTAMP", + "TRACE", + "RESOURCE", + "RESOURCE_ATTRIBUTES", + "SCOPE", + "SCOPE_ATTRIBUTES", + "RECORD_TYPE", + "RECORD", + "RECORD_ATTRIBUTES", + "VALUE", + "EXEMPLARS", + ] + + metrics = manager.get_metrics( + service_name=name.identifier, + container_name=container_name, + instance_id=instance_id, + since=since, + until=until, + first=first, + last=last, + ) + transformed_metrics = [dict(zip(column_names, metrix)) for metrix in metrics] + + if not transformed_metrics: + return MessageResult("No metrics found.") + + return CollectionResult(transformed_metrics) + + @app.command(requires_connection=True) def upgrade( name: FQN = ServiceNameArgument, diff --git a/src/snowflake/cli/_plugins/spcs/services/manager.py b/src/snowflake/cli/_plugins/spcs/services/manager.py index 504fc4cc81..44a9c8bbaa 100644 --- a/src/snowflake/cli/_plugins/spcs/services/manager.py +++ b/src/snowflake/cli/_plugins/spcs/services/manager.py @@ -16,6 +16,7 @@ import json import time +from datetime import datetime from pathlib import Path from typing import List, Optional @@ -31,7 +32,7 @@ from snowflake.cli.api.constants import DEFAULT_SIZE_LIMIT_MB, ObjectType from snowflake.cli.api.secure_path import SecurePath from snowflake.cli.api.sql_execution import SqlExecutionMixin -from snowflake.connector.cursor import SnowflakeCursor +from snowflake.connector.cursor import DictCursor, SnowflakeCursor from snowflake.connector.errors import ProgrammingError @@ -199,6 +200,146 @@ def stream_logs( except KeyboardInterrupt: return + def get_account_event_table(self) -> str: + query = "show parameters like 'event_table' in account" + results = self.execute_query(query, cursor_class=DictCursor) + return next((r["value"] for r in results if r["key"] == "EVENT_TABLE"), "") + + def get_events( + self, + service_name: str, + instance_id: str, + container_name: str, + since: str | datetime | None = None, + until: str | datetime | None = None, + first: int = -1, + last: int = -1, + ): + + account_event_table = self.get_account_event_table() + if not account_event_table: + return [] + + resource_filters = [] + if service_name: + resource_filters.append( + f"resource_attributes:\"snow.service.name\" = '{service_name}'" + ) + if instance_id: + resource_filters.append( + f"(resource_attributes:\"snow.service.instance\" = '{instance_id}' OR resource_attributes:\"snow.service.container.instance\" = '{instance_id}')" + ) + if container_name: + resource_filters.append( + f"resource_attributes:\"snow.service.container.name\" = '{container_name}'" + ) + + resource_clause = " and ".join(resource_filters) if resource_filters else "1=1" + + if isinstance(since, datetime): + since_clause = f"and timestamp >= '{since}'" + elif isinstance(since, str) and since: + since_clause = f"and timestamp >= sysdate() - interval '{since}'" + else: + since_clause = "" + + if isinstance(until, datetime): + until_clause = f"and timestamp <= '{until}'" + elif isinstance(until, str) and until: + until_clause = f"and timestamp <= sysdate() - interval '{until}'" + else: + until_clause = "" + + first_clause = f"limit {first}" if first >= 0 else "" + last_clause = f"limit {last}" if last >= 0 else "" + + query = self.execute_query( + f"""\ + select * from ( + select * + from {account_event_table} + where ( + {resource_clause} + {since_clause} + {until_clause} + ) + AND RECORD_TYPE = 'LOG' + AND SCOPE['name'] = 'snow.spcs.platform' + order by timestamp desc + {last_clause} + ) order by timestamp asc + {first_clause} + """ + ) + return query + + def get_metrics( + self, + service_name: str, + instance_id: str, + container_name: str, + since: str | datetime | None = None, + until: str | datetime | None = None, + first: int = -1, + last: int = -1, + ): + account_event_table = self.get_account_event_table() + if not account_event_table: + return [] + + resource_filters = [] + if service_name: + resource_filters.append( + f"resource_attributes:\"snow.service.name\" = '{service_name}'" + ) + if instance_id: + resource_filters.append( + f"(resource_attributes:\"snow.service.instance\" = '{instance_id}' OR resource_attributes:\"snow.service.container.instance\" = '{instance_id}')" + ) + if container_name: + resource_filters.append( + f"resource_attributes:\"snow.service.container.name\" = '{container_name}'" + ) + + resource_clause = " and ".join(resource_filters) if resource_filters else "1=1" + + if isinstance(since, datetime): + since_clause = f"and timestamp >= '{since}'" + elif isinstance(since, str) and since: + since_clause = f"and timestamp >= sysdate() - interval '{since}'" + else: + since_clause = "" + + if isinstance(until, datetime): + until_clause = f"and timestamp <= '{until}'" + elif isinstance(until, str) and until: + until_clause = f"and timestamp <= sysdate() - interval '{until}'" + else: + until_clause = "" + + first_clause = f"limit {first}" if first >= 0 else "" + last_clause = f"limit {last}" if last >= 0 else "" + + query = self.execute_query( + f"""\ + select * from ( + select * + from {account_event_table} + where ( + {resource_clause} + {since_clause} + {until_clause} + ) + AND RECORD_TYPE = 'METRIC' + AND SCOPE['name'] = 'snow.spcs.platform' + order by timestamp desc + {last_clause} + ) order by timestamp asc + {first_clause} + """ + ) + return query + def upgrade_spec(self, service_name: str, spec_path: Path): spec = self._read_yaml(spec_path) query = f"alter service {service_name} from specification $$ {spec} $$" From fc6797771e192d46f05d1dfd279f28ec99f4fe7a Mon Sep 17 00:00:00 2001 From: Abby Shen Date: Fri, 13 Dec 2024 14:46:19 -0800 Subject: [PATCH 2/9] snapshot and feature flag --- .../cli/_plugins/spcs/services/commands.py | 14 +- src/snowflake/cli/api/feature_flags.py | 2 + tests/__snapshots__/test_help_messages.ambr | 1235 ++++------------- tests/spcs/test_services.py | 70 + 4 files changed, 334 insertions(+), 987 deletions(-) diff --git a/src/snowflake/cli/_plugins/spcs/services/commands.py b/src/snowflake/cli/_plugins/spcs/services/commands.py index 825b950a89..17dd1f06c2 100644 --- a/src/snowflake/cli/_plugins/spcs/services/commands.py +++ b/src/snowflake/cli/_plugins/spcs/services/commands.py @@ -333,7 +333,12 @@ def events( ), **options, ): - """Fetches events for this app from the event table configured in Snowflake.""" + if FeatureFlag.ENABLE_SPCS_SERVICE_EVENTS.is_disabled(): + raise FeatureNotEnabledError( + "ENABLE_SPCS_SERVICE_EVENTS", + "Service events collection from SPCS event table is disabled.", + ) + if first >= 0 and last >= 0: raise IncompatibleParametersError(["--first", "--last"]) @@ -406,7 +411,12 @@ def metrics( ), **options, ): - """Fetches events for this app from the event table configured in Snowflake.""" + if FeatureFlag.ENABLE_SPCS_SERVICE_METRICS.is_disabled(): + raise FeatureNotEnabledError( + "ENABLE_SPCS_SERVICE_METRICS", + "Service metrics collection from SPCS event table is disabled.", + ) + if first >= 0 and last >= 0: raise IncompatibleParametersError(["--first", "--last"]) diff --git a/src/snowflake/cli/api/feature_flags.py b/src/snowflake/cli/api/feature_flags.py index 2a56458083..2aefe4893e 100644 --- a/src/snowflake/cli/api/feature_flags.py +++ b/src/snowflake/cli/api/feature_flags.py @@ -64,3 +64,5 @@ class FeatureFlag(FeatureFlagMixin): "ENABLE_STREAMLIT_VERSIONED_STAGE", False ) ENABLE_SPCS_LOG_STREAMING = BooleanFlag("ENABLE_SPCS_LOG_STREAMING", False) + ENABLE_SPCS_SERVICE_EVENTS = BooleanFlag("ENABLE_SPCS_SERVICE_EVENTS", False) + ENABLE_SPCS_SERVICE_METRICS = BooleanFlag("ENABLE_SPCS_SERVICE_METRICS", False) diff --git a/tests/__snapshots__/test_help_messages.ambr b/tests/__snapshots__/test_help_messages.ambr index 9e8d9b808d..0188bec9ae 100644 --- a/tests/__snapshots__/test_help_messages.ambr +++ b/tests/__snapshots__/test_help_messages.ambr @@ -578,891 +578,6 @@ +------------------------------------------------------------------------------+ - ''' -# --- -# name: test_help_messages[app.release-channel.add-accounts] - ''' - - Usage: default app release-channel add-accounts [OPTIONS] CHANNEL - - Adds accounts to a release channel. - - +- Arguments ------------------------------------------------------------------+ - | * channel TEXT The release channel to add accounts to. | - | [required] | - +------------------------------------------------------------------------------+ - +- Options --------------------------------------------------------------------+ - | * --target-accounts TEXT The accounts to add to the release | - | channel. Format has to be | - | org1.account1,org2.account2. | - | [required] | - | --package-entity-id TEXT The ID of the package entity on which | - | to operate when definition_version is | - | 2 or higher. | - | --app-entity-id TEXT The ID of the application entity on | - | which to operate when | - | definition_version is 2 or higher. | - | --project -p TEXT Path where Snowflake project resides. | - | Defaults to current working directory. | - | --env TEXT String in format of key=value. | - | Overrides variables from env section | - | used for templates. | - | --help -h Show this message and exit. | - +------------------------------------------------------------------------------+ - +- Connection configuration ---------------------------------------------------+ - | --connection,--environment -c TEXT Name of the connection, as | - | defined in your config.toml | - | file. Default: default. | - | --host TEXT Host address for the | - | connection. Overrides the | - | value specified for the | - | connection. | - | --port INTEGER Port for the connection. | - | Overrides the value | - | specified for the | - | connection. | - | --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-file,--private… TEXT Snowflake private key file | - | path. Overrides the value | - | specified for the | - | connection. | - | --token-file-path TEXT Path to file with an OAuth | - | token that should be used | - | when connecting to Snowflake | - | --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) | - | --enable-diag Run Python connector | - | diagnostic test | - | --diag-log-path TEXT Diagnostic report path | - | --diag-allowlist-path TEXT Diagnostic report path to | - | optional allowlist | - +------------------------------------------------------------------------------+ - +- 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 contain additional | - | information. | - | --silent Turns off intermediate output to console. | - +------------------------------------------------------------------------------+ - - - ''' -# --- -# name: test_help_messages[app.release-channel.add-version] - ''' - - Usage: default app release-channel add-version [OPTIONS] CHANNEL - - Adds a version to a release channel. - - +- Arguments ------------------------------------------------------------------+ - | * channel TEXT The release channel to add a version to. | - | [required] | - +------------------------------------------------------------------------------+ - +- Options --------------------------------------------------------------------+ - | * --version TEXT The version to add to the release | - | channel. | - | [required] | - | --package-entity-id TEXT The ID of the package entity on which | - | to operate when definition_version is | - | 2 or higher. | - | --app-entity-id TEXT The ID of the application entity on | - | which to operate when | - | definition_version is 2 or higher. | - | --project -p TEXT Path where Snowflake project resides. | - | Defaults to current working directory. | - | --env TEXT String in format of key=value. | - | Overrides variables from env section | - | used for templates. | - | --help -h Show this message and exit. | - +------------------------------------------------------------------------------+ - +- Connection configuration ---------------------------------------------------+ - | --connection,--environment -c TEXT Name of the connection, as | - | defined in your config.toml | - | file. Default: default. | - | --host TEXT Host address for the | - | connection. Overrides the | - | value specified for the | - | connection. | - | --port INTEGER Port for the connection. | - | Overrides the value | - | specified for the | - | connection. | - | --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-file,--private… TEXT Snowflake private key file | - | path. Overrides the value | - | specified for the | - | connection. | - | --token-file-path TEXT Path to file with an OAuth | - | token that should be used | - | when connecting to Snowflake | - | --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) | - | --enable-diag Run Python connector | - | diagnostic test | - | --diag-log-path TEXT Diagnostic report path | - | --diag-allowlist-path TEXT Diagnostic report path to | - | optional allowlist | - +------------------------------------------------------------------------------+ - +- 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 contain additional | - | information. | - | --silent Turns off intermediate output to console. | - +------------------------------------------------------------------------------+ - - - ''' -# --- -# name: test_help_messages[app.release-channel.list] - ''' - - Usage: default app release-channel list [OPTIONS] [CHANNEL] - - Lists the release channels available for an application package. - - +- Arguments ------------------------------------------------------------------+ - | channel [CHANNEL] The release channel to list. If not provided, all | - | release channels are listed. | - +------------------------------------------------------------------------------+ - +- Options --------------------------------------------------------------------+ - | --package-entity-id TEXT The ID of the package entity on which to | - | operate when definition_version is 2 or | - | higher. | - | --app-entity-id TEXT The ID of the application entity on which | - | to operate when definition_version is 2 | - | or higher. | - | --project -p TEXT Path where Snowflake project resides. | - | Defaults to current working directory. | - | --env TEXT String in format of key=value. Overrides | - | variables from env section used for | - | templates. | - | --help -h Show this message and exit. | - +------------------------------------------------------------------------------+ - +- Connection configuration ---------------------------------------------------+ - | --connection,--environment -c TEXT Name of the connection, as | - | defined in your config.toml | - | file. Default: default. | - | --host TEXT Host address for the | - | connection. Overrides the | - | value specified for the | - | connection. | - | --port INTEGER Port for the connection. | - | Overrides the value | - | specified for the | - | connection. | - | --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-file,--private… TEXT Snowflake private key file | - | path. Overrides the value | - | specified for the | - | connection. | - | --token-file-path TEXT Path to file with an OAuth | - | token that should be used | - | when connecting to Snowflake | - | --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) | - | --enable-diag Run Python connector | - | diagnostic test | - | --diag-log-path TEXT Diagnostic report path | - | --diag-allowlist-path TEXT Diagnostic report path to | - | optional allowlist | - +------------------------------------------------------------------------------+ - +- 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 contain additional | - | information. | - | --silent Turns off intermediate output to console. | - +------------------------------------------------------------------------------+ - - - ''' -# --- -# name: test_help_messages[app.release-channel.remove-accounts] - ''' - - Usage: default app release-channel remove-accounts [OPTIONS] CHANNEL - - Removes accounts from a release channel. - - +- Arguments ------------------------------------------------------------------+ - | * channel TEXT The release channel to remove accounts from. | - | [required] | - +------------------------------------------------------------------------------+ - +- Options --------------------------------------------------------------------+ - | * --target-accounts TEXT The accounts to remove from the | - | release channel. Format has to be | - | org1.account1,org2.account2. | - | [required] | - | --package-entity-id TEXT The ID of the package entity on which | - | to operate when definition_version is | - | 2 or higher. | - | --app-entity-id TEXT The ID of the application entity on | - | which to operate when | - | definition_version is 2 or higher. | - | --project -p TEXT Path where Snowflake project resides. | - | Defaults to current working directory. | - | --env TEXT String in format of key=value. | - | Overrides variables from env section | - | used for templates. | - | --help -h Show this message and exit. | - +------------------------------------------------------------------------------+ - +- Connection configuration ---------------------------------------------------+ - | --connection,--environment -c TEXT Name of the connection, as | - | defined in your config.toml | - | file. Default: default. | - | --host TEXT Host address for the | - | connection. Overrides the | - | value specified for the | - | connection. | - | --port INTEGER Port for the connection. | - | Overrides the value | - | specified for the | - | connection. | - | --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-file,--private… TEXT Snowflake private key file | - | path. Overrides the value | - | specified for the | - | connection. | - | --token-file-path TEXT Path to file with an OAuth | - | token that should be used | - | when connecting to Snowflake | - | --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) | - | --enable-diag Run Python connector | - | diagnostic test | - | --diag-log-path TEXT Diagnostic report path | - | --diag-allowlist-path TEXT Diagnostic report path to | - | optional allowlist | - +------------------------------------------------------------------------------+ - +- 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 contain additional | - | information. | - | --silent Turns off intermediate output to console. | - +------------------------------------------------------------------------------+ - - - ''' -# --- -# name: test_help_messages[app.release-channel.remove-version] - ''' - - Usage: default app release-channel remove-version [OPTIONS] CHANNEL - - Removes a version from a release channel. - - +- Arguments ------------------------------------------------------------------+ - | * channel TEXT The release channel to remove a version from. | - | [required] | - +------------------------------------------------------------------------------+ - +- Options --------------------------------------------------------------------+ - | * --version TEXT The version to remove from the release | - | channel. | - | [required] | - | --package-entity-id TEXT The ID of the package entity on which | - | to operate when definition_version is | - | 2 or higher. | - | --app-entity-id TEXT The ID of the application entity on | - | which to operate when | - | definition_version is 2 or higher. | - | --project -p TEXT Path where Snowflake project resides. | - | Defaults to current working directory. | - | --env TEXT String in format of key=value. | - | Overrides variables from env section | - | used for templates. | - | --help -h Show this message and exit. | - +------------------------------------------------------------------------------+ - +- Connection configuration ---------------------------------------------------+ - | --connection,--environment -c TEXT Name of the connection, as | - | defined in your config.toml | - | file. Default: default. | - | --host TEXT Host address for the | - | connection. Overrides the | - | value specified for the | - | connection. | - | --port INTEGER Port for the connection. | - | Overrides the value | - | specified for the | - | connection. | - | --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-file,--private… TEXT Snowflake private key file | - | path. Overrides the value | - | specified for the | - | connection. | - | --token-file-path TEXT Path to file with an OAuth | - | token that should be used | - | when connecting to Snowflake | - | --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) | - | --enable-diag Run Python connector | - | diagnostic test | - | --diag-log-path TEXT Diagnostic report path | - | --diag-allowlist-path TEXT Diagnostic report path to | - | optional allowlist | - +------------------------------------------------------------------------------+ - +- 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 contain additional | - | information. | - | --silent Turns off intermediate output to console. | - +------------------------------------------------------------------------------+ - - - ''' -# --- -# name: test_help_messages[app.release-channel] - ''' - - Usage: default app release-channel [OPTIONS] COMMAND [ARGS]... - - Manages release channels of an application package - - +- Options --------------------------------------------------------------------+ - | --help -h Show this message and exit. | - +------------------------------------------------------------------------------+ - +- Commands -------------------------------------------------------------------+ - | add-accounts Adds accounts to a release channel. | - | add-version Adds a version to a release channel. | - | list Lists the release channels available for an application | - | package. | - | remove-accounts Removes accounts from a release channel. | - | remove-version Removes a version from a release channel. | - +------------------------------------------------------------------------------+ - - - ''' -# --- -# name: test_help_messages[app.release-directive.list] - ''' - - Usage: default app release-directive list [OPTIONS] - - Lists release directives in an application package. If no release channel is - specified, release directives for all channels are listed. If a release - channel is specified, only release directives for that channel are listed. - If --like is provided, only release directives matching the SQL pattern are - listed. - - +- Options --------------------------------------------------------------------+ - | --like -l TEXT SQL LIKE pattern for filtering objects by | - | name. For example, snow app | - | release-directive list --like='my%' lists | - | all release directives starting with | - | 'my'. | - | [default: %%] | - | --channel TEXT The release channel to use when listing | - | release directives. If not provided, | - | release directives from all release | - | channels are listed. | - | --package-entity-id TEXT The ID of the package entity on which to | - | operate when definition_version is 2 or | - | higher. | - | --app-entity-id TEXT The ID of the application entity on which | - | to operate when definition_version is 2 | - | or higher. | - | --project -p TEXT Path where Snowflake project resides. | - | Defaults to current working directory. | - | --env TEXT String in format of key=value. Overrides | - | variables from env section used for | - | templates. | - | --help -h Show this message and exit. | - +------------------------------------------------------------------------------+ - +- Connection configuration ---------------------------------------------------+ - | --connection,--environment -c TEXT Name of the connection, as | - | defined in your config.toml | - | file. Default: default. | - | --host TEXT Host address for the | - | connection. Overrides the | - | value specified for the | - | connection. | - | --port INTEGER Port for the connection. | - | Overrides the value | - | specified for the | - | connection. | - | --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-file,--private… TEXT Snowflake private key file | - | path. Overrides the value | - | specified for the | - | connection. | - | --token-file-path TEXT Path to file with an OAuth | - | token that should be used | - | when connecting to Snowflake | - | --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) | - | --enable-diag Run Python connector | - | diagnostic test | - | --diag-log-path TEXT Diagnostic report path | - | --diag-allowlist-path TEXT Diagnostic report path to | - | optional allowlist | - +------------------------------------------------------------------------------+ - +- 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 contain additional | - | information. | - | --silent Turns off intermediate output to console. | - +------------------------------------------------------------------------------+ - - - ''' -# --- -# name: test_help_messages[app.release-directive.set] - ''' - - Usage: default app release-directive set [OPTIONS] DIRECTIVE - - Sets a release directive. - target_accounts cannot be specified for default release directives. - target_accounts field is required when creating a new non-default release - directive. - - +- Arguments ------------------------------------------------------------------+ - | * directive TEXT Name of the release directive to set | - | [required] | - +------------------------------------------------------------------------------+ - +- Options --------------------------------------------------------------------+ - | --channel TEXT Name of the release channel to use | - | [default: DEFAULT] | - | --target-accounts TEXT List of the accounts to apply the | - | release directive to. Format has to | - | be org1.account1,org2.account2 | - | * --version TEXT Version of the application package | - | to use | - | [required] | - | * --patch INTEGER Patch number to use for the | - | selected version | - | [required] | - | --package-entity-id TEXT The ID of the package entity on | - | which to operate when | - | definition_version is 2 or higher. | - | --app-entity-id TEXT The ID of the application entity on | - | which to operate when | - | definition_version is 2 or higher. | - | --project -p TEXT Path where Snowflake project | - | resides. Defaults to current | - | working directory. | - | --env TEXT String in format of key=value. | - | Overrides variables from env | - | section used for templates. | - | --help -h Show this message and exit. | - +------------------------------------------------------------------------------+ - +- Connection configuration ---------------------------------------------------+ - | --connection,--environment -c TEXT Name of the connection, as | - | defined in your config.toml | - | file. Default: default. | - | --host TEXT Host address for the | - | connection. Overrides the | - | value specified for the | - | connection. | - | --port INTEGER Port for the connection. | - | Overrides the value | - | specified for the | - | connection. | - | --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-file,--private… TEXT Snowflake private key file | - | path. Overrides the value | - | specified for the | - | connection. | - | --token-file-path TEXT Path to file with an OAuth | - | token that should be used | - | when connecting to Snowflake | - | --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) | - | --enable-diag Run Python connector | - | diagnostic test | - | --diag-log-path TEXT Diagnostic report path | - | --diag-allowlist-path TEXT Diagnostic report path to | - | optional allowlist | - +------------------------------------------------------------------------------+ - +- 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 contain additional | - | information. | - | --silent Turns off intermediate output to console. | - +------------------------------------------------------------------------------+ - - - ''' -# --- -# name: test_help_messages[app.release-directive.unset] - ''' - - Usage: default app release-directive unset [OPTIONS] DIRECTIVE - - Unsets a release directive. - - +- Arguments ------------------------------------------------------------------+ - | * directive TEXT Name of the release directive | - | [required] | - +------------------------------------------------------------------------------+ - +- Options --------------------------------------------------------------------+ - | --channel TEXT Name of the release channel to use | - | [default: DEFAULT] | - | --package-entity-id TEXT The ID of the package entity on which to | - | operate when definition_version is 2 or | - | higher. | - | --app-entity-id TEXT The ID of the application entity on which | - | to operate when definition_version is 2 | - | or higher. | - | --project -p TEXT Path where Snowflake project resides. | - | Defaults to current working directory. | - | --env TEXT String in format of key=value. Overrides | - | variables from env section used for | - | templates. | - | --help -h Show this message and exit. | - +------------------------------------------------------------------------------+ - +- Connection configuration ---------------------------------------------------+ - | --connection,--environment -c TEXT Name of the connection, as | - | defined in your config.toml | - | file. Default: default. | - | --host TEXT Host address for the | - | connection. Overrides the | - | value specified for the | - | connection. | - | --port INTEGER Port for the connection. | - | Overrides the value | - | specified for the | - | connection. | - | --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-file,--private… TEXT Snowflake private key file | - | path. Overrides the value | - | specified for the | - | connection. | - | --token-file-path TEXT Path to file with an OAuth | - | token that should be used | - | when connecting to Snowflake | - | --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) | - | --enable-diag Run Python connector | - | diagnostic test | - | --diag-log-path TEXT Diagnostic report path | - | --diag-allowlist-path TEXT Diagnostic report path to | - | optional allowlist | - +------------------------------------------------------------------------------+ - +- 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 contain additional | - | information. | - | --silent Turns off intermediate output to console. | - +------------------------------------------------------------------------------+ - - - ''' -# --- -# name: test_help_messages[app.release-directive] - ''' - - Usage: default app release-directive [OPTIONS] COMMAND [ARGS]... - - Manages release directives of an application package - - +- Options --------------------------------------------------------------------+ - | --help -h Show this message and exit. | - +------------------------------------------------------------------------------+ - +- Commands -------------------------------------------------------------------+ - | list Lists release directives in an application package. If no release | - | channel is specified, release directives for all channels are | - | listed. If a release channel is specified, only release directives | - | for that channel are listed. | - | set Sets a release directive. | - | unset Unsets a release directive. | - +------------------------------------------------------------------------------+ - - ''' # --- # name: test_help_messages[app.run] @@ -1519,18 +634,6 @@ | determined from the | | project definition | | file. Default: unset. | - | --channel TEXT The name of the | - | release channel to | - | use when creating or | - | upgrading an | - | application instance | - | from a release | - | directive. Requires | - | the | - | --from-release-direc… | - | flag to be set. If | - | unset, the default | - | channel will be used. | | --interactive --no-interactive When enabled, this | | option displays | | prompts even if the | @@ -2303,28 +1406,23 @@ | --help -h Show this message and exit. | +------------------------------------------------------------------------------+ +- Commands -------------------------------------------------------------------+ - | bundle Prepares a local folder with configured app artifacts. | - | deploy 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, as in snow app deploy, | - | is a shorthand for snow app deploy --prune --recursive. | - | events Fetches events for this app from the event table | - | configured in Snowflake. | - | open Opens the Snowflake Native App inside of your browser, | - | once it has been installed in your account. | - | release-channel Manages release channels of an application package | - | release-directive Manages release directives of an application package | - | run Creates an application package in your Snowflake | - | account, uploads code files to its stage, then creates | - | or upgrades an application object from the application | - | package. | - | teardown Attempts to drop both the application object and | - | application package as defined in the project definition | - | file. | - | validate Validates a deployed Snowflake Native App's setup | - | script. | - | version Manages versions defined in an application package | + | bundle Prepares a local folder with configured app artifacts. | + | deploy 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, | + | as in snow app deploy, is a shorthand for snow app deploy --prune | + | --recursive. | + | events Fetches events for this app from the event table configured in | + | Snowflake. | + | open Opens the Snowflake Native App inside of your browser, once it | + | has been installed in your account. | + | run Creates an application package in your Snowflake account, uploads | + | code files to its stage, then creates or upgrades an application | + | object from the application package. | + | teardown Attempts to drop both the application object and application | + | package as defined in the project definition file. | + | validate Validates a deployed Snowflake Native App's setup script. | + | version Manages versions defined in an application package | +------------------------------------------------------------------------------+ @@ -7625,6 +6723,110 @@ +------------------------------------------------------------------------------+ + ''' +# --- +# name: test_help_messages[spcs.service.events] + ''' + + Usage: default spcs service events [OPTIONS] NAME + + Fetches events for this app from the event table configured in Snowflake. + + +- Arguments ------------------------------------------------------------------+ + | * name TEXT Identifier of the service; for example: my_service | + | [required] | + +------------------------------------------------------------------------------+ + +- Options --------------------------------------------------------------------+ + | * --container-name TEXT Name of the container. | + | [required] | + | * --instance-id TEXT ID of the service instance, starting | + | with 0. | + | [required] | + | --since TEXT Fetch events that are newer than this | + | time ago, in Snowflake interval | + | syntax. | + | --until TEXT Fetch events that are older than this | + | time ago, in Snowflake interval | + | syntax. | + | --first INTEGER Fetch only the first N events. Cannot | + | be used with --last. | + | --last INTEGER Fetch only the last N events. Cannot | + | be used with --first. | + | --help -h Show this message and exit. | + +------------------------------------------------------------------------------+ + +- Connection configuration ---------------------------------------------------+ + | --connection,--environment -c TEXT Name of the connection, as | + | defined in your config.toml | + | file. Default: default. | + | --host TEXT Host address for the | + | connection. Overrides the | + | value specified for the | + | connection. | + | --port INTEGER Port for the connection. | + | Overrides the value | + | specified for the | + | connection. | + | --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-file,--private… TEXT Snowflake private key file | + | path. Overrides the value | + | specified for the | + | connection. | + | --token-file-path TEXT Path to file with an OAuth | + | token that should be used | + | when connecting to Snowflake | + | --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) | + | --enable-diag Run Python connector | + | diagnostic test | + | --diag-log-path TEXT Diagnostic report path | + | --diag-allowlist-path TEXT Diagnostic report path to | + | optional allowlist | + +------------------------------------------------------------------------------+ + +- 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 contain additional | + | information. | + | --silent Turns off intermediate output to console. | + +------------------------------------------------------------------------------+ + + ''' # --- # name: test_help_messages[spcs.service.execute-job] @@ -8287,6 +7489,110 @@ +------------------------------------------------------------------------------+ + ''' +# --- +# name: test_help_messages[spcs.service.metrics] + ''' + + Usage: default spcs service metrics [OPTIONS] NAME + + Fetches events for this app from the event table configured in Snowflake. + + +- Arguments ------------------------------------------------------------------+ + | * name TEXT Identifier of the service; for example: my_service | + | [required] | + +------------------------------------------------------------------------------+ + +- Options --------------------------------------------------------------------+ + | * --container-name TEXT Name of the container. | + | [required] | + | * --instance-id TEXT ID of the service instance, starting | + | with 0. | + | [required] | + | --since TEXT Fetch events that are newer than this | + | time ago, in Snowflake interval | + | syntax. | + | --until TEXT Fetch events that are older than this | + | time ago, in Snowflake interval | + | syntax. | + | --first INTEGER Fetch only the first N events. Cannot | + | be used with --last. | + | --last INTEGER Fetch only the last N events. Cannot | + | be used with --first. | + | --help -h Show this message and exit. | + +------------------------------------------------------------------------------+ + +- Connection configuration ---------------------------------------------------+ + | --connection,--environment -c TEXT Name of the connection, as | + | defined in your config.toml | + | file. Default: default. | + | --host TEXT Host address for the | + | connection. Overrides the | + | value specified for the | + | connection. | + | --port INTEGER Port for the connection. | + | Overrides the value | + | specified for the | + | connection. | + | --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-file,--private… TEXT Snowflake private key file | + | path. Overrides the value | + | specified for the | + | connection. | + | --token-file-path TEXT Path to file with an OAuth | + | token that should be used | + | when connecting to Snowflake | + | --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) | + | --enable-diag Run Python connector | + | diagnostic test | + | --diag-log-path TEXT Diagnostic report path | + | --diag-allowlist-path TEXT Diagnostic report path to | + | optional allowlist | + +------------------------------------------------------------------------------+ + +- 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 contain additional | + | information. | + | --silent Turns off intermediate output to console. | + +------------------------------------------------------------------------------+ + + ''' # --- # name: test_help_messages[spcs.service.resume] @@ -8898,6 +8204,8 @@ | schema. | | describe Provides description of service. | | drop Drops service with given name. | + | events Fetches events for this app from the event | + | table configured in Snowflake. | | execute-job Creates and executes a job service in the | | current schema. | | list Lists all available services. | @@ -8907,6 +8215,8 @@ | list-roles Lists all service roles in a service. | | logs Retrieves local logs from a service | | container. | + | metrics Fetches events for this app from the event | + | table configured in Snowflake. | | resume Resumes the service from a SUSPENDED state. | | set Sets one or more properties for the | | service. | @@ -10659,50 +9969,6 @@ +------------------------------------------------------------------------------+ - ''' -# --- -# name: test_help_messages_no_help_flag[app.release-channel] - ''' - - Usage: default app release-channel [OPTIONS] COMMAND [ARGS]... - - Manages release channels of an application package - - +- Options --------------------------------------------------------------------+ - | --help -h Show this message and exit. | - +------------------------------------------------------------------------------+ - +- Commands -------------------------------------------------------------------+ - | add-accounts Adds accounts to a release channel. | - | add-version Adds a version to a release channel. | - | list Lists the release channels available for an application | - | package. | - | remove-accounts Removes accounts from a release channel. | - | remove-version Removes a version from a release channel. | - +------------------------------------------------------------------------------+ - - - ''' -# --- -# name: test_help_messages_no_help_flag[app.release-directive] - ''' - - Usage: default app release-directive [OPTIONS] COMMAND [ARGS]... - - Manages release directives of an application package - - +- Options --------------------------------------------------------------------+ - | --help -h Show this message and exit. | - +------------------------------------------------------------------------------+ - +- Commands -------------------------------------------------------------------+ - | list Lists release directives in an application package. If no release | - | channel is specified, release directives for all channels are | - | listed. If a release channel is specified, only release directives | - | for that channel are listed. | - | set Sets a release directive. | - | unset Unsets a release directive. | - +------------------------------------------------------------------------------+ - - ''' # --- # name: test_help_messages_no_help_flag[app.version] @@ -10739,28 +10005,23 @@ | --help -h Show this message and exit. | +------------------------------------------------------------------------------+ +- Commands -------------------------------------------------------------------+ - | bundle Prepares a local folder with configured app artifacts. | - | deploy 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, as in snow app deploy, | - | is a shorthand for snow app deploy --prune --recursive. | - | events Fetches events for this app from the event table | - | configured in Snowflake. | - | open Opens the Snowflake Native App inside of your browser, | - | once it has been installed in your account. | - | release-channel Manages release channels of an application package | - | release-directive Manages release directives of an application package | - | run Creates an application package in your Snowflake | - | account, uploads code files to its stage, then creates | - | or upgrades an application object from the application | - | package. | - | teardown Attempts to drop both the application object and | - | application package as defined in the project definition | - | file. | - | validate Validates a deployed Snowflake Native App's setup | - | script. | - | version Manages versions defined in an application package | + | bundle Prepares a local folder with configured app artifacts. | + | deploy 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, | + | as in snow app deploy, is a shorthand for snow app deploy --prune | + | --recursive. | + | events Fetches events for this app from the event table configured in | + | Snowflake. | + | open Opens the Snowflake Native App inside of your browser, once it | + | has been installed in your account. | + | run Creates an application package in your Snowflake account, uploads | + | code files to its stage, then creates or upgrades an application | + | object from the application package. | + | teardown Attempts to drop both the application object and application | + | package as defined in the project definition file. | + | validate Validates a deployed Snowflake Native App's setup script. | + | version Manages versions defined in an application package | +------------------------------------------------------------------------------+ @@ -11001,6 +10262,8 @@ | schema. | | describe Provides description of service. | | drop Drops service with given name. | + | events Fetches events for this app from the event | + | table configured in Snowflake. | | execute-job Creates and executes a job service in the | | current schema. | | list Lists all available services. | @@ -11010,6 +10273,8 @@ | list-roles Lists all service roles in a service. | | logs Retrieves local logs from a service | | container. | + | metrics Fetches events for this app from the event | + | table configured in Snowflake. | | resume Resumes the service from a SUSPENDED state. | | set Sets one or more properties for the | | service. | diff --git a/tests/spcs/test_services.py b/tests/spcs/test_services.py index b48dedd1ce..44df650b85 100644 --- a/tests/spcs/test_services.py +++ b/tests/spcs/test_services.py @@ -699,6 +699,76 @@ def test_logs_streaming_disabled(mock_is_disabled, runner): ), f"Expected formatted output not found: {result.output}" +@patch( + "snowflake.cli.api.feature_flags.FeatureFlag.ENABLE_SPCS_SERVICE_EVENTS.is_disabled" +) +def test_service_events_disabled(mock_is_disabled, runner): + mock_is_disabled.return_value = True + result = runner.invoke( + [ + "spcs", + "service", + "events", + "LOG_EVENT", + "--container-name", + "log-printer", + "--instance-id", + "0", + "--since", + "1 minute", + ] + ) + assert ( + result.exit_code != 0 + ), "Expected a non-zero exit code due to feature flag being disabled" + + expected_output = ( + "+- Error ----------------------------------------------------------------------+\n" + "| Service events collection from SPCS event table is disabled. To enable it, |\n" + "| add 'ENABLE_SPCS_SERVICE_EVENTS = true' to '[cli.features]' section of your |\n" + "| configuration file. |\n" + "+------------------------------------------------------------------------------+\n" + ) + assert ( + result.output == expected_output + ), f"Expected formatted output not found: {result.output}" + + +@patch( + "snowflake.cli.api.feature_flags.FeatureFlag.ENABLE_SPCS_SERVICE_METRICS.is_disabled" +) +def test_service_metrics_disabled(mock_is_disabled, runner): + mock_is_disabled.return_value = True + result = runner.invoke( + [ + "spcs", + "service", + "metrics", + "LOG_EVENT", + "--container-name", + "log-printer", + "--instance-id", + "0", + "--since", + "1 minute", + ] + ) + assert ( + result.exit_code != 0 + ), "Expected a non-zero exit code due to feature flag being disabled" + + expected_output = ( + "+- Error ----------------------------------------------------------------------+\n" + "| Service metrics collection from SPCS event table is disabled. To enable it, |\n" + "| add 'ENABLE_SPCS_SERVICE_METRICS = true' to '[cli.features]' section of your |\n" + "| configuration file. |\n" + "+------------------------------------------------------------------------------+\n" + ) + assert ( + result.output == expected_output + ), f"Expected formatted output not found: {result.output}" + + def test_read_yaml(other_directory): tmp_dir = Path(other_directory) spec_path = tmp_dir / "spec.yml" From 3426e90a3261642574fc6723415594b7dd27317c Mon Sep 17 00:00:00 2001 From: Abby Shen Date: Fri, 13 Dec 2024 15:30:57 -0800 Subject: [PATCH 3/9] snapshot --- tests/__snapshots__/test_help_messages.ambr | 475 ++++++++++++++++++-- 1 file changed, 429 insertions(+), 46 deletions(-) diff --git a/tests/__snapshots__/test_help_messages.ambr b/tests/__snapshots__/test_help_messages.ambr index 0188bec9ae..214a3a11f5 100644 --- a/tests/__snapshots__/test_help_messages.ambr +++ b/tests/__snapshots__/test_help_messages.ambr @@ -578,6 +578,355 @@ +------------------------------------------------------------------------------+ + ''' +# --- +# name: test_help_messages[app.release-directive.list] + ''' + + Usage: default app release-directive list [OPTIONS] + + Lists release directives in an application package. If no release channel is + specified, release directives for all channels are listed. If a release + channel is specified, only release directives for that channel are listed. + If --like is provided, only release directives matching the SQL pattern are + listed. + + +- Options --------------------------------------------------------------------+ + | --like -l TEXT SQL LIKE pattern for filtering objects by | + | name. For example, snow app | + | release-directive list --like='my%' lists | + | all release directives starting with | + | 'my'. | + | [default: %%] | + | --channel TEXT The release channel to use when listing | + | release directives. If not provided, | + | release directives from all release | + | channels are listed. | + | --package-entity-id TEXT The ID of the package entity on which to | + | operate when definition_version is 2 or | + | higher. | + | --app-entity-id TEXT The ID of the application entity on which | + | to operate when definition_version is 2 | + | or higher. | + | --project -p TEXT Path where Snowflake project resides. | + | Defaults to current working directory. | + | --env TEXT String in format of key=value. Overrides | + | variables from env section used for | + | templates. | + | --help -h Show this message and exit. | + +------------------------------------------------------------------------------+ + +- Connection configuration ---------------------------------------------------+ + | --connection,--environment -c TEXT Name of the connection, as | + | defined in your config.toml | + | file. Default: default. | + | --host TEXT Host address for the | + | connection. Overrides the | + | value specified for the | + | connection. | + | --port INTEGER Port for the connection. | + | Overrides the value | + | specified for the | + | connection. | + | --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-file,--private… TEXT Snowflake private key file | + | path. Overrides the value | + | specified for the | + | connection. | + | --token-file-path TEXT Path to file with an OAuth | + | token that should be used | + | when connecting to Snowflake | + | --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) | + | --enable-diag Run Python connector | + | diagnostic test | + | --diag-log-path TEXT Diagnostic report path | + | --diag-allowlist-path TEXT Diagnostic report path to | + | optional allowlist | + +------------------------------------------------------------------------------+ + +- 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 contain additional | + | information. | + | --silent Turns off intermediate output to console. | + +------------------------------------------------------------------------------+ + + + ''' +# --- +# name: test_help_messages[app.release-directive.set] + ''' + + Usage: default app release-directive set [OPTIONS] DIRECTIVE + + Sets a release directive. + target_accounts cannot be specified for default release directives. + target_accounts field is required when creating a new non-default release + directive. + + +- Arguments ------------------------------------------------------------------+ + | * directive TEXT Name of the release directive to set | + | [required] | + +------------------------------------------------------------------------------+ + +- Options --------------------------------------------------------------------+ + | --channel TEXT Name of the release channel to use | + | [default: DEFAULT] | + | --target-accounts TEXT List of the accounts to apply the | + | release directive to. Format has to | + | be org1.account1,org2.account2 | + | * --version TEXT Version of the application package | + | to use | + | [required] | + | * --patch INTEGER Patch number to use for the | + | selected version | + | [required] | + | --package-entity-id TEXT The ID of the package entity on | + | which to operate when | + | definition_version is 2 or higher. | + | --app-entity-id TEXT The ID of the application entity on | + | which to operate when | + | definition_version is 2 or higher. | + | --project -p TEXT Path where Snowflake project | + | resides. Defaults to current | + | working directory. | + | --env TEXT String in format of key=value. | + | Overrides variables from env | + | section used for templates. | + | --help -h Show this message and exit. | + +------------------------------------------------------------------------------+ + +- Connection configuration ---------------------------------------------------+ + | --connection,--environment -c TEXT Name of the connection, as | + | defined in your config.toml | + | file. Default: default. | + | --host TEXT Host address for the | + | connection. Overrides the | + | value specified for the | + | connection. | + | --port INTEGER Port for the connection. | + | Overrides the value | + | specified for the | + | connection. | + | --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-file,--private… TEXT Snowflake private key file | + | path. Overrides the value | + | specified for the | + | connection. | + | --token-file-path TEXT Path to file with an OAuth | + | token that should be used | + | when connecting to Snowflake | + | --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) | + | --enable-diag Run Python connector | + | diagnostic test | + | --diag-log-path TEXT Diagnostic report path | + | --diag-allowlist-path TEXT Diagnostic report path to | + | optional allowlist | + +------------------------------------------------------------------------------+ + +- 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 contain additional | + | information. | + | --silent Turns off intermediate output to console. | + +------------------------------------------------------------------------------+ + + + ''' +# --- +# name: test_help_messages[app.release-directive.unset] + ''' + + Usage: default app release-directive unset [OPTIONS] DIRECTIVE + + Unsets a release directive. + + +- Arguments ------------------------------------------------------------------+ + | * directive TEXT Name of the release directive | + | [required] | + +------------------------------------------------------------------------------+ + +- Options --------------------------------------------------------------------+ + | --channel TEXT Name of the release channel to use | + | [default: DEFAULT] | + | --package-entity-id TEXT The ID of the package entity on which to | + | operate when definition_version is 2 or | + | higher. | + | --app-entity-id TEXT The ID of the application entity on which | + | to operate when definition_version is 2 | + | or higher. | + | --project -p TEXT Path where Snowflake project resides. | + | Defaults to current working directory. | + | --env TEXT String in format of key=value. Overrides | + | variables from env section used for | + | templates. | + | --help -h Show this message and exit. | + +------------------------------------------------------------------------------+ + +- Connection configuration ---------------------------------------------------+ + | --connection,--environment -c TEXT Name of the connection, as | + | defined in your config.toml | + | file. Default: default. | + | --host TEXT Host address for the | + | connection. Overrides the | + | value specified for the | + | connection. | + | --port INTEGER Port for the connection. | + | Overrides the value | + | specified for the | + | connection. | + | --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-file,--private… TEXT Snowflake private key file | + | path. Overrides the value | + | specified for the | + | connection. | + | --token-file-path TEXT Path to file with an OAuth | + | token that should be used | + | when connecting to Snowflake | + | --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) | + | --enable-diag Run Python connector | + | diagnostic test | + | --diag-log-path TEXT Diagnostic report path | + | --diag-allowlist-path TEXT Diagnostic report path to | + | optional allowlist | + +------------------------------------------------------------------------------+ + +- 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 contain additional | + | information. | + | --silent Turns off intermediate output to console. | + +------------------------------------------------------------------------------+ + + + ''' +# --- +# name: test_help_messages[app.release-directive] + ''' + + Usage: default app release-directive [OPTIONS] COMMAND [ARGS]... + + Manages release directives of an application package + + +- Options --------------------------------------------------------------------+ + | --help -h Show this message and exit. | + +------------------------------------------------------------------------------+ + +- Commands -------------------------------------------------------------------+ + | list Lists release directives in an application package. If no release | + | channel is specified, release directives for all channels are | + | listed. If a release channel is specified, only release directives | + | for that channel are listed. | + | set Sets a release directive. | + | unset Unsets a release directive. | + +------------------------------------------------------------------------------+ + + ''' # --- # name: test_help_messages[app.run] @@ -634,6 +983,18 @@ | determined from the | | project definition | | file. Default: unset. | + | --channel TEXT The name of the | + | release channel to | + | use when creating or | + | upgrading an | + | application instance | + | from a release | + | directive. Requires | + | the | + | --from-release-direc… | + | flag to be set. If | + | unset, the default | + | channel will be used. | | --interactive --no-interactive When enabled, this | | option displays | | prompts even if the | @@ -1406,23 +1767,27 @@ | --help -h Show this message and exit. | +------------------------------------------------------------------------------+ +- Commands -------------------------------------------------------------------+ - | bundle Prepares a local folder with configured app artifacts. | - | deploy 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, | - | as in snow app deploy, is a shorthand for snow app deploy --prune | - | --recursive. | - | events Fetches events for this app from the event table configured in | - | Snowflake. | - | open Opens the Snowflake Native App inside of your browser, once it | - | has been installed in your account. | - | run Creates an application package in your Snowflake account, uploads | - | code files to its stage, then creates or upgrades an application | - | object from the application package. | - | teardown Attempts to drop both the application object and application | - | package as defined in the project definition file. | - | validate Validates a deployed Snowflake Native App's setup script. | - | version Manages versions defined in an application package | + | bundle Prepares a local folder with configured app artifacts. | + | deploy 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, as in snow app deploy, | + | is a shorthand for snow app deploy --prune --recursive. | + | events Fetches events for this app from the event table | + | configured in Snowflake. | + | open Opens the Snowflake Native App inside of your browser, | + | once it has been installed in your account. | + | release-directive Manages release directives of an application package | + | run Creates an application package in your Snowflake | + | account, uploads code files to its stage, then creates | + | or upgrades an application object from the application | + | package. | + | teardown Attempts to drop both the application object and | + | application package as defined in the project definition | + | file. | + | validate Validates a deployed Snowflake Native App's setup | + | script. | + | version Manages versions defined in an application package | +------------------------------------------------------------------------------+ @@ -6730,8 +7095,6 @@ Usage: default spcs service events [OPTIONS] NAME - Fetches events for this app from the event table configured in Snowflake. - +- Arguments ------------------------------------------------------------------+ | * name TEXT Identifier of the service; for example: my_service | | [required] | @@ -7496,8 +7859,6 @@ Usage: default spcs service metrics [OPTIONS] NAME - Fetches events for this app from the event table configured in Snowflake. - +- Arguments ------------------------------------------------------------------+ | * name TEXT Identifier of the service; for example: my_service | | [required] | @@ -8204,8 +8565,7 @@ | schema. | | describe Provides description of service. | | drop Drops service with given name. | - | events Fetches events for this app from the event | - | table configured in Snowflake. | + | events | | execute-job Creates and executes a job service in the | | current schema. | | list Lists all available services. | @@ -8215,8 +8575,7 @@ | list-roles Lists all service roles in a service. | | logs Retrieves local logs from a service | | container. | - | metrics Fetches events for this app from the event | - | table configured in Snowflake. | + | metrics | | resume Resumes the service from a SUSPENDED state. | | set Sets one or more properties for the | | service. | @@ -9969,6 +10328,28 @@ +------------------------------------------------------------------------------+ + ''' +# --- +# name: test_help_messages_no_help_flag[app.release-directive] + ''' + + Usage: default app release-directive [OPTIONS] COMMAND [ARGS]... + + Manages release directives of an application package + + +- Options --------------------------------------------------------------------+ + | --help -h Show this message and exit. | + +------------------------------------------------------------------------------+ + +- Commands -------------------------------------------------------------------+ + | list Lists release directives in an application package. If no release | + | channel is specified, release directives for all channels are | + | listed. If a release channel is specified, only release directives | + | for that channel are listed. | + | set Sets a release directive. | + | unset Unsets a release directive. | + +------------------------------------------------------------------------------+ + + ''' # --- # name: test_help_messages_no_help_flag[app.version] @@ -10005,23 +10386,27 @@ | --help -h Show this message and exit. | +------------------------------------------------------------------------------+ +- Commands -------------------------------------------------------------------+ - | bundle Prepares a local folder with configured app artifacts. | - | deploy 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, | - | as in snow app deploy, is a shorthand for snow app deploy --prune | - | --recursive. | - | events Fetches events for this app from the event table configured in | - | Snowflake. | - | open Opens the Snowflake Native App inside of your browser, once it | - | has been installed in your account. | - | run Creates an application package in your Snowflake account, uploads | - | code files to its stage, then creates or upgrades an application | - | object from the application package. | - | teardown Attempts to drop both the application object and application | - | package as defined in the project definition file. | - | validate Validates a deployed Snowflake Native App's setup script. | - | version Manages versions defined in an application package | + | bundle Prepares a local folder with configured app artifacts. | + | deploy 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, as in snow app deploy, | + | is a shorthand for snow app deploy --prune --recursive. | + | events Fetches events for this app from the event table | + | configured in Snowflake. | + | open Opens the Snowflake Native App inside of your browser, | + | once it has been installed in your account. | + | release-directive Manages release directives of an application package | + | run Creates an application package in your Snowflake | + | account, uploads code files to its stage, then creates | + | or upgrades an application object from the application | + | package. | + | teardown Attempts to drop both the application object and | + | application package as defined in the project definition | + | file. | + | validate Validates a deployed Snowflake Native App's setup | + | script. | + | version Manages versions defined in an application package | +------------------------------------------------------------------------------+ @@ -10262,8 +10647,7 @@ | schema. | | describe Provides description of service. | | drop Drops service with given name. | - | events Fetches events for this app from the event | - | table configured in Snowflake. | + | events | | execute-job Creates and executes a job service in the | | current schema. | | list Lists all available services. | @@ -10273,8 +10657,7 @@ | list-roles Lists all service roles in a service. | | logs Retrieves local logs from a service | | container. | - | metrics Fetches events for this app from the event | - | table configured in Snowflake. | + | metrics | | resume Resumes the service from a SUSPENDED state. | | set Sets one or more properties for the | | service. | From 8971b789eeec9d8181b83296a8b9c06666ac39b0 Mon Sep 17 00:00:00 2001 From: Abby Shen Date: Sun, 15 Dec 2024 00:04:24 -0800 Subject: [PATCH 4/9] add new test --- tests/spcs/test_services.py | 264 ++++++++++++++++++++++++++++++++++++ 1 file changed, 264 insertions(+) diff --git a/tests/spcs/test_services.py b/tests/spcs/test_services.py index 44df650b85..0225fce16f 100644 --- a/tests/spcs/test_services.py +++ b/tests/spcs/test_services.py @@ -734,6 +734,141 @@ def test_service_events_disabled(mock_is_disabled, runner): ), f"Expected formatted output not found: {result.output}" +@patch( + "snowflake.cli.api.feature_flags.FeatureFlag.ENABLE_SPCS_SERVICE_EVENTS.is_disabled" +) +@patch("snowflake.cli._plugins.spcs.services.manager.ServiceManager.execute_query") +def test_events_all_filters(mock_execute_query, mock_is_disabled, runner): + mock_is_disabled.return_value = False + mock_execute_query.side_effect = [ + [ + { + "key": "EVENT_TABLE", + "value": "event_table_db.data_schema.snowservices_logs", + } + ], + [ + ( + "2024-12-14 22:27:25.420", + None, + "2024-12-14 22:27:25.420", + None, + None, + json.dumps( + { + "snow.compute_pool.id": 230, + "snow.compute_pool.name": "MY_POOL", + "snow.database.id": 5, + "snow.database.name": "TESTDB", + "snow.schema.id": 5, + "snow.schema.name": "PUBLIC", + "snow.service.container.name": "log-printer", + "snow.service.id": 1568, + "snow.service.instance": "0", + "snow.service.name": "LOG_EVENT", + "snow.service.type": "SERVICE", + } + ), + json.dumps({"name": "snow.spcs.platform"}), + None, + "LOG", + json.dumps({"severity_text": "INFO"}), + json.dumps({"event.name": "CONTAINER.STATUS_CHANGE"}), + json.dumps({"message": "Running", "status": "READY"}), + None, + ) + ], + ] + + result = runner.invoke( + [ + "spcs", + "service", + "events", + "LOG_EVENT", + "--container-name", + "log-printer", + "--instance-id", + "0", + "--since", + "2 hours", + "--until", + "1 hour", + "--last", + "10", + "--warehouse", + "XSMALL", + "--role", + "sysadmin", + ] + ) + + assert result.exit_code == 0, f"Command failed with output: {result.output}" + + call_0 = mock_execute_query.mock_calls[0].args[0] + assert ( + call_0 == "show parameters like 'event_table' in account" + ), f"Unexpected query in Call 0: {call_0}" + + actual_query = mock_execute_query.mock_calls[1].args[0] + expected_query = ( + " select * from (\n" + " select *\n" + " from event_table_db.data_schema.snowservices_logs\n" + " where (\n" + ' resource_attributes:"snow.service.name" = ' + "'LOG_EVENT' and (resource_attributes:\"snow.service.instance\" = '0' OR " + "resource_attributes:\"snow.service.container.instance\" = '0') and " + "resource_attributes:\"snow.service.container.name\" = 'log-printer'\n" + " and timestamp >= sysdate() - interval '2 hours'\n" + " and timestamp <= sysdate() - interval '1 hour'\n" + " )\n" + " AND RECORD_TYPE = 'LOG'\n" + " AND SCOPE['name'] = 'snow.spcs.platform'\n" + " order by timestamp desc\n" + " limit 10\n" + " ) order by timestamp asc\n" + " \n" + " " + ) + + assert ( + actual_query == expected_query + ), f"Generated query does not match expected query.\n\nActual:\n{actual_query}\n\nExpected:\n{expected_query}" + + +@patch( + "snowflake.cli.api.feature_flags.FeatureFlag.ENABLE_SPCS_SERVICE_EVENTS.is_disabled" +) +def test_events_first_last_incompatibility(mock_is_disabled, runner): + mock_is_disabled.return_value = False + result = runner.invoke( + [ + "spcs", + "service", + "events", + "LOG_EVENT", + "--container-name", + "log-printer", + "--instance-id", + "0", + "--first", + "10", + "--last", + "5", + "--warehouse", + "XSMALL", + "--role", + "sysadmin", + ] + ) + + assert result.exit_code != 0, result.output + + expected_error = "Parameters '--first' and '--last' are incompatible" + assert expected_error in result.output + + @patch( "snowflake.cli.api.feature_flags.FeatureFlag.ENABLE_SPCS_SERVICE_METRICS.is_disabled" ) @@ -769,6 +904,135 @@ def test_service_metrics_disabled(mock_is_disabled, runner): ), f"Expected formatted output not found: {result.output}" +@patch( + "snowflake.cli.api.feature_flags.FeatureFlag.ENABLE_SPCS_SERVICE_EVENTS.is_disabled" +) +@patch("snowflake.cli._plugins.spcs.services.manager.ServiceManager.execute_query") +def test_metrics_all_filters(mock_execute_query, mock_is_disabled, runner): + mock_is_disabled.return_value = False + mock_execute_query.side_effect = [ + [ + { + "key": "EVENT_TABLE", + "value": "event_table_db.data_schema.snowservices_logs", + } + ], + [ + ( + datetime(2024, 12, 10, 18, 53, 21, 809000), + datetime(2024, 12, 10, 18, 52, 51, 809000), + None, + None, + None, + json.dumps( + { + "snow.account.name": "XACCOUNTTEST1", + "snow.compute_pool.id": 20641, + "snow.compute_pool.name": "MY_POOL", + "snow.service.container.name": "log-printer", + "snow.service.name": "LOG_EVENT", + } + ), + json.dumps({"name": "snow.spcs.platform"}), + None, + "METRIC", + json.dumps({"metric": {"name": "container.cpu.usage", "unit": "cpu"}}), + None, + "0.0005007168666666691", + None, + ) + ], + ] + + result = runner.invoke( + [ + "spcs", + "service", + "metrics", + "LOG_EVENT", + "--container-name", + "log-printer", + "--instance-id", + "0", + "--since", + "2 hours", + "--until", + "1 hour", + "--last", + "10", + "--warehouse", + "XSMALL", + "--role", + "sysadmin", + ] + ) + + assert result.exit_code == 0, f"Command failed with output: {result.output}" + + call_0 = mock_execute_query.mock_calls[0].args[0] + assert ( + call_0 == "show parameters like 'event_table' in account" + ), f"Unexpected query in Call 0: {call_0}" + + actual_query = mock_execute_query.mock_calls[1].args[0] + expected_query = ( + " select * from (\n" + " select *\n" + " from event_table_db.data_schema.snowservices_logs\n" + " where (\n" + ' resource_attributes:"snow.service.name" = ' + "'LOG_EVENT' and (resource_attributes:\"snow.service.instance\" = '0' OR " + "resource_attributes:\"snow.service.container.instance\" = '0') and " + "resource_attributes:\"snow.service.container.name\" = 'log-printer'\n" + " and timestamp >= sysdate() - interval '2 hours'\n" + " and timestamp <= sysdate() - interval '1 hour'\n" + " )\n" + " AND RECORD_TYPE = 'METRIC'\n" + " AND SCOPE['name'] = 'snow.spcs.platform'\n" + " order by timestamp desc\n" + " limit 10\n" + " ) order by timestamp asc\n" + " \n" + " " + ) + + assert ( + actual_query == expected_query + ), f"Generated query does not match expected query.\n\nActual:\n{actual_query}\n\nExpected:\n{expected_query}" + + +@patch( + "snowflake.cli.api.feature_flags.FeatureFlag.ENABLE_SPCS_SERVICE_METRICS.is_disabled" +) +def test_metrics_first_last_incompatibility(mock_is_disabled, runner): + mock_is_disabled.return_value = False + result = runner.invoke( + [ + "spcs", + "service", + "metrics", + "LOG_EVENT", + "--container-name", + "log-printer", + "--instance-id", + "0", + "--first", + "10", + "--last", + "5", + "--warehouse", + "XSMALL", + "--role", + "sysadmin", + ] + ) + + assert result.exit_code != 0, result.output + + expected_error = "Parameters '--first' and '--last' are incompatible" + assert expected_error in result.output + + def test_read_yaml(other_directory): tmp_dir = Path(other_directory) spec_path = tmp_dir / "spec.yml" From 5a7dc267ed60aabc08d17f4bc14c2a639beea124 Mon Sep 17 00:00:00 2001 From: Abby Shen Date: Sun, 15 Dec 2024 22:45:35 -0800 Subject: [PATCH 5/9] fix test --- tests/spcs/test_services.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/spcs/test_services.py b/tests/spcs/test_services.py index 0225fce16f..75edeb128b 100644 --- a/tests/spcs/test_services.py +++ b/tests/spcs/test_services.py @@ -905,7 +905,7 @@ def test_service_metrics_disabled(mock_is_disabled, runner): @patch( - "snowflake.cli.api.feature_flags.FeatureFlag.ENABLE_SPCS_SERVICE_EVENTS.is_disabled" + "snowflake.cli.api.feature_flags.FeatureFlag.ENABLE_SPCS_SERVICE_METRICS.is_disabled" ) @patch("snowflake.cli._plugins.spcs.services.manager.ServiceManager.execute_query") def test_metrics_all_filters(mock_execute_query, mock_is_disabled, runner): From 0babe9f064ed6e7c654d04686d9d4ca8cc9451cc Mon Sep 17 00:00:00 2001 From: Abby Shen Date: Tue, 17 Dec 2024 18:56:46 -0800 Subject: [PATCH 6/9] new table format and new test and new snapshot --- src/snowflake/cli/_plugins/spcs/common.py | 129 +++++++++ .../cli/_plugins/spcs/services/commands.py | 93 ++---- .../cli/_plugins/spcs/services/manager.py | 219 ++++++++------ tests/__snapshots__/test_help_messages.ambr | 28 +- tests/spcs/test_services.py | 273 +++++++++++------- 5 files changed, 462 insertions(+), 280 deletions(-) diff --git a/src/snowflake/cli/_plugins/spcs/common.py b/src/snowflake/cli/_plugins/spcs/common.py index 100bdf6d82..a2a8e6a142 100644 --- a/src/snowflake/cli/_plugins/spcs/common.py +++ b/src/snowflake/cli/_plugins/spcs/common.py @@ -14,7 +14,9 @@ from __future__ import annotations +import json import sys +from datetime import datetime from typing import TextIO from click import ClickException @@ -23,6 +25,22 @@ from snowflake.cli.api.project.util import unquote_identifier from snowflake.connector.errors import ProgrammingError +EVENT_COLUMN_NAMES = [ + "TIMESTAMP", + "START_TIMESTAMP", + "OBSERVED_TIMESTAMP", + "TRACE", + "RESOURCE", + "RESOURCE_ATTRIBUTES", + "SCOPE", + "SCOPE_ATTRIBUTES", + "RECORD_TYPE", + "RECORD", + "RECORD_ATTRIBUTES", + "VALUE", + "EXEMPLARS", +] + if not sys.stdout.closed and sys.stdout.isatty(): GREEN = "\033[32m" BLUE = "\033[34m" @@ -124,5 +142,116 @@ def new_logs_only(prev_log_records: list[str], new_log_records: list[str]) -> li return new_log_records_sorted +def build_resource_clause( + service_name: str, instance_id: str, container_name: str +) -> str: + resource_filters = [] + if service_name: + resource_filters.append( + f"resource_attributes:\"snow.service.name\" = '{service_name}'" + ) + if instance_id: + resource_filters.append( + f"(resource_attributes:\"snow.service.instance\" = '{instance_id}' " + f"OR resource_attributes:\"snow.service.container.instance\" = '{instance_id}')" + ) + if container_name: + resource_filters.append( + f"resource_attributes:\"snow.service.container.name\" = '{container_name}'" + ) + return " and ".join(resource_filters) if resource_filters else "1=1" + + +def build_time_clauses( + since: str | datetime | None, until: str | datetime | None +) -> tuple[str, str]: + since_clause = "" + until_clause = "" + + if isinstance(since, datetime): + since_clause = f"and timestamp >= '{since}'" + elif isinstance(since, str) and since: + since_clause = f"and timestamp >= sysdate() - interval '{since}'" + + if isinstance(until, datetime): + until_clause = f"and timestamp <= '{until}'" + elif isinstance(until, str) and until: + until_clause = f"and timestamp <= sysdate() - interval '{until}'" + + return since_clause, until_clause + + +def format_event_row(event_dict: dict) -> dict: + try: + resource_attributes = json.loads(event_dict.get("RESOURCE_ATTRIBUTES", "{}")) + record_attributes = json.loads(event_dict.get("RECORD_ATTRIBUTES", "{}")) + record = json.loads(event_dict.get("RECORD", "{}")) + + database_name = resource_attributes.get("snow.database.name", "N/A") + schema_name = resource_attributes.get("snow.schema.name", "N/A") + service_name = resource_attributes.get("snow.service.name", "N/A") + instance_name = resource_attributes.get("snow.service.instance", "N/A") + container_name = resource_attributes.get("snow.service.container.name", "N/A") + event_name = record_attributes.get("event.name", "Unknown Event") + event_value = event_dict.get("VALUE", "Unknown Value") + severity = record.get("severity_text", "Unknown Severity") + + return { + "TIMESTAMP": event_dict.get("TIMESTAMP", "N/A"), + "DATABASE NAME": database_name, + "SCHEMA NAME": schema_name, + "SERVICE NAME": service_name, + "INSTANCE NAME": instance_name, + "CONTAINER NAME": container_name, + "SEVERITY": severity, + "EVENT NAME": event_name, + "EVENT VALUE": event_value, + } + except (json.JSONDecodeError, KeyError) as e: + raise RecordProcessingError(f"Error processing event row: {str(e)}") + + +def format_metric_row(metric_dict: dict) -> dict: + try: + resource_attributes = json.loads(metric_dict["RESOURCE_ATTRIBUTES"]) + record = json.loads(metric_dict["RECORD"]) + + database_name = resource_attributes.get("snow.database.name", "N/A") + schema_name = resource_attributes.get("snow.schema.name", "N/A") + service_name = resource_attributes.get("snow.service.name", "N/A") + instance_name = resource_attributes.get( + "snow.service.container.instance", "N/A" + ) + container_name = resource_attributes.get("snow.service.container.name", "N/A") + + metric_name = record["metric"].get("name", "Unknown Metric") + metric_value = metric_dict.get("VALUE", "Unknown Value") + + return { + "TIMESTAMP": metric_dict.get("TIMESTAMP", "N/A"), + "DATABASE NAME": database_name, + "SCHEMA NAME": schema_name, + "SERVICE NAME": service_name, + "INSTANCE NAME": instance_name, + "CONTAINER NAME": container_name, + "METRIC NAME": metric_name, + "METRIC VALUE": metric_value, + } + except (json.JSONDecodeError, KeyError) as e: + raise RecordProcessingError(f"Error processing metric row: {str(e)}") + + +class RecordProcessingError(ClickException): + """Raised when processing an event or metric record fails due to invalid data.""" + + pass + + +class SPCSEventTableError(ClickException): + """Raised when there is an issue related to the SPCS event table.""" + + pass + + class NoPropertiesProvidedError(ClickException): pass diff --git a/src/snowflake/cli/_plugins/spcs/services/commands.py b/src/snowflake/cli/_plugins/spcs/services/commands.py index 17dd1f06c2..9f0c343f93 100644 --- a/src/snowflake/cli/_plugins/spcs/services/commands.py +++ b/src/snowflake/cli/_plugins/spcs/services/commands.py @@ -331,6 +331,12 @@ def events( show_default=False, help="Fetch only the last N events. Cannot be used with --first.", ), + show_all_columns: bool = typer.Option( + False, + "--all", + is_flag=True, + help="Fetch all columns.", + ), **options, ): if FeatureFlag.ENABLE_SPCS_SERVICE_EVENTS.is_disabled(): @@ -343,22 +349,6 @@ def events( raise IncompatibleParametersError(["--first", "--last"]) manager = ServiceManager() - column_names = [ - "TIMESTAMP", - "START_TIMESTAMP", - "OBSERVED_TIMESTAMP", - "TRACE", - "RESOURCE", - "RESOURCE_ATTRIBUTES", - "SCOPE", - "SCOPE_ATTRIBUTES", - "RECORD_TYPE", - "RECORD", - "RECORD_ATTRIBUTES", - "VALUE", - "EXEMPLARS", - ] - events = manager.get_events( service_name=name.identifier, container_name=container_name, @@ -367,13 +357,9 @@ def events( until=until, first=first, last=last, + show_all_columns=show_all_columns, ) - transformed_events = [dict(zip(column_names, event)) for event in events] - - if not transformed_events: - return MessageResult("No events found.") - - return CollectionResult(transformed_events) + return CollectionResult(events) @app.command(requires_connection=True) @@ -399,15 +385,11 @@ def metrics( default="", help="Fetch events that are older than this time ago, in Snowflake interval syntax.", ), - first: int = typer.Option( - default=-1, - show_default=False, - help="Fetch only the first N events. Cannot be used with --last.", - ), - last: int = typer.Option( - default=-1, - show_default=False, - help="Fetch only the last N events. Cannot be used with --first.", + show_all_columns: bool = typer.Option( + False, + "--all", + is_flag=True, + help="Fetch all columns.", ), **options, ): @@ -417,41 +399,28 @@ def metrics( "Service metrics collection from SPCS event table is disabled.", ) - if first >= 0 and last >= 0: - raise IncompatibleParametersError(["--first", "--last"]) - manager = ServiceManager() - column_names = [ - "TIMESTAMP", - "START_TIMESTAMP", - "OBSERVED_TIMESTAMP", - "TRACE", - "RESOURCE", - "RESOURCE_ATTRIBUTES", - "SCOPE", - "SCOPE_ATTRIBUTES", - "RECORD_TYPE", - "RECORD", - "RECORD_ATTRIBUTES", - "VALUE", - "EXEMPLARS", - ] - - metrics = manager.get_metrics( - service_name=name.identifier, - container_name=container_name, - instance_id=instance_id, - since=since, - until=until, - first=first, - last=last, - ) - transformed_metrics = [dict(zip(column_names, metrix)) for metrix in metrics] + if since or until: + metrics = manager.get_all_metrics( + service_name=name.identifier, + container_name=container_name, + instance_id=instance_id, + since=since, + until=until, + show_all_columns=show_all_columns, + ) + else: + metrics = manager.get_latest_metrics( + service_name=name.identifier, + container_name=container_name, + instance_id=instance_id, + show_all_columns=show_all_columns, + ) - if not transformed_metrics: + if not metrics: return MessageResult("No metrics found.") - return CollectionResult(transformed_metrics) + return CollectionResult(metrics) @app.command(requires_connection=True) diff --git a/src/snowflake/cli/_plugins/spcs/services/manager.py b/src/snowflake/cli/_plugins/spcs/services/manager.py index 44a9c8bbaa..ed53c3d87c 100644 --- a/src/snowflake/cli/_plugins/spcs/services/manager.py +++ b/src/snowflake/cli/_plugins/spcs/services/manager.py @@ -23,8 +23,14 @@ import yaml from snowflake.cli._plugins.object.common import Tag from snowflake.cli._plugins.spcs.common import ( + EVENT_COLUMN_NAMES, NoPropertiesProvidedError, + SPCSEventTableError, + build_resource_clause, + build_time_clauses, filter_log_timestamp, + format_event_row, + format_metric_row, handle_object_already_exists, new_logs_only, strip_empty_lines, @@ -200,10 +206,15 @@ def stream_logs( except KeyboardInterrupt: return - def get_account_event_table(self) -> str: + def get_account_event_table(self): query = "show parameters like 'event_table' in account" results = self.execute_query(query, cursor_class=DictCursor) - return next((r["value"] for r in results if r["key"] == "EVENT_TABLE"), "") + event_table = next( + (r["value"] for r in results if r["key"] == "EVENT_TABLE"), "" + ) + if not event_table: + raise SPCSEventTableError("No SPCS event table configured in the account.") + return event_table def get_events( self, @@ -214,48 +225,70 @@ def get_events( until: str | datetime | None = None, first: int = -1, last: int = -1, + show_all_columns: bool = False, ): account_event_table = self.get_account_event_table() - if not account_event_table: + resource_clause = build_resource_clause( + service_name, instance_id, container_name + ) + since_clause, until_clause = build_time_clauses(since, until) + + first_clause = f"limit {first}" if first >= 0 else "" + last_clause = f"limit {last}" if last >= 0 else "" + + query = f"""\ + select * + from ( + select * + from {account_event_table} + where ( + {resource_clause} + {since_clause} + {until_clause} + ) + and record_type = 'LOG' + and scope['name'] = 'snow.spcs.platform' + order by timestamp desc + {last_clause} + ) + order by timestamp asc + {first_clause} + """ + + cursor = self.execute_query(query) + raw_events = cursor.fetchall() + if not raw_events: return [] - resource_filters = [] - if service_name: - resource_filters.append( - f"resource_attributes:\"snow.service.name\" = '{service_name}'" - ) - if instance_id: - resource_filters.append( - f"(resource_attributes:\"snow.service.instance\" = '{instance_id}' OR resource_attributes:\"snow.service.container.instance\" = '{instance_id}')" - ) - if container_name: - resource_filters.append( - f"resource_attributes:\"snow.service.container.name\" = '{container_name}'" - ) + if show_all_columns: + return [dict(zip(EVENT_COLUMN_NAMES, event)) for event in raw_events] - resource_clause = " and ".join(resource_filters) if resource_filters else "1=1" + formatted_events = [] + for raw_event in raw_events: + event_dict = dict(zip(EVENT_COLUMN_NAMES, raw_event)) + formatted = format_event_row(event_dict) + formatted_events.append(formatted) - if isinstance(since, datetime): - since_clause = f"and timestamp >= '{since}'" - elif isinstance(since, str) and since: - since_clause = f"and timestamp >= sysdate() - interval '{since}'" - else: - since_clause = "" + return formatted_events - if isinstance(until, datetime): - until_clause = f"and timestamp <= '{until}'" - elif isinstance(until, str) and until: - until_clause = f"and timestamp <= sysdate() - interval '{until}'" - else: - until_clause = "" + def get_all_metrics( + self, + service_name: str, + instance_id: str, + container_name: str, + since: str | datetime | None = None, + until: str | datetime | None = None, + show_all_columns: bool = False, + ): - first_clause = f"limit {first}" if first >= 0 else "" - last_clause = f"limit {last}" if last >= 0 else "" + account_event_table = self.get_account_event_table() + resource_clause = build_resource_clause( + service_name, instance_id, container_name + ) + since_clause, until_clause = build_time_clauses(since, until) - query = self.execute_query( - f"""\ - select * from ( + query = f"""\ select * from {account_event_table} where ( @@ -263,82 +296,76 @@ def get_events( {since_clause} {until_clause} ) - AND RECORD_TYPE = 'LOG' - AND SCOPE['name'] = 'snow.spcs.platform' + and record_type = 'METRIC' + and scope['name'] = 'snow.spcs.platform' order by timestamp desc - {last_clause} - ) order by timestamp asc - {first_clause} """ - ) - return query - def get_metrics( + cursor = self.execute_query(query) + raw_metrics = cursor.fetchall() + if not raw_metrics: + return [] + + if show_all_columns: + return [dict(zip(EVENT_COLUMN_NAMES, metric)) for metric in raw_metrics] + + formatted_metrics = [] + for raw_metric in raw_metrics: + metric_dict = dict(zip(EVENT_COLUMN_NAMES, raw_metric)) + formatted = format_metric_row(metric_dict) + formatted_metrics.append(formatted) + + return formatted_metrics + + def get_latest_metrics( self, service_name: str, instance_id: str, container_name: str, - since: str | datetime | None = None, - until: str | datetime | None = None, - first: int = -1, - last: int = -1, + show_all_columns: bool = False, ): + account_event_table = self.get_account_event_table() - if not account_event_table: - return [] + resource_clause = build_resource_clause( + service_name, instance_id, container_name + ) - resource_filters = [] - if service_name: - resource_filters.append( - f"resource_attributes:\"snow.service.name\" = '{service_name}'" - ) - if instance_id: - resource_filters.append( - f"(resource_attributes:\"snow.service.instance\" = '{instance_id}' OR resource_attributes:\"snow.service.container.instance\" = '{instance_id}')" + query = f""" + with rankedmetrics as ( + select + *, + row_number() over ( + partition by record['metric']['name'] + order by timestamp desc + ) as rank + from {account_event_table} + where + record_type = 'METRIC' + and scope['name'] = 'snow.spcs.platform' + and {resource_clause} + and timestamp > dateadd('hour', -1, current_timestamp) ) - if container_name: - resource_filters.append( - f"resource_attributes:\"snow.service.container.name\" = '{container_name}'" - ) - - resource_clause = " and ".join(resource_filters) if resource_filters else "1=1" - - if isinstance(since, datetime): - since_clause = f"and timestamp >= '{since}'" - elif isinstance(since, str) and since: - since_clause = f"and timestamp >= sysdate() - interval '{since}'" - else: - since_clause = "" + select * + from rankedmetrics + where rank = 1 + order by timestamp desc; + """ + + cursor = self.execute_query(query) + raw_metrics = cursor.fetchall() + if not raw_metrics: + return [] - if isinstance(until, datetime): - until_clause = f"and timestamp <= '{until}'" - elif isinstance(until, str) and until: - until_clause = f"and timestamp <= sysdate() - interval '{until}'" - else: - until_clause = "" + if show_all_columns: + return [dict(zip(EVENT_COLUMN_NAMES, metric)) for metric in raw_metrics] - first_clause = f"limit {first}" if first >= 0 else "" - last_clause = f"limit {last}" if last >= 0 else "" + formatted_metrics = [] + for raw_metric in raw_metrics: + metric_dict = dict(zip(EVENT_COLUMN_NAMES, raw_metric)) + formatted = format_metric_row(metric_dict) + formatted_metrics.append(formatted) - query = self.execute_query( - f"""\ - select * from ( - select * - from {account_event_table} - where ( - {resource_clause} - {since_clause} - {until_clause} - ) - AND RECORD_TYPE = 'METRIC' - AND SCOPE['name'] = 'snow.spcs.platform' - order by timestamp desc - {last_clause} - ) order by timestamp asc - {first_clause} - """ - ) - return query + return formatted_metrics def upgrade_spec(self, service_name: str, spec_path: Path): spec = self._read_yaml(spec_path) diff --git a/tests/__snapshots__/test_help_messages.ambr b/tests/__snapshots__/test_help_messages.ambr index 214a3a11f5..bb2f3655ae 100644 --- a/tests/__snapshots__/test_help_messages.ambr +++ b/tests/__snapshots__/test_help_messages.ambr @@ -7115,6 +7115,7 @@ | be used with --last. | | --last INTEGER Fetch only the last N events. Cannot | | be used with --first. | + | --all Fetch all columns. | | --help -h Show this message and exit. | +------------------------------------------------------------------------------+ +- Connection configuration ---------------------------------------------------+ @@ -7864,22 +7865,17 @@ | [required] | +------------------------------------------------------------------------------+ +- Options --------------------------------------------------------------------+ - | * --container-name TEXT Name of the container. | - | [required] | - | * --instance-id TEXT ID of the service instance, starting | - | with 0. | - | [required] | - | --since TEXT Fetch events that are newer than this | - | time ago, in Snowflake interval | - | syntax. | - | --until TEXT Fetch events that are older than this | - | time ago, in Snowflake interval | - | syntax. | - | --first INTEGER Fetch only the first N events. Cannot | - | be used with --last. | - | --last INTEGER Fetch only the last N events. Cannot | - | be used with --first. | - | --help -h Show this message and exit. | + | * --container-name TEXT Name of the container. | + | [required] | + | * --instance-id TEXT ID of the service instance, starting with | + | 0. | + | [required] | + | --since TEXT Fetch events that are newer than this | + | time ago, in Snowflake interval syntax. | + | --until TEXT Fetch events that are older than this | + | time ago, in Snowflake interval syntax. | + | --all Fetch all columns. | + | --help -h Show this message and exit. | +------------------------------------------------------------------------------+ +- Connection configuration ---------------------------------------------------+ | --connection,--environment -c TEXT Name of the connection, as | diff --git a/tests/spcs/test_services.py b/tests/spcs/test_services.py index 75edeb128b..88c68568d1 100644 --- a/tests/spcs/test_services.py +++ b/tests/spcs/test_services.py @@ -747,37 +747,39 @@ def test_events_all_filters(mock_execute_query, mock_is_disabled, runner): "value": "event_table_db.data_schema.snowservices_logs", } ], - [ - ( - "2024-12-14 22:27:25.420", - None, - "2024-12-14 22:27:25.420", - None, - None, - json.dumps( - { - "snow.compute_pool.id": 230, - "snow.compute_pool.name": "MY_POOL", - "snow.database.id": 5, - "snow.database.name": "TESTDB", - "snow.schema.id": 5, - "snow.schema.name": "PUBLIC", - "snow.service.container.name": "log-printer", - "snow.service.id": 1568, - "snow.service.instance": "0", - "snow.service.name": "LOG_EVENT", - "snow.service.type": "SERVICE", - } - ), - json.dumps({"name": "snow.spcs.platform"}), - None, - "LOG", - json.dumps({"severity_text": "INFO"}), - json.dumps({"event.name": "CONTAINER.STATUS_CHANGE"}), - json.dumps({"message": "Running", "status": "READY"}), - None, - ) - ], + Mock( + fetchall=lambda: [ + ( + "2024-12-14 22:27:25.420", + None, + "2024-12-14 22:27:25.420", + None, + None, + json.dumps( + { + "snow.compute_pool.id": 230, + "snow.compute_pool.name": "MY_POOL", + "snow.database.id": 5, + "snow.database.name": "TESTDB", + "snow.schema.id": 5, + "snow.schema.name": "PUBLIC", + "snow.service.container.name": "log-printer", + "snow.service.id": 1568, + "snow.service.instance": "0", + "snow.service.name": "LOG_EVENT", + "snow.service.type": "SERVICE", + } + ), + json.dumps({"name": "snow.spcs.platform"}), + None, + "LOG", + json.dumps({"severity_text": "INFO"}), + json.dumps({"event.name": "CONTAINER.STATUS_CHANGE"}), + json.dumps({"message": "Running", "status": "READY"}), + None, + ) + ] + ), ] result = runner.invoke( @@ -812,23 +814,22 @@ def test_events_all_filters(mock_execute_query, mock_is_disabled, runner): actual_query = mock_execute_query.mock_calls[1].args[0] expected_query = ( - " select * from (\n" - " select *\n" - " from event_table_db.data_schema.snowservices_logs\n" - " where (\n" - ' resource_attributes:"snow.service.name" = ' - "'LOG_EVENT' and (resource_attributes:\"snow.service.instance\" = '0' OR " - "resource_attributes:\"snow.service.container.instance\" = '0') and " - "resource_attributes:\"snow.service.container.name\" = 'log-printer'\n" - " and timestamp >= sysdate() - interval '2 hours'\n" - " and timestamp <= sysdate() - interval '1 hour'\n" + " select *\n" + " from (\n" + " select *\n" + " from event_table_db.data_schema.snowservices_logs\n" + " where (\n" + " resource_attributes:\"snow.service.name\" = 'LOG_EVENT' and (resource_attributes:\"snow.service.instance\" = '0' OR resource_attributes:\"snow.service.container.instance\" = '0') and resource_attributes:\"snow.service.container.name\" = 'log-printer'\n" + " and timestamp >= sysdate() - interval '2 hours'\n" + " and timestamp <= sysdate() - interval '1 hour'\n" + " )\n" + " and record_type = 'LOG'\n" + " and scope['name'] = 'snow.spcs.platform'\n" + " order by timestamp desc\n" + " limit 10\n" " )\n" - " AND RECORD_TYPE = 'LOG'\n" - " AND SCOPE['name'] = 'snow.spcs.platform'\n" - " order by timestamp desc\n" - " limit 10\n" - " ) order by timestamp asc\n" - " \n" + " order by timestamp asc\n" + " \n" " " ) @@ -908,7 +909,7 @@ def test_service_metrics_disabled(mock_is_disabled, runner): "snowflake.cli.api.feature_flags.FeatureFlag.ENABLE_SPCS_SERVICE_METRICS.is_disabled" ) @patch("snowflake.cli._plugins.spcs.services.manager.ServiceManager.execute_query") -def test_metrics_all_filters(mock_execute_query, mock_is_disabled, runner): +def test_latest_metrics(mock_execute_query, mock_is_disabled, runner): mock_is_disabled.return_value = False mock_execute_query.side_effect = [ [ @@ -917,31 +918,35 @@ def test_metrics_all_filters(mock_execute_query, mock_is_disabled, runner): "value": "event_table_db.data_schema.snowservices_logs", } ], - [ - ( - datetime(2024, 12, 10, 18, 53, 21, 809000), - datetime(2024, 12, 10, 18, 52, 51, 809000), - None, - None, - None, - json.dumps( - { - "snow.account.name": "XACCOUNTTEST1", - "snow.compute_pool.id": 20641, - "snow.compute_pool.name": "MY_POOL", - "snow.service.container.name": "log-printer", - "snow.service.name": "LOG_EVENT", - } - ), - json.dumps({"name": "snow.spcs.platform"}), - None, - "METRIC", - json.dumps({"metric": {"name": "container.cpu.usage", "unit": "cpu"}}), - None, - "0.0005007168666666691", - None, - ) - ], + Mock( + fetchall=lambda: [ + ( + datetime(2024, 12, 10, 18, 53, 21, 809000), + datetime(2024, 12, 10, 18, 52, 51, 809000), + None, + None, + None, + json.dumps( + { + "snow.account.name": "XACCOUNTTEST1", + "snow.compute_pool.id": 20641, + "snow.compute_pool.name": "MY_POOL", + "snow.service.container.name": "log-printer", + "snow.service.name": "LOG_EVENT", + } + ), + json.dumps({"name": "snow.spcs.platform"}), + None, + "METRIC", + json.dumps( + {"metric": {"name": "container.cpu.usage", "unit": "cpu"}} + ), + None, + "0.0005007168666666691", + None, + ) + ] + ), ] result = runner.invoke( @@ -954,12 +959,6 @@ def test_metrics_all_filters(mock_execute_query, mock_is_disabled, runner): "log-printer", "--instance-id", "0", - "--since", - "2 hours", - "--until", - "1 hour", - "--last", - "10", "--warehouse", "XSMALL", "--role", @@ -976,36 +975,77 @@ def test_metrics_all_filters(mock_execute_query, mock_is_disabled, runner): actual_query = mock_execute_query.mock_calls[1].args[0] expected_query = ( - " select * from (\n" - " select *\n" - " from event_table_db.data_schema.snowservices_logs\n" - " where (\n" - ' resource_attributes:"snow.service.name" = ' - "'LOG_EVENT' and (resource_attributes:\"snow.service.instance\" = '0' OR " - "resource_attributes:\"snow.service.container.instance\" = '0') and " - "resource_attributes:\"snow.service.container.name\" = 'log-printer'\n" - " and timestamp >= sysdate() - interval '2 hours'\n" - " and timestamp <= sysdate() - interval '1 hour'\n" - " )\n" - " AND RECORD_TYPE = 'METRIC'\n" - " AND SCOPE['name'] = 'snow.spcs.platform'\n" - " order by timestamp desc\n" - " limit 10\n" - " ) order by timestamp asc\n" - " \n" - " " + "\n" + " with rankedmetrics as (\n" + " select \n" + " *,\n" + " row_number() over (\n" + " partition by record['metric']['name'] \n" + " order by timestamp desc\n" + " ) as rank\n" + " from event_table_db.data_schema.snowservices_logs\n" + " where \n" + " record_type = 'METRIC'\n" + " and scope['name'] = 'snow.spcs.platform'\n" + " and resource_attributes:\"snow.service.name\" = 'LOG_EVENT' and (resource_attributes:\"snow.service.instance\" = '0' OR resource_attributes:\"snow.service.container.instance\" = '0') and resource_attributes:\"snow.service.container.name\" = 'log-printer' \n" + " and timestamp > dateadd('hour', -1, current_timestamp) \n" + " )\n" + " select *\n" + " from rankedmetrics\n" + " where rank = 1\n" + " order by timestamp desc;\n" + " " ) assert ( actual_query == expected_query - ), f"Generated query does not match expected query.\n\nActual:\n{actual_query}\n\nExpected:\n{expected_query}" + ), f"Generated query does not match expected query.\n\nActual:\n{repr(actual_query)}\n\nExpected:\n{repr(expected_query)}" @patch( "snowflake.cli.api.feature_flags.FeatureFlag.ENABLE_SPCS_SERVICE_METRICS.is_disabled" ) -def test_metrics_first_last_incompatibility(mock_is_disabled, runner): +@patch("snowflake.cli._plugins.spcs.services.manager.ServiceManager.execute_query") +def test_metrics_all_filters(mock_execute_query, mock_is_disabled, runner): mock_is_disabled.return_value = False + mock_execute_query.side_effect = [ + [ + { + "key": "EVENT_TABLE", + "value": "event_table_db.data_schema.snowservices_logs", + } + ], + Mock( + fetchall=lambda: [ + ( + datetime(2024, 12, 10, 18, 53, 21, 809000), + datetime(2024, 12, 10, 18, 52, 51, 809000), + None, + None, + None, + json.dumps( + { + "snow.account.name": "XACCOUNTTEST1", + "snow.compute_pool.id": 20641, + "snow.compute_pool.name": "MY_POOL", + "snow.service.container.name": "log-printer", + "snow.service.name": "LOG_EVENT", + } + ), + json.dumps({"name": "snow.spcs.platform"}), + None, + "METRIC", + json.dumps( + {"metric": {"name": "container.cpu.usage", "unit": "cpu"}} + ), + None, + "0.0005007168666666691", + None, + ) + ] + ), + ] + result = runner.invoke( [ "spcs", @@ -1016,10 +1056,10 @@ def test_metrics_first_last_incompatibility(mock_is_disabled, runner): "log-printer", "--instance-id", "0", - "--first", - "10", - "--last", - "5", + "--since", + "2 hour", + "--until", + "1 hour", "--warehouse", "XSMALL", "--role", @@ -1027,10 +1067,31 @@ def test_metrics_first_last_incompatibility(mock_is_disabled, runner): ] ) - assert result.exit_code != 0, result.output + assert result.exit_code == 0, f"Command failed with output: {result.output}" - expected_error = "Parameters '--first' and '--last' are incompatible" - assert expected_error in result.output + call_0 = mock_execute_query.mock_calls[0].args[0] + assert ( + call_0 == "show parameters like 'event_table' in account" + ), f"Unexpected query in Call 0: {call_0}" + + actual_query = mock_execute_query.mock_calls[1].args[0] + expected_query = ( + " select *\n" + " from event_table_db.data_schema.snowservices_logs\n" + " where (\n" + " resource_attributes:\"snow.service.name\" = 'LOG_EVENT' and (resource_attributes:\"snow.service.instance\" = '0' OR resource_attributes:\"snow.service.container.instance\" = '0') and resource_attributes:\"snow.service.container.name\" = 'log-printer'\n" + " and timestamp >= sysdate() - interval '2 hour'\n" + " and timestamp <= sysdate() - interval '1 hour'\n" + " )\n" + " and record_type = 'METRIC'\n" + " and scope['name'] = 'snow.spcs.platform'\n" + " order by timestamp desc\n" + " " + ) + + assert ( + actual_query == expected_query + ), f"Generated query does not match expected query.\n\nActual:\n{actual_query}\n\nExpected:\n{expected_query}" def test_read_yaml(other_directory): From 8f04e80e995cd230a77cc4e5cbc31703ceb21641 Mon Sep 17 00:00:00 2001 From: Abby Shen Date: Wed, 18 Dec 2024 13:59:54 -0800 Subject: [PATCH 7/9] fix comment --- src/snowflake/cli/_plugins/spcs/common.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/snowflake/cli/_plugins/spcs/common.py b/src/snowflake/cli/_plugins/spcs/common.py index a2a8e6a142..770803e717 100644 --- a/src/snowflake/cli/_plugins/spcs/common.py +++ b/src/snowflake/cli/_plugins/spcs/common.py @@ -208,7 +208,7 @@ def format_event_row(event_dict: dict) -> dict: "EVENT VALUE": event_value, } except (json.JSONDecodeError, KeyError) as e: - raise RecordProcessingError(f"Error processing event row: {str(e)}") + raise RecordProcessingError(f"Error processing event row.") def format_metric_row(metric_dict: dict) -> dict: @@ -238,7 +238,7 @@ def format_metric_row(metric_dict: dict) -> dict: "METRIC VALUE": metric_value, } except (json.JSONDecodeError, KeyError) as e: - raise RecordProcessingError(f"Error processing metric row: {str(e)}") + raise RecordProcessingError(f"Error processing metric row.") class RecordProcessingError(ClickException): From 6789138d0d0fbd1c9d1505b548685560f149923f Mon Sep 17 00:00:00 2001 From: Abby Shen Date: Wed, 18 Dec 2024 14:09:52 -0800 Subject: [PATCH 8/9] release notes --- RELEASE-NOTES.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 2763cc0a24..5aaabc23f0 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -25,12 +25,15 @@ * `snow app release-directive set` * `snow app release-directive unset` * `snow app version create` now returns version, patch, and label in JSON format. -* Add support for release channels: - * Add support for release channels feature in native app version creation/drop. - * Add ability to specify release channel when creating application instance from release directive: `snow app run --from-release-directive --channel=` - * Add ability to list release channels through `snow app release-channel list` command - * Add ability to add and remove accounts from release channels through `snow app release-channel add-accounts` and snow app release-channel remove-accounts` commands. - * Add ability to add/remove versions to/from release channels through `snow app release-channel add-version` and `snow app release-channel remove-version` commands. +* Add ability to specify release channel when creating application instance from release directive: `snow app run --from-release-directive --channel=` +* Add ability to list release channels through `snow app release-channel list` command +* Add `snow spcs service events` command to retrieve service-specific events: + * Supports filtering by service name, container name, instance ID, time intervals (`--since`, `--until`), and pagination (`--first`, `--last`). + * Use `--all` to fetch all columns. +* Add `snow spcs service metrics` command to fetch service metrics: + * Supports filtering by service name, container name, instance ID, and time intervals (`--since`, `--until`). + * Use `--all` to fetch all columns. + ## Fixes and improvements * Fixed crashes with older x86_64 Intel CPUs. From 281ca397f9941bc702d27409d8d73e958fd327e7 Mon Sep 17 00:00:00 2001 From: Teja Kommineni Date: Tue, 31 Dec 2024 10:59:04 -0800 Subject: [PATCH 9/9] fix comments --- RELEASE-NOTES.md | 9 +- src/snowflake/cli/_plugins/spcs/common.py | 4 +- .../cli/_plugins/spcs/services/commands.py | 124 ++-- .../cli/_plugins/spcs/services/manager.py | 8 +- tests/__snapshots__/test_help_messages.ambr | 576 +++++++++++++++++- tests/spcs/test_services.py | 16 +- 6 files changed, 654 insertions(+), 83 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 5aaabc23f0..850b40a4f3 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -25,8 +25,12 @@ * `snow app release-directive set` * `snow app release-directive unset` * `snow app version create` now returns version, patch, and label in JSON format. -* Add ability to specify release channel when creating application instance from release directive: `snow app run --from-release-directive --channel=` -* Add ability to list release channels through `snow app release-channel list` command +* Add support for release channels: + * Add support for release channels feature in native app version creation/drop. + * Add ability to specify release channel when creating application instance from release directive: `snow app run --from-release-directive --channel=` + * Add ability to list release channels through `snow app release-channel list` command + * Add ability to add and remove accounts from release channels through `snow app release-channel add-accounts` and snow app release-channel remove-accounts` commands. + * Add ability to add/remove versions to/from release channels through `snow app release-channel add-version` and `snow app release-channel remove-version` commands. * Add `snow spcs service events` command to retrieve service-specific events: * Supports filtering by service name, container name, instance ID, time intervals (`--since`, `--until`), and pagination (`--first`, `--last`). * Use `--all` to fetch all columns. @@ -34,7 +38,6 @@ * Supports filtering by service name, container name, instance ID, and time intervals (`--since`, `--until`). * Use `--all` to fetch all columns. - ## Fixes and improvements * Fixed crashes with older x86_64 Intel CPUs. * Fixed inability to add patches to lowercase quoted versions diff --git a/src/snowflake/cli/_plugins/spcs/common.py b/src/snowflake/cli/_plugins/spcs/common.py index 770803e717..6855a34419 100644 --- a/src/snowflake/cli/_plugins/spcs/common.py +++ b/src/snowflake/cli/_plugins/spcs/common.py @@ -201,7 +201,7 @@ def format_event_row(event_dict: dict) -> dict: "DATABASE NAME": database_name, "SCHEMA NAME": schema_name, "SERVICE NAME": service_name, - "INSTANCE NAME": instance_name, + "INSTANCE ID": instance_name, "CONTAINER NAME": container_name, "SEVERITY": severity, "EVENT NAME": event_name, @@ -232,7 +232,7 @@ def format_metric_row(metric_dict: dict) -> dict: "DATABASE NAME": database_name, "SCHEMA NAME": schema_name, "SERVICE NAME": service_name, - "INSTANCE NAME": instance_name, + "INSTANCE ID": instance_name, "CONTAINER NAME": container_name, "METRIC NAME": metric_name, "METRIC VALUE": metric_value, diff --git a/src/snowflake/cli/_plugins/spcs/services/commands.py b/src/snowflake/cli/_plugins/spcs/services/commands.py index 9f0c343f93..b8ede471d5 100644 --- a/src/snowflake/cli/_plugins/spcs/services/commands.py +++ b/src/snowflake/cli/_plugins/spcs/services/commands.py @@ -60,6 +60,38 @@ short_help="Manages services.", ) +# Define common options +container_name_option = typer.Option( + ..., + "--container-name", + help="Name of the container.", + show_default=False, +) + +instance_id_option = typer.Option( + ..., + "--instance-id", + help="ID of the service instance, starting with 0.", + show_default=False, +) + +since_option = typer.Option( + default="", + help="Fetch events that are newer than this time ago, in Snowflake interval syntax.", +) + +until_option = typer.Option( + default="", + help="Fetch events that are older than this time ago, in Snowflake interval syntax.", +) + +show_all_columns_option = typer.Option( + False, + "--all", + is_flag=True, + help="Fetch all columns.", +) + def _service_name_callback(name: FQN) -> FQN: if not is_valid_object_name(name.identifier, max_depth=2, allow_quoted=False): @@ -214,18 +246,8 @@ def status(name: FQN = ServiceNameArgument, **options) -> CommandResult: @app.command(requires_connection=True) def logs( name: FQN = ServiceNameArgument, - container_name: str = typer.Option( - ..., - "--container-name", - help="Name of the container.", - show_default=False, - ), - instance_id: str = typer.Option( - ..., - "--instance-id", - help="ID of the service instance, starting with 0.", - show_default=False, - ), + container_name: str = container_name_option, + instance_id: str = instance_id_option, num_lines: int = typer.Option( DEFAULT_NUM_LINES, "--num-lines", help="Number of lines to retrieve." ), @@ -301,51 +323,33 @@ def logs( @app.command(requires_connection=True) def events( name: FQN = ServiceNameArgument, - container_name: str = typer.Option( - ..., - "--container-name", - help="Name of the container.", - show_default=False, - ), - instance_id: str = typer.Option( - ..., - "--instance-id", - help="ID of the service instance, starting with 0.", - show_default=False, - ), - since: str = typer.Option( - default="", - help="Fetch events that are newer than this time ago, in Snowflake interval syntax.", - ), - until: str = typer.Option( - default="", - help="Fetch events that are older than this time ago, in Snowflake interval syntax.", - ), + container_name: str = container_name_option, + instance_id: str = instance_id_option, + since: str = since_option, + until: str = until_option, first: int = typer.Option( - default=-1, + default=None, show_default=False, help="Fetch only the first N events. Cannot be used with --last.", ), last: int = typer.Option( - default=-1, + default=None, show_default=False, help="Fetch only the last N events. Cannot be used with --first.", ), - show_all_columns: bool = typer.Option( - False, - "--all", - is_flag=True, - help="Fetch all columns.", - ), + show_all_columns: bool = show_all_columns_option, **options, ): + """ + Retrieve platform events for a service container. + """ if FeatureFlag.ENABLE_SPCS_SERVICE_EVENTS.is_disabled(): raise FeatureNotEnabledError( "ENABLE_SPCS_SERVICE_EVENTS", "Service events collection from SPCS event table is disabled.", ) - if first >= 0 and last >= 0: + if first is not None and last is not None: raise IncompatibleParametersError(["--first", "--last"]) manager = ServiceManager() @@ -359,40 +363,26 @@ def events( last=last, show_all_columns=show_all_columns, ) + + if not events: + return MessageResult("No events found.") + return CollectionResult(events) @app.command(requires_connection=True) def metrics( name: FQN = ServiceNameArgument, - container_name: str = typer.Option( - ..., - "--container-name", - help="Name of the container.", - show_default=False, - ), - instance_id: str = typer.Option( - ..., - "--instance-id", - help="ID of the service instance, starting with 0.", - show_default=False, - ), - since: str = typer.Option( - default="", - help="Fetch events that are newer than this time ago, in Snowflake interval syntax.", - ), - until: str = typer.Option( - default="", - help="Fetch events that are older than this time ago, in Snowflake interval syntax.", - ), - show_all_columns: bool = typer.Option( - False, - "--all", - is_flag=True, - help="Fetch all columns.", - ), + container_name: str = container_name_option, + instance_id: str = instance_id_option, + since: str = since_option, + until: str = until_option, + show_all_columns: bool = show_all_columns_option, **options, ): + """ + Retrieve platform metrics for a service container. + """ if FeatureFlag.ENABLE_SPCS_SERVICE_METRICS.is_disabled(): raise FeatureNotEnabledError( "ENABLE_SPCS_SERVICE_METRICS", diff --git a/src/snowflake/cli/_plugins/spcs/services/manager.py b/src/snowflake/cli/_plugins/spcs/services/manager.py index ed53c3d87c..86d8dad9a5 100644 --- a/src/snowflake/cli/_plugins/spcs/services/manager.py +++ b/src/snowflake/cli/_plugins/spcs/services/manager.py @@ -223,8 +223,8 @@ def get_events( container_name: str, since: str | datetime | None = None, until: str | datetime | None = None, - first: int = -1, - last: int = -1, + first: Optional[int] = None, + last: Optional[int] = None, show_all_columns: bool = False, ): @@ -234,8 +234,8 @@ def get_events( ) since_clause, until_clause = build_time_clauses(since, until) - first_clause = f"limit {first}" if first >= 0 else "" - last_clause = f"limit {last}" if last >= 0 else "" + first_clause = f"limit {first}" if first is not None else "" + last_clause = f"limit {last}" if last is not None else "" query = f"""\ select * diff --git a/tests/__snapshots__/test_help_messages.ambr b/tests/__snapshots__/test_help_messages.ambr index bb2f3655ae..c47c99e1cc 100644 --- a/tests/__snapshots__/test_help_messages.ambr +++ b/tests/__snapshots__/test_help_messages.ambr @@ -578,6 +578,542 @@ +------------------------------------------------------------------------------+ + ''' +# --- +# name: test_help_messages[app.release-channel.add-accounts] + ''' + + Usage: default app release-channel add-accounts [OPTIONS] CHANNEL + + Adds accounts to a release channel. + + +- Arguments ------------------------------------------------------------------+ + | * channel TEXT The release channel to add accounts to. | + | [required] | + +------------------------------------------------------------------------------+ + +- Options --------------------------------------------------------------------+ + | * --target-accounts TEXT The accounts to add to the release | + | channel. Format has to be | + | org1.account1,org2.account2. | + | [required] | + | --package-entity-id TEXT The ID of the package entity on which | + | to operate when definition_version is | + | 2 or higher. | + | --app-entity-id TEXT The ID of the application entity on | + | which to operate when | + | definition_version is 2 or higher. | + | --project -p TEXT Path where Snowflake project resides. | + | Defaults to current working directory. | + | --env TEXT String in format of key=value. | + | Overrides variables from env section | + | used for templates. | + | --help -h Show this message and exit. | + +------------------------------------------------------------------------------+ + +- Connection configuration ---------------------------------------------------+ + | --connection,--environment -c TEXT Name of the connection, as | + | defined in your config.toml | + | file. Default: default. | + | --host TEXT Host address for the | + | connection. Overrides the | + | value specified for the | + | connection. | + | --port INTEGER Port for the connection. | + | Overrides the value | + | specified for the | + | connection. | + | --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-file,--private… TEXT Snowflake private key file | + | path. Overrides the value | + | specified for the | + | connection. | + | --token-file-path TEXT Path to file with an OAuth | + | token that should be used | + | when connecting to Snowflake | + | --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) | + | --enable-diag Run Python connector | + | diagnostic test | + | --diag-log-path TEXT Diagnostic report path | + | --diag-allowlist-path TEXT Diagnostic report path to | + | optional allowlist | + +------------------------------------------------------------------------------+ + +- 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 contain additional | + | information. | + | --silent Turns off intermediate output to console. | + +------------------------------------------------------------------------------+ + + + ''' +# --- +# name: test_help_messages[app.release-channel.add-version] + ''' + + Usage: default app release-channel add-version [OPTIONS] CHANNEL + + Adds a version to a release channel. + + +- Arguments ------------------------------------------------------------------+ + | * channel TEXT The release channel to add a version to. | + | [required] | + +------------------------------------------------------------------------------+ + +- Options --------------------------------------------------------------------+ + | * --version TEXT The version to add to the release | + | channel. | + | [required] | + | --package-entity-id TEXT The ID of the package entity on which | + | to operate when definition_version is | + | 2 or higher. | + | --app-entity-id TEXT The ID of the application entity on | + | which to operate when | + | definition_version is 2 or higher. | + | --project -p TEXT Path where Snowflake project resides. | + | Defaults to current working directory. | + | --env TEXT String in format of key=value. | + | Overrides variables from env section | + | used for templates. | + | --help -h Show this message and exit. | + +------------------------------------------------------------------------------+ + +- Connection configuration ---------------------------------------------------+ + | --connection,--environment -c TEXT Name of the connection, as | + | defined in your config.toml | + | file. Default: default. | + | --host TEXT Host address for the | + | connection. Overrides the | + | value specified for the | + | connection. | + | --port INTEGER Port for the connection. | + | Overrides the value | + | specified for the | + | connection. | + | --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-file,--private… TEXT Snowflake private key file | + | path. Overrides the value | + | specified for the | + | connection. | + | --token-file-path TEXT Path to file with an OAuth | + | token that should be used | + | when connecting to Snowflake | + | --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) | + | --enable-diag Run Python connector | + | diagnostic test | + | --diag-log-path TEXT Diagnostic report path | + | --diag-allowlist-path TEXT Diagnostic report path to | + | optional allowlist | + +------------------------------------------------------------------------------+ + +- 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 contain additional | + | information. | + | --silent Turns off intermediate output to console. | + +------------------------------------------------------------------------------+ + + + ''' +# --- +# name: test_help_messages[app.release-channel.list] + ''' + + Usage: default app release-channel list [OPTIONS] [CHANNEL] + + Lists the release channels available for an application package. + + +- Arguments ------------------------------------------------------------------+ + | channel [CHANNEL] The release channel to list. If not provided, all | + | release channels are listed. | + +------------------------------------------------------------------------------+ + +- Options --------------------------------------------------------------------+ + | --package-entity-id TEXT The ID of the package entity on which to | + | operate when definition_version is 2 or | + | higher. | + | --app-entity-id TEXT The ID of the application entity on which | + | to operate when definition_version is 2 | + | or higher. | + | --project -p TEXT Path where Snowflake project resides. | + | Defaults to current working directory. | + | --env TEXT String in format of key=value. Overrides | + | variables from env section used for | + | templates. | + | --help -h Show this message and exit. | + +------------------------------------------------------------------------------+ + +- Connection configuration ---------------------------------------------------+ + | --connection,--environment -c TEXT Name of the connection, as | + | defined in your config.toml | + | file. Default: default. | + | --host TEXT Host address for the | + | connection. Overrides the | + | value specified for the | + | connection. | + | --port INTEGER Port for the connection. | + | Overrides the value | + | specified for the | + | connection. | + | --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-file,--private… TEXT Snowflake private key file | + | path. Overrides the value | + | specified for the | + | connection. | + | --token-file-path TEXT Path to file with an OAuth | + | token that should be used | + | when connecting to Snowflake | + | --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) | + | --enable-diag Run Python connector | + | diagnostic test | + | --diag-log-path TEXT Diagnostic report path | + | --diag-allowlist-path TEXT Diagnostic report path to | + | optional allowlist | + +------------------------------------------------------------------------------+ + +- 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 contain additional | + | information. | + | --silent Turns off intermediate output to console. | + +------------------------------------------------------------------------------+ + + + ''' +# --- +# name: test_help_messages[app.release-channel.remove-accounts] + ''' + + Usage: default app release-channel remove-accounts [OPTIONS] CHANNEL + + Removes accounts from a release channel. + + +- Arguments ------------------------------------------------------------------+ + | * channel TEXT The release channel to remove accounts from. | + | [required] | + +------------------------------------------------------------------------------+ + +- Options --------------------------------------------------------------------+ + | * --target-accounts TEXT The accounts to remove from the | + | release channel. Format has to be | + | org1.account1,org2.account2. | + | [required] | + | --package-entity-id TEXT The ID of the package entity on which | + | to operate when definition_version is | + | 2 or higher. | + | --app-entity-id TEXT The ID of the application entity on | + | which to operate when | + | definition_version is 2 or higher. | + | --project -p TEXT Path where Snowflake project resides. | + | Defaults to current working directory. | + | --env TEXT String in format of key=value. | + | Overrides variables from env section | + | used for templates. | + | --help -h Show this message and exit. | + +------------------------------------------------------------------------------+ + +- Connection configuration ---------------------------------------------------+ + | --connection,--environment -c TEXT Name of the connection, as | + | defined in your config.toml | + | file. Default: default. | + | --host TEXT Host address for the | + | connection. Overrides the | + | value specified for the | + | connection. | + | --port INTEGER Port for the connection. | + | Overrides the value | + | specified for the | + | connection. | + | --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-file,--private… TEXT Snowflake private key file | + | path. Overrides the value | + | specified for the | + | connection. | + | --token-file-path TEXT Path to file with an OAuth | + | token that should be used | + | when connecting to Snowflake | + | --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) | + | --enable-diag Run Python connector | + | diagnostic test | + | --diag-log-path TEXT Diagnostic report path | + | --diag-allowlist-path TEXT Diagnostic report path to | + | optional allowlist | + +------------------------------------------------------------------------------+ + +- 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 contain additional | + | information. | + | --silent Turns off intermediate output to console. | + +------------------------------------------------------------------------------+ + + + ''' +# --- +# name: test_help_messages[app.release-channel.remove-version] + ''' + + Usage: default app release-channel remove-version [OPTIONS] CHANNEL + + Removes a version from a release channel. + + +- Arguments ------------------------------------------------------------------+ + | * channel TEXT The release channel to remove a version from. | + | [required] | + +------------------------------------------------------------------------------+ + +- Options --------------------------------------------------------------------+ + | * --version TEXT The version to remove from the release | + | channel. | + | [required] | + | --package-entity-id TEXT The ID of the package entity on which | + | to operate when definition_version is | + | 2 or higher. | + | --app-entity-id TEXT The ID of the application entity on | + | which to operate when | + | definition_version is 2 or higher. | + | --project -p TEXT Path where Snowflake project resides. | + | Defaults to current working directory. | + | --env TEXT String in format of key=value. | + | Overrides variables from env section | + | used for templates. | + | --help -h Show this message and exit. | + +------------------------------------------------------------------------------+ + +- Connection configuration ---------------------------------------------------+ + | --connection,--environment -c TEXT Name of the connection, as | + | defined in your config.toml | + | file. Default: default. | + | --host TEXT Host address for the | + | connection. Overrides the | + | value specified for the | + | connection. | + | --port INTEGER Port for the connection. | + | Overrides the value | + | specified for the | + | connection. | + | --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-file,--private… TEXT Snowflake private key file | + | path. Overrides the value | + | specified for the | + | connection. | + | --token-file-path TEXT Path to file with an OAuth | + | token that should be used | + | when connecting to Snowflake | + | --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) | + | --enable-diag Run Python connector | + | diagnostic test | + | --diag-log-path TEXT Diagnostic report path | + | --diag-allowlist-path TEXT Diagnostic report path to | + | optional allowlist | + +------------------------------------------------------------------------------+ + +- 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 contain additional | + | information. | + | --silent Turns off intermediate output to console. | + +------------------------------------------------------------------------------+ + + + ''' +# --- +# name: test_help_messages[app.release-channel] + ''' + + Usage: default app release-channel [OPTIONS] COMMAND [ARGS]... + + Manages release channels of an application package + + +- Options --------------------------------------------------------------------+ + | --help -h Show this message and exit. | + +------------------------------------------------------------------------------+ + +- Commands -------------------------------------------------------------------+ + | add-accounts Adds accounts to a release channel. | + | add-version Adds a version to a release channel. | + | list Lists the release channels available for an application | + | package. | + | remove-accounts Removes accounts from a release channel. | + | remove-version Removes a version from a release channel. | + +------------------------------------------------------------------------------+ + + ''' # --- # name: test_help_messages[app.release-directive.list] @@ -1777,6 +2313,7 @@ | configured in Snowflake. | | open Opens the Snowflake Native App inside of your browser, | | once it has been installed in your account. | + | release-channel Manages release channels of an application package | | release-directive Manages release directives of an application package | | run Creates an application package in your Snowflake | | account, uploads code files to its stage, then creates | @@ -7095,6 +7632,8 @@ Usage: default spcs service events [OPTIONS] NAME + Retrieve platform events for a service container. + +- Arguments ------------------------------------------------------------------+ | * name TEXT Identifier of the service; for example: my_service | | [required] | @@ -7860,6 +8399,8 @@ Usage: default spcs service metrics [OPTIONS] NAME + Retrieve platform metrics for a service container. + +- Arguments ------------------------------------------------------------------+ | * name TEXT Identifier of the service; for example: my_service | | [required] | @@ -8561,7 +9102,8 @@ | schema. | | describe Provides description of service. | | drop Drops service with given name. | - | events | + | events Retrieve platform events for a service | + | container. | | execute-job Creates and executes a job service in the | | current schema. | | list Lists all available services. | @@ -8571,7 +9113,8 @@ | list-roles Lists all service roles in a service. | | logs Retrieves local logs from a service | | container. | - | metrics | + | metrics Retrieve platform metrics for a service | + | container. | | resume Resumes the service from a SUSPENDED state. | | set Sets one or more properties for the | | service. | @@ -10324,6 +10867,28 @@ +------------------------------------------------------------------------------+ + ''' +# --- +# name: test_help_messages_no_help_flag[app.release-channel] + ''' + + Usage: default app release-channel [OPTIONS] COMMAND [ARGS]... + + Manages release channels of an application package + + +- Options --------------------------------------------------------------------+ + | --help -h Show this message and exit. | + +------------------------------------------------------------------------------+ + +- Commands -------------------------------------------------------------------+ + | add-accounts Adds accounts to a release channel. | + | add-version Adds a version to a release channel. | + | list Lists the release channels available for an application | + | package. | + | remove-accounts Removes accounts from a release channel. | + | remove-version Removes a version from a release channel. | + +------------------------------------------------------------------------------+ + + ''' # --- # name: test_help_messages_no_help_flag[app.release-directive] @@ -10392,6 +10957,7 @@ | configured in Snowflake. | | open Opens the Snowflake Native App inside of your browser, | | once it has been installed in your account. | + | release-channel Manages release channels of an application package | | release-directive Manages release directives of an application package | | run Creates an application package in your Snowflake | | account, uploads code files to its stage, then creates | @@ -10643,7 +11209,8 @@ | schema. | | describe Provides description of service. | | drop Drops service with given name. | - | events | + | events Retrieve platform events for a service | + | container. | | execute-job Creates and executes a job service in the | | current schema. | | list Lists all available services. | @@ -10653,7 +11220,8 @@ | list-roles Lists all service roles in a service. | | logs Retrieves local logs from a service | | container. | - | metrics | + | metrics Retrieve platform metrics for a service | + | container. | | resume Resumes the service from a SUSPENDED state. | | set Sets one or more properties for the | | service. | diff --git a/tests/spcs/test_services.py b/tests/spcs/test_services.py index 88c68568d1..d06fbb86d9 100644 --- a/tests/spcs/test_services.py +++ b/tests/spcs/test_services.py @@ -13,6 +13,7 @@ # limitations under the License. import itertools import json +import re from datetime import datetime from pathlib import Path from textwrap import dedent @@ -997,9 +998,13 @@ def test_latest_metrics(mock_execute_query, mock_is_disabled, runner): " " ) - assert ( - actual_query == expected_query - ), f"Generated query does not match expected query.\n\nActual:\n{repr(actual_query)}\n\nExpected:\n{repr(expected_query)}" + actual_normalized = normalize_query(actual_query) + expected_normalized = normalize_query(expected_query) + + assert actual_normalized == expected_normalized, ( + f"Generated query does not match expected query.\n\n" + f"Actual:\n{actual_query}\n\nExpected:\n{expected_query}" + ) @patch( @@ -1547,3 +1552,8 @@ def test_command_aliases(mock_connector, runner, mock_ctx, command, parameters): queries = ctx.get_queries() assert queries[0] == queries[1] + + +def normalize_query(query): + """Normalize SQL query by stripping extra whitespace and formatting.""" + return re.sub(r"\s+", " ", query.strip())