Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -153,12 +153,15 @@ jobs:
run: echo "VERSION=${GITHUB_REF/refs\/tags\//}" >> "$GITHUB_OUTPUT"
- name: Create a tag on ZenML Cloud plugins repo
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
VERSION: ${{ steps.get_version.outputs.VERSION }}
SHA: ${{ steps.get_sha.outputs.sha }}
with:
github-token: ${{ secrets.CLOUD_PLUGINS_REPO_PAT }}
script: |-
await github.rest.git.createRef({
owner: 'zenml-io',
repo: 'zenml-cloud-plugins',
ref: 'refs/tags/${{ steps.get_version.outputs.VERSION }}',
sha: '${{ steps.get_sha.outputs.sha }}'
ref: `refs/tags/${process.env.VERSION}`,
sha: process.env.SHA
})
11 changes: 4 additions & 7 deletions .github/zizmor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ rules:
# These are ZenML's own templates and are intentionally unpinned
unpinned-uses:
ignore:
- update-templates-to-examples.yml:44 # zenml-io/template-e2e-batch
- update-templates-to-examples.yml:46 # zenml-io/template-e2e-batch
- update-templates-to-examples.yml:120 # zenml-io/template-nlp
- update-templates-to-examples.yml:195 # zenml-io/template-starter
- update-templates-to-examples.yml:272 # zenml-io/template-llm-finetuning
- update-templates-to-examples.yml:193 # zenml-io/template-starter
- update-templates-to-examples.yml:268 # zenml-io/template-llm-finetuning
- weekly-agent-pipelines-test.yml:101 # Ilshidur/action-discord uses @master, can't be SHA-pinned

# excessive-permissions: These workflows use default permissions.
Expand Down Expand Up @@ -48,12 +48,9 @@ rules:
disable: true

# Detect dangerous template expansions
# Ignores: Low-confidence findings for step outputs used in github-script
# (step outputs within same workflow are trusted)
# Ignores: Low-confidence findings for trusted values used in github-script
template-injection:
ignore:
- release.yml:169 # VERSION from step output
- release.yml:170 # sha from step output
- snack-it.yml:352 # issue_number from step output
- snack-it.yml:353 # continuation of above
- snack-it.yml:354 # project_added from step output
Expand Down
1 change: 1 addition & 0 deletions src/zenml/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2591,3 +2591,4 @@ def my_pipeline(...):
from zenml.cli.project import * # noqa
from zenml.cli.tag import * # noqa
from zenml.cli.trigger import * # noqa
from zenml.cli.webhook_integration import * # noqa
185 changes: 185 additions & 0 deletions src/zenml/cli/webhook_integration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
# Copyright (c) ZenML GmbH 2026. All Rights Reserved.
#
# 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:
#
# https://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.
"""CLI commands for webhooks."""

from typing import Any

import click

from zenml.cli import utils as cli_utils
from zenml.cli.cli import TagGroup, cli
from zenml.cli.utils import OutputFormat, list_options
from zenml.client import Client
from zenml.console import console
from zenml.enums import CliCategories, WebhookType
from zenml.models import WebhookIntegrationFilter


@cli.group("webhook", cls=TagGroup, tag=CliCategories.MANAGEMENT_TOOLS)
def webhook() -> None:
"""Manage external webhook intake endpoints."""


@webhook.command("create")
@click.argument("name", type=str)
@click.option(
"--type",
"webhook_type",
type=click.Choice([value.value for value in WebhookType]),
required=True,
)
@click.option(
"--secret",
type=str,
default=None,
help="Set a signing secret.",
)
@click.option("--inactive", is_flag=True, default=False)
def create_webhook(
name: str,
webhook_type: str,
secret: str | None,
inactive: bool,
) -> None:
"""Create a webhook.

Args:
name: The webhook name.
webhook_type: The webhook provider type.
secret: An optional user-supplied signing secret.
inactive: Whether to create the webhook inactive.
"""
result = Client().create_webhook(
name=name,
webhook_type=WebhookType(webhook_type),
active=not inactive,
secret=secret,
)
cli_utils.print_pydantic_model(
title=f"Webhook '{name}'",
model=result.webhook,
)
if result.secret is not None:
cli_utils.declare(
f"Signing secret: {result.secret.get_secret_value()}"
)


@webhook.command("describe")
@click.argument("name_or_id", type=str)
def describe_webhook(name_or_id: str) -> None:
"""Describe a webhook.

Args:
name_or_id: The webhook name or ID.
"""
integration = Client().get_webhook(name_or_id)
cli_utils.print_pydantic_model(
title=f"Webhook '{integration.name}'",
model=integration,
)
cli_utils.declare(f"Endpoint path: {integration.endpoint_path}")


@webhook.command("list")
@list_options(
WebhookIntegrationFilter,
default_columns=["id", "name", "webhook_type", "active"],
)
def list_webhooks(
columns: str, output_format: OutputFormat, **kwargs: Any
) -> None:
"""List webhooks.

Args:
columns: The columns to display.
output_format: The output format.
**kwargs: The webhook filters.
"""
with console.status("Listing webhooks...\n"):
integrations = Client().list_webhooks(**kwargs)
cli_utils.print_page(
integrations,
columns,
output_format,
empty_message="No webhooks found for this filter.",
)


@webhook.command("update")
@click.argument("name_or_id", type=str)
@click.option("--name", "new_name", type=str, default=None)
@click.option(
"--active/--inactive", "active", default=None, help="Set active state."
)
def update_webhook(
name_or_id: str,
new_name: str | None,
active: bool | None,
) -> None:
"""Update a webhook.

Args:
name_or_id: The webhook name or ID.
new_name: The new webhook name.
active: The new active state.
"""
integration = Client().update_webhook(
name_id_or_prefix=name_or_id,
name=new_name,
active=active,
)
cli_utils.print_pydantic_model(
title=f"Webhook '{integration.name}'",
model=integration,
)


@webhook.command("rotate-secret")
@click.argument("name_or_id", type=str)
@click.option(
"--secret",
type=str,
default=None,
help="Set an optional direct replacement secret.",
)
def rotate_webhook_secret(name_or_id: str, secret: str | None) -> None:
"""Rotate a webhook signing secret.

Args:
name_or_id: The webhook name or ID.
secret: An optional direct replacement secret.
"""
result = Client().rotate_webhook_secret(
name_id_or_prefix=name_or_id, secret=secret
)
cli_utils.declare(f"Signing secret: {result.secret.get_secret_value()}")


@webhook.command("delete")
@click.argument("name_or_id", type=str)
@click.option("--yes", "confirmed", is_flag=True)
def delete_webhook(name_or_id: str, confirmed: bool) -> None:
"""Delete a webhook.

Args:
name_or_id: The webhook name or ID.
confirmed: Whether to skip the confirmation prompt.
"""
if not confirmed and not cli_utils.confirmation(
f"Delete webhook `{name_or_id}`?"
):
return
Client().delete_webhook(name_or_id)
cli_utils.declare(f"Deleted webhook `{name_or_id}`.")
Loading
Loading