snowflake-cli 3.2.2__py3-none-any.whl → 3.4.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- snowflake/cli/__about__.py +1 -1
- snowflake/cli/_app/__main__.py +2 -2
- snowflake/cli/_app/cli_app.py +224 -192
- snowflake/cli/_app/commands_registration/commands_registration_with_callbacks.py +1 -27
- snowflake/cli/_app/constants.py +4 -0
- snowflake/cli/_app/snow_connector.py +12 -0
- snowflake/cli/_app/telemetry.py +10 -3
- snowflake/cli/_plugins/connection/util.py +12 -19
- snowflake/cli/_plugins/cortex/commands.py +2 -4
- snowflake/cli/_plugins/git/manager.py +1 -1
- snowflake/cli/_plugins/helpers/commands.py +207 -1
- snowflake/cli/_plugins/nativeapp/artifacts.py +16 -628
- snowflake/cli/_plugins/nativeapp/bundle_context.py +1 -1
- snowflake/cli/_plugins/nativeapp/codegen/artifact_processor.py +1 -1
- snowflake/cli/_plugins/nativeapp/codegen/compiler.py +42 -20
- snowflake/cli/_plugins/nativeapp/codegen/setup/native_app_setup_processor.py +9 -2
- snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py +6 -3
- snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +44 -34
- snowflake/cli/_plugins/nativeapp/commands.py +113 -21
- snowflake/cli/_plugins/nativeapp/constants.py +5 -0
- snowflake/cli/_plugins/nativeapp/entities/application.py +226 -296
- snowflake/cli/_plugins/nativeapp/entities/application_package.py +911 -141
- snowflake/cli/_plugins/nativeapp/entities/application_package_child_interface.py +43 -0
- snowflake/cli/_plugins/nativeapp/feature_flags.py +5 -1
- snowflake/cli/_plugins/nativeapp/release_channel/__init__.py +13 -0
- snowflake/cli/_plugins/nativeapp/release_channel/commands.py +246 -0
- snowflake/cli/_plugins/nativeapp/release_directive/__init__.py +13 -0
- snowflake/cli/_plugins/nativeapp/release_directive/commands.py +243 -0
- snowflake/cli/_plugins/nativeapp/same_account_install_method.py +9 -17
- snowflake/cli/_plugins/nativeapp/sf_facade_exceptions.py +80 -0
- snowflake/cli/_plugins/nativeapp/sf_sql_facade.py +1184 -80
- snowflake/cli/_plugins/nativeapp/utils.py +11 -0
- snowflake/cli/_plugins/nativeapp/v2_conversions/compat.py +7 -3
- snowflake/cli/_plugins/nativeapp/version/commands.py +32 -5
- snowflake/cli/_plugins/notebook/commands.py +55 -2
- snowflake/cli/_plugins/notebook/exceptions.py +1 -1
- snowflake/cli/_plugins/notebook/manager.py +7 -5
- snowflake/cli/_plugins/notebook/notebook_entity.py +120 -0
- snowflake/cli/_plugins/notebook/notebook_entity_model.py +42 -0
- snowflake/cli/_plugins/notebook/notebook_project_paths.py +15 -0
- snowflake/cli/_plugins/notebook/types.py +3 -0
- snowflake/cli/_plugins/snowpark/commands.py +48 -30
- snowflake/cli/_plugins/snowpark/common.py +47 -2
- snowflake/cli/_plugins/snowpark/snowpark_entity.py +247 -4
- snowflake/cli/_plugins/snowpark/snowpark_entity_model.py +18 -30
- snowflake/cli/_plugins/snowpark/snowpark_project_paths.py +156 -23
- snowflake/cli/_plugins/snowpark/zipper.py +33 -1
- snowflake/cli/_plugins/spcs/common.py +129 -0
- snowflake/cli/_plugins/spcs/services/commands.py +131 -14
- snowflake/cli/_plugins/spcs/services/manager.py +169 -1
- snowflake/cli/_plugins/stage/commands.py +2 -1
- snowflake/cli/_plugins/stage/diff.py +60 -39
- snowflake/cli/_plugins/stage/manager.py +34 -13
- snowflake/cli/_plugins/stage/utils.py +1 -1
- snowflake/cli/_plugins/streamlit/commands.py +10 -1
- snowflake/cli/_plugins/streamlit/manager.py +70 -22
- snowflake/cli/_plugins/streamlit/streamlit_entity.py +131 -1
- snowflake/cli/_plugins/streamlit/streamlit_entity_model.py +14 -24
- snowflake/cli/_plugins/streamlit/streamlit_project_paths.py +30 -0
- snowflake/cli/_plugins/workspace/commands.py +6 -5
- snowflake/cli/_plugins/workspace/manager.py +9 -5
- snowflake/cli/api/artifacts/__init__.py +13 -0
- snowflake/cli/api/artifacts/bundle_map.py +500 -0
- snowflake/cli/api/artifacts/common.py +78 -0
- snowflake/cli/api/artifacts/utils.py +82 -0
- snowflake/cli/api/cli_global_context.py +36 -2
- snowflake/cli/api/commands/flags.py +10 -4
- snowflake/cli/api/commands/utils.py +28 -2
- snowflake/cli/api/config.py +6 -2
- snowflake/cli/api/connections.py +12 -1
- snowflake/cli/api/constants.py +10 -1
- snowflake/cli/api/entities/common.py +81 -14
- snowflake/cli/api/entities/resolver.py +160 -0
- snowflake/cli/api/entities/utils.py +65 -23
- snowflake/cli/api/errno.py +63 -3
- snowflake/cli/api/feature_flags.py +19 -4
- snowflake/cli/api/metrics.py +21 -27
- snowflake/cli/api/project/definition_conversion.py +4 -4
- snowflake/cli/api/project/project_paths.py +28 -0
- snowflake/cli/api/project/schemas/entities/common.py +130 -1
- snowflake/cli/api/project/schemas/entities/entities.py +4 -0
- snowflake/cli/api/project/schemas/project_definition.py +54 -6
- snowflake/cli/api/project/schemas/updatable_model.py +2 -2
- snowflake/cli/api/project/schemas/v1/native_app/native_app.py +5 -7
- snowflake/cli/api/project/schemas/v1/streamlit/streamlit.py +1 -1
- snowflake/cli/api/project/util.py +45 -0
- snowflake/cli/api/secure_path.py +6 -0
- snowflake/cli/api/sql_execution.py +5 -1
- snowflake/cli/api/stage_path.py +7 -2
- snowflake/cli/api/utils/graph.py +3 -0
- snowflake/cli/api/utils/path_utils.py +24 -0
- {snowflake_cli-3.2.2.dist-info → snowflake_cli-3.4.1.dist-info}/METADATA +14 -15
- {snowflake_cli-3.2.2.dist-info → snowflake_cli-3.4.1.dist-info}/RECORD +96 -82
- {snowflake_cli-3.2.2.dist-info → snowflake_cli-3.4.1.dist-info}/WHEEL +1 -1
- snowflake/cli/api/project/schemas/v1/native_app/path_mapping.py +0 -65
- {snowflake_cli-3.2.2.dist-info → snowflake_cli-3.4.1.dist-info}/entry_points.txt +0 -0
- {snowflake_cli-3.2.2.dist-info → snowflake_cli-3.4.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
+
import os
|
|
4
5
|
import re
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from functools import cached_property
|
|
5
8
|
from pathlib import Path
|
|
6
9
|
from textwrap import dedent
|
|
7
|
-
from typing import List, Literal, Optional, Union
|
|
10
|
+
from typing import Any, List, Literal, Optional, Set, Union
|
|
8
11
|
|
|
9
12
|
import typer
|
|
10
|
-
from click import BadOptionUsage, ClickException
|
|
13
|
+
from click import BadOptionUsage, ClickException, UsageError
|
|
11
14
|
from pydantic import Field, field_validator
|
|
15
|
+
from snowflake.cli._plugins.connection.util import UIParameter
|
|
12
16
|
from snowflake.cli._plugins.nativeapp.artifacts import (
|
|
13
|
-
BundleMap,
|
|
14
17
|
VersionInfo,
|
|
15
18
|
build_bundle,
|
|
19
|
+
find_setup_script_file,
|
|
16
20
|
find_version_info_in_manifest_file,
|
|
17
21
|
)
|
|
18
22
|
from snowflake.cli._plugins.nativeapp.bundle_context import BundleContext
|
|
@@ -20,14 +24,19 @@ from snowflake.cli._plugins.nativeapp.codegen.compiler import NativeAppCompiler
|
|
|
20
24
|
from snowflake.cli._plugins.nativeapp.constants import (
|
|
21
25
|
ALLOWED_SPECIAL_COMMENTS,
|
|
22
26
|
COMMENT_COL,
|
|
27
|
+
DEFAULT_CHANNEL,
|
|
28
|
+
DEFAULT_DIRECTIVE,
|
|
23
29
|
EXTERNAL_DISTRIBUTION,
|
|
24
30
|
INTERNAL_DISTRIBUTION,
|
|
31
|
+
MAX_VERSIONS_IN_RELEASE_CHANNEL,
|
|
25
32
|
NAME_COL,
|
|
26
33
|
OWNER_COL,
|
|
27
34
|
PATCH_COL,
|
|
28
|
-
SPECIAL_COMMENT,
|
|
29
35
|
VERSION_COL,
|
|
30
36
|
)
|
|
37
|
+
from snowflake.cli._plugins.nativeapp.entities.application_package_child_interface import (
|
|
38
|
+
ApplicationPackageChildInterface,
|
|
39
|
+
)
|
|
31
40
|
from snowflake.cli._plugins.nativeapp.exceptions import (
|
|
32
41
|
ApplicationPackageAlreadyExistsError,
|
|
33
42
|
ApplicationPackageDoesNotExistError,
|
|
@@ -35,6 +44,7 @@ from snowflake.cli._plugins.nativeapp.exceptions import (
|
|
|
35
44
|
ObjectPropertyNotFoundError,
|
|
36
45
|
SetupScriptFailedValidation,
|
|
37
46
|
)
|
|
47
|
+
from snowflake.cli._plugins.nativeapp.feature_flags import FeatureFlag
|
|
38
48
|
from snowflake.cli._plugins.nativeapp.policy import (
|
|
39
49
|
AllowAlwaysPolicy,
|
|
40
50
|
AskAlwaysPolicy,
|
|
@@ -45,36 +55,57 @@ from snowflake.cli._plugins.nativeapp.sf_facade import get_snowflake_facade
|
|
|
45
55
|
from snowflake.cli._plugins.nativeapp.sf_facade_exceptions import (
|
|
46
56
|
InsufficientPrivilegesError,
|
|
47
57
|
)
|
|
48
|
-
from snowflake.cli._plugins.nativeapp.
|
|
49
|
-
from snowflake.cli._plugins.
|
|
50
|
-
from snowflake.cli._plugins.
|
|
58
|
+
from snowflake.cli._plugins.nativeapp.sf_sql_facade import ReleaseChannel, Version
|
|
59
|
+
from snowflake.cli._plugins.nativeapp.utils import needs_confirmation, sanitize_dir_name
|
|
60
|
+
from snowflake.cli._plugins.snowpark.snowpark_entity_model import (
|
|
61
|
+
FunctionEntityModel,
|
|
62
|
+
ProcedureEntityModel,
|
|
63
|
+
)
|
|
64
|
+
from snowflake.cli._plugins.stage.diff import DiffResult, compute_stage_diff
|
|
65
|
+
from snowflake.cli._plugins.stage.manager import (
|
|
66
|
+
DefaultStagePathParts,
|
|
67
|
+
StageManager,
|
|
68
|
+
StagePathParts,
|
|
69
|
+
)
|
|
70
|
+
from snowflake.cli._plugins.stage.utils import print_diff_to_console
|
|
71
|
+
from snowflake.cli._plugins.streamlit.streamlit_entity_model import (
|
|
72
|
+
StreamlitEntityModel,
|
|
73
|
+
)
|
|
51
74
|
from snowflake.cli._plugins.workspace.context import ActionContext
|
|
52
|
-
from snowflake.cli.api.
|
|
75
|
+
from snowflake.cli.api.artifacts.bundle_map import BundleMap
|
|
76
|
+
from snowflake.cli.api.cli_global_context import span
|
|
77
|
+
from snowflake.cli.api.entities.common import (
|
|
78
|
+
EntityBase,
|
|
79
|
+
attach_spans_to_entity_actions,
|
|
80
|
+
)
|
|
53
81
|
from snowflake.cli.api.entities.utils import (
|
|
54
82
|
drop_generic_object,
|
|
55
83
|
execute_post_deploy_hooks,
|
|
56
84
|
generic_sql_error_handler,
|
|
85
|
+
get_sql_executor,
|
|
57
86
|
sync_deploy_root_with_stage,
|
|
58
87
|
validation_item_to_str,
|
|
59
88
|
)
|
|
60
89
|
from snowflake.cli.api.errno import DOES_NOT_EXIST_OR_NOT_AUTHORIZED
|
|
61
90
|
from snowflake.cli.api.exceptions import SnowflakeSQLExecutionError
|
|
62
91
|
from snowflake.cli.api.project.schemas.entities.common import (
|
|
63
|
-
|
|
92
|
+
EntityModelBaseWithArtifacts,
|
|
64
93
|
Identifier,
|
|
65
94
|
PostDeployHook,
|
|
66
95
|
)
|
|
67
96
|
from snowflake.cli.api.project.schemas.updatable_model import (
|
|
68
97
|
DiscriminatorField,
|
|
69
98
|
IdentifierField,
|
|
99
|
+
UpdatableModel,
|
|
70
100
|
)
|
|
71
101
|
from snowflake.cli.api.project.schemas.v1.native_app.package import DistributionOptions
|
|
72
|
-
from snowflake.cli.api.project.schemas.v1.native_app.path_mapping import PathMapping
|
|
73
102
|
from snowflake.cli.api.project.util import (
|
|
74
103
|
SCHEMA_AND_NAME,
|
|
104
|
+
VALID_IDENTIFIER_REGEX,
|
|
75
105
|
append_test_resource_suffix,
|
|
76
|
-
extract_schema,
|
|
77
106
|
identifier_to_show_like_pattern,
|
|
107
|
+
same_identifiers,
|
|
108
|
+
sql_match,
|
|
78
109
|
to_identifier,
|
|
79
110
|
unquote_identifier,
|
|
80
111
|
)
|
|
@@ -82,30 +113,64 @@ from snowflake.cli.api.utils.cursor import find_all_rows
|
|
|
82
113
|
from snowflake.connector import DictCursor, ProgrammingError
|
|
83
114
|
from snowflake.connector.cursor import SnowflakeCursor
|
|
84
115
|
|
|
116
|
+
ApplicationPackageChildrenTypes = (
|
|
117
|
+
StreamlitEntityModel | FunctionEntityModel | ProcedureEntityModel
|
|
118
|
+
)
|
|
85
119
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
title="
|
|
120
|
+
|
|
121
|
+
class ApplicationPackageChildIdentifier(UpdatableModel):
|
|
122
|
+
schema_: Optional[str] = Field(
|
|
123
|
+
title="Child entity schema", alias="schema", default=None
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class EnsureUsableByField(UpdatableModel):
|
|
128
|
+
application_roles: Optional[Union[str, Set[str]]] = Field(
|
|
129
|
+
title="One or more application roles to be granted with the required privileges",
|
|
130
|
+
default=None,
|
|
90
131
|
)
|
|
132
|
+
|
|
133
|
+
@field_validator("application_roles")
|
|
134
|
+
@classmethod
|
|
135
|
+
def ensure_app_roles_is_a_set(
|
|
136
|
+
cls, application_roles: Optional[Union[str, Set[str]]]
|
|
137
|
+
) -> Optional[Union[Set[str]]]:
|
|
138
|
+
if isinstance(application_roles, str):
|
|
139
|
+
return set([application_roles])
|
|
140
|
+
return application_roles
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class ApplicationPackageChildField(UpdatableModel):
|
|
144
|
+
target: str = Field(title="The key of the entity to include in this package")
|
|
145
|
+
ensure_usable_by: Optional[EnsureUsableByField] = Field(
|
|
146
|
+
title="Automatically grant the required privileges on the child object and its schema",
|
|
147
|
+
default=None,
|
|
148
|
+
)
|
|
149
|
+
identifier: ApplicationPackageChildIdentifier = Field(
|
|
150
|
+
title="Entity identifier", default=None
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class ApplicationPackageEntityModel(EntityModelBaseWithArtifacts):
|
|
155
|
+
type: Literal["application package"] = DiscriminatorField() # noqa: A003
|
|
91
156
|
bundle_root: Optional[str] = Field(
|
|
92
|
-
title="Folder at the root of your project where artifacts necessary to perform the bundle step are stored
|
|
157
|
+
title="Folder at the root of your project where artifacts necessary to perform the bundle step are stored",
|
|
93
158
|
default="output/bundle/",
|
|
94
159
|
)
|
|
95
|
-
|
|
96
|
-
title="Folder
|
|
97
|
-
default="
|
|
160
|
+
children_artifacts_dir: Optional[str] = Field(
|
|
161
|
+
title="Folder under deploy_root where the child artifacts will be stored",
|
|
162
|
+
default="_children/",
|
|
98
163
|
)
|
|
99
164
|
generated_root: Optional[str] = Field(
|
|
100
|
-
title="Subdirectory of the deploy root where files generated by the Snowflake CLI will be written
|
|
165
|
+
title="Subdirectory of the deploy root where files generated by the Snowflake CLI will be written",
|
|
101
166
|
default="__generated/",
|
|
102
167
|
)
|
|
103
168
|
stage: Optional[str] = IdentifierField(
|
|
104
|
-
title="Identifier of the stage that stores the application artifacts
|
|
169
|
+
title="Identifier of the stage that stores the application artifacts",
|
|
105
170
|
default="app_src.stage",
|
|
106
171
|
)
|
|
107
172
|
scratch_stage: Optional[str] = IdentifierField(
|
|
108
|
-
title="Identifier of the stage that stores temporary scratch data used by the Snowflake CLI
|
|
173
|
+
title="Identifier of the stage that stores temporary scratch data used by the Snowflake CLI",
|
|
109
174
|
default="app_src.stage_snowflake_cli_scratch",
|
|
110
175
|
)
|
|
111
176
|
distribution: Optional[DistributionOptions] = Field(
|
|
@@ -117,6 +182,24 @@ class ApplicationPackageEntityModel(EntityModelBase):
|
|
|
117
182
|
default="",
|
|
118
183
|
)
|
|
119
184
|
|
|
185
|
+
stage_subdirectory: Optional[str] = Field(
|
|
186
|
+
title="Subfolder in stage to upload the artifacts to, instead of the root of the application package's stage",
|
|
187
|
+
default="",
|
|
188
|
+
)
|
|
189
|
+
children: Optional[List[ApplicationPackageChildField]] = Field(
|
|
190
|
+
title="Entities that will be bundled and deployed as part of this application package",
|
|
191
|
+
default=[],
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
@field_validator("children")
|
|
195
|
+
@classmethod
|
|
196
|
+
def verify_children_behind_flag(
|
|
197
|
+
cls, input_value: Optional[List[ApplicationPackageChildField]]
|
|
198
|
+
) -> Optional[List[ApplicationPackageChildField]]:
|
|
199
|
+
if input_value and not FeatureFlag.ENABLE_NATIVE_APP_CHILDREN.is_enabled():
|
|
200
|
+
raise AttributeError("Application package children are not supported yet")
|
|
201
|
+
return input_value
|
|
202
|
+
|
|
120
203
|
@field_validator("identifier")
|
|
121
204
|
@classmethod
|
|
122
205
|
def append_test_resource_suffix_to_identifier(
|
|
@@ -130,23 +213,6 @@ class ApplicationPackageEntityModel(EntityModelBase):
|
|
|
130
213
|
return input_value.model_copy(update=dict(name=with_suffix))
|
|
131
214
|
return with_suffix
|
|
132
215
|
|
|
133
|
-
@field_validator("artifacts")
|
|
134
|
-
@classmethod
|
|
135
|
-
def transform_artifacts(
|
|
136
|
-
cls, orig_artifacts: List[Union[PathMapping, str]]
|
|
137
|
-
) -> List[PathMapping]:
|
|
138
|
-
transformed_artifacts = []
|
|
139
|
-
if orig_artifacts is None:
|
|
140
|
-
return transformed_artifacts
|
|
141
|
-
|
|
142
|
-
for artifact in orig_artifacts:
|
|
143
|
-
if isinstance(artifact, PathMapping):
|
|
144
|
-
transformed_artifacts.append(artifact)
|
|
145
|
-
else:
|
|
146
|
-
transformed_artifacts.append(PathMapping(src=artifact))
|
|
147
|
-
|
|
148
|
-
return transformed_artifacts
|
|
149
|
-
|
|
150
216
|
@field_validator("stage")
|
|
151
217
|
@classmethod
|
|
152
218
|
def validate_source_stage(cls, input_value: str):
|
|
@@ -157,6 +223,7 @@ class ApplicationPackageEntityModel(EntityModelBase):
|
|
|
157
223
|
return input_value
|
|
158
224
|
|
|
159
225
|
|
|
226
|
+
@attach_spans_to_entity_actions(entity_name="app_pkg")
|
|
160
227
|
class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
161
228
|
"""
|
|
162
229
|
A Native App application package.
|
|
@@ -168,7 +235,15 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
168
235
|
|
|
169
236
|
@property
|
|
170
237
|
def deploy_root(self) -> Path:
|
|
171
|
-
return
|
|
238
|
+
return (
|
|
239
|
+
self.project_root
|
|
240
|
+
/ self._entity_model.deploy_root
|
|
241
|
+
/ self._entity_model.stage_subdirectory
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
@property
|
|
245
|
+
def children_artifacts_deploy_root(self) -> Path:
|
|
246
|
+
return self.deploy_root / self._entity_model.children_artifacts_dir
|
|
172
247
|
|
|
173
248
|
@property
|
|
174
249
|
def bundle_root(self) -> Path:
|
|
@@ -195,12 +270,16 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
195
270
|
) or to_identifier(self._workspace_ctx.default_warehouse)
|
|
196
271
|
|
|
197
272
|
@property
|
|
198
|
-
def
|
|
199
|
-
return
|
|
273
|
+
def scratch_stage_path(self) -> DefaultStagePathParts:
|
|
274
|
+
return DefaultStagePathParts.from_fqn(
|
|
275
|
+
f"{self.name}.{self._entity_model.scratch_stage}"
|
|
276
|
+
)
|
|
200
277
|
|
|
201
|
-
@
|
|
202
|
-
def
|
|
203
|
-
|
|
278
|
+
@cached_property
|
|
279
|
+
def stage_path(self) -> DefaultStagePathParts:
|
|
280
|
+
stage_fqn = f"{self.name}.{self._entity_model.stage}"
|
|
281
|
+
subdir = self._entity_model.stage_subdirectory
|
|
282
|
+
return DefaultStagePathParts.from_fqn(stage_fqn, subdir)
|
|
204
283
|
|
|
205
284
|
@property
|
|
206
285
|
def post_deploy_hooks(self) -> list[PostDeployHook] | None:
|
|
@@ -208,7 +287,24 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
208
287
|
return model.meta and model.meta.post_deploy
|
|
209
288
|
|
|
210
289
|
def action_bundle(self, action_ctx: ActionContext, *args, **kwargs):
|
|
211
|
-
return self._bundle()
|
|
290
|
+
return self._bundle(action_ctx)
|
|
291
|
+
|
|
292
|
+
def action_diff(
|
|
293
|
+
self, action_ctx: ActionContext, print_to_console: bool, *args, **kwargs
|
|
294
|
+
):
|
|
295
|
+
"""
|
|
296
|
+
Compute the diff between the local artifacts and the remote ones on the stage.
|
|
297
|
+
"""
|
|
298
|
+
bundle_map = self._bundle()
|
|
299
|
+
diff = compute_stage_diff(
|
|
300
|
+
local_root=self.deploy_root,
|
|
301
|
+
stage_path=self.stage_path,
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
if print_to_console:
|
|
305
|
+
print_diff_to_console(diff, bundle_map)
|
|
306
|
+
|
|
307
|
+
return diff
|
|
212
308
|
|
|
213
309
|
def action_deploy(
|
|
214
310
|
self,
|
|
@@ -219,18 +315,18 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
219
315
|
validate: bool,
|
|
220
316
|
interactive: bool,
|
|
221
317
|
force: bool,
|
|
222
|
-
stage_fqn: Optional[str] = None,
|
|
223
318
|
*args,
|
|
224
319
|
**kwargs,
|
|
225
320
|
):
|
|
226
321
|
return self._deploy(
|
|
322
|
+
action_ctx=action_ctx,
|
|
227
323
|
bundle_map=None,
|
|
228
324
|
prune=prune,
|
|
229
325
|
recursive=recursive,
|
|
230
326
|
paths=paths,
|
|
231
327
|
print_diff=True,
|
|
232
328
|
validate=validate,
|
|
233
|
-
|
|
329
|
+
stage_path=self.stage_path,
|
|
234
330
|
interactive=interactive,
|
|
235
331
|
force=force,
|
|
236
332
|
)
|
|
@@ -248,21 +344,14 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
248
344
|
)
|
|
249
345
|
return
|
|
250
346
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
if show_versions_cursor.rowcount > 0:
|
|
261
|
-
# allow dropping a package with versions when --force is set
|
|
262
|
-
if not force_drop:
|
|
263
|
-
raise CouldNotDropApplicationPackageWithVersions(
|
|
264
|
-
"Drop versions first, or use --force to override."
|
|
265
|
-
)
|
|
347
|
+
# 2. Check for versions in the application package
|
|
348
|
+
versions_in_pkg = get_snowflake_facade().show_versions(self.name, self.role)
|
|
349
|
+
if len(versions_in_pkg) > 0:
|
|
350
|
+
# allow dropping a package with versions when --force is set
|
|
351
|
+
if not force_drop:
|
|
352
|
+
raise CouldNotDropApplicationPackageWithVersions(
|
|
353
|
+
"Drop versions first, or use --force to override."
|
|
354
|
+
)
|
|
266
355
|
|
|
267
356
|
# 3. Check distribution of the existing application package
|
|
268
357
|
actual_distribution = self.get_app_pkg_distribution_in_snowflake()
|
|
@@ -323,6 +412,7 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
323
412
|
**kwargs,
|
|
324
413
|
):
|
|
325
414
|
self.validate_setup_script(
|
|
415
|
+
action_ctx=action_ctx,
|
|
326
416
|
use_scratch_stage=use_scratch_stage,
|
|
327
417
|
interactive=interactive,
|
|
328
418
|
force=force,
|
|
@@ -336,15 +426,7 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
336
426
|
Get all existing versions, if defined, for an application package.
|
|
337
427
|
It executes a 'show versions in application package' query and returns all the results.
|
|
338
428
|
"""
|
|
339
|
-
|
|
340
|
-
with sql_executor.use_role(self.role):
|
|
341
|
-
show_obj_query = f"show versions in application package {self.name}"
|
|
342
|
-
show_obj_cursor = sql_executor.execute_query(show_obj_query)
|
|
343
|
-
|
|
344
|
-
if show_obj_cursor.rowcount is None:
|
|
345
|
-
raise SnowflakeSQLExecutionError(show_obj_query)
|
|
346
|
-
|
|
347
|
-
return show_obj_cursor
|
|
429
|
+
return get_snowflake_facade().show_versions(self.name, self.role)
|
|
348
430
|
|
|
349
431
|
def action_version_create(
|
|
350
432
|
self,
|
|
@@ -355,9 +437,10 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
355
437
|
skip_git_check: bool,
|
|
356
438
|
interactive: bool,
|
|
357
439
|
force: bool,
|
|
440
|
+
from_stage: Optional[bool],
|
|
358
441
|
*args,
|
|
359
442
|
**kwargs,
|
|
360
|
-
):
|
|
443
|
+
) -> VersionInfo:
|
|
361
444
|
"""
|
|
362
445
|
Create a version and/or patch for a new or existing application package.
|
|
363
446
|
Always performs a deploy action before creating version or patch.
|
|
@@ -372,12 +455,7 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
372
455
|
else:
|
|
373
456
|
policy = DenyAlwaysPolicy()
|
|
374
457
|
|
|
375
|
-
|
|
376
|
-
git_policy = DenyAlwaysPolicy()
|
|
377
|
-
else:
|
|
378
|
-
git_policy = AllowAlwaysPolicy()
|
|
379
|
-
|
|
380
|
-
bundle_map = self._bundle()
|
|
458
|
+
bundle_map = self._bundle(action_ctx)
|
|
381
459
|
resolved_version, resolved_patch, resolved_label = self.resolve_version_info(
|
|
382
460
|
version=version,
|
|
383
461
|
patch=patch,
|
|
@@ -387,20 +465,31 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
387
465
|
interactive=interactive,
|
|
388
466
|
)
|
|
389
467
|
|
|
390
|
-
if
|
|
468
|
+
if not skip_git_check:
|
|
391
469
|
self.check_index_changes_in_git_repo(policy=policy, interactive=interactive)
|
|
392
470
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
471
|
+
# if user is asking to create the version from the current stage,
|
|
472
|
+
# then do not re-deploy the artifacts or touch the stage
|
|
473
|
+
if from_stage:
|
|
474
|
+
# verify package exists:
|
|
475
|
+
if not self.get_existing_app_pkg_info():
|
|
476
|
+
raise ClickException(
|
|
477
|
+
"Cannot create version from stage because the application package does not exist yet. "
|
|
478
|
+
"Try removing --from-stage flag or executing `snow app deploy` to deploy the application package first."
|
|
479
|
+
)
|
|
480
|
+
else:
|
|
481
|
+
self._deploy(
|
|
482
|
+
action_ctx=action_ctx,
|
|
483
|
+
bundle_map=bundle_map,
|
|
484
|
+
prune=True,
|
|
485
|
+
recursive=True,
|
|
486
|
+
paths=[],
|
|
487
|
+
print_diff=True,
|
|
488
|
+
validate=True,
|
|
489
|
+
stage_path=self.stage_path,
|
|
490
|
+
interactive=interactive,
|
|
491
|
+
force=force,
|
|
492
|
+
)
|
|
404
493
|
|
|
405
494
|
# Warn if the version exists in a release directive(s)
|
|
406
495
|
try:
|
|
@@ -440,12 +529,14 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
440
529
|
# Define a new version in the application package
|
|
441
530
|
if not self.get_existing_version_info(resolved_version):
|
|
442
531
|
self.add_new_version(version=resolved_version, label=resolved_label)
|
|
443
|
-
|
|
532
|
+
# A new version created automatically has patch 0, we do not need to further increment the patch.
|
|
533
|
+
return VersionInfo(resolved_version, 0, resolved_label)
|
|
444
534
|
|
|
445
535
|
# Add a new patch to an existing (old) version
|
|
446
|
-
self.add_new_patch_to_version(
|
|
536
|
+
patch = self.add_new_patch_to_version(
|
|
447
537
|
version=resolved_version, patch=resolved_patch, label=resolved_label
|
|
448
538
|
)
|
|
539
|
+
return VersionInfo(resolved_version, patch, resolved_label)
|
|
449
540
|
|
|
450
541
|
def action_version_drop(
|
|
451
542
|
self,
|
|
@@ -492,7 +583,7 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
492
583
|
"""
|
|
493
584
|
)
|
|
494
585
|
)
|
|
495
|
-
self._bundle()
|
|
586
|
+
self._bundle(action_ctx)
|
|
496
587
|
version_info = find_version_info_in_manifest_file(self.deploy_root)
|
|
497
588
|
version = version_info.version_name
|
|
498
589
|
if not version:
|
|
@@ -524,20 +615,283 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
524
615
|
raise typer.Exit(1)
|
|
525
616
|
|
|
526
617
|
# Drop the version
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
sql_executor.execute_query(
|
|
531
|
-
f"alter application package {self.name} drop version {version}"
|
|
532
|
-
)
|
|
533
|
-
except ProgrammingError as err:
|
|
534
|
-
raise err # e.g. version is referenced in a release directive(s)
|
|
618
|
+
get_snowflake_facade().drop_version_from_package(
|
|
619
|
+
package_name=self.name, version=version, role=self.role
|
|
620
|
+
)
|
|
535
621
|
|
|
536
622
|
console.message(
|
|
537
623
|
f"Version {version} in application package {self.name} dropped successfully."
|
|
538
624
|
)
|
|
539
625
|
|
|
540
|
-
def
|
|
626
|
+
def _validate_target_accounts(self, accounts: list[str]) -> None:
|
|
627
|
+
"""
|
|
628
|
+
Validates the target accounts provided by the user.
|
|
629
|
+
"""
|
|
630
|
+
for account in accounts:
|
|
631
|
+
if not re.fullmatch(
|
|
632
|
+
f"{VALID_IDENTIFIER_REGEX}\\.{VALID_IDENTIFIER_REGEX}", account
|
|
633
|
+
):
|
|
634
|
+
raise ClickException(
|
|
635
|
+
f"Target account {account} is not in a valid format. Make sure you provide the target account in the format 'org.account'."
|
|
636
|
+
)
|
|
637
|
+
|
|
638
|
+
def get_sanitized_release_channel(
|
|
639
|
+
self,
|
|
640
|
+
release_channel: Optional[str],
|
|
641
|
+
available_release_channels: Optional[list[ReleaseChannel]] = None,
|
|
642
|
+
) -> Optional[str]:
|
|
643
|
+
"""
|
|
644
|
+
Sanitize the release channel name provided by the user and validate it against the available release channels.
|
|
645
|
+
|
|
646
|
+
A return value of None indicates that release channels should not be used. Returns None if:
|
|
647
|
+
- Release channel is not provided
|
|
648
|
+
- Release channels are not enabled in the application package and the user provided the default release channel
|
|
649
|
+
"""
|
|
650
|
+
if not release_channel:
|
|
651
|
+
return None
|
|
652
|
+
|
|
653
|
+
if available_release_channels is None:
|
|
654
|
+
available_release_channels = get_snowflake_facade().show_release_channels(
|
|
655
|
+
self.name, self.role
|
|
656
|
+
)
|
|
657
|
+
|
|
658
|
+
if not available_release_channels and same_identifiers(
|
|
659
|
+
release_channel, DEFAULT_CHANNEL
|
|
660
|
+
):
|
|
661
|
+
return None
|
|
662
|
+
|
|
663
|
+
self.validate_release_channel(release_channel, available_release_channels)
|
|
664
|
+
return release_channel
|
|
665
|
+
|
|
666
|
+
def validate_release_channel(
|
|
667
|
+
self,
|
|
668
|
+
release_channel: str,
|
|
669
|
+
available_release_channels: Optional[list[ReleaseChannel]] = None,
|
|
670
|
+
) -> None:
|
|
671
|
+
"""
|
|
672
|
+
Validates the release channel provided by the user and make sure it is a valid release channel for the application package.
|
|
673
|
+
"""
|
|
674
|
+
|
|
675
|
+
if available_release_channels is None:
|
|
676
|
+
available_release_channels = get_snowflake_facade().show_release_channels(
|
|
677
|
+
self.name, self.role
|
|
678
|
+
)
|
|
679
|
+
if not available_release_channels:
|
|
680
|
+
raise UsageError(
|
|
681
|
+
f"Release channels are not enabled for application package {self.name}."
|
|
682
|
+
)
|
|
683
|
+
for channel in available_release_channels:
|
|
684
|
+
if unquote_identifier(release_channel) == channel["name"]:
|
|
685
|
+
return
|
|
686
|
+
|
|
687
|
+
raise UsageError(
|
|
688
|
+
f"Release channel {release_channel} is not available in application package {self.name}. "
|
|
689
|
+
f"Available release channels are: ({', '.join(channel['name'] for channel in available_release_channels)})."
|
|
690
|
+
)
|
|
691
|
+
|
|
692
|
+
def action_release_directive_list(
|
|
693
|
+
self,
|
|
694
|
+
action_ctx: ActionContext,
|
|
695
|
+
release_channel: Optional[str],
|
|
696
|
+
like: str,
|
|
697
|
+
*args,
|
|
698
|
+
**kwargs,
|
|
699
|
+
) -> list[dict[str, Any]]:
|
|
700
|
+
"""
|
|
701
|
+
Get all existing release directives for an application package.
|
|
702
|
+
Limit the results to a specific release channel, if provided.
|
|
703
|
+
|
|
704
|
+
If `like` is provided, only release directives matching the SQL LIKE pattern are listed.
|
|
705
|
+
"""
|
|
706
|
+
release_channel = self.get_sanitized_release_channel(release_channel)
|
|
707
|
+
|
|
708
|
+
release_directives = get_snowflake_facade().show_release_directives(
|
|
709
|
+
package_name=self.name,
|
|
710
|
+
role=self.role,
|
|
711
|
+
release_channel=release_channel,
|
|
712
|
+
)
|
|
713
|
+
|
|
714
|
+
return [
|
|
715
|
+
directive
|
|
716
|
+
for directive in release_directives
|
|
717
|
+
if sql_match(pattern=like, value=directive.get("name", ""))
|
|
718
|
+
]
|
|
719
|
+
|
|
720
|
+
def action_release_directive_set(
|
|
721
|
+
self,
|
|
722
|
+
action_ctx: ActionContext,
|
|
723
|
+
version: str,
|
|
724
|
+
patch: int,
|
|
725
|
+
release_directive: str,
|
|
726
|
+
release_channel: str,
|
|
727
|
+
target_accounts: Optional[list[str]],
|
|
728
|
+
*args,
|
|
729
|
+
**kwargs,
|
|
730
|
+
):
|
|
731
|
+
"""
|
|
732
|
+
Sets a release directive to the specified version and patch using the specified release channel.
|
|
733
|
+
Target accounts can only be specified for non-default release directives.
|
|
734
|
+
|
|
735
|
+
For non-default release directives, update the existing release directive if target accounts are not provided.
|
|
736
|
+
"""
|
|
737
|
+
if target_accounts:
|
|
738
|
+
self._validate_target_accounts(target_accounts)
|
|
739
|
+
|
|
740
|
+
if target_accounts and same_identifiers(release_directive, DEFAULT_DIRECTIVE):
|
|
741
|
+
raise BadOptionUsage(
|
|
742
|
+
"target_accounts",
|
|
743
|
+
"Target accounts can only be specified for non-default named release directives.",
|
|
744
|
+
)
|
|
745
|
+
|
|
746
|
+
sanitized_release_channel = self.get_sanitized_release_channel(release_channel)
|
|
747
|
+
|
|
748
|
+
get_snowflake_facade().set_release_directive(
|
|
749
|
+
package_name=self.name,
|
|
750
|
+
release_directive=release_directive,
|
|
751
|
+
release_channel=sanitized_release_channel,
|
|
752
|
+
target_accounts=target_accounts,
|
|
753
|
+
version=version,
|
|
754
|
+
patch=patch,
|
|
755
|
+
role=self.role,
|
|
756
|
+
)
|
|
757
|
+
|
|
758
|
+
def action_release_directive_unset(
|
|
759
|
+
self,
|
|
760
|
+
action_ctx: ActionContext,
|
|
761
|
+
release_directive: str,
|
|
762
|
+
release_channel: str,
|
|
763
|
+
):
|
|
764
|
+
"""
|
|
765
|
+
Unsets a release directive from the specified release channel.
|
|
766
|
+
"""
|
|
767
|
+
if same_identifiers(release_directive, DEFAULT_DIRECTIVE):
|
|
768
|
+
raise ClickException(
|
|
769
|
+
"Cannot unset default release directive. Please specify a non-default release directive."
|
|
770
|
+
)
|
|
771
|
+
|
|
772
|
+
get_snowflake_facade().unset_release_directive(
|
|
773
|
+
package_name=self.name,
|
|
774
|
+
release_directive=release_directive,
|
|
775
|
+
release_channel=self.get_sanitized_release_channel(release_channel),
|
|
776
|
+
role=self.role,
|
|
777
|
+
)
|
|
778
|
+
|
|
779
|
+
def action_release_directive_add_accounts(
|
|
780
|
+
self,
|
|
781
|
+
action_ctx: ActionContext,
|
|
782
|
+
release_directive: str,
|
|
783
|
+
release_channel: str,
|
|
784
|
+
target_accounts: list[str],
|
|
785
|
+
*args,
|
|
786
|
+
**kwargs,
|
|
787
|
+
):
|
|
788
|
+
"""
|
|
789
|
+
Adds target accounts to a release directive.
|
|
790
|
+
"""
|
|
791
|
+
|
|
792
|
+
if not target_accounts:
|
|
793
|
+
raise ClickException("No target accounts provided.")
|
|
794
|
+
|
|
795
|
+
self._validate_target_accounts(target_accounts)
|
|
796
|
+
|
|
797
|
+
get_snowflake_facade().add_accounts_to_release_directive(
|
|
798
|
+
package_name=self.name,
|
|
799
|
+
release_directive=release_directive,
|
|
800
|
+
release_channel=self.get_sanitized_release_channel(release_channel),
|
|
801
|
+
target_accounts=target_accounts,
|
|
802
|
+
role=self.role,
|
|
803
|
+
)
|
|
804
|
+
|
|
805
|
+
def action_release_directive_remove_accounts(
|
|
806
|
+
self,
|
|
807
|
+
action_ctx: ActionContext,
|
|
808
|
+
release_directive: str,
|
|
809
|
+
release_channel: str,
|
|
810
|
+
target_accounts: list[str],
|
|
811
|
+
*args,
|
|
812
|
+
**kwargs,
|
|
813
|
+
):
|
|
814
|
+
"""
|
|
815
|
+
Removes target accounts from a release directive.
|
|
816
|
+
"""
|
|
817
|
+
|
|
818
|
+
if not target_accounts:
|
|
819
|
+
raise ClickException("No target accounts provided.")
|
|
820
|
+
|
|
821
|
+
self._validate_target_accounts(target_accounts)
|
|
822
|
+
|
|
823
|
+
get_snowflake_facade().remove_accounts_from_release_directive(
|
|
824
|
+
package_name=self.name,
|
|
825
|
+
release_directive=release_directive,
|
|
826
|
+
release_channel=self.get_sanitized_release_channel(release_channel),
|
|
827
|
+
target_accounts=target_accounts,
|
|
828
|
+
role=self.role,
|
|
829
|
+
)
|
|
830
|
+
|
|
831
|
+
def _print_channel_to_console(self, channel: ReleaseChannel) -> None:
|
|
832
|
+
"""
|
|
833
|
+
Prints the release channel details to the console.
|
|
834
|
+
"""
|
|
835
|
+
console = self._workspace_ctx.console
|
|
836
|
+
|
|
837
|
+
console.message(f"""[bold]{channel["name"]}[/bold]""")
|
|
838
|
+
accounts_list: Optional[list[str]] = channel["targets"].get("accounts")
|
|
839
|
+
target_accounts = (
|
|
840
|
+
f"({', '.join(accounts_list)})"
|
|
841
|
+
if accounts_list is not None
|
|
842
|
+
else "ALL ACCOUNTS"
|
|
843
|
+
)
|
|
844
|
+
|
|
845
|
+
formatted_created_on = (
|
|
846
|
+
channel["created_on"].astimezone().strftime("%Y-%m-%d %H:%M:%S.%f %Z")
|
|
847
|
+
if channel["created_on"]
|
|
848
|
+
else ""
|
|
849
|
+
)
|
|
850
|
+
|
|
851
|
+
formatted_updated_on = (
|
|
852
|
+
channel["updated_on"].astimezone().strftime("%Y-%m-%d %H:%M:%S.%f %Z")
|
|
853
|
+
if channel["updated_on"]
|
|
854
|
+
else ""
|
|
855
|
+
)
|
|
856
|
+
with console.indented():
|
|
857
|
+
console.message(f"Description: {channel['description']}")
|
|
858
|
+
console.message(f"Versions: ({', '.join(channel['versions'])})")
|
|
859
|
+
console.message(f"Created on: {formatted_created_on}")
|
|
860
|
+
console.message(f"Updated on: {formatted_updated_on}")
|
|
861
|
+
console.message(f"Target accounts: {target_accounts}")
|
|
862
|
+
|
|
863
|
+
def action_release_channel_list(
|
|
864
|
+
self,
|
|
865
|
+
action_ctx: ActionContext,
|
|
866
|
+
release_channel: Optional[str],
|
|
867
|
+
*args,
|
|
868
|
+
**kwargs,
|
|
869
|
+
) -> list[ReleaseChannel]:
|
|
870
|
+
"""
|
|
871
|
+
Get all existing release channels for an application package.
|
|
872
|
+
If `release_channel` is provided, only the specified release channel is listed.
|
|
873
|
+
"""
|
|
874
|
+
console = self._workspace_ctx.console
|
|
875
|
+
available_channels = get_snowflake_facade().show_release_channels(
|
|
876
|
+
self.name, self.role
|
|
877
|
+
)
|
|
878
|
+
|
|
879
|
+
filtered_channels = [
|
|
880
|
+
channel
|
|
881
|
+
for channel in available_channels
|
|
882
|
+
if release_channel is None
|
|
883
|
+
or unquote_identifier(release_channel) == channel["name"]
|
|
884
|
+
]
|
|
885
|
+
|
|
886
|
+
if not filtered_channels:
|
|
887
|
+
console.message("No release channels found.")
|
|
888
|
+
else:
|
|
889
|
+
for channel in filtered_channels:
|
|
890
|
+
self._print_channel_to_console(channel)
|
|
891
|
+
|
|
892
|
+
return filtered_channels
|
|
893
|
+
|
|
894
|
+
def _bundle(self, action_ctx: ActionContext = None):
|
|
541
895
|
model = self._entity_model
|
|
542
896
|
bundle_map = build_bundle(self.project_root, self.deploy_root, model.artifacts)
|
|
543
897
|
bundle_context = BundleContext(
|
|
@@ -550,17 +904,391 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
550
904
|
)
|
|
551
905
|
compiler = NativeAppCompiler(bundle_context)
|
|
552
906
|
compiler.compile_artifacts()
|
|
907
|
+
|
|
908
|
+
if self._entity_model.children:
|
|
909
|
+
# Bundle children and append their SQL to setup script
|
|
910
|
+
# TODO Consider re-writing the logic below as a processor
|
|
911
|
+
children_sql = self._bundle_children(action_ctx=action_ctx)
|
|
912
|
+
setup_file_path = find_setup_script_file(deploy_root=self.deploy_root)
|
|
913
|
+
with open(setup_file_path, "r", encoding="utf-8") as file:
|
|
914
|
+
existing_setup_script = file.read()
|
|
915
|
+
if setup_file_path.is_symlink():
|
|
916
|
+
setup_file_path.unlink()
|
|
917
|
+
with open(setup_file_path, "w", encoding="utf-8") as file:
|
|
918
|
+
file.write(existing_setup_script)
|
|
919
|
+
file.write("\n-- AUTO GENERATED CHILDREN SECTION\n")
|
|
920
|
+
file.write("\n".join(children_sql))
|
|
921
|
+
file.write("\n")
|
|
922
|
+
|
|
553
923
|
return bundle_map
|
|
554
924
|
|
|
925
|
+
def action_release_channel_add_accounts(
|
|
926
|
+
self,
|
|
927
|
+
action_ctx: ActionContext,
|
|
928
|
+
release_channel: str,
|
|
929
|
+
target_accounts: list[str],
|
|
930
|
+
*args,
|
|
931
|
+
**kwargs,
|
|
932
|
+
):
|
|
933
|
+
"""
|
|
934
|
+
Adds target accounts to a release channel.
|
|
935
|
+
"""
|
|
936
|
+
|
|
937
|
+
if not target_accounts:
|
|
938
|
+
raise ClickException("No target accounts provided.")
|
|
939
|
+
|
|
940
|
+
self.validate_release_channel(release_channel)
|
|
941
|
+
self._validate_target_accounts(target_accounts)
|
|
942
|
+
|
|
943
|
+
get_snowflake_facade().add_accounts_to_release_channel(
|
|
944
|
+
package_name=self.name,
|
|
945
|
+
release_channel=release_channel,
|
|
946
|
+
target_accounts=target_accounts,
|
|
947
|
+
role=self.role,
|
|
948
|
+
)
|
|
949
|
+
|
|
950
|
+
def action_release_channel_remove_accounts(
|
|
951
|
+
self,
|
|
952
|
+
action_ctx: ActionContext,
|
|
953
|
+
release_channel: str,
|
|
954
|
+
target_accounts: list[str],
|
|
955
|
+
*args,
|
|
956
|
+
**kwargs,
|
|
957
|
+
):
|
|
958
|
+
"""
|
|
959
|
+
Removes target accounts from a release channel.
|
|
960
|
+
"""
|
|
961
|
+
|
|
962
|
+
if not target_accounts:
|
|
963
|
+
raise ClickException("No target accounts provided.")
|
|
964
|
+
|
|
965
|
+
self.validate_release_channel(release_channel)
|
|
966
|
+
self._validate_target_accounts(target_accounts)
|
|
967
|
+
|
|
968
|
+
get_snowflake_facade().remove_accounts_from_release_channel(
|
|
969
|
+
package_name=self.name,
|
|
970
|
+
release_channel=release_channel,
|
|
971
|
+
target_accounts=target_accounts,
|
|
972
|
+
role=self.role,
|
|
973
|
+
)
|
|
974
|
+
|
|
975
|
+
def action_release_channel_set_accounts(
|
|
976
|
+
self,
|
|
977
|
+
action_ctx: ActionContext,
|
|
978
|
+
release_channel: str,
|
|
979
|
+
target_accounts: list[str],
|
|
980
|
+
*args,
|
|
981
|
+
**kwargs,
|
|
982
|
+
):
|
|
983
|
+
"""
|
|
984
|
+
Sets target accounts for a release channel.
|
|
985
|
+
"""
|
|
986
|
+
|
|
987
|
+
if not target_accounts:
|
|
988
|
+
raise ClickException("No target accounts provided.")
|
|
989
|
+
|
|
990
|
+
self.validate_release_channel(release_channel)
|
|
991
|
+
self._validate_target_accounts(target_accounts)
|
|
992
|
+
|
|
993
|
+
get_snowflake_facade().set_accounts_for_release_channel(
|
|
994
|
+
package_name=self.name,
|
|
995
|
+
release_channel=release_channel,
|
|
996
|
+
target_accounts=target_accounts,
|
|
997
|
+
role=self.role,
|
|
998
|
+
)
|
|
999
|
+
|
|
1000
|
+
def action_release_channel_add_version(
|
|
1001
|
+
self,
|
|
1002
|
+
action_ctx: ActionContext,
|
|
1003
|
+
release_channel: str,
|
|
1004
|
+
version: str,
|
|
1005
|
+
*args,
|
|
1006
|
+
**kwargs,
|
|
1007
|
+
):
|
|
1008
|
+
"""
|
|
1009
|
+
Adds a version to a release channel.
|
|
1010
|
+
"""
|
|
1011
|
+
|
|
1012
|
+
self.validate_release_channel(release_channel)
|
|
1013
|
+
get_snowflake_facade().add_version_to_release_channel(
|
|
1014
|
+
package_name=self.name,
|
|
1015
|
+
release_channel=release_channel,
|
|
1016
|
+
version=version,
|
|
1017
|
+
role=self.role,
|
|
1018
|
+
)
|
|
1019
|
+
|
|
1020
|
+
def action_release_channel_remove_version(
|
|
1021
|
+
self,
|
|
1022
|
+
action_ctx: ActionContext,
|
|
1023
|
+
release_channel: str,
|
|
1024
|
+
version: str,
|
|
1025
|
+
*args,
|
|
1026
|
+
**kwargs,
|
|
1027
|
+
):
|
|
1028
|
+
"""
|
|
1029
|
+
Removes a version from a release channel.
|
|
1030
|
+
"""
|
|
1031
|
+
|
|
1032
|
+
self.validate_release_channel(release_channel)
|
|
1033
|
+
get_snowflake_facade().remove_version_from_release_channel(
|
|
1034
|
+
package_name=self.name,
|
|
1035
|
+
release_channel=release_channel,
|
|
1036
|
+
version=version,
|
|
1037
|
+
role=self.role,
|
|
1038
|
+
)
|
|
1039
|
+
|
|
1040
|
+
def _find_version_with_no_recent_update(
|
|
1041
|
+
self, versions_info: list[Version], free_versions: set[str]
|
|
1042
|
+
) -> Optional[str]:
|
|
1043
|
+
"""
|
|
1044
|
+
Finds the version with the oldest created_on date from the free versions.
|
|
1045
|
+
"""
|
|
1046
|
+
|
|
1047
|
+
if not free_versions:
|
|
1048
|
+
return None
|
|
1049
|
+
|
|
1050
|
+
# map of versionId to last Updated Date. Last Updated Date is based on patch creation date.
|
|
1051
|
+
last_updated_map: dict[str, datetime] = {}
|
|
1052
|
+
for version_info in versions_info:
|
|
1053
|
+
last_updated_value = last_updated_map.get(version_info["version"], None)
|
|
1054
|
+
if (
|
|
1055
|
+
not last_updated_value
|
|
1056
|
+
or version_info["created_on"] > last_updated_value
|
|
1057
|
+
):
|
|
1058
|
+
last_updated_map[version_info["version"]] = version_info["created_on"]
|
|
1059
|
+
|
|
1060
|
+
oldest_version = None
|
|
1061
|
+
oldest_version_last_updated_on = None
|
|
1062
|
+
|
|
1063
|
+
for version in free_versions:
|
|
1064
|
+
last_updated = last_updated_map[version]
|
|
1065
|
+
if not oldest_version or last_updated < oldest_version_last_updated_on:
|
|
1066
|
+
oldest_version = version
|
|
1067
|
+
oldest_version_last_updated_on = last_updated
|
|
1068
|
+
|
|
1069
|
+
return oldest_version
|
|
1070
|
+
|
|
1071
|
+
def action_publish(
|
|
1072
|
+
self,
|
|
1073
|
+
action_ctx: ActionContext,
|
|
1074
|
+
version: Optional[str],
|
|
1075
|
+
patch: Optional[int],
|
|
1076
|
+
release_channel: Optional[str],
|
|
1077
|
+
release_directive: str,
|
|
1078
|
+
interactive: bool,
|
|
1079
|
+
force: bool,
|
|
1080
|
+
*args,
|
|
1081
|
+
create_version: bool = False,
|
|
1082
|
+
from_stage: bool = False,
|
|
1083
|
+
label: Optional[str] = None,
|
|
1084
|
+
**kwargs,
|
|
1085
|
+
) -> VersionInfo:
|
|
1086
|
+
"""
|
|
1087
|
+
Publishes a version and a patch to a release directive of a release channel.
|
|
1088
|
+
|
|
1089
|
+
The version is first added to the release channel,
|
|
1090
|
+
and then the release directive is set to the version and patch provided.
|
|
1091
|
+
|
|
1092
|
+
If the number of versions in a release channel exceeds the maximum allowable versions,
|
|
1093
|
+
the user is prompted to remove an existing version to make space for the new version.
|
|
1094
|
+
"""
|
|
1095
|
+
if force:
|
|
1096
|
+
policy = AllowAlwaysPolicy()
|
|
1097
|
+
elif interactive:
|
|
1098
|
+
policy = AskAlwaysPolicy()
|
|
1099
|
+
else:
|
|
1100
|
+
policy = DenyAlwaysPolicy()
|
|
1101
|
+
|
|
1102
|
+
if from_stage and not create_version:
|
|
1103
|
+
raise UsageError(
|
|
1104
|
+
"--from-stage flag can only be used with --create-version flag."
|
|
1105
|
+
)
|
|
1106
|
+
if label is not None and not create_version:
|
|
1107
|
+
raise UsageError("--label can only be used with --create-version flag.")
|
|
1108
|
+
|
|
1109
|
+
console = self._workspace_ctx.console
|
|
1110
|
+
if create_version:
|
|
1111
|
+
result = self.action_version_create(
|
|
1112
|
+
action_ctx=action_ctx,
|
|
1113
|
+
version=version,
|
|
1114
|
+
patch=patch,
|
|
1115
|
+
label=label,
|
|
1116
|
+
skip_git_check=True,
|
|
1117
|
+
interactive=interactive,
|
|
1118
|
+
force=force,
|
|
1119
|
+
from_stage=from_stage,
|
|
1120
|
+
)
|
|
1121
|
+
version = result.version_name
|
|
1122
|
+
patch = result.patch_number
|
|
1123
|
+
|
|
1124
|
+
if version is None:
|
|
1125
|
+
raise UsageError(
|
|
1126
|
+
"Please provide a version using --version or use --create-version flag to create a version based on the manifest file."
|
|
1127
|
+
)
|
|
1128
|
+
if patch is None:
|
|
1129
|
+
raise UsageError(
|
|
1130
|
+
"Please provide a patch number using --patch or use --create-version flag to auto create a patch."
|
|
1131
|
+
)
|
|
1132
|
+
|
|
1133
|
+
versions_info = get_snowflake_facade().show_versions(self.name, self.role)
|
|
1134
|
+
|
|
1135
|
+
available_patches = [
|
|
1136
|
+
version_info["patch"]
|
|
1137
|
+
for version_info in versions_info
|
|
1138
|
+
if version_info["version"] == unquote_identifier(version)
|
|
1139
|
+
]
|
|
1140
|
+
|
|
1141
|
+
if not available_patches:
|
|
1142
|
+
raise ClickException(
|
|
1143
|
+
f"Version {version} does not exist in application package {self.name}. Use --create-version flag to create a new version."
|
|
1144
|
+
)
|
|
1145
|
+
|
|
1146
|
+
if patch not in available_patches:
|
|
1147
|
+
raise ClickException(
|
|
1148
|
+
f"Patch {patch} does not exist for version {version} in application package {self.name}. Use --create-version flag to add a new patch."
|
|
1149
|
+
)
|
|
1150
|
+
|
|
1151
|
+
available_release_channels = get_snowflake_facade().show_release_channels(
|
|
1152
|
+
self.name, self.role
|
|
1153
|
+
)
|
|
1154
|
+
|
|
1155
|
+
release_channel = self.get_sanitized_release_channel(
|
|
1156
|
+
release_channel, available_release_channels
|
|
1157
|
+
)
|
|
1158
|
+
|
|
1159
|
+
if release_channel:
|
|
1160
|
+
release_channel_info = {}
|
|
1161
|
+
for channel_info in available_release_channels:
|
|
1162
|
+
if channel_info["name"] == unquote_identifier(release_channel):
|
|
1163
|
+
release_channel_info = channel_info
|
|
1164
|
+
break
|
|
1165
|
+
|
|
1166
|
+
versions_in_channel = release_channel_info["versions"]
|
|
1167
|
+
if unquote_identifier(version) not in release_channel_info["versions"]:
|
|
1168
|
+
if len(versions_in_channel) >= MAX_VERSIONS_IN_RELEASE_CHANNEL:
|
|
1169
|
+
# If we hit the maximum allowable versions in a release channel, we need to remove one version to make space for the new version
|
|
1170
|
+
all_release_directives = (
|
|
1171
|
+
get_snowflake_facade().show_release_directives(
|
|
1172
|
+
package_name=self.name,
|
|
1173
|
+
role=self.role,
|
|
1174
|
+
release_channel=release_channel,
|
|
1175
|
+
)
|
|
1176
|
+
)
|
|
1177
|
+
|
|
1178
|
+
# check which versions are attached to any release directive
|
|
1179
|
+
targeted_versions = {d["version"] for d in all_release_directives}
|
|
1180
|
+
|
|
1181
|
+
free_versions = {
|
|
1182
|
+
v for v in versions_in_channel if v not in targeted_versions
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
if not free_versions:
|
|
1186
|
+
raise ClickException(
|
|
1187
|
+
f"Maximum number of versions in release channel {release_channel} reached. Cannot add more versions."
|
|
1188
|
+
)
|
|
1189
|
+
|
|
1190
|
+
version_to_remove = self._find_version_with_no_recent_update(
|
|
1191
|
+
versions_info, free_versions
|
|
1192
|
+
)
|
|
1193
|
+
user_prompt = f"Maximum number of versions in release channel reached. Would you like to remove version {version_to_remove} to make space for version {version}?"
|
|
1194
|
+
if not policy.should_proceed(user_prompt):
|
|
1195
|
+
raise ClickException(
|
|
1196
|
+
"Cannot proceed with publishing the new version. Please remove an existing version from the release channel to make space for the new version, or use --force to automatically clean up unused versions."
|
|
1197
|
+
)
|
|
1198
|
+
|
|
1199
|
+
console.warning(
|
|
1200
|
+
f"Maximum number of versions in release channel reached. Removing version {version_to_remove} from release_channel {release_channel} to make space for version {version}."
|
|
1201
|
+
)
|
|
1202
|
+
get_snowflake_facade().remove_version_from_release_channel(
|
|
1203
|
+
package_name=self.name,
|
|
1204
|
+
release_channel=release_channel,
|
|
1205
|
+
version=version_to_remove,
|
|
1206
|
+
role=self.role,
|
|
1207
|
+
)
|
|
1208
|
+
|
|
1209
|
+
get_snowflake_facade().add_version_to_release_channel(
|
|
1210
|
+
package_name=self.name,
|
|
1211
|
+
release_channel=release_channel,
|
|
1212
|
+
version=version,
|
|
1213
|
+
role=self.role,
|
|
1214
|
+
)
|
|
1215
|
+
|
|
1216
|
+
get_snowflake_facade().set_release_directive(
|
|
1217
|
+
package_name=self.name,
|
|
1218
|
+
release_directive=release_directive,
|
|
1219
|
+
release_channel=release_channel,
|
|
1220
|
+
target_accounts=None,
|
|
1221
|
+
version=version,
|
|
1222
|
+
patch=patch,
|
|
1223
|
+
role=self.role,
|
|
1224
|
+
)
|
|
1225
|
+
return VersionInfo(version, patch, None)
|
|
1226
|
+
|
|
1227
|
+
def _bundle_children(self, action_ctx: ActionContext) -> List[str]:
|
|
1228
|
+
# Create _children directory
|
|
1229
|
+
children_artifacts_dir = self.children_artifacts_deploy_root
|
|
1230
|
+
os.makedirs(children_artifacts_dir)
|
|
1231
|
+
children_sql = []
|
|
1232
|
+
for child in self._entity_model.children:
|
|
1233
|
+
# Create child sub directory
|
|
1234
|
+
child_artifacts_dir = children_artifacts_dir / sanitize_dir_name(
|
|
1235
|
+
child.target
|
|
1236
|
+
)
|
|
1237
|
+
try:
|
|
1238
|
+
os.makedirs(child_artifacts_dir)
|
|
1239
|
+
except FileExistsError:
|
|
1240
|
+
raise ClickException(
|
|
1241
|
+
f"Could not create sub-directory at {child_artifacts_dir}. Make sure child entity names do not collide with each other."
|
|
1242
|
+
)
|
|
1243
|
+
child_entity: ApplicationPackageChildInterface = action_ctx.get_entity(
|
|
1244
|
+
child.target
|
|
1245
|
+
)
|
|
1246
|
+
child_entity.bundle(child_artifacts_dir)
|
|
1247
|
+
app_role = (
|
|
1248
|
+
to_identifier(
|
|
1249
|
+
child.ensure_usable_by.application_roles.pop() # TODO Support more than one application role
|
|
1250
|
+
)
|
|
1251
|
+
if child.ensure_usable_by and child.ensure_usable_by.application_roles
|
|
1252
|
+
else None
|
|
1253
|
+
)
|
|
1254
|
+
child_schema = (
|
|
1255
|
+
to_identifier(child.identifier.schema_)
|
|
1256
|
+
if child.identifier and child.identifier.schema_
|
|
1257
|
+
else None
|
|
1258
|
+
)
|
|
1259
|
+
children_sql.append(
|
|
1260
|
+
child_entity.get_deploy_sql(
|
|
1261
|
+
artifacts_dir=child_artifacts_dir.relative_to(self.deploy_root),
|
|
1262
|
+
schema=child_schema,
|
|
1263
|
+
# TODO Allow users to override the hard-coded value for specific children
|
|
1264
|
+
replace=True,
|
|
1265
|
+
)
|
|
1266
|
+
)
|
|
1267
|
+
if app_role:
|
|
1268
|
+
children_sql.append(
|
|
1269
|
+
f"CREATE APPLICATION ROLE IF NOT EXISTS {app_role};"
|
|
1270
|
+
)
|
|
1271
|
+
if child_schema:
|
|
1272
|
+
children_sql.append(
|
|
1273
|
+
f"GRANT USAGE ON SCHEMA {child_schema} TO APPLICATION ROLE {app_role};"
|
|
1274
|
+
)
|
|
1275
|
+
children_sql.append(
|
|
1276
|
+
child_entity.get_usage_grant_sql(
|
|
1277
|
+
app_role=app_role, schema=child_schema
|
|
1278
|
+
)
|
|
1279
|
+
)
|
|
1280
|
+
return children_sql
|
|
1281
|
+
|
|
555
1282
|
def _deploy(
|
|
556
1283
|
self,
|
|
1284
|
+
action_ctx: ActionContext,
|
|
557
1285
|
bundle_map: BundleMap | None,
|
|
558
1286
|
prune: bool,
|
|
559
1287
|
recursive: bool,
|
|
560
1288
|
paths: list[Path],
|
|
561
1289
|
print_diff: bool,
|
|
562
1290
|
validate: bool,
|
|
563
|
-
|
|
1291
|
+
stage_path: StagePathParts,
|
|
564
1292
|
interactive: bool,
|
|
565
1293
|
force: bool,
|
|
566
1294
|
run_post_deploy_hooks: bool = True,
|
|
@@ -575,10 +1303,10 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
575
1303
|
policy = DenyAlwaysPolicy()
|
|
576
1304
|
|
|
577
1305
|
console = workspace_ctx.console
|
|
578
|
-
|
|
1306
|
+
stage_path = stage_path or self.stage_path
|
|
579
1307
|
|
|
580
1308
|
# 1. Create a bundle if one wasn't passed in
|
|
581
|
-
bundle_map = bundle_map or self._bundle()
|
|
1309
|
+
bundle_map = bundle_map or self._bundle(action_ctx)
|
|
582
1310
|
|
|
583
1311
|
# 2. Create an empty application package, if none exists
|
|
584
1312
|
try:
|
|
@@ -590,17 +1318,15 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
590
1318
|
|
|
591
1319
|
with get_sql_executor().use_role(self.role):
|
|
592
1320
|
# 3. Upload files from deploy root local folder to the above stage
|
|
593
|
-
stage_schema = extract_schema(stage_fqn)
|
|
594
1321
|
diff = sync_deploy_root_with_stage(
|
|
595
1322
|
console=console,
|
|
596
1323
|
deploy_root=self.deploy_root,
|
|
597
1324
|
package_name=self.name,
|
|
598
|
-
stage_schema=stage_schema,
|
|
599
1325
|
bundle_map=bundle_map,
|
|
600
1326
|
role=self.role,
|
|
601
1327
|
prune=prune,
|
|
602
1328
|
recursive=recursive,
|
|
603
|
-
|
|
1329
|
+
stage_path=stage_path,
|
|
604
1330
|
local_paths_to_sync=paths,
|
|
605
1331
|
print_diff=print_diff,
|
|
606
1332
|
)
|
|
@@ -610,6 +1336,7 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
610
1336
|
|
|
611
1337
|
if validate:
|
|
612
1338
|
self.validate_setup_script(
|
|
1339
|
+
action_ctx=action_ctx,
|
|
613
1340
|
use_scratch_stage=False,
|
|
614
1341
|
interactive=interactive,
|
|
615
1342
|
force=force,
|
|
@@ -657,7 +1384,7 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
657
1384
|
It executes a 'show release directives in application package' query and returns the filtered results, if they exist.
|
|
658
1385
|
"""
|
|
659
1386
|
release_directives = get_snowflake_facade().show_release_directives(
|
|
660
|
-
self.name, self.role
|
|
1387
|
+
package_name=self.name, role=self.role
|
|
661
1388
|
)
|
|
662
1389
|
return [
|
|
663
1390
|
directive
|
|
@@ -678,7 +1405,7 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
678
1405
|
get_snowflake_facade().create_version_in_package(
|
|
679
1406
|
role=self.role,
|
|
680
1407
|
package_name=self.name,
|
|
681
|
-
|
|
1408
|
+
path_to_version_directory=self.stage_path.full_path,
|
|
682
1409
|
version=version,
|
|
683
1410
|
label=label,
|
|
684
1411
|
)
|
|
@@ -688,9 +1415,10 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
688
1415
|
|
|
689
1416
|
def add_new_patch_to_version(
|
|
690
1417
|
self, version: str, patch: int | None = None, label: str | None = None
|
|
691
|
-
):
|
|
1418
|
+
) -> int:
|
|
692
1419
|
"""
|
|
693
1420
|
Add a new patch, optionally a custom one, to an existing version in an application package.
|
|
1421
|
+
Returns the patch number of the newly created patch.
|
|
694
1422
|
"""
|
|
695
1423
|
console = self._workspace_ctx.console
|
|
696
1424
|
|
|
@@ -702,7 +1430,7 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
702
1430
|
new_patch = get_snowflake_facade().add_patch_to_package_version(
|
|
703
1431
|
role=self.role,
|
|
704
1432
|
package_name=self.name,
|
|
705
|
-
|
|
1433
|
+
path_to_version_directory=self.stage_path.full_path,
|
|
706
1434
|
version=version,
|
|
707
1435
|
patch=patch,
|
|
708
1436
|
label=label,
|
|
@@ -710,6 +1438,7 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
710
1438
|
console.message(
|
|
711
1439
|
f"Patch {new_patch}{with_label_prompt} created for version {version} defined in application package {self.name}."
|
|
712
1440
|
)
|
|
1441
|
+
return new_patch
|
|
713
1442
|
|
|
714
1443
|
def check_index_changes_in_git_repo(
|
|
715
1444
|
self, policy: PolicyBase, interactive: bool
|
|
@@ -795,7 +1524,7 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
795
1524
|
) -> bool:
|
|
796
1525
|
"""
|
|
797
1526
|
Returns true if the 'distribution' attribute of an existing application package in snowflake
|
|
798
|
-
is the same as the
|
|
1527
|
+
is the same as the attribute specified in project definition file.
|
|
799
1528
|
"""
|
|
800
1529
|
model = self._entity_model
|
|
801
1530
|
workspace_ctx = self._workspace_ctx
|
|
@@ -818,6 +1547,28 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
818
1547
|
return False
|
|
819
1548
|
return True
|
|
820
1549
|
|
|
1550
|
+
def _get_enable_release_channels_flag(self) -> Optional[bool]:
|
|
1551
|
+
"""
|
|
1552
|
+
Returns the requested value of enable_release_channels flag for the application package.
|
|
1553
|
+
It retrieves the value from the configuration file and checks that the feature is enabled in the account.
|
|
1554
|
+
If return value is None, it means do not explicitly set the flag.
|
|
1555
|
+
"""
|
|
1556
|
+
feature_flag_from_config = FeatureFlag.ENABLE_RELEASE_CHANNELS.get_value()
|
|
1557
|
+
feature_enabled_in_account = (
|
|
1558
|
+
get_snowflake_facade().get_ui_parameter(
|
|
1559
|
+
UIParameter.NA_FEATURE_RELEASE_CHANNELS, "ENABLED"
|
|
1560
|
+
)
|
|
1561
|
+
== "ENABLED"
|
|
1562
|
+
)
|
|
1563
|
+
|
|
1564
|
+
if feature_flag_from_config is not None and not feature_enabled_in_account:
|
|
1565
|
+
self._workspace_ctx.console.warning(
|
|
1566
|
+
f"Ignoring feature flag {FeatureFlag.ENABLE_RELEASE_CHANNELS.name} because release channels are not enabled in the current account."
|
|
1567
|
+
)
|
|
1568
|
+
return None
|
|
1569
|
+
|
|
1570
|
+
return feature_flag_from_config
|
|
1571
|
+
|
|
821
1572
|
def create_app_package(self) -> None:
|
|
822
1573
|
"""
|
|
823
1574
|
Creates the application package with our up-to-date stage if none exists.
|
|
@@ -845,21 +1596,23 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
845
1596
|
if row_comment not in ALLOWED_SPECIAL_COMMENTS:
|
|
846
1597
|
raise ApplicationPackageAlreadyExistsError(self.name)
|
|
847
1598
|
|
|
1599
|
+
# 4. Update the application package with setting enable_release_channels if necessary
|
|
1600
|
+
get_snowflake_facade().alter_application_package_properties(
|
|
1601
|
+
package_name=self.name,
|
|
1602
|
+
enable_release_channels=self._get_enable_release_channels_flag(),
|
|
1603
|
+
role=self.role,
|
|
1604
|
+
)
|
|
1605
|
+
|
|
848
1606
|
return
|
|
849
1607
|
|
|
850
1608
|
# If no application package pre-exists, create an application package, with the specified distribution in the project definition file.
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
comment = {SPECIAL_COMMENT}
|
|
859
|
-
distribution = {model.distribution}
|
|
860
|
-
"""
|
|
861
|
-
)
|
|
862
|
-
)
|
|
1609
|
+
console.step(f"Creating new application package {self.name} in account.")
|
|
1610
|
+
get_snowflake_facade().create_application_package(
|
|
1611
|
+
role=self.role,
|
|
1612
|
+
enable_release_channels=self._get_enable_release_channels_flag(),
|
|
1613
|
+
distribution=model.distribution,
|
|
1614
|
+
package_name=self.name,
|
|
1615
|
+
)
|
|
863
1616
|
|
|
864
1617
|
def execute_post_deploy_hooks(self):
|
|
865
1618
|
execute_post_deploy_hooks(
|
|
@@ -873,7 +1626,11 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
873
1626
|
)
|
|
874
1627
|
|
|
875
1628
|
def validate_setup_script(
|
|
876
|
-
self,
|
|
1629
|
+
self,
|
|
1630
|
+
action_ctx: ActionContext,
|
|
1631
|
+
use_scratch_stage: bool,
|
|
1632
|
+
interactive: bool,
|
|
1633
|
+
force: bool,
|
|
877
1634
|
):
|
|
878
1635
|
workspace_ctx = self._workspace_ctx
|
|
879
1636
|
console = workspace_ctx.console
|
|
@@ -881,6 +1638,7 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
881
1638
|
"""Validates Native App setup script SQL."""
|
|
882
1639
|
with console.phase(f"Validating Snowflake Native App setup script."):
|
|
883
1640
|
validation_result = self.get_validation_result(
|
|
1641
|
+
action_ctx=action_ctx,
|
|
884
1642
|
use_scratch_stage=use_scratch_stage,
|
|
885
1643
|
force=force,
|
|
886
1644
|
interactive=interactive,
|
|
@@ -900,26 +1658,35 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
900
1658
|
if validation_result["status"] == "FAIL":
|
|
901
1659
|
raise SetupScriptFailedValidation()
|
|
902
1660
|
|
|
1661
|
+
@span("validate_setup_script")
|
|
903
1662
|
def get_validation_result(
|
|
904
|
-
self,
|
|
1663
|
+
self,
|
|
1664
|
+
action_ctx: ActionContext,
|
|
1665
|
+
use_scratch_stage: bool,
|
|
1666
|
+
interactive: bool,
|
|
1667
|
+
force: bool,
|
|
905
1668
|
):
|
|
906
1669
|
"""Call system$validate_native_app_setup() to validate deployed Native App setup script."""
|
|
907
|
-
|
|
1670
|
+
stage_path = self.stage_path
|
|
908
1671
|
if use_scratch_stage:
|
|
909
|
-
|
|
1672
|
+
stage_path = self.scratch_stage_path
|
|
910
1673
|
self._deploy(
|
|
1674
|
+
action_ctx=action_ctx,
|
|
911
1675
|
bundle_map=None,
|
|
912
1676
|
prune=True,
|
|
913
1677
|
recursive=True,
|
|
914
1678
|
paths=[],
|
|
915
1679
|
print_diff=False,
|
|
916
1680
|
validate=False,
|
|
917
|
-
|
|
1681
|
+
stage_path=stage_path,
|
|
918
1682
|
interactive=interactive,
|
|
919
1683
|
force=force,
|
|
920
1684
|
run_post_deploy_hooks=False,
|
|
921
1685
|
)
|
|
922
|
-
prefixed_stage_fqn = StageManager.get_standard_stage_prefix(
|
|
1686
|
+
prefixed_stage_fqn = StageManager.get_standard_stage_prefix(
|
|
1687
|
+
stage_path.full_path
|
|
1688
|
+
)
|
|
1689
|
+
|
|
923
1690
|
sql_executor = get_sql_executor()
|
|
924
1691
|
try:
|
|
925
1692
|
cursor = sql_executor.execute_query(
|
|
@@ -936,11 +1703,11 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
936
1703
|
finally:
|
|
937
1704
|
if use_scratch_stage:
|
|
938
1705
|
self._workspace_ctx.console.step(
|
|
939
|
-
f"Dropping stage {self.
|
|
1706
|
+
f"Dropping stage {self.scratch_stage_path.stage}."
|
|
940
1707
|
)
|
|
941
1708
|
with sql_executor.use_role(self.role):
|
|
942
1709
|
sql_executor.execute_query(
|
|
943
|
-
f"drop stage if exists {self.
|
|
1710
|
+
f"drop stage if exists {self.scratch_stage_path.stage}"
|
|
944
1711
|
)
|
|
945
1712
|
|
|
946
1713
|
def resolve_version_info(
|
|
@@ -951,7 +1718,7 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
951
1718
|
bundle_map: BundleMap | None,
|
|
952
1719
|
policy: PolicyBase,
|
|
953
1720
|
interactive: bool,
|
|
954
|
-
):
|
|
1721
|
+
) -> VersionInfo:
|
|
955
1722
|
"""Determine version name, patch number, and label from CLI provided values and manifest.yml version entry.
|
|
956
1723
|
@param [Optional] version: version name as specified in the command
|
|
957
1724
|
@param [Optional] patch: patch number as specified in the command
|
|
@@ -959,12 +1726,14 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
959
1726
|
@param [Optional] bundle_map: bundle_map if a deploy_root is prepared. _bundle() is performed otherwise.
|
|
960
1727
|
@param policy: CLI policy
|
|
961
1728
|
@param interactive: True if command is run in interactive mode, otherwise False
|
|
1729
|
+
|
|
1730
|
+
@return VersionInfo: version_name, patch_number, label resolved from CLI and manifest.yml
|
|
962
1731
|
"""
|
|
963
1732
|
console = self._workspace_ctx.console
|
|
964
1733
|
|
|
965
1734
|
resolved_version = None
|
|
966
1735
|
resolved_patch = None
|
|
967
|
-
resolved_label =
|
|
1736
|
+
resolved_label = None
|
|
968
1737
|
|
|
969
1738
|
# If version is specified in CLI, no version information from manifest.yml is used (except for comment, we can't control comment as of now).
|
|
970
1739
|
if version is not None:
|
|
@@ -972,7 +1741,7 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
972
1741
|
"Ignoring version information from the application manifest since a version was explicitly specified with the command."
|
|
973
1742
|
)
|
|
974
1743
|
resolved_patch = patch
|
|
975
|
-
resolved_label = label
|
|
1744
|
+
resolved_label = label
|
|
976
1745
|
resolved_version = version
|
|
977
1746
|
|
|
978
1747
|
# When version is not set by CLI, version name is read from manifest.yml. patch and label from CLI will be used, if provided.
|
|
@@ -1026,17 +1795,18 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
1026
1795
|
resolved_label = label if label is not None else label_manifest
|
|
1027
1796
|
|
|
1028
1797
|
# Check if patch needs to throw a bad option error, either if application package does not exist or if version does not exist
|
|
1029
|
-
|
|
1798
|
+
# If patch is 0 and version does not exist, it is a valid case, because patch 0 is the first patch in a version.
|
|
1799
|
+
if resolved_patch:
|
|
1030
1800
|
try:
|
|
1031
|
-
if not self.get_existing_version_info(resolved_version):
|
|
1801
|
+
if not self.get_existing_version_info(to_identifier(resolved_version)):
|
|
1032
1802
|
raise BadOptionUsage(
|
|
1033
1803
|
option_name="patch",
|
|
1034
|
-
message=f"
|
|
1804
|
+
message=f"Version {resolved_version} is not defined in the application package {self.name}. Try again with a patch of 0 or without specifying any patch.",
|
|
1035
1805
|
)
|
|
1036
1806
|
except ApplicationPackageDoesNotExistError as app_err:
|
|
1037
1807
|
raise BadOptionUsage(
|
|
1038
1808
|
option_name="patch",
|
|
1039
|
-
message=f"
|
|
1809
|
+
message=f"Application package {self.name} does not exist yet. Try again with a patch of 0 or without specifying any patch.",
|
|
1040
1810
|
)
|
|
1041
1811
|
|
|
1042
1812
|
return VersionInfo(
|