mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-20 16:02:19 +00:00
d6c642b170
* feat: add Azure Event Grid triggers (EE)
Introduces a new enterprise trigger kind `azure` that supports three
modes via a single unified trigger type:
- basic_push: Azure Event Grid basic — custom topics, system topics
(Storage, Resource Manager, Key Vault, etc.), domains (push only)
- namespace_push: Event Grid Namespace topics (CloudEvents over HTTP push)
- namespace_pull: Event Grid Namespace topics (HTTP pull with lock-token
ack/reject for dead-lettering)
Auth uses a Service Principal resource (tenant_id, client_id,
client_secret, subscription_id). Subscriptions are created in
CloudEvents 1.0 schema so the push webhook handler and the pull listener
share one payload parser.
Backend
- New crate `windmill-trigger-azure` (OSS stubs + EE impl symlinked from
windmill-ee-private)
- Migration `azure_trigger` table with CHECK constraints enforcing
mode/columns coherence
- `TriggerKind::Azure`, `JobTriggerKind::Azure`,
`DeployedObject::AzureTrigger` variants
- Push route `/api/azure/w/{workspace}/*path` handles classic
Event Grid SubscriptionValidation handshake and CloudEvents 1.0
abuse-protection OPTIONS handshake
- Optional inbound JWT validation (audience check only for v1)
- Feature flag `azure_trigger` propagated through windmill-api,
windmill-store (resource helper), and added to ee_core
Frontend
- `triggers/azure/` editor with mode toggle (basic/namespace-push/
namespace-pull) and per-mode config (topic ARM id / namespace +
topic name / subscription / filters / push auth / pull options)
- Registered in icon map, display names, save functions, badge,
wrapper, editor, add-trigger menu
OpenAPI
- `AzureTrigger`, `AzureTriggerData`, `AzureMode`,
`AzureSubscriptionMode`, `AzureDeliveryConfig`, `TestAzureConnection`
schemas; `/azure_triggers/*` endpoints; client regenerated
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to eaa7c3a9cb37a9ccc93f10a2535d929365acd2d8
This commit updates the EE repository reference after PR #541 was merged in windmill-ee-private.
Previous ee-repo-ref: 9689014e8c12c36c1059fd8fa5758d550b8b8bc9
New ee-repo-ref: eaa7c3a9cb37a9ccc93f10a2535d929365acd2d8
Automated by sync-ee-ref workflow.
* feat(azure-trigger): secret-auth push, ARM discovery, capture isolation, CLI + parity
Frontend:
- Split mode selector into Namespace/Basic + Pull/Push
- ARM resource dropdowns (namespaces, Basic topics, namespace topics)
populated from the service principal; cascade with stale-selection
reset on SP / edition change
- Remove stale authenticate toggle + audience input (server-managed
push_auth_config has replaced them)
- Azure listing page: "Create from template" button; "Also delete Azure
subscription" toggle in the delete modal; simplified trigger label
falling back to path
- AzureCapture.svelte: "Test subscription name" with -wm-capture suffix
- CompareWorkspaces.svelte: wire Azure for fork/compare
- Drop Trigger-deployed/event-loss warning (capture subscription is
isolated with -wm-capture)
Backend:
- Shared-secret push auth (see EE crate for detail)
- JSONB push_auth_config column (renamed from delivery_config), #[serde(skip)]
so clients/CLI/exports never see it
- Drop redundant enabled column; mode supersedes
- Azure capture infra: AzureTriggerConfig + set_azure_trigger_config +
azure_payload route + TriggerKind::Azure arm; PT15M queue TTL on
capture subscriptions so they bound storage after tab close
- Granular ACLs, users offboarding, trash, git-sync deployed-object:
all include azure_trigger
CLI:
- Add azure to TRIGGER_TYPES, pushObj dispatch, getTypeStrFromPath,
trigger commands (get/update/create/list/template), sync delete
switch + regex; e2e test for `trigger new --kind azure`
- system_prompts: SCHEMA_MAPPINGS + schema_names include AzureTrigger;
auto-generated/* regenerated
Skill:
- .claude/skills/adding-a-trigger/ checklist covering every file that
needs editing when wiring a new trigger type (learned from this PR)
ee-repo-ref bumped to b0e490cbf3724b7b64c6a5b010e3bdf24acd873c.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(azure-trigger): ci — ShareModal Kind + regenerated system_prompts
- frontend/src/lib/components/ShareModal.svelte: add 'azure_trigger'
to the Kind type so the listing page's "Permissions" action compiles
(ts2345 — caught by npm_check on CI, missed by fast-check locally).
- system_prompts/auto-generated/: regenerate to drop the stale
delivery_config / AzureDeliveryConfig fields from the Azure schema
(check-freshness on CI).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(azure-trigger): use workspace constant_time_eq crate
Drop hand-rolled constant-time compare in favour of the workspace
constant_time_eq crate (same one used by http_trigger_auth).
ee-repo-ref bumped to 9659382d47286e7f7f66d01b6f5dd8d4ed34848b.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(azure-trigger): pass placeholder + disabled via inputProps
`TextInput`'s `placeholder` and `disabled` go through its `inputProps`
prop — CI's `npm run check` caught the stale top-level passing that
`npm run check:fast` missed. Align with the DefaultEmailConfigSection
pattern.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(azure-trigger): correct LATEST_GIT_SYNC_SCRIPT_PATH version to 28213
The hub deploy of the azure-aware sync-script is version 28213, not
28214. Backend was pinning a non-existent hub script, which broke the
git_sync_e2e suite (every deploy's sync step 404'd).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(azure-trigger): add azure_triggers to token scope selector + skill
- windmill-api/src/token.rs: `build_trigger_scope_domains` was missing
`("azure_triggers", "Azure Event Grid")`, so the CreateToken UI's scope
selector didn't surface azure_triggers:read/write. Backend already had
`ScopeDomain::AzureTriggers` wired (scopes.rs), this just exposes it.
- .claude/skills/adding-a-trigger/SKILL.md: capture both scope-related
files under the hardcoded-arrays section so future triggers don't miss
the UI surface.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(adding-a-trigger-skill): clarify token.rs scope effect
Not a regression — nothing was working before. Skipping TRIGGER_DOMAINS
just means the scope works via API/CLI but has no UI checkbox.
* docs(adding-a-trigger-skill): trim token.rs bullet
* fix(azure-trigger): regen openapi-deref + swap textarea for TextInput
- Run build_openapi.sh to regenerate openapi-deref.{yaml,json} with the
12 azure_triggers paths + schemas. These files are served by the
runtime (include_str! in windmill-api/src/lib.rs) to external SDK
consumers; without this regen the new endpoints wouldn't be advertised.
- Replace the raw <textarea> for event type filters with the
design-system TextInput in textarea mode (frontend/CLAUDE.md bans raw
HTML elements).
Addresses cubic + claude PR review items.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
386 lines
15 KiB
Rust
386 lines
15 KiB
Rust
/*
|
|
* Author: Ruben Fiszel
|
|
* Copyright: Windmill Labs, Inc 2022
|
|
* This file and its contents are licensed under the AGPLv3 License.
|
|
* Please see the included NOTICE for copyright information and
|
|
* LICENSE-AGPL for a copy of the license.
|
|
*/
|
|
|
|
use windmill_common::{scripts::ScriptHash, DB};
|
|
|
|
#[cfg(feature = "private")]
|
|
pub mod git_sync_ee;
|
|
pub mod git_sync_oss;
|
|
|
|
#[cfg(feature = "private")]
|
|
pub use git_sync_ee::{handle_deployment_metadata, handle_fork_branch_creation};
|
|
|
|
#[cfg(not(feature = "private"))]
|
|
pub use git_sync_oss::{handle_deployment_metadata, handle_fork_branch_creation};
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub enum DeployedObject {
|
|
Script { hash: ScriptHash, path: String, parent_path: Option<String> },
|
|
Flow { path: String, parent_path: Option<String>, version: i64 },
|
|
App { path: String, version: i64, parent_path: Option<String> },
|
|
RawApp { path: String, version: i64, parent_path: Option<String> },
|
|
Folder { path: String },
|
|
Resource { path: String, parent_path: Option<String> },
|
|
Variable { path: String, parent_path: Option<String> },
|
|
Schedule { path: String },
|
|
ResourceType { path: String },
|
|
User { email: String },
|
|
Group { name: String },
|
|
HttpTrigger { path: String, parent_path: Option<String> },
|
|
WebsocketTrigger { path: String, parent_path: Option<String> },
|
|
KafkaTrigger { path: String, parent_path: Option<String> },
|
|
NatsTrigger { path: String, parent_path: Option<String> },
|
|
PostgresTrigger { path: String, parent_path: Option<String> },
|
|
MqttTrigger { path: String, parent_path: Option<String> },
|
|
SqsTrigger { path: String, parent_path: Option<String> },
|
|
GcpTrigger { path: String, parent_path: Option<String> },
|
|
AzureTrigger { path: String, parent_path: Option<String> },
|
|
EmailTrigger { path: String, parent_path: Option<String> },
|
|
Settings { setting_type: String },
|
|
Key { key_type: String },
|
|
WorkspaceDependencies { path: String },
|
|
}
|
|
|
|
impl DeployedObject {
|
|
pub fn get_path(&self) -> String {
|
|
match self {
|
|
DeployedObject::Script { path, .. } => path.to_owned(),
|
|
DeployedObject::Flow { path, .. } => path.to_owned(),
|
|
DeployedObject::App { path, .. } => path.to_owned(),
|
|
DeployedObject::RawApp { path, .. } => path.to_owned(),
|
|
DeployedObject::Folder { path, .. } => path.to_owned(),
|
|
DeployedObject::Resource { path, .. } => path.to_owned(),
|
|
DeployedObject::Variable { path, .. } => path.to_owned(),
|
|
DeployedObject::Schedule { path, .. } => path.to_owned(),
|
|
DeployedObject::ResourceType { path, .. } => path.to_owned(),
|
|
DeployedObject::User { email } => format!("users/{email}"),
|
|
DeployedObject::Group { name } => format!("groups/{name}"),
|
|
DeployedObject::HttpTrigger { path, .. } => path.to_owned(),
|
|
DeployedObject::WebsocketTrigger { path, .. } => path.to_owned(),
|
|
DeployedObject::KafkaTrigger { path, .. } => path.to_owned(),
|
|
DeployedObject::NatsTrigger { path, .. } => path.to_owned(),
|
|
DeployedObject::PostgresTrigger { path, .. } => path.to_owned(),
|
|
DeployedObject::MqttTrigger { path, .. } => path.to_owned(),
|
|
DeployedObject::SqsTrigger { path, .. } => path.to_owned(),
|
|
DeployedObject::GcpTrigger { path, .. } => path.to_owned(),
|
|
DeployedObject::AzureTrigger { path, .. } => path.to_owned(),
|
|
DeployedObject::EmailTrigger { path, .. } => path.to_owned(),
|
|
DeployedObject::Settings { .. } => "settings.yaml".to_string(),
|
|
DeployedObject::Key { .. } => "encryption_key.yaml".to_string(),
|
|
DeployedObject::WorkspaceDependencies { path, .. } => path.to_owned(),
|
|
}
|
|
}
|
|
|
|
pub fn get_ignore_regex_filter(&self) -> bool {
|
|
match self {
|
|
Self::User { .. }
|
|
| Self::Group { .. }
|
|
| Self::ResourceType { .. }
|
|
| Self::Settings { .. }
|
|
| Self::Key { .. }
|
|
| Self::WorkspaceDependencies { .. } => true,
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
pub fn get_parent_path(&self) -> Option<String> {
|
|
match self {
|
|
DeployedObject::Script { parent_path, .. } => parent_path.to_owned(),
|
|
DeployedObject::Flow { parent_path, .. } => parent_path.to_owned(),
|
|
DeployedObject::App { parent_path, .. } => parent_path.to_owned(),
|
|
DeployedObject::RawApp { parent_path, .. } => parent_path.to_owned(),
|
|
DeployedObject::Folder { .. } => None,
|
|
DeployedObject::Resource { parent_path, .. } => parent_path.to_owned(),
|
|
DeployedObject::Variable { parent_path, .. } => parent_path.to_owned(),
|
|
DeployedObject::Schedule { .. } => None,
|
|
DeployedObject::ResourceType { .. } => None,
|
|
DeployedObject::User { .. } => None,
|
|
DeployedObject::Group { .. } => None,
|
|
DeployedObject::HttpTrigger { parent_path, .. } => parent_path.to_owned(),
|
|
DeployedObject::WebsocketTrigger { parent_path, .. } => parent_path.to_owned(),
|
|
DeployedObject::KafkaTrigger { parent_path, .. } => parent_path.to_owned(),
|
|
DeployedObject::NatsTrigger { parent_path, .. } => parent_path.to_owned(),
|
|
DeployedObject::PostgresTrigger { parent_path, .. } => parent_path.to_owned(),
|
|
DeployedObject::MqttTrigger { parent_path, .. } => parent_path.to_owned(),
|
|
DeployedObject::SqsTrigger { parent_path, .. } => parent_path.to_owned(),
|
|
DeployedObject::GcpTrigger { parent_path, .. } => parent_path.to_owned(),
|
|
DeployedObject::AzureTrigger { parent_path, .. } => parent_path.to_owned(),
|
|
DeployedObject::EmailTrigger { parent_path, .. } => parent_path.to_owned(),
|
|
DeployedObject::Settings { .. } => None,
|
|
DeployedObject::Key { .. } => None,
|
|
DeployedObject::WorkspaceDependencies { .. } => None,
|
|
}
|
|
}
|
|
|
|
pub fn get_kind(&self) -> String {
|
|
match self {
|
|
DeployedObject::Script { .. } => "script",
|
|
DeployedObject::Flow { .. } => "flow",
|
|
DeployedObject::App { .. } => "app",
|
|
DeployedObject::RawApp { .. } => "raw_app",
|
|
DeployedObject::Folder { .. } => "folder",
|
|
DeployedObject::Resource { .. } => "resource",
|
|
DeployedObject::Variable { .. } => "variable",
|
|
DeployedObject::Schedule { .. } => "schedule",
|
|
DeployedObject::ResourceType { .. } => "resource_type",
|
|
DeployedObject::User { .. } => "user",
|
|
DeployedObject::Group { .. } => "group",
|
|
DeployedObject::HttpTrigger { .. } => "http_trigger",
|
|
DeployedObject::WebsocketTrigger { .. } => "websocket_trigger",
|
|
DeployedObject::KafkaTrigger { .. } => "kafka_trigger",
|
|
DeployedObject::NatsTrigger { .. } => "nats_trigger",
|
|
DeployedObject::PostgresTrigger { .. } => "postgres_trigger",
|
|
DeployedObject::MqttTrigger { .. } => "mqtt_trigger",
|
|
DeployedObject::SqsTrigger { .. } => "sqs_trigger",
|
|
DeployedObject::GcpTrigger { .. } => "gcp_trigger",
|
|
DeployedObject::AzureTrigger { .. } => "azure_trigger",
|
|
DeployedObject::EmailTrigger { .. } => "email_trigger",
|
|
DeployedObject::Settings { .. } => "settings",
|
|
DeployedObject::Key { .. } => "key",
|
|
DeployedObject::WorkspaceDependencies { .. } => "workspace_dependencies",
|
|
}
|
|
.to_string()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use windmill_common::scripts::ScriptHash;
|
|
|
|
// --- DeployedObject::get_path tests ---
|
|
|
|
#[test]
|
|
fn test_get_path_script() {
|
|
let obj = DeployedObject::Script {
|
|
hash: ScriptHash(123),
|
|
path: "f/folder/script".to_string(),
|
|
parent_path: None,
|
|
};
|
|
assert_eq!(obj.get_path(), "f/folder/script");
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_path_flow() {
|
|
let obj = DeployedObject::Flow {
|
|
path: "f/folder/flow".to_string(),
|
|
parent_path: Some("f/folder/old_flow".to_string()),
|
|
version: 1,
|
|
};
|
|
assert_eq!(obj.get_path(), "f/folder/flow");
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_path_user() {
|
|
let obj = DeployedObject::User { email: "user@example.com".to_string() };
|
|
assert_eq!(obj.get_path(), "users/user@example.com");
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_path_group() {
|
|
let obj = DeployedObject::Group { name: "admins".to_string() };
|
|
assert_eq!(obj.get_path(), "groups/admins");
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_path_settings() {
|
|
let obj = DeployedObject::Settings { setting_type: "error_handler".to_string() };
|
|
assert_eq!(obj.get_path(), "settings.yaml");
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_path_key() {
|
|
let obj = DeployedObject::Key { key_type: "encryption".to_string() };
|
|
assert_eq!(obj.get_path(), "encryption_key.yaml");
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_path_workspace_dependencies() {
|
|
let obj = DeployedObject::WorkspaceDependencies {
|
|
path: "workspace-dependencies/python".to_string(),
|
|
};
|
|
assert_eq!(obj.get_path(), "workspace-dependencies/python");
|
|
}
|
|
|
|
// --- DeployedObject::get_ignore_regex_filter tests ---
|
|
|
|
#[test]
|
|
fn test_ignore_regex_filter_user() {
|
|
let obj = DeployedObject::User { email: "user@example.com".to_string() };
|
|
assert!(obj.get_ignore_regex_filter());
|
|
}
|
|
|
|
#[test]
|
|
fn test_ignore_regex_filter_group() {
|
|
let obj = DeployedObject::Group { name: "admins".to_string() };
|
|
assert!(obj.get_ignore_regex_filter());
|
|
}
|
|
|
|
#[test]
|
|
fn test_ignore_regex_filter_resource_type() {
|
|
let obj = DeployedObject::ResourceType { path: "postgresql".to_string() };
|
|
assert!(obj.get_ignore_regex_filter());
|
|
}
|
|
|
|
#[test]
|
|
fn test_ignore_regex_filter_settings() {
|
|
let obj = DeployedObject::Settings { setting_type: "error_handler".to_string() };
|
|
assert!(obj.get_ignore_regex_filter());
|
|
}
|
|
|
|
#[test]
|
|
fn test_ignore_regex_filter_key() {
|
|
let obj = DeployedObject::Key { key_type: "encryption".to_string() };
|
|
assert!(obj.get_ignore_regex_filter());
|
|
}
|
|
|
|
#[test]
|
|
fn test_ignore_regex_filter_workspace_dependencies() {
|
|
let obj = DeployedObject::WorkspaceDependencies {
|
|
path: "workspace-dependencies/python".to_string(),
|
|
};
|
|
assert!(obj.get_ignore_regex_filter());
|
|
}
|
|
|
|
#[test]
|
|
fn test_ignore_regex_filter_script() {
|
|
let obj = DeployedObject::Script {
|
|
hash: ScriptHash(123),
|
|
path: "f/folder/script".to_string(),
|
|
parent_path: None,
|
|
};
|
|
assert!(!obj.get_ignore_regex_filter());
|
|
}
|
|
|
|
#[test]
|
|
fn test_ignore_regex_filter_flow() {
|
|
let obj = DeployedObject::Flow {
|
|
path: "f/folder/flow".to_string(),
|
|
parent_path: None,
|
|
version: 1,
|
|
};
|
|
assert!(!obj.get_ignore_regex_filter());
|
|
}
|
|
|
|
// --- DeployedObject::get_parent_path tests ---
|
|
|
|
#[test]
|
|
fn test_get_parent_path_script_with_parent() {
|
|
let obj = DeployedObject::Script {
|
|
hash: ScriptHash(123),
|
|
path: "f/folder/script".to_string(),
|
|
parent_path: Some("f/folder/old_script".to_string()),
|
|
};
|
|
assert_eq!(
|
|
obj.get_parent_path(),
|
|
Some("f/folder/old_script".to_string())
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_parent_path_script_without_parent() {
|
|
let obj = DeployedObject::Script {
|
|
hash: ScriptHash(123),
|
|
path: "f/folder/script".to_string(),
|
|
parent_path: None,
|
|
};
|
|
assert_eq!(obj.get_parent_path(), None);
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_parent_path_folder() {
|
|
let obj = DeployedObject::Folder { path: "f/folder".to_string() };
|
|
assert_eq!(obj.get_parent_path(), None);
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_parent_path_workspace_dependencies() {
|
|
let obj = DeployedObject::WorkspaceDependencies {
|
|
path: "workspace-dependencies/python".to_string(),
|
|
};
|
|
assert_eq!(obj.get_parent_path(), None);
|
|
}
|
|
|
|
// --- DeployedObject::get_kind tests ---
|
|
|
|
#[test]
|
|
fn test_get_kind_script() {
|
|
let obj = DeployedObject::Script {
|
|
hash: ScriptHash(123),
|
|
path: "test".to_string(),
|
|
parent_path: None,
|
|
};
|
|
assert_eq!(obj.get_kind(), "script");
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_kind_flow() {
|
|
let obj = DeployedObject::Flow { path: "test".to_string(), parent_path: None, version: 1 };
|
|
assert_eq!(obj.get_kind(), "flow");
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_kind_app() {
|
|
let obj = DeployedObject::App { path: "test".to_string(), version: 1, parent_path: None };
|
|
assert_eq!(obj.get_kind(), "app");
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_kind_workspace_dependencies() {
|
|
let obj = DeployedObject::WorkspaceDependencies {
|
|
path: "workspace-dependencies/python".to_string(),
|
|
};
|
|
assert_eq!(obj.get_kind(), "workspace_dependencies");
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_kind_all_triggers() {
|
|
assert_eq!(
|
|
DeployedObject::HttpTrigger { path: "t".to_string(), parent_path: None }.get_kind(),
|
|
"http_trigger"
|
|
);
|
|
assert_eq!(
|
|
DeployedObject::WebsocketTrigger { path: "t".to_string(), parent_path: None }
|
|
.get_kind(),
|
|
"websocket_trigger"
|
|
);
|
|
assert_eq!(
|
|
DeployedObject::KafkaTrigger { path: "t".to_string(), parent_path: None }.get_kind(),
|
|
"kafka_trigger"
|
|
);
|
|
assert_eq!(
|
|
DeployedObject::NatsTrigger { path: "t".to_string(), parent_path: None }.get_kind(),
|
|
"nats_trigger"
|
|
);
|
|
assert_eq!(
|
|
DeployedObject::PostgresTrigger { path: "t".to_string(), parent_path: None }.get_kind(),
|
|
"postgres_trigger"
|
|
);
|
|
assert_eq!(
|
|
DeployedObject::MqttTrigger { path: "t".to_string(), parent_path: None }.get_kind(),
|
|
"mqtt_trigger"
|
|
);
|
|
assert_eq!(
|
|
DeployedObject::SqsTrigger { path: "t".to_string(), parent_path: None }.get_kind(),
|
|
"sqs_trigger"
|
|
);
|
|
assert_eq!(
|
|
DeployedObject::GcpTrigger { path: "t".to_string(), parent_path: None }.get_kind(),
|
|
"gcp_trigger"
|
|
);
|
|
assert_eq!(
|
|
DeployedObject::AzureTrigger { path: "t".to_string(), parent_path: None }.get_kind(),
|
|
"azure_trigger"
|
|
);
|
|
assert_eq!(
|
|
DeployedObject::EmailTrigger { path: "t".to_string(), parent_path: None }.get_kind(),
|
|
"email_trigger"
|
|
);
|
|
}
|
|
}
|