Skip to content

Commit

Permalink
Add warning when templating is used in v1 (#1279)
Browse files Browse the repository at this point in the history
  • Loading branch information
sfc-gh-melnacouzi authored Jul 4, 2024
1 parent f4989e1 commit 3fcf12a
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 11 deletions.
1 change: 1 addition & 0 deletions RELEASE-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
* Improved stage diff output in `snow app` commands
* Hid the diff from `snow app validate` output since it was redundant
* Added log into the file with loaded external plugins
* Warn users if they attempt to use templating with project definition version 1
* Improved output and format of Pydantic validation errors

# v2.5.0
Expand Down
51 changes: 41 additions & 10 deletions src/snowflake/cli/api/utils/definition_rendering.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@
import copy
from typing import Any, Optional

from jinja2 import Environment, nodes
from jinja2 import Environment, TemplateSyntaxError, nodes
from packaging.version import Version
from snowflake.cli.api.console import cli_console as cc
from snowflake.cli.api.exceptions import CycleDetectedError, InvalidTemplate
from snowflake.cli.api.project.schemas.project_definition import (
ProjectDefinition,
Expand Down Expand Up @@ -49,7 +50,13 @@ def render(self, template_value: Any, context: Context) -> Any:

def get_referenced_vars(self, template_value: Any) -> set[TemplateVar]:
template_str = str(template_value)
ast = self._jinja_env.parse(template_str)
try:
ast = self._jinja_env.parse(template_str)
except TemplateSyntaxError as e:
raise InvalidTemplate(
f"Error parsing template from project definition file. Value: '{template_str}'. Error: {e}"
) from e

return self._get_referenced_vars(ast, template_str)

def _get_referenced_vars(
Expand Down Expand Up @@ -232,6 +239,26 @@ def _validate_env_section(env_section: dict):
)


def _get_referenced_vars_in_definition(
template_env: TemplatedEnvironment, definition: Definition
):
referenced_vars = set()

def find_any_template_vars(element):
referenced_vars.update(template_env.get_referenced_vars(element))

traverse(definition, visit_action=find_any_template_vars)

return referenced_vars


def _template_version_warning():
cc.warning(
"Ignoring template pattern in project definition file. "
"Update 'definition_version' to 1.1 or later in snowflake.yml to enable template expansion."
)


def render_definition_template(
original_definition: Optional[Definition], context_overrides: Context
) -> ProjectProperties:
Expand Down Expand Up @@ -259,25 +286,29 @@ def render_definition_template(
return ProjectProperties(None, {CONTEXT_KEY: {"env": environment_overrides}})

project_context = {CONTEXT_KEY: definition}
template_env = TemplatedEnvironment(get_snowflake_cli_jinja_env())

if "definition_version" not in definition or Version(
definition["definition_version"]
) < Version("1.1"):
try:
referenced_vars = _get_referenced_vars_in_definition(
template_env, definition
)
if referenced_vars:
_template_version_warning()
except Exception:
# also warn on Exception, as it means the user is incorrectly attempting to use templating
_template_version_warning()

project_definition = ProjectDefinition(**original_definition)
project_context[CONTEXT_KEY]["env"] = environment_overrides
return ProjectProperties(project_definition, project_context)

default_env = definition.get("env", {})
_validate_env_section(default_env)

template_env = TemplatedEnvironment(get_snowflake_cli_jinja_env())

referenced_vars = set()

def find_any_template_vars(element):
referenced_vars.update(template_env.get_referenced_vars(element))

traverse(definition, visit_action=find_any_template_vars)
referenced_vars = _get_referenced_vars_in_definition(template_env, definition)

dependencies_graph = _build_dependency_graph(
template_env, referenced_vars, project_context, environment_overrides
Expand Down
84 changes: 83 additions & 1 deletion tests/api/utils/test_definition_rendering.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
from snowflake.cli.api.utils.definition_rendering import render_definition_template
from snowflake.cli.api.utils.models import ProjectEnvironment

from tests.nativeapp.utils import NATIVEAPP_MODULE


@mock.patch.dict(os.environ, {}, clear=True)
def test_resolve_variables_in_project_no_cross_variable_dependencies():
Expand Down Expand Up @@ -70,7 +72,7 @@ def test_resolve_variables_in_project_cross_variable_dependencies():


@mock.patch.dict(os.environ, {}, clear=True)
def test_no_resolve_in_version_1():
def test_env_not_supported_in_version_1():
definition = {
"definition_version": "1",
"env": {
Expand All @@ -83,6 +85,86 @@ def test_no_resolve_in_version_1():
render_definition_template(definition, {})


@mock.patch.dict(os.environ, {"A": "value"}, clear=True)
@mock.patch(f"{NATIVEAPP_MODULE}.cc.warning")
def test_no_resolve_and_warning_in_version_1(warning_mock):
definition = {
"definition_version": "1",
"native_app": {"name": "test_source_<% ctx.env.A %>", "artifacts": []},
}
result = render_definition_template(definition, {}).project_context

assert result == {
"ctx": {
"definition_version": "1",
"native_app": {"name": "test_source_<% ctx.env.A %>", "artifacts": []},
"env": ProjectEnvironment({}, {}),
}
}
warning_mock.assert_called_once_with(
"Ignoring template pattern in project definition file. "
"Update 'definition_version' to 1.1 or later in snowflake.yml to enable template expansion."
)


@mock.patch.dict(os.environ, {"A": "value"}, clear=True)
@mock.patch(f"{NATIVEAPP_MODULE}.cc.warning")
def test_partial_invalid_template_in_version_1(warning_mock):
definition = {
"definition_version": "1",
"native_app": {"name": "test_source_<% ctx.env.A", "artifacts": []},
}
result = render_definition_template(definition, {}).project_context

assert result == {
"ctx": {
"definition_version": "1",
"native_app": {"name": "test_source_<% ctx.env.A", "artifacts": []},
"env": ProjectEnvironment({}, {}),
}
}
# we still want to warn if there was an incorrect attempt to use templating
warning_mock.assert_called_once_with(
"Ignoring template pattern in project definition file. "
"Update 'definition_version' to 1.1 or later in snowflake.yml to enable template expansion."
)


@mock.patch.dict(os.environ, {"A": "value"}, clear=True)
@mock.patch(f"{NATIVEAPP_MODULE}.cc.warning")
def test_no_warning_in_version_1_1(warning_mock):
definition = {
"definition_version": "1.1",
"native_app": {"name": "test_source_<% ctx.env.A %>", "artifacts": []},
}
result = render_definition_template(definition, {}).project_context

assert result == {
"ctx": {
"definition_version": "1.1",
"native_app": {"name": "test_source_value", "artifacts": []},
"env": ProjectEnvironment({}, {}),
}
}
warning_mock.assert_not_called()


@mock.patch.dict(os.environ, {"A": "value"}, clear=True)
def test_invalid_template_in_version_1_1():
definition = {
"definition_version": "1.1",
"native_app": {"name": "test_source_<% ctx.env.A", "artifacts": []},
}
with pytest.raises(InvalidTemplate) as err:
render_definition_template(definition, {})

assert err.value.message.startswith(
"Error parsing template from project definition file. "
"Value: 'test_source_<% ctx.env.A'. "
"Error: unexpected end of template, expected 'end of print statement'."
)


@mock.patch.dict(os.environ, {}, clear=True)
def test_resolve_variables_in_project_cross_project_dependencies():
definition = {
Expand Down

0 comments on commit 3fcf12a

Please sign in to comment.