Skip to content

Commit 0ac74fa

Browse files
committed
[dg] adds dg plus branch-deployment command
1 parent 02a8ad8 commit 0ac74fa

9 files changed

Lines changed: 1334 additions & 0 deletions

File tree

python_modules/libraries/dagster-dg-cli/dagster_dg_cli/cli/plus/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import click
22
from dagster_dg_core.utils import DgClickGroup
33

4+
from dagster_dg_cli.cli.plus.branch_deployment import branch_deployment_group
45
from dagster_dg_cli.cli.plus.create import plus_create_group
56
from dagster_dg_cli.cli.plus.deploy import deploy_group
67
from dagster_dg_cli.cli.plus.login import login_command
@@ -11,6 +12,7 @@
1112
name="plus",
1213
cls=DgClickGroup,
1314
commands={
15+
"branch-deployment": branch_deployment_group,
1416
"create": plus_create_group,
1517
"login": login_command,
1618
"pull": plus_pull_group,
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
"""Branch deployment command group for Dagster+."""
2+
3+
import click
4+
from dagster_dg_core.utils import DgClickGroup
5+
6+
from dagster_dg_cli.cli.plus.branch_deployment.commands import (
7+
create_or_update_command,
8+
delete_command,
9+
)
10+
11+
12+
@click.group(name="branch-deployment", cls=DgClickGroup)
13+
def branch_deployment_group():
14+
"""Manage branch deployments in Dagster+.
15+
16+
Branch deployments are ephemeral environments that allow you to test code
17+
changes before merging to production. They are automatically created based
18+
on git branches and can include metadata about commits and pull requests.
19+
20+
Use these commands to create, update, and delete branch deployments.
21+
"""
22+
23+
24+
# Register commands
25+
branch_deployment_group.add_command(create_or_update_command)
26+
branch_deployment_group.add_command(delete_command)
Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
"""Commands for managing branch deployments in Dagster+."""
2+
3+
from pathlib import Path
4+
from typing import Optional
5+
6+
import click
7+
from dagster_cloud_cli.types import SnapshotBaseDeploymentCondition
8+
from dagster_dg_core.utils import DgClickCommand
9+
from dagster_dg_core.utils.telemetry import cli_telemetry_wrapper
10+
from dagster_shared.plus.config import DagsterPlusCliConfig
11+
12+
from dagster_dg_cli.utils.plus.git_utils import get_git_metadata_for_branch_deployment
13+
from dagster_dg_cli.utils.plus.gql_client import DagsterPlusGraphQLClient
14+
from dagster_dg_cli.utils.plus.gql_mutations import (
15+
create_or_update_branch_deployment,
16+
delete_branch_deployment,
17+
)
18+
19+
20+
def _get_organization(input_organization: Optional[str]) -> str:
21+
"""Get organization from input or config.
22+
23+
Args:
24+
input_organization: Organization from CLI flag (optional)
25+
26+
Returns:
27+
Organization name
28+
29+
Raises:
30+
click.UsageError: If organization not found
31+
"""
32+
organization = input_organization
33+
if not organization:
34+
plus_config = DagsterPlusCliConfig.get()
35+
organization = plus_config.organization
36+
37+
if not organization:
38+
raise click.UsageError(
39+
"Organization not specified. To specify an organization, use the --organization option "
40+
"or run `dg plus login`."
41+
)
42+
return organization
43+
44+
45+
@click.command(name="create-or-update", cls=DgClickCommand)
46+
@click.option(
47+
"--organization",
48+
help="Dagster+ organization name. If not set, defaults to the value set by `dg plus login`.",
49+
envvar="DAGSTER_CLOUD_ORGANIZATION",
50+
)
51+
@click.option(
52+
"--read-git-state",
53+
is_flag=True,
54+
help="Read commit metadata (hash, timestamp, author, message) from git automatically.",
55+
)
56+
@click.option(
57+
"--commit-hash",
58+
help="Git commit hash. Required if --read-git-state is not used.",
59+
)
60+
@click.option(
61+
"--timestamp",
62+
type=float,
63+
help="Commit timestamp in Unix time (seconds since epoch). Required if --read-git-state is not used.",
64+
)
65+
@click.option(
66+
"--commit-message",
67+
help="Commit message for the latest commit.",
68+
)
69+
@click.option(
70+
"--author-name",
71+
help="Author name for the latest commit.",
72+
)
73+
@click.option(
74+
"--author-email",
75+
help="Author email for the latest commit.",
76+
)
77+
@click.option(
78+
"--author-avatar-url",
79+
help="URL for the avatar of the commit author.",
80+
)
81+
@click.option(
82+
"--branch-url",
83+
help="URL to the branch in version control.",
84+
)
85+
@click.option(
86+
"--pull-request-url",
87+
"--code-review-url",
88+
help="URL to the pull request or merge request for this branch.",
89+
)
90+
@click.option(
91+
"--pull-request-status",
92+
help="Status of the pull request (e.g., 'open', 'merged', 'closed').",
93+
)
94+
@click.option(
95+
"--pull-request-number",
96+
"--code-review-id",
97+
help="Pull request or merge request number/ID.",
98+
)
99+
@click.option(
100+
"--base-deployment-name",
101+
help="Name of the deployment to use as the base deployment for comparison.",
102+
)
103+
@click.option(
104+
"--snapshot-base-condition",
105+
type=click.Choice([c.value for c in SnapshotBaseDeploymentCondition]),
106+
help=(
107+
"When to snapshot the base deployment for highlighting changes:\n"
108+
" - on-create: Snapshot when branch deployment is first created\n"
109+
" - on-update: Update snapshot every time branch deployment is updated"
110+
),
111+
)
112+
@cli_telemetry_wrapper
113+
def create_or_update_command(
114+
organization: Optional[str],
115+
read_git_state: bool,
116+
commit_hash: Optional[str],
117+
timestamp: Optional[float],
118+
commit_message: Optional[str],
119+
author_name: Optional[str],
120+
author_email: Optional[str],
121+
author_avatar_url: Optional[str],
122+
branch_url: Optional[str],
123+
pull_request_url: Optional[str],
124+
pull_request_status: Optional[str],
125+
pull_request_number: Optional[str],
126+
base_deployment_name: Optional[str],
127+
snapshot_base_condition: Optional[str],
128+
**global_options: object,
129+
) -> None:
130+
r"""Create or update a branch deployment with git metadata.
131+
132+
This command creates or updates a branch deployment in Dagster+ with metadata
133+
about the current git state. Branch deployments are ephemeral environments for
134+
testing code changes before merging to production.
135+
136+
The repository name and branch name are automatically detected from your git
137+
configuration. You can either use --read-git-state to automatically read commit
138+
metadata, or manually specify --commit-hash and --timestamp.
139+
140+
Examples:
141+
# Create/update with auto-detected git metadata
142+
$ dg plus branch-deployment create-or-update --read-git-state
143+
144+
# Create/update with manual commit info
145+
$ dg plus branch-deployment create-or-update \\
146+
--commit-hash abc123def456 \\
147+
--timestamp 1234567890
148+
149+
# Include PR information
150+
$ dg plus branch-deployment create-or-update \\
151+
--read-git-state \\
152+
--pull-request-url https://github.com/org/repo/pull/123 \\
153+
--pull-request-number 123 \\
154+
--pull-request-status open
155+
"""
156+
# Get organization (validates auth is configured)
157+
_get_organization(organization)
158+
159+
# Get git metadata (repo name, branch name, and optionally commit metadata)
160+
_, repo_name, branch_name, git_commit_metadata = get_git_metadata_for_branch_deployment(
161+
Path.cwd(), read_git_state
162+
)
163+
164+
# Validate commit info
165+
if read_git_state and git_commit_metadata:
166+
# Use git metadata
167+
final_commit_hash = str(git_commit_metadata["commit_hash"])
168+
final_timestamp = git_commit_metadata["timestamp"]
169+
# Use git metadata for author info if not explicitly provided
170+
if not commit_message:
171+
commit_message = git_commit_metadata.get("commit_message") # type: ignore
172+
if not author_name:
173+
author_name = git_commit_metadata.get("author_name") # type: ignore
174+
if not author_email:
175+
author_email = git_commit_metadata.get("author_email") # type: ignore
176+
else:
177+
# Use manually provided values
178+
final_commit_hash = commit_hash
179+
final_timestamp = timestamp
180+
181+
# Validate required fields
182+
if not final_commit_hash or final_timestamp is None:
183+
raise click.UsageError(
184+
"Must provide either --read-git-state flag, or both --commit-hash and --timestamp."
185+
)
186+
187+
# Type narrowing - after validation we know these are not None
188+
assert isinstance(final_commit_hash, str)
189+
assert isinstance(final_timestamp, (int, float))
190+
191+
# Convert snapshot_base_condition string to enum
192+
snapshot_enum = None
193+
if snapshot_base_condition:
194+
snapshot_enum = SnapshotBaseDeploymentCondition(snapshot_base_condition)
195+
196+
# Create GraphQL client
197+
plus_config = DagsterPlusCliConfig.get()
198+
client = DagsterPlusGraphQLClient.from_config(plus_config)
199+
200+
# Call the mutation
201+
click.echo(f"Creating/updating branch deployment for {repo_name}:{branch_name}...")
202+
deployment_name = create_or_update_branch_deployment(
203+
client=client,
204+
repo_name=repo_name,
205+
branch_name=branch_name,
206+
commit_hash=final_commit_hash,
207+
timestamp=float(final_timestamp),
208+
commit_message=commit_message,
209+
author_name=author_name,
210+
author_email=author_email,
211+
author_avatar_url=author_avatar_url,
212+
branch_url=branch_url,
213+
pull_request_url=pull_request_url,
214+
pull_request_status=pull_request_status,
215+
pull_request_number=pull_request_number,
216+
base_deployment_name=base_deployment_name,
217+
snapshot_base_condition=snapshot_enum,
218+
)
219+
220+
click.echo(f"✓ Branch deployment '{deployment_name}' created/updated successfully")
221+
222+
223+
@click.command(name="delete", cls=DgClickCommand)
224+
@click.argument("deployment", type=str)
225+
@click.option(
226+
"--organization",
227+
help="Dagster+ organization name. If not set, defaults to the value set by `dg plus login`.",
228+
envvar="DAGSTER_CLOUD_ORGANIZATION",
229+
)
230+
@cli_telemetry_wrapper
231+
def delete_command(
232+
deployment: str,
233+
organization: Optional[str],
234+
**global_options: object,
235+
) -> None:
236+
"""Delete a branch deployment by name.
237+
238+
This command deletes a branch deployment from Dagster+. Only branch deployments
239+
can be deleted - you cannot delete full deployments or prod deployments with
240+
this command.
241+
242+
Arguments:
243+
DEPLOYMENT: Name of the branch deployment to delete
244+
245+
Examples:
246+
# Delete a branch deployment
247+
$ dg plus branch-deployment delete my-feature-branch
248+
249+
# Delete with explicit organization
250+
$ dg plus branch-deployment delete my-feature-branch --organization myorg
251+
"""
252+
# Get organization (this also validates we have auth configured)
253+
_get_organization(organization)
254+
255+
# Create GraphQL client
256+
plus_config = DagsterPlusCliConfig.get()
257+
client = DagsterPlusGraphQLClient.from_config(plus_config)
258+
259+
# Delete the deployment
260+
click.echo(f"Deleting branch deployment '{deployment}'...")
261+
deployment_id = delete_branch_deployment(client, deployment)
262+
263+
click.echo(f"✓ Branch deployment '{deployment}' (ID: {deployment_id}) deleted successfully")

0 commit comments

Comments
 (0)