Skip to content

Commit

Permalink
Add release channels list support
Browse files Browse the repository at this point in the history
  • Loading branch information
sfc-gh-melnacouzi committed Dec 13, 2024
1 parent 24809a8 commit a2e711b
Show file tree
Hide file tree
Showing 8 changed files with 635 additions and 1 deletion.
1 change: 1 addition & 0 deletions RELEASE-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
* Add support for release channels feature in native app version creation/drop.
* `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=<channel>`
* Add ability to list release channels through `snow app release-channel list` command

## Fixes and improvements
* Fixed crashes with older x86_64 Intel CPUs.
Expand Down
4 changes: 4 additions & 0 deletions src/snowflake/cli/_plugins/nativeapp/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
from snowflake.cli._plugins.nativeapp.entities.application_package import (
ApplicationPackageEntityModel,
)
from snowflake.cli._plugins.nativeapp.release_channel.commands import (
app as release_channels_app,
)
from snowflake.cli._plugins.nativeapp.release_directive.commands import (
app as release_directives_app,
)
Expand Down Expand Up @@ -71,6 +74,7 @@
)
app.add_typer(versions_app)
app.add_typer(release_directives_app)
app.add_typer(release_channels_app)

log = logging.getLogger(__name__)

Expand Down
128 changes: 128 additions & 0 deletions src/snowflake/cli/_plugins/nativeapp/entities/application_package.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import json
import os
import re
from datetime import datetime
from pathlib import Path
from textwrap import dedent
from typing import Any, List, Literal, Optional, Set, Union
Expand Down Expand Up @@ -65,6 +66,7 @@
)
from snowflake.cli._plugins.workspace.context import ActionContext
from snowflake.cli.api.cli_global_context import span
from snowflake.cli.api.console.abc import AbstractConsole
from snowflake.cli.api.entities.common import (
EntityBase,
attach_spans_to_entity_actions,
Expand Down Expand Up @@ -134,6 +136,99 @@ def ensure_app_roles_is_a_set(
return application_roles


class ReleaseChannel(dict[str, Any]):
"""
Represents a release channel.
This class is a dictionary with the following keys:
- name: The name of the release channel.
- description: The description of the release channel.
- created_on: The timestamp when the release channel was created.
- updated_on: The timestamp when the release channel was last updated.
- targets: The target accounts for the release channel.
- versions: The versions added to the release channel.
"""

def __init__(self, data: dict[str, Any]):
targets = json.loads(str(data.get("targets"))) if data.get("targets") else {}
versions = json.loads(str(data.get("versions"))) if data.get("versions") else []

super().__init__(
{
"name": data.get("name") or "",
"description": data.get("description") or "",
"created_on": data.get("created_on"),
"updated_on": data.get("updated_on"),
"targets": targets,
"versions": versions,
}
)

@property
def name(self) -> str:
return self.get("name") # type: ignore

@property
def description(self) -> str:
return self.get("description") # type: ignore

@property
def created_on(self) -> Optional[datetime]:
return self.get("created_on") # type: ignore

@property
def updated_on(self) -> Optional[datetime]:
return self.get("updated_on") # type: ignore

@property
def targets(self) -> dict[str, Any]:
return self.get("targets") # type: ignore

@property
def versions(self) -> list[str]:
return self.get("versions") # type: ignore

def print_to_console(self, console: AbstractConsole) -> None:
"""
Prints the release channel details to the console.
"""
console.message(f"""[bold]{self.name}[/bold]""")
accounts_list: Optional[list[str]] = self.targets.get("accounts")
target_accounts = (
f"({', '.join(accounts_list)})"
if accounts_list is not None
else "ALL ACCOUNTS"
)

formatted_created_on = (
self.created_on.astimezone().strftime("%Y-%m-%d %H:%M:%S.%f %Z")
if self.created_on
else ""
)

formatted_updated_on = (
self.updated_on.astimezone().strftime("%Y-%m-%d %H:%M:%S.%f %Z")
if self.updated_on
else ""
)
with console.indented():
console.message(f"Description: {self.description}")
console.message(f"Versions: ({', '.join(self.versions)})")
console.message(f"Created on: {formatted_created_on}")
console.message(f"Updated on: {formatted_updated_on}")
console.message(f"Target accounts: {target_accounts}")

def matches_identifier(self, identifier: Optional[str]) -> bool:
"""
Checks if the release channel matches the provided identifier.
If the identifier is None, it matches everything.
"""
if identifier is None:
return True

return same_identifiers(self.name, identifier)


class ApplicationPackageChildField(UpdatableModel):
target: str = Field(title="The key of the entity to include in this package")
ensure_usable_by: Optional[EnsureUsableByField] = Field(
Expand Down Expand Up @@ -766,6 +861,39 @@ def action_release_directive_unset(
role=self.role,
)

def action_release_channel_list(
self,
action_ctx: ActionContext,
release_channel: Optional[str] = None,
*args,
**kwargs,
) -> list[ReleaseChannel]:
"""
Get all existing release channels for an application package.
If `release_channel` is provided, only the specified release channel is listed.
"""
console = self._workspace_ctx.console
available_channels = [
ReleaseChannel(channel)
for channel in get_snowflake_facade().show_release_channels(
self.name, self.role
)
]

filtered_channels = [
channel
for channel in available_channels
if channel.matches_identifier(release_channel)
]

if not filtered_channels:
console.message("No release channels found.")
else:
for channel in filtered_channels:
channel.print_to_console(console)

return filtered_channels

def _bundle(self, action_ctx: ActionContext = None):
model = self._entity_model
bundle_map = build_bundle(self.project_root, self.deploy_root, model.artifacts)
Expand Down
13 changes: 13 additions & 0 deletions src/snowflake/cli/_plugins/nativeapp/release_channel/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copyright (c) 2024 Snowflake Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
71 changes: 71 additions & 0 deletions src/snowflake/cli/_plugins/nativeapp/release_channel/commands.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Copyright (c) 2024 Snowflake Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import annotations

import logging
from typing import Optional

import typer
from snowflake.cli._plugins.nativeapp.v2_conversions.compat import (
force_project_definition_v2,
)
from snowflake.cli._plugins.workspace.manager import WorkspaceManager
from snowflake.cli.api.cli_global_context import get_cli_context
from snowflake.cli.api.commands.decorators import with_project_definition
from snowflake.cli.api.commands.snow_typer import SnowTyperFactory
from snowflake.cli.api.entities.common import EntityActions
from snowflake.cli.api.output.formats import OutputFormat
from snowflake.cli.api.output.types import (
CollectionResult,
CommandResult,
)

app = SnowTyperFactory(
name="release-channel",
help="Manages release channels of an application package",
)

log = logging.getLogger(__name__)


@app.command("list", requires_connection=True)
@with_project_definition()
@force_project_definition_v2()
def release_channel_list(
channel: Optional[str] = typer.Argument(
default=None,
show_default=False,
help="The release channel to list. If not provided, all release channels are listed.",
),
**options,
) -> CommandResult:
"""
Lists the release channels available for an application package.
"""

cli_context = get_cli_context()
ws = WorkspaceManager(
project_definition=cli_context.project_definition,
project_root=cli_context.project_root,
)
package_id = options["package_entity_id"]
channels = ws.perform_action(
package_id,
EntityActions.RELEASE_CHANNEL_LIST,
release_channel=channel,
)

if cli_context.output_format == OutputFormat.JSON:
return CollectionResult(channels)
Loading

0 comments on commit a2e711b

Please sign in to comment.