Skip to content

Commit e990532

Browse files
SNOW-2229492 and SNOW-2229493 - cleanup versions in DCM (#2511)
* remove --no-version parameter * remove dcm add_version command * remove `--version` flag for `dcm plan` and `dcm deploy` - use given stage or upload local files * Reverted logic in test_project_deploy_from_stage * Reverted logic in test_project_drop_version * Reverted logic in test_project_deploy * Add support for `snow dcm deploy --alias` and revert drop_version integration test logic * port `--prune` from non-existing `add-version` to `dcm plan` and `dcm deploy` * fix the integation test for prune * SNOW-2229498 rename show versions to show deployments in dcm (#2510) * review fixes * rely on existing user-facing errors
1 parent 24112f5 commit e990532

File tree

6 files changed

+571
-735
lines changed

6 files changed

+571
-735
lines changed

src/snowflake/cli/_plugins/dcm/commands.py

Lines changed: 51 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14-
1514
from typing import List, Optional
1615

1716
import typer
@@ -22,13 +21,13 @@
2221
from snowflake.cli._plugins.object.command_aliases import add_object_command_aliases
2322
from snowflake.cli._plugins.object.commands import scope_option
2423
from snowflake.cli._plugins.object.manager import ObjectManager
24+
from snowflake.cli.api.artifacts.upload import sync_artifacts_with_stage
2525
from snowflake.cli.api.cli_global_context import get_cli_context
2626
from snowflake.cli.api.commands.decorators import with_project_definition
2727
from snowflake.cli.api.commands.flags import (
2828
IfExistsOption,
2929
IfNotExistsOption,
3030
OverrideableOption,
31-
PruneOption,
3231
entity_argument,
3332
identifier_argument,
3433
like_option,
@@ -46,6 +45,7 @@
4645
QueryJsonValueResult,
4746
QueryResult,
4847
)
48+
from snowflake.cli.api.project.project_paths import ProjectPaths
4949

5050
app = SnowTyperFactory(
5151
name="dcm",
@@ -54,12 +54,6 @@
5454
)
5555

5656
dcm_identifier = identifier_argument(sf_object="DCM Project", example="MY_PROJECT")
57-
version_flag = typer.Option(
58-
None,
59-
"--version",
60-
help="Version of the DCM Project to use. If not specified default version is used. For names containing '$', use single quotes to prevent shell expansion (e.g., 'VERSION$1').",
61-
show_default=False,
62-
)
6357
variables_flag = variables_option(
6458
'Variables for the execution context; for example: `-D "<key>=<value>"`.'
6559
)
@@ -72,6 +66,22 @@
7266
from_option = OverrideableOption(
7367
None,
7468
"--from",
69+
mutually_exclusive=["prune"],
70+
show_default=False,
71+
)
72+
73+
prune_option = OverrideableOption(
74+
False,
75+
"--prune",
76+
help="Remove unused artifacts from the stage during sync. Mutually exclusive with --from.",
77+
mutually_exclusive=["from_stage"],
78+
show_default=False,
79+
)
80+
81+
alias_option = typer.Option(
82+
None,
83+
"--alias",
84+
help="Alias for the deployment.",
7585
show_default=False,
7686
)
7787

@@ -91,52 +101,46 @@
91101
@app.command(requires_connection=True)
92102
def deploy(
93103
identifier: FQN = dcm_identifier,
94-
version: Optional[str] = version_flag,
95104
from_stage: Optional[str] = from_option(
96-
help="Apply changes defined in given stage instead of using a specific project version."
105+
help="Deploy DCM Project deployment from a given stage."
97106
),
98107
variables: Optional[List[str]] = variables_flag,
99108
configuration: Optional[str] = configuration_flag,
109+
alias: Optional[str] = alias_option,
110+
prune: bool = prune_option(),
100111
**options,
101112
):
102113
"""
103114
Applies changes defined in DCM Project to Snowflake.
104115
"""
105-
if version and from_stage:
106-
raise CliError("--version and --from are mutually exclusive.")
107-
108116
result = DCMProjectManager().execute(
109117
project_name=identifier,
110118
configuration=configuration,
111-
version=version,
112-
from_stage=from_stage,
119+
from_stage=from_stage if from_stage else _sync_local_files(prune=prune),
113120
variables=variables,
121+
alias=alias,
114122
)
115123
return QueryJsonValueResult(result)
116124

117125

118126
@app.command(requires_connection=True)
119127
def plan(
120128
identifier: FQN = dcm_identifier,
121-
version: Optional[str] = version_flag,
122129
from_stage: Optional[str] = from_option(
123-
help="Plan DCM Project deployment from given stage instead of using a specific version."
130+
help="Plan DCM Project deployment from a given stage."
124131
),
125132
variables: Optional[List[str]] = variables_flag,
126133
configuration: Optional[str] = configuration_flag,
134+
prune: bool = prune_option(),
127135
**options,
128136
):
129137
"""
130138
Plans a DCM Project deployment (validates without executing).
131139
"""
132-
if version and from_stage:
133-
raise CliError("--version and --from are mutually exclusive.")
134-
135140
result = DCMProjectManager().execute(
136141
project_name=identifier,
137142
configuration=configuration,
138-
version=version,
139-
from_stage=from_stage,
143+
from_stage=from_stage if from_stage else _sync_local_files(prune=prune),
140144
dry_run=True,
141145
variables=variables,
142146
)
@@ -147,19 +151,13 @@ def plan(
147151
@with_project_definition()
148152
def create(
149153
entity_id: str = entity_argument("dcm"),
150-
no_version: bool = typer.Option(
151-
False,
152-
"--no-version",
153-
help="Do not initialize DCM Project with a new version, only create the snowflake object.",
154-
),
155154
if_not_exists: bool = IfNotExistsOption(
156155
help="Do nothing if the project already exists."
157156
),
158157
**options,
159158
):
160159
"""
161160
Creates a DCM Project in Snowflake.
162-
By default, the DCM Project is initialized with a new version created from local files.
163161
"""
164162
cli_context = get_cli_context()
165163
project: DCMProjectEntityModel = get_entity_for_operation(
@@ -175,76 +173,23 @@ def create(
175173
return MessageResult(message)
176174
raise CliError(message)
177175

178-
if not no_version and om.object_exists(
179-
object_type="stage", fqn=FQN.from_stage(project.stage)
180-
):
176+
if om.object_exists(object_type="stage", fqn=FQN.from_stage(project.stage)):
181177
raise CliError(f"Stage '{project.stage}' already exists.")
182178

183179
dpm = DCMProjectManager()
184180
with cli_console.phase(f"Creating DCM Project '{project.fqn}'"):
185-
dpm.create(project=project, initialize_version_from_local_files=not no_version)
186-
187-
if no_version:
188-
return MessageResult(f"DCM Project '{project.fqn}' successfully created.")
189-
return MessageResult(
190-
f"DCM Project '{project.fqn}' successfully created and initial version is added."
191-
)
192-
181+
dpm.create(project=project)
193182

194-
@app.command(requires_connection=True)
195-
@with_project_definition()
196-
def add_version(
197-
entity_id: str = entity_argument("dcm"),
198-
_from: Optional[str] = from_option(
199-
help="Create a new version using given stage instead of uploading local files."
200-
),
201-
_alias: Optional[str] = typer.Option(
202-
None, "--alias", help="Alias for the version.", show_default=False
203-
),
204-
comment: Optional[str] = typer.Option(
205-
None, "--comment", help="Version comment.", show_default=False
206-
),
207-
prune: bool = PruneOption(default=True),
208-
**options,
209-
):
210-
"""Uploads local files to Snowflake and cerates a new DCM Project version."""
211-
if _from is not None and prune:
212-
cli_console.warning(
213-
"When `--from` option is used, `--prune` option will be ignored and files from stage will be used as they are."
214-
)
215-
prune = False
216-
cli_context = get_cli_context()
217-
project: DCMProjectEntityModel = get_entity_for_operation(
218-
cli_context=cli_context,
219-
entity_id=entity_id,
220-
project_definition=cli_context.project_definition,
221-
entity_type="dcm",
222-
)
223-
om = ObjectManager()
224-
if not om.object_exists(object_type="dcm", fqn=project.fqn):
225-
raise CliError(
226-
f"DCM Project '{project.fqn}' does not exist. Use `dcm create` command first."
227-
)
228-
DCMProjectManager().add_version(
229-
project=project,
230-
prune=prune,
231-
from_stage=_from,
232-
alias=_alias,
233-
comment=comment,
234-
)
235-
alias_str = "" if _alias is None else f"'{_alias}' "
236-
return MessageResult(
237-
f"New version {alias_str}added to DCM Project '{project.fqn}'."
238-
)
183+
return MessageResult(f"DCM Project '{project.fqn}' successfully created.")
239184

240185

241186
@app.command(requires_connection=True)
242-
def list_versions(
187+
def list_deployments(
243188
identifier: FQN = dcm_identifier,
244189
**options,
245190
):
246191
"""
247-
Lists versions of given DCM Project.
192+
Lists deployments of given DCM Project.
248193
"""
249194
pm = DCMProjectManager()
250195
results = pm.list_versions(project_name=identifier)
@@ -280,3 +225,23 @@ def drop_version(
280225
return MessageResult(
281226
f"Version '{version_name}' dropped from DCM Project '{identifier}'."
282227
)
228+
229+
230+
def _sync_local_files(prune: bool = False) -> str:
231+
cli_context = get_cli_context()
232+
project_entity = get_entity_for_operation(
233+
cli_context=cli_context,
234+
entity_id=None,
235+
project_definition=cli_context.project_definition,
236+
entity_type="dcm",
237+
)
238+
239+
with cli_console.phase("Syncing local files to stage"):
240+
sync_artifacts_with_stage(
241+
project_paths=ProjectPaths(project_root=cli_context.project_root),
242+
stage_root=project_entity.stage,
243+
artifacts=project_entity.artifacts,
244+
prune=prune,
245+
)
246+
247+
return project_entity.stage

src/snowflake/cli/_plugins/dcm/manager.py

Lines changed: 12 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -11,37 +11,35 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14-
from textwrap import dedent
15-
from typing import List, Optional
14+
15+
from typing import List
1616

1717
from snowflake.cli._plugins.dcm.dcm_project_entity_model import DCMProjectEntityModel
1818
from snowflake.cli._plugins.stage.manager import StageManager
19-
from snowflake.cli.api.artifacts.upload import sync_artifacts_with_stage
20-
from snowflake.cli.api.cli_global_context import get_cli_context
2119
from snowflake.cli.api.commands.utils import parse_key_value_variables
22-
from snowflake.cli.api.console.console import cli_console
2320
from snowflake.cli.api.identifiers import FQN
24-
from snowflake.cli.api.project.project_paths import ProjectPaths
2521
from snowflake.cli.api.sql_execution import SqlExecutionMixin
2622
from snowflake.cli.api.stage_path import StagePath
27-
from snowflake.connector.cursor import SnowflakeCursor
2823

2924

3025
class DCMProjectManager(SqlExecutionMixin):
3126
def execute(
3227
self,
3328
project_name: FQN,
29+
from_stage: str,
3430
configuration: str | None = None,
35-
version: str | None = None,
36-
from_stage: str | None = None,
3731
variables: List[str] | None = None,
3832
dry_run: bool = False,
33+
alias: str | None = None,
3934
):
35+
4036
query = f"EXECUTE DCM PROJECT {project_name.sql_identifier}"
4137
if dry_run:
4238
query += " PLAN"
4339
else:
4440
query += " DEPLOY"
41+
if alias:
42+
query += f" AS {alias}"
4543
if configuration or variables:
4644
query += f" USING"
4745
if configuration:
@@ -50,23 +48,13 @@ def execute(
5048
query += StageManager.parse_execute_variables(
5149
parse_key_value_variables(variables)
5250
).removeprefix(" using")
53-
if version:
54-
query += f" WITH VERSION {version}"
55-
elif from_stage:
56-
stage_path = StagePath.from_stage_str(from_stage)
57-
query += f" FROM {stage_path.absolute_path()}"
51+
stage_path = StagePath.from_stage_str(from_stage)
52+
query += f" FROM {stage_path.absolute_path()}"
5853
return self.execute_query(query=query)
5954

60-
def _create_object(self, project_name: FQN) -> SnowflakeCursor:
61-
query = dedent(f"CREATE DCM PROJECT {project_name.sql_identifier}")
62-
return self.execute_query(query)
63-
64-
def create(
65-
self, project: DCMProjectEntityModel, initialize_version_from_local_files: bool
66-
) -> None:
67-
self._create_object(project.fqn)
68-
if initialize_version_from_local_files:
69-
self.add_version(project=project)
55+
def create(self, project: DCMProjectEntityModel) -> None:
56+
query = f"CREATE DCM PROJECT {project.fqn.sql_identifier}"
57+
self.execute_query(query)
7058

7159
def _create_version(
7260
self,
@@ -84,38 +72,6 @@ def _create_version(
8472
query += f" COMMENT = '{comment}'"
8573
return self.execute_query(query=query)
8674

87-
def add_version(
88-
self,
89-
project: DCMProjectEntityModel,
90-
prune: bool = False,
91-
from_stage: Optional[str] = None,
92-
alias: Optional[str] = None,
93-
comment: Optional[str] = None,
94-
):
95-
"""
96-
Adds a version to DCM Project. If [from_stage] is not defined,
97-
uploads local files to the stage defined in DCM Project definition.
98-
"""
99-
100-
if not from_stage:
101-
cli_context = get_cli_context()
102-
from_stage = project.stage
103-
with cli_console.phase("Uploading artifacts"):
104-
sync_artifacts_with_stage(
105-
project_paths=ProjectPaths(project_root=cli_context.project_root),
106-
stage_root=from_stage,
107-
artifacts=project.artifacts,
108-
prune=prune,
109-
)
110-
111-
with cli_console.phase(f"Creating DCM Project version from stage {from_stage}"):
112-
return self._create_version(
113-
project_name=project.fqn,
114-
from_stage=from_stage, # type:ignore
115-
alias=alias,
116-
comment=comment,
117-
)
118-
11975
def list_versions(self, project_name: FQN):
12076
query = f"SHOW VERSIONS IN DCM PROJECT {project_name.identifier}"
12177
return self.execute_query(query=query)

0 commit comments

Comments
 (0)