diff --git a/CLI.md b/CLI.md
index 27bf625b..bbf327b5 100644
--- a/CLI.md
+++ b/CLI.md
@@ -745,6 +745,1831 @@ Delete a watchlist:
secops watchlist delete --watchlist-id "abc-123-def"
```
+### Integration Management
+
+#### Marketplace Integrations
+
+List marketplace integrations:
+
+```bash
+# List all marketplace integration (returns dict with pagination metadata)
+secops integration marketplace list
+
+# List marketplace integration as a direct list (fetches all pages automatically)
+secops integration marketplace list --as-list
+```
+
+Get marketplace integration details:
+
+```bash
+secops integration marketplace get --integration-name "AWSSecurityHub"
+```
+
+Get marketplace integration diff between installed version and latest version:
+
+```bash
+secops integration marketplace diff --integration-name "AWSSecurityHub"
+```
+
+Install or update a marketplace integration:
+
+```bash
+# Install with default settings
+secops integration marketplace install --integration-name "AWSSecurityHub"
+
+# Install to staging environment and override any existing ontology mappings
+secops integration marketplace install --integration-name "AWSSecurityHub" --staging --override-mapping
+
+# Installing a currently installed integration with no specified version
+# number will update it to the latest version
+secops integration marketplace install --integration-name "AWSSecurityHub"
+
+# Or you can specify a specific version to install
+secops integration marketplace install --integration-name "AWSSecurityHub" --version "5.0"
+```
+
+Uninstall a marketplace integration:
+
+```bash
+secops integration marketplace uninstall --integration-name "AWSSecurityHub"
+```
+
+#### Integration Actions
+
+List integration actions:
+
+```bash
+# List all actions for an integration
+secops integration actions list --integration-name "MyIntegration"
+
+# List actions as a direct list (fetches all pages automatically)
+secops integration actions list --integration-name "MyIntegration" --as-list
+
+# List with pagination
+secops integration actions list --integration-name "MyIntegration" --page-size 50
+
+# List with filtering
+secops integration actions list --integration-name "MyIntegration" --filter-string "enabled = true"
+```
+
+Get action details:
+
+```bash
+secops integration actions get --integration-name "MyIntegration" --action-id "123"
+```
+
+Create a new action:
+
+```bash
+# Create a basic action with Python code
+secops integration actions create \
+ --integration-name "MyIntegration" \
+ --display-name "Send Alert" \
+ --code "def main(context): return {'status': 'success'}"
+
+# Create an async action
+secops integration actions create \
+ --integration-name "MyIntegration" \
+ --display-name "Async Task" \
+ --code "async def main(context): return await process()" \
+ --is-async
+
+# Create with description
+secops integration actions create \
+ --integration-name "MyIntegration" \
+ --display-name "My Action" \
+ --code "def main(context): return {}" \
+ --description "Action description"
+```
+
+> **Note:** When creating an action, the following default values are automatically applied:
+> - `timeout_seconds`: 300 (5 minutes)
+> - `enabled`: true
+> - `script_result_name`: "result"
+>
+> The `--code` parameter contains the Python script that will be executed by the action.
+
+Update an existing action:
+
+```bash
+# Update display name
+secops integration actions update \
+ --integration-name "MyIntegration" \
+ --action-id "123" \
+ --display-name "Updated Action Name"
+
+# Update code
+secops integration actions update \
+ --integration-name "MyIntegration" \
+ --action-id "123" \
+ --code "def main(context): return {'status': 'updated'}"
+
+# Update multiple fields with update mask
+secops integration actions update \
+ --integration-name "MyIntegration" \
+ --action-id "123" \
+ --display-name "New Name" \
+ --description "New description" \
+ --update-mask "displayName,description"
+```
+
+Delete an action:
+
+```bash
+secops integration actions delete --integration-name "MyIntegration" --action-id "123"
+```
+
+Test an action:
+
+```bash
+# Test an action to verify it executes correctly
+secops integration actions test --integration-name "MyIntegration" --action-id "123"
+```
+
+Get action template:
+
+```bash
+# Get synchronous action template
+secops integration actions template --integration-name "MyIntegration"
+
+# Get asynchronous action template
+secops integration actions template --integration-name "MyIntegration" --is-async
+```
+
+#### Action Revisions
+
+List action revisions:
+
+```bash
+# List all revisions for an action
+secops integration action-revisions list \
+ --integration-name "MyIntegration" \
+ --action-id "123"
+
+# List revisions as a direct list
+secops integration action-revisions list \
+ --integration-name "MyIntegration" \
+ --action-id "123" \
+ --as-list
+
+# List with pagination
+secops integration action-revisions list \
+ --integration-name "MyIntegration" \
+ --action-id "123" \
+ --page-size 10
+
+# List with filtering and ordering
+secops integration action-revisions list \
+ --integration-name "MyIntegration" \
+ --action-id "123" \
+ --filter-string 'version = "1.0"' \
+ --order-by "createTime desc"
+```
+
+Create a revision backup:
+
+```bash
+# Create revision with comment
+secops integration action-revisions create \
+ --integration-name "MyIntegration" \
+ --action-id "123" \
+ --comment "Backup before major refactor"
+
+# Create revision without comment
+secops integration action-revisions create \
+ --integration-name "MyIntegration" \
+ --action-id "123"
+```
+
+Rollback to a previous revision:
+
+```bash
+secops integration action-revisions rollback \
+ --integration-name "MyIntegration" \
+ --action-id "123" \
+ --revision-id "r456"
+```
+
+Delete an old revision:
+
+```bash
+secops integration action-revisions delete \
+ --integration-name "MyIntegration" \
+ --action-id "123" \
+ --revision-id "r789"
+```
+
+#### Integration Connectors
+
+List integration connectors:
+
+```bash
+# List all connectors for an integration
+secops integration connectors list --integration-name "MyIntegration"
+
+# List connectors as a direct list (fetches all pages automatically)
+secops integration connectors list --integration-name "MyIntegration" --as-list
+
+# List with pagination
+secops integration connectors list --integration-name "MyIntegration" --page-size 50
+
+# List with filtering
+secops integration connectors list --integration-name "MyIntegration" --filter-string "enabled = true"
+```
+
+Get connector details:
+
+```bash
+secops integration connectors get --integration-name "MyIntegration" --connector-id "c1"
+```
+
+Create a new connector:
+
+```bash
+secops integration connectors create \
+ --integration-name "MyIntegration" \
+ --display-name "Data Ingestion" \
+ --code "def fetch_data(context): return []"
+
+# Create with description and custom ID
+secops integration connectors create \
+ --integration-name "MyIntegration" \
+ --display-name "My Connector" \
+ --code "def fetch_data(context): return []" \
+ --description "Connector description" \
+ --connector-id "custom-connector-id"
+```
+
+Update an existing connector:
+
+```bash
+# Update display name
+secops integration connectors update \
+ --integration-name "MyIntegration" \
+ --connector-id "c1" \
+ --display-name "Updated Connector Name"
+
+# Update code
+secops integration connectors update \
+ --integration-name "MyIntegration" \
+ --connector-id "c1" \
+ --code "def fetch_data(context): return updated_data()"
+
+# Update multiple fields with update mask
+secops integration connectors update \
+ --integration-name "MyIntegration" \
+ --connector-id "c1" \
+ --display-name "New Name" \
+ --description "New description" \
+ --update-mask "displayName,description"
+```
+
+Delete a connector:
+
+```bash
+secops integration connectors delete --integration-name "MyIntegration" --connector-id "c1"
+```
+
+Test a connector:
+
+```bash
+secops integration connectors test --integration-name "MyIntegration" --connector-id "c1"
+```
+
+Get connector template:
+
+```bash
+secops integration connectors template --integration-name "MyIntegration"
+```
+
+#### Connector Revisions
+
+List connector revisions:
+
+```bash
+# List all revisions for a connector
+secops integration connector-revisions list \
+ --integration-name "MyIntegration" \
+ --connector-id "c1"
+
+# List revisions as a direct list
+secops integration connector-revisions list \
+ --integration-name "MyIntegration" \
+ --connector-id "c1" \
+ --as-list
+
+# List with pagination
+secops integration connector-revisions list \
+ --integration-name "MyIntegration" \
+ --connector-id "c1" \
+ --page-size 10
+
+# List with filtering and ordering
+secops integration connector-revisions list \
+ --integration-name "MyIntegration" \
+ --connector-id "c1" \
+ --filter-string 'version = "1.0"' \
+ --order-by "createTime desc"
+```
+
+Create a revision backup:
+
+```bash
+# Create revision with comment
+secops integration connector-revisions create \
+ --integration-name "MyIntegration" \
+ --connector-id "c1" \
+ --comment "Backup before field mapping changes"
+
+# Create revision without comment
+secops integration connector-revisions create \
+ --integration-name "MyIntegration" \
+ --connector-id "c1"
+```
+
+Rollback to a previous revision:
+
+```bash
+secops integration connector-revisions rollback \
+ --integration-name "MyIntegration" \
+ --connector-id "c1" \
+ --revision-id "r456"
+```
+
+Delete an old revision:
+
+```bash
+secops integration connector-revisions delete \
+ --integration-name "MyIntegration" \
+ --connector-id "c1" \
+ --revision-id "r789"
+```
+
+#### Connector Context Properties
+
+List connector context properties:
+
+```bash
+# List all properties for a connector context
+secops integration connector-context-properties list \
+ --integration-name "MyIntegration" \
+ --connector-id "c1" \
+ --context-id "mycontext"
+
+# List properties as a direct list
+secops integration connector-context-properties list \
+ --integration-name "MyIntegration" \
+ --connector-id "c1" \
+ --context-id "mycontext" \
+ --as-list
+
+# List with pagination
+secops integration connector-context-properties list \
+ --integration-name "MyIntegration" \
+ --connector-id "c1" \
+ --context-id "mycontext" \
+ --page-size 50
+
+# List with filtering
+secops integration connector-context-properties list \
+ --integration-name "MyIntegration" \
+ --connector-id "c1" \
+ --context-id "mycontext" \
+ --filter-string 'key = "last_run_time"'
+```
+
+Get a specific context property:
+
+```bash
+secops integration connector-context-properties get \
+ --integration-name "MyIntegration" \
+ --connector-id "c1" \
+ --context-id "mycontext" \
+ --property-id "prop123"
+```
+
+Create a new context property:
+
+```bash
+# Store last run time
+secops integration connector-context-properties create \
+ --integration-name "MyIntegration" \
+ --connector-id "c1" \
+ --context-id "mycontext" \
+ --key "last_run_time" \
+ --value "2026-03-09T10:00:00Z"
+
+# Store checkpoint for incremental sync
+secops integration connector-context-properties create \
+ --integration-name "MyIntegration" \
+ --connector-id "c1" \
+ --context-id "mycontext" \
+ --key "checkpoint" \
+ --value "page_token_xyz123"
+```
+
+Update a context property:
+
+```bash
+# Update last run time
+secops integration connector-context-properties update \
+ --integration-name "MyIntegration" \
+ --connector-id "c1" \
+ --context-id "mycontext" \
+ --property-id "prop123" \
+ --value "2026-03-09T11:00:00Z"
+```
+
+Delete a context property:
+
+```bash
+secops integration connector-context-properties delete \
+ --integration-name "MyIntegration" \
+ --connector-id "c1" \
+ --context-id "mycontext" \
+ --property-id "prop123"
+```
+
+Clear all context properties:
+
+```bash
+# Clear all properties for a specific context
+secops integration connector-context-properties clear-all \
+ --integration-name "MyIntegration" \
+ --connector-id "c1" \
+ --context-id "mycontext"
+```
+
+#### Connector Instance Logs
+
+List connector instance logs:
+
+```bash
+# List all logs for a connector instance
+secops integration connector-instance-logs list \
+ --integration-name "MyIntegration" \
+ --connector-id "c1" \
+ --connector-instance-id "inst123"
+
+# List logs as a direct list
+secops integration connector-instance-logs list \
+ --integration-name "MyIntegration" \
+ --connector-id "c1" \
+ --connector-instance-id "inst123" \
+ --as-list
+
+# List with pagination
+secops integration connector-instance-logs list \
+ --integration-name "MyIntegration" \
+ --connector-id "c1" \
+ --connector-instance-id "inst123" \
+ --page-size 50
+
+# List with filtering (filter by severity or timestamp)
+secops integration connector-instance-logs list \
+ --integration-name "MyIntegration" \
+ --connector-id "c1" \
+ --connector-instance-id "inst123" \
+ --filter-string 'severity = "ERROR"' \
+ --order-by "createTime desc"
+```
+
+Get a specific log entry:
+
+```bash
+secops integration connector-instance-logs get \
+ --integration-name "MyIntegration" \
+ --connector-id "c1" \
+ --connector-instance-id "inst123" \
+ --log-id "log456"
+```
+
+#### Connector Instances
+
+List connector instances:
+
+```bash
+# List all instances for a connector
+secops integration connector-instances list \
+ --integration-name "MyIntegration" \
+ --connector-id "c1"
+
+# List instances as a direct list
+secops integration connector-instances list \
+ --integration-name "MyIntegration" \
+ --connector-id "c1" \
+ --as-list
+
+# List with pagination
+secops integration connector-instances list \
+ --integration-name "MyIntegration" \
+ --connector-id "c1" \
+ --page-size 50
+
+# List with filtering
+secops integration connector-instances list \
+ --integration-name "MyIntegration" \
+ --connector-id "c1" \
+ --filter-string 'enabled = true'
+```
+
+Get connector instance details:
+
+```bash
+secops integration connector-instances get \
+ --integration-name "MyIntegration" \
+ --connector-id "c1" \
+ --connector-instance-id "inst123"
+```
+
+Create a new connector instance:
+
+```bash
+# Create basic connector instance
+secops integration connector-instances create \
+ --integration-name "MyIntegration" \
+ --connector-id "c1" \
+ --environment "production" \
+ --display-name "Production Data Collector"
+
+# Create with schedule and timeout
+secops integration connector-instances create \
+ --integration-name "MyIntegration" \
+ --connector-id "c1" \
+ --environment "production" \
+ --display-name "Hourly Sync" \
+ --interval-seconds 3600 \
+ --timeout-seconds 300 \
+ --enabled
+```
+
+Update a connector instance:
+
+```bash
+# Update display name
+secops integration connector-instances update \
+ --integration-name "MyIntegration" \
+ --connector-id "c1" \
+ --connector-instance-id "inst123" \
+ --display-name "Updated Display Name"
+
+# Update interval and timeout
+secops integration connector-instances update \
+ --integration-name "MyIntegration" \
+ --connector-id "c1" \
+ --connector-instance-id "inst123" \
+ --interval-seconds 7200 \
+ --timeout-seconds 600
+
+# Enable or disable instance
+secops integration connector-instances update \
+ --integration-name "MyIntegration" \
+ --connector-id "c1" \
+ --connector-instance-id "inst123" \
+ --enabled true
+
+# Update multiple fields with update mask
+secops integration connector-instances update \
+ --integration-name "MyIntegration" \
+ --connector-id "c1" \
+ --connector-instance-id "inst123" \
+ --display-name "New Name" \
+ --interval-seconds 3600 \
+ --update-mask "displayName,intervalSeconds"
+```
+
+Delete a connector instance:
+
+```bash
+secops integration connector-instances delete \
+ --integration-name "MyIntegration" \
+ --connector-id "c1" \
+ --connector-instance-id "inst123"
+```
+
+Fetch latest definition:
+
+```bash
+# Get the latest definition of a connector instance
+secops integration connector-instances fetch-latest \
+ --integration-name "MyIntegration" \
+ --connector-id "c1" \
+ --connector-instance-id "inst123"
+```
+
+Enable or disable log collection:
+
+```bash
+# Enable log collection for debugging
+secops integration connector-instances set-logs \
+ --integration-name "MyIntegration" \
+ --connector-id "c1" \
+ --connector-instance-id "inst123" \
+ --enabled true
+
+# Disable log collection
+secops integration connector-instances set-logs \
+ --integration-name "MyIntegration" \
+ --connector-id "c1" \
+ --connector-instance-id "inst123" \
+ --enabled false
+```
+
+Run connector instance on demand:
+
+```bash
+# Trigger an immediate execution for testing
+secops integration connector-instances run-ondemand \
+ --integration-name "MyIntegration" \
+ --connector-id "c1" \
+ --connector-instance-id "inst123"
+```
+
+#### Integration Jobs
+
+List integration jobs:
+
+```bash
+# List all jobs for an integration
+secops integration jobs list --integration-name "MyIntegration"
+
+# List jobs as a direct list (fetches all pages automatically)
+secops integration jobs list --integration-name "MyIntegration" --as-list
+
+# List with pagination
+secops integration jobs list --integration-name "MyIntegration" --page-size 50
+
+# List with filtering
+secops integration jobs list --integration-name "MyIntegration" --filter-string "enabled = true"
+
+# Exclude staging jobs
+secops integration jobs list --integration-name "MyIntegration" --exclude-staging
+```
+
+Get job details:
+
+```bash
+secops integration jobs get --integration-name "MyIntegration" --job-id "job1"
+```
+
+Create a new job:
+
+```bash
+secops integration jobs create \
+ --integration-name "MyIntegration" \
+ --display-name "Data Processing Job" \
+ --code "def process_data(context): return {'status': 'processed'}"
+
+# Create with description and custom ID
+secops integration jobs create \
+ --integration-name "MyIntegration" \
+ --display-name "Scheduled Report" \
+ --code "def generate_report(context): return report_data()" \
+ --description "Daily report generation job" \
+ --job-id "daily-report-job"
+
+# Create with parameters
+secops integration jobs create \
+ --integration-name "MyIntegration" \
+ --display-name "Configurable Job" \
+ --code "def run(context, params): return process(params)" \
+ --parameters '[{"name":"interval","type":"STRING","required":true}]'
+```
+
+Update an existing job:
+
+```bash
+# Update display name
+secops integration jobs update \
+ --integration-name "MyIntegration" \
+ --job-id "job1" \
+ --display-name "Updated Job Name"
+
+# Update code
+secops integration jobs update \
+ --integration-name "MyIntegration" \
+ --job-id "job1" \
+ --code "def run(context): return {'status': 'updated'}"
+
+# Update multiple fields with update mask
+secops integration jobs update \
+ --integration-name "MyIntegration" \
+ --job-id "job1" \
+ --display-name "New Name" \
+ --description "New description" \
+ --update-mask "displayName,description"
+
+# Update parameters
+secops integration jobs update \
+ --integration-name "MyIntegration" \
+ --job-id "job1" \
+ --parameters '[{"name":"timeout","type":"INTEGER","required":false}]'
+```
+
+Delete a job:
+
+```bash
+secops integration jobs delete --integration-name "MyIntegration" --job-id "job1"
+```
+
+Test a job:
+
+```bash
+secops integration jobs test --integration-name "MyIntegration" --job-id "job1"
+```
+
+Get job template:
+
+```bash
+secops integration jobs template --integration-name "MyIntegration"
+```
+
+#### Job Revisions
+
+List job revisions:
+
+```bash
+# List all revisions for a job
+secops integration job-revisions list \
+ --integration-name "MyIntegration" \
+ --job-id "job1"
+
+# List revisions as a direct list
+secops integration job-revisions list \
+ --integration-name "MyIntegration" \
+ --job-id "job1" \
+ --as-list
+
+# List with pagination
+secops integration job-revisions list \
+ --integration-name "MyIntegration" \
+ --job-id "job1" \
+ --page-size 10
+
+# List with filtering and ordering
+secops integration job-revisions list \
+ --integration-name "MyIntegration" \
+ --job-id "job1" \
+ --filter-string 'version = "1.0"' \
+ --order-by "createTime desc"
+```
+
+Create a revision backup:
+
+```bash
+# Create revision with comment
+secops integration job-revisions create \
+ --integration-name "MyIntegration" \
+ --job-id "job1" \
+ --comment "Backup before refactoring job logic"
+
+# Create revision without comment
+secops integration job-revisions create \
+ --integration-name "MyIntegration" \
+ --job-id "job1"
+```
+
+Rollback to a previous revision:
+
+```bash
+secops integration job-revisions rollback \
+ --integration-name "MyIntegration" \
+ --job-id "job1" \
+ --revision-id "r456"
+```
+
+Delete an old revision:
+
+```bash
+secops integration job-revisions delete \
+ --integration-name "MyIntegration" \
+ --job-id "job1" \
+ --revision-id "r789"
+```
+
+#### Job Context Properties
+
+List job context properties:
+
+```bash
+# List all properties for a job context
+secops integration job-context-properties list \
+ --integration-name "MyIntegration" \
+ --job-id "job1" \
+ --context-id "mycontext"
+
+# List properties as a direct list
+secops integration job-context-properties list \
+ --integration-name "MyIntegration" \
+ --job-id "job1" \
+ --context-id "mycontext" \
+ --as-list
+
+# List with pagination
+secops integration job-context-properties list \
+ --integration-name "MyIntegration" \
+ --job-id "job1" \
+ --context-id "mycontext" \
+ --page-size 50
+
+# List with filtering
+secops integration job-context-properties list \
+ --integration-name "MyIntegration" \
+ --job-id "job1" \
+ --context-id "mycontext" \
+ --filter-string 'key = "last_run_time"'
+```
+
+Get a specific context property:
+
+```bash
+secops integration job-context-properties get \
+ --integration-name "MyIntegration" \
+ --job-id "job1" \
+ --context-id "mycontext" \
+ --property-id "prop123"
+```
+
+Create a new context property:
+
+```bash
+# Store last execution time
+secops integration job-context-properties create \
+ --integration-name "MyIntegration" \
+ --job-id "job1" \
+ --context-id "mycontext" \
+ --key "last_execution_time" \
+ --value "2026-03-09T10:00:00Z"
+
+# Store job state for resumable operations
+secops integration job-context-properties create \
+ --integration-name "MyIntegration" \
+ --job-id "job1" \
+ --context-id "mycontext" \
+ --key "processing_offset" \
+ --value "1000"
+```
+
+Update a context property:
+
+```bash
+# Update execution time
+secops integration job-context-properties update \
+ --integration-name "MyIntegration" \
+ --job-id "job1" \
+ --context-id "mycontext" \
+ --property-id "prop123" \
+ --value "2026-03-09T11:00:00Z"
+```
+
+Delete a context property:
+
+```bash
+secops integration job-context-properties delete \
+ --integration-name "MyIntegration" \
+ --job-id "job1" \
+ --context-id "mycontext" \
+ --property-id "prop123"
+```
+
+Clear all context properties:
+
+```bash
+# Clear all properties for a specific context
+secops integration job-context-properties clear-all \
+ --integration-name "MyIntegration" \
+ --job-id "job1" \
+ --context-id "mycontext"
+```
+
+#### Job Instance Logs
+
+List job instance logs:
+
+```bash
+# List all logs for a job instance
+secops integration job-instance-logs list \
+ --integration-name "MyIntegration" \
+ --job-id "job1" \
+ --job-instance-id "inst123"
+
+# List logs as a direct list
+secops integration job-instance-logs list \
+ --integration-name "MyIntegration" \
+ --job-id "job1" \
+ --job-instance-id "inst123" \
+ --as-list
+
+# List with pagination
+secops integration job-instance-logs list \
+ --integration-name "MyIntegration" \
+ --job-id "job1" \
+ --job-instance-id "inst123" \
+ --page-size 50
+
+# List with filtering (filter by severity or timestamp)
+secops integration job-instance-logs list \
+ --integration-name "MyIntegration" \
+ --job-id "job1" \
+ --job-instance-id "inst123" \
+ --filter-string 'severity = "ERROR"' \
+ --order-by "createTime desc"
+```
+
+Get a specific log entry:
+
+```bash
+secops integration job-instance-logs get \
+ --integration-name "MyIntegration" \
+ --job-id "job1" \
+ --job-instance-id "inst123" \
+ --log-id "log456"
+```
+
+#### Job Instances
+
+List job instances:
+
+```bash
+# List all instances for a job
+secops integration job-instances list \
+ --integration-name "MyIntegration" \
+ --job-id "job1"
+
+# List instances as a direct list
+secops integration job-instances list \
+ --integration-name "MyIntegration" \
+ --job-id "job1" \
+ --as-list
+
+# List with pagination
+secops integration job-instances list \
+ --integration-name "MyIntegration" \
+ --job-id "job1" \
+ --page-size 50
+
+# List with filtering
+secops integration job-instances list \
+ --integration-name "MyIntegration" \
+ --job-id "job1" \
+ --filter-string 'enabled = true'
+```
+
+Get job instance details:
+
+```bash
+secops integration job-instances get \
+ --integration-name "MyIntegration" \
+ --job-id "job1" \
+ --job-instance-id "inst123"
+```
+
+Create a new job instance:
+
+```bash
+# Create basic job instance
+secops integration job-instances create \
+ --integration-name "MyIntegration" \
+ --job-id "job1" \
+ --environment "production" \
+ --display-name "Daily Report Generator"
+
+# Create with schedule and timeout
+secops integration job-instances create \
+ --integration-name "MyIntegration" \
+ --job-id "job1" \
+ --environment "production" \
+ --display-name "Hourly Data Sync" \
+ --schedule "0 * * * *" \
+ --timeout-seconds 300 \
+ --enabled
+
+# Create with parameters
+secops integration job-instances create \
+ --integration-name "MyIntegration" \
+ --job-id "job1" \
+ --environment "production" \
+ --display-name "Custom Job Instance" \
+ --schedule "0 0 * * *" \
+ --parameters '[{"name":"batch_size","value":"1000"}]'
+```
+
+Update a job instance:
+
+```bash
+# Update display name
+secops integration job-instances update \
+ --integration-name "MyIntegration" \
+ --job-id "job1" \
+ --job-instance-id "inst123" \
+ --display-name "Updated Display Name"
+
+# Update schedule and timeout
+secops integration job-instances update \
+ --integration-name "MyIntegration" \
+ --job-id "job1" \
+ --job-instance-id "inst123" \
+ --schedule "0 */2 * * *" \
+ --timeout-seconds 600
+
+# Enable or disable instance
+secops integration job-instances update \
+ --integration-name "MyIntegration" \
+ --job-id "job1" \
+ --job-instance-id "inst123" \
+ --enabled true
+
+# Update multiple fields with update mask
+secops integration job-instances update \
+ --integration-name "MyIntegration" \
+ --job-id "job1" \
+ --job-instance-id "inst123" \
+ --display-name "New Name" \
+ --schedule "0 6 * * *" \
+ --update-mask "displayName,schedule"
+
+# Update parameters
+secops integration job-instances update \
+ --integration-name "MyIntegration" \
+ --job-id "job1" \
+ --job-instance-id "inst123" \
+ --parameters '[{"name":"batch_size","value":"2000"}]'
+```
+
+Delete a job instance:
+
+```bash
+secops integration job-instances delete \
+ --integration-name "MyIntegration" \
+ --job-id "job1" \
+ --job-instance-id "inst123"
+```
+
+Run job instance on demand:
+
+```bash
+# Trigger an immediate execution for testing
+secops integration job-instances run-ondemand \
+ --integration-name "MyIntegration" \
+ --job-id "job1" \
+ --job-instance-id "inst123"
+
+# Run with custom parameters
+secops integration job-instances run-ondemand \
+ --integration-name "MyIntegration" \
+ --job-id "job1" \
+ --job-instance-id "inst123" \
+ --parameters '[{"name":"batch_size","value":"500"}]'
+```
+
+#### Integration Managers
+
+List integration managers:
+
+```bash
+# List all managers for an integration
+secops integration managers list --integration-name "MyIntegration"
+
+# List managers as a direct list (fetches all pages automatically)
+secops integration managers list --integration-name "MyIntegration" --as-list
+
+# List with pagination
+secops integration managers list --integration-name "MyIntegration" --page-size 50
+
+# List with filtering
+secops integration managers list --integration-name "MyIntegration" --filter-string "enabled = true"
+```
+
+Get manager details:
+
+```bash
+secops integration managers get --integration-name "MyIntegration" --manager-id "mgr1"
+```
+
+Create a new manager:
+
+```bash
+secops integration managers create \
+ --integration-name "MyIntegration" \
+ --display-name "Configuration Manager" \
+ --code "def manage_config(context): return {'status': 'configured'}"
+
+# Create with description and custom ID
+secops integration managers create \
+ --integration-name "MyIntegration" \
+ --display-name "My Manager" \
+ --code "def manage(context): return {}" \
+ --description "Manager description" \
+ --manager-id "custom-manager-id"
+```
+
+Update an existing manager:
+
+```bash
+# Update display name
+secops integration managers update \
+ --integration-name "MyIntegration" \
+ --manager-id "mgr1" \
+ --display-name "Updated Manager Name"
+
+# Update code
+secops integration managers update \
+ --integration-name "MyIntegration" \
+ --manager-id "mgr1" \
+ --code "def manage(context): return {'status': 'updated'}"
+
+# Update multiple fields with update mask
+secops integration managers update \
+ --integration-name "MyIntegration" \
+ --manager-id "mgr1" \
+ --display-name "New Name" \
+ --description "New description" \
+ --update-mask "displayName,description"
+```
+
+Delete a manager:
+
+```bash
+secops integration managers delete --integration-name "MyIntegration" --manager-id "mgr1"
+```
+
+Get manager template:
+
+```bash
+secops integration managers template --integration-name "MyIntegration"
+```
+
+#### Manager Revisions
+
+List manager revisions:
+
+```bash
+# List all revisions for a manager
+secops integration manager-revisions list \
+ --integration-name "MyIntegration" \
+ --manager-id "mgr1"
+
+# List revisions as a direct list
+secops integration manager-revisions list \
+ --integration-name "MyIntegration" \
+ --manager-id "mgr1" \
+ --as-list
+
+# List with pagination
+secops integration manager-revisions list \
+ --integration-name "MyIntegration" \
+ --manager-id "mgr1" \
+ --page-size 10
+
+# List with filtering and ordering
+secops integration manager-revisions list \
+ --integration-name "MyIntegration" \
+ --manager-id "mgr1" \
+ --filter-string 'version = "1.0"' \
+ --order-by "createTime desc"
+```
+
+Get a specific revision:
+
+```bash
+secops integration manager-revisions get \
+ --integration-name "MyIntegration" \
+ --manager-id "mgr1" \
+ --revision-id "r456"
+```
+
+Create a revision backup:
+
+```bash
+# Create revision with comment
+secops integration manager-revisions create \
+ --integration-name "MyIntegration" \
+ --manager-id "mgr1" \
+ --comment "Backup before major refactor"
+
+# Create revision without comment
+secops integration manager-revisions create \
+ --integration-name "MyIntegration" \
+ --manager-id "mgr1"
+```
+
+Rollback to a previous revision:
+
+```bash
+secops integration manager-revisions rollback \
+ --integration-name "MyIntegration" \
+ --manager-id "mgr1" \
+ --revision-id "r456"
+```
+
+Delete an old revision:
+
+```bash
+secops integration manager-revisions delete \
+ --integration-name "MyIntegration" \
+ --manager-id "mgr1" \
+ --revision-id "r789"
+```
+
+#### Integration Instances
+
+List integration instances:
+
+```bash
+# List all instances for an integration
+secops integration instances list --integration-name "MyIntegration"
+
+# List instances as a direct list (fetches all pages automatically)
+secops integration instances list --integration-name "MyIntegration" --as-list
+
+# List with pagination
+secops integration instances list --integration-name "MyIntegration" --page-size 50
+
+# List with filtering
+secops integration instances list --integration-name "MyIntegration" --filter-string "enabled = true"
+```
+
+Get integration instance details:
+
+```bash
+secops integration instances get \
+ --integration-name "MyIntegration" \
+ --instance-id "inst123"
+```
+
+Create a new integration instance:
+
+```bash
+# Create basic integration instance
+secops integration instances create \
+ --integration-name "MyIntegration" \
+ --display-name "Production Instance" \
+ --environment "production"
+
+# Create with description and custom ID
+secops integration instances create \
+ --integration-name "MyIntegration" \
+ --display-name "Test Instance" \
+ --environment "test" \
+ --description "Testing environment instance" \
+ --instance-id "test-inst-001"
+
+# Create with configuration
+secops integration instances create \
+ --integration-name "MyIntegration" \
+ --display-name "Configured Instance" \
+ --environment "production" \
+ --config '{"api_key":"secret123","region":"us-east1"}'
+```
+
+Update an integration instance:
+
+```bash
+# Update display name
+secops integration instances update \
+ --integration-name "MyIntegration" \
+ --instance-id "inst123" \
+ --display-name "Updated Instance Name"
+
+# Update configuration
+secops integration instances update \
+ --integration-name "MyIntegration" \
+ --instance-id "inst123" \
+ --config '{"api_key":"newsecret456","region":"us-west1"}'
+
+# Update multiple fields with update mask
+secops integration instances update \
+ --integration-name "MyIntegration" \
+ --instance-id "inst123" \
+ --display-name "New Name" \
+ --description "New description" \
+ --update-mask "displayName,description"
+```
+
+Delete an integration instance:
+
+```bash
+secops integration instances delete \
+ --integration-name "MyIntegration" \
+ --instance-id "inst123"
+```
+
+Test an integration instance:
+
+```bash
+# Test the instance configuration
+secops integration instances test \
+ --integration-name "MyIntegration" \
+ --instance-id "inst123"
+```
+
+Get affected items:
+
+```bash
+# Get items affected by this instance
+secops integration instances get-affected-items \
+ --integration-name "MyIntegration" \
+ --instance-id "inst123"
+```
+
+Get default instance:
+
+```bash
+# Get the default integration instance
+secops integration instances get-default \
+ --integration-name "MyIntegration"
+```
+
+#### Integration Transformers
+
+List integration transformers:
+
+```bash
+# List all transformers for an integration
+secops integration transformers list --integration-name "MyIntegration"
+
+# List transformers as a direct list (fetches all pages automatically)
+secops integration transformers list --integration-name "MyIntegration" --as-list
+
+# List with pagination
+secops integration transformers list --integration-name "MyIntegration" --page-size 50
+
+# List with filtering
+secops integration transformers list --integration-name "MyIntegration" --filter-string "enabled = true"
+
+# Exclude staging transformers
+secops integration transformers list --integration-name "MyIntegration" --exclude-staging
+
+# List with expanded details
+secops integration transformers list --integration-name "MyIntegration" --expand "parameters"
+```
+
+Get transformer details:
+
+```bash
+# Get basic transformer details
+secops integration transformers get \
+ --integration-name "MyIntegration" \
+ --transformer-id "t1"
+
+# Get transformer with expanded parameters
+secops integration transformers get \
+ --integration-name "MyIntegration" \
+ --transformer-id "t1" \
+ --expand "parameters"
+```
+
+Create a new transformer:
+
+```bash
+# Create a basic transformer
+secops integration transformers create \
+ --integration-name "MyIntegration" \
+ --display-name "JSON Parser" \
+ --script "def transform(data): import json; return json.loads(data)" \
+ --script-timeout "60s" \
+ --enabled
+
+# Create transformer with description
+secops integration transformers create \
+ --integration-name "MyIntegration" \
+ --display-name "Data Enricher" \
+ --script "def transform(data): return {'enriched': data, 'timestamp': '2024-01-01'}" \
+ --script-timeout "120s" \
+ --description "Enriches data with additional fields" \
+ --enabled
+```
+
+> **Note:** When creating a transformer:
+> - `--script-timeout` should be specified with a unit (e.g., "60s", "2m")
+> - Use `--enabled` flag to enable the transformer on creation (default is disabled)
+> - The script must be valid Python code with a `transform()` function
+
+Update an existing transformer:
+
+```bash
+# Update display name
+secops integration transformers update \
+ --integration-name "MyIntegration" \
+ --transformer-id "t1" \
+ --display-name "Updated Transformer Name"
+
+# Update script
+secops integration transformers update \
+ --integration-name "MyIntegration" \
+ --transformer-id "t1" \
+ --script "def transform(data): return data.upper()"
+
+# Update multiple fields
+secops integration transformers update \
+ --integration-name "MyIntegration" \
+ --transformer-id "t1" \
+ --display-name "Enhanced Transformer" \
+ --description "Updated with better error handling" \
+ --script-timeout "90s" \
+ --enabled true
+
+# Update with custom update mask
+secops integration transformers update \
+ --integration-name "MyIntegration" \
+ --transformer-id "t1" \
+ --display-name "New Name" \
+ --description "New description" \
+ --update-mask "displayName,description"
+```
+
+Delete a transformer:
+
+```bash
+secops integration transformers delete \
+ --integration-name "MyIntegration" \
+ --transformer-id "t1"
+```
+
+Test a transformer:
+
+```bash
+# Test an existing transformer to verify it works correctly
+secops integration transformers test \
+ --integration-name "MyIntegration" \
+ --transformer-id "t1"
+```
+
+Get transformer template:
+
+```bash
+# Get a boilerplate template for creating a new transformer
+secops integration transformers template --integration-name "MyIntegration"
+```
+
+#### Transformer Revisions
+
+List transformer revisions:
+
+```bash
+# List all revisions for a transformer
+secops integration transformer-revisions list \
+ --integration-name "MyIntegration" \
+ --transformer-id "t1"
+
+# List revisions as a direct list
+secops integration transformer-revisions list \
+ --integration-name "MyIntegration" \
+ --transformer-id "t1" \
+ --as-list
+
+# List with pagination
+secops integration transformer-revisions list \
+ --integration-name "MyIntegration" \
+ --transformer-id "t1" \
+ --page-size 10
+
+# List with filtering and ordering
+secops integration transformer-revisions list \
+ --integration-name "MyIntegration" \
+ --transformer-id "t1" \
+ --filter-string "version = '1.0'" \
+ --order-by "createTime desc"
+```
+
+Delete a transformer revision:
+
+```bash
+# Delete a specific revision
+secops integration transformer-revisions delete \
+ --integration-name "MyIntegration" \
+ --transformer-id "t1" \
+ --revision-id "rev-456"
+```
+
+Create a new revision:
+
+```bash
+# Create a backup revision before making changes
+secops integration transformer-revisions create \
+ --integration-name "MyIntegration" \
+ --transformer-id "t1" \
+ --comment "Backup before major refactor"
+
+# Create a revision with descriptive comment
+secops integration transformer-revisions create \
+ --integration-name "MyIntegration" \
+ --transformer-id "t1" \
+ --comment "Version 2.0 - Enhanced error handling"
+```
+
+Rollback to a previous revision:
+
+```bash
+# Rollback transformer to a specific revision
+secops integration transformer-revisions rollback \
+ --integration-name "MyIntegration" \
+ --transformer-id "t1" \
+ --revision-id "rev-456"
+```
+
+Example workflow: Safe transformer updates with revision control:
+
+```bash
+# 1. Create a backup revision
+secops integration transformer-revisions create \
+ --integration-name "MyIntegration" \
+ --transformer-id "t1" \
+ --comment "Backup before updating transformation logic"
+
+# 2. Update the transformer
+secops integration transformers update \
+ --integration-name "MyIntegration" \
+ --transformer-id "t1" \
+ --script "def transform(data): return data.upper()" \
+ --description "Updated with new transformation"
+
+# 3. Test the updated transformer
+secops integration transformers test \
+ --integration-name "MyIntegration" \
+ --transformer-id "t1"
+
+# 4. If test fails, rollback to the backup revision
+# First, list revisions to get the backup revision ID
+secops integration transformer-revisions list \
+ --integration-name "MyIntegration" \
+ --transformer-id "t1" \
+ --order-by "createTime desc" \
+ --page-size 1
+
+# Then rollback using the revision ID
+secops integration transformer-revisions rollback \
+ --integration-name "MyIntegration" \
+ --transformer-id "t1" \
+ --revision-id "rev-backup-id"
+```
+
+#### Logical Operators
+
+List logical operators:
+
+```bash
+# List all logical operators for an integration
+secops integration logical-operators list --integration-name "MyIntegration"
+
+# List logical operators as a direct list
+secops integration logical-operators list \
+ --integration-name "MyIntegration" \
+ --as-list
+
+# List with pagination
+secops integration logical-operators list \
+ --integration-name "MyIntegration" \
+ --page-size 50
+
+# List with filtering
+secops integration logical-operators list \
+ --integration-name "MyIntegration" \
+ --filter-string "enabled = true"
+
+# Exclude staging logical operators
+secops integration logical-operators list \
+ --integration-name "MyIntegration" \
+ --exclude-staging
+
+# List with expanded details
+secops integration logical-operators list \
+ --integration-name "MyIntegration" \
+ --expand "parameters"
+```
+
+Get logical operator details:
+
+```bash
+# Get basic logical operator details
+secops integration logical-operators get \
+ --integration-name "MyIntegration" \
+ --logical-operator-id "lo1"
+
+# Get logical operator with expanded parameters
+secops integration logical-operators get \
+ --integration-name "MyIntegration" \
+ --logical-operator-id "lo1" \
+ --expand "parameters"
+```
+
+Create a new logical operator:
+
+```bash
+# Create a basic equality operator
+secops integration logical-operators create \
+ --integration-name "MyIntegration" \
+ --display-name "Equals Operator" \
+ --script "def evaluate(a, b): return a == b" \
+ --script-timeout "60s" \
+ --enabled
+
+# Create logical operator with description
+secops integration logical-operators create \
+ --integration-name "MyIntegration" \
+ --display-name "Threshold Checker" \
+ --script "def evaluate(value, threshold): return value > threshold" \
+ --script-timeout "30s" \
+ --description "Checks if value exceeds threshold" \
+ --enabled
+```
+
+> **Note:** When creating a logical operator:
+> - `--script-timeout` should be specified with a unit (e.g., "60s", "2m")
+> - Use `--enabled` flag to enable the operator on creation (default is disabled)
+> - The script must be valid Python code with an `evaluate()` function
+
+Update an existing logical operator:
+
+```bash
+# Update display name
+secops integration logical-operators update \
+ --integration-name "MyIntegration" \
+ --logical-operator-id "lo1" \
+ --display-name "Updated Operator Name"
+
+# Update script
+secops integration logical-operators update \
+ --integration-name "MyIntegration" \
+ --logical-operator-id "lo1" \
+ --script "def evaluate(a, b): return a != b"
+
+# Update multiple fields
+secops integration logical-operators update \
+ --integration-name "MyIntegration" \
+ --logical-operator-id "lo1" \
+ --display-name "Enhanced Operator" \
+ --description "Updated with better logic" \
+ --script-timeout "45s" \
+ --enabled true
+
+# Update with custom update mask
+secops integration logical-operators update \
+ --integration-name "MyIntegration" \
+ --logical-operator-id "lo1" \
+ --display-name "New Name" \
+ --description "New description" \
+ --update-mask "displayName,description"
+```
+
+Delete a logical operator:
+
+```bash
+secops integration logical-operators delete \
+ --integration-name "MyIntegration" \
+ --logical-operator-id "lo1"
+```
+
+Test a logical operator:
+
+```bash
+# Test an existing logical operator to verify it works correctly
+secops integration logical-operators test \
+ --integration-name "MyIntegration" \
+ --logical-operator-id "lo1"
+```
+
+Get logical operator template:
+
+```bash
+# Get a boilerplate template for creating a new logical operator
+secops integration logical-operators template --integration-name "MyIntegration"
+```
+
+Example workflow: Building conditional logic:
+
+```bash
+# 1. Get a template to start with
+secops integration logical-operators template \
+ --integration-name "MyIntegration"
+
+# 2. Create a severity checker operator
+secops integration logical-operators create \
+ --integration-name "MyIntegration" \
+ --display-name "Severity Level Check" \
+ --script "def evaluate(severity, min_level): return severity >= min_level" \
+ --script-timeout "30s" \
+ --description "Checks if severity meets minimum threshold"
+
+# 3. Test the operator
+secops integration logical-operators test \
+ --integration-name "MyIntegration" \
+ --logical-operator-id "lo1"
+
+# 4. Enable the operator if test passes
+secops integration logical-operators update \
+ --integration-name "MyIntegration" \
+ --logical-operator-id "lo1" \
+ --enabled true
+
+# 5. List all operators to see what's available
+secops integration logical-operators list \
+ --integration-name "MyIntegration" \
+ --as-list
+```
+
+#### Logical Operator Revisions
+
+List logical operator revisions:
+
+```bash
+# List all revisions for a logical operator
+secops integration logical-operator-revisions list \
+ --integration-name "MyIntegration" \
+ --logical-operator-id "lo1"
+
+# List revisions as a direct list
+secops integration logical-operator-revisions list \
+ --integration-name "MyIntegration" \
+ --logical-operator-id "lo1" \
+ --as-list
+
+# List with pagination
+secops integration logical-operator-revisions list \
+ --integration-name "MyIntegration" \
+ --logical-operator-id "lo1" \
+ --page-size 10
+
+# List with filtering and ordering
+secops integration logical-operator-revisions list \
+ --integration-name "MyIntegration" \
+ --logical-operator-id "lo1" \
+ --filter-string "version = '1.0'" \
+ --order-by "createTime desc"
+```
+
+Delete a logical operator revision:
+
+```bash
+# Delete a specific revision
+secops integration logical-operator-revisions delete \
+ --integration-name "MyIntegration" \
+ --logical-operator-id "lo1" \
+ --revision-id "rev-456"
+```
+
+Create a new revision:
+
+```bash
+# Create a backup revision before making changes
+secops integration logical-operator-revisions create \
+ --integration-name "MyIntegration" \
+ --logical-operator-id "lo1" \
+ --comment "Backup before refactoring evaluation logic"
+
+# Create a revision with descriptive comment
+secops integration logical-operator-revisions create \
+ --integration-name "MyIntegration" \
+ --logical-operator-id "lo1" \
+ --comment "Version 2.0 - Enhanced comparison logic"
+```
+
+Rollback to a previous revision:
+
+```bash
+# Rollback logical operator to a specific revision
+secops integration logical-operator-revisions rollback \
+ --integration-name "MyIntegration" \
+ --logical-operator-id "lo1" \
+ --revision-id "rev-456"
+```
+
+Example workflow: Safe logical operator updates with revision control:
+
+```bash
+# 1. Create a backup revision
+secops integration logical-operator-revisions create \
+ --integration-name "MyIntegration" \
+ --logical-operator-id "lo1" \
+ --comment "Backup before updating conditional logic"
+
+# 2. Update the logical operator
+secops integration logical-operators update \
+ --integration-name "MyIntegration" \
+ --logical-operator-id "lo1" \
+ --script "def evaluate(a, b): return a >= b" \
+ --description "Updated with greater-than-or-equal logic"
+
+# 3. Test the updated logical operator
+secops integration logical-operators test \
+ --integration-name "MyIntegration" \
+ --logical-operator-id "lo1"
+
+# 4. If test fails, rollback to the backup revision
+# First, list revisions to get the backup revision ID
+secops integration logical-operator-revisions list \
+ --integration-name "MyIntegration" \
+ --logical-operator-id "lo1" \
+ --order-by "createTime desc" \
+ --page-size 1
+
+# Then rollback using the revision ID
+secops integration logical-operator-revisions rollback \
+ --integration-name "MyIntegration" \
+ --logical-operator-id "lo1" \
+ --revision-id "rev-backup-id"
+```
+
### Rule Management
List detection rules:
@@ -896,7 +2721,6 @@ secops curated-rule search-detections \
--end-time "2024-01-31T23:59:59Z" \
--list-basis "DETECTION_TIME" \
--page-size 50
-
```
List all curated rule sets:
@@ -1543,39 +3367,7 @@ secops reference-list create \
secops parser list
# Get details of a specific parser
-secops parser get --log-type "WINDOWS" --id "pa_12345"
-
-# Create a custom parser for a new log format
-secops parser create \
- --log-type "CUSTOM_APPLICATION" \
- --parser-code-file "/path/to/custom_parser.conf" \
- --validated-on-empty-logs
-
-# Copy an existing parser as a starting point
-secops parser copy --log-type "OKTA" --id "pa_okta_base"
-
-# Activate your custom parser
-secops parser activate --log-type "CUSTOM_APPLICATION" --id "pa_new_custom"
-
-# If needed, deactivate and delete old parser
-secops parser deactivate --log-type "CUSTOM_APPLICATION" --id "pa_old_custom"
-secops parser delete --log-type "CUSTOM_APPLICATION" --id "pa_old_custom"
-```
-
-### Complete Parser Workflow Example: Retrieve, Run, and Ingest
-
-This example demonstrates the complete workflow of retrieving an OKTA parser, running it against a sample log, and ingesting the parsed UDM event:
-
-```bash
-# Step 1: List OKTA parsers to find an active one
-secops parser list --log-type "OKTA" > okta_parsers.json
-
-# Extract the first parser ID (you can use jq or grep)
-PARSER_ID=$(cat okta_parsers.json | jq -r '.[0].name' | awk -F'/' '{print $NF}')
-echo "Using parser: $PARSER_ID"
-
-# Step 2: Get the parser details and save to a file
-secops parser get --log-type "OKTA" --id "$PARSER_ID" > parser_details.json
+secops parser get --log-type "WINDOWS" --id "$PARSER_ID" > parser_details.json
# Extract and decode the parser code (base64 encoded in 'cbn' field)
cat parser_details.json | jq -r '.cbn' | base64 -d > okta_parser.conf
@@ -1713,7 +3505,7 @@ secops feed update --id "feed-123" --display-name "Updated Feed Name"
secops feed update --id "feed-123" --details '{"httpSettings":{"uri":"https://example.com/updated-feed","sourceType":"FILES"}}'
# Update both display name and details
-secops feed update --id "feed-123" --display-name "Updated Name" --details '{"httpSettings":{"uri":"https://example.com/updated-feed"}}'
+secops feed update --id "feed-123" --display-name "New Name" --details '{"httpSettings":{"uri":"https://example.com/updated-feed"}}'
```
Enable and disable feeds:
@@ -1854,4 +3646,5 @@ secops dashboard-query get --id query-id
## Conclusion
-The SecOps CLI provides a powerful way to interact with Google Security Operations products directly from your terminal. For more detailed information about the SDK capabilities, refer to the [main README](README.md).
\ No newline at end of file
+The SecOps CLI provides a powerful way to interact with Google Security Operations products directly from your terminal. For more detailed information about the SDK capabilities, refer to the [main README](README.md).
+
diff --git a/README.md b/README.md
index d746ad5a..9a49afe9 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,5 @@
+from tests.chronicle.test_rule_integration import chronicle
+
# Google SecOps SDK for Python
[](https://pypi.org/project/secops/)
@@ -1907,6 +1909,3302 @@ for watchlist in watchlists:
print(f"Watchlist: {watchlist.get('displayName')}")
```
+## Integration Management
+
+### Marketplace Integrations
+
+List available marketplace integrations:
+
+```python
+# Get all available marketplace integration
+integrations = chronicle.list_marketplace_integrations()
+for integration in integrations.get("marketplaceIntegrations", []):
+ integration_title = integration.get("title")
+ integration_id = integration.get("name", "").split("/")[-1]
+ integration_version = integration.get("version", "")
+ documentation_url = integration.get("documentationUri", "")
+
+# Get all integration as a list
+integrations = chronicle.list_marketplace_integrations(as_list=True)
+
+# Get all currently installed integration
+integrations = chronicle.list_marketplace_integrations(filter_string="installed = true")
+
+# Get all installed integration with updates available
+integrations = chronicle.list_marketplace_integrations(filter_string="installed = true AND updateAvailable = true")
+
+# Specify use of V1 Alpha API version
+integrations = chronicle.list_marketplace_integrations(api_version=APIVersion.V1ALPHA)
+```
+
+Get a specific marketplace integration:
+
+```python
+integration = chronicle.get_marketplace_integration("AWSSecurityHub")
+```
+
+Get the diff between the currently installed version and the latest
+available version of an integration:
+
+```python
+diff = chronicle.get_marketplace_integration_diff("AWSSecurityHub")
+```
+
+Install or update a marketplace integration:
+
+```python
+# Install an integration with the default settings
+integration_name = "AWSSecurityHub"
+integration = chronicle.install_marketplace_integration(integration_name)
+
+# Install to staging environment and override any existing ontology mappings
+integration = chronicle.install_marketplace_integration(
+ integration_name,
+ staging=True,
+ override_ontology_mappings=True
+)
+
+# Installing a currently installed integration with no specified version
+# number will update it to the latest version
+integration = chronicle.install_marketplace_integration(integration_name)
+
+# Or you can specify a specific version to install
+integration = chronicle.install_marketplace_integration(
+ integration_name,
+ version="5.0"
+)
+```
+
+Uninstall a marketplace integration:
+
+```python
+chronicle.uninstall_marketplace_integration("AWSSecurityHub")
+```
+
+### Integrations
+List all installed integrations:
+
+```python
+# Get all integrations
+integrations = chronicle.list_integrations()
+for i in integrations.get("integrations", []):
+ integration_id = i["identifier"]
+ integration_display_name = i["displayName"]
+ integration_type = i["type"]
+
+# Get all integrations as a list
+integrations = chronicle.list_integrations(as_list=True)
+
+for i in integrations:
+ integration = chronicle.get_integration(i["identifier"])
+ if integration.get("parameters"):
+ print(json.dumps(integration, indent=2))
+
+
+# Get integrations ordered by display name
+integrations = chronicle.list_integrations(order_by="displayName", as_list=True)
+ ```
+
+Get details of a specific integration:
+
+```python
+integration = chronicle.get_integration("AWSSecurityHub")
+```
+
+Create an integration:
+
+```python
+from secops.chronicle.models (
+ IntegrationParam,
+ IntegrationParamType,
+ IntegrationType,
+ PythonVersion
+)
+
+integration = chronicle.create_integration(
+ display_name="MyNewIntegration",
+ staging=True,
+ description="This is my integration",
+ python_version=PythonVersion.PYTHON_3_11,
+ parameters=[
+ IntegrationParam(
+ display_name="AWS Access Key",
+ property_name="aws_access_key",
+ type=IntegrationParamType.STRING,
+ description="AWS access key for authentication",
+ mandatory=True,
+ ),
+ IntegrationParam(
+ display_name="AWS Secret Key",
+ property_name="aws_secret_key",
+ type=IntegrationParamType.PASSWORD,
+ description="AWS secret key for authentication",
+ mandatory=False,
+ ),
+ ],
+ categories=[
+ "Cloud Security",
+ "Cloud",
+ "Security"
+ ],
+ integration_type=IntegrationType.RESPONSE,
+)
+```
+
+Update an integration:
+
+```python
+from secops.chronicle.models import IntegrationParam, IntegrationParamType
+
+updated_integration = chronicle.update_integration(
+ integration_name="MyNewIntegration",
+ display_name="Updated Integration Name",
+ description="Updated description",
+ parameters=[
+ IntegrationParam(
+ display_name="AWS Region",
+ property_name="aws_region",
+ type=IntegrationParamType.STRING,
+ description="AWS region to use",
+ mandatory=True,
+ ),
+ ],
+ categories=[
+ "Cloud Security",
+ "Cloud",
+ "Security"
+ ],
+)
+```
+
+Delete an integration:
+
+```python
+chronicle.delete_integration("MyNewIntegration")
+```
+
+Download an entire integration as a bytes object and save it as a .zip file
+This includes all the integration details, parameters, and actions in a format that can be re-uploaded to Chronicle or used for backup purposes.
+
+```python
+integration_bytes = chronicle.download_integration("MyIntegration")
+with open("MyIntegration.zip", "wb") as f:
+ f.write(integration_bytes)
+```
+
+Export selected items from an integration (e.g. only actions) as a .zip file:
+
+```python
+# Export only actions with IDs 1 and 2 from the integration
+
+export_bytes = chronicle.export_integration_items(
+ integration_name="AWSSecurityHub",
+ actions=["1", "2"] # IDs of the actions to export
+)
+with open("AWSSecurityHub_FullExport.zip", "wb") as f:
+ f.write(export_bytes)
+```
+
+Get dependencies for an integration:
+
+```python
+dependencies = chronicle.get_integration_dependencies("AWSSecurityHub")
+for dep in dependencies.get("dependencies", []):
+ parts = dep.split("-")
+ dependency_name = parts[0]
+ dependency_version = parts[1] if len(parts) > 1 else "latest"
+ print(f"Dependency: {dependency_name}, Version: {dependency_version}")
+```
+
+Force dependency update for an integration:
+
+```python
+# Defining a version:
+chronicle.download_integration_dependency(
+ "MyIntegration",
+ "boto3==1.42.44"
+)
+
+# Install the latest version of a dependency:
+chronicle.download_integration_dependency(
+ "MyIntegration",
+ "boto3"
+)
+```
+
+Get remote agents that would be restricted from running an updated version of the integration
+
+```python
+from secops.chronicle.models import PythonVersion
+
+agents = chronicle.get_integration_restricted_agents(
+ integration_name="AWSSecurityHub",
+ required_python_version=PythonVersion.PYTHON_3_11,
+)
+```
+
+Get integration diff between two versions of an integration:
+
+```python
+from secops.chronicle.models import DiffType
+
+# Get the diff between the commercial version of the integration and the current version in the environment.
+diff = chronicle.get_integration_diff(
+ integration_name="AWSSecurityHub",
+ diff_type=DiffType.COMMERCIAL
+)
+
+# Get the difference between the staging integration and its matching production version.
+diff = chronicle.get_integration_diff(
+ integration_name="AWSSecurityHub",
+ diff_type=DiffType.PRODUCTION
+)
+
+# Get the difference between the production integration and its corresponding staging version.
+diff = chronicle.get_integration_diff(
+ integration_name="AWSSecurityHub",
+ diff_type=DiffType.STAGING
+)
+```
+
+Transition an integration to staging or production environment:
+
+```python
+from secops.chronicle.models import TargetMode
+
+# Transition to staging environment
+chronicle.transition_integration_environment(
+ integration_name="AWSSecurityHub",
+ target_mode=TargetMode.STAGING
+)
+
+# Transition to production environment
+chronicle.transition_integration_environment(
+ integration_name="AWSSecurityHub",
+ target_mode=TargetMode.PRODUCTION
+)
+```
+
+### Integration Actions
+
+List all available actions for an integration:
+
+```python
+# Get all actions for an integration
+actions = chronicle.list_integration_actions("AWSSecurityHub")
+for action in actions.get("actions", []):
+ print(f"Action: {action.get('displayName')}, ID: {action.get('name')}")
+
+# Get all actions as a list
+actions = chronicle.list_integration_actions("AWSSecurityHub", as_list=True)
+
+# Get only enabled actions
+actions = chronicle.list_integration_actions("AWSSecurityHub", filter_string="enabled = true")
+```
+
+Get details of a specific action:
+
+```python
+
+action = chronicle.get_integration_action(
+ integration_name="AWSSecurityHub",
+ action_id="123"
+)
+```
+
+Create an integration action
+
+```python
+from secops.chronicle.models import ActionParameter, ActionParamType
+
+new_action = chronicle.create_integration_action(
+ integration_name="MyIntegration",
+ display_name="New Action",
+ description="This is a new action",
+ enabled=True,
+ timeout_seconds=900,
+ is_async=False,
+ script_result_name="script_result",
+ parameters=[
+ ActionParameter(
+ display_name="Parameter 1",
+ type=ActionParamType.STRING,
+ description="This is parameter 1",
+ mandatory=True,
+ )
+ ],
+ script="print('Hello, World!')"
+ )
+```
+
+Update an integration action
+
+```python
+from secops.chronicle.models import ActionParameter, ActionParamType
+
+updated_action = chronicle.update_integration_action(
+ integration_name="MyIntegration",
+ action_id="123",
+ display_name="Updated Action Name",
+ description="Updated description",
+ enabled=False,
+ parameters=[
+ ActionParameter(
+ display_name="New Parameter",
+ type=ActionParamType.PASSWORD,
+ description="This is a new parameter",
+ mandatory=True,
+ )
+ ],
+ script="print('Updated script')"
+)
+```
+
+Delete an integration action
+
+```python
+chronicle.delete_integration_action(
+ integration_name="MyIntegration",
+ action_id="123"
+)
+```
+
+Execute test run of an integration action
+
+```python
+# Get the integration instance ID by using chronicle.list_integration_instances()
+integration_instance_id = "abc-123-def-456"
+
+test_run = chronicle.execute_integration_action_test(
+ integration_name="MyIntegration",
+ test_case_id=123456,
+ action=chronicle.get_integration_action("MyIntegration", "123"),
+ scope="TEST",
+ integration_instance_id=integration_instance_id,
+)
+```
+
+Get integration actions by environment
+
+```python
+# Get all actions for an integration in the Default Environment
+actions = chronicle.get_integration_actions_by_environment(
+ integration_name="MyIntegration",
+ environments=["Default Environment"],
+ include_widgets=True,
+)
+```
+
+Get a template for creating an action in an integration
+
+```python
+template = chronicle.get_integration_action_template("MyIntegration")
+```
+
+### Integration Action Revisions
+
+List all revisions for an action:
+
+```python
+# Get all revisions for an action
+revisions = chronicle.list_integration_action_revisions(
+ integration_name="MyIntegration",
+ action_id="123"
+)
+for revision in revisions.get("revisions", []):
+ print(f"Revision: {revision.get('name')}, Comment: {revision.get('comment')}")
+
+# Get all revisions as a list
+revisions = chronicle.list_integration_action_revisions(
+ integration_name="MyIntegration",
+ action_id="123",
+ as_list=True
+)
+
+# Filter revisions
+revisions = chronicle.list_integration_action_revisions(
+ integration_name="MyIntegration",
+ action_id="123",
+ filter_string='version = "1.0"',
+ order_by="createTime desc"
+)
+```
+
+Delete a specific action revision:
+
+```python
+chronicle.delete_integration_action_revision(
+ integration_name="MyIntegration",
+ action_id="123",
+ revision_id="rev-456"
+)
+```
+
+Create a new revision before making changes:
+
+```python
+# Get the current action
+action = chronicle.get_integration_action(
+ integration_name="MyIntegration",
+ action_id="123"
+)
+
+# Create a backup revision
+new_revision = chronicle.create_integration_action_revision(
+ integration_name="MyIntegration",
+ action_id="123",
+ action=action,
+ comment="Backup before major refactor"
+)
+print(f"Created revision: {new_revision.get('name')}")
+
+# Create revision with custom comment
+new_revision = chronicle.create_integration_action_revision(
+ integration_name="MyIntegration",
+ action_id="123",
+ action=action,
+ comment="Version 2.0 - Added error handling"
+)
+```
+
+Rollback to a previous revision:
+
+```python
+# Rollback to a previous working version
+rollback_result = chronicle.rollback_integration_action_revision(
+ integration_name="MyIntegration",
+ action_id="123",
+ revision_id="rev-456"
+)
+print(f"Rolled back to: {rollback_result.get('name')}")
+```
+
+Example workflow: Safe action updates with revision control:
+
+```python
+# 1. Get the current action
+action = chronicle.get_integration_action(
+ integration_name="MyIntegration",
+ action_id="123"
+)
+
+# 2. Create a backup revision
+backup = chronicle.create_integration_action_revision(
+ integration_name="MyIntegration",
+ action_id="123",
+ action=action,
+ comment="Backup before updating logic"
+)
+
+# 3. Make changes to the action
+updated_action = chronicle.update_integration_action(
+ integration_name="MyIntegration",
+ action_id="123",
+ display_name="Updated Action",
+ script="""
+def main(context):
+ # New logic here
+ return {"status": "success"}
+"""
+)
+
+# 4. Test the updated action
+test_result = chronicle.execute_integration_action_test(
+ integration_name="MyIntegration",
+ action_id="123",
+ action=updated_action
+)
+
+# 5. If test fails, rollback to backup
+if not test_result.get("successful"):
+ print("Test failed - rolling back")
+ chronicle.rollback_integration_action_revision(
+ integration_name="MyIntegration",
+ action_id="123",
+ revision_id=backup.get("name").split("/")[-1]
+ )
+else:
+ print("Test passed - changes saved")
+```
+
+### Integration Connectors
+
+List all available connectors for an integration:
+
+```python
+# Get all connectors for an integration
+connectors = chronicle.list_integration_connectors("AWSSecurityHub")
+
+# Get all connectors as a list
+connectors = chronicle.list_integration_connectors("AWSSecurityHub", as_list=True)
+
+# Get only enabled connectors
+connectors = chronicle.list_integration_connectors(
+ "AWSSecurityHub",
+ filter_string="enabled = true"
+)
+
+# Exclude staging connectors
+connectors = chronicle.list_integration_connectors(
+ "AWSSecurityHub",
+ exclude_staging=True
+)
+```
+
+Get details of a specific connector:
+
+```python
+connector = chronicle.get_integration_connector(
+ integration_name="AWSSecurityHub",
+ connector_id="123"
+)
+```
+
+Create an integration connector:
+
+```python
+from secops.chronicle.models import (
+ ConnectorParameter,
+ ParamType,
+ ConnectorParamMode,
+ ConnectorRule,
+ ConnectorRuleType
+)
+
+new_connector = chronicle.create_integration_connector(
+ integration_name="MyIntegration",
+ display_name="New Connector",
+ description="This is a new connector",
+ script="print('Fetching data...')",
+ timeout_seconds=300,
+ enabled=True,
+ product_field_name="product",
+ event_field_name="event_type",
+ parameters=[
+ ConnectorParameter(
+ display_name="API Key",
+ type=ParamType.PASSWORD,
+ mode=ConnectorParamMode.CONNECTIVITY,
+ mandatory=True,
+ description="API key for authentication"
+ )
+ ],
+ rules=[
+ ConnectorRule(
+ display_name="Allow List",
+ type=ConnectorRuleType.ALLOW_LIST
+ )
+ ]
+)
+```
+
+Update an integration connector:
+
+```python
+from secops.chronicle.models import (
+ ConnectorParameter,
+ ParamType,
+ ConnectorParamMode
+)
+
+updated_connector = chronicle.update_integration_connector(
+ integration_name="MyIntegration",
+ connector_id="123",
+ display_name="Updated Connector Name",
+ description="Updated description",
+ enabled=False,
+ timeout_seconds=600,
+ parameters=[
+ ConnectorParameter(
+ display_name="API Token",
+ type=ParamType.PASSWORD,
+ mode=ConnectorParamMode.CONNECTIVITY,
+ mandatory=True,
+ description="Updated authentication token"
+ )
+ ],
+ script="print('Updated connector script')"
+)
+```
+
+Delete an integration connector:
+
+```python
+chronicle.delete_integration_connector(
+ integration_name="MyIntegration",
+ connector_id="123"
+)
+```
+
+Execute a test run of an integration connector:
+
+```python
+# Test a connector before saving it
+connector_config = {
+ "displayName": "Test Connector",
+ "script": "print('Testing connector')",
+ "enabled": True,
+ "timeoutSeconds": 300,
+ "productFieldName": "product",
+ "eventFieldName": "event_type"
+}
+
+test_result = chronicle.execute_integration_connector_test(
+ integration_name="MyIntegration",
+ connector=connector_config
+)
+
+print(f"Output: {test_result.get('outputMessage')}")
+print(f"Debug: {test_result.get('debugOutputMessage')}")
+
+# Test with a specific agent for remote execution
+test_result = chronicle.execute_integration_connector_test(
+ integration_name="MyIntegration",
+ connector=connector_config,
+ agent_identifier="agent-123"
+)
+```
+
+Get a template for creating a connector in an integration:
+
+```python
+template = chronicle.get_integration_connector_template("MyIntegration")
+print(f"Template script: {template.get('script')}")
+```
+
+### Integration Connector Revisions
+
+List all revisions for a specific integration connector:
+
+```python
+# Get all revisions for a connector
+revisions = chronicle.list_integration_connector_revisions(
+ integration_name="MyIntegration",
+ connector_id="c1"
+)
+for revision in revisions.get("revisions", []):
+ print(f"Revision ID: {revision.get('name')}")
+ print(f"Comment: {revision.get('comment')}")
+ print(f"Created: {revision.get('createTime')}")
+
+# Get all revisions as a list
+revisions = chronicle.list_integration_connector_revisions(
+ integration_name="MyIntegration",
+ connector_id="c1",
+ as_list=True
+)
+
+# Filter revisions with order
+revisions = chronicle.list_integration_connector_revisions(
+ integration_name="MyIntegration",
+ connector_id="c1",
+ order_by="createTime desc",
+ page_size=10
+)
+```
+
+Delete a specific connector revision:
+
+```python
+# Clean up old revision from version history
+chronicle.delete_integration_connector_revision(
+ integration_name="MyIntegration",
+ connector_id="c1",
+ revision_id="r1"
+)
+```
+
+Create a new connector revision snapshot:
+
+```python
+# Get the current connector configuration
+connector = chronicle.get_integration_connector(
+ integration_name="MyIntegration",
+ connector_id="c1"
+)
+
+# Create a revision without comment
+new_revision = chronicle.create_integration_connector_revision(
+ integration_name="MyIntegration",
+ connector_id="c1",
+ connector=connector
+)
+
+# Create a revision with descriptive comment
+new_revision = chronicle.create_integration_connector_revision(
+ integration_name="MyIntegration",
+ connector_id="c1",
+ connector=connector,
+ comment="Stable version before adding new field mapping"
+)
+
+print(f"Created revision: {new_revision.get('name')}")
+```
+
+Rollback a connector to a previous revision:
+
+```python
+# Revert to a known good configuration
+rolled_back = chronicle.rollback_integration_connector_revision(
+ integration_name="MyIntegration",
+ connector_id="c1",
+ revision_id="r1"
+)
+
+print(f"Rolled back to revision: {rolled_back.get('name')}")
+print(f"Connector script restored")
+```
+
+Example workflow: Safe connector updates with revisions:
+
+```python
+# 1. Get current connector
+connector = chronicle.get_integration_connector(
+ integration_name="MyIntegration",
+ connector_id="c1"
+)
+
+# 2. Create backup revision before changes
+backup = chronicle.create_integration_connector_revision(
+ integration_name="MyIntegration",
+ connector_id="c1",
+ connector=connector,
+ comment="Backup before timeout increase"
+)
+print(f"Backup created: {backup.get('name')}")
+
+# 3. Update the connector
+updated_connector = chronicle.update_integration_connector(
+ integration_name="MyIntegration",
+ connector_id="c1",
+ timeout_seconds=600,
+ description="Increased timeout for large data pulls"
+)
+
+# 4. Test the updated connector
+test_result = chronicle.execute_integration_connector_test(
+ integration_name="MyIntegration",
+ connector=updated_connector
+)
+
+# 5. If test fails, rollback to the backup
+if not test_result.get("outputMessage"):
+ print("Test failed, rolling back...")
+ chronicle.rollback_integration_connector_revision(
+ integration_name="MyIntegration",
+ connector_id="c1",
+ revision_id=backup.get("name").split("/")[-1]
+ )
+ print("Rollback complete")
+else:
+ print("Test passed, changes applied successfully")
+```
+
+### Connector Context Properties
+
+List all context properties for a specific connector:
+
+```python
+# Get all context properties for a connector
+context_properties = chronicle.list_connector_context_properties(
+ integration_name="MyIntegration",
+ connector_id="c1"
+)
+for prop in context_properties.get("contextProperties", []):
+ print(f"Key: {prop.get('key')}, Value: {prop.get('value')}")
+
+# Get all context properties as a list
+context_properties = chronicle.list_connector_context_properties(
+ integration_name="MyIntegration",
+ connector_id="c1",
+ as_list=True
+)
+
+# Filter context properties
+context_properties = chronicle.list_connector_context_properties(
+ integration_name="MyIntegration",
+ connector_id="c1",
+ filter_string='key = "last_run_time"',
+ order_by="key"
+)
+```
+
+Get a specific context property:
+
+```python
+property_value = chronicle.get_connector_context_property(
+ integration_name="MyIntegration",
+ connector_id="c1",
+ context_property_id="last_run_time"
+)
+print(f"Value: {property_value.get('value')}")
+```
+
+Create a new context property:
+
+```python
+# Create context property with auto-generated key
+new_property = chronicle.create_connector_context_property(
+ integration_name="MyIntegration",
+ connector_id="c1",
+ value="2026-03-09T10:00:00Z"
+)
+
+# Create context property with custom key
+new_property = chronicle.create_connector_context_property(
+ integration_name="MyIntegration",
+ connector_id="c1",
+ value="2026-03-09T10:00:00Z",
+ key="last-sync-time"
+)
+print(f"Created property: {new_property.get('name')}")
+```
+
+Update an existing context property:
+
+```python
+# Update property value
+updated_property = chronicle.update_connector_context_property(
+ integration_name="MyIntegration",
+ connector_id="c1",
+ context_property_id="last-sync-time",
+ value="2026-03-09T11:00:00Z"
+)
+print(f"Updated value: {updated_property.get('value')}")
+```
+
+Delete a context property:
+
+```python
+chronicle.delete_connector_context_property(
+ integration_name="MyIntegration",
+ connector_id="c1",
+ context_property_id="last-sync-time"
+)
+```
+
+Delete all context properties:
+
+```python
+# Clear all properties for a connector
+chronicle.delete_all_connector_context_properties(
+ integration_name="MyIntegration",
+ connector_id="c1"
+)
+
+# Clear all properties for a specific context ID
+chronicle.delete_all_connector_context_properties(
+ integration_name="MyIntegration",
+ connector_id="c1",
+ context_id="my-context"
+)
+```
+
+Example workflow: Track connector state with context properties:
+
+```python
+# 1. Check if we have a last run time stored
+try:
+ last_run = chronicle.get_connector_context_property(
+ integration_name="MyIntegration",
+ connector_id="c1",
+ context_property_id="last-run-time"
+ )
+ print(f"Last run: {last_run.get('value')}")
+except APIError:
+ print("No previous run time found")
+ # Create initial property
+ chronicle.create_connector_context_property(
+ integration_name="MyIntegration",
+ connector_id="c1",
+ value="2026-01-01T00:00:00Z",
+ key="last-run-time"
+ )
+
+# 2. Run the connector and process data
+# ... connector execution logic ...
+
+# 3. Update the last run time after successful execution
+from datetime import datetime
+current_time = datetime.utcnow().isoformat() + "Z"
+chronicle.update_connector_context_property(
+ integration_name="MyIntegration",
+ connector_id="c1",
+ context_property_id="last-run-time",
+ value=current_time
+)
+
+# 4. Store additional context like record count
+chronicle.create_connector_context_property(
+ integration_name="MyIntegration",
+ connector_id="c1",
+ value="1500",
+ key="records-processed"
+)
+
+# 5. List all context to see connector state
+all_context = chronicle.list_connector_context_properties(
+ integration_name="MyIntegration",
+ connector_id="c1",
+ as_list=True
+)
+for prop in all_context:
+ print(f"{prop.get('key')}: {prop.get('value')}")
+```
+
+### Connector Instance Logs
+
+List all execution logs for a connector instance:
+
+```python
+# Get all logs for a connector instance
+logs = chronicle.list_connector_instance_logs(
+ integration_name="MyIntegration",
+ connector_id="c1",
+ connector_instance_id="ci1"
+)
+for log in logs.get("logs", []):
+ print(f"Log ID: {log.get('name')}, Severity: {log.get('severity')}")
+ print(f"Timestamp: {log.get('timestamp')}")
+ print(f"Message: {log.get('message')}")
+
+# Get all logs as a list
+logs = chronicle.list_connector_instance_logs(
+ integration_name="MyIntegration",
+ connector_id="c1",
+ connector_instance_id="ci1",
+ as_list=True
+)
+
+# Filter logs by severity
+logs = chronicle.list_connector_instance_logs(
+ integration_name="MyIntegration",
+ connector_id="c1",
+ connector_instance_id="ci1",
+ filter_string='severity = "ERROR"',
+ order_by="timestamp desc"
+)
+```
+
+Get a specific log entry:
+
+```python
+log_entry = chronicle.get_connector_instance_log(
+ integration_name="MyIntegration",
+ connector_id="c1",
+ connector_instance_id="ci1",
+ log_id="log123"
+)
+print(f"Severity: {log_entry.get('severity')}")
+print(f"Timestamp: {log_entry.get('timestamp')}")
+print(f"Message: {log_entry.get('message')}")
+```
+
+Monitor connector execution and troubleshooting:
+
+```python
+# Get recent logs for monitoring
+recent_logs = chronicle.list_connector_instance_logs(
+ integration_name="MyIntegration",
+ connector_id="c1",
+ connector_instance_id="ci1",
+ order_by="timestamp desc",
+ page_size=10,
+ as_list=True
+)
+
+# Check for errors
+for log in recent_logs:
+ if log.get("severity") in ["ERROR", "CRITICAL"]:
+ print(f"Error at {log.get('timestamp')}")
+ log_details = chronicle.get_connector_instance_log(
+ integration_name="MyIntegration",
+ connector_id="c1",
+ connector_instance_id="ci1",
+ log_id=log.get("name").split("/")[-1]
+ )
+ print(f"Error message: {log_details.get('message')}")
+```
+
+Analyze connector performance and reliability:
+
+```python
+# Get all logs to calculate error rate
+all_logs = chronicle.list_connector_instance_logs(
+ integration_name="MyIntegration",
+ connector_id="c1",
+ connector_instance_id="ci1",
+ as_list=True
+)
+
+errors = sum(1 for log in all_logs if log.get("severity") in ["ERROR", "CRITICAL"])
+warnings = sum(1 for log in all_logs if log.get("severity") == "WARNING")
+total = len(all_logs)
+
+if total > 0:
+ error_rate = (errors / total) * 100
+ print(f"Error Rate: {error_rate:.2f}%")
+ print(f"Total Logs: {total}")
+ print(f"Errors: {errors}, Warnings: {warnings}")
+```
+
+### Connector Instances
+
+List all connector instances for a specific connector:
+
+```python
+# Get all instances for a connector
+instances = chronicle.list_connector_instances(
+ integration_name="MyIntegration",
+ connector_id="c1"
+)
+for instance in instances.get("connectorInstances", []):
+ print(f"Instance: {instance.get('displayName')}, Enabled: {instance.get('enabled')}")
+
+# Get all instances as a list
+instances = chronicle.list_connector_instances(
+ integration_name="MyIntegration",
+ connector_id="c1",
+ as_list=True
+)
+
+# Filter instances
+instances = chronicle.list_connector_instances(
+ integration_name="MyIntegration",
+ connector_id="c1",
+ filter_string='enabled = true',
+ order_by="displayName"
+)
+```
+
+Get a specific connector instance:
+
+```python
+instance = chronicle.get_connector_instance(
+ integration_name="MyIntegration",
+ connector_id="c1",
+ connector_instance_id="ci1"
+)
+print(f"Display Name: {instance.get('displayName')}")
+print(f"Environment: {instance.get('environment')}")
+print(f"Interval: {instance.get('intervalSeconds')} seconds")
+```
+
+Create a new connector instance:
+
+```python
+from secops.chronicle.models import ConnectorInstanceParameter
+
+# Create basic connector instance
+new_instance = chronicle.create_connector_instance(
+ integration_name="MyIntegration",
+ connector_id="c1",
+ environment="production",
+ display_name="Production Instance",
+ interval_seconds=3600, # Run every hour
+ timeout_seconds=300, # 5 minute timeout
+ enabled=True
+)
+
+# Create instance with parameters
+param = ConnectorInstanceParameter()
+param.value = "my-api-key"
+
+new_instance = chronicle.create_connector_instance(
+ integration_name="MyIntegration",
+ connector_id="c1",
+ environment="production",
+ display_name="Production Instance",
+ interval_seconds=3600,
+ timeout_seconds=300,
+ description="Main production connector instance",
+ parameters=[param],
+ enabled=True
+)
+print(f"Created instance: {new_instance.get('name')}")
+```
+
+Update an existing connector instance:
+
+```python
+# Update display name
+updated_instance = chronicle.update_connector_instance(
+ integration_name="MyIntegration",
+ connector_id="c1",
+ connector_instance_id="ci1",
+ display_name="Updated Production Instance"
+)
+
+# Update multiple fields including parameters
+param = ConnectorInstanceParameter()
+param.value = "new-api-key"
+
+updated_instance = chronicle.update_connector_instance(
+ integration_name="MyIntegration",
+ connector_id="c1",
+ connector_instance_id="ci1",
+ display_name="Updated Instance",
+ interval_seconds=7200, # Change to every 2 hours
+ parameters=[param],
+ enabled=True
+)
+```
+
+Delete a connector instance:
+
+```python
+chronicle.delete_connector_instance(
+ integration_name="MyIntegration",
+ connector_id="c1",
+ connector_instance_id="ci1"
+)
+```
+
+Refresh instance with latest connector definition:
+
+```python
+# Fetch latest definition from marketplace
+refreshed_instance = chronicle.get_connector_instance_latest_definition(
+ integration_name="MyIntegration",
+ connector_id="c1",
+ connector_instance_id="ci1"
+)
+print(f"Updated to latest definition")
+```
+
+Enable/disable logs collection for debugging:
+
+```python
+# Enable logs collection
+result = chronicle.set_connector_instance_logs_collection(
+ integration_name="MyIntegration",
+ connector_id="c1",
+ connector_instance_id="ci1",
+ enabled=True
+)
+print(f"Logs enabled until: {result.get('loggingEnabledUntilUnixMs')}")
+
+# Disable logs collection
+chronicle.set_connector_instance_logs_collection(
+ integration_name="MyIntegration",
+ connector_id="c1",
+ connector_instance_id="ci1",
+ enabled=False
+)
+```
+
+Run a connector instance on demand for testing:
+
+```python
+# Get the current instance configuration
+instance = chronicle.get_connector_instance(
+ integration_name="MyIntegration",
+ connector_id="c1",
+ connector_instance_id="ci1"
+)
+
+# Run on demand to test configuration
+test_result = chronicle.run_connector_instance_on_demand(
+ integration_name="MyIntegration",
+ connector_id="c1",
+ connector_instance_id="ci1",
+ connector_instance=instance
+)
+
+if test_result.get("success"):
+ print("Test execution successful!")
+ print(f"Debug output: {test_result.get('debugOutput')}")
+else:
+ print("Test execution failed")
+ print(f"Error: {test_result.get('debugOutput')}")
+```
+
+Example workflow: Deploy and test a new connector instance:
+
+```python
+from secops.chronicle.models import ConnectorInstanceParameter
+
+# 1. Create a new connector instance
+param = ConnectorInstanceParameter()
+param.value = "test-api-key"
+
+new_instance = chronicle.create_connector_instance(
+ integration_name="MyIntegration",
+ connector_id="c1",
+ environment="development",
+ display_name="Dev Test Instance",
+ interval_seconds=3600,
+ timeout_seconds=300,
+ description="Development testing instance",
+ parameters=[param],
+ enabled=False # Start disabled for testing
+)
+
+instance_id = new_instance.get("name").split("/")[-1]
+print(f"Created instance: {instance_id}")
+
+# 2. Enable logs collection for debugging
+chronicle.set_connector_instance_logs_collection(
+ integration_name="MyIntegration",
+ connector_id="c1",
+ connector_instance_id=instance_id,
+ enabled=True
+)
+
+# 3. Run on demand to test
+test_result = chronicle.run_connector_instance_on_demand(
+ integration_name="MyIntegration",
+ connector_id="c1",
+ connector_instance_id=instance_id,
+ connector_instance=new_instance
+)
+
+# 4. Check test results
+if test_result.get("success"):
+ print("✓ Test passed - enabling instance")
+ # Enable the instance for scheduled runs
+ chronicle.update_connector_instance(
+ integration_name="MyIntegration",
+ connector_id="c1",
+ connector_instance_id=instance_id,
+ enabled=True
+ )
+else:
+ print("✗ Test failed - reviewing logs")
+ # Get logs to debug the issue
+ logs = chronicle.list_connector_instance_logs(
+ integration_name="MyIntegration",
+ connector_id="c1",
+ connector_instance_id=instance_id,
+ filter_string='severity = "ERROR"',
+ as_list=True
+ )
+ for log in logs:
+ print(f"Error: {log.get('message')}")
+
+# 5. Monitor execution after enabling
+instances = chronicle.list_connector_instances(
+ integration_name="MyIntegration",
+ connector_id="c1",
+ filter_string=f'name = "{new_instance.get("name")}"',
+ as_list=True
+)
+if instances:
+ print(f"Instance status: Enabled={instances[0].get('enabled')}")
+```
+
+### Integration Jobs
+
+List all available jobs for an integration:
+
+```python
+# Get all jobs for an integration
+jobs = chronicle.list_integration_jobs("MyIntegration")
+for job in jobs.get("jobs", []):
+ print(f"Job: {job.get('displayName')}, ID: {job.get('name')}")
+
+# Get all jobs as a list
+jobs = chronicle.list_integration_jobs("MyIntegration", as_list=True)
+
+# Get only custom jobs
+jobs = chronicle.list_integration_jobs(
+ "MyIntegration",
+ filter_string="custom = true"
+)
+
+# Exclude staging jobs
+jobs = chronicle.list_integration_jobs(
+ "MyIntegration",
+ exclude_staging=True
+)
+```
+
+Get details of a specific job:
+
+```python
+job = chronicle.get_integration_job(
+ integration_name="MyIntegration",
+ job_id="123"
+)
+```
+
+Create an integration job:
+
+```python
+from secops.chronicle.models import JobParameter, ParamType
+
+new_job = chronicle.create_integration_job(
+ integration_name="MyIntegration",
+ display_name="Scheduled Sync Job",
+ description="Syncs data from external source",
+ script="print('Running scheduled job...')",
+ version=1,
+ enabled=True,
+ custom=True,
+ parameters=[
+ JobParameter(
+ id=1,
+ display_name="Sync Interval",
+ description="Interval in minutes",
+ type=ParamType.INT,
+ mandatory=True,
+ default_value="60"
+ )
+ ]
+)
+```
+
+Update an integration job:
+
+```python
+from secops.chronicle.models import JobParameter, ParamType
+
+updated_job = chronicle.update_integration_job(
+ integration_name="MyIntegration",
+ job_id="123",
+ display_name="Updated Job Name",
+ description="Updated description",
+ enabled=False,
+ version=2,
+ parameters=[
+ JobParameter(
+ id=1,
+ display_name="New Parameter",
+ description="Updated parameter",
+ type=ParamType.STRING,
+ mandatory=True,
+ )
+ ],
+ script="print('Updated job script')"
+)
+```
+
+Delete an integration job:
+
+```python
+chronicle.delete_integration_job(
+ integration_name="MyIntegration",
+ job_id="123"
+)
+```
+
+Execute a test run of an integration job:
+
+```python
+# Test a job before saving it
+job = chronicle.get_integration_job(
+ integration_name="MyIntegration",
+ job_id="123"
+)
+
+test_result = chronicle.execute_integration_job_test(
+ integration_name="MyIntegration",
+ job=job
+)
+
+print(f"Output: {test_result.get('output')}")
+print(f"Debug: {test_result.get('debugOutput')}")
+
+# Test with a specific agent for remote execution
+test_result = chronicle.execute_integration_job_test(
+ integration_name="MyIntegration",
+ job=job,
+ agent_identifier="agent-123"
+)
+```
+
+Get a template for creating a job in an integration:
+
+```python
+template = chronicle.get_integration_job_template("MyIntegration")
+print(f"Template script: {template.get('script')}")
+```
+
+### Integration Managers
+
+List all available managers for an integration:
+
+```python
+# Get all managers for an integration
+managers = chronicle.list_integration_managers("MyIntegration")
+for manager in managers.get("managers", []):
+ print(f"Manager: {manager.get('displayName')}, ID: {manager.get('name')}")
+
+# Get all managers as a list
+managers = chronicle.list_integration_managers("MyIntegration", as_list=True)
+
+# Filter managers by display name
+managers = chronicle.list_integration_managers(
+ "MyIntegration",
+ filter_string='displayName = "API Helper"'
+)
+
+# Sort managers by display name
+managers = chronicle.list_integration_managers(
+ "MyIntegration",
+ order_by="displayName"
+)
+```
+
+Get details of a specific manager:
+
+```python
+manager = chronicle.get_integration_manager(
+ integration_name="MyIntegration",
+ manager_id="123"
+)
+```
+
+Create an integration manager:
+
+```python
+new_manager = chronicle.create_integration_manager(
+ integration_name="MyIntegration",
+ display_name="API Helper",
+ description="Shared utility functions for API calls",
+ script="""
+def make_api_request(url, headers=None):
+ '''Helper function to make API requests'''
+ import requests
+ return requests.get(url, headers=headers)
+
+def parse_response(response):
+ '''Parse API response'''
+ return response.json()
+"""
+)
+```
+
+Update an integration manager:
+
+```python
+updated_manager = chronicle.update_integration_manager(
+ integration_name="MyIntegration",
+ manager_id="123",
+ display_name="Updated API Helper",
+ description="Updated shared utility functions",
+ script="""
+def make_api_request(url, headers=None, method='GET'):
+ '''Updated helper function with method parameter'''
+ import requests
+ if method == 'GET':
+ return requests.get(url, headers=headers)
+ elif method == 'POST':
+ return requests.post(url, headers=headers)
+"""
+)
+
+# Update only specific fields
+updated_manager = chronicle.update_integration_manager(
+ integration_name="MyIntegration",
+ manager_id="123",
+ description="New description only"
+)
+```
+
+Delete an integration manager:
+
+```python
+chronicle.delete_integration_manager(
+ integration_name="MyIntegration",
+ manager_id="123"
+)
+```
+
+Get a template for creating a manager in an integration:
+
+```python
+template = chronicle.get_integration_manager_template("MyIntegration")
+print(f"Template script: {template.get('script')}")
+```
+
+### Integration Manager Revisions
+
+List all revisions for a specific manager:
+
+```python
+# Get all revisions for a manager
+revisions = chronicle.list_integration_manager_revisions(
+ integration_name="MyIntegration",
+ manager_id="123"
+)
+for revision in revisions.get("revisions", []):
+ print(f"Revision: {revision.get('name')}, Comment: {revision.get('comment')}")
+
+# Get all revisions as a list
+revisions = chronicle.list_integration_manager_revisions(
+ integration_name="MyIntegration",
+ manager_id="123",
+ as_list=True
+)
+
+# Filter revisions
+revisions = chronicle.list_integration_manager_revisions(
+ integration_name="MyIntegration",
+ manager_id="123",
+ filter_string='comment contains "backup"',
+ order_by="createTime desc"
+)
+```
+
+Get details of a specific revision:
+
+```python
+revision = chronicle.get_integration_manager_revision(
+ integration_name="MyIntegration",
+ manager_id="123",
+ revision_id="r1"
+)
+print(f"Revision script: {revision.get('manager', {}).get('script')}")
+```
+
+Create a new revision snapshot:
+
+```python
+# Get the current manager
+manager = chronicle.get_integration_manager(
+ integration_name="MyIntegration",
+ manager_id="123"
+)
+
+# Create a revision before making changes
+revision = chronicle.create_integration_manager_revision(
+ integration_name="MyIntegration",
+ manager_id="123",
+ manager=manager,
+ comment="Backup before major refactor"
+)
+print(f"Created revision: {revision.get('name')}")
+```
+
+Rollback to a previous revision:
+
+```python
+# Rollback to a previous working version
+rollback_result = chronicle.rollback_integration_manager_revision(
+ integration_name="MyIntegration",
+ manager_id="123",
+ revision_id="acb123de-abcd-1234-ef00-1234567890ab"
+)
+print(f"Rolled back to: {rollback_result.get('name')}")
+```
+
+Delete a revision:
+
+```python
+chronicle.delete_integration_manager_revision(
+ integration_name="MyIntegration",
+ manager_id="123",
+ revision_id="r1"
+)
+```
+
+### Integration Job Revisions
+
+List all revisions for a specific job:
+
+```python
+# Get all revisions for a job
+revisions = chronicle.list_integration_job_revisions(
+ integration_name="MyIntegration",
+ job_id="456"
+)
+for revision in revisions.get("revisions", []):
+ print(f"Revision: {revision.get('name')}, Comment: {revision.get('comment')}")
+
+# Get all revisions as a list
+revisions = chronicle.list_integration_job_revisions(
+ integration_name="MyIntegration",
+ job_id="456",
+ as_list=True
+)
+
+# Filter revisions by version
+revisions = chronicle.list_integration_job_revisions(
+ integration_name="MyIntegration",
+ job_id="456",
+ filter_string='version = "2"',
+ order_by="createTime desc"
+)
+```
+
+Delete a job revision:
+
+```python
+chronicle.delete_integration_job_revision(
+ integration_name="MyIntegration",
+ job_id="456",
+ revision_id="r2"
+)
+```
+
+Create a new job revision snapshot:
+
+```python
+# Get the current job
+job = chronicle.get_integration_job(
+ integration_name="MyIntegration",
+ job_id="456"
+)
+
+# Create a revision before making changes
+revision = chronicle.create_integration_job_revision(
+ integration_name="MyIntegration",
+ job_id="456",
+ job=job,
+ comment="Backup before scheduled update"
+)
+print(f"Created revision: {revision.get('name')}")
+```
+
+Rollback to a previous job revision:
+
+```python
+# Rollback to a previous working version
+rollback_result = chronicle.rollback_integration_job_revision(
+ integration_name="MyIntegration",
+ job_id="456",
+ revision_id="r2"
+)
+print(f"Rolled back to: {rollback_result.get('name')}")
+```
+
+### Integration Job Instances
+
+List all job instances for a specific job:
+
+```python
+# Get all job instances for a job
+job_instances = chronicle.list_integration_job_instances(
+ integration_name="MyIntegration",
+ job_id="456"
+)
+for instance in job_instances.get("jobInstances", []):
+ print(f"Instance: {instance.get('displayName')}, Enabled: {instance.get('enabled')}")
+
+# Get all job instances as a list
+job_instances = chronicle.list_integration_job_instances(
+ integration_name="MyIntegration",
+ job_id="456",
+ as_list=True
+)
+
+# Filter job instances
+job_instances = chronicle.list_integration_job_instances(
+ integration_name="MyIntegration",
+ job_id="456",
+ filter_string="enabled = true",
+ order_by="displayName"
+)
+```
+
+Get details of a specific job instance:
+
+```python
+job_instance = chronicle.get_integration_job_instance(
+ integration_name="MyIntegration",
+ job_id="456",
+ job_instance_id="ji1"
+)
+print(f"Interval: {job_instance.get('intervalSeconds')} seconds")
+```
+
+Create a new job instance:
+
+```python
+from secops.chronicle.models import IntegrationJobInstanceParameter
+
+# Create a job instance with basic scheduling (interval-based)
+new_job_instance = chronicle.create_integration_job_instance(
+ integration_name="MyIntegration",
+ job_id="456",
+ display_name="Daily Data Sync",
+ description="Syncs data from external source daily",
+ interval_seconds=86400, # 24 hours
+ enabled=True,
+ advanced=False,
+ parameters=[
+ IntegrationJobInstanceParameter(value="production"),
+ IntegrationJobInstanceParameter(value="https://api.example.com")
+ ]
+)
+```
+
+Create a job instance with advanced scheduling:
+
+```python
+from secops.chronicle.models import (
+ AdvancedConfig,
+ ScheduleType,
+ DailyScheduleDetails,
+ Date,
+ TimeOfDay
+)
+
+# Create with daily schedule
+advanced_job_instance = chronicle.create_integration_job_instance(
+ integration_name="MyIntegration",
+ job_id="456",
+ display_name="Daily Backup at 2 AM",
+ interval_seconds=86400,
+ enabled=True,
+ advanced=True,
+ advanced_config=AdvancedConfig(
+ time_zone="America/New_York",
+ schedule_type=ScheduleType.DAILY,
+ daily_schedule=DailyScheduleDetails(
+ start_date=Date(year=2025, month=1, day=1),
+ time=TimeOfDay(hours=2, minutes=0),
+ interval=1 # Every 1 day
+ )
+ ),
+ agent="agent-123" # For remote execution
+)
+```
+
+Create a job instance with weekly schedule:
+
+```python
+from secops.chronicle.models import (
+ AdvancedConfig,
+ ScheduleType,
+ WeeklyScheduleDetails,
+ DayOfWeek,
+ Date,
+ TimeOfDay
+)
+
+# Run every Monday and Friday at 9 AM
+weekly_job_instance = chronicle.create_integration_job_instance(
+ integration_name="MyIntegration",
+ job_id="456",
+ display_name="Weekly Report",
+ interval_seconds=604800, # 1 week
+ enabled=True,
+ advanced=True,
+ advanced_config=AdvancedConfig(
+ time_zone="UTC",
+ schedule_type=ScheduleType.WEEKLY,
+ weekly_schedule=WeeklyScheduleDetails(
+ start_date=Date(year=2025, month=1, day=1),
+ days=[DayOfWeek.MONDAY, DayOfWeek.FRIDAY],
+ time=TimeOfDay(hours=9, minutes=0),
+ interval=1 # Every 1 week
+ )
+ )
+)
+```
+
+Create a job instance with monthly schedule:
+
+```python
+from secops.chronicle.models import (
+ AdvancedConfig,
+ ScheduleType,
+ MonthlyScheduleDetails,
+ Date,
+ TimeOfDay
+)
+
+# Run on the 1st of every month at midnight
+monthly_job_instance = chronicle.create_integration_job_instance(
+ integration_name="MyIntegration",
+ job_id="456",
+ display_name="Monthly Cleanup",
+ interval_seconds=2592000, # ~30 days
+ enabled=True,
+ advanced=True,
+ advanced_config=AdvancedConfig(
+ time_zone="America/Los_Angeles",
+ schedule_type=ScheduleType.MONTHLY,
+ monthly_schedule=MonthlyScheduleDetails(
+ start_date=Date(year=2025, month=1, day=1),
+ day=1, # Day of month (1-31)
+ time=TimeOfDay(hours=0, minutes=0),
+ interval=1 # Every 1 month
+ )
+ )
+)
+```
+
+Create a one-time job instance:
+
+```python
+from secops.chronicle.models import (
+ AdvancedConfig,
+ ScheduleType,
+ OneTimeScheduleDetails,
+ Date,
+ TimeOfDay
+)
+
+# Run once at a specific date and time
+onetime_job_instance = chronicle.create_integration_job_instance(
+ integration_name="MyIntegration",
+ job_id="456",
+ display_name="One-Time Migration",
+ interval_seconds=0, # Not used for one-time
+ enabled=True,
+ advanced=True,
+ advanced_config=AdvancedConfig(
+ time_zone="Europe/London",
+ schedule_type=ScheduleType.ONCE,
+ one_time_schedule=OneTimeScheduleDetails(
+ start_date=Date(year=2025, month=12, day=25),
+ time=TimeOfDay(hours=10, minutes=30)
+ )
+ )
+)
+```
+
+Update a job instance:
+
+```python
+from secops.chronicle.models import IntegrationJobInstanceParameter
+
+# Update scheduling and enable/disable
+updated_instance = chronicle.update_integration_job_instance(
+ integration_name="MyIntegration",
+ job_id="456",
+ job_instance_id="ji1",
+ display_name="Updated Sync Job",
+ interval_seconds=43200, # 12 hours
+ enabled=False
+)
+
+# Update parameters
+updated_instance = chronicle.update_integration_job_instance(
+ integration_name="MyIntegration",
+ job_id="456",
+ job_instance_id="ji1",
+ parameters=[
+ IntegrationJobInstanceParameter(value="staging"),
+ IntegrationJobInstanceParameter(value="https://staging-api.example.com")
+ ]
+)
+
+# Update to use advanced scheduling
+from secops.chronicle.models import (
+ AdvancedConfig,
+ ScheduleType,
+ DailyScheduleDetails,
+ Date,
+ TimeOfDay
+)
+
+updated_instance = chronicle.update_integration_job_instance(
+ integration_name="MyIntegration",
+ job_id="456",
+ job_instance_id="ji1",
+ advanced=True,
+ advanced_config=AdvancedConfig(
+ time_zone="UTC",
+ schedule_type=ScheduleType.DAILY,
+ daily_schedule=DailyScheduleDetails(
+ start_date=Date(year=2025, month=1, day=1),
+ time=TimeOfDay(hours=12, minutes=0),
+ interval=1
+ )
+ )
+)
+
+# Update only specific fields
+updated_instance = chronicle.update_integration_job_instance(
+ integration_name="MyIntegration",
+ job_id="456",
+ job_instance_id="ji1",
+ enabled=True,
+ update_mask="enabled"
+)
+```
+
+Delete a job instance:
+
+```python
+chronicle.delete_integration_job_instance(
+ integration_name="MyIntegration",
+ job_id="456",
+ job_instance_id="ji1"
+)
+```
+
+Run a job instance on demand:
+
+```python
+# Run immediately without waiting for schedule
+result = chronicle.run_integration_job_instance_on_demand(
+ integration_name="MyIntegration",
+ job_id="456",
+ job_instance_id="ji1"
+)
+print(f"Job execution started: {result}")
+
+# Run with parameter overrides
+result = chronicle.run_integration_job_instance_on_demand(
+ integration_name="MyIntegration",
+ job_id="456",
+ job_instance_id="ji1",
+ parameters=[
+ IntegrationJobInstanceParameter(id=1, value="test-mode")
+ ]
+)
+```
+
+### Job Context Properties
+
+List all context properties for a job:
+
+```python
+# Get all context properties for a job
+context_properties = chronicle.list_job_context_properties(
+ integration_name="MyIntegration",
+ job_id="456"
+)
+for prop in context_properties.get("contextProperties", []):
+ print(f"Key: {prop.get('key')}, Value: {prop.get('value')}")
+
+# Get all context properties as a list
+context_properties = chronicle.list_job_context_properties(
+ integration_name="MyIntegration",
+ job_id="456",
+ as_list=True
+)
+
+# Filter context properties
+context_properties = chronicle.list_job_context_properties(
+ integration_name="MyIntegration",
+ job_id="456",
+ filter_string='key = "api-token"',
+ order_by="key"
+)
+```
+
+Get a specific context property:
+
+```python
+property_value = chronicle.get_job_context_property(
+ integration_name="MyIntegration",
+ job_id="456",
+ context_property_id="api-endpoint"
+)
+print(f"Value: {property_value.get('value')}")
+```
+
+Create a new context property:
+
+```python
+# Create with auto-generated key
+new_property = chronicle.create_job_context_property(
+ integration_name="MyIntegration",
+ job_id="456",
+ value="https://api.example.com/v2"
+)
+print(f"Created property: {new_property.get('key')}")
+
+# Create with custom key (must be 4-63 chars, match /[a-z][0-9]-/)
+new_property = chronicle.create_job_context_property(
+ integration_name="MyIntegration",
+ job_id="456",
+ value="my-secret-token",
+ key="apitoken"
+)
+```
+
+Update a context property:
+
+```python
+# Update the value of an existing property
+updated_property = chronicle.update_job_context_property(
+ integration_name="MyIntegration",
+ job_id="456",
+ context_property_id="api-endpoint",
+ value="https://api.example.com/v3"
+)
+print(f"Updated to: {updated_property.get('value')}")
+```
+
+Delete a context property:
+
+```python
+chronicle.delete_job_context_property(
+ integration_name="MyIntegration",
+ job_id="456",
+ context_property_id="api-endpoint"
+)
+```
+
+Delete all context properties:
+
+```python
+# Clear all context properties for a job
+chronicle.delete_all_job_context_properties(
+ integration_name="MyIntegration",
+ job_id="456"
+)
+
+# Clear all properties for a specific context ID
+chronicle.delete_all_job_context_properties(
+ integration_name="MyIntegration",
+ job_id="456",
+ context_id="mycontext"
+)
+```
+
+### Job Instance Logs
+
+List all execution logs for a job instance:
+
+```python
+# Get all logs for a job instance
+logs = chronicle.list_job_instance_logs(
+ integration_name="MyIntegration",
+ job_id="456",
+ job_instance_id="ji1"
+)
+for log in logs.get("logs", []):
+ print(f"Log ID: {log.get('name')}, Status: {log.get('status')}")
+ print(f"Start: {log.get('startTime')}, End: {log.get('endTime')}")
+
+# Get all logs as a list
+logs = chronicle.list_job_instance_logs(
+ integration_name="MyIntegration",
+ job_id="456",
+ job_instance_id="ji1",
+ as_list=True
+)
+
+# Filter logs by status
+logs = chronicle.list_job_instance_logs(
+ integration_name="MyIntegration",
+ job_id="456",
+ job_instance_id="ji1",
+ filter_string="status = SUCCESS",
+ order_by="startTime desc"
+)
+```
+
+Get a specific log entry:
+
+```python
+log_entry = chronicle.get_job_instance_log(
+ integration_name="MyIntegration",
+ job_id="456",
+ job_instance_id="ji1",
+ log_id="log123"
+)
+print(f"Status: {log_entry.get('status')}")
+print(f"Start Time: {log_entry.get('startTime')}")
+print(f"End Time: {log_entry.get('endTime')}")
+print(f"Output: {log_entry.get('output')}")
+```
+
+Browse historical execution logs to monitor job performance:
+
+```python
+# Get recent logs for monitoring
+recent_logs = chronicle.list_job_instance_logs(
+ integration_name="MyIntegration",
+ job_id="456",
+ job_instance_id="ji1",
+ order_by="startTime desc",
+ page_size=10,
+ as_list=True
+)
+
+# Check for failures
+for log in recent_logs:
+ if log.get("status") == "FAILED":
+ print(f"Failed execution at {log.get('startTime')}")
+ log_details = chronicle.get_job_instance_log(
+ integration_name="MyIntegration",
+ job_id="456",
+ job_instance_id="ji1",
+ log_id=log.get("name").split("/")[-1]
+ )
+ print(f"Error output: {log_details.get('output')}")
+```
+
+Monitor job reliability and performance:
+
+```python
+# Get all logs to calculate success rate
+all_logs = chronicle.list_job_instance_logs(
+ integration_name="MyIntegration",
+ job_id="456",
+ job_instance_id="ji1",
+ as_list=True
+)
+
+successful = sum(1 for log in all_logs if log.get("status") == "SUCCESS")
+failed = sum(1 for log in all_logs if log.get("status") == "FAILED")
+total = len(all_logs)
+
+if total > 0:
+ success_rate = (successful / total) * 100
+ print(f"Success Rate: {success_rate:.2f}%")
+ print(f"Total Executions: {total}")
+ print(f"Successful: {successful}, Failed: {failed}")
+```
+
+### Integration Instances
+
+List all instances for a specific integration:
+
+```python
+# Get all instances for an integration
+instances = chronicle.list_integration_instances("MyIntegration")
+for instance in instances.get("integrationInstances", []):
+ print(f"Instance: {instance.get('displayName')}, ID: {instance.get('name')}")
+ print(f"Environment: {instance.get('environment')}")
+
+# Get all instances as a list
+instances = chronicle.list_integration_instances("MyIntegration", as_list=True)
+
+# Get instances for a specific environment
+instances = chronicle.list_integration_instances(
+ "MyIntegration",
+ filter_string="environment = 'production'"
+)
+```
+
+Get details of a specific integration instance:
+
+```python
+instance = chronicle.get_integration_instance(
+ integration_name="MyIntegration",
+ integration_instance_id="ii1"
+)
+print(f"Display Name: {instance.get('displayName')}")
+print(f"Environment: {instance.get('environment')}")
+print(f"Agent: {instance.get('agent')}")
+```
+
+Create a new integration instance:
+
+```python
+from secops.chronicle.models import IntegrationInstanceParameter
+
+# Create instance with required fields only
+new_instance = chronicle.create_integration_instance(
+ integration_name="MyIntegration",
+ environment="production"
+)
+
+# Create instance with all fields
+new_instance = chronicle.create_integration_instance(
+ integration_name="MyIntegration",
+ environment="production",
+ display_name="Production Instance",
+ description="Main production integration instance",
+ parameters=[
+ IntegrationInstanceParameter(
+ value="api_key_value"
+ ),
+ IntegrationInstanceParameter(
+ value="https://api.example.com"
+ )
+ ],
+ agent="agent-123"
+)
+```
+
+Update an existing integration instance:
+
+```python
+from secops.chronicle.models import IntegrationInstanceParameter
+
+# Update instance display name
+updated_instance = chronicle.update_integration_instance(
+ integration_name="MyIntegration",
+ integration_instance_id="ii1",
+ display_name="Updated Production Instance"
+)
+
+# Update multiple fields including parameters
+updated_instance = chronicle.update_integration_instance(
+ integration_name="MyIntegration",
+ integration_instance_id="ii1",
+ display_name="Updated Instance",
+ description="Updated description",
+ environment="staging",
+ parameters=[
+ IntegrationInstanceParameter(
+ value="new_api_key"
+ )
+ ]
+)
+
+# Use custom update mask
+updated_instance = chronicle.update_integration_instance(
+ integration_name="MyIntegration",
+ integration_instance_id="ii1",
+ display_name="New Name",
+ update_mask="displayName"
+)
+```
+
+Delete an integration instance:
+
+```python
+chronicle.delete_integration_instance(
+ integration_name="MyIntegration",
+ integration_instance_id="ii1"
+)
+```
+
+Execute a connectivity test for an integration instance:
+
+```python
+# Test if the instance can connect to the third-party service
+test_result = chronicle.execute_integration_instance_test(
+ integration_name="MyIntegration",
+ integration_instance_id="ii1"
+)
+print(f"Test Successful: {test_result.get('successful')}")
+print(f"Message: {test_result.get('message')}")
+```
+
+Get affected items (playbooks) that depend on an integration instance:
+
+```python
+# Perform impact analysis before deleting or modifying an instance
+affected_items = chronicle.get_integration_instance_affected_items(
+ integration_name="MyIntegration",
+ integration_instance_id="ii1"
+)
+for playbook in affected_items.get("affectedPlaybooks", []):
+ print(f"Playbook: {playbook.get('displayName')}")
+ print(f" ID: {playbook.get('name')}")
+```
+
+Get the default integration instance:
+
+```python
+# Get the system default configuration for a commercial product
+default_instance = chronicle.get_default_integration_instance(
+ integration_name="AWSSecurityHub"
+)
+print(f"Default Instance: {default_instance.get('displayName')}")
+print(f"Environment: {default_instance.get('environment')}")
+```
+
+### Integration Transformers
+
+List all transformers for a specific integration:
+
+```python
+# Get all transformers for an integration
+transformers = chronicle.list_integration_transformers("MyIntegration")
+for transformer in transformers.get("transformers", []):
+ print(f"Transformer: {transformer.get('displayName')}, ID: {transformer.get('name')}")
+
+# Get all transformers as a list
+transformers = chronicle.list_integration_transformers("MyIntegration", as_list=True)
+
+# Get only enabled transformers
+transformers = chronicle.list_integration_transformers(
+ "MyIntegration",
+ filter_string="enabled = true"
+)
+
+# Exclude staging transformers
+transformers = chronicle.list_integration_transformers(
+ "MyIntegration",
+ exclude_staging=True
+)
+
+# Get transformers with expanded details
+transformers = chronicle.list_integration_transformers(
+ "MyIntegration",
+ expand="parameters"
+)
+```
+
+Get details of a specific transformer:
+
+```python
+transformer = chronicle.get_integration_transformer(
+ integration_name="MyIntegration",
+ transformer_id="t1"
+)
+print(f"Display Name: {transformer.get('displayName')}")
+print(f"Script: {transformer.get('script')}")
+print(f"Enabled: {transformer.get('enabled')}")
+
+# Get transformer with expanded parameters
+transformer = chronicle.get_integration_transformer(
+ integration_name="MyIntegration",
+ transformer_id="t1",
+ expand="parameters"
+)
+```
+
+Create a new transformer:
+
+```python
+# Create a basic transformer
+new_transformer = chronicle.create_integration_transformer(
+ integration_name="MyIntegration",
+ display_name="JSON Parser",
+ script="""
+def transform(data):
+ import json
+ try:
+ return json.loads(data)
+ except Exception as e:
+ return {"error": str(e)}
+""",
+ script_timeout="60s",
+ enabled=True
+)
+
+# Create transformer with all fields
+new_transformer = chronicle.create_integration_transformer(
+ integration_name="MyIntegration",
+ display_name="Advanced Data Transformer",
+ description="Transforms and enriches incoming data",
+ script="""
+def transform(data, api_key, endpoint_url):
+ import json
+ import requests
+
+ # Parse input data
+ parsed = json.loads(data)
+
+ # Enrich with external API call
+ response = requests.get(
+ endpoint_url,
+ headers={"Authorization": f"Bearer {api_key}"}
+ )
+ parsed["enrichment"] = response.json()
+
+ return parsed
+""",
+ script_timeout="120s",
+ enabled=True,
+ parameters=[
+ {
+ "name": "api_key",
+ "type": "STRING",
+ "displayName": "API Key",
+ "mandatory": True
+ },
+ {
+ "name": "endpoint_url",
+ "type": "STRING",
+ "displayName": "Endpoint URL",
+ "mandatory": True
+ }
+ ],
+ usage_example="Used to enrich security events with external threat intelligence",
+ expected_input='{"event": "data", "timestamp": "2024-01-01T00:00:00Z"}',
+ expected_output='{"event": "data", "timestamp": "2024-01-01T00:00:00Z", "enrichment": {...}}'
+)
+```
+
+Update an existing transformer:
+
+```python
+# Update transformer display name
+updated_transformer = chronicle.update_integration_transformer(
+ integration_name="MyIntegration",
+ transformer_id="t1",
+ display_name="Updated Transformer Name"
+)
+
+# Update transformer script
+updated_transformer = chronicle.update_integration_transformer(
+ integration_name="MyIntegration",
+ transformer_id="t1",
+ script="""
+def transform(data):
+ # Updated transformation logic
+ return data.upper()
+"""
+)
+
+# Update multiple fields including parameters
+updated_transformer = chronicle.update_integration_transformer(
+ integration_name="MyIntegration",
+ transformer_id="t1",
+ display_name="Enhanced Transformer",
+ description="Updated with better error handling",
+ script="""
+def transform(data, timeout=30):
+ import json
+ try:
+ result = json.loads(data)
+ result["processed"] = True
+ return result
+ except Exception as e:
+ return {"error": str(e), "original": data}
+""",
+ script_timeout="90s",
+ enabled=True,
+ parameters=[
+ {
+ "name": "timeout",
+ "type": "INTEGER",
+ "displayName": "Processing Timeout",
+ "mandatory": False,
+ "defaultValue": "30"
+ }
+ ]
+)
+
+# Use custom update mask
+updated_transformer = chronicle.update_integration_transformer(
+ integration_name="MyIntegration",
+ transformer_id="t1",
+ display_name="New Name",
+ description="New Description",
+ update_mask="displayName,description"
+)
+```
+
+Delete a transformer:
+
+```python
+chronicle.delete_integration_transformer(
+ integration_name="MyIntegration",
+ transformer_id="t1"
+)
+```
+
+Execute a test run of a transformer:
+
+```python
+# Get the transformer
+transformer = chronicle.get_integration_transformer(
+ integration_name="MyIntegration",
+ transformer_id="t1"
+)
+
+# Test the transformer with sample data
+test_result = chronicle.execute_integration_transformer_test(
+ integration_name="MyIntegration",
+ transformer=transformer
+)
+print(f"Output Message: {test_result.get('outputMessage')}")
+print(f"Debug Output: {test_result.get('debugOutputMessage')}")
+print(f"Result Value: {test_result.get('resultValue')}")
+
+# You can also test a transformer before creating it
+test_transformer = {
+ "displayName": "Test Transformer",
+ "script": """
+def transform(data):
+ return {"transformed": data, "status": "success"}
+""",
+ "scriptTimeout": "60s",
+ "enabled": True
+}
+
+test_result = chronicle.execute_integration_transformer_test(
+ integration_name="MyIntegration",
+ transformer=test_transformer
+)
+```
+
+Get a template for creating a transformer:
+
+```python
+# Get a boilerplate template for a new transformer
+template = chronicle.get_integration_transformer_template("MyIntegration")
+print(f"Template Script: {template.get('script')}")
+print(f"Template Display Name: {template.get('displayName')}")
+
+# Use the template as a starting point
+new_transformer = chronicle.create_integration_transformer(
+ integration_name="MyIntegration",
+ display_name="My Custom Transformer",
+ script=template.get('script'), # Customize this
+ script_timeout="60s",
+ enabled=True
+)
+```
+
+Example workflow: Safe transformer development with testing:
+
+```python
+# 1. Get a template to start with
+template = chronicle.get_integration_transformer_template("MyIntegration")
+
+# 2. Customize the script
+custom_transformer = {
+ "displayName": "CSV to JSON Transformer",
+ "description": "Converts CSV data to JSON format",
+ "script": """
+def transform(data):
+ import csv
+ import json
+ from io import StringIO
+
+ # Parse CSV
+ reader = csv.DictReader(StringIO(data))
+ rows = list(reader)
+
+ return json.dumps(rows)
+""",
+ "scriptTimeout": "60s",
+ "enabled": False, # Start disabled for testing
+ "usageExample": "Input CSV with headers, output JSON array of objects"
+}
+
+# 3. Test the transformer before creating it
+test_result = chronicle.execute_integration_transformer_test(
+ integration_name="MyIntegration",
+ transformer=custom_transformer
+)
+
+# 4. If test is successful, create the transformer
+if test_result.get('resultValue'):
+ created_transformer = chronicle.create_integration_transformer(
+ integration_name="MyIntegration",
+ display_name=custom_transformer["displayName"],
+ description=custom_transformer["description"],
+ script=custom_transformer["script"],
+ script_timeout=custom_transformer["scriptTimeout"],
+ enabled=True, # Enable after successful testing
+ usage_example=custom_transformer["usageExample"]
+ )
+ print(f"Transformer created: {created_transformer.get('name')}")
+else:
+ print(f"Test failed: {test_result.get('debugOutputMessage')}")
+
+# 5. Continue testing and refining
+transformer_id = created_transformer.get('name').split('/')[-1]
+updated = chronicle.update_integration_transformer(
+ integration_name="MyIntegration",
+ transformer_id=transformer_id,
+ script="""
+def transform(data, delimiter=','):
+ import csv
+ import json
+ from io import StringIO
+
+ # Parse CSV with custom delimiter
+ reader = csv.DictReader(StringIO(data), delimiter=delimiter)
+ rows = list(reader)
+
+ return json.dumps(rows, indent=2)
+""",
+ parameters=[
+ {
+ "name": "delimiter",
+ "type": "STRING",
+ "displayName": "CSV Delimiter",
+ "mandatory": False,
+ "defaultValue": ","
+ }
+ ]
+)
+```
+
+### Integration Transformer Revisions
+
+List all revisions for a transformer:
+
+```python
+# Get all revisions for a transformer
+revisions = chronicle.list_integration_transformer_revisions(
+ integration_name="MyIntegration",
+ transformer_id="t1"
+)
+for revision in revisions.get("revisions", []):
+ print(f"Revision: {revision.get('name')}, Comment: {revision.get('comment')}")
+
+# Get all revisions as a list
+revisions = chronicle.list_integration_transformer_revisions(
+ integration_name="MyIntegration",
+ transformer_id="t1",
+ as_list=True
+)
+
+# Filter revisions
+revisions = chronicle.list_integration_transformer_revisions(
+ integration_name="MyIntegration",
+ transformer_id="t1",
+ filter_string='version = "1.0"',
+ order_by="createTime desc"
+)
+```
+
+Delete a specific transformer revision:
+
+```python
+chronicle.delete_integration_transformer_revision(
+ integration_name="MyIntegration",
+ transformer_id="t1",
+ revision_id="rev-456"
+)
+```
+
+Create a new revision before making changes:
+
+```python
+# Get the current transformer
+transformer = chronicle.get_integration_transformer(
+ integration_name="MyIntegration",
+ transformer_id="t1"
+)
+
+# Create a backup revision
+new_revision = chronicle.create_integration_transformer_revision(
+ integration_name="MyIntegration",
+ transformer_id="t1",
+ transformer=transformer,
+ comment="Backup before major refactor"
+)
+print(f"Created revision: {new_revision.get('name')}")
+
+# Create revision with custom comment
+new_revision = chronicle.create_integration_transformer_revision(
+ integration_name="MyIntegration",
+ transformer_id="t1",
+ transformer=transformer,
+ comment="Version 2.0 - Enhanced error handling"
+)
+```
+
+Rollback to a previous revision:
+
+```python
+# Rollback to a previous working version
+rollback_result = chronicle.rollback_integration_transformer_revision(
+ integration_name="MyIntegration",
+ transformer_id="t1",
+ revision_id="rev-456"
+)
+print(f"Rolled back to: {rollback_result.get('name')}")
+```
+
+Example workflow: Safe transformer updates with revision control:
+
+```python
+# 1. Get the current transformer
+transformer = chronicle.get_integration_transformer(
+ integration_name="MyIntegration",
+ transformer_id="t1"
+)
+
+# 2. Create a backup revision
+backup = chronicle.create_integration_transformer_revision(
+ integration_name="MyIntegration",
+ transformer_id="t1",
+ transformer=transformer,
+ comment="Backup before updating transformation logic"
+)
+
+# 3. Make changes to the transformer
+updated_transformer = chronicle.update_integration_transformer(
+ integration_name="MyIntegration",
+ transformer_id="t1",
+ display_name="Enhanced Transformer",
+ script="""
+def transform(data, enrichment_enabled=True):
+ import json
+
+ try:
+ # Parse input data
+ parsed = json.loads(data)
+
+ # Apply transformations
+ parsed["processed"] = True
+ parsed["timestamp"] = "2024-01-01T00:00:00Z"
+
+ # Optional enrichment
+ if enrichment_enabled:
+ parsed["enriched"] = True
+
+ return json.dumps(parsed)
+ except Exception as e:
+ return json.dumps({"error": str(e), "original": data})
+"""
+)
+
+# 4. Test the updated transformer
+test_result = chronicle.execute_integration_transformer_test(
+ integration_name="MyIntegration",
+ transformer=updated_transformer
+)
+
+# 5. If test fails, rollback to backup
+if not test_result.get("resultValue"):
+ print("Test failed - rolling back")
+ chronicle.rollback_integration_transformer_revision(
+ integration_name="MyIntegration",
+ transformer_id="t1",
+ revision_id=backup.get("name").split("/")[-1]
+ )
+else:
+ print("Test passed - transformer updated successfully")
+
+# 6. List all revisions to see history
+all_revisions = chronicle.list_integration_transformer_revisions(
+ integration_name="MyIntegration",
+ transformer_id="t1",
+ as_list=True
+)
+print(f"Total revisions: {len(all_revisions)}")
+for rev in all_revisions:
+ print(f" - {rev.get('comment', 'No comment')} (ID: {rev.get('name').split('/')[-1]})")
+```
+
+### Integration Logical Operators
+
+List all logical operators for a specific integration:
+
+```python
+# Get all logical operators for an integration
+logical_operators = chronicle.list_integration_logical_operators("MyIntegration")
+for operator in logical_operators.get("logicalOperators", []):
+ print(f"Operator: {operator.get('displayName')}, ID: {operator.get('name')}")
+
+# Get all logical operators as a list
+logical_operators = chronicle.list_integration_logical_operators(
+ "MyIntegration",
+ as_list=True
+)
+
+# Get only enabled logical operators
+logical_operators = chronicle.list_integration_logical_operators(
+ "MyIntegration",
+ filter_string="enabled = true"
+)
+
+# Exclude staging logical operators
+logical_operators = chronicle.list_integration_logical_operators(
+ "MyIntegration",
+ exclude_staging=True
+)
+
+# Get logical operators with expanded details
+logical_operators = chronicle.list_integration_logical_operators(
+ "MyIntegration",
+ expand="parameters"
+)
+```
+
+Get details of a specific logical operator:
+
+```python
+operator = chronicle.get_integration_logical_operator(
+ integration_name="MyIntegration",
+ logical_operator_id="lo1"
+)
+print(f"Display Name: {operator.get('displayName')}")
+print(f"Script: {operator.get('script')}")
+print(f"Enabled: {operator.get('enabled')}")
+
+# Get logical operator with expanded parameters
+operator = chronicle.get_integration_logical_operator(
+ integration_name="MyIntegration",
+ logical_operator_id="lo1",
+ expand="parameters"
+)
+```
+
+Create a new logical operator:
+
+```python
+# Create a basic equality operator
+new_operator = chronicle.create_integration_logical_operator(
+ integration_name="MyIntegration",
+ display_name="Equals Operator",
+ script="""
+def evaluate(a, b):
+ return a == b
+""",
+ script_timeout="60s",
+ enabled=True
+)
+
+# Create a more complex conditional operator with parameters
+new_operator = chronicle.create_integration_logical_operator(
+ integration_name="MyIntegration",
+ display_name="Threshold Checker",
+ description="Checks if a value exceeds a threshold",
+ script="""
+def evaluate(value, threshold, inclusive=False):
+ if inclusive:
+ return value >= threshold
+ else:
+ return value > threshold
+""",
+ script_timeout="30s",
+ enabled=True,
+ parameters=[
+ {
+ "name": "value",
+ "type": "INTEGER",
+ "displayName": "Value to Check",
+ "mandatory": True
+ },
+ {
+ "name": "threshold",
+ "type": "INTEGER",
+ "displayName": "Threshold Value",
+ "mandatory": True
+ },
+ {
+ "name": "inclusive",
+ "type": "BOOLEAN",
+ "displayName": "Inclusive Comparison",
+ "mandatory": False,
+ "defaultValue": "false"
+ }
+ ]
+)
+
+# Create a string matching operator
+pattern_operator = chronicle.create_integration_logical_operator(
+ integration_name="MyIntegration",
+ display_name="Pattern Matcher",
+ description="Matches strings against patterns",
+ script="""
+def evaluate(text, pattern, case_sensitive=True):
+ import re
+ flags = 0 if case_sensitive else re.IGNORECASE
+ return bool(re.search(pattern, text, flags))
+""",
+ script_timeout="60s",
+ enabled=True,
+ parameters=[
+ {
+ "name": "text",
+ "type": "STRING",
+ "displayName": "Text to Match",
+ "mandatory": True
+ },
+ {
+ "name": "pattern",
+ "type": "STRING",
+ "displayName": "Regex Pattern",
+ "mandatory": True
+ },
+ {
+ "name": "case_sensitive",
+ "type": "BOOLEAN",
+ "displayName": "Case Sensitive",
+ "mandatory": False,
+ "defaultValue": "true"
+ }
+ ]
+)
+```
+
+Update an existing logical operator:
+
+```python
+# Update logical operator display name
+updated_operator = chronicle.update_integration_logical_operator(
+ integration_name="MyIntegration",
+ logical_operator_id="lo1",
+ display_name="Updated Operator Name"
+)
+
+# Update logical operator script
+updated_operator = chronicle.update_integration_logical_operator(
+ integration_name="MyIntegration",
+ logical_operator_id="lo1",
+ script="""
+def evaluate(a, b):
+ # Updated logic with type checking
+ if type(a) != type(b):
+ return False
+ return a == b
+"""
+)
+
+# Update multiple fields including parameters
+updated_operator = chronicle.update_integration_logical_operator(
+ integration_name="MyIntegration",
+ logical_operator_id="lo1",
+ display_name="Enhanced Operator",
+ description="Updated with better validation",
+ script="""
+def evaluate(value, min_value, max_value):
+ try:
+ return min_value <= value <= max_value
+ except Exception:
+ return False
+""",
+ script_timeout="45s",
+ enabled=True,
+ parameters=[
+ {
+ "name": "value",
+ "type": "INTEGER",
+ "displayName": "Value",
+ "mandatory": True
+ },
+ {
+ "name": "min_value",
+ "type": "INTEGER",
+ "displayName": "Minimum Value",
+ "mandatory": True
+ },
+ {
+ "name": "max_value",
+ "type": "INTEGER",
+ "displayName": "Maximum Value",
+ "mandatory": True
+ }
+ ]
+)
+
+# Use custom update mask
+updated_operator = chronicle.update_integration_logical_operator(
+ integration_name="MyIntegration",
+ logical_operator_id="lo1",
+ display_name="New Name",
+ description="New Description",
+ update_mask="displayName,description"
+)
+```
+
+Delete a logical operator:
+
+```python
+chronicle.delete_integration_logical_operator(
+ integration_name="MyIntegration",
+ logical_operator_id="lo1"
+)
+```
+
+Execute a test run of a logical operator:
+
+```python
+# Get the logical operator
+operator = chronicle.get_integration_logical_operator(
+ integration_name="MyIntegration",
+ logical_operator_id="lo1"
+)
+
+# Test the logical operator with sample data
+test_result = chronicle.execute_integration_logical_operator_test(
+ integration_name="MyIntegration",
+ logical_operator=operator
+)
+print(f"Output Message: {test_result.get('outputMessage')}")
+print(f"Debug Output: {test_result.get('debugOutputMessage')}")
+print(f"Result Value: {test_result.get('resultValue')}") # True or False
+
+# You can also test a logical operator before creating it
+test_operator = {
+ "displayName": "Test Equality Operator",
+ "script": """
+def evaluate(a, b):
+ return a == b
+""",
+ "scriptTimeout": "30s",
+ "enabled": True
+}
+
+test_result = chronicle.execute_integration_logical_operator_test(
+ integration_name="MyIntegration",
+ logical_operator=test_operator
+)
+```
+
+Get a template for creating a logical operator:
+
+```python
+# Get a boilerplate template for a new logical operator
+template = chronicle.get_integration_logical_operator_template("MyIntegration")
+print(f"Template Script: {template.get('script')}")
+print(f"Template Display Name: {template.get('displayName')}")
+
+# Use the template as a starting point
+new_operator = chronicle.create_integration_logical_operator(
+ integration_name="MyIntegration",
+ display_name="My Custom Operator",
+ script=template.get('script'), # Customize this
+ script_timeout="60s",
+ enabled=True
+)
+```
+
+Example workflow: Building conditional logic for integration workflows:
+
+```python
+# 1. Get a template to start with
+template = chronicle.get_integration_logical_operator_template("MyIntegration")
+
+# 2. Create a custom logical operator for severity checking
+severity_operator = chronicle.create_integration_logical_operator(
+ integration_name="MyIntegration",
+ display_name="Severity Level Check",
+ description="Checks if severity meets minimum threshold",
+ script="""
+def evaluate(severity, min_severity='MEDIUM'):
+ severity_levels = {
+ 'LOW': 1,
+ 'MEDIUM': 2,
+ 'HIGH': 3,
+ 'CRITICAL': 4
+ }
+
+ current_level = severity_levels.get(severity.upper(), 0)
+ min_level = severity_levels.get(min_severity.upper(), 0)
+
+ return current_level >= min_level
+""",
+ script_timeout="30s",
+ enabled=False, # Start disabled for testing
+ parameters=[
+ {
+ "name": "severity",
+ "type": "STRING",
+ "displayName": "Event Severity",
+ "mandatory": True
+ },
+ {
+ "name": "min_severity",
+ "type": "STRING",
+ "displayName": "Minimum Severity",
+ "mandatory": False,
+ "defaultValue": "MEDIUM"
+ }
+ ]
+)
+
+# 3. Test the operator before enabling
+test_result = chronicle.execute_integration_logical_operator_test(
+ integration_name="MyIntegration",
+ logical_operator=severity_operator
+)
+
+# 4. If test is successful, enable the operator
+if test_result.get('resultValue') is not None:
+ operator_id = severity_operator.get('name').split('/')[-1]
+ enabled_operator = chronicle.update_integration_logical_operator(
+ integration_name="MyIntegration",
+ logical_operator_id=operator_id,
+ enabled=True
+ )
+ print(f"Operator enabled: {enabled_operator.get('name')}")
+else:
+ print(f"Test failed: {test_result.get('debugOutputMessage')}")
+
+# 5. Create additional operators for workflow automation
+# IP address validation operator
+ip_validator = chronicle.create_integration_logical_operator(
+ integration_name="MyIntegration",
+ display_name="IP Address Validator",
+ description="Validates if a string is a valid IP address",
+ script="""
+def evaluate(ip_string):
+ import ipaddress
+ try:
+ ipaddress.ip_address(ip_string)
+ return True
+ except ValueError:
+ return False
+""",
+ script_timeout="30s",
+ enabled=True
+)
+
+# Time range checker
+time_checker = chronicle.create_integration_logical_operator(
+ integration_name="MyIntegration",
+ display_name="Business Hours Checker",
+ description="Checks if timestamp falls within business hours",
+ script="""
+def evaluate(timestamp, start_hour=9, end_hour=17):
+ from datetime import datetime
+
+ dt = datetime.fromisoformat(timestamp.replace('Z', '+00:00'))
+ hour = dt.hour
+
+ return start_hour <= hour < end_hour
+""",
+ script_timeout="30s",
+ enabled=True,
+ parameters=[
+ {
+ "name": "timestamp",
+ "type": "STRING",
+ "displayName": "Timestamp",
+ "mandatory": True
+ },
+ {
+ "name": "start_hour",
+ "type": "INTEGER",
+ "displayName": "Business Day Start Hour",
+ "mandatory": False,
+ "defaultValue": "9"
+ },
+ {
+ "name": "end_hour",
+ "type": "INTEGER",
+ "displayName": "Business Day End Hour",
+ "mandatory": False,
+ "defaultValue": "17"
+ }
+ ]
+)
+
+# 6. List all logical operators for the integration
+all_operators = chronicle.list_integration_logical_operators(
+ integration_name="MyIntegration",
+ as_list=True
+)
+print(f"Total logical operators: {len(all_operators)}")
+for op in all_operators:
+ print(f" - {op.get('displayName')} (Enabled: {op.get('enabled')})")
+```
+
+### Integration Logical Operator Revisions
+
+List all revisions for a logical operator:
+
+```python
+# Get all revisions for a logical operator
+revisions = chronicle.list_integration_logical_operator_revisions(
+ integration_name="MyIntegration",
+ logical_operator_id="lo1"
+)
+for revision in revisions.get("revisions", []):
+ print(f"Revision: {revision.get('name')}, Comment: {revision.get('comment')}")
+
+# Get all revisions as a list
+revisions = chronicle.list_integration_logical_operator_revisions(
+ integration_name="MyIntegration",
+ logical_operator_id="lo1",
+ as_list=True
+)
+
+# Filter revisions
+revisions = chronicle.list_integration_logical_operator_revisions(
+ integration_name="MyIntegration",
+ logical_operator_id="lo1",
+ filter_string='version = "1.0"',
+ order_by="createTime desc"
+)
+```
+
+Delete a specific logical operator revision:
+
+```python
+chronicle.delete_integration_logical_operator_revision(
+ integration_name="MyIntegration",
+ logical_operator_id="lo1",
+ revision_id="rev-456"
+)
+```
+
+Create a new revision before making changes:
+
+```python
+# Get the current logical operator
+logical_operator = chronicle.get_integration_logical_operator(
+ integration_name="MyIntegration",
+ logical_operator_id="lo1"
+)
+
+# Create a backup revision
+new_revision = chronicle.create_integration_logical_operator_revision(
+ integration_name="MyIntegration",
+ logical_operator_id="lo1",
+ logical_operator=logical_operator,
+ comment="Backup before refactoring conditional logic"
+)
+print(f"Created revision: {new_revision.get('name')}")
+
+# Create revision with custom comment
+new_revision = chronicle.create_integration_logical_operator_revision(
+ integration_name="MyIntegration",
+ logical_operator_id="lo1",
+ logical_operator=logical_operator,
+ comment="Version 2.0 - Enhanced comparison logic"
+)
+```
+
+Rollback to a previous revision:
+
+```python
+# Rollback to a previous working version
+rollback_result = chronicle.rollback_integration_logical_operator_revision(
+ integration_name="MyIntegration",
+ logical_operator_id="lo1",
+ revision_id="rev-456"
+)
+print(f"Rolled back to: {rollback_result.get('name')}")
+```
+
+Example workflow: Safe logical operator updates with revision control:
+
+```python
+# 1. Get the current logical operator
+logical_operator = chronicle.get_integration_logical_operator(
+ integration_name="MyIntegration",
+ logical_operator_id="lo1"
+)
+
+# 2. Create a backup revision
+backup = chronicle.create_integration_logical_operator_revision(
+ integration_name="MyIntegration",
+ logical_operator_id="lo1",
+ logical_operator=logical_operator,
+ comment="Backup before updating evaluation logic"
+)
+
+# 3. Make changes to the logical operator
+updated_operator = chronicle.update_integration_logical_operator(
+ integration_name="MyIntegration",
+ logical_operator_id="lo1",
+ display_name="Enhanced Conditional Operator",
+ script="""
+def evaluate(severity, threshold, include_medium=False):
+ severity_levels = {
+ 'LOW': 1,
+ 'MEDIUM': 2,
+ 'HIGH': 3,
+ 'CRITICAL': 4
+ }
+
+ current = severity_levels.get(severity.upper(), 0)
+ min_level = severity_levels.get(threshold.upper(), 0)
+
+ if include_medium and current >= severity_levels['MEDIUM']:
+ return True
+
+ return current >= min_level
+"""
+)
+
+# 4. Test the updated logical operator
+test_result = chronicle.execute_integration_logical_operator_test(
+ integration_name="MyIntegration",
+ logical_operator=updated_operator
+)
+
+# 5. If test fails, rollback to backup
+if test_result.get("resultValue") is None or "error" in test_result.get("debugOutputMessage", "").lower():
+ print("Test failed - rolling back")
+ chronicle.rollback_integration_logical_operator_revision(
+ integration_name="MyIntegration",
+ logical_operator_id="lo1",
+ revision_id=backup.get("name").split("/")[-1]
+ )
+else:
+ print("Test passed - logical operator updated successfully")
+
+# 6. List all revisions to see history
+all_revisions = chronicle.list_integration_logical_operator_revisions(
+ integration_name="MyIntegration",
+ logical_operator_id="lo1",
+ as_list=True
+)
+print(f"Total revisions: {len(all_revisions)}")
+for rev in all_revisions:
+ print(f" - {rev.get('comment', 'No comment')} (ID: {rev.get('name').split('/')[-1]})")
+```
+
+
## Rule Management
The SDK provides comprehensive support for managing Chronicle detection rules:
diff --git a/api_module_mapping.md b/api_module_mapping.md
index bcfa632d..3d006a93 100644
--- a/api_module_mapping.md
+++ b/api_module_mapping.md
@@ -7,10 +7,643 @@ Following shows mapping between SecOps [REST Resource](https://cloud.google.com/
## Implementation Statistics
- **v1:** 17 endpoints implemented
-- **v1alpha:** 113 endpoints implemented
+- **v1beta:** 88 endpoints implemented
+- **v1alpha:** 203 endpoints implemented
## Endpoint Mapping
+| REST Resource | Version | secops-wrapper module | CLI Command |
+|--------------------------------------------------------------------------------|---------|------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------|
+| dataAccessLabels.create | v1 | | |
+| dataAccessLabels.delete | v1 | | |
+| dataAccessLabels.get | v1 | | |
+| dataAccessLabels.list | v1 | | |
+| dataAccessLabels.patch | v1 | | |
+| dataAccessScopes.create | v1 | | |
+| dataAccessScopes.delete | v1 | | |
+| dataAccessScopes.get | v1 | | |
+| dataAccessScopes.list | v1 | | |
+| dataAccessScopes.patch | v1 | | |
+| get | v1 | | |
+| operations.cancel | v1 | | |
+| operations.delete | v1 | | |
+| operations.get | v1 | | |
+| operations.list | v1 | | |
+| referenceLists.create | v1 | chronicle.reference_list.create_reference_list | secops reference-list create |
+| referenceLists.get | v1 | chronicle.reference_list.get_reference_list | secops reference-list get |
+| referenceLists.list | v1 | chronicle.reference_list.list_reference_lists | secops reference-list list |
+| referenceLists.patch | v1 | chronicle.reference_list.update_reference_list | secops reference-list update |
+| rules.create | v1 | chronicle.rule.create_rule | secops rule create |
+| rules.delete | v1 | chronicle.rule.delete_rule | secops rule delete |
+| rules.deployments.list | v1 | | |
+| rules.get | v1 | chronicle.rule.get_rule | secops rule get |
+| rules.getDeployment | v1 | | |
+| rules.list | v1 | chronicle.rule.list_rules | secops rule list |
+| rules.listRevisions | v1 | | |
+| rules.patch | v1 | chronicle.rule.update_rule | secops rule update |
+| rules.retrohunts.create | v1 | chronicle.rule_retrohunt.create_retrohunt | secops rule-retrohunt create |
+| rules.retrohunts.get | v1 | chronicle.rule_retrohunt.get_retrohunt | secops rule-retrohunt get |
+| rules.retrohunts.list | v1 | chronicle.rule_retrohunt.list_retrohunts | secops rule-retrohunt list |
+| rules.updateDeployment | v1 | chronicle.rule.enable_rule | secops rule enable |
+| watchlists.create | v1 | chronicle.watchlist.create_watchlist | secops watchlist create |
+| watchlists.delete | v1 | chronicle.watchlist.delete_watchlist | secops watchlist delete |
+| watchlists.get | v1 | chronicle.watchlist.get_watchlist | secops watchlist get |
+| watchlists.list | v1 | chronicle.watchlist.list_watchlists | secops watchlist list |
+| watchlists.patch | v1 | chronicle.watchlist.update_watchlist | secops watchlist update |
+| dataAccessLabels.create | v1beta | | |
+| dataAccessLabels.delete | v1beta | | |
+| dataAccessLabels.get | v1beta | | |
+| dataAccessLabels.list | v1beta | | |
+| dataAccessLabels.patch | v1beta | | |
+| dataAccessScopes.create | v1beta | | |
+| dataAccessScopes.delete | v1beta | | |
+| dataAccessScopes.get | v1beta | | |
+| dataAccessScopes.list | v1beta | | |
+| dataAccessScopes.patch | v1beta | | |
+| get | v1beta | | |
+| integrations.create | v1beta | | |
+| integrations.delete | v1beta | chronicle.integration.integrations.delete_integration | secops integration integrations delete |
+| integrations.download | v1beta | chronicle.integration.integrations.download_integration | secops integration integrations download |
+| integrations.downloadDependency | v1beta | chronicle.integration.integrations.download_integration_dependency | secops integration integrations download-dependency |
+| integrations.exportIntegrationItems | v1beta | chronicle.integration.integrations.export_integration_items | secops integration integrations export-items |
+| integrations.fetchAffectedItems | v1beta | chronicle.integration.integrations.get_integration_affected_items | secops integration integrations get-affected-items |
+| integrations.fetchAgentIntegrations | v1beta | chronicle.integration.integrations.get_agent_integrations | secops integration integrations get-agent |
+| integrations.fetchCommercialDiff | v1beta | chronicle.integration.integrations.get_integration_diff | secops integration integrations get-diff |
+| integrations.fetchDependencies | v1beta | chronicle.integration.integrations.get_integration_dependencies | secops integration integrations get-dependencies |
+| integrations.fetchRestrictedAgents | v1beta | chronicle.integration.integrations.get_integration_restricted_agents | secops integration integrations get-restricted-agents |
+| integrations.get | v1beta | chronicle.integration.integrations.get_integration | secops integration integrations get |
+| integrations.getFetchProductionDiff | v1beta | chronicle.integration.integrations.get_integration_diff(diff_type=DiffType.PRODUCTION) | secops integration integrations get-diff |
+| integrations.getFetchStagingDiff | v1beta | chronicle.integration.integrations.get_integration_diff(diff_type=DiffType.STAGING) | secops integration integrations get-diff |
+| integrations.import | v1beta | | |
+| integrations.importIntegrationDependency | v1beta | | |
+| integrations.importIntegrationItems | v1beta | | |
+| integrations.list | v1beta | chronicle.integration.integrations.list_integrations | secops integration integrations list |
+| integrations.patch | v1beta | | |
+| integrations.pushToProduction | v1beta | chronicle.integration.integrations.transition_integration(target_mode=TargetMode.PRODUCTION) | secops integration integrations transition |
+| integrations.pushToStaging | v1beta | chronicle.integration.integrations.transition_integration(target_mode=TargetMode.STAGING) | secops integration integrations transition |
+| integrations.updateCustomIntegration | v1beta | | |
+| integrations.upload | v1beta | | |
+| integrations.actions.create | v1beta | chronicle.integration.actions.create_integration_action | secops integration actions create |
+| integrations.actions.delete | v1beta | chronicle.integration.actions.delete_integration_action | secops integration actions delete |
+| integrations.actions.executeTest | v1beta | chronicle.integration.actions.execute_integration_action_test | secops integration actions test |
+| integrations.actions.fetchActionsByEnvironment | v1beta | chronicle.integration.actions.get_integration_actions_by_environment | |
+| integrations.actions.fetchTemplate | v1beta | chronicle.integration.actions.get_integration_action_template | secops integration actions template |
+| integrations.actions.get | v1beta | chronicle.integration.actions.get_integration_action | secops integration actions get |
+| integrations.actions.list | v1beta | chronicle.integration.actions.list_integration_actions | secops integration actions list |
+| integrations.actions.patch | v1beta | chronicle.integration.actions.update_integration_action | secops integration actions update |
+| integrations.actions.revisions.create | v1beta | chronicle.integration.action_revisions.create_integration_action_revision | secops integration action-revisions create |
+| integrations.actions.revisions.delete | v1beta | chronicle.integration.action_revisions.delete_integration_action_revision | secops integration action-revisions delete |
+| integrations.actions.revisions.list | v1beta | chronicle.integration.action_revisions.list_integration_action_revisions | secops integration action-revisions list |
+| integrations.actions.revisions.rollback | v1beta | chronicle.integration.action_revisions.rollback_integration_action_revision | secops integration action-revisions rollback |
+| integrations.connectors.create | v1beta | chronicle.integration.connectors.create_integration_connector | secops integration connectors create |
+| integrations.connectors.delete | v1beta | chronicle.integration.connectors.delete_integration_connector | secops integration connectors delete |
+| integrations.connectors.executeTest | v1beta | chronicle.integration.connectors.execute_integration_connector_test | secops integration connectors test |
+| integrations.connectors.fetchTemplate | v1beta | chronicle.integration.connectors.get_integration_connector_template | secops integration connectors template |
+| integrations.connectors.get | v1beta | chronicle.integration.connectors.get_integration_connector | secops integration connectors get |
+| integrations.connectors.list | v1beta | chronicle.integration.connectors.list_integration_connectors | secops integration connectors list |
+| integrations.connectors.patch | v1beta | chronicle.integration.connectors.update_integration_connector | secops integration connectors update |
+| integrations.connectors.revisions.create | v1beta | chronicle.integration.connector_revisions.create_integration_connector_revision | secops integration connector-revisions create |
+| integrations.connectors.revisions.delete | v1beta | chronicle.integration.connector_revisions.delete_integration_connector_revision | secops integration connector-revisions delete |
+| integrations.connectors.revisions.list | v1beta | chronicle.integration.connector_revisions.list_integration_connector_revisions | secops integration connector-revisions list |
+| integrations.connectors.revisions.rollback | v1beta | chronicle.integration.connector_revisions.rollback_integration_connector_revision | secops integration connector-revisions rollback|
+| integrations.connectors.contextProperties.clearAll | v1beta | chronicle.integration.connector_context_properties.delete_all_connector_context_properties | secops integration connector-context-properties delete-all |
+| integrations.connectors.contextProperties.create | v1beta | chronicle.integration.connector_context_properties.create_connector_context_property | secops integration connector-context-properties create |
+| integrations.connectors.contextProperties.delete | v1beta | chronicle.integration.connector_context_properties.delete_connector_context_property | secops integration connector-context-properties delete |
+| integrations.connectors.contextProperties.get | v1beta | chronicle.integration.connector_context_properties.get_connector_context_property | secops integration connector-context-properties get |
+| integrations.connectors.contextProperties.list | v1beta | chronicle.integration.connector_context_properties.list_connector_context_properties | secops integration connector-context-properties list |
+| integrations.connectors.contextProperties.patch | v1beta | chronicle.integration.connector_context_properties.update_connector_context_property | secops integration connector-context-properties update |
+| integrations.connectors.connectorInstances.logs.get | v1beta | chronicle.integration.connector_instance_logs.get_connector_instance_log | secops integration connector-instance-logs get |
+| integrations.connectors.connectorInstances.logs.list | v1beta | chronicle.integration.connector_instance_logs.list_connector_instance_logs | secops integration connector-instance-logs list|
+| integrations.connectors.connectorInstances.create | v1beta | chronicle.integration.connector_instances.create_connector_instance | secops integration connector-instances create |
+| integrations.connectors.connectorInstances.delete | v1beta | chronicle.integration.connector_instances.delete_connector_instance | secops integration connector-instances delete |
+| integrations.connectors.connectorInstances.fetchLatestDefinition | v1beta | chronicle.integration.connector_instances.get_connector_instance_latest_definition | secops integration connector-instances get-latest-definition |
+| integrations.connectors.connectorInstances.get | v1beta | chronicle.integration.connector_instances.get_connector_instance | secops integration connector-instances get |
+| integrations.connectors.connectorInstances.list | v1beta | chronicle.integration.connector_instances.list_connector_instances | secops integration connector-instances list |
+| integrations.connectors.connectorInstances.patch | v1beta | chronicle.integration.connector_instances.update_connector_instance | secops integration connector-instances update |
+| integrations.connectors.connectorInstances.runOnDemand | v1beta | chronicle.integration.connector_instances.run_connector_instance_on_demand | secops integration connector-instances run-on-demand |
+| integrations.connectors.connectorInstances.setLogsCollection | v1beta | chronicle.integration.connector_instances.set_connector_instance_logs_collection | secops integration connector-instances set-logs-collection |
+| integrations.integrationInstances.create | v1beta | chronicle.integration.integration_instances.create_integration_instance | secops integration instances create |
+| integrations.integrationInstances.delete | v1beta | chronicle.integration.integration_instances.delete_integration_instance | secops integration instances delete |
+| integrations.integrationInstances.executeTest | v1beta | chronicle.integration.integration_instances.execute_integration_instance_test | secops integration instances test |
+| integrations.integrationInstances.fetchAffectedItems | v1beta | chronicle.integration.integration_instances.get_integration_instance_affected_items | secops integration instances get-affected-items|
+| integrations.integrationInstances.fetchDefaultInstance | v1beta | chronicle.integration.integration_instances.get_default_integration_instance | secops integration instances get-default |
+| integrations.integrationInstances.get | v1beta | chronicle.integration.integration_instances.get_integration_instance | secops integration instances get |
+| integrations.integrationInstances.list | v1beta | chronicle.integration.integration_instances.list_integration_instances | secops integration instances list |
+| integrations.integrationInstances.patch | v1beta | chronicle.integration.integration_instances.update_integration_instance | secops integration instances update |
+| integrations.jobs.create | v1beta | chronicle.integration.jobs.create_integration_job | secops integration jobs create |
+| integrations.jobs.delete | v1beta | chronicle.integration.jobs.delete_integration_job | secops integration jobs delete |
+| integrations.jobs.executeTest | v1beta | chronicle.integration.jobs.execute_integration_job_test | secops integration jobs test |
+| integrations.jobs.fetchTemplate | v1beta | chronicle.integration.jobs.get_integration_job_template | secops integration jobs template |
+| integrations.jobs.get | v1beta | chronicle.integration.jobs.get_integration_job | secops integration jobs get |
+| integrations.jobs.list | v1beta | chronicle.integration.jobs.list_integration_jobs | secops integration jobs list |
+| integrations.jobs.patch | v1beta | chronicle.integration.jobs.update_integration_job | secops integration jobs update |
+| integrations.managers.create | v1beta | chronicle.integration.managers.create_integration_manager | secops integration managers create |
+| integrations.managers.delete | v1beta | chronicle.integration.managers.delete_integration_manager | secops integration managers delete |
+| integrations.managers.fetchTemplate | v1beta | chronicle.integration.managers.get_integration_manager_template | secops integration managers template |
+| integrations.managers.get | v1beta | chronicle.integration.managers.get_integration_manager | secops integration managers get |
+| integrations.managers.list | v1beta | chronicle.integration.managers.list_integration_managers | secops integration managers list |
+| integrations.managers.patch | v1beta | chronicle.integration.managers.update_integration_manager | secops integration managers update |
+| integrations.managers.revisions.create | v1beta | chronicle.integration.manager_revisions.create_integration_manager_revision | secops integration manager-revisions create |
+| integrations.managers.revisions.delete | v1beta | chronicle.integration.manager_revisions.delete_integration_manager_revision | secops integration manager-revisions delete |
+| integrations.managers.revisions.get | v1beta | chronicle.integration.manager_revisions.get_integration_manager_revision | secops integration manager-revisions get |
+| integrations.managers.revisions.list | v1beta | chronicle.integration.manager_revisions.list_integration_manager_revisions | secops integration manager-revisions list |
+| integrations.managers.revisions.rollback | v1beta | chronicle.integration.manager_revisions.rollback_integration_manager_revision | secops integration manager-revisions rollback |
+| integrations.jobs.revisions.create | v1beta | chronicle.integration.job_revisions.create_integration_job_revision | secops integration job-revisions create |
+| integrations.jobs.revisions.delete | v1beta | chronicle.integration.job_revisions.delete_integration_job_revision | secops integration job-revisions delete |
+| integrations.jobs.revisions.list | v1beta | chronicle.integration.job_revisions.list_integration_job_revisions | secops integration job-revisions list |
+| integrations.jobs.revisions.rollback | v1beta | chronicle.integration.job_revisions.rollback_integration_job_revision | secops integration job-revisions rollback |
+| integrations.jobs.jobInstances.create | v1beta | chronicle.integration.job_instances.create_integration_job_instance | secops integration job-instances create |
+| integrations.jobs.jobInstances.delete | v1beta | chronicle.integration.job_instances.delete_integration_job_instance | secops integration job-instances delete |
+| integrations.jobs.jobInstances.get | v1beta | chronicle.integration.job_instances.get_integration_job_instance | secops integration job-instances get |
+| integrations.jobs.jobInstances.list | v1beta | chronicle.integration.job_instances.list_integration_job_instances | secops integration job-instances list |
+| integrations.jobs.jobInstances.patch | v1beta | chronicle.integration.job_instances.update_integration_job_instance | secops integration job-instances update |
+| integrations.jobs.jobInstances.runOnDemand | v1beta | chronicle.integration.job_instances.run_integration_job_instance_on_demand | secops integration job-instances run-on-demand |
+| integrations.jobs.contextProperties.clearAll | v1beta | chronicle.integration.job_context_properties.delete_all_job_context_properties | secops integration job-context-properties delete-all |
+| integrations.jobs.contextProperties.create | v1beta | chronicle.integration.job_context_properties.create_job_context_property | secops integration job-context-properties create |
+| integrations.jobs.contextProperties.delete | v1beta | chronicle.integration.job_context_properties.delete_job_context_property | secops integration job-context-properties delete |
+| integrations.jobs.contextProperties.get | v1beta | chronicle.integration.job_context_properties.get_job_context_property | secops integration job-context-properties get |
+| integrations.jobs.contextProperties.list | v1beta | chronicle.integration.job_context_properties.list_job_context_properties | secops integration job-context-properties list |
+| integrations.jobs.contextProperties.patch | v1beta | chronicle.integration.job_context_properties.update_job_context_property | secops integration job-context-properties update |
+| integrations.jobs.jobInstances.logs.get | v1beta | chronicle.integration.job_instance_logs.get_job_instance_log | secops integration job-instance-logs get |
+| integrations.jobs.jobInstances.logs.list | v1beta | chronicle.integration.job_instance_logs.list_job_instance_logs | secops integration job-instance-logs list |
+| marketplaceIntegrations.get | v1beta | chronicle.marketplace_integrations.get_marketplace_integration | secops integration marketplace get |
+| marketplaceIntegrations.getDiff | v1beta | chronicle.marketplace_integrations.get_marketplace_integration_diff | secops integration marketplace diff |
+| marketplaceIntegrations.install | v1beta | chronicle.marketplace_integrations.install_marketplace_integration | secops integration marketplace install |
+| marketplaceIntegrations.list | v1beta | chronicle.marketplace_integrations.list_marketplace_integrations | secops integration marketplace list |
+| marketplaceIntegrations.uninstall | v1beta | chronicle.marketplace_integrations.uninstall_marketplace_integration | secops integration marketplace uninstall |
+| operations.cancel | v1beta | | |
+| operations.delete | v1beta | | |
+| operations.get | v1beta | | |
+| operations.list | v1beta | | |
+| referenceLists.create | v1beta | | |
+| referenceLists.get | v1beta | | |
+| referenceLists.list | v1beta | | |
+| referenceLists.patch | v1beta | | |
+| rules.create | v1beta | | |
+| rules.delete | v1beta | | |
+| rules.deployments.list | v1beta | | |
+| rules.get | v1beta | | |
+| rules.getDeployment | v1beta | | |
+| rules.list | v1beta | | |
+| rules.listRevisions | v1beta | | |
+| rules.patch | v1beta | | |
+| rules.retrohunts.create | v1beta | | |
+| rules.retrohunts.get | v1beta | | |
+| rules.retrohunts.list | v1beta | | |
+| rules.updateDeployment | v1beta | | |
+| watchlists.create | v1beta | | |
+| watchlists.delete | v1beta | | |
+| watchlists.get | v1beta | | |
+| watchlists.list | v1beta | | |
+| watchlists.patch | v1beta | | |
+| analytics.entities.analyticValues.list | v1alpha | | |
+| analytics.list | v1alpha | | |
+| batchValidateWatchlistEntities | v1alpha | | |
+| bigQueryAccess.provide | v1alpha | | |
+| bigQueryExport.provision | v1alpha | | |
+| cases.countPriorities | v1alpha | | |
+| contentHub.featuredContentRules.list | v1alpha | chronicle.featured_content_rules.list_featured_content_rules | secops featured-content-rules list |
+| curatedRuleSetCategories.curatedRuleSets.curatedRuleSetDeployments.batchUpdate | v1alpha | chronicle.rule_set.batch_update_curated_rule_set_deployments | |
+| curatedRuleSetCategories.curatedRuleSets.curatedRuleSetDeployments.patch | v1alpha | chronicle.rule_set.update_curated_rule_set_deployment | secops curated-rule rule-set-deployment update |
+| curatedRuleSetCategories.curatedRuleSets.curatedRuleSetDeployments.list | v1alpha | chronicle.rule_set.list_curated_rule_set_deployments | secops curated-rule rule-set-deployment list |
+| curatedRuleSetCategories.curatedRuleSets.curatedRuleSetDeployments.get | v1alpha | chronicle.rule_set.get_curated_rule_set_deployment
chronicle.rule_set.get_curated_rule_set_deployment_by_name | secops curated-rule rule-set-deployment get |
+| curatedRuleSetCategories.curatedRuleSets.get | v1alpha | chronicle.rule_set.get_curated_rule_set | secops curated-rule rule-set get |
+| curatedRuleSetCategories.curatedRuleSets.list | v1alpha | chronicle.rule_set.list_curated_rule_sets | secops curated-rule rule-set list |
+| curatedRuleSetCategories.get | v1alpha | chronicle.rule_set.get_curated_rule_set_category | secops curated-rule rule-set-category get |
+| curatedRuleSetCategories.list | v1alpha | chronicle.rule_set.list_curated_rule_set_categories | secops curated-rule rule-set-category list |
+| curatedRules.get | v1alpha | chronicle.rule_set.get_curated_rule
chronicle.rule_set.get_curated_rule_by_name | secops curated-rule rule get |
+| curatedRules.list | v1alpha | chronicle.rule_set.list_curated_rules | secops curated-rule rule list |
+| dashboardCharts.batchGet | v1alpha | | |
+| dashboardCharts.get | v1alpha | chronicle.dashboard.get_chart | secops dashboard get-chart |
+| dashboardQueries.execute | v1alpha | chronicle.dashboard_query.execute_query | secops dashboard-query execute |
+| dashboardQueries.get | v1alpha | chronicle.dashboard_query.get_execute_query | secops dashboard-query get |
+| dashboards.copy | v1alpha | | |
+| dashboards.create | v1alpha | | |
+| dashboards.delete | v1alpha | | |
+| dashboards.get | v1alpha | | |
+| dashboards.list | v1alpha | | |
+| dataAccessLabels.create | v1alpha | | |
+| dataAccessLabels.delete | v1alpha | | |
+| dataAccessLabels.get | v1alpha | | |
+| dataAccessLabels.list | v1alpha | | |
+| dataAccessLabels.patch | v1alpha | | |
+| dataAccessScopes.create | v1alpha | | |
+| dataAccessScopes.delete | v1alpha | | |
+| dataAccessScopes.get | v1alpha | | |
+| dataAccessScopes.list | v1alpha | | |
+| dataAccessScopes.patch | v1alpha | | |
+| dataExports.cancel | v1alpha | chronicle.data_export.cancel_data_export | secops export cancel |
+| dataExports.create | v1alpha | chronicle.data_export.create_data_export | secops export create |
+| dataExports.fetchavailablelogtypes | v1alpha | chronicle.data_export.fetch_available_log_types | secops export log-types |
+| dataExports.get | v1alpha | chronicle.data_export.get_data_export | secops export status |
+| dataExports.list | v1alpha | chronicle.data_export.list_data_export | secops export list |
+| dataExports.patch | v1alpha | chronicle.data_export.update_data_export | secops export update |
+| dataTableOperationErrors.get | v1alpha | | |
+| dataTables.create | v1alpha | chronicle.data_table.create_data_table | secops data-table create |
+| dataTables.dataTableRows.bulkCreate | v1alpha | chronicle.data_table.create_data_table_rows | secops data-table add-rows |
+| dataTables.dataTableRows.bulkCreateAsync | v1alpha | | |
+| dataTables.dataTableRows.bulkGet | v1alpha | | |
+| dataTables.dataTableRows.bulkReplace | v1alpha | chronicle.data_table.replace_data_table_rows | secops data-table replace-rows |
+| dataTables.dataTableRows.bulkReplaceAsync | v1alpha | | |
+| dataTables.dataTableRows.bulkUpdate | v1alpha | chronicle.data_table.update_data_table_rows | secops data-table update-rows |
+| dataTables.dataTableRows.bulkUpdateAsync | v1alpha | | |
+| dataTables.dataTableRows.create | v1alpha | | |
+| dataTables.dataTableRows.delete | v1alpha | chronicle.data_table.delete_data_table_rows | secops data-table delete-rows |
+| dataTables.dataTableRows.get | v1alpha | | |
+| dataTables.dataTableRows.list | v1alpha | chronicle.data_table.list_data_table_rows | secops data-table list-rows |
+| dataTables.dataTableRows.patch | v1alpha | | |
+| dataTables.delete | v1alpha | chronicle.data_table.delete_data_table | secops data-table delete |
+| dataTables.get | v1alpha | chronicle.data_table.get_data_table | secops data-table get |
+| dataTables.list | v1alpha | chronicle.data_table.list_data_tables | secops data-table list |
+| dataTables.patch | v1alpha | | |
+| dataTables.upload | v1alpha | | |
+| dataTaps.create | v1alpha | | |
+| dataTaps.delete | v1alpha | | |
+| dataTaps.get | v1alpha | | |
+| dataTaps.list | v1alpha | | |
+| dataTaps.patch | v1alpha | | |
+| delete | v1alpha | | |
+| enrichmentControls.create | v1alpha | | |
+| enrichmentControls.delete | v1alpha | | |
+| enrichmentControls.get | v1alpha | | |
+| enrichmentControls.list | v1alpha | | |
+| entities.get | v1alpha | | |
+| entities.import | v1alpha | chronicle.log_ingest.import_entities | secops entity import |
+| entities.modifyEntityRiskScore | v1alpha | | |
+| entities.queryEntityRiskScoreModifications | v1alpha | | |
+| entityRiskScores.query | v1alpha | | |
+| errorNotificationConfigs.create | v1alpha | | |
+| errorNotificationConfigs.delete | v1alpha | | |
+| errorNotificationConfigs.get | v1alpha | | |
+| errorNotificationConfigs.list | v1alpha | | |
+| errorNotificationConfigs.patch | v1alpha | | |
+| events.batchGet | v1alpha | | |
+| events.get | v1alpha | | |
+| events.import | v1alpha | chronicle.log_ingest.ingest_udm | secops log ingest-udm |
+| extractSyslog | v1alpha | | |
+| federationGroups.create | v1alpha | | |
+| federationGroups.delete | v1alpha | | |
+| federationGroups.get | v1alpha | | |
+| federationGroups.list | v1alpha | | |
+| federationGroups.patch | v1alpha | | |
+| feedPacks.get | v1alpha | | |
+| feedPacks.list | v1alpha | | |
+| feedServiceAccounts.fetchServiceAccountForCustomer | v1alpha | | |
+| feedSourceTypeSchemas.list | v1alpha | | |
+| feedSourceTypeSchemas.logTypeSchemas.list | v1alpha | | |
+| feeds.create | v1alpha | chronicle.feeds.create_feed | secops feed create |
+| feeds.delete | v1alpha | chronicle.feeds.delete_feed | secops feed delete |
+| feeds.disable | v1alpha | chronicle.feeds.disable_feed | secops feed disable |
+| feeds.enable | v1alpha | chronicle.feeds.enable_feed | secops feed enable |
+| feeds.generateSecret | v1alpha | chronicle.feeds.generate_secret | secops feed secret |
+| feeds.get | v1alpha | chronicle.feeds.get_feed | secops feed get |
+| feeds.importPushLogs | v1alpha | | |
+| feeds.list | v1alpha | chronicle.feeds.list_feeds | secops feed list |
+| feeds.patch | v1alpha | chronicle.feeds.update_feed | secops feed update |
+| feeds.scheduleTransfer | v1alpha | | |
+| fetchFederationAccess | v1alpha | | |
+| findEntity | v1alpha | | |
+| findEntityAlerts | v1alpha | | |
+| findRelatedEntities | v1alpha | | |
+| findUdmFieldValues | v1alpha | | |
+| findingsGraph.exploreNode | v1alpha | | |
+| findingsGraph.initializeGraph | v1alpha | | |
+| findingsRefinements.computeFindingsRefinementActivity | v1alpha | chronicle.rule_exclusion.compute_rule_exclusion_activity | secops rule-exclusion compute-activity |
+| findingsRefinements.create | v1alpha | chronicle.rule_exclusion.create_rule_exclusion | secops rule-exclusion create |
+| findingsRefinements.get | v1alpha | chronicle.rule_exclusion.get_rule_exclusion | secops rule-exclusion get |
+| findingsRefinements.getDeployment | v1alpha | chronicle.rule_exclusion.get_rule_exclusion_deployment | secops rule-exclusion get-deployment |
+| findingsRefinements.list | v1alpha | chronicle.rule_exclusion.list_rule_exclusions | secops rule-exclusion list |
+| findingsRefinements.patch | v1alpha | chronicle.rule_exclusion.patch_rule_exclusion | secops rule-exclusion update |
+| findingsRefinements.updateDeployment | v1alpha | chronicle.rule_exclusion.update_rule_exclusion_deployment | secops rule-exclusion update-deployment |
+| forwarders.collectors.create | v1alpha | | |
+| forwarders.collectors.delete | v1alpha | | |
+| forwarders.collectors.get | v1alpha | | |
+| forwarders.collectors.list | v1alpha | | |
+| forwarders.collectors.patch | v1alpha | | |
+| forwarders.create | v1alpha | chronicle.log_ingest.create_forwarder | secops forwarder create |
+| forwarders.delete | v1alpha | chronicle.log_ingest.delete_forwarder | secops forwarder delete |
+| forwarders.generateForwarderFiles | v1alpha | | |
+| forwarders.get | v1alpha | chronicle.log_ingest.get_forwarder | secops forwarder get |
+| forwarders.importStatsEvents | v1alpha | | |
+| forwarders.list | v1alpha | chronicle.log_ingest.list_forwarder | secops forwarder list |
+| forwarders.patch | v1alpha | chronicle.log_ingest.update_forwarder | secops forwarder update |
+| generateCollectionAgentAuth | v1alpha | | |
+| generateSoarAuthJwt | v1alpha | | |
+| generateUdmKeyValueMappings | v1alpha | | |
+| generateWorkspaceConnectionToken | v1alpha | | |
+| get | v1alpha | | |
+| getBigQueryExport | v1alpha | | |
+| getMultitenantDirectory | v1alpha | | |
+| getRiskConfig | v1alpha | | |
+| ingestionLogLabels.get | v1alpha | | |
+| ingestionLogLabels.list | v1alpha | | |
+| ingestionLogNamespaces.get | v1alpha | | |
+| ingestionLogNamespaces.list | v1alpha | | |
+| integrations.create | v1alpha | | |
+| integrations.delete | v1alpha | chronicle.integration.integrations.delete_integration(api_version=APIVersion.V1ALPHA) | secops integration integrations delete |
+| integrations.download | v1alpha | chronicle.integration.integrations.download_integration(api_version=APIVersion.V1ALPHA) | secops integration integrations download |
+| integrations.downloadDependency | v1alpha | chronicle.integration.integrations.download_integration_dependency(api_version=APIVersion.V1ALPHA) | secops integration integrations download-dependency |
+| integrations.exportIntegrationItems | v1alpha | chronicle.integration.integrations.export_integration_items(api_version=APIVersion.V1ALPHA) | secops integration integrations export-items |
+| integrations.fetchAffectedItems | v1alpha | chronicle.integration.integrations.get_integration_affected_items(api_version=APIVersion.V1ALPHA) | secops integration integrations get-affected-items |
+| integrations.fetchAgentIntegrations | v1alpha | chronicle.integration.integrations.get_agent_integrations(api_version=APIVersion.V1ALPHA) | secops integration integrations get-agent |
+| integrations.fetchCommercialDiff | v1alpha | chronicle.integration.integrations.get_integration_diff(api_version=APIVersion.V1ALPHA) | secops integration integrations get-diff |
+| integrations.fetchDependencies | v1alpha | chronicle.integration.integrations.get_integration_dependencies(api_version=APIVersion.V1ALPHA) | secops integration integrations get-dependencies |
+| integrations.fetchRestrictedAgents | v1alpha | chronicle.integration.integrations.get_integration_restricted_agents(api_version=APIVersion.V1ALPHA) | secops integration integrations get-restricted-agents |
+| integrations.get | v1alpha | chronicle.integration.integrations.get_integration(api_version=APIVersion.V1ALPHA) | secops integration integrations get |
+| integrations.getFetchProductionDiff | v1alpha | chronicle.integration.integrations.get_integration_diff(api_version=APIVersion.V1ALPHA, diff_type=DiffType.PRODUCTION) | secops integration integrations get-diff |
+| integrations.getFetchStagingDiff | v1alpha | chronicle.integration.integrations.get_integration_diffapi_version=APIVersion.V1ALPHA, (diff_type=DiffType.STAGING) | secops integration integrations get-diff |
+| integrations.import | v1alpha | | |
+| integrations.importIntegrationDependency | v1alpha | | |
+| integrations.importIntegrationItems | v1alpha | | |
+| integrations.list | v1alpha | chronicle.integration.integrations.list_integrations(api_version=APIVersion.V1ALPHA) | secops integration integrations list |
+| integrations.patch | v1alpha | | |
+| integrations.pushToProduction | v1alpha | chronicle.integration.integrations.transition_integration(api_version=APIVersion.V1ALPHA, target_mode=TargetMode.PRODUCTION) | secops integration integrations transition |
+| integrations.pushToStaging | v1alpha | chronicle.integration.integrations.transition_integration(api_version=APIVersion.V1ALPHA, target_mode=TargetMode.STAGING) | secops integration integrations transition |
+| integrations.updateCustomIntegration | v1alpha | | |
+| integrations.upload | v1alpha | | |
+| integrations.actions.create | v1alpha | chronicle.integration.actions.create_integration_action(api_version=APIVersion.V1ALPHA) | secops integration actions create |
+| integrations.actions.delete | v1alpha | chronicle.integration.actions.delete_integration_action(api_version=APIVersion.V1ALPHA) | secops integration actions delete |
+| integrations.actions.executeTest | v1alpha | chronicle.integration.actions.execute_integration_action_test(api_version=APIVersion.V1ALPHA) | secops integration actions test |
+| integrations.actions.fetchActionsByEnvironment | v1alpha | chronicle.integration.actions.get_integration_actions_by_environment(api_version=APIVersion.V1ALPHA) | |
+| integrations.actions.fetchTemplate | v1alpha | chronicle.integration.actions.get_integration_action_template(api_version=APIVersion.V1ALPHA) | secops integration actions template |
+| integrations.actions.get | v1alpha | chronicle.integration.actions.get_integration_action(api_version=APIVersion.V1ALPHA) | secops integration actions get |
+| integrations.actions.list | v1alpha | chronicle.integration.actions.list_integration_actions(api_version=APIVersion.V1ALPHA) | secops integration actions list |
+| integrations.actions.patch | v1alpha | chronicle.integration.actions.update_integration_action(api_version=APIVersion.V1ALPHA) | secops integration actions update |
+| integrations.actions.revisions.create | v1alpha | chronicle.integration.action_revisions.create_integration_action_revision(api_version=APIVersion.V1ALPHA) | secops integration action-revisions create |
+| integrations.actions.revisions.delete | v1alpha | chronicle.integration.action_revisions.delete_integration_action_revision(api_version=APIVersion.V1ALPHA) | secops integration action-revisions delete |
+| integrations.actions.revisions.list | v1alpha | chronicle.integration.action_revisions.list_integration_action_revisions(api_version=APIVersion.V1ALPHA) | secops integration action-revisions list |
+| integrations.actions.revisions.rollback | v1alpha | chronicle.integration.action_revisions.rollback_integration_action_revision(api_version=APIVersion.V1ALPHA) | secops integration action-revisions rollback |
+| integrations.connectors.create | v1alpha | chronicle.integration.connectors.create_integration_connector(api_version=APIVersion.V1ALPHA) | secops integration connectors create |
+| integrations.connectors.delete | v1alpha | chronicle.integration.connectors.delete_integration_connector(api_version=APIVersion.V1ALPHA) | secops integration connectors delete |
+| integrations.connectors.executeTest | v1alpha | chronicle.integration.connectors.execute_integration_connector_test(api_version=APIVersion.V1ALPHA) | secops integration connectors test |
+| integrations.connectors.fetchTemplate | v1alpha | chronicle.integration.connectors.get_integration_connector_template(api_version=APIVersion.V1ALPHA) | secops integration connectors template |
+| integrations.connectors.get | v1alpha | chronicle.integration.connectors.get_integration_connector(api_version=APIVersion.V1ALPHA) | secops integration connectors get |
+| integrations.connectors.list | v1alpha | chronicle.integration.connectors.list_integration_connectors(api_version=APIVersion.V1ALPHA) | secops integration connectors list |
+| integrations.connectors.patch | v1alpha | chronicle.integration.connectors.update_integration_connector(api_version=APIVersion.V1ALPHA) | secops integration connectors update |
+| integrations.connectors.revisions.create | v1alpha | chronicle.integration.connector_revisions.create_integration_connector_revision(api_version=APIVersion.V1ALPHA) | secops integration connector-revisions create |
+| integrations.connectors.revisions.delete | v1alpha | chronicle.integration.connector_revisions.delete_integration_connector_revision(api_version=APIVersion.V1ALPHA) | secops integration connector-revisions delete |
+| integrations.connectors.revisions.list | v1alpha | chronicle.integration.connector_revisions.list_integration_connector_revisions(api_version=APIVersion.V1ALPHA) | secops integration connector-revisions list |
+| integrations.connectors.revisions.rollback | v1alpha | chronicle.integration.connector_revisions.rollback_integration_connector_revision(api_version=APIVersion.V1ALPHA) | secops integration connector-revisions rollback|
+| integrations.connectors.contextProperties.clearAll | v1alpha | chronicle.integration.connector_context_properties.delete_all_connector_context_properties(api_version=APIVersion.V1ALPHA) | secops integration connector-context-properties delete-all |
+| integrations.connectors.contextProperties.create | v1alpha | chronicle.integration.connector_context_properties.create_connector_context_property(api_version=APIVersion.V1ALPHA) | secops integration connector-context-properties create |
+| integrations.connectors.contextProperties.delete | v1alpha | chronicle.integration.connector_context_properties.delete_connector_context_property(api_version=APIVersion.V1ALPHA) | secops integration connector-context-properties delete |
+| integrations.connectors.contextProperties.get | v1alpha | chronicle.integration.connector_context_properties.get_connector_context_property(api_version=APIVersion.V1ALPHA) | secops integration connector-context-properties get |
+| integrations.connectors.contextProperties.list | v1alpha | chronicle.integration.connector_context_properties.list_connector_context_properties(api_version=APIVersion.V1ALPHA) | secops integration connector-context-properties list |
+| integrations.connectors.contextProperties.patch | v1alpha | chronicle.integration.connector_context_properties.update_connector_context_property(api_version=APIVersion.V1ALPHA) | secops integration connector-context-properties update |
+| integrations.connectors.connectorInstances.logs.get | v1alpha | chronicle.integration.connector_instance_logs.get_connector_instance_log(api_version=APIVersion.V1ALPHA) | secops integration connector-instance-logs get |
+| integrations.connectors.connectorInstances.logs.list | v1alpha | chronicle.integration.connector_instance_logs.list_connector_instance_logs(api_version=APIVersion.V1ALPHA) | secops integration connector-instance-logs list|
+| integrations.connectors.connectorInstances.create | v1alpha | chronicle.integration.connector_instances.create_connector_instance(api_version=APIVersion.V1ALPHA) | secops integration connector-instances create |
+| integrations.connectors.connectorInstances.delete | v1alpha | chronicle.integration.connector_instances.delete_connector_instance(api_version=APIVersion.V1ALPHA) | secops integration connector-instances delete |
+| integrations.connectors.connectorInstances.fetchLatestDefinition | v1alpha | chronicle.integration.connector_instances.get_connector_instance_latest_definition(api_version=APIVersion.V1ALPHA) | secops integration connector-instances get-latest-definition |
+| integrations.connectors.connectorInstances.get | v1alpha | chronicle.integration.connector_instances.get_connector_instance(api_version=APIVersion.V1ALPHA) | secops integration connector-instances get |
+| integrations.connectors.connectorInstances.list | v1alpha | chronicle.integration.connector_instances.list_connector_instances(api_version=APIVersion.V1ALPHA) | secops integration connector-instances list |
+| integrations.connectors.connectorInstances.patch | v1alpha | chronicle.integration.connector_instances.update_connector_instance(api_version=APIVersion.V1ALPHA) | secops integration connector-instances update |
+| integrations.connectors.connectorInstances.runOnDemand | v1alpha | chronicle.integration.connector_instances.run_connector_instance_on_demand(api_version=APIVersion.V1ALPHA) | secops integration connector-instances run-on-demand |
+| integrations.connectors.connectorInstances.setLogsCollection | v1alpha | chronicle.integration.connector_instances.set_connector_instance_logs_collection(api_version=APIVersion.V1ALPHA) | secops integration connector-instances set-logs-collection |
+| integrations.integrationInstances.create | v1alpha | chronicle.integration.integration_instances.create_integration_instance(api_version=APIVersion.V1ALPHA) | secops integration instances create |
+| integrations.integrationInstances.delete | v1alpha | chronicle.integration.integration_instances.delete_integration_instance(api_version=APIVersion.V1ALPHA) | secops integration instances delete |
+| integrations.integrationInstances.executeTest | v1alpha | chronicle.integration.integration_instances.execute_integration_instance_test(api_version=APIVersion.V1ALPHA) | secops integration instances test |
+| integrations.integrationInstances.fetchAffectedItems | v1alpha | chronicle.integration.integration_instances.get_integration_instance_affected_items(api_version=APIVersion.V1ALPHA) | secops integration instances get-affected-items|
+| integrations.integrationInstances.fetchDefaultInstance | v1alpha | chronicle.integration.integration_instances.get_default_integration_instance(api_version=APIVersion.V1ALPHA) | secops integration instances get-default |
+| integrations.integrationInstances.get | v1alpha | chronicle.integration.integration_instances.get_integration_instance(api_version=APIVersion.V1ALPHA) | secops integration instances get |
+| integrations.integrationInstances.list | v1alpha | chronicle.integration.integration_instances.list_integration_instances(api_version=APIVersion.V1ALPHA) | secops integration instances list |
+| integrations.integrationInstances.patch | v1alpha | chronicle.integration.integration_instances.update_integration_instance(api_version=APIVersion.V1ALPHA) | secops integration instances update |
+| integrations.transformers.create | v1alpha | chronicle.integration.transformers.create_integration_transformer | secops integration transformers create |
+| integrations.transformers.delete | v1alpha | chronicle.integration.transformers.delete_integration_transformer | secops integration transformers delete |
+| integrations.transformers.executeTest | v1alpha | chronicle.integration.transformers.execute_integration_transformer_test | secops integration transformers test |
+| integrations.transformers.fetchTemplate | v1alpha | chronicle.integration.transformers.get_integration_transformer_template | secops integration transformers template |
+| integrations.transformers.get | v1alpha | chronicle.integration.transformers.get_integration_transformer | secops integration transformers get |
+| integrations.transformers.list | v1alpha | chronicle.integration.transformers.list_integration_transformers | secops integration transformers list |
+| integrations.transformers.patch | v1alpha | chronicle.integration.transformers.update_integration_transformer | secops integration transformers update |
+| integrations.transformers.revisions.create | v1alpha | chronicle.integration.transformer_revisions.create_integration_transformer_revision | secops integration transformer-revisions create|
+| integrations.transformers.revisions.delete | v1alpha | chronicle.integration.transformer_revisions.delete_integration_transformer_revision | secops integration transformer-revisions delete|
+| integrations.transformers.revisions.list | v1alpha | chronicle.integration.transformer_revisions.list_integration_transformer_revisions | secops integration transformer-revisions list |
+| integrations.transformers.revisions.rollback | v1alpha | chronicle.integration.transformer_revisions.rollback_integration_transformer_revision | secops integration transformer-revisions rollback|
+| integrations.logicalOperators.create | v1alpha | chronicle.integration.logical_operators.create_integration_logical_operator | secops integration logical-operators create |
+| integrations.logicalOperators.delete | v1alpha | chronicle.integration.logical_operators.delete_integration_logical_operator | secops integration logical-operators delete |
+| integrations.logicalOperators.executeTest | v1alpha | chronicle.integration.logical_operators.execute_integration_logical_operator_test | secops integration logical-operators test |
+| integrations.logicalOperators.fetchTemplate | v1alpha | chronicle.integration.logical_operators.get_integration_logical_operator_template | secops integration logical-operators template |
+| integrations.logicalOperators.get | v1alpha | chronicle.integration.logical_operators.get_integration_logical_operator | secops integration logical-operators get |
+| integrations.logicalOperators.list | v1alpha | chronicle.integration.logical_operators.list_integration_logical_operators | secops integration logical-operators list |
+| integrations.logicalOperators.patch | v1alpha | chronicle.integration.logical_operators.update_integration_logical_operator | secops integration logical-operators update |
+| integrations.logicalOperators.revisions.create | v1alpha | chronicle.integration.logical_operator_revisions.create_integration_logical_operator_revision | secops integration logical-operator-revisions create |
+| integrations.logicalOperators.revisions.delete | v1alpha | chronicle.integration.logical_operator_revisions.delete_integration_logical_operator_revision | secops integration logical-operator-revisions delete |
+| integrations.logicalOperators.revisions.list | v1alpha | chronicle.integration.logical_operator_revisions.list_integration_logical_operator_revisions | secops integration logical-operator-revisions list |
+| integrations.logicalOperators.revisions.rollback | v1alpha | chronicle.integration.logical_operator_revisions.rollback_integration_logical_operator_revision | secops integration logical-operator-revisions rollback |
+| integrations.jobs.create | v1alpha | chronicle.integration.jobs.create_integration_job(api_version=APIVersion.V1ALPHA) | secops integration jobs create |
+| integrations.jobs.delete | v1alpha | chronicle.integration.jobs.delete_integration_job(api_version=APIVersion.V1ALPHA) | secops integration jobs delete |
+| integrations.jobs.executeTest | v1alpha | chronicle.integration.jobs.execute_integration_job_test(api_version=APIVersion.V1ALPHA) | secops integration jobs test |
+| integrations.jobs.fetchTemplate | v1alpha | chronicle.integration.jobs.get_integration_job_template(api_version=APIVersion.V1ALPHA) | secops integration jobs template |
+| integrations.jobs.get | v1alpha | chronicle.integration.jobs.get_integration_job(api_version=APIVersion.V1ALPHA) | secops integration jobs get |
+| integrations.jobs.list | v1alpha | chronicle.integration.jobs.list_integration_jobs(api_version=APIVersion.V1ALPHA) | secops integration jobs list |
+| integrations.jobs.patch | v1alpha | chronicle.integration.jobs.update_integration_job(api_version=APIVersion.V1ALPHA) | secops integration jobs update |
+| integrations.managers.create | v1alpha | chronicle.integration.managers.create_integration_manager(api_version=APIVersion.V1ALPHA) | secops integration managers create |
+| integrations.managers.delete | v1alpha | chronicle.integration.managers.delete_integration_manager(api_version=APIVersion.V1ALPHA) | secops integration managers delete |
+| integrations.managers.fetchTemplate | v1alpha | chronicle.integration.managers.get_integration_manager_template(api_version=APIVersion.V1ALPHA) | secops integration managers template |
+| integrations.managers.get | v1alpha | chronicle.integration.managers.get_integration_manager(api_version=APIVersion.V1ALPHA) | secops integration managers get |
+| integrations.managers.list | v1alpha | chronicle.integration.managers.list_integration_managers(api_version=APIVersion.V1ALPHA) | secops integration managers list |
+| integrations.managers.patch | v1alpha | chronicle.integration.managers.update_integration_manager(api_version=APIVersion.V1ALPHA) | secops integration managers update |
+| integrations.managers.revisions.create | v1alpha | chronicle.integration.manager_revisions.create_integration_manager_revision(api_version=APIVersion.V1ALPHA) | secops integration manager-revisions create |
+| integrations.managers.revisions.delete | v1alpha | chronicle.integration.manager_revisions.delete_integration_manager_revision(api_version=APIVersion.V1ALPHA) | secops integration manager-revisions delete |
+| integrations.managers.revisions.get | v1alpha | chronicle.integration.manager_revisions.get_integration_manager_revision(api_version=APIVersion.V1ALPHA) | secops integration manager-revisions get |
+| integrations.managers.revisions.list | v1alpha | chronicle.integration.manager_revisions.list_integration_manager_revisions(api_version=APIVersion.V1ALPHA) | secops integration manager-revisions list |
+| integrations.managers.revisions.rollback | v1alpha | chronicle.integration.manager_revisions.rollback_integration_manager_revision(api_version=APIVersion.V1ALPHA) | secops integration manager-revisions rollback |
+| integrations.jobs.revisions.create | v1alpha | chronicle.integration.job_revisions.create_integration_job_revision(api_version=APIVersion.V1ALPHA) | secops integration job-revisions create |
+| integrations.jobs.revisions.delete | v1alpha | chronicle.integration.job_revisions.delete_integration_job_revision(api_version=APIVersion.V1ALPHA) | secops integration job-revisions delete |
+| integrations.jobs.revisions.list | v1alpha | chronicle.integration.job_revisions.list_integration_job_revisions(api_version=APIVersion.V1ALPHA) | secops integration job-revisions list |
+| integrations.jobs.revisions.rollback | v1alpha | chronicle.integration.job_revisions.rollback_integration_job_revision(api_version=APIVersion.V1ALPHA) | secops integration job-revisions rollback |
+| integrations.jobs.jobInstances.create | v1alpha | chronicle.integration.job_instances.create_integration_job_instance(api_version=APIVersion.V1ALPHA) | secops integration job-instances create |
+| integrations.jobs.jobInstances.delete | v1alpha | chronicle.integration.job_instances.delete_integration_job_instance(api_version=APIVersion.V1ALPHA) | secops integration job-instances delete |
+| integrations.jobs.jobInstances.get | v1alpha | chronicle.integration.job_instances.get_integration_job_instance(api_version=APIVersion.V1ALPHA) | secops integration job-instances get |
+| integrations.jobs.jobInstances.list | v1alpha | chronicle.integration.job_instances.list_integration_job_instances(api_version=APIVersion.V1ALPHA) | secops integration job-instances list |
+| integrations.jobs.jobInstances.patch | v1alpha | chronicle.integration.job_instances.update_integration_job_instance(api_version=APIVersion.V1ALPHA) | secops integration job-instances update |
+| integrations.jobs.jobInstances.runOnDemand | v1alpha | chronicle.integration.job_instances.run_integration_job_instance_on_demand(api_version=APIVersion.V1ALPHA) | secops integration job-instances run-on-demand |
+| integrations.jobs.contextProperties.clearAll | v1alpha | chronicle.integration.job_context_properties.delete_all_job_context_properties(api_version=APIVersion.V1ALPHA) | secops integration job-context-properties delete-all |
+| integrations.jobs.contextProperties.create | v1alpha | chronicle.integration.job_context_properties.create_job_context_property(api_version=APIVersion.V1ALPHA) | secops integration job-context-properties create |
+| integrations.jobs.contextProperties.delete | v1alpha | chronicle.integration.job_context_properties.delete_job_context_property(api_version=APIVersion.V1ALPHA) | secops integration job-context-properties delete |
+| integrations.jobs.contextProperties.get | v1alpha | chronicle.integration.job_context_properties.get_job_context_property(api_version=APIVersion.V1ALPHA) | secops integration job-context-properties get |
+| integrations.jobs.contextProperties.list | v1alpha | chronicle.integration.job_context_properties.list_job_context_properties(api_version=APIVersion.V1ALPHA) | secops integration job-context-properties list |
+| integrations.jobs.contextProperties.patch | v1alpha | chronicle.integration.job_context_properties.update_job_context_property(api_version=APIVersion.V1ALPHA) | secops integration job-context-properties update |
+| integrations.jobs.jobInstances.logs.get | v1alpha | chronicle.integration.job_instance_logs.get_job_instance_log(api_version=APIVersion.V1ALPHA) | secops integration job-instance-logs get |
+| integrations.jobs.jobInstances.logs.list | v1alpha | chronicle.integration.job_instance_logs.list_job_instance_logs(api_version=APIVersion.V1ALPHA) | secops integration job-instance-logs list |
+| investigations.fetchAssociated | v1alpha | chronicle.investigations.fetch_associated_investigations | secops investigation fetch-associated |
+| investigations.get | v1alpha | chronicle.investigations.get_investigation | secops investigation get |
+| investigations.list | v1alpha | chronicle.investigations.list_investigations | secops investigation list |
+| investigations.trigger | v1alpha | chronicle.investigations.trigger_investigation | secops investigation trigger |
+| iocs.batchGet | v1alpha | | |
+| iocs.findFirstAndLastSeen | v1alpha | | |
+| iocs.get | v1alpha | | |
+| iocs.getIocState | v1alpha | | |
+| iocs.searchCuratedDetectionsForIoc | v1alpha | | |
+| iocs.updateIocState | v1alpha | | |
+| legacy.legacyBatchGetCases | v1alpha | chronicle.case.get_cases_from_list | secops case |
+| legacy.legacyBatchGetCollections | v1alpha | | |
+| legacy.legacyCreateOrUpdateCase | v1alpha | | |
+| legacy.legacyCreateSoarAlert | v1alpha | | |
+| legacy.legacyFetchAlertsView | v1alpha | chronicle.alert.get_alerts | secops alert |
+| legacy.legacyFetchUdmSearchCsv | v1alpha | chronicle.udm_search.fetch_udm_search_csv | secops search --csv |
+| legacy.legacyFetchUdmSearchView | v1alpha | chronicle.udm_search.fetch_udm_search_view | secops udm-search-view |
+| legacy.legacyFindAssetEvents | v1alpha | | |
+| legacy.legacyFindRawLogs | v1alpha | | |
+| legacy.legacyFindUdmEvents | v1alpha | | |
+| legacy.legacyGetAlert | v1alpha | chronicle.rule_alert.get_alert | |
+| legacy.legacyGetCuratedRulesTrends | v1alpha | | |
+| legacy.legacyGetDetection | v1alpha | | |
+| legacy.legacyGetEventForDetection | v1alpha | | |
+| legacy.legacyGetRuleCounts | v1alpha | | |
+| legacy.legacyGetRulesTrends | v1alpha | | |
+| legacy.legacyListCases | v1alpha | chronicle.case.get_cases | secops case --ids |
+| legacy.legacyRunTestRule | v1alpha | chronicle.rule.run_rule_test | secops rule validate |
+| legacy.legacySearchArtifactEvents | v1alpha | | |
+| legacy.legacySearchArtifactIoCDetails | v1alpha | | |
+| legacy.legacySearchAssetEvents | v1alpha | | |
+| legacy.legacySearchCuratedDetections | v1alpha | | |
+| legacy.legacySearchCustomerStats | v1alpha | | |
+| legacy.legacySearchDetections | v1alpha | chronicle.rule_detection.list_detections | |
+| legacy.legacySearchDomainsRecentlyRegistered | v1alpha | | |
+| legacy.legacySearchDomainsTimingStats | v1alpha | | |
+| legacy.legacySearchEnterpriseWideAlerts | v1alpha | | |
+| legacy.legacySearchEnterpriseWideIoCs | v1alpha | chronicle.ioc.list_iocs | secops iocs |
+| legacy.legacySearchFindings | v1alpha | | |
+| legacy.legacySearchIngestionStats | v1alpha | | |
+| legacy.legacySearchIoCInsights | v1alpha | | |
+| legacy.legacySearchRawLogs | v1alpha | | |
+| legacy.legacySearchRuleDetectionCountBuckets | v1alpha | | |
+| legacy.legacySearchRuleDetectionEvents | v1alpha | | |
+| legacy.legacySearchRuleResults | v1alpha | | |
+| legacy.legacySearchRulesAlerts | v1alpha | chronicle.rule_alert.search_rule_alerts | |
+| legacy.legacySearchUserEvents | v1alpha | | |
+| legacy.legacyStreamDetectionAlerts | v1alpha | | |
+| legacy.legacyTestRuleStreaming | v1alpha | | |
+| legacy.legacyUpdateAlert | v1alpha | chronicle.rule_alert.update_alert | |
+| listAllFindingsRefinementDeployments | v1alpha | | |
+| logProcessingPipelines.associateStreams | v1alpha | chronicle.log_processing_pipelines.associate_streams | secops log-processing associate-streams |
+| logProcessingPipelines.create | v1alpha | chronicle.log_processing_pipelines.create_log_processing_pipeline | secops log-processing create |
+| logProcessingPipelines.delete | v1alpha | chronicle.log_processing_pipelines.delete_log_processing_pipeline | secops log-processing delete |
+| logProcessingPipelines.dissociateStreams | v1alpha | chronicle.log_processing_pipelines.dissociate_streams | secops log-processing dissociate-streams |
+| logProcessingPipelines.fetchAssociatedPipeline | v1alpha | chronicle.log_processing_pipelines.fetch_associated_pipeline | secops log-processing fetch-associated |
+| logProcessingPipelines.fetchSampleLogsByStreams | v1alpha | chronicle.log_processing_pipelines.fetch_sample_logs_by_streams | secops log-processing fetch-sample-logs |
+| logProcessingPipelines.get | v1alpha | chronicle.log_processing_pipelines.get_log_processing_pipeline | secops log-processing get |
+| logProcessingPipelines.list | v1alpha | chronicle.log_processing_pipelines.list_log_processing_pipelines | secops log-processing list |
+| logProcessingPipelines.patch | v1alpha | chronicle.log_processing_pipelines.update_log_processing_pipeline | secops log-processing update |
+| logProcessingPipelines.testPipeline | v1alpha | chronicle.log_processing_pipelines.test_pipeline | secops log-processing test |
+| logTypes.create | v1alpha | | |
+| logTypes.generateEventTypesSuggestions | v1alpha | | |
+| logTypes.get | v1alpha | | |
+| logTypes.getLogTypeSetting | v1alpha | | |
+| logTypes.legacySubmitParserExtension | v1alpha | | |
+| logTypes.list | v1alpha | | |
+| logTypes.logs.export | v1alpha | | |
+| logTypes.logs.get | v1alpha | | |
+| logTypes.logs.import | v1alpha | chronicle.log_ingest.ingest_log | secops log ingest |
+| logTypes.logs.list | v1alpha | | |
+| logTypes.parserExtensions.activate | v1alpha | chronicle.parser_extension.activate_parser_extension | secops parser-extension activate |
+| logTypes.parserExtensions.create | v1alpha | chronicle.parser_extension.create_parser_extension | secops parser-extension create |
+| logTypes.parserExtensions.delete | v1alpha | chronicle.parser_extension.delete_parser_extension | secops parser-extension delete |
+| logTypes.parserExtensions.extensionValidationReports.get | v1alpha | | |
+| logTypes.parserExtensions.extensionValidationReports.list | v1alpha | | |
+| logTypes.parserExtensions.extensionValidationReports.validationErrors.list | v1alpha | | |
+| logTypes.parserExtensions.get | v1alpha | chronicle.parser_extension.get_parser_extension | secops parser-extension get |
+| logTypes.parserExtensions.list | v1alpha | chronicle.parser_extension.list_parser_extensions | secops parser-extension list |
+| logTypes.parserExtensions.validationReports.get | v1alpha | | |
+| logTypes.parserExtensions.validationReports.parsingErrors.list | v1alpha | | |
+| logTypes.parsers.activate | v1alpha | chronicle.parser.activate_parser | secops parser activate |
+| logTypes.parsers.activateReleaseCandidateParser | v1alpha | chronicle.parser.activate_release_candidate | secops parser activate-rc |
+| logTypes.parsers.copy | v1alpha | chronicle.parser.copy_parser | secops parser copy |
+| logTypes.parsers.create | v1alpha | chronicle.parser.create_parser | secops parser create |
+| logTypes.parsers.deactivate | v1alpha | chronicle.parser.deactivate_parser | secops parser deactivate |
+| logTypes.parsers.delete | v1alpha | chronicle.parser.delete_parser | secops parser delete |
+| logTypes.parsers.get | v1alpha | chronicle.parser.get_parser | secops parser get |
+| logTypes.parsers.list | v1alpha | chronicle.parser.list_parsers | secops parser list |
+| logTypes.parsers.validationReports.get | v1alpha | | |
+| logTypes.parsers.validationReports.parsingErrors.list | v1alpha | | |
+| logTypes.patch | v1alpha | | |
+| logTypes.runParser | v1alpha | chronicle.parser.run_parser | secops parser run |
+| logTypes.updateLogTypeSetting | v1alpha | | |
+| logs.classify | v1alpha | chronicle.log_types.classify_logs | secops log classify |
+| marketplaceIntegrations.get | v1alpha | chronicle.marketplace_integrations.get_marketplace_integration(api_version=APIVersion.V1ALPHA) | secops integration marketplace get |
+| marketplaceIntegrations.getDiff | v1alpha | chronicle.marketplace_integrations.get_marketplace_integration_diff(api_version=APIVersion.V1ALPHA) | secops integration marketplace diff |
+| marketplaceIntegrations.install | v1alpha | chronicle.marketplace_integrations.install_marketplace_integration(api_version=APIVersion.V1ALPHA) | secops integration marketplace install |
+| marketplaceIntegrations.list | v1alpha | chronicle.marketplace_integrations.list_marketplace_integrations(api_version=APIVersion.V1ALPHA) | secops integration marketplace list |
+| marketplaceIntegrations.uninstall | v1alpha | chronicle.marketplace_integrations.uninstall_marketplace_integration(api_version=APIVersion.V1ALPHA) | secops integration marketplace uninstall |
+| nativeDashboards.addChart | v1alpha | chronicle.dashboard.add_chart | secops dashboard add-chart |
+| nativeDashboards.create | v1alpha | chronicle.dashboard.create_dashboard | secops dashboard create |
+| nativeDashboards.delete | v1alpha | chronicle.dashboard.delete_dashboard | secops dashboard delete |
+| nativeDashboards.duplicate | v1alpha | chronicle.dashboard.duplicate_dashboard | secops dashboard duplicate |
+| nativeDashboards.duplicateChart | v1alpha | | |
+| nativeDashboards.editChart | v1alpha | chronicle.dashboard.edit_chart | secops dashboard edit-chart |
+| nativeDashboards.export | v1alpha | chronicle.dashboard.export_dashboard | secops dashboard export |
+| nativeDashboards.get | v1alpha | chronicle.dashboard.get_dashboard | secops dashboard get |
+| nativeDashboards.import | v1alpha | chronicle.dashboard.import_dashboard | secops dashboard import |
+| nativeDashboards.list | v1alpha | chronicle.dashboard.list_dashboards | secops dashboard list |
+| nativeDashboards.patch | v1alpha | chronicle.dashboard.update_dashboard | secops dashboard update |
+| nativeDashboards.removeChart | v1alpha | chronicle.dashboard.remove_chart | secops dashboard remove-chart |
+| operations.cancel | v1alpha | | |
+| operations.delete | v1alpha | | |
+| operations.get | v1alpha | | |
+| operations.list | v1alpha | | |
+| operations.streamSearch | v1alpha | | |
+| queryProductSourceStats | v1alpha | | |
+| referenceLists.create | v1alpha | | |
+| referenceLists.get | v1alpha | | |
+| referenceLists.list | v1alpha | | |
+| referenceLists.patch | v1alpha | | |
+| report | v1alpha | | |
+| ruleExecutionErrors.list | v1alpha | chronicle.rule_detection.list_errors | |
+| rules.create | v1alpha | | |
+| rules.delete | v1alpha | | |
+| rules.deployments.list | v1alpha | | |
+| rules.get | v1alpha | | |
+| rules.getDeployment | v1alpha | | |
+| rules.list | v1alpha | | |
+| rules.listRevisions | v1alpha | | |
+| rules.patch | v1alpha | | |
+| rules.retrohunts.create | v1alpha | | |
+| rules.retrohunts.get | v1alpha | | |
+| rules.retrohunts.list | v1alpha | | |
+| rules.updateDeployment | v1alpha | | |
+| searchEntities | v1alpha | | |
+| searchRawLogs | v1alpha | | |
+| summarizeEntitiesFromQuery | v1alpha | chronicle.entity.summarize_entity | secops entity |
+| summarizeEntity | v1alpha | chronicle.entity.summarize_entity | |
+| testFindingsRefinement | v1alpha | | |
+| translateUdmQuery | v1alpha | chronicle.nl_search.translate_nl_to_udm | |
+| translateYlRule | v1alpha | | |
+| udmSearch | v1alpha | chronicle.search.search_udm | secops search |
+| undelete | v1alpha | | |
+| updateBigQueryExport | v1alpha | | |
+| updateRiskConfig | v1alpha | | |
+| users.clearConversationHistory | v1alpha | | |
+| users.conversations.create | v1alpha | chronicle.gemini.create_conversation | |
+| users.conversations.delete | v1alpha | | |
+| users.conversations.get | v1alpha | | |
+| users.conversations.list | v1alpha | | |
+| users.conversations.messages.create | v1alpha | chronicle.gemini.query_gemini | secops gemini |
+| users.conversations.messages.delete | v1alpha | | |
+| users.conversations.messages.get | v1alpha | | |
+| users.conversations.messages.list | v1alpha | | |
+| users.conversations.messages.patch | v1alpha | | |
+| users.conversations.patch | v1alpha | | |
+| users.getPreferenceSet | v1alpha | chronicle.gemini.opt_in_to_gemini | secops gemini --opt-in |
+| users.searchQueries.create | v1alpha | | |
+| users.searchQueries.delete | v1alpha | | |
+| users.searchQueries.get | v1alpha | | |
+| users.searchQueries.list | v1alpha | | |
+| users.searchQueries.patch | v1alpha | | |
+| users.updatePreferenceSet | v1alpha | | |
+| validateQuery | v1alpha | chronicle.validate.validate_query | |
+| verifyReferenceList | v1alpha | | |
+| verifyRuleText | v1alpha | chronicle.rule_validation.validate_rule | secops rule validate |
+| watchlists.create | v1alpha | | |
+| watchlists.delete | v1alpha | | |
+| watchlists.entities.add | v1alpha | | |
+| watchlists.entities.batchAdd | v1alpha | | |
+| watchlists.entities.batchRemove | v1alpha | | |
+| watchlists.entities.remove | v1alpha | | |
+| watchlists.get | v1alpha | | |
+| watchlists.list | v1alpha | | |
+| watchlists.listEntities | v1alpha | | |
+| watchlists.patch | v1alpha | | |
| REST Resource | Version | secops-wrapper module | CLI Command |
|--------------------------------------------------------------------------------|---------|-------------------------------------------------------------------------------------------------------------------|------------------------------------------------|
| dataAccessLabels.create | v1 | | |
diff --git a/src/secops/chronicle/__init__.py b/src/secops/chronicle/__init__.py
index 15320ac9..b3d0b286 100644
--- a/src/secops/chronicle/__init__.py
+++ b/src/secops/chronicle/__init__.py
@@ -98,27 +98,43 @@
search_log_types,
)
from secops.chronicle.models import (
+ AdvancedConfig,
AlertCount,
AlertState,
Case,
CaseList,
+ DailyScheduleDetails,
DataExport,
DataExportStage,
DataExportStatus,
+ Date,
+ DayOfWeek,
DetectionType,
+ DiffType,
Entity,
EntityMetadata,
EntityMetrics,
EntitySummary,
FileMetadataAndProperties,
InputInterval,
+ IntegrationJobInstanceParameter,
+ IntegrationParam,
+ IntegrationParamType,
+ IntegrationType,
ListBasis,
+ MonthlyScheduleDetails,
+ OneTimeScheduleDetails,
PrevalenceData,
+ PythonVersion,
+ ScheduleType,
SoarPlatformInfo,
+ TargetMode,
TileType,
TimeInterval,
Timeline,
TimelineBucket,
+ TimeOfDay,
+ WeeklyScheduleDetails,
WidgetMetadata,
)
from secops.chronicle.nl_search import translate_nl_to_udm
@@ -198,6 +214,169 @@
create_watchlist,
update_watchlist,
)
+from secops.chronicle.integration.integrations import (
+ list_integrations,
+ get_integration,
+ delete_integration,
+ create_integration,
+ transition_integration,
+ update_integration,
+ update_custom_integration,
+ get_integration_affected_items,
+ get_integration_dependencies,
+ get_integration_diff,
+ get_integration_restricted_agents,
+)
+from secops.chronicle.integration.actions import (
+ list_integration_actions,
+ get_integration_action,
+ delete_integration_action,
+ create_integration_action,
+ update_integration_action,
+ execute_integration_action_test,
+ get_integration_actions_by_environment,
+ get_integration_action_template,
+)
+from secops.chronicle.integration.action_revisions import (
+ list_integration_action_revisions,
+ delete_integration_action_revision,
+ create_integration_action_revision,
+ rollback_integration_action_revision,
+)
+from secops.chronicle.integration.connectors import (
+ list_integration_connectors,
+ get_integration_connector,
+ delete_integration_connector,
+ create_integration_connector,
+ update_integration_connector,
+ execute_integration_connector_test,
+ get_integration_connector_template,
+)
+from secops.chronicle.integration.connector_revisions import (
+ list_integration_connector_revisions,
+ delete_integration_connector_revision,
+ create_integration_connector_revision,
+ rollback_integration_connector_revision,
+)
+from secops.chronicle.integration.connector_context_properties import (
+ list_connector_context_properties,
+ get_connector_context_property,
+ delete_connector_context_property,
+ create_connector_context_property,
+ update_connector_context_property,
+ delete_all_connector_context_properties,
+)
+from secops.chronicle.integration.connector_instance_logs import (
+ list_connector_instance_logs,
+ get_connector_instance_log,
+)
+from secops.chronicle.integration.connector_instances import (
+ list_connector_instances,
+ get_connector_instance,
+ delete_connector_instance,
+ create_connector_instance,
+ update_connector_instance,
+ get_connector_instance_latest_definition,
+ set_connector_instance_logs_collection,
+ run_connector_instance_on_demand,
+)
+from secops.chronicle.integration.jobs import (
+ list_integration_jobs,
+ get_integration_job,
+ delete_integration_job,
+ create_integration_job,
+ update_integration_job,
+ execute_integration_job_test,
+ get_integration_job_template,
+)
+from secops.chronicle.integration.managers import (
+ list_integration_managers,
+ get_integration_manager,
+ delete_integration_manager,
+ create_integration_manager,
+ update_integration_manager,
+ get_integration_manager_template,
+)
+from secops.chronicle.integration.manager_revisions import (
+ list_integration_manager_revisions,
+ get_integration_manager_revision,
+ delete_integration_manager_revision,
+ create_integration_manager_revision,
+ rollback_integration_manager_revision,
+)
+from secops.chronicle.integration.job_revisions import (
+ list_integration_job_revisions,
+ delete_integration_job_revision,
+ create_integration_job_revision,
+ rollback_integration_job_revision,
+)
+from secops.chronicle.integration.job_instances import (
+ list_integration_job_instances,
+ get_integration_job_instance,
+ delete_integration_job_instance,
+ create_integration_job_instance,
+ update_integration_job_instance,
+ run_integration_job_instance_on_demand,
+)
+from secops.chronicle.integration.job_context_properties import (
+ list_job_context_properties,
+ get_job_context_property,
+ delete_job_context_property,
+ create_job_context_property,
+ update_job_context_property,
+ delete_all_job_context_properties,
+)
+from secops.chronicle.integration.job_instance_logs import (
+ list_job_instance_logs,
+ get_job_instance_log,
+)
+from secops.chronicle.integration.integration_instances import (
+ list_integration_instances,
+ get_integration_instance,
+ delete_integration_instance,
+ create_integration_instance,
+ update_integration_instance,
+ execute_integration_instance_test,
+ get_integration_instance_affected_items,
+ get_default_integration_instance,
+)
+from secops.chronicle.integration.transformers import (
+ list_integration_transformers,
+ get_integration_transformer,
+ delete_integration_transformer,
+ create_integration_transformer,
+ update_integration_transformer,
+ execute_integration_transformer_test,
+ get_integration_transformer_template,
+)
+from secops.chronicle.integration.transformer_revisions import (
+ list_integration_transformer_revisions,
+ delete_integration_transformer_revision,
+ create_integration_transformer_revision,
+ rollback_integration_transformer_revision,
+)
+from secops.chronicle.integration.logical_operators import (
+ list_integration_logical_operators,
+ get_integration_logical_operator,
+ delete_integration_logical_operator,
+ create_integration_logical_operator,
+ update_integration_logical_operator,
+ execute_integration_logical_operator_test,
+ get_integration_logical_operator_template,
+)
+from secops.chronicle.integration.logical_operator_revisions import (
+ list_integration_logical_operator_revisions,
+ delete_integration_logical_operator_revision,
+ create_integration_logical_operator_revision,
+ rollback_integration_logical_operator_revision,
+)
+from secops.chronicle.integration.marketplace_integrations import (
+ list_marketplace_integrations,
+ get_marketplace_integration,
+ get_marketplace_integration_diff,
+ install_marketplace_integration,
+ uninstall_marketplace_integration,
+)
__all__ = [
# Client
@@ -315,21 +494,31 @@
"execute_query",
"get_execute_query",
# Models
+ "AdvancedConfig",
+ "AlertCount",
+ "AlertState",
+ "Case",
+ "CaseList",
+ "DailyScheduleDetails",
+ "Date",
+ "DayOfWeek",
"Entity",
"EntityMetadata",
"EntityMetrics",
+ "EntitySummary",
+ "FileMetadataAndProperties",
+ "IntegrationJobInstanceParameter",
+ "MonthlyScheduleDetails",
+ "OneTimeScheduleDetails",
+ "PrevalenceData",
+ "ScheduleType",
+ "SoarPlatformInfo",
"TimeInterval",
- "TimelineBucket",
"Timeline",
+ "TimelineBucket",
+ "TimeOfDay",
+ "WeeklyScheduleDetails",
"WidgetMetadata",
- "EntitySummary",
- "AlertCount",
- "AlertState",
- "Case",
- "SoarPlatformInfo",
- "CaseList",
- "PrevalenceData",
- "FileMetadataAndProperties",
"ValidationResult",
"GeminiResponse",
"Block",
@@ -367,4 +556,146 @@
"delete_watchlist",
"create_watchlist",
"update_watchlist",
+ # Integrations
+ "list_integrations",
+ "get_integration",
+ "delete_integration",
+ "create_integration",
+ "transition_integration",
+ "update_integration",
+ "update_custom_integration",
+ "get_integration_affected_items",
+ "get_integration_dependencies",
+ "get_integration_diff",
+ "get_integration_restricted_agents",
+ # Integration Actions
+ "list_integration_actions",
+ "get_integration_action",
+ "delete_integration_action",
+ "create_integration_action",
+ "update_integration_action",
+ "execute_integration_action_test",
+ "get_integration_actions_by_environment",
+ "get_integration_action_template",
+ # Integration Action Revisions
+ "list_integration_action_revisions",
+ "delete_integration_action_revision",
+ "create_integration_action_revision",
+ "rollback_integration_action_revision",
+ # Integration Connectors
+ "list_integration_connectors",
+ "get_integration_connector",
+ "delete_integration_connector",
+ "create_integration_connector",
+ "update_integration_connector",
+ "execute_integration_connector_test",
+ "get_integration_connector_template",
+ # Integration Connector Revisions
+ "list_integration_connector_revisions",
+ "delete_integration_connector_revision",
+ "create_integration_connector_revision",
+ "rollback_integration_connector_revision",
+ # Connector Context Properties
+ "list_connector_context_properties",
+ "get_connector_context_property",
+ "delete_connector_context_property",
+ "create_connector_context_property",
+ "update_connector_context_property",
+ "delete_all_connector_context_properties",
+ # Connector Instance Logs
+ "list_connector_instance_logs",
+ "get_connector_instance_log",
+ # Connector Instances
+ "list_connector_instances",
+ "get_connector_instance",
+ "delete_connector_instance",
+ "create_connector_instance",
+ "update_connector_instance",
+ "get_connector_instance_latest_definition",
+ "set_connector_instance_logs_collection",
+ "run_connector_instance_on_demand",
+ # Integration Jobs
+ "list_integration_jobs",
+ "get_integration_job",
+ "delete_integration_job",
+ "create_integration_job",
+ "update_integration_job",
+ "execute_integration_job_test",
+ "get_integration_job_template",
+ # Integration Managers
+ "list_integration_managers",
+ "get_integration_manager",
+ "delete_integration_manager",
+ "create_integration_manager",
+ "update_integration_manager",
+ "get_integration_manager_template",
+ # Integration Manager Revisions
+ "list_integration_manager_revisions",
+ "get_integration_manager_revision",
+ "delete_integration_manager_revision",
+ "create_integration_manager_revision",
+ "rollback_integration_manager_revision",
+ # Integration Job Revisions
+ "list_integration_job_revisions",
+ "delete_integration_job_revision",
+ "create_integration_job_revision",
+ "rollback_integration_job_revision",
+ # Integration Job Instances
+ "list_integration_job_instances",
+ "get_integration_job_instance",
+ "delete_integration_job_instance",
+ "create_integration_job_instance",
+ "update_integration_job_instance",
+ "run_integration_job_instance_on_demand",
+ # Job Context Properties
+ "list_job_context_properties",
+ "get_job_context_property",
+ "delete_job_context_property",
+ "create_job_context_property",
+ "update_job_context_property",
+ "delete_all_job_context_properties",
+ # Job Instance Logs
+ "list_job_instance_logs",
+ "get_job_instance_log",
+ # Integration Instances
+ "list_integration_instances",
+ "get_integration_instance",
+ "delete_integration_instance",
+ "create_integration_instance",
+ "update_integration_instance",
+ "execute_integration_instance_test",
+ "get_integration_instance_affected_items",
+ "get_default_integration_instance",
+ # Integration Transformers
+ "list_integration_transformers",
+ "get_integration_transformer",
+ "delete_integration_transformer",
+ "create_integration_transformer",
+ "update_integration_transformer",
+ "execute_integration_transformer_test",
+ "get_integration_transformer_template",
+ # Integration Transformer Revisions
+ "list_integration_transformer_revisions",
+ "delete_integration_transformer_revision",
+ "create_integration_transformer_revision",
+ "rollback_integration_transformer_revision",
+ # Integration Logical Operators
+ "list_integration_logical_operators",
+ "get_integration_logical_operator",
+ "delete_integration_logical_operator",
+ "create_integration_logical_operator",
+ "update_integration_logical_operator",
+ "execute_integration_logical_operator_test",
+ "get_integration_logical_operator_template",
+ # Integration Logical Operator Revisions
+ "list_integration_logical_operator_revisions",
+ "delete_integration_logical_operator_revision",
+ "create_integration_logical_operator_revision",
+ "rollback_integration_logical_operator_revision",
+ # Marketplace Integrations
+ "list_marketplace_integrations",
+ "get_marketplace_integration",
+ "get_marketplace_integration_diff",
+ "install_marketplace_integration",
+ "uninstall_marketplace_integration",
]
diff --git a/src/secops/chronicle/client.py b/src/secops/chronicle/client.py
index 9b892272..e69a9fd2 100644
--- a/src/secops/chronicle/client.py
+++ b/src/secops/chronicle/client.py
@@ -13,6 +13,7 @@
# limitations under the License.
#
"""Chronicle API client."""
+
import ipaddress
import re
from collections.abc import Iterator
@@ -22,246 +23,363 @@
from google.auth.transport import requests as google_auth_requests
+# pylint: disable=line-too-long
from secops import auth as secops_auth
from secops.auth import RetryConfig
from secops.chronicle.alert import get_alerts as _get_alerts
from secops.chronicle.case import get_cases_from_list
-from secops.chronicle.dashboard import DashboardAccessType, DashboardView
-from secops.chronicle.dashboard import add_chart as _add_chart
-from secops.chronicle.dashboard import create_dashboard as _create_dashboard
-from secops.chronicle.dashboard import delete_dashboard as _delete_dashboard
from secops.chronicle.dashboard import (
+ DashboardAccessType,
+ DashboardView,
+ add_chart as _add_chart,
+ create_dashboard as _create_dashboard,
+ delete_dashboard as _delete_dashboard,
duplicate_dashboard as _duplicate_dashboard,
+ edit_chart as _edit_chart,
+ export_dashboard as _export_dashboard,
+ get_chart as _get_chart,
+ get_dashboard as _get_dashboard,
+ import_dashboard as _import_dashboard,
+ list_dashboards as _list_dashboards,
+ remove_chart as _remove_chart,
+ update_dashboard as _update_dashboard,
)
-from secops.chronicle.dashboard import edit_chart as _edit_chart
-from secops.chronicle.dashboard import export_dashboard as _export_dashboard
-from secops.chronicle.dashboard import get_chart as _get_chart
-from secops.chronicle.dashboard import get_dashboard as _get_dashboard
-from secops.chronicle.dashboard import import_dashboard as _import_dashboard
-from secops.chronicle.dashboard import list_dashboards as _list_dashboards
-from secops.chronicle.dashboard import remove_chart as _remove_chart
-from secops.chronicle.dashboard import update_dashboard as _update_dashboard
from secops.chronicle.dashboard_query import (
execute_query as _execute_dashboard_query,
-)
-from secops.chronicle.dashboard_query import (
get_execute_query as _get_execute_query,
)
from secops.chronicle.data_export import (
cancel_data_export as _cancel_data_export,
-)
-from secops.chronicle.data_export import (
create_data_export as _create_data_export,
-)
-from secops.chronicle.data_export import (
fetch_available_log_types as _fetch_available_log_types,
-)
-from secops.chronicle.data_export import get_data_export as _get_data_export
-from secops.chronicle.data_export import list_data_export as _list_data_export
-from secops.chronicle.data_export import (
+ get_data_export as _get_data_export,
+ list_data_export as _list_data_export,
update_data_export as _update_data_export,
)
-from secops.chronicle.data_table import DataTableColumnType
-from secops.chronicle.data_table import create_data_table as _create_data_table
from secops.chronicle.data_table import (
+ DataTableColumnType,
+ create_data_table as _create_data_table,
create_data_table_rows as _create_data_table_rows,
-)
-from secops.chronicle.data_table import delete_data_table as _delete_data_table
-from secops.chronicle.data_table import (
+ delete_data_table as _delete_data_table,
delete_data_table_rows as _delete_data_table_rows,
-)
-from secops.chronicle.data_table import get_data_table as _get_data_table
-from secops.chronicle.data_table import (
+ get_data_table as _get_data_table,
list_data_table_rows as _list_data_table_rows,
-)
-from secops.chronicle.data_table import list_data_tables as _list_data_tables
-from secops.chronicle.data_table import (
+ list_data_tables as _list_data_tables,
replace_data_table_rows as _replace_data_table_rows,
-)
-from secops.chronicle.data_table import update_data_table as _update_data_table
-from secops.chronicle.data_table import (
+ update_data_table as _update_data_table,
update_data_table_rows as _update_data_table_rows,
)
-from secops.chronicle.entity import _detect_value_type_for_query
-from secops.chronicle.entity import summarize_entity as _summarize_entity
-from secops.chronicle.feeds import CreateFeedModel, UpdateFeedModel
-from secops.chronicle.feeds import create_feed as _create_feed
-from secops.chronicle.feeds import delete_feed as _delete_feed
-from secops.chronicle.feeds import disable_feed as _disable_feed
-from secops.chronicle.feeds import enable_feed as _enable_feed
-from secops.chronicle.feeds import generate_secret as _generate_secret
-from secops.chronicle.feeds import get_feed as _get_feed
-from secops.chronicle.feeds import list_feeds as _list_feeds
-from secops.chronicle.feeds import update_feed as _update_feed
-from secops.chronicle.gemini import GeminiResponse
-from secops.chronicle.gemini import opt_in_to_gemini as _opt_in_to_gemini
-from secops.chronicle.gemini import query_gemini as _query_gemini
-from secops.chronicle.ioc import list_iocs as _list_iocs
-from secops.chronicle.investigations import (
- fetch_associated_investigations as _fetch_associated_investigations,
+from secops.chronicle.entity import (
+ _detect_value_type_for_query,
+ summarize_entity as _summarize_entity,
)
-from secops.chronicle.investigations import (
- get_investigation as _get_investigation,
+from secops.chronicle.featured_content_rules import (
+ list_featured_content_rules as _list_featured_content_rules,
)
-from secops.chronicle.investigations import (
- list_investigations as _list_investigations,
+from secops.chronicle.feeds import (
+ CreateFeedModel,
+ UpdateFeedModel,
+ create_feed as _create_feed,
+ delete_feed as _delete_feed,
+ disable_feed as _disable_feed,
+ enable_feed as _enable_feed,
+ generate_secret as _generate_secret,
+ get_feed as _get_feed,
+ list_feeds as _list_feeds,
+ update_feed as _update_feed,
+)
+from secops.chronicle.gemini import (
+ GeminiResponse,
+ opt_in_to_gemini as _opt_in_to_gemini,
+ query_gemini as _query_gemini,
)
from secops.chronicle.investigations import (
+ fetch_associated_investigations as _fetch_associated_investigations,
+ get_investigation as _get_investigation,
+ list_investigations as _list_investigations,
trigger_investigation as _trigger_investigation,
)
-from secops.chronicle.log_ingest import create_forwarder as _create_forwarder
-from secops.chronicle.log_ingest import delete_forwarder as _delete_forwarder
-from secops.chronicle.log_ingest import get_forwarder as _get_forwarder
+from secops.chronicle.ioc import list_iocs as _list_iocs
from secops.chronicle.log_ingest import (
+ create_forwarder as _create_forwarder,
+ delete_forwarder as _delete_forwarder,
+ get_forwarder as _get_forwarder,
get_or_create_forwarder as _get_or_create_forwarder,
+ import_entities as _import_entities,
+ ingest_log as _ingest_log,
+ ingest_udm as _ingest_udm,
+ list_forwarders as _list_forwarders,
+ update_forwarder as _update_forwarder,
+)
+from secops.chronicle.log_processing_pipelines import (
+ associate_streams as _associate_streams,
+ create_log_processing_pipeline as _create_log_processing_pipeline,
+ delete_log_processing_pipeline as _delete_log_processing_pipeline,
+ dissociate_streams as _dissociate_streams,
+ fetch_associated_pipeline as _fetch_associated_pipeline,
+ fetch_sample_logs_by_streams as _fetch_sample_logs_by_streams,
+ get_log_processing_pipeline as _get_log_processing_pipeline,
+ list_log_processing_pipelines as _list_log_processing_pipelines,
+ test_pipeline as _test_pipeline,
+ update_log_processing_pipeline as _update_log_processing_pipeline,
)
-from secops.chronicle.log_ingest import import_entities as _import_entities
-from secops.chronicle.log_ingest import ingest_log as _ingest_log
-from secops.chronicle.log_ingest import ingest_udm as _ingest_udm
-from secops.chronicle.log_ingest import list_forwarders as _list_forwarders
-from secops.chronicle.log_ingest import update_forwarder as _update_forwarder
-from secops.chronicle.log_types import classify_logs as _classify_logs
-from secops.chronicle.log_types import get_all_log_types as _get_all_log_types
from secops.chronicle.log_types import (
+ classify_logs as _classify_logs,
+ get_all_log_types as _get_all_log_types,
get_log_type_description as _get_log_type_description,
+ is_valid_log_type as _is_valid_log_type,
+ search_log_types as _search_log_types,
)
-from secops.chronicle.log_types import is_valid_log_type as _is_valid_log_type
-from secops.chronicle.log_types import search_log_types as _search_log_types
-from secops.chronicle.log_processing_pipelines import (
- associate_streams as _associate_streams,
+from secops.chronicle.integration.marketplace_integrations import (
+ get_marketplace_integration as _get_marketplace_integration,
+ get_marketplace_integration_diff as _get_marketplace_integration_diff,
+ install_marketplace_integration as _install_marketplace_integration,
+ list_marketplace_integrations as _list_marketplace_integrations,
+ uninstall_marketplace_integration as _uninstall_marketplace_integration,
)
-from secops.chronicle.log_processing_pipelines import (
- create_log_processing_pipeline as _create_log_processing_pipeline,
+from secops.chronicle.integration.integrations import (
+ create_integration as _create_integration,
+ delete_integration as _delete_integration,
+ download_integration as _download_integration,
+ download_integration_dependency as _download_integration_dependency,
+ export_integration_items as _export_integration_items,
+ get_agent_integrations as _get_agent_integrations,
+ get_integration as _get_integration,
+ get_integration_affected_items as _get_integration_affected_items,
+ get_integration_dependencies as _get_integration_dependencies,
+ get_integration_diff as _get_integration_diff,
+ get_integration_restricted_agents as _get_integration_restricted_agents,
+ list_integrations as _list_integrations,
+ transition_integration as _transition_integration,
+ update_custom_integration as _update_custom_integration,
+ update_integration as _update_integration,
)
-from secops.chronicle.log_processing_pipelines import (
- delete_log_processing_pipeline as _delete_log_processing_pipeline,
+from secops.chronicle.integration.actions import (
+ create_integration_action as _create_integration_action,
+ delete_integration_action as _delete_integration_action,
+ execute_integration_action_test as _execute_integration_action_test,
+ get_integration_action as _get_integration_action,
+ get_integration_action_template as _get_integration_action_template,
+ get_integration_actions_by_environment as _get_integration_actions_by_environment,
+ list_integration_actions as _list_integration_actions,
+ update_integration_action as _update_integration_action,
)
-from secops.chronicle.log_processing_pipelines import (
- dissociate_streams as _dissociate_streams,
+from secops.chronicle.integration.action_revisions import (
+ create_integration_action_revision as _create_integration_action_revision,
+ delete_integration_action_revision as _delete_integration_action_revision,
+ list_integration_action_revisions as _list_integration_action_revisions,
+ rollback_integration_action_revision as _rollback_integration_action_revision,
)
-from secops.chronicle.log_processing_pipelines import (
- fetch_associated_pipeline as _fetch_associated_pipeline,
+from secops.chronicle.integration.connectors import (
+ create_integration_connector as _create_integration_connector,
+ delete_integration_connector as _delete_integration_connector,
+ execute_integration_connector_test as _execute_integration_connector_test,
+ get_integration_connector as _get_integration_connector,
+ get_integration_connector_template as _get_integration_connector_template,
+ list_integration_connectors as _list_integration_connectors,
+ update_integration_connector as _update_integration_connector,
)
-from secops.chronicle.log_processing_pipelines import (
- fetch_sample_logs_by_streams as _fetch_sample_logs_by_streams,
+from secops.chronicle.integration.connector_revisions import (
+ create_integration_connector_revision as _create_integration_connector_revision,
+ delete_integration_connector_revision as _delete_integration_connector_revision,
+ list_integration_connector_revisions as _list_integration_connector_revisions,
+ rollback_integration_connector_revision as _rollback_integration_connector_revision,
)
-from secops.chronicle.log_processing_pipelines import (
- get_log_processing_pipeline as _get_log_processing_pipeline,
+from secops.chronicle.integration.connector_context_properties import (
+ create_connector_context_property as _create_connector_context_property,
+ delete_all_connector_context_properties as _delete_all_connector_context_properties,
+ delete_connector_context_property as _delete_connector_context_property,
+ get_connector_context_property as _get_connector_context_property,
+ list_connector_context_properties as _list_connector_context_properties,
+ update_connector_context_property as _update_connector_context_property,
)
-from secops.chronicle.log_processing_pipelines import (
- list_log_processing_pipelines as _list_log_processing_pipelines,
+from secops.chronicle.integration.connector_instance_logs import (
+ get_connector_instance_log as _get_connector_instance_log,
+ list_connector_instance_logs as _list_connector_instance_logs,
)
-from secops.chronicle.log_processing_pipelines import (
- update_log_processing_pipeline as _update_log_processing_pipeline,
+from secops.chronicle.integration.connector_instances import (
+ create_connector_instance as _create_connector_instance,
+ delete_connector_instance as _delete_connector_instance,
+ get_connector_instance as _get_connector_instance,
+ get_connector_instance_latest_definition as _get_connector_instance_latest_definition,
+ list_connector_instances as _list_connector_instances,
+ run_connector_instance_on_demand as _run_connector_instance_on_demand,
+ set_connector_instance_logs_collection as _set_connector_instance_logs_collection,
+ update_connector_instance as _update_connector_instance,
)
-from secops.chronicle.log_processing_pipelines import (
- test_pipeline as _test_pipeline,
+from secops.chronicle.integration.jobs import (
+ create_integration_job as _create_integration_job,
+ delete_integration_job as _delete_integration_job,
+ execute_integration_job_test as _execute_integration_job_test,
+ get_integration_job as _get_integration_job,
+ get_integration_job_template as _get_integration_job_template,
+ list_integration_jobs as _list_integration_jobs,
+ update_integration_job as _update_integration_job,
+)
+from secops.chronicle.integration.managers import (
+ create_integration_manager as _create_integration_manager,
+ delete_integration_manager as _delete_integration_manager,
+ get_integration_manager as _get_integration_manager,
+ get_integration_manager_template as _get_integration_manager_template,
+ list_integration_managers as _list_integration_managers,
+ update_integration_manager as _update_integration_manager,
+)
+from secops.chronicle.integration.manager_revisions import (
+ create_integration_manager_revision as _create_integration_manager_revision,
+ delete_integration_manager_revision as _delete_integration_manager_revision,
+ get_integration_manager_revision as _get_integration_manager_revision,
+ list_integration_manager_revisions as _list_integration_manager_revisions,
+ rollback_integration_manager_revision as _rollback_integration_manager_revision,
+)
+from secops.chronicle.integration.job_revisions import (
+ create_integration_job_revision as _create_integration_job_revision,
+ delete_integration_job_revision as _delete_integration_job_revision,
+ list_integration_job_revisions as _list_integration_job_revisions,
+ rollback_integration_job_revision as _rollback_integration_job_revision,
+)
+from secops.chronicle.integration.job_instances import (
+ create_integration_job_instance as _create_integration_job_instance,
+ delete_integration_job_instance as _delete_integration_job_instance,
+ get_integration_job_instance as _get_integration_job_instance,
+ list_integration_job_instances as _list_integration_job_instances,
+ run_integration_job_instance_on_demand as _run_integration_job_instance_on_demand,
+ update_integration_job_instance as _update_integration_job_instance,
+)
+from secops.chronicle.integration.job_context_properties import (
+ create_job_context_property as _create_job_context_property,
+ delete_all_job_context_properties as _delete_all_job_context_properties,
+ delete_job_context_property as _delete_job_context_property,
+ get_job_context_property as _get_job_context_property,
+ list_job_context_properties as _list_job_context_properties,
+ update_job_context_property as _update_job_context_property,
+)
+from secops.chronicle.integration.job_instance_logs import (
+ get_job_instance_log as _get_job_instance_log,
+ list_job_instance_logs as _list_job_instance_logs,
+)
+from secops.chronicle.integration.integration_instances import (
+ create_integration_instance as _create_integration_instance,
+ delete_integration_instance as _delete_integration_instance,
+ execute_integration_instance_test as _execute_integration_instance_test,
+ get_default_integration_instance as _get_default_integration_instance,
+ get_integration_instance as _get_integration_instance,
+ get_integration_instance_affected_items as _get_integration_instance_affected_items,
+ list_integration_instances as _list_integration_instances,
+ update_integration_instance as _update_integration_instance,
+)
+from secops.chronicle.integration.transformers import (
+ create_integration_transformer as _create_integration_transformer,
+ delete_integration_transformer as _delete_integration_transformer,
+ execute_integration_transformer_test as _execute_integration_transformer_test,
+ get_integration_transformer as _get_integration_transformer,
+ get_integration_transformer_template as _get_integration_transformer_template,
+ list_integration_transformers as _list_integration_transformers,
+ update_integration_transformer as _update_integration_transformer,
+)
+from secops.chronicle.integration.transformer_revisions import (
+ create_integration_transformer_revision as _create_integration_transformer_revision,
+ delete_integration_transformer_revision as _delete_integration_transformer_revision,
+ list_integration_transformer_revisions as _list_integration_transformer_revisions,
+ rollback_integration_transformer_revision as _rollback_integration_transformer_revision,
+)
+from secops.chronicle.integration.logical_operators import (
+ create_integration_logical_operator as _create_integration_logical_operator,
+ delete_integration_logical_operator as _delete_integration_logical_operator,
+ execute_integration_logical_operator_test as _execute_integration_logical_operator_test,
+ get_integration_logical_operator as _get_integration_logical_operator,
+ get_integration_logical_operator_template as _get_integration_logical_operator_template,
+ list_integration_logical_operators as _list_integration_logical_operators,
+ update_integration_logical_operator as _update_integration_logical_operator,
+)
+from secops.chronicle.integration.logical_operator_revisions import (
+ create_integration_logical_operator_revision as _create_integration_logical_operator_revision,
+ delete_integration_logical_operator_revision as _delete_integration_logical_operator_revision,
+ list_integration_logical_operator_revisions as _list_integration_logical_operator_revisions,
+ rollback_integration_logical_operator_revision as _rollback_integration_logical_operator_revision,
)
from secops.chronicle.models import (
APIVersion,
CaseList,
+ ConnectorParameter,
+ ConnectorRule,
DashboardChart,
DashboardQuery,
+ DiffType,
EntitySummary,
InputInterval,
+ IntegrationInstanceParameter,
+ IntegrationType,
+ JobParameter,
+ PythonVersion,
+ TargetMode,
TileType,
+ IntegrationParam,
+ ConnectorInstanceParameter,
+)
+from secops.chronicle.nl_search import (
+ nl_search as _nl_search,
+ translate_nl_to_udm,
)
-from secops.chronicle.nl_search import nl_search as _nl_search
-from secops.chronicle.nl_search import translate_nl_to_udm
-from secops.chronicle.parser import activate_parser as _activate_parser
from secops.chronicle.parser import (
+ activate_parser as _activate_parser,
activate_release_candidate_parser as _activate_release_candidate_parser,
+ copy_parser as _copy_parser,
+ create_parser as _create_parser,
+ deactivate_parser as _deactivate_parser,
+ delete_parser as _delete_parser,
+ get_parser as _get_parser,
+ list_parsers as _list_parsers,
+ run_parser as _run_parser,
)
-from secops.chronicle.parser import copy_parser as _copy_parser
-from secops.chronicle.parser import create_parser as _create_parser
-from secops.chronicle.parser import deactivate_parser as _deactivate_parser
-from secops.chronicle.parser import delete_parser as _delete_parser
-from secops.chronicle.parser import get_parser as _get_parser
-from secops.chronicle.parser import list_parsers as _list_parsers
-from secops.chronicle.parser import run_parser as _run_parser
-from secops.chronicle.parser_extension import ParserExtensionConfig
from secops.chronicle.parser_extension import (
+ ParserExtensionConfig,
activate_parser_extension as _activate_parser_extension,
-)
-from secops.chronicle.parser_extension import (
create_parser_extension as _create_parser_extension,
-)
-from secops.chronicle.parser_extension import (
delete_parser_extension as _delete_parser_extension,
-)
-from secops.chronicle.parser_extension import (
get_parser_extension as _get_parser_extension,
-)
-from secops.chronicle.parser_extension import (
list_parser_extensions as _list_parser_extensions,
)
from secops.chronicle.reference_list import (
ReferenceListSyntaxType,
ReferenceListView,
-)
-from secops.chronicle.reference_list import (
create_reference_list as _create_reference_list,
-)
-from secops.chronicle.reference_list import (
get_reference_list as _get_reference_list,
-)
-from secops.chronicle.reference_list import (
list_reference_lists as _list_reference_lists,
-)
-from secops.chronicle.reference_list import (
update_reference_list as _update_reference_list,
)
-
-# Import rule functions
-from secops.chronicle.rule import create_rule as _create_rule
-from secops.chronicle.rule import delete_rule as _delete_rule
-from secops.chronicle.rule import enable_rule as _enable_rule
-from secops.chronicle.rule import get_rule as _get_rule
-from secops.chronicle.rule import get_rule_deployment as _get_rule_deployment
from secops.chronicle.rule import (
+ create_rule as _create_rule,
+ delete_rule as _delete_rule,
+ enable_rule as _enable_rule,
+ get_rule as _get_rule,
+ get_rule_deployment as _get_rule_deployment,
list_rule_deployments as _list_rule_deployments,
-)
-from secops.chronicle.rule import list_rules as _list_rules
-from secops.chronicle.rule import run_rule_test
-from secops.chronicle.rule import search_rules as _search_rules
-from secops.chronicle.rule import set_rule_alerting as _set_rule_alerting
-from secops.chronicle.rule import update_rule as _update_rule
-from secops.chronicle.rule import (
+ list_rules as _list_rules,
+ run_rule_test,
+ search_rules as _search_rules,
+ set_rule_alerting as _set_rule_alerting,
+ update_rule as _update_rule,
update_rule_deployment as _update_rule_deployment,
)
from secops.chronicle.rule_alert import (
bulk_update_alerts as _bulk_update_alerts,
-)
-from secops.chronicle.rule_alert import get_alert as _get_alert
-from secops.chronicle.rule_alert import (
+ get_alert as _get_alert,
search_rule_alerts as _search_rule_alerts,
+ update_alert as _update_alert,
+)
+from secops.chronicle.rule_detection import (
+ list_detections as _list_detections,
+ list_errors as _list_errors,
)
-from secops.chronicle.rule_alert import update_alert as _update_alert
-from secops.chronicle.rule_detection import list_detections as _list_detections
-from secops.chronicle.rule_detection import list_errors as _list_errors
from secops.chronicle.rule_exclusion import (
RuleExclusionType,
UpdateRuleDeployment,
-)
-from secops.chronicle.rule_exclusion import (
compute_rule_exclusion_activity as _compute_rule_exclusion_activity,
-)
-from secops.chronicle.rule_exclusion import (
create_rule_exclusion as _create_rule_exclusion,
-)
-from secops.chronicle.rule_exclusion import (
get_rule_exclusion as _get_rule_exclusion,
-)
-from secops.chronicle.rule_exclusion import (
get_rule_exclusion_deployment as _get_rule_exclusion_deployment,
-)
-from secops.chronicle.rule_exclusion import (
list_rule_exclusions as _list_rule_exclusions,
-)
-from secops.chronicle.rule_exclusion import (
patch_rule_exclusion as _patch_rule_exclusion,
-)
-from secops.chronicle.rule_exclusion import (
update_rule_exclusion_deployment as _update_rule_exclusion_deployment,
)
from secops.chronicle.rule_retrohunt import (
@@ -270,72 +388,45 @@
list_retrohunts as _list_retrohunts,
)
from secops.chronicle.rule_set import (
- batch_update_curated_rule_set_deployments as _batch_update_curated_rule_set_deployments, # pylint: disable=line-too-long
-)
-from secops.chronicle.rule_set import get_curated_rule as _get_curated_rule
-from secops.chronicle.rule_set import (
+ batch_update_curated_rule_set_deployments as _batch_update_curated_rule_set_deployments,
+ get_curated_rule as _get_curated_rule,
get_curated_rule_by_name as _get_curated_rule_by_name,
-)
-from secops.chronicle.rule_set import (
get_curated_rule_set as _get_curated_rule_set,
-)
-from secops.chronicle.rule_set import (
get_curated_rule_set_category as _get_curated_rule_set_category,
-)
-from secops.chronicle.rule_set import (
get_curated_rule_set_deployment as _get_curated_rule_set_deployment,
-)
-from secops.chronicle.rule_set import (
- get_curated_rule_set_deployment_by_name as _get_curated_rule_set_deployment_by_name, # pylint: disable=line-too-long
-)
-from secops.chronicle.rule_set import (
+ get_curated_rule_set_deployment_by_name as _get_curated_rule_set_deployment_by_name,
list_curated_rule_set_categories as _list_curated_rule_set_categories,
-)
-from secops.chronicle.rule_set import (
list_curated_rule_set_deployments as _list_curated_rule_set_deployments,
-)
-from secops.chronicle.rule_set import (
list_curated_rule_sets as _list_curated_rule_sets,
-)
-from secops.chronicle.rule_set import list_curated_rules as _list_curated_rules
-from secops.chronicle.rule_set import (
+ list_curated_rules as _list_curated_rules,
search_curated_detections as _search_curated_detections,
-)
-from secops.chronicle.rule_set import (
update_curated_rule_set_deployment as _update_curated_rule_set_deployment,
)
-from secops.chronicle.featured_content_rules import (
- list_featured_content_rules as _list_featured_content_rules,
-)
from secops.chronicle.rule_validation import validate_rule as _validate_rule
from secops.chronicle.search import search_udm as _search_udm
from secops.chronicle.log_search import search_raw_logs as _search_raw_logs
from secops.chronicle.stats import get_stats as _get_stats
-from secops.chronicle.udm_mapping import RowLogFormat
from secops.chronicle.udm_mapping import (
+ RowLogFormat,
generate_udm_key_value_mappings as _generate_udm_key_value_mappings,
)
-
-# Import functions from the new modules
from secops.chronicle.udm_search import (
fetch_udm_search_csv as _fetch_udm_search_csv,
-)
-from secops.chronicle.udm_search import (
fetch_udm_search_view as _fetch_udm_search_view,
-)
-from secops.chronicle.udm_search import (
find_udm_field_values as _find_udm_field_values,
)
from secops.chronicle.validate import validate_query as _validate_query
from secops.chronicle.watchlist import (
- list_watchlists as _list_watchlists,
- get_watchlist as _get_watchlist,
- delete_watchlist as _delete_watchlist,
create_watchlist as _create_watchlist,
+ delete_watchlist as _delete_watchlist,
+ get_watchlist as _get_watchlist,
+ list_watchlists as _list_watchlists,
update_watchlist as _update_watchlist,
)
from secops.exceptions import SecOpsError
+# pylint: enable=line-too-long
+
class ValueType(Enum):
"""Chronicle API value types."""
@@ -761,6 +852,5204 @@ def update_watchlist(
update_mask,
)
+ # -------------------------------------------------------------------------
+ # Marketplace Integration methods
+ # -------------------------------------------------------------------------
+
+ def list_marketplace_integrations(
+ self,
+ page_size: int | None = None,
+ page_token: str | None = None,
+ filter_string: str | None = None,
+ order_by: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ as_list: bool = False,
+ ) -> dict[str, Any] | list[dict[str, Any]]:
+ """Get a list of all marketplace integration.
+
+ Args:
+ page_size: Maximum number of integration to return per page
+ page_token: Token for the next page of results, if available
+ filter_string: Filter expression to filter marketplace integration
+ order_by: Field to sort the marketplace integration by
+ api_version: API version to use. Defaults to V1BETA
+ as_list: If True, return a list of integration instead of a dict
+ with integration list and nextPageToken.
+
+ Returns:
+ If as_list is True: List of marketplace integration.
+ If as_list is False: Dict with marketplace integration list and
+ nextPageToken.
+
+ Raises:
+ APIError: If the API request fails
+ """
+ return _list_marketplace_integrations(
+ self,
+ page_size,
+ page_token,
+ filter_string,
+ order_by,
+ api_version,
+ as_list,
+ )
+
+ def get_marketplace_integration(
+ self,
+ integration_name: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Get a specific marketplace integration by integration name.
+
+ Args:
+ integration_name: name of the marketplace integration to retrieve
+ api_version: API version to use. Defaults to V1BETA
+
+ Returns:
+ Marketplace integration details
+
+ Raises:
+ APIError: If the API request fails
+ """
+ return _get_marketplace_integration(self, integration_name, api_version)
+
+ def get_marketplace_integration_diff(
+ self,
+ integration_name: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Get the differences between the currently installed version of
+ an integration and the commercial version available in the
+ marketplace.
+
+ Args:
+ integration_name: name of the marketplace integration
+ api_version: API version to use. Defaults to V1BETA
+
+ Returns:
+ Marketplace integration diff details
+
+ Raises:
+ APIError: If the API request fails
+ """
+ return _get_marketplace_integration_diff(
+ self, integration_name, api_version
+ )
+
+ def install_marketplace_integration(
+ self,
+ integration_name: str,
+ override_mapping: bool | None = None,
+ staging: bool | None = None,
+ version: str | None = None,
+ restore_from_snapshot: bool | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Install a marketplace integration by integration name
+
+ Args:
+ integration_name: Name of the marketplace integration to install
+ override_mapping: Optional. Determines if the integration should
+ override the ontology if already installed, if not provided,
+ set to false by default.
+ staging: Optional. Determines if the integration should be installed
+ as staging or production,
+ if not provided, installed as production.
+ version: Optional. Determines which version of the integration
+ should be installed.
+ restore_from_snapshot: Optional. Determines if the integration
+ should be installed from existing integration snapshot.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Installed marketplace integration details
+
+ Raises:
+ APIError: If the API request fails
+ """
+ return _install_marketplace_integration(
+ self,
+ integration_name,
+ override_mapping,
+ staging,
+ version,
+ restore_from_snapshot,
+ api_version,
+ )
+
+ def uninstall_marketplace_integration(
+ self,
+ integration_name: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Uninstall a marketplace integration by integration name
+
+ Args:
+ integration_name: Name of the marketplace integration to uninstall
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Empty dictionary if uninstallation is successful
+
+ Raises:
+ APIError: If the API request fails
+ """
+ return _uninstall_marketplace_integration(
+ self, integration_name, api_version
+ )
+
+ # -------------------------------------------------------------------------
+ # Integration methods
+ # -------------------------------------------------------------------------
+
+ def list_integrations(
+ self,
+ page_size: int | None = None,
+ page_token: str | None = None,
+ filter_string: str | None = None,
+ order_by: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ as_list: bool = False,
+ ) -> dict[str, Any] | list[dict[str, Any]]:
+ """Get a list of all integrations.
+
+ Args:
+ page_size: Maximum number of integrations to return per page
+ page_token: Token for the next page of results, if available
+ filter_string: Filter expression to filter integrations.
+ Only supports "displayName:" prefix.
+ order_by: Field to sort the integrations by
+ api_version: API version to use. Defaults to V1BETA
+ as_list: If True, return a list of integrations instead of a dict
+ with integration list and nextPageToken.
+
+ Returns:
+ If as_list is True: List of integrations.
+ If as_list is False: Dict with integration list and nextPageToken.
+
+ Raises:
+ APIError: If the API request fails
+ """
+ return _list_integrations(
+ self,
+ page_size,
+ page_token,
+ filter_string,
+ order_by,
+ api_version,
+ as_list,
+ )
+
+ def get_integration(
+ self,
+ integration_name: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Get a specific integration by integration name.
+
+ Args:
+ integration_name: name of the integration to retrieve
+ api_version: API version to use. Defaults to V1BETA
+
+ Returns:
+ Integration details
+
+ Raises:
+ APIError: If the API request fails
+ """
+ return _get_integration(self, integration_name, api_version)
+
+ def delete_integration(
+ self,
+ integration_name: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> None:
+ """Deletes a specific custom integration. Commercial integrations
+ cannot be deleted via this method.
+
+ Args:
+ integration_name: Name of the integration to delete
+ api_version: API version to use for the request.
+ Default is V1BETA.
+
+ Raises:
+ APIError: If the API request fails
+ """
+ _delete_integration(self, integration_name, api_version)
+
+ def create_integration(
+ self,
+ display_name: str,
+ staging: bool,
+ description: str | None = None,
+ image_base64: str | None = None,
+ svg_icon: str | None = None,
+ python_version: PythonVersion | None = None,
+ parameters: list[IntegrationParam | dict[str, Any]] | None = None,
+ categories: list[str] | None = None,
+ integration_type: IntegrationType | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Creates a new custom SOAR integration.
+
+ Args:
+ display_name: Required. The display name of the integration
+ (max 150 characters)
+ staging: Required. True if the integration is in staging mode
+ description: Optional. The integration's description
+ (max 1,500 characters)
+ image_base64: Optional. The integration's image encoded as a
+ base64 string (max 5 MB)
+ svg_icon: Optional. The integration's SVG icon (max 1 MB)
+ python_version: Optional. The integration's Python version
+ parameters: Optional. Integration parameters (max 50).
+ Each entry may be an IntegrationParam dataclass instance
+ or a plain dict with keys: id, defaultValue,
+ displayName, propertyName, type, description, mandatory.
+ categories: Optional. Integration categories (max 50)
+ integration_type: Optional. The integration's type
+ (response/extension)
+ api_version: API version to use for the request.
+ Default is V1BETA.
+
+ Returns:
+ Dict containing the details of the newly created integration
+
+ Raises:
+ APIError: If the API request fails
+ """
+ return _create_integration(
+ self,
+ display_name=display_name,
+ staging=staging,
+ description=description,
+ image_base64=image_base64,
+ svg_icon=svg_icon,
+ python_version=python_version,
+ parameters=parameters,
+ categories=categories,
+ integration_type=integration_type,
+ api_version=api_version,
+ )
+
+ def download_integration(
+ self,
+ integration_name: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> bytes:
+ """Exports the entire integration package as a ZIP file. Includes
+ all scripts, definitions, and the manifest file. Use this method
+ for backup or sharing.
+
+ Args:
+ integration_name: Name of the integration to download
+ api_version: API version to use for the request.
+ Default is V1BETA.
+
+ Returns:
+ Bytes of the ZIP file containing the integration package
+
+ Raises:
+ APIError: If the API request fails
+ """
+ return _download_integration(self, integration_name, api_version)
+
+ def download_integration_dependency(
+ self,
+ integration_name: str,
+ dependency_name: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Initiates the download of a Python dependency (e.g., a library
+ from PyPI) for a custom integration.
+
+ Args:
+ integration_name: Name of the integration whose dependency
+ to download
+ dependency_name: The dependency name to download. It can
+ contain the version or the repository.
+ api_version: API version to use for the request.
+ Default is V1BETA.
+
+ Returns:
+ Empty dict if the download was successful,
+ or a dict containing error
+ details if the download failed
+
+ Raises:
+ APIError: If the API request fails
+ """
+ return _download_integration_dependency(
+ self, integration_name, dependency_name, api_version
+ )
+
+ def export_integration_items(
+ self,
+ integration_name: str,
+ actions: list[str] | str | None = None,
+ jobs: list[str] | str | None = None,
+ connectors: list[str] | str | None = None,
+ managers: list[str] | str | None = None,
+ transformers: list[str] | str | None = None,
+ logical_operators: list[str] | str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> bytes:
+ """Exports specific items from an integration into a ZIP folder.
+ Use this method to extract only a subset of capabilities (e.g.,
+ just the connectors) for reuse.
+
+ Args:
+ integration_name: Name of the integration to export items from
+ actions: Optional. IDs of the actions to export as a list or
+ comma-separated string. Format: [1,2,3] or "1,2,3"
+ jobs: Optional. IDs of the jobs to export as a list or
+ comma-separated string.
+ connectors: Optional. IDs of the connectors to export as a
+ list or comma-separated string.
+ managers: Optional. IDs of the managers to export as a list
+ or comma-separated string.
+ transformers: Optional. IDs of the transformers to export as
+ a list or comma-separated string.
+ logical_operators: Optional. IDs of the logical operators to
+ export as a list or comma-separated string.
+ api_version: API version to use for the request.
+ Default is V1BETA.
+
+ Returns:
+ Bytes of the ZIP file containing the exported items
+
+ Raises:
+ APIError: If the API request fails
+ """
+ return _export_integration_items(
+ self,
+ integration_name,
+ actions=actions,
+ jobs=jobs,
+ connectors=connectors,
+ managers=managers,
+ transformers=transformers,
+ logical_operators=logical_operators,
+ api_version=api_version,
+ )
+
+ def get_integration_affected_items(
+ self,
+ integration_name: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Identifies all system items (e.g., connector instances, job
+ instances, playbooks) that would be affected by a change to or
+ deletion of this integration. Use this method to conduct impact
+ analysis before making breaking changes.
+
+ Args:
+ integration_name: Name of the integration to check for
+ affected items
+ api_version: API version to use for the request.
+ Default is V1BETA.
+
+ Returns:
+ Dict containing the list of items affected by changes to
+ the specified integration
+
+ Raises:
+ APIError: If the API request fails
+ """
+ return _get_integration_affected_items(
+ self, integration_name, api_version
+ )
+
+ def get_agent_integrations(
+ self,
+ agent_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Returns the set of integrations currently installed and
+ configured on a specific agent.
+
+ Args:
+ agent_id: The agent identifier
+ api_version: API version to use for the request.
+ Default is V1BETA.
+
+ Returns:
+ Dict containing the list of agent-based integrations
+
+ Raises:
+ APIError: If the API request fails
+ """
+ return _get_agent_integrations(self, agent_id, api_version)
+
+ def get_integration_dependencies(
+ self,
+ integration_name: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Returns the complete list of Python dependencies currently
+ associated with a custom integration.
+
+ Args:
+ integration_name: Name of the integration to check for
+ dependencies
+ api_version: API version to use for the request.
+ Default is V1BETA.
+
+ Returns:
+ Dict containing the list of dependencies for the specified
+ integration
+
+ Raises:
+ APIError: If the API request fails
+ """
+ return _get_integration_dependencies(
+ self, integration_name, api_version
+ )
+
+ def get_integration_diff(
+ self,
+ integration_name: str,
+ diff_type: DiffType | None = DiffType.COMMERCIAL,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Get the configuration diff of a specific integration.
+
+ Args:
+ integration_name: ID of the integration to retrieve the diff for
+ diff_type: Type of diff to retrieve (Commercial, Production, or
+ Staging). Default is Commercial.
+ COMMERCIAL: Diff between the commercial version of the
+ integration and the current version in the environment.
+ PRODUCTION: Returns the difference between the staging
+ integration and its matching production version.
+ STAGING: Returns the difference between the production
+ integration and its corresponding staging version.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the configuration diff of the specified integration
+
+ Raises:
+ APIError: If the API request fails
+ """
+ return _get_integration_diff(
+ self, integration_name, diff_type, api_version
+ )
+
+ def get_integration_restricted_agents(
+ self,
+ integration_name: str,
+ required_python_version: PythonVersion,
+ push_request: bool = False,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Identifies remote agents that would be restricted from running
+ an updated version of the integration, typically due to environment
+ incompatibilities like unsupported Python versions.
+
+ Args:
+ integration_name: Name of the integration to check for
+ restricted agents
+ required_python_version: Python version required for the
+ updated integration
+ push_request: Optional. Indicates whether the integration is
+ being pushed to a different mode (production/staging).
+ False by default.
+ api_version: API version to use for the request.
+ Default is V1BETA.
+
+ Returns:
+ Dict containing the list of agents that would be restricted
+ from running the updated integration
+
+ Raises:
+ APIError: If the API request fails
+ """
+ return _get_integration_restricted_agents(
+ self,
+ integration_name,
+ required_python_version=required_python_version,
+ push_request=push_request,
+ api_version=api_version,
+ )
+
+ def transition_integration(
+ self,
+ integration_name: str,
+ target_mode: TargetMode,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Transitions an integration to a different environment
+ (e.g. staging to production).
+
+ Args:
+ integration_name: Name of the integration to transition
+ target_mode: Target mode to transition the integration to.
+ PRODUCTION: Transition the integration to production.
+ STAGING: Transition the integration to staging.
+ api_version: API version to use for the request.
+ Default is V1BETA.
+
+ Returns:
+ Dict containing the details of the transitioned integration
+
+ Raises:
+ APIError: If the API request fails
+ """
+ return _transition_integration(
+ self, integration_name, target_mode, api_version
+ )
+
+ def update_integration(
+ self,
+ integration_name: str,
+ display_name: str | None = None,
+ description: str | None = None,
+ image_base64: str | None = None,
+ svg_icon: str | None = None,
+ python_version: PythonVersion | None = None,
+ parameters: list[dict[str, Any]] | None = None,
+ categories: list[str] | None = None,
+ integration_type: IntegrationType | None = None,
+ staging: bool | None = None,
+ dependencies_to_remove: list[str] | None = None,
+ update_mask: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Updates an existing integration's metadata. Use this method to
+ change the description or display image of a custom integration.
+
+ Args:
+ integration_name: Name of the integration to update
+ display_name: Optional. The display name of the integration
+ (max 150 characters)
+ description: Optional. The integration's description
+ (max 1,500 characters)
+ image_base64: Optional. The integration's image encoded as a
+ base64 string (max 5 MB)
+ svg_icon: Optional. The integration's SVG icon (max 1 MB)
+ python_version: Optional. The integration's Python version
+ parameters: Optional. Integration parameters (max 50)
+ categories: Optional. Integration categories (max 50)
+ integration_type: Optional. The integration's type
+ (response/extension)
+ staging: Optional. True if the integration is in staging mode
+ dependencies_to_remove: Optional. List of dependencies to
+ remove from the integration
+ update_mask: Optional. Comma-separated list of fields to
+ update. If not provided, all non-None fields are updated.
+ api_version: API version to use for the request.
+ Default is V1BETA.
+
+ Returns:
+ Dict containing the details of the updated integration
+
+ Raises:
+ APIError: If the API request fails
+ """
+ return _update_integration(
+ self,
+ integration_name,
+ display_name=display_name,
+ description=description,
+ image_base64=image_base64,
+ svg_icon=svg_icon,
+ python_version=python_version,
+ parameters=parameters,
+ categories=categories,
+ integration_type=integration_type,
+ staging=staging,
+ dependencies_to_remove=dependencies_to_remove,
+ update_mask=update_mask,
+ api_version=api_version,
+ )
+
+ def update_custom_integration(
+ self,
+ integration_name: str,
+ display_name: str | None = None,
+ description: str | None = None,
+ image_base64: str | None = None,
+ svg_icon: str | None = None,
+ python_version: PythonVersion | None = None,
+ parameters: list[dict[str, Any]] | None = None,
+ categories: list[str] | None = None,
+ integration_type: IntegrationType | None = None,
+ staging: bool | None = None,
+ dependencies_to_remove: list[str] | None = None,
+ update_mask: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Updates a custom integration definition, including its
+ parameters and dependencies. Use this method to refine the
+ operational behavior of a locally developed integration.
+
+ Args:
+ integration_name: Name of the integration to update
+ display_name: Optional. The display name of the integration
+ (max 150 characters)
+ description: Optional. The integration's description
+ (max 1,500 characters)
+ image_base64: Optional. The integration's image encoded as a
+ base64 string (max 5 MB)
+ svg_icon: Optional. The integration's SVG icon (max 1 MB)
+ python_version: Optional. The integration's Python version
+ parameters: Optional. Integration parameters (max 50)
+ categories: Optional. Integration categories (max 50)
+ integration_type: Optional. The integration's type
+ (response/extension)
+ staging: Optional. True if the integration is in staging mode
+ dependencies_to_remove: Optional. List of dependencies to
+ remove from the integration
+ update_mask: Optional. Comma-separated list of fields to
+ update. If not provided, all non-None fields are updated.
+ api_version: API version to use for the request.
+ Default is V1BETA.
+
+ Returns:
+ Dict containing:
+ - successful: Whether the integration was updated
+ successfully
+ - integration: The updated integration (if successful)
+ - dependencies: Dependency installation statuses
+ (if failed)
+
+ Raises:
+ APIError: If the API request fails
+ """
+ return _update_custom_integration(
+ self,
+ integration_name,
+ display_name=display_name,
+ description=description,
+ image_base64=image_base64,
+ svg_icon=svg_icon,
+ python_version=python_version,
+ parameters=parameters,
+ categories=categories,
+ integration_type=integration_type,
+ staging=staging,
+ dependencies_to_remove=dependencies_to_remove,
+ update_mask=update_mask,
+ api_version=api_version,
+ )
+
+ # -------------------------------------------------------------------------
+ # Integration Action methods
+ # -------------------------------------------------------------------------
+
+ def list_integration_actions(
+ self,
+ integration_name: str,
+ page_size: int | None = None,
+ page_token: str | None = None,
+ filter_string: str | None = None,
+ order_by: str | None = None,
+ expand: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ as_list: bool = False,
+ ) -> dict[str, Any] | list[dict[str, Any]]:
+ """Get a list of actions for a given integration.
+
+ Args:
+ integration_name: Name of the integration to get actions for
+ page_size: Number of results to return per page
+ page_token: Token for the page to retrieve
+ filter_string: Filter expression to filter actions
+ order_by: Field to sort the actions by
+ expand: Comma-separated list of fields to expand in the response
+ api_version: API version to use for the request. Default is V1BETA.
+ as_list: If True, return a list of actions instead of a dict with
+ actions list and nextPageToken.
+
+ Returns:
+ If as_list is True: List of actions.
+ If as_list is False: Dict with actions list and nextPageToken.
+
+ Raises:
+ APIError: If the API request fails
+ """
+ return _list_integration_actions(
+ self,
+ integration_name,
+ page_size=page_size,
+ page_token=page_token,
+ filter_string=filter_string,
+ order_by=order_by,
+ expand=expand,
+ api_version=api_version,
+ as_list=as_list,
+ )
+
+ def get_integration_action(
+ self,
+ integration_name: str,
+ action_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Get details of a specific action for a given integration.
+
+ Args:
+ integration_name: Name of the integration the action belongs to
+ action_id: ID of the action to retrieve
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing details of the specified action.
+
+ Raises:
+ APIError: If the API request fails
+ """
+ return _get_integration_action(
+ self,
+ integration_name,
+ action_id,
+ api_version=api_version,
+ )
+
+ def delete_integration_action(
+ self,
+ integration_name: str,
+ action_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> None:
+ """Delete a specific action from a given integration.
+
+ Args:
+ integration_name: Name of the integration the action belongs to
+ action_id: ID of the action to delete
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ None
+
+ Raises:
+ APIError: If the API request fails
+ """
+ return _delete_integration_action(
+ self,
+ integration_name,
+ action_id,
+ api_version=api_version,
+ )
+
+ def create_integration_action(
+ self,
+ integration_name: str,
+ display_name: str,
+ script: str,
+ timeout_seconds: int,
+ enabled: bool,
+ script_result_name: str,
+ is_async: bool,
+ description: str | None = None,
+ default_result_value: str | None = None,
+ async_polling_interval_seconds: int | None = None,
+ async_total_timeout_seconds: int | None = None,
+ dynamic_results: list[dict[str, Any]] | None = None,
+ parameters: list[dict[str, Any]] | None = None,
+ ai_generated: bool | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Create a new custom action for a given integration.
+
+ Args:
+ integration_name: Name of the integration to
+ create the action for.
+ display_name: Action's display name.
+ Maximum 150 characters. Required.
+ script: Action's Python script. Maximum size 5MB. Required.
+ timeout_seconds: Action timeout in seconds. Maximum 1200. Required.
+ enabled: Whether the action is enabled or disabled. Required.
+ script_result_name: Field name that holds the script result.
+ Maximum 100 characters. Required.
+ is_async: Whether the action is asynchronous. Required.
+ description: Action's description. Maximum 400 characters. Optional.
+ default_result_value: Action's default result value.
+ Maximum 1000 characters. Optional.
+ async_polling_interval_seconds: Polling interval
+ in seconds for async actions.
+ Cannot exceed total timeout. Optional.
+ async_total_timeout_seconds: Total async timeout in seconds. Maximum
+ 1209600 (14 days). Optional.
+ dynamic_results: List of dynamic result metadata dicts.
+ Max 50. Optional.
+ parameters: List of action parameter dicts. Max 50. Optional.
+ ai_generated: Whether the action was generated by AI. Optional.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the newly created IntegrationAction resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _create_integration_action(
+ self,
+ integration_name,
+ display_name,
+ script,
+ timeout_seconds,
+ enabled,
+ script_result_name,
+ is_async,
+ description=description,
+ default_result_value=default_result_value,
+ async_polling_interval_seconds=async_polling_interval_seconds,
+ async_total_timeout_seconds=async_total_timeout_seconds,
+ dynamic_results=dynamic_results,
+ parameters=parameters,
+ ai_generated=ai_generated,
+ api_version=api_version,
+ )
+
+ def update_integration_action(
+ self,
+ integration_name: str,
+ action_id: str,
+ display_name: str | None = None,
+ script: str | None = None,
+ timeout_seconds: int | None = None,
+ enabled: bool | None = None,
+ script_result_name: str | None = None,
+ is_async: bool | None = None,
+ description: str | None = None,
+ default_result_value: str | None = None,
+ async_polling_interval_seconds: int | None = None,
+ async_total_timeout_seconds: int | None = None,
+ dynamic_results: list[dict[str, Any]] | None = None,
+ parameters: list[dict[str, Any]] | None = None,
+ ai_generated: bool | None = None,
+ update_mask: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Update an existing custom action for a given integration.
+
+ Only custom actions can be updated; predefined commercial actions are
+ immutable.
+
+ Args:
+ integration_name: Name of the integration the action belongs to.
+ action_id: ID of the action to update.
+ display_name: Action's display name. Maximum 150 characters.
+ script: Action's Python script. Maximum size 5MB.
+ timeout_seconds: Action timeout in seconds. Maximum 1200.
+ enabled: Whether the action is enabled or disabled.
+ script_result_name: Field name that holds the script result.
+ Maximum 100 characters.
+ is_async: Whether the action is asynchronous.
+ description: Action's description. Maximum 400 characters.
+ default_result_value: Action's default result value.
+ Maximum 1000 characters.
+ async_polling_interval_seconds: Polling interval
+ in seconds for async actions. Cannot exceed total timeout.
+ async_total_timeout_seconds: Total async timeout in seconds. Maximum
+ 1209600 (14 days).
+ dynamic_results: List of dynamic result metadata dicts. Max 50.
+ parameters: List of action parameter dicts. Max 50.
+ ai_generated: Whether the action was generated by AI.
+ update_mask: Comma-separated list of fields to update. If omitted,
+ the mask is auto-generated from whichever fields are provided.
+ Example: "displayName,script".
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the updated IntegrationAction resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _update_integration_action(
+ self,
+ integration_name,
+ action_id,
+ display_name=display_name,
+ script=script,
+ timeout_seconds=timeout_seconds,
+ enabled=enabled,
+ script_result_name=script_result_name,
+ is_async=is_async,
+ description=description,
+ default_result_value=default_result_value,
+ async_polling_interval_seconds=async_polling_interval_seconds,
+ async_total_timeout_seconds=async_total_timeout_seconds,
+ dynamic_results=dynamic_results,
+ parameters=parameters,
+ ai_generated=ai_generated,
+ update_mask=update_mask,
+ api_version=api_version,
+ )
+
+ def execute_integration_action_test(
+ self,
+ integration_name: str,
+ test_case_id: int,
+ action: dict[str, Any],
+ scope: str,
+ integration_instance_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Execute a test run of an integration action's script.
+
+ Use this method to verify custom action logic, connectivity, and data
+ parsing against a specified integration instance and test case before
+ making the action available in playbooks.
+
+ Args:
+ integration_name: Name of the integration the action belongs to.
+ test_case_id: ID of the action test case.
+ action: Dict containing the IntegrationAction to test.
+ scope: The action test scope.
+ integration_instance_id: The integration instance ID to use.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict with the test execution results with the following fields:
+ - output: The script output.
+ - debugOutput: The script debug output.
+ - resultJson: The result JSON if it exists (optional).
+ - resultName: The script result name (optional).
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _execute_integration_action_test(
+ self,
+ integration_name,
+ test_case_id,
+ action,
+ scope,
+ integration_instance_id,
+ api_version=api_version,
+ )
+
+ def get_integration_actions_by_environment(
+ self,
+ integration_name: str,
+ environments: list[str],
+ include_widgets: bool,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """List actions executable within specified environments.
+
+ Use this method to discover which automated tasks have active
+ integration instances configured for a particular
+ network or organizational context.
+
+ Args:
+ integration_name: Name of the integration to fetch actions for.
+ environments: List of environments to filter actions by.
+ include_widgets: Whether to include widget actions in the response.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing a list of IntegrationAction objects that have
+ integration instances in one of the given environments.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _get_integration_actions_by_environment(
+ self,
+ integration_name,
+ environments,
+ include_widgets,
+ api_version=api_version,
+ )
+
+ def get_integration_action_template(
+ self,
+ integration_name: str,
+ is_async: bool = False,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Retrieve a default Python script template for a new
+ integration action.
+
+ Use this method to jumpstart the development of a custom automated task
+ by providing boilerplate code for either synchronous or asynchronous
+ operations.
+
+ Args:
+ integration_name: Name of the integration to fetch the template for.
+ is_async: Whether to fetch a template for an async action. Default
+ is False.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the IntegrationAction template.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _get_integration_action_template(
+ self,
+ integration_name,
+ is_async=is_async,
+ api_version=api_version,
+ )
+
+ # -------------------------------------------------------------------------
+ # Integration Action Revisions methods
+ # -------------------------------------------------------------------------
+
+ def list_integration_action_revisions(
+ self,
+ integration_name: str,
+ action_id: str,
+ page_size: int | None = None,
+ page_token: str | None = None,
+ filter_string: str | None = None,
+ order_by: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ as_list: bool = False,
+ ) -> dict[str, Any] | list[dict[str, Any]]:
+ """List all revisions for a specific integration action.
+
+ Use this method to view the history of changes to an action,
+ enabling version control and the ability to rollback to
+ previous configurations.
+
+ Args:
+ integration_name: Name of the integration the action
+ belongs to.
+ action_id: ID of the action to list revisions for.
+ page_size: Maximum number of revisions to return.
+ page_token: Page token from a previous call to retrieve the
+ next page.
+ filter_string: Filter expression to filter revisions.
+ order_by: Field to sort the revisions by.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+ as_list: If True, return a list of revisions instead of a
+ dict with revisions list and nextPageToken.
+
+ Returns:
+ If as_list is True: List of action revisions.
+ If as_list is False: Dict with action revisions list and
+ nextPageToken.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _list_integration_action_revisions(
+ self,
+ integration_name,
+ action_id,
+ page_size=page_size,
+ page_token=page_token,
+ filter_string=filter_string,
+ order_by=order_by,
+ api_version=api_version,
+ as_list=as_list,
+ )
+
+ def delete_integration_action_revision(
+ self,
+ integration_name: str,
+ action_id: str,
+ revision_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> None:
+ """Delete a specific action revision.
+
+ Use this method to permanently remove a revision from the
+ action's history.
+
+ Args:
+ integration_name: Name of the integration the action
+ belongs to.
+ action_id: ID of the action the revision belongs to.
+ revision_id: ID of the revision to delete.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ None
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _delete_integration_action_revision(
+ self,
+ integration_name,
+ action_id,
+ revision_id,
+ api_version=api_version,
+ )
+
+ def create_integration_action_revision(
+ self,
+ integration_name: str,
+ action_id: str,
+ action: dict[str, Any],
+ comment: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Create a new revision for an integration action.
+
+ Use this method to save a snapshot of the current action
+ configuration before making changes, enabling easy rollback if
+ needed.
+
+ Args:
+ integration_name: Name of the integration the action
+ belongs to.
+ action_id: ID of the action to create a revision for.
+ action: The action object to save as a revision.
+ comment: Optional comment describing the revision.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ Dict containing the newly created ActionRevision resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _create_integration_action_revision(
+ self,
+ integration_name,
+ action_id,
+ action,
+ comment=comment,
+ api_version=api_version,
+ )
+
+ def rollback_integration_action_revision(
+ self,
+ integration_name: str,
+ action_id: str,
+ revision_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Rollback an integration action to a previous revision.
+
+ Use this method to restore an action to a previously saved
+ state, reverting any changes made since that revision.
+
+ Args:
+ integration_name: Name of the integration the action
+ belongs to.
+ action_id: ID of the action to rollback.
+ revision_id: ID of the revision to rollback to.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ Dict containing the rolled back IntegrationAction resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _rollback_integration_action_revision(
+ self,
+ integration_name,
+ action_id,
+ revision_id,
+ api_version=api_version,
+ )
+
+ # -------------------------------------------------------------------------
+ # Integration Connector methods
+ # -------------------------------------------------------------------------
+
+ def list_integration_connectors(
+ self,
+ integration_name: str,
+ page_size: int | None = None,
+ page_token: str | None = None,
+ filter_string: str | None = None,
+ order_by: str | None = None,
+ exclude_staging: bool | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ as_list: bool = False,
+ ) -> dict[str, Any] | list[dict[str, Any]]:
+ """List all connectors defined for a specific integration.
+
+ Args:
+ integration_name: Name of the integration to list connectors
+ for.
+ page_size: Maximum number of connectors to return. Defaults
+ to 50, maximum is 1000.
+ page_token: Page token from a previous call to retrieve the
+ next page.
+ filter_string: Filter expression to filter connectors.
+ order_by: Field to sort the connectors by.
+ exclude_staging: Whether to exclude staging connectors from
+ the response. By default, staging connectors are included.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+ as_list: If True, return a list of connectors instead of a
+ dict with connectors list and nextPageToken.
+
+ Returns:
+ If as_list is True: List of connectors.
+ If as_list is False: Dict with connectors list and
+ nextPageToken.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _list_integration_connectors(
+ self,
+ integration_name,
+ page_size=page_size,
+ page_token=page_token,
+ filter_string=filter_string,
+ order_by=order_by,
+ exclude_staging=exclude_staging,
+ api_version=api_version,
+ as_list=as_list,
+ )
+
+ def get_integration_connector(
+ self,
+ integration_name: str,
+ connector_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Get a single connector for a given integration.
+
+ Use this method to retrieve the Python script, configuration parameters,
+ and field mapping logic for a specific connector.
+
+ Args:
+ integration_name: Name of the integration the connector belongs to.
+ connector_id: ID of the connector to retrieve.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing details of the specified IntegrationConnector.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _get_integration_connector(
+ self,
+ integration_name,
+ connector_id,
+ api_version=api_version,
+ )
+
+ def delete_integration_connector(
+ self,
+ integration_name: str,
+ connector_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> None:
+ """Delete a specific custom connector from a given integration.
+
+ Only custom connectors can be deleted; commercial connectors are
+ immutable.
+
+ Args:
+ integration_name: Name of the integration the connector belongs to.
+ connector_id: ID of the connector to delete.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ None
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _delete_integration_connector(
+ self,
+ integration_name,
+ connector_id,
+ api_version=api_version,
+ )
+
+ def create_integration_connector(
+ self,
+ integration_name: str,
+ display_name: str,
+ script: str,
+ timeout_seconds: int,
+ enabled: bool,
+ product_field_name: str,
+ event_field_name: str,
+ description: str | None = None,
+ parameters: list[dict[str, Any] | ConnectorParameter] | None = None,
+ rules: list[dict[str, Any] | ConnectorRule] | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Create a new custom connector for a given integration.
+
+ Use this method to define how to fetch and parse alerts from a
+ unique or unofficial data source. Each connector must have a
+ unique display name and a functional Python script.
+
+ Args:
+ integration_name: Name of the integration to create the
+ connector for.
+ display_name: Connector's display name. Required.
+ script: Connector's Python script. Required.
+ timeout_seconds: Timeout in seconds for a single script run.
+ Required.
+ enabled: Whether the connector is enabled or disabled.
+ Required.
+ product_field_name: Field name used to determine the device
+ product. Required.
+ event_field_name: Field name used to determine the event
+ name (sub-type). Required.
+ description: Connector's description. Optional.
+ parameters: List of ConnectorParameter instances or dicts.
+ Optional.
+ rules: List of ConnectorRule instances or dicts. Optional.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ Dict containing the newly created IntegrationConnector
+ resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _create_integration_connector(
+ self,
+ integration_name,
+ display_name,
+ script,
+ timeout_seconds,
+ enabled,
+ product_field_name,
+ event_field_name,
+ description=description,
+ parameters=parameters,
+ rules=rules,
+ api_version=api_version,
+ )
+
+ def update_integration_connector(
+ self,
+ integration_name: str,
+ connector_id: str,
+ display_name: str | None = None,
+ script: str | None = None,
+ timeout_seconds: int | None = None,
+ enabled: bool | None = None,
+ product_field_name: str | None = None,
+ event_field_name: str | None = None,
+ description: str | None = None,
+ parameters: list[dict[str, Any] | ConnectorParameter] | None = None,
+ rules: list[dict[str, Any] | ConnectorRule] | None = None,
+ update_mask: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Update an existing custom connector for a given integration.
+
+ Only custom connectors can be updated; commercial connectors are
+ immutable.
+
+ Args:
+ integration_name: Name of the integration the connector belongs to.
+ connector_id: ID of the connector to update.
+ display_name: Connector's display name.
+ script: Connector's Python script.
+ timeout_seconds: Timeout in seconds for a single script run.
+ enabled: Whether the connector is enabled or disabled.
+ product_field_name: Field name used to determine the device product.
+ event_field_name: Field name used to determine the event name
+ (sub-type).
+ description: Connector's description.
+ parameters: List of ConnectorParameter instances or dicts.
+ rules: List of ConnectorRule instances or dicts.
+ update_mask: Comma-separated list of fields to update. If omitted,
+ the mask is auto-generated from whichever fields are provided.
+ Example: "displayName,script".
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the updated IntegrationConnector resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _update_integration_connector(
+ self,
+ integration_name,
+ connector_id,
+ display_name=display_name,
+ script=script,
+ timeout_seconds=timeout_seconds,
+ enabled=enabled,
+ product_field_name=product_field_name,
+ event_field_name=event_field_name,
+ description=description,
+ parameters=parameters,
+ rules=rules,
+ update_mask=update_mask,
+ api_version=api_version,
+ )
+
+ def execute_integration_connector_test(
+ self,
+ integration_name: str,
+ connector: dict[str, Any],
+ agent_identifier: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Execute a test run of a connector's Python script.
+
+ Use this method to verify data fetching logic, authentication,
+ and parsing logic before enabling the connector for production
+ ingestion. The full connector object is required as the test
+ can be run without saving the connector first.
+
+ Args:
+ integration_name: Name of the integration the connector
+ belongs to.
+ connector: Dict containing the IntegrationConnector to test.
+ agent_identifier: Agent identifier for remote testing.
+ Optional.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ Dict containing the test execution results with the
+ following fields:
+ - outputMessage: Human-readable output message set by
+ the script.
+ - debugOutputMessage: The script debug output.
+ - resultJson: The result JSON if it exists (optional).
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _execute_integration_connector_test(
+ self,
+ integration_name,
+ connector,
+ agent_identifier=agent_identifier,
+ api_version=api_version,
+ )
+
+ def get_integration_connector_template(
+ self,
+ integration_name: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Retrieve a default Python script template for a new
+ integration connector.
+
+ Use this method to rapidly initialize the development of a new
+ connector.
+
+ Args:
+ integration_name: Name of the integration to fetch the
+ template for.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ Dict containing the IntegrationConnector template.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _get_integration_connector_template(
+ self,
+ integration_name,
+ api_version=api_version,
+ )
+
+ # -------------------------------------------------------------------------
+ # Integration Connector Revisions methods
+ # -------------------------------------------------------------------------
+
+ def list_integration_connector_revisions(
+ self,
+ integration_name: str,
+ connector_id: str,
+ page_size: int | None = None,
+ page_token: str | None = None,
+ filter_string: str | None = None,
+ order_by: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ as_list: bool = False,
+ ) -> dict[str, Any] | list[dict[str, Any]]:
+ """List all revisions for a specific integration connector.
+
+ Use this method to browse the version history and identify
+ potential rollback targets.
+
+ Args:
+ integration_name: Name of the integration the connector
+ belongs to.
+ connector_id: ID of the connector to list revisions for.
+ page_size: Maximum number of revisions to return.
+ page_token: Page token from a previous call to retrieve the
+ next page.
+ filter_string: Filter expression to filter revisions.
+ order_by: Field to sort the revisions by.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+ as_list: If True, return a list of revisions instead of a
+ dict with revisions list and nextPageToken.
+
+ Returns:
+ If as_list is True: List of revisions.
+ If as_list is False: Dict with revisions list and
+ nextPageToken.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _list_integration_connector_revisions(
+ self,
+ integration_name,
+ connector_id,
+ page_size=page_size,
+ page_token=page_token,
+ filter_string=filter_string,
+ order_by=order_by,
+ api_version=api_version,
+ as_list=as_list,
+ )
+
+ def delete_integration_connector_revision(
+ self,
+ integration_name: str,
+ connector_id: str,
+ revision_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> None:
+ """Delete a specific revision for a given integration
+ connector.
+
+ Use this method to clean up old or incorrect snapshots from the
+ version history.
+
+ Args:
+ integration_name: Name of the integration the connector
+ belongs to.
+ connector_id: ID of the connector the revision belongs to.
+ revision_id: ID of the revision to delete.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ None
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _delete_integration_connector_revision(
+ self,
+ integration_name,
+ connector_id,
+ revision_id,
+ api_version=api_version,
+ )
+
+ def create_integration_connector_revision(
+ self,
+ integration_name: str,
+ connector_id: str,
+ connector: dict[str, Any],
+ comment: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Create a new revision snapshot of the current integration
+ connector.
+
+ Use this method to save a stable configuration before making
+ experimental changes. Only custom connectors can be versioned.
+
+ Args:
+ integration_name: Name of the integration the connector
+ belongs to.
+ connector_id: ID of the connector to create a revision for.
+ connector: Dict containing the IntegrationConnector to
+ snapshot.
+ comment: Comment describing the revision. Maximum 400
+ characters. Optional.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ Dict containing the newly created ConnectorRevision
+ resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _create_integration_connector_revision(
+ self,
+ integration_name,
+ connector_id,
+ connector,
+ comment=comment,
+ api_version=api_version,
+ )
+
+ def rollback_integration_connector_revision(
+ self,
+ integration_name: str,
+ connector_id: str,
+ revision_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Revert the current connector definition to a previously
+ saved revision.
+
+ Use this method to quickly revert to a known good configuration
+ if an investigation or update is unsuccessful.
+
+ Args:
+ integration_name: Name of the integration the connector
+ belongs to.
+ connector_id: ID of the connector to rollback.
+ revision_id: ID of the revision to rollback to.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ Dict containing the ConnectorRevision rolled back to.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _rollback_integration_connector_revision(
+ self,
+ integration_name,
+ connector_id,
+ revision_id,
+ api_version=api_version,
+ )
+
+ # -------------------------------------------------------------------------
+ # Connector Context Properties methods
+ # -------------------------------------------------------------------------
+
+ def list_connector_context_properties(
+ self,
+ integration_name: str,
+ connector_id: str,
+ page_size: int | None = None,
+ page_token: str | None = None,
+ filter_string: str | None = None,
+ order_by: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ as_list: bool = False,
+ ) -> dict[str, Any] | list[dict[str, Any]]:
+ """List all context properties for a specific integration
+ connector.
+
+ Use this method to discover all custom data points associated
+ with a connector.
+
+ Args:
+ integration_name: Name of the integration the connector
+ belongs to.
+ connector_id: ID of the connector to list context
+ properties for.
+ page_size: Maximum number of context properties to return.
+ page_token: Page token from a previous call to retrieve the
+ next page.
+ filter_string: Filter expression to filter context
+ properties.
+ order_by: Field to sort the context properties by.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+ as_list: If True, return a list of context properties
+ instead of a dict with context properties list and
+ nextPageToken.
+
+ Returns:
+ If as_list is True: List of context properties.
+ If as_list is False: Dict with context properties list and
+ nextPageToken.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _list_connector_context_properties(
+ self,
+ integration_name,
+ connector_id,
+ page_size=page_size,
+ page_token=page_token,
+ filter_string=filter_string,
+ order_by=order_by,
+ api_version=api_version,
+ as_list=as_list,
+ )
+
+ def get_connector_context_property(
+ self,
+ integration_name: str,
+ connector_id: str,
+ context_property_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Get a single context property for a specific integration
+ connector.
+
+ Use this method to retrieve the value of a specific key within
+ a connector's context.
+
+ Args:
+ integration_name: Name of the integration the connector
+ belongs to.
+ connector_id: ID of the connector the context property
+ belongs to.
+ context_property_id: ID of the context property to
+ retrieve.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ Dict containing details of the specified ContextProperty.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _get_connector_context_property(
+ self,
+ integration_name,
+ connector_id,
+ context_property_id,
+ api_version=api_version,
+ )
+
+ def delete_connector_context_property(
+ self,
+ integration_name: str,
+ connector_id: str,
+ context_property_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> None:
+ """Delete a specific context property for a given integration
+ connector.
+
+ Use this method to remove a custom data point that is no longer
+ relevant to the connector's context.
+
+ Args:
+ integration_name: Name of the integration the connector
+ belongs to.
+ connector_id: ID of the connector the context property
+ belongs to.
+ context_property_id: ID of the context property to delete.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ None
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _delete_connector_context_property(
+ self,
+ integration_name,
+ connector_id,
+ context_property_id,
+ api_version=api_version,
+ )
+
+ def create_connector_context_property(
+ self,
+ integration_name: str,
+ connector_id: str,
+ value: str,
+ key: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Create a new context property for a specific integration
+ connector.
+
+ Use this method to attach custom data to a connector's context.
+ Property keys must be unique within their context. Key values
+ must be 4-63 characters and match /[a-z][0-9]-/.
+
+ Args:
+ integration_name: Name of the integration the connector
+ belongs to.
+ connector_id: ID of the connector to create the context
+ property for.
+ value: The property value. Required.
+ key: The context property ID to use. Must be 4-63
+ characters and match /[a-z][0-9]-/. Optional.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ Dict containing the newly created ContextProperty resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _create_connector_context_property(
+ self,
+ integration_name,
+ connector_id,
+ value,
+ key=key,
+ api_version=api_version,
+ )
+
+ def update_connector_context_property(
+ self,
+ integration_name: str,
+ connector_id: str,
+ context_property_id: str,
+ value: str,
+ update_mask: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Update an existing context property for a given integration
+ connector.
+
+ Use this method to modify the value of a previously saved key.
+
+ Args:
+ integration_name: Name of the integration the connector
+ belongs to.
+ connector_id: ID of the connector the context property
+ belongs to.
+ context_property_id: ID of the context property to update.
+ value: The new property value. Required.
+ update_mask: Comma-separated list of fields to update. Only
+ "value" is supported. If omitted, defaults to "value".
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ Dict containing the updated ContextProperty resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _update_connector_context_property(
+ self,
+ integration_name,
+ connector_id,
+ context_property_id,
+ value,
+ update_mask=update_mask,
+ api_version=api_version,
+ )
+
+ def delete_all_connector_context_properties(
+ self,
+ integration_name: str,
+ connector_id: str,
+ context_id: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> None:
+ """Delete all context properties for a specific integration
+ connector.
+
+ Use this method to quickly clear all supplemental data from a
+ connector's context.
+
+ Args:
+ integration_name: Name of the integration the connector
+ belongs to.
+ connector_id: ID of the connector to clear context
+ properties from.
+ context_id: The context ID to remove context properties
+ from. Must be 4-63 characters and match /[a-z][0-9]-/.
+ Optional.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ None
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _delete_all_connector_context_properties(
+ self,
+ integration_name,
+ connector_id,
+ context_id=context_id,
+ api_version=api_version,
+ )
+
+ # -------------------------------------------------------------------------
+ # Connector Instance Logs methods
+ # -------------------------------------------------------------------------
+
+ def list_connector_instance_logs(
+ self,
+ integration_name: str,
+ connector_id: str,
+ connector_instance_id: str,
+ page_size: int | None = None,
+ page_token: str | None = None,
+ filter_string: str | None = None,
+ order_by: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ as_list: bool = False,
+ ) -> dict[str, Any] | list[dict[str, Any]]:
+ """List all logs for a specific connector instance.
+
+ Use this method to browse the execution history and diagnostic
+ output of a connector. Supports filtering and pagination to
+ efficiently navigate large volumes of log data.
+
+ Args:
+ integration_name: Name of the integration the connector
+ belongs to.
+ connector_id: ID of the connector the instance belongs to.
+ connector_instance_id: ID of the connector instance to list
+ logs for.
+ page_size: Maximum number of logs to return.
+ page_token: Page token from a previous call to retrieve the
+ next page.
+ filter_string: Filter expression to filter logs.
+ order_by: Field to sort the logs by.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+ as_list: If True, return a list of logs instead of a dict
+ with logs list and nextPageToken.
+
+ Returns:
+ If as_list is True: List of logs.
+ If as_list is False: Dict with logs list and nextPageToken.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _list_connector_instance_logs(
+ self,
+ integration_name,
+ connector_id,
+ connector_instance_id,
+ page_size=page_size,
+ page_token=page_token,
+ filter_string=filter_string,
+ order_by=order_by,
+ api_version=api_version,
+ as_list=as_list,
+ )
+
+ def get_connector_instance_log(
+ self,
+ integration_name: str,
+ connector_id: str,
+ connector_instance_id: str,
+ log_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Get a single log entry for a specific connector instance.
+
+ Use this method to retrieve a specific log entry from a
+ connector instance's execution, including its message,
+ timestamp, and severity level. Useful for auditing and detailed
+ troubleshooting of a specific connector run.
+
+ Args:
+ integration_name: Name of the integration the connector
+ belongs to.
+ connector_id: ID of the connector the instance belongs to.
+ connector_instance_id: ID of the connector instance the log
+ belongs to.
+ log_id: ID of the log entry to retrieve.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ Dict containing details of the specified ConnectorLog.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _get_connector_instance_log(
+ self,
+ integration_name,
+ connector_id,
+ connector_instance_id,
+ log_id,
+ api_version=api_version,
+ )
+
+ # -------------------------------------------------------------------------
+ # Connector Instance methods
+ # -------------------------------------------------------------------------
+
+ def list_connector_instances(
+ self,
+ integration_name: str,
+ connector_id: str,
+ page_size: int | None = None,
+ page_token: str | None = None,
+ filter_string: str | None = None,
+ order_by: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ as_list: bool = False,
+ ) -> dict[str, Any] | list[dict[str, Any]]:
+ """List all instances for a specific integration connector.
+
+ Use this method to discover all configured instances of a
+ connector.
+
+ Args:
+ integration_name: Name of the integration the connector
+ belongs to.
+ connector_id: ID of the connector to list instances for.
+ page_size: Maximum number of instances to return.
+ page_token: Page token from a previous call to retrieve the
+ next page.
+ filter_string: Filter expression to filter instances.
+ order_by: Field to sort the instances by.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+ as_list: If True, return a list of instances instead of a
+ dict with instances list and nextPageToken.
+
+ Returns:
+ If as_list is True: List of connector instances.
+ If as_list is False: Dict with connector instances list and
+ nextPageToken.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _list_connector_instances(
+ self,
+ integration_name,
+ connector_id,
+ page_size=page_size,
+ page_token=page_token,
+ filter_string=filter_string,
+ order_by=order_by,
+ api_version=api_version,
+ as_list=as_list,
+ )
+
+ def get_connector_instance(
+ self,
+ integration_name: str,
+ connector_id: str,
+ connector_instance_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Get a single connector instance by ID.
+
+ Use this method to retrieve the configuration of a specific
+ connector instance, including its parameters, schedule, and
+ runtime settings.
+
+ Args:
+ integration_name: Name of the integration the connector
+ belongs to.
+ connector_id: ID of the connector the instance belongs to.
+ connector_instance_id: ID of the connector instance to
+ retrieve.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ Dict containing details of the specified ConnectorInstance.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _get_connector_instance(
+ self,
+ integration_name,
+ connector_id,
+ connector_instance_id,
+ api_version=api_version,
+ )
+
+ def delete_connector_instance(
+ self,
+ integration_name: str,
+ connector_id: str,
+ connector_instance_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> None:
+ """Delete a specific connector instance.
+
+ Use this method to permanently remove a connector instance and
+ its configuration.
+
+ Args:
+ integration_name: Name of the integration the connector
+ belongs to.
+ connector_id: ID of the connector the instance belongs to.
+ connector_instance_id: ID of the connector instance to
+ delete.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ None
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _delete_connector_instance(
+ self,
+ integration_name,
+ connector_id,
+ connector_instance_id,
+ api_version=api_version,
+ )
+
+ def create_connector_instance(
+ self,
+ integration_name: str,
+ connector_id: str,
+ environment: str,
+ display_name: str,
+ interval_seconds: int,
+ timeout_seconds: int,
+ description: str | None = None,
+ parameters: list[ConnectorInstanceParameter | dict] | None = None,
+ agent: str | None = None,
+ allow_list: list[str] | None = None,
+ product_field_name: str | None = None,
+ event_field_name: str | None = None,
+ integration_version: str | None = None,
+ version: str | None = None,
+ logging_enabled_until_unix_ms: str | None = None,
+ connector_instance_id: str | None = None,
+ enabled: bool | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Create a new connector instance.
+
+ Use this method to configure a new instance of a connector with
+ specific parameters and schedule settings.
+
+ Args:
+ integration_name: Name of the integration the connector
+ belongs to.
+ connector_id: ID of the connector to create an instance for.
+ environment: Environment for the instance (e.g.,
+ "production").
+ display_name: Display name for the instance. Required.
+ interval_seconds: Interval in seconds for recurring
+ execution. Required.
+ timeout_seconds: Timeout in seconds for execution. Required.
+ description: Description of the instance. Optional.
+ parameters: List of parameters for the instance. Optional.
+ agent: Agent identifier for remote execution. Optional.
+ allow_list: List of allowed IP addresses. Optional.
+ product_field_name: Product field name. Optional.
+ event_field_name: Event field name. Optional.
+ integration_version: Integration version. Optional.
+ version: Version. Optional.
+ logging_enabled_until_unix_ms: Logging enabled until
+ timestamp. Optional.
+ connector_instance_id: Custom ID for the instance. Optional.
+ enabled: Whether the instance is enabled. Optional.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ Dict containing the newly created ConnectorInstance resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _create_connector_instance(
+ self,
+ integration_name,
+ connector_id,
+ environment,
+ display_name,
+ interval_seconds,
+ timeout_seconds,
+ description=description,
+ parameters=parameters,
+ agent=agent,
+ allow_list=allow_list,
+ product_field_name=product_field_name,
+ event_field_name=event_field_name,
+ integration_version=integration_version,
+ version=version,
+ logging_enabled_until_unix_ms=logging_enabled_until_unix_ms,
+ connector_instance_id=connector_instance_id,
+ enabled=enabled,
+ api_version=api_version,
+ )
+
+ def update_connector_instance(
+ self,
+ integration_name: str,
+ connector_id: str,
+ connector_instance_id: str,
+ display_name: str | None = None,
+ description: str | None = None,
+ interval_seconds: int | None = None,
+ timeout_seconds: int | None = None,
+ parameters: list[ConnectorInstanceParameter | dict] | None = None,
+ allow_list: list[str] | None = None,
+ product_field_name: str | None = None,
+ event_field_name: str | None = None,
+ integration_version: str | None = None,
+ version: str | None = None,
+ logging_enabled_until_unix_ms: str | None = None,
+ enabled: bool | None = None,
+ update_mask: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Update an existing connector instance.
+
+ Use this method to modify the configuration, parameters, or
+ schedule of a connector instance.
+
+ Args:
+ integration_name: Name of the integration the connector
+ belongs to.
+ connector_id: ID of the connector the instance belongs to.
+ connector_instance_id: ID of the connector instance to
+ update.
+ display_name: Display name for the instance. Optional.
+ description: Description of the instance. Optional.
+ interval_seconds: Interval in seconds for recurring
+ execution. Optional.
+ timeout_seconds: Timeout in seconds for execution. Optional.
+ parameters: List of parameters for the instance. Optional.
+ agent: Agent identifier for remote execution. Optional.
+ allow_list: List of allowed IP addresses. Optional.
+ product_field_name: Product field name. Optional.
+ event_field_name: Event field name. Optional.
+ integration_version: Integration version. Optional.
+ version: Version. Optional.
+ logging_enabled_until_unix_ms: Logging enabled until
+ timestamp. Optional.
+ enabled: Whether the instance is enabled. Optional.
+ update_mask: Comma-separated list of fields to update. If
+ omitted, all provided fields will be updated.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ Dict containing the updated ConnectorInstance resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _update_connector_instance(
+ self,
+ integration_name,
+ connector_id,
+ connector_instance_id,
+ display_name=display_name,
+ description=description,
+ interval_seconds=interval_seconds,
+ timeout_seconds=timeout_seconds,
+ parameters=parameters,
+ allow_list=allow_list,
+ product_field_name=product_field_name,
+ event_field_name=event_field_name,
+ integration_version=integration_version,
+ version=version,
+ logging_enabled_until_unix_ms=logging_enabled_until_unix_ms,
+ enabled=enabled,
+ update_mask=update_mask,
+ api_version=api_version,
+ )
+
+ def get_connector_instance_latest_definition(
+ self,
+ integration_name: str,
+ connector_id: str,
+ connector_instance_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Fetch the latest definition for a connector instance.
+
+ Use this method to refresh a connector instance with the latest
+ connector definition from the marketplace.
+
+ Args:
+ integration_name: Name of the integration the connector
+ belongs to.
+ connector_id: ID of the connector the instance belongs to.
+ connector_instance_id: ID of the connector instance to
+ refresh.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ Dict containing the refreshed ConnectorInstance with latest
+ definition.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _get_connector_instance_latest_definition(
+ self,
+ integration_name,
+ connector_id,
+ connector_instance_id,
+ api_version=api_version,
+ )
+
+ def set_connector_instance_logs_collection(
+ self,
+ integration_name: str,
+ connector_id: str,
+ connector_instance_id: str,
+ enabled: bool,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Enable or disable logs collection for a connector instance.
+
+ Use this method to control whether execution logs are collected
+ for a specific connector instance.
+
+ Args:
+ integration_name: Name of the integration the connector
+ belongs to.
+ connector_id: ID of the connector the instance belongs to.
+ connector_instance_id: ID of the connector instance to
+ configure.
+ enabled: Whether to enable or disable logs collection.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ Dict containing the updated logs collection status.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _set_connector_instance_logs_collection(
+ self,
+ integration_name,
+ connector_id,
+ connector_instance_id,
+ enabled,
+ api_version=api_version,
+ )
+
+ def run_connector_instance_on_demand(
+ self,
+ integration_name: str,
+ connector_id: str,
+ connector_instance_id: str,
+ connector_instance: dict[str, Any],
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Run a connector instance on demand for testing.
+
+ Use this method to execute a connector instance immediately
+ without waiting for its scheduled run. Useful for testing
+ configuration changes.
+
+ Args:
+ integration_name: Name of the integration the connector
+ belongs to.
+ connector_id: ID of the connector the instance belongs to.
+ connector_instance_id: ID of the connector instance to run.
+ connector_instance: The connector instance configuration to
+ test. Should include parameters and other settings.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ Dict containing the execution result, including success
+ status and debug output.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _run_connector_instance_on_demand(
+ self,
+ integration_name,
+ connector_id,
+ connector_instance_id,
+ connector_instance,
+ api_version=api_version,
+ )
+
+ # -------------------------------------------------------------------------
+ # Integration Job methods
+ # -------------------------------------------------------------------------
+
+ def list_integration_jobs(
+ self,
+ integration_name: str,
+ page_size: int | None = None,
+ page_token: str | None = None,
+ filter_string: str | None = None,
+ order_by: str | None = None,
+ exclude_staging: bool | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ as_list: bool = False,
+ ) -> dict[str, Any] | list[dict[str, Any]]:
+ """List all jobs defined for a specific integration.
+
+ Use this method to browse the available background and scheduled
+ automation capabilities provided by a third-party connection.
+
+ Args:
+ integration_name: Name of the integration to list jobs for.
+ page_size: Maximum number of jobs to return.
+ page_token: Page token from a previous call to retrieve the
+ next page.
+ filter_string: Filter expression to filter jobs. Allowed
+ filters are: id, custom, system, author, version,
+ integration.
+ order_by: Field to sort the jobs by.
+ exclude_staging: Whether to exclude staging jobs from the
+ response. By default, staging jobs are included.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+ as_list: If True, return a list of jobs instead of a dict
+ with jobs list and nextPageToken.
+
+ Returns:
+ If as_list is True: List of jobs.
+ If as_list is False: Dict with jobs list and nextPageToken.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _list_integration_jobs(
+ self,
+ integration_name,
+ page_size=page_size,
+ page_token=page_token,
+ filter_string=filter_string,
+ order_by=order_by,
+ exclude_staging=exclude_staging,
+ api_version=api_version,
+ as_list=as_list,
+ )
+
+ def get_integration_job(
+ self,
+ integration_name: str,
+ job_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Get a single job for a given integration.
+
+ Use this method to retrieve the Python script, execution
+ parameters, and versioning information for a background
+ automation task.
+
+ Args:
+ integration_name: Name of the integration the job belongs
+ to.
+ job_id: ID of the job to retrieve.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ Dict containing details of the specified IntegrationJob.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _get_integration_job(
+ self,
+ integration_name,
+ job_id,
+ api_version=api_version,
+ )
+
+ def delete_integration_job(
+ self,
+ integration_name: str,
+ job_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> None:
+ """Delete a specific custom job from a given integration.
+
+ Only custom jobs can be deleted; commercial and system jobs
+ are immutable.
+
+ Args:
+ integration_name: Name of the integration the job belongs
+ to.
+ job_id: ID of the job to delete.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ None
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _delete_integration_job(
+ self,
+ integration_name,
+ job_id,
+ api_version=api_version,
+ )
+
+ def create_integration_job(
+ self,
+ integration_name: str,
+ display_name: str,
+ script: str,
+ version: int,
+ enabled: bool,
+ custom: bool,
+ description: str | None = None,
+ parameters: list[dict[str, Any] | JobParameter] | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Create a new custom job for a given integration.
+
+ Each job must have a unique display name and a functional
+ Python script for its background execution.
+
+ Args:
+ integration_name: Name of the integration to create the job
+ for.
+ display_name: Job's display name. Maximum 400 characters.
+ Required.
+ script: Job's Python script. Required.
+ version: Job's version. Required.
+ enabled: Whether the job is enabled. Required.
+ custom: Whether the job is custom or commercial. Required.
+ description: Job's description. Optional.
+ parameters: List of JobParameter instances or dicts.
+ Optional.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ Dict containing the newly created IntegrationJob resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _create_integration_job(
+ self,
+ integration_name,
+ display_name,
+ script,
+ version,
+ enabled,
+ custom,
+ description=description,
+ parameters=parameters,
+ api_version=api_version,
+ )
+
+ def update_integration_job(
+ self,
+ integration_name: str,
+ job_id: str,
+ display_name: str | None = None,
+ script: str | None = None,
+ version: int | None = None,
+ enabled: bool | None = None,
+ custom: bool | None = None,
+ description: str | None = None,
+ parameters: list[dict[str, Any] | JobParameter] | None = None,
+ update_mask: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Update an existing custom job for a given integration.
+
+ Use this method to modify the Python script or adjust the
+ parameter definitions for a job.
+
+ Args:
+ integration_name: Name of the integration the job belongs
+ to.
+ job_id: ID of the job to update.
+ display_name: Job's display name. Maximum 400 characters.
+ script: Job's Python script.
+ version: Job's version.
+ enabled: Whether the job is enabled.
+ custom: Whether the job is custom or commercial.
+ description: Job's description.
+ parameters: List of JobParameter instances or dicts.
+ update_mask: Comma-separated list of fields to update. If
+ omitted, the mask is auto-generated from whichever
+ fields are provided. Example: "displayName,script".
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ Dict containing the updated IntegrationJob resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _update_integration_job(
+ self,
+ integration_name,
+ job_id,
+ display_name=display_name,
+ script=script,
+ version=version,
+ enabled=enabled,
+ custom=custom,
+ description=description,
+ parameters=parameters,
+ update_mask=update_mask,
+ api_version=api_version,
+ )
+
+ def execute_integration_job_test(
+ self,
+ integration_name: str,
+ job: dict[str, Any],
+ agent_identifier: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Execute a test run of an integration job's Python script.
+
+ Use this method to verify background automation logic and
+ connectivity before deploying the job to an instance for
+ recurring execution.
+
+ Args:
+ integration_name: Name of the integration the job belongs
+ to.
+ job: Dict containing the IntegrationJob to test.
+ agent_identifier: Agent identifier for remote testing.
+ Optional.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ Dict containing the test execution results with the
+ following fields:
+ - output: The script output.
+ - debugOutput: The script debug output.
+ - resultObjectJson: The result JSON if it exists
+ (optional).
+ - resultName: The script result name (optional).
+ - resultValue: The script result value (optional).
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _execute_integration_job_test(
+ self,
+ integration_name,
+ job,
+ agent_identifier=agent_identifier,
+ api_version=api_version,
+ )
+
+ def get_integration_job_template(
+ self,
+ integration_name: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Retrieve a default Python script template for a new
+ integration job.
+
+ Use this method to rapidly initialize the development of a new
+ job.
+
+ Args:
+ integration_name: Name of the integration to fetch the
+ template for.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ Dict containing the IntegrationJob template.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _get_integration_job_template(
+ self,
+ integration_name,
+ api_version=api_version,
+ )
+
+ # -------------------------------------------------------------------------
+ # Integration Manager methods
+ # -------------------------------------------------------------------------
+
+ def list_integration_managers(
+ self,
+ integration_name: str,
+ page_size: int | None = None,
+ page_token: str | None = None,
+ filter_string: str | None = None,
+ order_by: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ as_list: bool = False,
+ ) -> dict[str, Any] | list[dict[str, Any]]:
+ """List all managers defined for a specific integration.
+
+ Use this method to discover the library of managers available
+ within a particular integration's scope.
+
+ Args:
+ integration_name: Name of the integration to list managers
+ for.
+ page_size: Maximum number of managers to return. Defaults to
+ 100, maximum is 100.
+ page_token: Page token from a previous call to retrieve the
+ next page.
+ filter_string: Filter expression to filter managers.
+ order_by: Field to sort the managers by.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+ as_list: If True, return a list of managers instead of a
+ dict with managers list and nextPageToken.
+
+ Returns:
+ If as_list is True: List of managers.
+ If as_list is False: Dict with managers list and
+ nextPageToken.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _list_integration_managers(
+ self,
+ integration_name,
+ page_size=page_size,
+ page_token=page_token,
+ filter_string=filter_string,
+ order_by=order_by,
+ api_version=api_version,
+ as_list=as_list,
+ )
+
+ def get_integration_manager(
+ self,
+ integration_name: str,
+ manager_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Get a single manager for a given integration.
+
+ Use this method to retrieve the manager script and its metadata
+ for review or reference.
+
+ Args:
+ integration_name: Name of the integration the manager
+ belongs to.
+ manager_id: ID of the manager to retrieve.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ Dict containing details of the specified IntegrationManager.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _get_integration_manager(
+ self,
+ integration_name,
+ manager_id,
+ api_version=api_version,
+ )
+
+ def delete_integration_manager(
+ self,
+ integration_name: str,
+ manager_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> None:
+ """Delete a specific custom manager from a given integration.
+
+ Note that deleting a manager may break components (actions,
+ jobs) that depend on its code.
+
+ Args:
+ integration_name: Name of the integration the manager
+ belongs to.
+ manager_id: ID of the manager to delete.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ None
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _delete_integration_manager(
+ self,
+ integration_name,
+ manager_id,
+ api_version=api_version,
+ )
+
+ def create_integration_manager(
+ self,
+ integration_name: str,
+ display_name: str,
+ script: str,
+ description: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Create a new custom manager for a given integration.
+
+ Use this method to add a new shared code utility. Each manager
+ must have a unique display name and a script containing valid
+ Python logic for reuse across actions, jobs, and connectors.
+
+ Args:
+ integration_name: Name of the integration to create the
+ manager for.
+ display_name: Manager's display name. Maximum 150
+ characters. Required.
+ script: Manager's Python script. Maximum 5MB. Required.
+ description: Manager's description. Maximum 400 characters.
+ Optional.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ Dict containing the newly created IntegrationManager
+ resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _create_integration_manager(
+ self,
+ integration_name,
+ display_name,
+ script,
+ description=description,
+ api_version=api_version,
+ )
+
+ def update_integration_manager(
+ self,
+ integration_name: str,
+ manager_id: str,
+ display_name: str | None = None,
+ script: str | None = None,
+ description: str | None = None,
+ update_mask: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Update an existing custom manager for a given integration.
+
+ Use this method to modify the shared code, adjust its
+ description, or refine its logic across all components that
+ import it.
+
+ Args:
+ integration_name: Name of the integration the manager
+ belongs to.
+ manager_id: ID of the manager to update.
+ display_name: Manager's display name. Maximum 150
+ characters.
+ script: Manager's Python script. Maximum 5MB.
+ description: Manager's description. Maximum 400 characters.
+ update_mask: Comma-separated list of fields to update. If
+ omitted, the mask is auto-generated from whichever
+ fields are provided. Example: "displayName,script".
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ Dict containing the updated IntegrationManager resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _update_integration_manager(
+ self,
+ integration_name,
+ manager_id,
+ display_name=display_name,
+ script=script,
+ description=description,
+ update_mask=update_mask,
+ api_version=api_version,
+ )
+
+ def get_integration_manager_template(
+ self,
+ integration_name: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Retrieve a default Python script template for a new
+ integration manager.
+
+ Use this method to quickly start developing new managers.
+
+ Args:
+ integration_name: Name of the integration to fetch the
+ template for.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ Dict containing the IntegrationManager template.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _get_integration_manager_template(
+ self,
+ integration_name,
+ api_version=api_version,
+ )
+
+ # -------------------------------------------------------------------------
+ # Integration Manager Revisions methods
+ # -------------------------------------------------------------------------
+
+ def list_integration_manager_revisions(
+ self,
+ integration_name: str,
+ manager_id: str,
+ page_size: int | None = None,
+ page_token: str | None = None,
+ filter_string: str | None = None,
+ order_by: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ as_list: bool = False,
+ ) -> dict[str, Any] | list[dict[str, Any]]:
+ """List all revisions for a specific integration manager.
+
+ Use this method to browse the version history and identify
+ previous functional states of a manager.
+
+ Args:
+ integration_name: Name of the integration the manager
+ belongs to.
+ manager_id: ID of the manager to list revisions for.
+ page_size: Maximum number of revisions to return.
+ page_token: Page token from a previous call to retrieve the
+ next page.
+ filter_string: Filter expression to filter revisions.
+ order_by: Field to sort the revisions by.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+ as_list: If True, return a list of revisions instead of a
+ dict with revisions list and nextPageToken.
+
+ Returns:
+ If as_list is True: List of revisions.
+ If as_list is False: Dict with revisions list and
+ nextPageToken.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _list_integration_manager_revisions(
+ self,
+ integration_name,
+ manager_id,
+ page_size=page_size,
+ page_token=page_token,
+ filter_string=filter_string,
+ order_by=order_by,
+ api_version=api_version,
+ as_list=as_list,
+ )
+
+ def get_integration_manager_revision(
+ self,
+ integration_name: str,
+ manager_id: str,
+ revision_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Get a single revision for a specific integration manager.
+
+ Use this method to retrieve a specific snapshot of an
+ IntegrationManagerRevision for comparison or review.
+
+ Args:
+ integration_name: Name of the integration the manager
+ belongs to.
+ manager_id: ID of the manager the revision belongs to.
+ revision_id: ID of the revision to retrieve.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ Dict containing details of the specified
+ IntegrationManagerRevision.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _get_integration_manager_revision(
+ self,
+ integration_name,
+ manager_id,
+ revision_id,
+ api_version=api_version,
+ )
+
+ def delete_integration_manager_revision(
+ self,
+ integration_name: str,
+ manager_id: str,
+ revision_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> None:
+ """Delete a specific revision for a given integration manager.
+
+ Use this method to clean up obsolete snapshots and manage the
+ historical record of managers.
+
+ Args:
+ integration_name: Name of the integration the manager
+ belongs to.
+ manager_id: ID of the manager the revision belongs to.
+ revision_id: ID of the revision to delete.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ None
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _delete_integration_manager_revision(
+ self,
+ integration_name,
+ manager_id,
+ revision_id,
+ api_version=api_version,
+ )
+
+ def create_integration_manager_revision(
+ self,
+ integration_name: str,
+ manager_id: str,
+ manager: dict[str, Any],
+ comment: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Create a new revision snapshot of the current integration
+ manager.
+
+ Use this method to establish a recovery point before making
+ significant updates to a manager.
+
+ Args:
+ integration_name: Name of the integration the manager
+ belongs to.
+ manager_id: ID of the manager to create a revision for.
+ manager: Dict containing the IntegrationManager to snapshot.
+ comment: Comment describing the revision. Maximum 400
+ characters. Optional.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ Dict containing the newly created
+ IntegrationManagerRevision resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _create_integration_manager_revision(
+ self,
+ integration_name,
+ manager_id,
+ manager,
+ comment=comment,
+ api_version=api_version,
+ )
+
+ def rollback_integration_manager_revision(
+ self,
+ integration_name: str,
+ manager_id: str,
+ revision_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Revert the current manager definition to a previously saved
+ revision.
+
+ Use this method to rapidly recover a functional state for
+ common code if an update causes operational issues in dependent
+ actions or jobs.
+
+ Args:
+ integration_name: Name of the integration the manager
+ belongs to.
+ manager_id: ID of the manager to rollback.
+ revision_id: ID of the revision to rollback to.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ Dict containing the IntegrationManagerRevision rolled back
+ to.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _rollback_integration_manager_revision(
+ self,
+ integration_name,
+ manager_id,
+ revision_id,
+ api_version=api_version,
+ )
+
+ # -------------------------------------------------------------------------
+ # Integration Job Revisions methods
+ # -------------------------------------------------------------------------
+
+ def list_integration_job_revisions(
+ self,
+ integration_name: str,
+ job_id: str,
+ page_size: int | None = None,
+ page_token: str | None = None,
+ filter_string: str | None = None,
+ order_by: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ as_list: bool = False,
+ ) -> dict[str, Any] | list[dict[str, Any]]:
+ """List all revisions for a specific integration job.
+
+ Use this method to browse the version history of a job and
+ identify previous functional states.
+
+ Args:
+ integration_name: Name of the integration the job belongs
+ to.
+ job_id: ID of the job to list revisions for.
+ page_size: Maximum number of revisions to return.
+ page_token: Page token from a previous call to retrieve the
+ next page.
+ filter_string: Filter expression to filter revisions.
+ order_by: Field to sort the revisions by.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+ as_list: If True, return a list of revisions instead of a
+ dict with revisions list and nextPageToken.
+
+ Returns:
+ If as_list is True: List of revisions.
+ If as_list is False: Dict with revisions list and
+ nextPageToken.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _list_integration_job_revisions(
+ self,
+ integration_name,
+ job_id,
+ page_size=page_size,
+ page_token=page_token,
+ filter_string=filter_string,
+ order_by=order_by,
+ api_version=api_version,
+ as_list=as_list,
+ )
+
+ def delete_integration_job_revision(
+ self,
+ integration_name: str,
+ job_id: str,
+ revision_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> None:
+ """Delete a specific revision for a given integration job.
+
+ Use this method to clean up obsolete snapshots and manage the
+ historical record of jobs.
+
+ Args:
+ integration_name: Name of the integration the job belongs
+ to.
+ job_id: ID of the job the revision belongs to.
+ revision_id: ID of the revision to delete.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ None
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _delete_integration_job_revision(
+ self,
+ integration_name,
+ job_id,
+ revision_id,
+ api_version=api_version,
+ )
+
+ def create_integration_job_revision(
+ self,
+ integration_name: str,
+ job_id: str,
+ job: dict[str, Any],
+ comment: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Create a new revision snapshot of the current integration
+ job.
+
+ Use this method to establish a recovery point before making
+ significant updates to a job.
+
+ Args:
+ integration_name: Name of the integration the job belongs
+ to.
+ job_id: ID of the job to create a revision for.
+ job: Dict containing the IntegrationJob to snapshot.
+ comment: Comment describing the revision. Maximum 400
+ characters. Optional.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ Dict containing the newly created IntegrationJobRevision
+ resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _create_integration_job_revision(
+ self,
+ integration_name,
+ job_id,
+ job,
+ comment=comment,
+ api_version=api_version,
+ )
+
+ def rollback_integration_job_revision(
+ self,
+ integration_name: str,
+ job_id: str,
+ revision_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Revert the current job definition to a previously saved
+ revision.
+
+ Use this method to rapidly recover a functional state if an
+ update causes operational issues in scheduled or background
+ automation.
+
+ Args:
+ integration_name: Name of the integration the job belongs
+ to.
+ job_id: ID of the job to rollback.
+ revision_id: ID of the revision to rollback to.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ Dict containing the IntegrationJobRevision rolled back to.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _rollback_integration_job_revision(
+ self,
+ integration_name,
+ job_id,
+ revision_id,
+ api_version=api_version,
+ )
+
+ # -------------------------------------------------------------------------
+ # Integration Job Instances methods
+ # -------------------------------------------------------------------------
+
+ def list_integration_job_instances(
+ self,
+ integration_name: str,
+ job_id: str,
+ page_size: int | None = None,
+ page_token: str | None = None,
+ filter_string: str | None = None,
+ order_by: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ as_list: bool = False,
+ ) -> dict[str, Any] | list[dict[str, Any]]:
+ """List all job instances for a specific integration job.
+
+ Use this method to browse the active job instances and their
+ last execution status.
+
+ Args:
+ integration_name: Name of the integration the job belongs
+ to.
+ job_id: ID of the job to list instances for.
+ page_size: Maximum number of job instances to return.
+ page_token: Page token from a previous call to retrieve the
+ next page.
+ filter_string: Filter expression to filter job instances.
+ order_by: Field to sort the job instances by.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+ as_list: If True, return a list of job instances instead of
+ a dict with job instances list and nextPageToken.
+
+ Returns:
+ If as_list is True: List of job instances.
+ If as_list is False: Dict with job instances list and
+ nextPageToken.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _list_integration_job_instances(
+ self,
+ integration_name,
+ job_id,
+ page_size=page_size,
+ page_token=page_token,
+ filter_string=filter_string,
+ order_by=order_by,
+ api_version=api_version,
+ as_list=as_list,
+ )
+
+ def get_integration_job_instance(
+ self,
+ integration_name: str,
+ job_id: str,
+ job_instance_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Get a single job instance for a specific integration job.
+
+ Use this method to retrieve configuration details and the
+ current schedule settings for a job instance.
+
+ Args:
+ integration_name: Name of the integration the job belongs
+ to.
+ job_id: ID of the job the instance belongs to.
+ job_instance_id: ID of the job instance to retrieve.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ Dict containing details of the specified
+ IntegrationJobInstance.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _get_integration_job_instance(
+ self,
+ integration_name,
+ job_id,
+ job_instance_id,
+ api_version=api_version,
+ )
+
+ def delete_integration_job_instance(
+ self,
+ integration_name: str,
+ job_id: str,
+ job_instance_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> None:
+ """Delete a specific job instance for a given integration job.
+
+ Use this method to remove scheduled or configured job instances
+ that are no longer needed.
+
+ Args:
+ integration_name: Name of the integration the job belongs
+ to.
+ job_id: ID of the job the instance belongs to.
+ job_instance_id: ID of the job instance to delete.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ None
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _delete_integration_job_instance(
+ self,
+ integration_name,
+ job_id,
+ job_instance_id,
+ api_version=api_version,
+ )
+
+ def create_integration_job_instance(
+ self,
+ integration_name: str,
+ job_id: str,
+ display_name: str,
+ interval_seconds: int,
+ enabled: bool,
+ advanced: bool,
+ description: str | None = None,
+ parameters: list[dict[str, Any]] | None = None,
+ agent: str | None = None,
+ advanced_config: dict[str, Any] | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Create a new job instance for a given integration job.
+
+ Use this method to schedule a job to run at regular intervals
+ or with advanced cron-style scheduling.
+
+ Args:
+ integration_name: Name of the integration the job belongs
+ to.
+ job_id: ID of the job to create an instance for.
+ display_name: Display name for the job instance.
+ interval_seconds: Interval in seconds between job runs.
+ enabled: Whether the job instance is enabled.
+ advanced: Whether advanced scheduling is used.
+ description: Description of the job instance. Optional.
+ parameters: List of parameter values for the job instance.
+ Optional.
+ agent: Agent identifier for remote execution. Optional.
+ advanced_config: Advanced scheduling configuration.
+ Optional.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ Dict containing the newly created IntegrationJobInstance
+ resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _create_integration_job_instance(
+ self,
+ integration_name,
+ job_id,
+ display_name,
+ interval_seconds,
+ enabled,
+ advanced,
+ description=description,
+ parameters=parameters,
+ agent=agent,
+ advanced_config=advanced_config,
+ api_version=api_version,
+ )
+
+ def update_integration_job_instance(
+ self,
+ integration_name: str,
+ job_id: str,
+ job_instance_id: str,
+ display_name: str | None = None,
+ description: str | None = None,
+ interval_seconds: int | None = None,
+ enabled: bool | None = None,
+ advanced: bool | None = None,
+ parameters: list[dict[str, Any]] | None = None,
+ advanced_config: dict[str, Any] | None = None,
+ update_mask: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Update an existing job instance for a given integration job.
+
+ Use this method to modify scheduling, parameters, or enable/
+ disable a job instance.
+
+ Args:
+ integration_name: Name of the integration the job belongs
+ to.
+ job_id: ID of the job the instance belongs to.
+ job_instance_id: ID of the job instance to update.
+ display_name: Display name for the job instance. Optional.
+ description: Description of the job instance. Optional.
+ interval_seconds: Interval in seconds between job runs.
+ Optional.
+ enabled: Whether the job instance is enabled. Optional.
+ advanced: Whether advanced scheduling is used. Optional.
+ parameters: List of parameter values for the job instance.
+ Optional.
+ advanced_config: Advanced scheduling configuration.
+ Optional.
+ update_mask: Comma-separated field paths to update. If not
+ provided, will be auto-generated. Optional.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ Dict containing the updated IntegrationJobInstance.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _update_integration_job_instance(
+ self,
+ integration_name,
+ job_id,
+ job_instance_id,
+ display_name=display_name,
+ description=description,
+ interval_seconds=interval_seconds,
+ enabled=enabled,
+ advanced=advanced,
+ parameters=parameters,
+ advanced_config=advanced_config,
+ update_mask=update_mask,
+ api_version=api_version,
+ )
+
+ def run_integration_job_instance_on_demand(
+ self,
+ integration_name: str,
+ job_id: str,
+ job_instance_id: str,
+ parameters: list[dict[str, Any]] | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Run a job instance immediately without waiting for the next
+ scheduled execution.
+
+ Use this method to manually trigger a job instance for testing
+ or immediate data collection.
+
+ Args:
+ integration_name: Name of the integration the job belongs
+ to.
+ job_id: ID of the job the instance belongs to.
+ job_instance_id: ID of the job instance to run.
+ parameters: Optional parameter overrides for this run.
+ Optional.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ Dict containing the result of the on-demand run.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _run_integration_job_instance_on_demand(
+ self,
+ integration_name,
+ job_id,
+ job_instance_id,
+ parameters=parameters,
+ api_version=api_version,
+ )
+
+ # -------------------------------------------------------------------------
+ # Job Context Properties methods
+ # -------------------------------------------------------------------------
+
+ def list_job_context_properties(
+ self,
+ integration_name: str,
+ job_id: str,
+ page_size: int | None = None,
+ page_token: str | None = None,
+ filter_string: str | None = None,
+ order_by: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ as_list: bool = False,
+ ) -> dict[str, Any] | list[dict[str, Any]]:
+ """List all context properties for a specific integration job.
+
+ Use this method to discover all custom data points associated
+ with a job.
+
+ Args:
+ integration_name: Name of the integration the job belongs
+ to.
+ job_id: ID of the job to list context properties for.
+ page_size: Maximum number of context properties to return.
+ page_token: Page token from a previous call to retrieve the
+ next page.
+ filter_string: Filter expression to filter context
+ properties.
+ order_by: Field to sort the context properties by.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+ as_list: If True, return a list of context properties
+ instead of a dict with context properties list and
+ nextPageToken.
+
+ Returns:
+ If as_list is True: List of context properties.
+ If as_list is False: Dict with context properties list and
+ nextPageToken.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _list_job_context_properties(
+ self,
+ integration_name,
+ job_id,
+ page_size=page_size,
+ page_token=page_token,
+ filter_string=filter_string,
+ order_by=order_by,
+ api_version=api_version,
+ as_list=as_list,
+ )
+
+ def get_job_context_property(
+ self,
+ integration_name: str,
+ job_id: str,
+ context_property_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Get a single context property for a specific integration
+ job.
+
+ Use this method to retrieve the value of a specific key within
+ a job's context.
+
+ Args:
+ integration_name: Name of the integration the job belongs
+ to.
+ job_id: ID of the job the context property belongs to.
+ context_property_id: ID of the context property to
+ retrieve.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ Dict containing details of the specified ContextProperty.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _get_job_context_property(
+ self,
+ integration_name,
+ job_id,
+ context_property_id,
+ api_version=api_version,
+ )
+
+ def delete_job_context_property(
+ self,
+ integration_name: str,
+ job_id: str,
+ context_property_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> None:
+ """Delete a specific context property for a given integration
+ job.
+
+ Use this method to remove a custom data point that is no longer
+ relevant to the job's context.
+
+ Args:
+ integration_name: Name of the integration the job belongs
+ to.
+ job_id: ID of the job the context property belongs to.
+ context_property_id: ID of the context property to delete.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ None
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _delete_job_context_property(
+ self,
+ integration_name,
+ job_id,
+ context_property_id,
+ api_version=api_version,
+ )
+
+ def create_job_context_property(
+ self,
+ integration_name: str,
+ job_id: str,
+ value: str,
+ key: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Create a new context property for a specific integration
+ job.
+
+ Use this method to attach custom data to a job's context.
+ Property keys must be unique within their context.
+
+ Args:
+ integration_name: Name of the integration the job belongs
+ to.
+ job_id: ID of the job to create the context property for.
+ value: The property value. Required.
+ key: The context property ID to use. Must be 4-63
+ characters and match /[a-z][0-9]-/. Optional.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ Dict containing the newly created ContextProperty resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _create_job_context_property(
+ self,
+ integration_name,
+ job_id,
+ value,
+ key=key,
+ api_version=api_version,
+ )
+
+ def update_job_context_property(
+ self,
+ integration_name: str,
+ job_id: str,
+ context_property_id: str,
+ value: str,
+ update_mask: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Update an existing context property for a given integration
+ job.
+
+ Use this method to modify the value of a previously saved key.
+
+ Args:
+ integration_name: Name of the integration the job belongs
+ to.
+ job_id: ID of the job the context property belongs to.
+ context_property_id: ID of the context property to update.
+ value: The new property value. Required.
+ update_mask: Comma-separated list of fields to update. Only
+ "value" is supported. If omitted, defaults to "value".
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ Dict containing the updated ContextProperty resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _update_job_context_property(
+ self,
+ integration_name,
+ job_id,
+ context_property_id,
+ value,
+ update_mask=update_mask,
+ api_version=api_version,
+ )
+
+ def delete_all_job_context_properties(
+ self,
+ integration_name: str,
+ job_id: str,
+ context_id: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> None:
+ """Delete all context properties for a specific integration
+ job.
+
+ Use this method to quickly clear all supplemental data from a
+ job's context.
+
+ Args:
+ integration_name: Name of the integration the job belongs
+ to.
+ job_id: ID of the job to clear context properties from.
+ context_id: The context ID to remove context properties
+ from. Must be 4-63 characters and match /[a-z][0-9]-/.
+ Optional.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ None
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _delete_all_job_context_properties(
+ self,
+ integration_name,
+ job_id,
+ context_id=context_id,
+ api_version=api_version,
+ )
+
+ # -------------------------------------------------------------------------
+ # Job Instance Logs methods
+ # -------------------------------------------------------------------------
+
+ def list_job_instance_logs(
+ self,
+ integration_name: str,
+ job_id: str,
+ job_instance_id: str,
+ page_size: int | None = None,
+ page_token: str | None = None,
+ filter_string: str | None = None,
+ order_by: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ as_list: bool = False,
+ ) -> dict[str, Any] | list[dict[str, Any]]:
+ """List all execution logs for a specific job instance.
+
+ Use this method to browse the historical performance and
+ reliability of a background automation schedule.
+
+ Args:
+ integration_name: Name of the integration the job belongs
+ to.
+ job_id: ID of the job the instance belongs to.
+ job_instance_id: ID of the job instance to list logs for.
+ page_size: Maximum number of logs to return.
+ page_token: Page token from a previous call to retrieve the
+ next page.
+ filter_string: Filter expression to filter logs.
+ order_by: Field to sort the logs by.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+ as_list: If True, return a list of logs instead of a dict
+ with logs list and nextPageToken.
+
+ Returns:
+ If as_list is True: List of logs.
+ If as_list is False: Dict with logs list and nextPageToken.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _list_job_instance_logs(
+ self,
+ integration_name,
+ job_id,
+ job_instance_id,
+ page_size=page_size,
+ page_token=page_token,
+ filter_string=filter_string,
+ order_by=order_by,
+ api_version=api_version,
+ as_list=as_list,
+ )
+
+ def get_job_instance_log(
+ self,
+ integration_name: str,
+ job_id: str,
+ job_instance_id: str,
+ log_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Get a single log entry for a specific job instance.
+
+ Use this method to retrieve the detailed output message,
+ start/end times, and final status of a specific background task
+ execution.
+
+ Args:
+ integration_name: Name of the integration the job belongs
+ to.
+ job_id: ID of the job the instance belongs to.
+ job_instance_id: ID of the job instance the log belongs to.
+ log_id: ID of the log entry to retrieve.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ Dict containing details of the specified JobInstanceLog.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _get_job_instance_log(
+ self,
+ integration_name,
+ job_id,
+ job_instance_id,
+ log_id,
+ api_version=api_version,
+ )
+
+ # -------------------------------------------------------------------------
+ # Integration Instances methods
+ # -------------------------------------------------------------------------
+
+ def list_integration_instances(
+ self,
+ integration_name: str,
+ page_size: int | None = None,
+ page_token: str | None = None,
+ filter_string: str | None = None,
+ order_by: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ as_list: bool = False,
+ ) -> dict[str, Any] | list[dict[str, Any]]:
+ """List all instances for a specific integration.
+
+ Use this method to browse the configured integration instances
+ available for a custom or third-party product across different
+ environments.
+
+ Args:
+ integration_name: Name of the integration to list instances
+ for.
+ page_size: Maximum number of integration instances to
+ return.
+ page_token: Page token from a previous call to retrieve the
+ next page.
+ filter_string: Filter expression to filter integration
+ instances.
+ order_by: Field to sort the integration instances by.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+ as_list: If True, return a list of integration instances
+ instead of a dict with integration instances list and
+ nextPageToken.
+
+ Returns:
+ If as_list is True: List of integration instances.
+ If as_list is False: Dict with integration instances list
+ and nextPageToken.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _list_integration_instances(
+ self,
+ integration_name,
+ page_size=page_size,
+ page_token=page_token,
+ filter_string=filter_string,
+ order_by=order_by,
+ api_version=api_version,
+ as_list=as_list,
+ )
+
+ def get_integration_instance(
+ self,
+ integration_name: str,
+ integration_instance_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Get a single instance for a specific integration.
+
+ Use this method to retrieve the specific configuration,
+ connection status, and environment mapping for an active
+ integration.
+
+ Args:
+ integration_name: Name of the integration the instance
+ belongs to.
+ integration_instance_id: ID of the integration instance to
+ retrieve.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ Dict containing details of the specified
+ IntegrationInstance.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _get_integration_instance(
+ self,
+ integration_name,
+ integration_instance_id,
+ api_version=api_version,
+ )
+
+ def delete_integration_instance(
+ self,
+ integration_name: str,
+ integration_instance_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> None:
+ """Delete a specific integration instance.
+
+ Use this method to permanently remove an integration instance
+ and stop all associated automated tasks (connectors or jobs)
+ using this instance.
+
+ Args:
+ integration_name: Name of the integration the instance
+ belongs to.
+ integration_instance_id: ID of the integration instance to
+ delete.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ None
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _delete_integration_instance(
+ self,
+ integration_name,
+ integration_instance_id,
+ api_version=api_version,
+ )
+
+ def create_integration_instance(
+ self,
+ integration_name: str,
+ environment: str,
+ display_name: str | None = None,
+ description: str | None = None,
+ parameters: (
+ list[dict[str, Any] | IntegrationInstanceParameter] | None
+ ) = None,
+ agent: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Create a new integration instance for a specific
+ integration.
+
+ Use this method to establish a new integration instance to a
+ custom or third-party security product for a specific
+ environment. All mandatory parameters required by the
+ integration definition must be provided.
+
+ Args:
+ integration_name: Name of the integration to create the
+ instance for.
+ environment: The integration instance environment. Required.
+ display_name: The display name of the integration instance.
+ Automatically generated if not provided. Maximum 110
+ characters.
+ description: The integration instance description. Maximum
+ 1500 characters.
+ parameters: List of IntegrationInstanceParameter instances
+ or dicts.
+ agent: Agent identifier for a remote integration instance.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ Dict containing the newly created IntegrationInstance
+ resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _create_integration_instance(
+ self,
+ integration_name,
+ environment,
+ display_name=display_name,
+ description=description,
+ parameters=parameters,
+ agent=agent,
+ api_version=api_version,
+ )
+
+ def update_integration_instance(
+ self,
+ integration_name: str,
+ integration_instance_id: str,
+ environment: str | None = None,
+ display_name: str | None = None,
+ description: str | None = None,
+ parameters: (
+ list[dict[str, Any] | IntegrationInstanceParameter] | None
+ ) = None,
+ agent: str | None = None,
+ update_mask: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Update an existing integration instance.
+
+ Use this method to modify connection parameters (e.g., rotate
+ an API key), change the display name, or update the description
+ of a configured integration instance.
+
+ Args:
+ integration_name: Name of the integration the instance
+ belongs to.
+ integration_instance_id: ID of the integration instance to
+ update.
+ environment: The integration instance environment.
+ display_name: The display name of the integration instance.
+ Maximum 110 characters.
+ description: The integration instance description. Maximum
+ 1500 characters.
+ parameters: List of IntegrationInstanceParameter instances
+ or dicts.
+ agent: Agent identifier for a remote integration instance.
+ update_mask: Comma-separated list of fields to update. If
+ omitted, the mask is auto-generated from whichever
+ fields are provided. Example:
+ "displayName,description".
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ Dict containing the updated IntegrationInstance resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _update_integration_instance(
+ self,
+ integration_name,
+ integration_instance_id,
+ environment=environment,
+ display_name=display_name,
+ description=description,
+ parameters=parameters,
+ agent=agent,
+ update_mask=update_mask,
+ api_version=api_version,
+ )
+
+ def execute_integration_instance_test(
+ self,
+ integration_name: str,
+ integration_instance_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Execute a connectivity test for a specific integration
+ instance.
+
+ Use this method to verify that SecOps can successfully
+ communicate with the third-party security product using the
+ provided credentials.
+
+ Args:
+ integration_name: Name of the integration the instance
+ belongs to.
+ integration_instance_id: ID of the integration instance to
+ test.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ Dict containing the test results with the following fields:
+ - successful: Indicates if the test was successful.
+ - message: Test result message (optional).
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _execute_integration_instance_test(
+ self,
+ integration_name,
+ integration_instance_id,
+ api_version=api_version,
+ )
+
+ def get_integration_instance_affected_items(
+ self,
+ integration_name: str,
+ integration_instance_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """List all playbooks that depend on a specific integration
+ instance.
+
+ Use this method to perform impact analysis before deleting or
+ significantly changing a connection configuration.
+
+ Args:
+ integration_name: Name of the integration the instance
+ belongs to.
+ integration_instance_id: ID of the integration instance to
+ fetch affected items for.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ Dict containing a list of AffectedPlaybookResponse objects
+ that depend on the specified integration instance.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _get_integration_instance_affected_items(
+ self,
+ integration_name,
+ integration_instance_id,
+ api_version=api_version,
+ )
+
+ def get_default_integration_instance(
+ self,
+ integration_name: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ ) -> dict[str, Any]:
+ """Get the system default configuration for a specific
+ integration.
+
+ Use this method to retrieve the baseline integration instance
+ details provided for a commercial product.
+
+ Args:
+ integration_name: Name of the integration to fetch the
+ default instance for.
+ api_version: API version to use for the request. Default is
+ V1BETA.
+
+ Returns:
+ Dict containing the default IntegrationInstance resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _get_default_integration_instance(
+ self,
+ integration_name,
+ api_version=api_version,
+ )
+
+ # -- Integration Transformers methods --
+
+ def list_integration_transformers(
+ self,
+ integration_name: str,
+ page_size: int | None = None,
+ page_token: str | None = None,
+ filter_string: str | None = None,
+ order_by: str | None = None,
+ exclude_staging: bool | None = None,
+ expand: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1ALPHA,
+ ) -> dict[str, Any] | list[dict[str, Any]]:
+ """List all transformer definitions for a specific integration.
+
+ Use this method to browse the available transformers.
+
+ Args:
+ integration_name: Name of the integration to list transformers
+ for.
+ page_size: Maximum number of transformers to return. Defaults
+ to 100, maximum is 200.
+ page_token: Page token from a previous call to retrieve the
+ next page.
+ filter_string: Filter expression to filter transformers.
+ order_by: Field to sort the transformers by.
+ exclude_staging: Whether to exclude staging transformers from
+ the response. By default, staging transformers are included.
+ expand: Expand the response with the full transformer details.
+ api_version: API version to use for the request. Default is
+ V1ALPHA.
+
+ Returns:
+ If as_list is True: List of transformers.
+ If as_list is False: Dict with transformers list and
+ nextPageToken.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _list_integration_transformers(
+ self,
+ integration_name,
+ page_size=page_size,
+ page_token=page_token,
+ filter_string=filter_string,
+ order_by=order_by,
+ exclude_staging=exclude_staging,
+ expand=expand,
+ api_version=api_version,
+ )
+
+ def get_integration_transformer(
+ self,
+ integration_name: str,
+ transformer_id: str,
+ expand: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1ALPHA,
+ ) -> dict[str, Any]:
+ """Get a single transformer definition for a specific integration.
+
+ Use this method to retrieve the Python script, input parameters,
+ and expected input, output and usage example schema for a specific
+ data transformation logic within an integration.
+
+ Args:
+ integration_name: Name of the integration the transformer
+ belongs to.
+ transformer_id: ID of the transformer to retrieve.
+ expand: Expand the response with the full transformer details.
+ Optional.
+ api_version: API version to use for the request. Default is
+ V1ALPHA.
+
+ Returns:
+ Dict containing details of the specified TransformerDefinition.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _get_integration_transformer(
+ self,
+ integration_name,
+ transformer_id,
+ expand=expand,
+ api_version=api_version,
+ )
+
+ def delete_integration_transformer(
+ self,
+ integration_name: str,
+ transformer_id: str,
+ api_version: APIVersion | None = APIVersion.V1ALPHA,
+ ) -> None:
+ """Delete a custom transformer definition from a given integration.
+
+ Use this method to permanently remove an obsolete transformer from
+ an integration.
+
+ Args:
+ integration_name: Name of the integration the transformer
+ belongs to.
+ transformer_id: ID of the transformer to delete.
+ api_version: API version to use for the request. Default is
+ V1ALPHA.
+
+ Returns:
+ None
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _delete_integration_transformer(
+ self,
+ integration_name,
+ transformer_id,
+ api_version=api_version,
+ )
+
+ def create_integration_transformer(
+ self,
+ integration_name: str,
+ display_name: str,
+ script: str,
+ script_timeout: str,
+ enabled: bool,
+ description: str | None = None,
+ parameters: list[dict[str, Any]] | None = None,
+ usage_example: str | None = None,
+ expected_output: str | None = None,
+ expected_input: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1ALPHA,
+ ) -> dict[str, Any]:
+ """Create a new transformer definition for a given integration.
+
+ Use this method to define a transformer, specifying its functional
+ Python script and necessary input parameters.
+
+ Args:
+ integration_name: Name of the integration to create the
+ transformer for.
+ display_name: Transformer's display name. Maximum 150 characters.
+ Required.
+ script: Transformer's Python script. Required.
+ script_timeout: Timeout in seconds for a single script run.
+ Default is 60. Required.
+ enabled: Whether the transformer is enabled or disabled.
+ Required.
+ description: Transformer's description. Maximum 2050 characters.
+ Optional.
+ parameters: List of transformer parameter dicts. Optional.
+ usage_example: Transformer's usage example. Optional.
+ expected_output: Transformer's expected output. Optional.
+ expected_input: Transformer's expected input. Optional.
+ api_version: API version to use for the request. Default is
+ V1ALPHA.
+
+ Returns:
+ Dict containing the newly created TransformerDefinition
+ resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _create_integration_transformer(
+ self,
+ integration_name,
+ display_name,
+ script,
+ script_timeout,
+ enabled,
+ description=description,
+ parameters=parameters,
+ usage_example=usage_example,
+ expected_output=expected_output,
+ expected_input=expected_input,
+ api_version=api_version,
+ )
+
+ def update_integration_transformer(
+ self,
+ integration_name: str,
+ transformer_id: str,
+ display_name: str | None = None,
+ script: str | None = None,
+ script_timeout: str | None = None,
+ enabled: bool | None = None,
+ description: str | None = None,
+ parameters: list[dict[str, Any]] | None = None,
+ usage_example: str | None = None,
+ expected_output: str | None = None,
+ expected_input: str | None = None,
+ update_mask: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1ALPHA,
+ ) -> dict[str, Any]:
+ """Update an existing transformer definition for a given
+ integration.
+
+ Use this method to modify a transformation's Python script, adjust
+ its description, or refine its parameter definitions.
+
+ Args:
+ integration_name: Name of the integration the transformer
+ belongs to.
+ transformer_id: ID of the transformer to update.
+ display_name: Transformer's display name. Maximum 150
+ characters.
+ script: Transformer's Python script.
+ script_timeout: Timeout in seconds for a single script run.
+ enabled: Whether the transformer is enabled or disabled.
+ description: Transformer's description. Maximum 2050 characters.
+ parameters: List of transformer parameter dicts. When updating
+ existing parameters, id must be provided in each parameter.
+ usage_example: Transformer's usage example.
+ expected_output: Transformer's expected output.
+ expected_input: Transformer's expected input.
+ update_mask: Comma-separated list of fields to update. If
+ omitted, the mask is auto-generated from whichever fields
+ are provided. Example: "displayName,script".
+ api_version: API version to use for the request. Default is
+ V1ALPHA.
+
+ Returns:
+ Dict containing the updated TransformerDefinition resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _update_integration_transformer(
+ self,
+ integration_name,
+ transformer_id,
+ display_name=display_name,
+ script=script,
+ script_timeout=script_timeout,
+ enabled=enabled,
+ description=description,
+ parameters=parameters,
+ usage_example=usage_example,
+ expected_output=expected_output,
+ expected_input=expected_input,
+ update_mask=update_mask,
+ api_version=api_version,
+ )
+
+ def execute_integration_transformer_test(
+ self,
+ integration_name: str,
+ transformer: dict[str, Any],
+ api_version: APIVersion | None = APIVersion.V1ALPHA,
+ ) -> dict[str, Any]:
+ """Execute a test run of a transformer's Python script.
+
+ Use this method to verify transformation logic and ensure data is
+ being parsed and formatted correctly before saving or deploying
+ the transformer. The full transformer object is required as the
+ test can be run without saving the transformer first.
+
+ Args:
+ integration_name: Name of the integration the transformer
+ belongs to.
+ transformer: Dict containing the TransformerDefinition to test.
+ api_version: API version to use for the request. Default is
+ V1ALPHA.
+
+ Returns:
+ Dict containing the test execution results with the following
+ fields:
+ - outputMessage: Human-readable output message set by the
+ script.
+ - debugOutputMessage: The script debug output.
+ - resultValue: The script result value.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _execute_integration_transformer_test(
+ self,
+ integration_name,
+ transformer,
+ api_version=api_version,
+ )
+
+ def get_integration_transformer_template(
+ self,
+ integration_name: str,
+ api_version: APIVersion | None = APIVersion.V1ALPHA,
+ ) -> dict[str, Any]:
+ """Retrieve a default Python script template for a new transformer.
+
+ Use this method to jumpstart the development of a custom data
+ transformation logic by providing boilerplate code.
+
+ Args:
+ integration_name: Name of the integration to fetch the template
+ for.
+ api_version: API version to use for the request. Default is
+ V1ALPHA.
+
+ Returns:
+ Dict containing the TransformerDefinition template.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _get_integration_transformer_template(
+ self,
+ integration_name,
+ api_version=api_version,
+ )
+
+ # -- Integration Transformer Revisions methods --
+
+ def list_integration_transformer_revisions(
+ self,
+ integration_name: str,
+ transformer_id: str,
+ page_size: int | None = None,
+ page_token: str | None = None,
+ filter_string: str | None = None,
+ order_by: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1ALPHA,
+ as_list: bool = False,
+ ) -> dict[str, Any] | list[dict[str, Any]]:
+ """List all revisions for a specific transformer.
+
+ Use this method to view the revision history of a transformer,
+ enabling you to track changes and potentially rollback to previous
+ versions.
+
+ Args:
+ integration_name: Name of the integration the transformer
+ belongs to.
+ transformer_id: ID of the transformer to list revisions for.
+ page_size: Maximum number of revisions to return. Defaults to
+ 100, maximum is 200.
+ page_token: Page token from a previous call to retrieve the
+ next page.
+ filter_string: Filter expression to filter revisions.
+ order_by: Field to sort the revisions by.
+ api_version: API version to use for the request. Default is
+ V1ALPHA.
+ as_list: If True, automatically fetches all pages and returns
+ a list of revisions. If False, returns dict with revisions
+ and nextPageToken.
+
+ Returns:
+ If as_list is True: List of transformer revisions.
+ If as_list is False: Dict with revisions list and nextPageToken.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _list_integration_transformer_revisions(
+ self,
+ integration_name,
+ transformer_id,
+ page_size=page_size,
+ page_token=page_token,
+ filter_string=filter_string,
+ order_by=order_by,
+ api_version=api_version,
+ as_list=as_list,
+ )
+
+ def delete_integration_transformer_revision(
+ self,
+ integration_name: str,
+ transformer_id: str,
+ revision_id: str,
+ api_version: APIVersion | None = APIVersion.V1ALPHA,
+ ) -> None:
+ """Delete a specific transformer revision.
+
+ Use this method to remove obsolete or incorrect revisions from
+ a transformer's history.
+
+ Args:
+ integration_name: Name of the integration the transformer
+ belongs to.
+ transformer_id: ID of the transformer.
+ revision_id: ID of the revision to delete.
+ api_version: API version to use for the request. Default is
+ V1ALPHA.
+
+ Returns:
+ None
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _delete_integration_transformer_revision(
+ self,
+ integration_name,
+ transformer_id,
+ revision_id,
+ api_version=api_version,
+ )
+
+ def create_integration_transformer_revision(
+ self,
+ integration_name: str,
+ transformer_id: str,
+ transformer: dict[str, Any],
+ comment: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1ALPHA,
+ ) -> dict[str, Any]:
+ """Create a new revision for a transformer.
+
+ Use this method to create a snapshot of the transformer's current
+ state before making changes, enabling you to rollback if needed.
+
+ Args:
+ integration_name: Name of the integration the transformer
+ belongs to.
+ transformer_id: ID of the transformer to create a revision for.
+ transformer: Dict containing the TransformerDefinition to save
+ as a revision.
+ comment: Optional comment describing the revision or changes.
+ api_version: API version to use for the request. Default is
+ V1ALPHA.
+
+ Returns:
+ Dict containing the newly created TransformerRevision resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _create_integration_transformer_revision(
+ self,
+ integration_name,
+ transformer_id,
+ transformer,
+ comment=comment,
+ api_version=api_version,
+ )
+
+ def rollback_integration_transformer_revision(
+ self,
+ integration_name: str,
+ transformer_id: str,
+ revision_id: str,
+ api_version: APIVersion | None = APIVersion.V1ALPHA,
+ ) -> dict[str, Any]:
+ """Rollback a transformer to a previous revision.
+
+ Use this method to restore a transformer to a previous working
+ state by rolling back to a specific revision.
+
+ Args:
+ integration_name: Name of the integration the transformer
+ belongs to.
+ transformer_id: ID of the transformer to rollback.
+ revision_id: ID of the revision to rollback to.
+ api_version: API version to use for the request. Default is
+ V1ALPHA.
+
+ Returns:
+ Dict containing the updated TransformerDefinition resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _rollback_integration_transformer_revision(
+ self,
+ integration_name,
+ transformer_id,
+ revision_id,
+ api_version=api_version,
+ )
+
+ # -- Integration Logical Operators methods --
+
+ def list_integration_logical_operators(
+ self,
+ integration_name: str,
+ page_size: int | None = None,
+ page_token: str | None = None,
+ filter_string: str | None = None,
+ order_by: str | None = None,
+ exclude_staging: bool | None = None,
+ expand: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1ALPHA,
+ as_list: bool = False,
+ ) -> dict[str, Any] | list[dict[str, Any]]:
+ """List all logical operator definitions for a specific integration.
+
+ Use this method to browse the available logical operators that can
+ be used for conditional logic in your integration workflows.
+
+ Args:
+ integration_name: Name of the integration to list logical
+ operators for.
+ page_size: Maximum number of logical operators to return.
+ Defaults to 100, maximum is 200.
+ page_token: Page token from a previous call to retrieve the
+ next page.
+ filter_string: Filter expression to filter logical operators.
+ order_by: Field to sort the logical operators by.
+ exclude_staging: Whether to exclude staging logical operators
+ from the response. By default, staging operators are included.
+ expand: Expand the response with the full logical operator
+ details.
+ api_version: API version to use for the request. Default is
+ V1ALPHA.
+ as_list: If True, automatically fetches all pages and returns
+ a list. If False, returns dict with list and nextPageToken.
+
+ Returns:
+ If as_list is True: List of logical operators.
+ If as_list is False: Dict with logicalOperators list and
+ nextPageToken.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _list_integration_logical_operators(
+ self,
+ integration_name,
+ page_size=page_size,
+ page_token=page_token,
+ filter_string=filter_string,
+ order_by=order_by,
+ exclude_staging=exclude_staging,
+ expand=expand,
+ api_version=api_version,
+ as_list=as_list,
+ )
+
+ def get_integration_logical_operator(
+ self,
+ integration_name: str,
+ logical_operator_id: str,
+ expand: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1ALPHA,
+ ) -> dict[str, Any]:
+ """Get a single logical operator definition for a specific integration.
+
+ Use this method to retrieve the Python script, input parameters,
+ and evaluation logic for a specific logical operator within an
+ integration.
+
+ Args:
+ integration_name: Name of the integration the logical operator
+ belongs to.
+ logical_operator_id: ID of the logical operator to retrieve.
+ expand: Expand the response with the full logical operator
+ details. Optional.
+ api_version: API version to use for the request. Default is
+ V1ALPHA.
+
+ Returns:
+ Dict containing details of the specified LogicalOperator
+ definition.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _get_integration_logical_operator(
+ self,
+ integration_name,
+ logical_operator_id,
+ expand=expand,
+ api_version=api_version,
+ )
+
+ def delete_integration_logical_operator(
+ self,
+ integration_name: str,
+ logical_operator_id: str,
+ api_version: APIVersion | None = APIVersion.V1ALPHA,
+ ) -> None:
+ """Delete a custom logical operator definition from a given integration.
+
+ Use this method to permanently remove an obsolete logical operator
+ from an integration.
+
+ Args:
+ integration_name: Name of the integration the logical operator
+ belongs to.
+ logical_operator_id: ID of the logical operator to delete.
+ api_version: API version to use for the request. Default is
+ V1ALPHA.
+
+ Returns:
+ None
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _delete_integration_logical_operator(
+ self,
+ integration_name,
+ logical_operator_id,
+ api_version=api_version,
+ )
+
+ def create_integration_logical_operator(
+ self,
+ integration_name: str,
+ display_name: str,
+ script: str,
+ script_timeout: str,
+ enabled: bool,
+ description: str | None = None,
+ parameters: list[dict[str, Any]] | None = None,
+ api_version: APIVersion | None = APIVersion.V1ALPHA,
+ ) -> dict[str, Any]:
+ """Create a new logical operator definition for a given integration.
+
+ Use this method to define a logical operator, specifying its
+ functional Python script and necessary input parameters for
+ conditional evaluations.
+
+ Args:
+ integration_name: Name of the integration to create the
+ logical operator for.
+ display_name: Logical operator's display name. Maximum 150
+ characters. Required.
+ script: Logical operator's Python script. Required.
+ script_timeout: Timeout in seconds for a single script run.
+ Default is 60. Required.
+ enabled: Whether the logical operator is enabled or disabled.
+ Required.
+ description: Logical operator's description. Maximum 2050
+ characters. Optional.
+ parameters: List of logical operator parameter dicts. Optional.
+ api_version: API version to use for the request. Default is
+ V1ALPHA.
+
+ Returns:
+ Dict containing the newly created LogicalOperator resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _create_integration_logical_operator(
+ self,
+ integration_name,
+ display_name,
+ script,
+ script_timeout,
+ enabled,
+ description=description,
+ parameters=parameters,
+ api_version=api_version,
+ )
+
+ def update_integration_logical_operator(
+ self,
+ integration_name: str,
+ logical_operator_id: str,
+ display_name: str | None = None,
+ script: str | None = None,
+ script_timeout: str | None = None,
+ enabled: bool | None = None,
+ description: str | None = None,
+ parameters: list[dict[str, Any]] | None = None,
+ update_mask: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1ALPHA,
+ ) -> dict[str, Any]:
+ """Update an existing logical operator definition for a given
+ integration.
+
+ Use this method to modify a logical operator's Python script,
+ adjust its description, or refine its parameter definitions.
+
+ Args:
+ integration_name: Name of the integration the logical operator
+ belongs to.
+ logical_operator_id: ID of the logical operator to update.
+ display_name: Logical operator's display name. Maximum 150
+ characters.
+ script: Logical operator's Python script.
+ script_timeout: Timeout in seconds for a single script run.
+ enabled: Whether the logical operator is enabled or disabled.
+ description: Logical operator's description. Maximum 2050
+ characters.
+ parameters: List of logical operator parameter dicts. When
+ updating existing parameters, id must be provided in each
+ parameter.
+ update_mask: Comma-separated list of fields to update. If
+ omitted, the mask is auto-generated from whichever fields
+ are provided. Example: "displayName,script".
+ api_version: API version to use for the request. Default is
+ V1ALPHA.
+
+ Returns:
+ Dict containing the updated LogicalOperator resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _update_integration_logical_operator(
+ self,
+ integration_name,
+ logical_operator_id,
+ display_name=display_name,
+ script=script,
+ script_timeout=script_timeout,
+ enabled=enabled,
+ description=description,
+ parameters=parameters,
+ update_mask=update_mask,
+ api_version=api_version,
+ )
+
+ def execute_integration_logical_operator_test(
+ self,
+ integration_name: str,
+ logical_operator: dict[str, Any],
+ api_version: APIVersion | None = APIVersion.V1ALPHA,
+ ) -> dict[str, Any]:
+ """Execute a test run of a logical operator's Python script.
+
+ Use this method to verify logical operator evaluation logic and
+ ensure conditions are being assessed correctly before saving or
+ deploying the operator. The full logical operator object is
+ required as the test can be run without saving the operator first.
+
+ Args:
+ integration_name: Name of the integration the logical operator
+ belongs to.
+ logical_operator: Dict containing the LogicalOperator
+ definition to test.
+ api_version: API version to use for the request. Default is
+ V1ALPHA.
+
+ Returns:
+ Dict containing the test execution results with the following
+ fields:
+ - outputMessage: Human-readable output message set by the
+ script.
+ - debugOutputMessage: The script debug output.
+ - resultValue: The script result value (True/False).
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _execute_integration_logical_operator_test(
+ self,
+ integration_name,
+ logical_operator,
+ api_version=api_version,
+ )
+
+ def get_integration_logical_operator_template(
+ self,
+ integration_name: str,
+ api_version: APIVersion | None = APIVersion.V1ALPHA,
+ ) -> dict[str, Any]:
+ """Retrieve a default Python script template for a new logical operator.
+
+ Use this method to jumpstart the development of a custom
+ conditional logic by providing boilerplate code.
+
+ Args:
+ integration_name: Name of the integration to fetch the template
+ for.
+ api_version: API version to use for the request. Default is
+ V1ALPHA.
+
+ Returns:
+ Dict containing the LogicalOperator template.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _get_integration_logical_operator_template(
+ self,
+ integration_name,
+ api_version=api_version,
+ )
+
+ # -- Integration Logical Operator Revisions methods --
+
+ def list_integration_logical_operator_revisions(
+ self,
+ integration_name: str,
+ logical_operator_id: str,
+ page_size: int | None = None,
+ page_token: str | None = None,
+ filter_string: str | None = None,
+ order_by: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1ALPHA,
+ as_list: bool = False,
+ ) -> dict[str, Any] | list[dict[str, Any]]:
+ """List all revisions for a specific logical operator.
+
+ Use this method to view the revision history of a logical operator,
+ enabling you to track changes and potentially rollback to previous
+ versions.
+
+ Args:
+ integration_name: Name of the integration the logical operator
+ belongs to.
+ logical_operator_id: ID of the logical operator to list
+ revisions for.
+ page_size: Maximum number of revisions to return. Defaults to
+ 100, maximum is 200.
+ page_token: Page token from a previous call to retrieve the
+ next page.
+ filter_string: Filter expression to filter revisions.
+ order_by: Field to sort the revisions by.
+ api_version: API version to use for the request. Default is
+ V1ALPHA.
+ as_list: If True, automatically fetches all pages and returns
+ a list of revisions. If False, returns dict with revisions
+ and nextPageToken.
+
+ Returns:
+ If as_list is True: List of logical operator revisions.
+ If as_list is False: Dict with revisions list and nextPageToken.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _list_integration_logical_operator_revisions(
+ self,
+ integration_name,
+ logical_operator_id,
+ page_size=page_size,
+ page_token=page_token,
+ filter_string=filter_string,
+ order_by=order_by,
+ api_version=api_version,
+ as_list=as_list,
+ )
+
+ def delete_integration_logical_operator_revision(
+ self,
+ integration_name: str,
+ logical_operator_id: str,
+ revision_id: str,
+ api_version: APIVersion | None = APIVersion.V1ALPHA,
+ ) -> None:
+ """Delete a specific logical operator revision.
+
+ Use this method to remove obsolete or incorrect revisions from
+ a logical operator's history.
+
+ Args:
+ integration_name: Name of the integration the logical operator
+ belongs to.
+ logical_operator_id: ID of the logical operator.
+ revision_id: ID of the revision to delete.
+ api_version: API version to use for the request. Default is
+ V1ALPHA.
+
+ Returns:
+ None
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _delete_integration_logical_operator_revision(
+ self,
+ integration_name,
+ logical_operator_id,
+ revision_id,
+ api_version=api_version,
+ )
+
+ def create_integration_logical_operator_revision(
+ self,
+ integration_name: str,
+ logical_operator_id: str,
+ logical_operator: dict[str, Any],
+ comment: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1ALPHA,
+ ) -> dict[str, Any]:
+ """Create a new revision for a logical operator.
+
+ Use this method to create a snapshot of the logical operator's
+ current state before making changes, enabling you to rollback if
+ needed.
+
+ Args:
+ integration_name: Name of the integration the logical operator
+ belongs to.
+ logical_operator_id: ID of the logical operator to create a
+ revision for.
+ logical_operator: Dict containing the LogicalOperator
+ definition to save as a revision.
+ comment: Optional comment describing the revision or changes.
+ api_version: API version to use for the request. Default is
+ V1ALPHA.
+
+ Returns:
+ Dict containing the newly created LogicalOperatorRevision
+ resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _create_integration_logical_operator_revision(
+ self,
+ integration_name,
+ logical_operator_id,
+ logical_operator,
+ comment=comment,
+ api_version=api_version,
+ )
+
+ def rollback_integration_logical_operator_revision(
+ self,
+ integration_name: str,
+ logical_operator_id: str,
+ revision_id: str,
+ api_version: APIVersion | None = APIVersion.V1ALPHA,
+ ) -> dict[str, Any]:
+ """Rollback a logical operator to a previous revision.
+
+ Use this method to restore a logical operator to a previous
+ working state by rolling back to a specific revision.
+
+ Args:
+ integration_name: Name of the integration the logical operator
+ belongs to.
+ logical_operator_id: ID of the logical operator to rollback.
+ revision_id: ID of the revision to rollback to.
+ api_version: API version to use for the request. Default is
+ V1ALPHA.
+
+ Returns:
+ Dict containing the updated LogicalOperator resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return _rollback_integration_logical_operator_revision(
+ self,
+ integration_name,
+ logical_operator_id,
+ revision_id,
+ api_version=api_version,
+ )
+
def get_stats(
self,
query: str,
diff --git a/src/secops/chronicle/entity.py b/src/secops/chronicle/entity.py
index 429d4393..84e5060a 100644
--- a/src/secops/chronicle/entity.py
+++ b/src/secops/chronicle/entity.py
@@ -15,6 +15,7 @@
"""
Provides entity search, analysis and summarization functionality for Chronicle.
"""
+
import ipaddress
import re
from datetime import datetime
diff --git a/src/secops/chronicle/feeds.py b/src/secops/chronicle/feeds.py
index b9ed7f22..8030b753 100644
--- a/src/secops/chronicle/feeds.py
+++ b/src/secops/chronicle/feeds.py
@@ -15,6 +15,7 @@
"""
Provides ingestion feed management functionality for Chronicle.
"""
+
import json
import os
import sys
diff --git a/src/secops/chronicle/gemini.py b/src/secops/chronicle/gemini.py
index abed52cb..eee42374 100644
--- a/src/secops/chronicle/gemini.py
+++ b/src/secops/chronicle/gemini.py
@@ -16,6 +16,7 @@
Provides access to Chronicle's Gemini conversational AI interface.
"""
+
import re
from typing import Any
diff --git a/src/secops/chronicle/integration/__init__.py b/src/secops/chronicle/integration/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/src/secops/chronicle/integration/action_revisions.py b/src/secops/chronicle/integration/action_revisions.py
new file mode 100644
index 00000000..b5229b3c
--- /dev/null
+++ b/src/secops/chronicle/integration/action_revisions.py
@@ -0,0 +1,201 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Integration action revisions functionality for Chronicle."""
+
+from typing import Any, TYPE_CHECKING
+
+from secops.chronicle.models import APIVersion
+from secops.chronicle.utils.format_utils import format_resource_id
+from secops.chronicle.utils.request_utils import (
+ chronicle_paginated_request,
+ chronicle_request,
+)
+
+if TYPE_CHECKING:
+ from secops.chronicle.client import ChronicleClient
+
+
+def list_integration_action_revisions(
+ client: "ChronicleClient",
+ integration_name: str,
+ action_id: str,
+ page_size: int | None = None,
+ page_token: str | None = None,
+ filter_string: str | None = None,
+ order_by: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ as_list: bool = False,
+) -> dict[str, Any] | list[dict[str, Any]]:
+ """List all revisions for a specific integration action.
+
+ Use this method to browse the version history and identify previous
+ configurations of an automated task.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the action belongs to.
+ action_id: ID of the action to list revisions for.
+ page_size: Maximum number of revisions to return.
+ page_token: Page token from a previous call to retrieve the next page.
+ filter_string: Filter expression to filter revisions.
+ order_by: Field to sort the revisions by.
+ api_version: API version to use for the request. Default is V1BETA.
+ as_list: If True, return a list of revisions instead of a dict with
+ revisions list and nextPageToken.
+
+ Returns:
+ If as_list is True: List of revisions.
+ If as_list is False: Dict with revisions list and nextPageToken.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ extra_params = {
+ "filter": filter_string,
+ "orderBy": order_by,
+ }
+
+ # Remove keys with None values
+ extra_params = {k: v for k, v in extra_params.items() if v is not None}
+
+ return chronicle_paginated_request(
+ client,
+ api_version=api_version,
+ path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"actions/{action_id}/revisions"
+ ),
+ items_key="revisions",
+ page_size=page_size,
+ page_token=page_token,
+ extra_params=extra_params,
+ as_list=as_list,
+ )
+
+
+def delete_integration_action_revision(
+ client: "ChronicleClient",
+ integration_name: str,
+ action_id: str,
+ revision_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> None:
+ """Delete a specific revision for a given integration action.
+
+ Use this method to clean up obsolete action revisions.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the action belongs to.
+ action_id: ID of the action the revision belongs to.
+ revision_id: ID of the revision to delete.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ None
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ chronicle_request(
+ client,
+ method="DELETE",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"actions/{action_id}/revisions/{revision_id}"
+ ),
+ api_version=api_version,
+ )
+
+
+def create_integration_action_revision(
+ client: "ChronicleClient",
+ integration_name: str,
+ action_id: str,
+ action: dict[str, Any],
+ comment: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Create a new revision snapshot of the current integration action.
+
+ Use this method to establish a recovery point before making significant
+ changes to a security operation's script or parameters.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the action belongs to.
+ action_id: ID of the action to create a revision for.
+ action: Dict containing the IntegrationAction to snapshot.
+ comment: Comment describing the revision. Maximum 400 characters.
+ Optional.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the newly created IntegrationActionRevision resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ body = {"action": action}
+
+ if comment is not None:
+ body["comment"] = comment
+
+ return chronicle_request(
+ client,
+ method="POST",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"actions/{action_id}/revisions"
+ ),
+ api_version=api_version,
+ json=body,
+ )
+
+
+def rollback_integration_action_revision(
+ client: "ChronicleClient",
+ integration_name: str,
+ action_id: str,
+ revision_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Revert the current action definition to a previously saved revision.
+
+ Use this method to rapidly recover a functional automation state if an
+ update causes operational issues.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the action belongs to.
+ action_id: ID of the action to rollback.
+ revision_id: ID of the revision to rollback to.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the IntegrationActionRevision rolled back to.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return chronicle_request(
+ client,
+ method="POST",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"actions/{action_id}/revisions/{revision_id}:rollback"
+ ),
+ api_version=api_version,
+ )
diff --git a/src/secops/chronicle/integration/actions.py b/src/secops/chronicle/integration/actions.py
new file mode 100644
index 00000000..d52ba28b
--- /dev/null
+++ b/src/secops/chronicle/integration/actions.py
@@ -0,0 +1,452 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Integration actions functionality for Chronicle."""
+
+from typing import Any, TYPE_CHECKING
+
+from secops.chronicle.models import APIVersion, ActionParameter
+from secops.chronicle.utils.format_utils import (
+ format_resource_id,
+ build_patch_body,
+)
+from secops.chronicle.utils.request_utils import (
+ chronicle_paginated_request,
+ chronicle_request,
+)
+
+if TYPE_CHECKING:
+ from secops.chronicle.client import ChronicleClient
+
+
+def list_integration_actions(
+ client: "ChronicleClient",
+ integration_name: str,
+ page_size: int | None = None,
+ page_token: str | None = None,
+ filter_string: str | None = None,
+ order_by: str | None = None,
+ expand: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ as_list: bool = False,
+) -> dict[str, Any] | list[dict[str, Any]]:
+ """Get a list of actions for a given integration.
+
+ Args:
+ client: ChronicleClient instance
+ integration_name: Name of the integration to get actions for
+ page_size: Number of results to return per page
+ page_token: Token for the page to retrieve
+ filter_string: Filter expression to filter actions
+ order_by: Field to sort the actions by
+ expand: Comma-separated list of fields to expand in the response
+ api_version: API version to use for the request. Default is V1BETA.
+ as_list: If True, return a list of actions instead of a dict with
+ actions list and nextPageToken.
+
+ Returns:
+ If as_list is True: List of actions.
+ If as_list is False: Dict with actions list and nextPageToken.
+
+ Raises:
+ APIError: If the API request fails
+ """
+ field_map = {
+ "filter": filter_string,
+ "orderBy": order_by,
+ "expand": expand,
+ }
+
+ # Remove keys with None values
+ field_map = {k: v for k, v in field_map.items() if v is not None}
+
+ return chronicle_paginated_request(
+ client,
+ api_version=api_version,
+ path=f"integrations/{format_resource_id(integration_name)}/actions",
+ items_key="actions",
+ page_size=page_size,
+ page_token=page_token,
+ extra_params=field_map,
+ as_list=as_list,
+ )
+
+
+def get_integration_action(
+ client: "ChronicleClient",
+ integration_name: str,
+ action_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Get details of a specific action for a given integration.
+
+ Args:
+ client: ChronicleClient instance
+ integration_name: Name of the integration the action belongs to
+ action_id: ID of the action to retrieve
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing details of the specified action.
+
+ Raises:
+ APIError: If the API request fails
+ """
+ return chronicle_request(
+ client,
+ method="GET",
+ endpoint_path=f"integrations/{format_resource_id(integration_name)}"
+ f"/actions/{action_id}",
+ api_version=api_version,
+ )
+
+
+def delete_integration_action(
+ client: "ChronicleClient",
+ integration_name: str,
+ action_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> None:
+ """Delete a specific action from a given integration.
+
+ Args:
+ client: ChronicleClient instance
+ integration_name: Name of the integration the action belongs to
+ action_id: ID of the action to delete
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ None
+
+ Raises:
+ APIError: If the API request fails
+ """
+ chronicle_request(
+ client,
+ method="DELETE",
+ endpoint_path=f"integrations/{format_resource_id(integration_name)}"
+ f"/actions/{action_id}",
+ api_version=api_version,
+ )
+
+
+def create_integration_action(
+ client: "ChronicleClient",
+ integration_name: str,
+ display_name: str,
+ script: str,
+ timeout_seconds: int,
+ enabled: bool,
+ script_result_name: str,
+ is_async: bool,
+ description: str | None = None,
+ default_result_value: str | None = None,
+ async_polling_interval_seconds: int | None = None,
+ async_total_timeout_seconds: int | None = None,
+ dynamic_results: list[dict[str, Any]] | None = None,
+ parameters: list[dict[str, Any] | ActionParameter] | None = None,
+ ai_generated: bool | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Create a new custom action for a given integration.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration to create the action for.
+ display_name: Action's display name. Maximum 150 characters. Required.
+ script: Action's Python script. Maximum size 5MB. Required.
+ timeout_seconds: Action timeout in seconds. Maximum 1200. Required.
+ enabled: Whether the action is enabled or disabled. Required.
+ script_result_name: Field name that holds the script result.
+ Maximum 100 characters. Required.
+ is_async: Whether the action is asynchronous. Required.
+ description: Action's description. Maximum 400 characters. Optional.
+ default_result_value: Action's default result value.
+ Maximum 1000 characters. Optional.
+ async_polling_interval_seconds: Polling interval in seconds for async
+ actions. Cannot exceed total timeout. Optional.
+ async_total_timeout_seconds: Total async timeout in seconds.
+ Maximum 1209600 (14 days). Optional.
+ dynamic_results: List of dynamic result metadata dicts.
+ Max 50. Optional.
+ parameters: List of ActionParameter instances or dicts.
+ Max 50. Optional.
+ ai_generated: Whether the action was generated by AI. Optional.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the newly created IntegrationAction resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ resolved_parameters = (
+ [
+ p.to_dict() if isinstance(p, ActionParameter) else p
+ for p in parameters
+ ]
+ if parameters is not None
+ else None
+ )
+
+ body = {
+ "displayName": display_name,
+ "script": script,
+ "timeoutSeconds": timeout_seconds,
+ "enabled": enabled,
+ "scriptResultName": script_result_name,
+ "async": is_async,
+ "description": description,
+ "defaultResultValue": default_result_value,
+ "asyncPollingIntervalSeconds": async_polling_interval_seconds,
+ "asyncTotalTimeoutSeconds": async_total_timeout_seconds,
+ "dynamicResults": dynamic_results,
+ "parameters": resolved_parameters,
+ "aiGenerated": ai_generated,
+ }
+
+ # Remove keys with None values
+ body = {k: v for k, v in body.items() if v is not None}
+
+ return chronicle_request(
+ client,
+ method="POST",
+ endpoint_path=f"integrations/{format_resource_id(integration_name)}"
+ f"/actions",
+ api_version=api_version,
+ json=body,
+ )
+
+
+def update_integration_action(
+ client: "ChronicleClient",
+ integration_name: str,
+ action_id: str,
+ display_name: str | None = None,
+ script: str | None = None,
+ timeout_seconds: int | None = None,
+ enabled: bool | None = None,
+ script_result_name: str | None = None,
+ is_async: bool | None = None,
+ description: str | None = None,
+ default_result_value: str | None = None,
+ async_polling_interval_seconds: int | None = None,
+ async_total_timeout_seconds: int | None = None,
+ dynamic_results: list[dict[str, Any]] | None = None,
+ parameters: list[dict[str, Any] | ActionParameter] | None = None,
+ ai_generated: bool | None = None,
+ update_mask: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Update an existing custom action for a given integration.
+
+ Only custom actions can be updated; predefined commercial actions are
+ immutable.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the action belongs to.
+ action_id: ID of the action to update.
+ display_name: Action's display name. Maximum 150 characters.
+ script: Action's Python script. Maximum size 5MB.
+ timeout_seconds: Action timeout in seconds. Maximum 1200.
+ enabled: Whether the action is enabled or disabled.
+ script_result_name: Field name that holds the script result.
+ Maximum 100 characters.
+ is_async: Whether the action is asynchronous.
+ description: Action's description. Maximum 400 characters.
+ default_result_value: Action's default result value.
+ Maximum 1000 characters.
+ async_polling_interval_seconds: Polling interval in seconds for async
+ actions. Cannot exceed total timeout.
+ async_total_timeout_seconds: Total async timeout in seconds. Maximum
+ 1209600 (14 days).
+ dynamic_results: List of dynamic result metadata dicts. Max 50.
+ parameters: List of ActionParameter instances or dicts.
+ Max 50. Optional.
+ ai_generated: Whether the action was generated by AI.
+ update_mask: Comma-separated list of fields to update. If omitted,
+ the mask is auto-generated from whichever fields are provided.
+ Example: "displayName,script".
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the updated IntegrationAction resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ body, params = build_patch_body(
+ field_map=[
+ ("displayName", "displayName", display_name),
+ ("script", "script", script),
+ ("timeoutSeconds", "timeoutSeconds", timeout_seconds),
+ ("enabled", "enabled", enabled),
+ ("scriptResultName", "scriptResultName", script_result_name),
+ ("async", "async", is_async),
+ ("description", "description", description),
+ ("defaultResultValue", "defaultResultValue", default_result_value),
+ (
+ "asyncPollingIntervalSeconds",
+ "asyncPollingIntervalSeconds",
+ async_polling_interval_seconds,
+ ),
+ (
+ "asyncTotalTimeoutSeconds",
+ "asyncTotalTimeoutSeconds",
+ async_total_timeout_seconds,
+ ),
+ ("dynamicResults", "dynamicResults", dynamic_results),
+ ("parameters", "parameters", parameters),
+ ("aiGenerated", "aiGenerated", ai_generated),
+ ],
+ update_mask=update_mask,
+ )
+
+ return chronicle_request(
+ client,
+ method="PATCH",
+ endpoint_path=f"integrations/{format_resource_id(integration_name)}"
+ f"/actions/{action_id}",
+ api_version=api_version,
+ json=body,
+ params=params,
+ )
+
+
+def execute_integration_action_test(
+ client: "ChronicleClient",
+ integration_name: str,
+ test_case_id: int,
+ action: dict[str, Any],
+ scope: str,
+ integration_instance_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Execute a test run of an integration action's script.
+
+ Use this method to verify custom action logic, connectivity, and data
+ parsing against a specified integration instance and test case before
+ making the action available in playbooks.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the action belongs to.
+ test_case_id: ID of the action test case.
+ action: Dict containing the IntegrationAction to test.
+ scope: The action test scope.
+ integration_instance_id: The integration instance ID to use.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the test execution results with the following fields:
+ - output: The script output.
+ - debugOutput: The script debug output.
+ - resultJson: The result JSON if it exists (optional).
+ - resultName: The script result name (optional).
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ body = {
+ "testCaseId": test_case_id,
+ "action": action,
+ "scope": scope,
+ "integrationInstanceId": integration_instance_id,
+ }
+
+ return chronicle_request(
+ client,
+ method="POST",
+ endpoint_path=f"integrations/{format_resource_id(integration_name)}"
+ f"/actions:executeTest",
+ api_version=api_version,
+ json=body,
+ )
+
+
+def get_integration_actions_by_environment(
+ client: "ChronicleClient",
+ integration_name: str,
+ environments: list[str],
+ include_widgets: bool,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """List actions executable within specified environments.
+
+ Use this method to discover which automated tasks have active integration
+ instances configured for a particular network or organizational context.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration to fetch actions for.
+ environments: List of environments to filter actions by.
+ include_widgets: Whether to include widget actions in the response.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing a list of IntegrationAction objects that have
+ integration instances in one of the given environments.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ params = {
+ "environments": environments,
+ "includeWidgets": include_widgets,
+ }
+
+ return chronicle_request(
+ client,
+ method="GET",
+ endpoint_path=f"integrations/{format_resource_id(integration_name)}"
+ f"/actions:fetchActionsByEnvironment",
+ api_version=api_version,
+ params=params,
+ )
+
+
+def get_integration_action_template(
+ client: "ChronicleClient",
+ integration_name: str,
+ is_async: bool = False,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Retrieve a default Python script template for a new integration action.
+
+ Use this method to jumpstart the development of a custom automated task
+ by providing boilerplate code for either synchronous or asynchronous
+ operations.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration to fetch the template for.
+ is_async: Whether to fetch a template for an async action. Default
+ is False.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the IntegrationAction template.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return chronicle_request(
+ client,
+ method="GET",
+ endpoint_path=f"integrations/{format_resource_id(integration_name)}"
+ f"/actions:fetchTemplate",
+ api_version=api_version,
+ params={"async": is_async},
+ )
diff --git a/src/secops/chronicle/integration/connector_context_properties.py b/src/secops/chronicle/integration/connector_context_properties.py
new file mode 100644
index 00000000..24e59f66
--- /dev/null
+++ b/src/secops/chronicle/integration/connector_context_properties.py
@@ -0,0 +1,299 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Integration connector context properties functionality for Chronicle."""
+
+from typing import Any, TYPE_CHECKING
+
+from secops.chronicle.models import APIVersion
+from secops.chronicle.utils.format_utils import (
+ format_resource_id,
+ build_patch_body,
+)
+from secops.chronicle.utils.request_utils import (
+ chronicle_paginated_request,
+ chronicle_request,
+)
+
+if TYPE_CHECKING:
+ from secops.chronicle.client import ChronicleClient
+
+
+def list_connector_context_properties(
+ client: "ChronicleClient",
+ integration_name: str,
+ connector_id: str,
+ page_size: int | None = None,
+ page_token: str | None = None,
+ filter_string: str | None = None,
+ order_by: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ as_list: bool = False,
+) -> dict[str, Any] | list[dict[str, Any]]:
+ """List all context properties for a specific integration connector.
+
+ Use this method to discover all custom data points associated with a
+ connector.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the connector belongs to.
+ connector_id: ID of the connector to list context properties for.
+ page_size: Maximum number of context properties to return.
+ page_token: Page token from a previous call to retrieve the next page.
+ filter_string: Filter expression to filter context properties.
+ order_by: Field to sort the context properties by.
+ api_version: API version to use for the request. Default is V1BETA.
+ as_list: If True, return a list of context properties instead of a
+ dict with context properties list and nextPageToken.
+
+ Returns:
+ If as_list is True: List of context properties.
+ If as_list is False: Dict with context properties list and
+ nextPageToken.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ extra_params = {
+ "filter": filter_string,
+ "orderBy": order_by,
+ }
+
+ # Remove keys with None values
+ extra_params = {k: v for k, v in extra_params.items() if v is not None}
+
+ return chronicle_paginated_request(
+ client,
+ api_version=api_version,
+ path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"connectors/{connector_id}/contextProperties"
+ ),
+ items_key="contextProperties",
+ page_size=page_size,
+ page_token=page_token,
+ extra_params=extra_params,
+ as_list=as_list,
+ )
+
+
+def get_connector_context_property(
+ client: "ChronicleClient",
+ integration_name: str,
+ connector_id: str,
+ context_property_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Get a single context property for a specific integration connector.
+
+ Use this method to retrieve the value of a specific key within a
+ connector's context.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the connector belongs to.
+ connector_id: ID of the connector the context property belongs to.
+ context_property_id: ID of the context property to retrieve.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing details of the specified ContextProperty.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return chronicle_request(
+ client,
+ method="GET",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"connectors/{connector_id}/contextProperties/{context_property_id}"
+ ),
+ api_version=api_version,
+ )
+
+
+def delete_connector_context_property(
+ client: "ChronicleClient",
+ integration_name: str,
+ connector_id: str,
+ context_property_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> None:
+ """Delete a specific context property for a given integration connector.
+
+ Use this method to remove a custom data point that is no longer relevant
+ to the connector's context.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the connector belongs to.
+ connector_id: ID of the connector the context property belongs to.
+ context_property_id: ID of the context property to delete.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ None
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ chronicle_request(
+ client,
+ method="DELETE",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"connectors/{connector_id}/contextProperties/{context_property_id}"
+ ),
+ api_version=api_version,
+ )
+
+
+def create_connector_context_property(
+ client: "ChronicleClient",
+ integration_name: str,
+ connector_id: str,
+ value: str,
+ key: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Create a new context property for a specific integration connector.
+
+ Use this method to attach custom data to a connector's context. Property
+ keys must be unique within their context. Key values must be 4-63
+ characters and match /[a-z][0-9]-/.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the connector belongs to.
+ connector_id: ID of the connector to create the context property for.
+ value: The property value. Required.
+ key: The context property ID to use. Must be 4-63 characters and
+ match /[a-z][0-9]-/. Optional.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the newly created ContextProperty resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ body = {"value": value}
+
+ if key is not None:
+ body["key"] = key
+
+ return chronicle_request(
+ client,
+ method="POST",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"connectors/{connector_id}/contextProperties"
+ ),
+ api_version=api_version,
+ json=body,
+ )
+
+
+def update_connector_context_property(
+ client: "ChronicleClient",
+ integration_name: str,
+ connector_id: str,
+ context_property_id: str,
+ value: str,
+ update_mask: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Update an existing context property for a given integration connector.
+
+ Use this method to modify the value of a previously saved key.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the connector belongs to.
+ connector_id: ID of the connector the context property belongs to.
+ context_property_id: ID of the context property to update.
+ value: The new property value. Required.
+ update_mask: Comma-separated list of fields to update. Only "value"
+ is supported. If omitted, defaults to "value".
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the updated ContextProperty resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ body, params = build_patch_body(
+ field_map=[
+ ("value", "value", value),
+ ],
+ update_mask=update_mask,
+ )
+
+ return chronicle_request(
+ client,
+ method="PATCH",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"connectors/{connector_id}/contextProperties/{context_property_id}"
+ ),
+ api_version=api_version,
+ json=body,
+ params=params,
+ )
+
+
+def delete_all_connector_context_properties(
+ client: "ChronicleClient",
+ integration_name: str,
+ connector_id: str,
+ context_id: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> None:
+ """Delete all context properties for a specific integration connector.
+
+ Use this method to quickly clear all supplemental data from a connector's
+ context.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the connector belongs to.
+ connector_id: ID of the connector to clear context properties from.
+ context_id: The context ID to remove context properties from. Must be
+ 4-63 characters and match /[a-z][0-9]-/. Optional.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ None
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ body = {}
+
+ if context_id is not None:
+ body["contextId"] = context_id
+
+ chronicle_request(
+ client,
+ method="POST",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"connectors/{connector_id}/contextProperties:clearAll"
+ ),
+ api_version=api_version,
+ json=body,
+ )
diff --git a/src/secops/chronicle/integration/connector_instance_logs.py b/src/secops/chronicle/integration/connector_instance_logs.py
new file mode 100644
index 00000000..0be7bd25
--- /dev/null
+++ b/src/secops/chronicle/integration/connector_instance_logs.py
@@ -0,0 +1,130 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Integration connector instance logs functionality for Chronicle."""
+
+from typing import Any, TYPE_CHECKING
+
+from secops.chronicle.models import APIVersion
+from secops.chronicle.utils.format_utils import format_resource_id
+from secops.chronicle.utils.request_utils import (
+ chronicle_paginated_request,
+ chronicle_request,
+)
+
+if TYPE_CHECKING:
+ from secops.chronicle.client import ChronicleClient
+
+
+def list_connector_instance_logs(
+ client: "ChronicleClient",
+ integration_name: str,
+ connector_id: str,
+ connector_instance_id: str,
+ page_size: int | None = None,
+ page_token: str | None = None,
+ filter_string: str | None = None,
+ order_by: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ as_list: bool = False,
+) -> dict[str, Any] | list[dict[str, Any]]:
+ """List all logs for a specific connector instance.
+
+ Use this method to browse the execution history and diagnostic output of
+ a connector. Supports filtering and pagination to efficiently navigate
+ large volumes of log data.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the connector belongs to.
+ connector_id: ID of the connector the instance belongs to.
+ connector_instance_id: ID of the connector instance to list logs for.
+ page_size: Maximum number of logs to return.
+ page_token: Page token from a previous call to retrieve the next page.
+ filter_string: Filter expression to filter logs.
+ order_by: Field to sort the logs by.
+ api_version: API version to use for the request. Default is V1BETA.
+ as_list: If True, return a list of logs instead of a dict with logs
+ list and nextPageToken.
+
+ Returns:
+ If as_list is True: List of logs.
+ If as_list is False: Dict with logs list and nextPageToken.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ extra_params = {
+ "filter": filter_string,
+ "orderBy": order_by,
+ }
+
+ # Remove keys with None values
+ extra_params = {k: v for k, v in extra_params.items() if v is not None}
+
+ return chronicle_paginated_request(
+ client,
+ api_version=api_version,
+ path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"connectors/{connector_id}/connectorInstances/"
+ f"{connector_instance_id}/logs"
+ ),
+ items_key="logs",
+ page_size=page_size,
+ page_token=page_token,
+ extra_params=extra_params,
+ as_list=as_list,
+ )
+
+
+def get_connector_instance_log(
+ client: "ChronicleClient",
+ integration_name: str,
+ connector_id: str,
+ connector_instance_id: str,
+ log_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Get a single log entry for a specific connector instance.
+
+ Use this method to retrieve a specific log entry from a connector
+ instance's execution, including its message, timestamp, and severity
+ level. Useful for auditing and detailed troubleshooting of a specific
+ connector run.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the connector belongs to.
+ connector_id: ID of the connector the instance belongs to.
+ connector_instance_id: ID of the connector instance the log belongs to.
+ log_id: ID of the log entry to retrieve.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing details of the specified ConnectorLog.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return chronicle_request(
+ client,
+ method="GET",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"connectors/{connector_id}/connectorInstances/"
+ f"{connector_instance_id}/logs/{log_id}"
+ ),
+ api_version=api_version,
+ )
diff --git a/src/secops/chronicle/integration/connector_instances.py b/src/secops/chronicle/integration/connector_instances.py
new file mode 100644
index 00000000..c6b563cc
--- /dev/null
+++ b/src/secops/chronicle/integration/connector_instances.py
@@ -0,0 +1,489 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Integration connector instances functionality for Chronicle."""
+
+from typing import Any, TYPE_CHECKING
+
+from secops.chronicle.models import (
+ APIVersion,
+ ConnectorInstanceParameter,
+)
+from secops.chronicle.utils.format_utils import (
+ format_resource_id,
+ build_patch_body,
+)
+from secops.chronicle.utils.request_utils import (
+ chronicle_paginated_request,
+ chronicle_request,
+)
+
+if TYPE_CHECKING:
+ from secops.chronicle.client import ChronicleClient
+
+
+def list_connector_instances(
+ client: "ChronicleClient",
+ integration_name: str,
+ connector_id: str,
+ page_size: int | None = None,
+ page_token: str | None = None,
+ filter_string: str | None = None,
+ order_by: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ as_list: bool = False,
+) -> dict[str, Any] | list[dict[str, Any]]:
+ """List all instances for a specific integration connector.
+
+ Use this method to discover all configured instances of a connector.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the connector belongs to.
+ connector_id: ID of the connector to list instances for.
+ page_size: Maximum number of connector instances to return.
+ page_token: Page token from a previous call to retrieve the next page.
+ filter_string: Filter expression to filter connector instances.
+ order_by: Field to sort the connector instances by.
+ api_version: API version to use for the request. Default is V1BETA.
+ as_list: If True, return a list of connector instances instead of a
+ dict with connector instances list and nextPageToken.
+
+ Returns:
+ If as_list is True: List of connector instances.
+ If as_list is False: Dict with connector instances list and
+ nextPageToken.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ extra_params = {
+ "filter": filter_string,
+ "orderBy": order_by,
+ }
+
+ # Remove keys with None values
+ extra_params = {k: v for k, v in extra_params.items() if v is not None}
+
+ return chronicle_paginated_request(
+ client,
+ api_version=api_version,
+ path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"connectors/{connector_id}/connectorInstances"
+ ),
+ items_key="connectorInstances",
+ page_size=page_size,
+ page_token=page_token,
+ extra_params=extra_params,
+ as_list=as_list,
+ )
+
+
+def get_connector_instance(
+ client: "ChronicleClient",
+ integration_name: str,
+ connector_id: str,
+ connector_instance_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Get a single instance for a specific integration connector.
+
+ Use this method to retrieve the configuration and status of a specific
+ connector instance.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the connector belongs to.
+ connector_id: ID of the connector the instance belongs to.
+ connector_instance_id: ID of the connector instance to retrieve.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing details of the specified ConnectorInstance.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return chronicle_request(
+ client,
+ method="GET",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"connectors/{connector_id}/connectorInstances/"
+ f"{connector_instance_id}"
+ ),
+ api_version=api_version,
+ )
+
+
+def delete_connector_instance(
+ client: "ChronicleClient",
+ integration_name: str,
+ connector_id: str,
+ connector_instance_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> None:
+ """Delete a specific connector instance.
+
+ Use this method to permanently remove a data ingestion stream. For remote
+ connectors, the associated agent must be live and have no pending packages.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the connector belongs to.
+ connector_id: ID of the connector the instance belongs to.
+ connector_instance_id: ID of the connector instance to delete.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ None
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ chronicle_request(
+ client,
+ method="DELETE",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"connectors/{connector_id}/connectorInstances/"
+ f"{connector_instance_id}"
+ ),
+ api_version=api_version,
+ )
+
+
+def create_connector_instance(
+ client: "ChronicleClient",
+ integration_name: str,
+ connector_id: str,
+ environment: str,
+ display_name: str,
+ interval_seconds: int,
+ timeout_seconds: int,
+ description: str | None = None,
+ agent: str | None = None,
+ allow_list: list[str] | None = None,
+ product_field_name: str | None = None,
+ event_field_name: str | None = None,
+ integration_version: str | None = None,
+ version: str | None = None,
+ logging_enabled_until_unix_ms: str | None = None,
+ parameters: list[dict[str, Any] | ConnectorInstanceParameter] | None = None,
+ connector_instance_id: str | None = None,
+ enabled: bool | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Create a new connector instance for a specific integration connector.
+
+ Use this method to establish a new data ingestion stream from a security
+ product. Note that agent and remote cannot be patched after creation.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the connector belongs to.
+ connector_id: ID of the connector to create an instance for.
+ environment: Connector instance environment. Cannot be patched for
+ remote connectors. Required.
+ display_name: Connector instance display name. Required.
+ interval_seconds: Connector instance execution interval in seconds.
+ Required.
+ timeout_seconds: Timeout of a single Python script run. Required.
+ description: Connector instance description. Optional.
+ agent: Agent identifier for a remote connector instance. Cannot be
+ patched after creation. Optional.
+ allow_list: Connector instance allow list. Optional.
+ product_field_name: Connector's device product field. Optional.
+ event_field_name: Connector's event name field. Optional.
+ integration_version: The integration version. Optional.
+ version: The connector instance version. Optional.
+ logging_enabled_until_unix_ms: Timeout when log collecting will be
+ disabled. Optional.
+ parameters: List of ConnectorInstanceParameter instances or dicts.
+ Optional.
+ connector_instance_id: The connector instance id. Optional.
+ enabled: Whether the connector instance is enabled. Optional.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the newly created ConnectorInstance resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ resolved_parameters = (
+ [
+ p.to_dict() if isinstance(p, ConnectorInstanceParameter) else p
+ for p in parameters
+ ]
+ if parameters is not None
+ else None
+ )
+
+ body = {
+ "environment": environment,
+ "displayName": display_name,
+ "intervalSeconds": interval_seconds,
+ "timeoutSeconds": timeout_seconds,
+ "description": description,
+ "agent": agent,
+ "allowList": allow_list,
+ "productFieldName": product_field_name,
+ "eventFieldName": event_field_name,
+ "integrationVersion": integration_version,
+ "version": version,
+ "loggingEnabledUntilUnixMs": logging_enabled_until_unix_ms,
+ "parameters": resolved_parameters,
+ "id": connector_instance_id,
+ "enabled": enabled,
+ }
+
+ # Remove keys with None values
+ body = {k: v for k, v in body.items() if v is not None}
+
+ return chronicle_request(
+ client,
+ method="POST",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"connectors/{connector_id}/connectorInstances"
+ ),
+ api_version=api_version,
+ json=body,
+ )
+
+
+def update_connector_instance(
+ client: "ChronicleClient",
+ integration_name: str,
+ connector_id: str,
+ connector_instance_id: str,
+ display_name: str | None = None,
+ description: str | None = None,
+ interval_seconds: int | None = None,
+ timeout_seconds: int | None = None,
+ allow_list: list[str] | None = None,
+ product_field_name: str | None = None,
+ event_field_name: str | None = None,
+ integration_version: str | None = None,
+ version: str | None = None,
+ logging_enabled_until_unix_ms: str | None = None,
+ parameters: list[dict[str, Any] | ConnectorInstanceParameter] | None = None,
+ enabled: bool | None = None,
+ update_mask: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Update an existing connector instance.
+
+ Use this method to enable or disable a connector, change its display
+ name, or adjust its ingestion parameters. Note that agent, remote, and
+ environment cannot be patched after creation.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the connector belongs to.
+ connector_id: ID of the connector the instance belongs to.
+ connector_instance_id: ID of the connector instance to update.
+ display_name: Connector instance display name.
+ description: Connector instance description.
+ interval_seconds: Connector instance execution interval in seconds.
+ timeout_seconds: Timeout of a single Python script run.
+ allow_list: Connector instance allow list.
+ product_field_name: Connector's device product field.
+ event_field_name: Connector's event name field.
+ integration_version: The integration version. Required on patch if
+ provided.
+ version: The connector instance version.
+ logging_enabled_until_unix_ms: Timeout when log collecting will be
+ disabled.
+ parameters: List of ConnectorInstanceParameter instances or dicts.
+ enabled: Whether the connector instance is enabled.
+ update_mask: Comma-separated list of fields to update. If omitted,
+ the mask is auto-generated from whichever fields are provided.
+ Example: "displayName,intervalSeconds".
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the updated ConnectorInstance resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ resolved_parameters = (
+ [
+ p.to_dict() if isinstance(p, ConnectorInstanceParameter) else p
+ for p in parameters
+ ]
+ if parameters is not None
+ else None
+ )
+
+ body, params = build_patch_body(
+ field_map=[
+ ("displayName", "displayName", display_name),
+ ("description", "description", description),
+ ("intervalSeconds", "intervalSeconds", interval_seconds),
+ ("timeoutSeconds", "timeoutSeconds", timeout_seconds),
+ ("allowList", "allowList", allow_list),
+ ("productFieldName", "productFieldName", product_field_name),
+ ("eventFieldName", "eventFieldName", event_field_name),
+ ("integrationVersion", "integrationVersion", integration_version),
+ ("version", "version", version),
+ (
+ "loggingEnabledUntilUnixMs",
+ "loggingEnabledUntilUnixMs",
+ logging_enabled_until_unix_ms,
+ ),
+ ("parameters", "parameters", resolved_parameters),
+ ("id", "id", connector_instance_id),
+ ("enabled", "enabled", enabled),
+ ],
+ update_mask=update_mask,
+ )
+
+ return chronicle_request(
+ client,
+ method="PATCH",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"connectors/{connector_id}/connectorInstances/"
+ f"{connector_instance_id}"
+ ),
+ api_version=api_version,
+ json=body,
+ params=params,
+ )
+
+
+def get_connector_instance_latest_definition(
+ client: "ChronicleClient",
+ integration_name: str,
+ connector_id: str,
+ connector_instance_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Refresh a connector instance with the latest definition.
+
+ Use this method to discover new parameters or updated scripts for an
+ existing connector instance.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the connector belongs to.
+ connector_id: ID of the connector the instance belongs to.
+ connector_instance_id: ID of the connector instance to refresh.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the refreshed ConnectorInstance resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return chronicle_request(
+ client,
+ method="GET",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"connectors/{connector_id}/connectorInstances/"
+ f"{connector_instance_id}:fetchLatestDefinition"
+ ),
+ api_version=api_version,
+ )
+
+
+def set_connector_instance_logs_collection(
+ client: "ChronicleClient",
+ integration_name: str,
+ connector_id: str,
+ connector_instance_id: str,
+ enabled: bool,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Enable or disable debug log collection for a connector instance.
+
+ When enabled is set to True, existing logs are cleared and a new
+ collection period is started.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the connector belongs to.
+ connector_id: ID of the connector the instance belongs to.
+ connector_instance_id: ID of the connector instance to configure.
+ enabled: Whether logs collection is enabled for the connector
+ instance. Required.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the log enable expiration time in unix ms.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return chronicle_request(
+ client,
+ method="POST",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"connectors/{connector_id}/connectorInstances/"
+ f"{connector_instance_id}:setLogsCollection"
+ ),
+ api_version=api_version,
+ json={"enabled": enabled},
+ )
+
+
+def run_connector_instance_on_demand(
+ client: "ChronicleClient",
+ integration_name: str,
+ connector_id: str,
+ connector_instance_id: str,
+ connector_instance: dict[str, Any],
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Trigger an immediate, single execution of a connector instance.
+
+ Use this method for testing configuration changes or manually
+ force-starting a data ingestion cycle.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the connector belongs to.
+ connector_id: ID of the connector the instance belongs to.
+ connector_instance_id: ID of the connector instance to run.
+ connector_instance: Dict containing the ConnectorInstance with
+ values to use for the run. Required.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the run results with the following fields:
+ - debugOutput: The execution debug output message.
+ - success: True if the execution was successful.
+ - sampleCases: List of alerts produced by the connector run.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return chronicle_request(
+ client,
+ method="POST",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"connectors/{connector_id}/connectorInstances/"
+ f"{connector_instance_id}:runOnDemand"
+ ),
+ api_version=api_version,
+ json={"connectorInstance": connector_instance},
+ )
diff --git a/src/secops/chronicle/integration/connector_revisions.py b/src/secops/chronicle/integration/connector_revisions.py
new file mode 100644
index 00000000..a5908864
--- /dev/null
+++ b/src/secops/chronicle/integration/connector_revisions.py
@@ -0,0 +1,202 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Integration connector revisions functionality for Chronicle."""
+
+from typing import Any, TYPE_CHECKING
+
+from secops.chronicle.models import APIVersion
+from secops.chronicle.utils.format_utils import format_resource_id
+from secops.chronicle.utils.request_utils import (
+ chronicle_paginated_request,
+ chronicle_request,
+)
+
+if TYPE_CHECKING:
+ from secops.chronicle.client import ChronicleClient
+
+
+def list_integration_connector_revisions(
+ client: "ChronicleClient",
+ integration_name: str,
+ connector_id: str,
+ page_size: int | None = None,
+ page_token: str | None = None,
+ filter_string: str | None = None,
+ order_by: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ as_list: bool = False,
+) -> dict[str, Any] | list[dict[str, Any]]:
+ """List all revisions for a specific integration connector.
+
+ Use this method to browse the version history and identify potential
+ rollback targets.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the connector belongs to.
+ connector_id: ID of the connector to list revisions for.
+ page_size: Maximum number of revisions to return.
+ page_token: Page token from a previous call to retrieve the next page.
+ filter_string: Filter expression to filter revisions.
+ order_by: Field to sort the revisions by.
+ api_version: API version to use for the request. Default is V1BETA.
+ as_list: If True, return a list of revisions instead of a dict with
+ revisions list and nextPageToken.
+
+ Returns:
+ If as_list is True: List of revisions.
+ If as_list is False: Dict with revisions list and nextPageToken.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ extra_params = {
+ "filter": filter_string,
+ "orderBy": order_by,
+ }
+
+ # Remove keys with None values
+ extra_params = {k: v for k, v in extra_params.items() if v is not None}
+
+ return chronicle_paginated_request(
+ client,
+ api_version=api_version,
+ path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"connectors/{connector_id}/revisions"
+ ),
+ items_key="revisions",
+ page_size=page_size,
+ page_token=page_token,
+ extra_params=extra_params,
+ as_list=as_list,
+ )
+
+
+def delete_integration_connector_revision(
+ client: "ChronicleClient",
+ integration_name: str,
+ connector_id: str,
+ revision_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> None:
+ """Delete a specific revision for a given integration connector.
+
+ Use this method to clean up old or incorrect snapshots from the version
+ history.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the connector belongs to.
+ connector_id: ID of the connector the revision belongs to.
+ revision_id: ID of the revision to delete.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ None
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ chronicle_request(
+ client,
+ method="DELETE",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"connectors/{connector_id}/revisions/{revision_id}"
+ ),
+ api_version=api_version,
+ )
+
+
+def create_integration_connector_revision(
+ client: "ChronicleClient",
+ integration_name: str,
+ connector_id: str,
+ connector: dict[str, Any],
+ comment: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Create a new revision snapshot of the current integration connector.
+
+ Use this method to save a stable configuration before making experimental
+ changes. Only custom connectors can be versioned.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the connector belongs to.
+ connector_id: ID of the connector to create a revision for.
+ connector: Dict containing the IntegrationConnector to snapshot.
+ comment: Comment describing the revision. Maximum 400 characters.
+ Optional.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the newly created ConnectorRevision resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ body = {"connector": connector}
+
+ if comment is not None:
+ body["comment"] = comment
+
+ return chronicle_request(
+ client,
+ method="POST",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"connectors/{connector_id}/revisions"
+ ),
+ api_version=api_version,
+ json=body,
+ )
+
+
+def rollback_integration_connector_revision(
+ client: "ChronicleClient",
+ integration_name: str,
+ connector_id: str,
+ revision_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Revert the current connector definition to a previously saved revision.
+
+ Use this method to quickly revert to a known good configuration if an
+ investigation or update is unsuccessful.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the connector belongs to.
+ connector_id: ID of the connector to rollback.
+ revision_id: ID of the revision to rollback to.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the ConnectorRevision rolled back to.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return chronicle_request(
+ client,
+ method="POST",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"connectors/{connector_id}/revisions/{revision_id}:rollback"
+ ),
+ api_version=api_version,
+ )
diff --git a/src/secops/chronicle/integration/connectors.py b/src/secops/chronicle/integration/connectors.py
new file mode 100644
index 00000000..b2c0ccd1
--- /dev/null
+++ b/src/secops/chronicle/integration/connectors.py
@@ -0,0 +1,405 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Integration connectors functionality for Chronicle."""
+
+from typing import Any, TYPE_CHECKING
+
+from secops.chronicle.models import (
+ APIVersion,
+ ConnectorParameter,
+ ConnectorRule,
+)
+from secops.chronicle.utils.format_utils import (
+ format_resource_id,
+ build_patch_body,
+)
+from secops.chronicle.utils.request_utils import (
+ chronicle_paginated_request,
+ chronicle_request,
+)
+
+if TYPE_CHECKING:
+ from secops.chronicle.client import ChronicleClient
+
+
+def list_integration_connectors(
+ client: "ChronicleClient",
+ integration_name: str,
+ page_size: int | None = None,
+ page_token: str | None = None,
+ filter_string: str | None = None,
+ order_by: str | None = None,
+ exclude_staging: bool | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ as_list: bool = False,
+) -> dict[str, Any] | list[dict[str, Any]]:
+ """List all connectors defined for a specific integration.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration to list connectors for.
+ page_size: Maximum number of connectors to return. Defaults to 50,
+ maximum is 1000.
+ page_token: Page token from a previous call to retrieve the next page.
+ filter_string: Filter expression to filter connectors.
+ order_by: Field to sort the connectors by.
+ exclude_staging: Whether to exclude staging connectors from the
+ response. By default, staging connectors are included.
+ api_version: API version to use for the request. Default is V1BETA.
+ as_list: If True, return a list of connectors instead of a dict with
+ connectors list and nextPageToken.
+
+ Returns:
+ If as_list is True: List of connectors.
+ If as_list is False: Dict with connectors list and nextPageToken.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ extra_params = {
+ "filter": filter_string,
+ "orderBy": order_by,
+ "excludeStaging": exclude_staging,
+ }
+
+ # Remove keys with None values
+ extra_params = {k: v for k, v in extra_params.items() if v is not None}
+
+ return chronicle_paginated_request(
+ client,
+ api_version=api_version,
+ path=f"integrations/{format_resource_id(integration_name)}/connectors",
+ items_key="connectors",
+ page_size=page_size,
+ page_token=page_token,
+ extra_params=extra_params,
+ as_list=as_list,
+ )
+
+
+def get_integration_connector(
+ client: "ChronicleClient",
+ integration_name: str,
+ connector_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Get a single connector for a given integration.
+
+ Use this method to retrieve the Python script, configuration parameters,
+ and field mapping logic for a specific connector.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the connector belongs to.
+ connector_id: ID of the connector to retrieve.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing details of the specified IntegrationConnector.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return chronicle_request(
+ client,
+ method="GET",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"connectors/{connector_id}"
+ ),
+ api_version=api_version,
+ )
+
+
+def delete_integration_connector(
+ client: "ChronicleClient",
+ integration_name: str,
+ connector_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> None:
+ """Delete a specific custom connector from a given integration.
+
+ Only custom connectors can be deleted; commercial connectors are
+ immutable.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the connector belongs to.
+ connector_id: ID of the connector to delete.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ None
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ chronicle_request(
+ client,
+ method="DELETE",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"connectors/{connector_id}"
+ ),
+ api_version=api_version,
+ )
+
+
+def create_integration_connector(
+ client: "ChronicleClient",
+ integration_name: str,
+ display_name: str,
+ script: str,
+ timeout_seconds: int,
+ enabled: bool,
+ product_field_name: str,
+ event_field_name: str,
+ description: str | None = None,
+ parameters: list[dict[str, Any] | ConnectorParameter] | None = None,
+ rules: list[dict[str, Any] | ConnectorRule] | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Create a new custom connector for a given integration.
+
+ Use this method to define how to fetch and parse alerts from a unique or
+ unofficial data source. Each connector must have a unique display name
+ and a functional Python script.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration to create the connector for.
+ display_name: Connector's display name. Required.
+ script: Connector's Python script. Required.
+ timeout_seconds: Timeout in seconds for a single script run. Required.
+ enabled: Whether the connector is enabled or disabled. Required.
+ product_field_name: Field name used to determine the device product.
+ Required.
+ event_field_name: Field name used to determine the event name
+ (sub-type). Required.
+ description: Connector's description. Optional.
+ parameters: List of ConnectorParameter instances or dicts. Optional.
+ rules: List of ConnectorRule instances or dicts. Optional.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the newly created IntegrationConnector resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ resolved_parameters = (
+ [
+ p.to_dict() if isinstance(p, ConnectorParameter) else p
+ for p in parameters
+ ]
+ if parameters is not None
+ else None
+ )
+ resolved_rules = (
+ [r.to_dict() if isinstance(r, ConnectorRule) else r for r in rules]
+ if rules is not None
+ else None
+ )
+
+ body = {
+ "displayName": display_name,
+ "script": script,
+ "timeoutSeconds": timeout_seconds,
+ "enabled": enabled,
+ "productFieldName": product_field_name,
+ "eventFieldName": event_field_name,
+ "description": description,
+ "parameters": resolved_parameters,
+ "rules": resolved_rules,
+ }
+
+ # Remove keys with None values
+ body = {k: v for k, v in body.items() if v is not None}
+
+ return chronicle_request(
+ client,
+ method="POST",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"connectors"
+ ),
+ api_version=api_version,
+ json=body,
+ )
+
+
+def update_integration_connector(
+ client: "ChronicleClient",
+ integration_name: str,
+ connector_id: str,
+ display_name: str | None = None,
+ script: str | None = None,
+ timeout_seconds: int | None = None,
+ enabled: bool | None = None,
+ product_field_name: str | None = None,
+ event_field_name: str | None = None,
+ description: str | None = None,
+ parameters: list[dict[str, Any] | ConnectorParameter] | None = None,
+ rules: list[dict[str, Any] | ConnectorRule] | None = None,
+ update_mask: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Update an existing custom connector for a given integration.
+
+ Only custom connectors can be updated; commercial connectors are
+ immutable.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the connector belongs to.
+ connector_id: ID of the connector to update.
+ display_name: Connector's display name.
+ script: Connector's Python script.
+ timeout_seconds: Timeout in seconds for a single script run.
+ enabled: Whether the connector is enabled or disabled.
+ product_field_name: Field name used to determine the device product.
+ event_field_name: Field name used to determine the event name
+ (sub-type).
+ description: Connector's description.
+ parameters: List of ConnectorParameter instances or dicts.
+ rules: List of ConnectorRule instances or dicts.
+ update_mask: Comma-separated list of fields to update. If omitted,
+ the mask is auto-generated from whichever fields are provided.
+ Example: "displayName,script".
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the updated IntegrationConnector resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ resolved_parameters = (
+ [
+ p.to_dict() if isinstance(p, ConnectorParameter) else p
+ for p in parameters
+ ]
+ if parameters is not None
+ else None
+ )
+ resolved_rules = (
+ [r.to_dict() if isinstance(r, ConnectorRule) else r for r in rules]
+ if rules is not None
+ else None
+ )
+
+ body, params = build_patch_body(
+ field_map=[
+ ("displayName", "displayName", display_name),
+ ("script", "script", script),
+ ("timeoutSeconds", "timeoutSeconds", timeout_seconds),
+ ("enabled", "enabled", enabled),
+ ("productFieldName", "productFieldName", product_field_name),
+ ("eventFieldName", "eventFieldName", event_field_name),
+ ("description", "description", description),
+ ("parameters", "parameters", resolved_parameters),
+ ("rules", "rules", resolved_rules),
+ ],
+ update_mask=update_mask,
+ )
+
+ return chronicle_request(
+ client,
+ method="PATCH",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"connectors/{connector_id}"
+ ),
+ api_version=api_version,
+ json=body,
+ params=params,
+ )
+
+
+def execute_integration_connector_test(
+ client: "ChronicleClient",
+ integration_name: str,
+ connector: dict[str, Any],
+ agent_identifier: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Execute a test run of a connector's Python script.
+
+ Use this method to verify data fetching logic, authentication, and parsing
+ logic before enabling the connector for production ingestion. The full
+ connector object is required as the test can be run without saving the
+ connector first.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the connector belongs to.
+ connector: Dict containing the IntegrationConnector to test.
+ agent_identifier: Agent identifier for remote testing. Optional.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the test execution results with the following fields:
+ - outputMessage: Human-readable output message set by the script.
+ - debugOutputMessage: The script debug output.
+ - resultJson: The result JSON if it exists (optional).
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ body = {"connector": connector}
+
+ if agent_identifier is not None:
+ body["agentIdentifier"] = agent_identifier
+
+ return chronicle_request(
+ client,
+ method="POST",
+ endpoint_path=f"integrations/{format_resource_id(integration_name)}"
+ f"/connectors:executeTest",
+ api_version=api_version,
+ json=body,
+ )
+
+
+def get_integration_connector_template(
+ client: "ChronicleClient",
+ integration_name: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Retrieve a default Python script template for a
+ new integration connector.
+
+ Use this method to rapidly initialize the development of a new connector.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration to fetch the template for.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the IntegrationConnector template.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return chronicle_request(
+ client,
+ method="GET",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"connectors:fetchTemplate"
+ ),
+ api_version=api_version,
+ )
diff --git a/src/secops/chronicle/integration/integration_instances.py b/src/secops/chronicle/integration/integration_instances.py
new file mode 100644
index 00000000..c7e88dd7
--- /dev/null
+++ b/src/secops/chronicle/integration/integration_instances.py
@@ -0,0 +1,403 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Integration instances functionality for Chronicle."""
+
+from typing import Any, TYPE_CHECKING
+
+from secops.chronicle.models import APIVersion, IntegrationInstanceParameter
+from secops.chronicle.utils.format_utils import (
+ format_resource_id,
+ build_patch_body,
+)
+from secops.chronicle.utils.request_utils import (
+ chronicle_paginated_request,
+ chronicle_request,
+)
+
+if TYPE_CHECKING:
+ from secops.chronicle.client import ChronicleClient
+
+
+def list_integration_instances(
+ client: "ChronicleClient",
+ integration_name: str,
+ page_size: int | None = None,
+ page_token: str | None = None,
+ filter_string: str | None = None,
+ order_by: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ as_list: bool = False,
+) -> dict[str, Any] | list[dict[str, Any]]:
+ """List all instances for a specific integration.
+
+ Use this method to browse the configured integration instances available
+ for a custom or third-party product across different environments.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration to list instances for.
+ page_size: Maximum number of integration instances to return.
+ page_token: Page token from a previous call to retrieve the next page.
+ filter_string: Filter expression to filter integration instances.
+ order_by: Field to sort the integration instances by.
+ api_version: API version to use for the request. Default is V1BETA.
+ as_list: If True, return a list of integration instances instead of a
+ dict with integration instances list and nextPageToken.
+
+ Returns:
+ If as_list is True: List of integration instances.
+ If as_list is False: Dict with integration instances list and
+ nextPageToken.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ extra_params = {
+ "filter": filter_string,
+ "orderBy": order_by,
+ }
+
+ # Remove keys with None values
+ extra_params = {k: v for k, v in extra_params.items() if v is not None}
+
+ return chronicle_paginated_request(
+ client,
+ api_version=api_version,
+ path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"integrationInstances"
+ ),
+ items_key="integrationInstances",
+ page_size=page_size,
+ page_token=page_token,
+ extra_params=extra_params,
+ as_list=as_list,
+ )
+
+
+def get_integration_instance(
+ client: "ChronicleClient",
+ integration_name: str,
+ integration_instance_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Get a single instance for a specific integration.
+
+ Use this method to retrieve the specific configuration, connection status,
+ and environment mapping for an active integration.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the instance belongs to.
+ integration_instance_id: ID of the integration instance to retrieve.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing details of the specified IntegrationInstance.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return chronicle_request(
+ client,
+ method="GET",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"integrationInstances/{integration_instance_id}"
+ ),
+ api_version=api_version,
+ )
+
+
+def delete_integration_instance(
+ client: "ChronicleClient",
+ integration_name: str,
+ integration_instance_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> None:
+ """Delete a specific integration instance.
+
+ Use this method to permanently remove an integration instance and stop all
+ associated automated tasks (connectors or jobs) using this instance.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the instance belongs to.
+ integration_instance_id: ID of the integration instance to delete.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ None
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ chronicle_request(
+ client,
+ method="DELETE",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"integrationInstances/{integration_instance_id}"
+ ),
+ api_version=api_version,
+ )
+
+
+def create_integration_instance(
+ client: "ChronicleClient",
+ integration_name: str,
+ environment: str,
+ display_name: str | None = None,
+ description: str | None = None,
+ parameters: (
+ list[dict[str, Any] | IntegrationInstanceParameter] | None
+ ) = None,
+ agent: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Create a new integration instance for a specific integration.
+
+ Use this method to establish a new integration instance to a custom or
+ third-party security product for a specific environment. All mandatory
+ parameters required by the integration definition must be provided.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration to create the instance for.
+ environment: The integration instance environment. Required.
+ display_name: The display name of the integration instance.
+ Automatically generated if not provided. Maximum 110 characters.
+ description: The integration instance description. Maximum 1500
+ characters.
+ parameters: List of IntegrationInstanceParameter instances or dicts.
+ agent: Agent identifier for a remote integration instance.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the newly created IntegrationInstance resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ resolved_parameters = (
+ [
+ p.to_dict() if isinstance(p, IntegrationInstanceParameter) else p
+ for p in parameters
+ ]
+ if parameters is not None
+ else None
+ )
+
+ body = {
+ "environment": environment,
+ "displayName": display_name,
+ "description": description,
+ "parameters": resolved_parameters,
+ "agent": agent,
+ }
+
+ # Remove keys with None values
+ body = {k: v for k, v in body.items() if v is not None}
+
+ return chronicle_request(
+ client,
+ method="POST",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"integrationInstances"
+ ),
+ api_version=api_version,
+ json=body,
+ )
+
+
+def update_integration_instance(
+ client: "ChronicleClient",
+ integration_name: str,
+ integration_instance_id: str,
+ environment: str | None = None,
+ display_name: str | None = None,
+ description: str | None = None,
+ parameters: (
+ list[dict[str, Any] | IntegrationInstanceParameter] | None
+ ) = None,
+ agent: str | None = None,
+ update_mask: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Update an existing integration instance.
+
+ Use this method to modify connection parameters (e.g., rotate an API
+ key), change the display name, or update the description of a configured
+ integration instance.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the instance belongs to.
+ integration_instance_id: ID of the integration instance to update.
+ environment: The integration instance environment.
+ display_name: The display name of the integration instance. Maximum
+ 110 characters.
+ description: The integration instance description. Maximum 1500
+ characters.
+ parameters: List of IntegrationInstanceParameter instances or dicts.
+ agent: Agent identifier for a remote integration instance.
+ update_mask: Comma-separated list of fields to update. If omitted,
+ the mask is auto-generated from whichever fields are provided.
+ Example: "displayName,description".
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the updated IntegrationInstance resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ resolved_parameters = (
+ [
+ p.to_dict() if isinstance(p, IntegrationInstanceParameter) else p
+ for p in parameters
+ ]
+ if parameters is not None
+ else None
+ )
+
+ body, params = build_patch_body(
+ field_map=[
+ ("environment", "environment", environment),
+ ("displayName", "displayName", display_name),
+ ("description", "description", description),
+ ("parameters", "parameters", resolved_parameters),
+ ("agent", "agent", agent),
+ ],
+ update_mask=update_mask,
+ )
+
+ return chronicle_request(
+ client,
+ method="PATCH",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"integrationInstances/{integration_instance_id}"
+ ),
+ api_version=api_version,
+ json=body,
+ params=params,
+ )
+
+
+def execute_integration_instance_test(
+ client: "ChronicleClient",
+ integration_name: str,
+ integration_instance_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Execute a connectivity test for a specific integration instance.
+
+ Use this method to verify that SecOps can successfully communicate with
+ the third-party security product using the provided credentials.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the instance belongs to.
+ integration_instance_id: ID of the integration instance to test.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the test results with the following fields:
+ - successful: Indicates if the test was successful.
+ - message: Test result message (optional).
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return chronicle_request(
+ client,
+ method="POST",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"integrationInstances/{integration_instance_id}:executeTest"
+ ),
+ api_version=api_version,
+ )
+
+
+def get_integration_instance_affected_items(
+ client: "ChronicleClient",
+ integration_name: str,
+ integration_instance_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """List all playbooks that depend on a specific integration instance.
+
+ Use this method to perform impact analysis before deleting or
+ significantly changing a connection configuration.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the instance belongs to.
+ integration_instance_id: ID of the integration instance to fetch
+ affected items for.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing a list of AffectedPlaybookResponse objects that
+ depend on the specified integration instance.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return chronicle_request(
+ client,
+ method="GET",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"integrationInstances/{integration_instance_id}:fetchAffectedItems"
+ ),
+ api_version=api_version,
+ )
+
+
+def get_default_integration_instance(
+ client: "ChronicleClient",
+ integration_name: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Get the system default configuration for a specific integration.
+
+ Use this method to retrieve the baseline integration instance details
+ provided for a commercial product.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration to fetch the default
+ instance for.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the default IntegrationInstance resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return chronicle_request(
+ client,
+ method="GET",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"integrationInstances:fetchDefaultInstance"
+ ),
+ api_version=api_version,
+ )
diff --git a/src/secops/chronicle/integration/integrations.py b/src/secops/chronicle/integration/integrations.py
new file mode 100644
index 00000000..4f72f5ea
--- /dev/null
+++ b/src/secops/chronicle/integration/integrations.py
@@ -0,0 +1,686 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Integrations functionality for Chronicle."""
+
+from typing import Any, TYPE_CHECKING
+
+from secops.chronicle.models import (
+ APIVersion,
+ DiffType,
+ IntegrationParam,
+ TargetMode,
+ PythonVersion,
+ IntegrationType,
+)
+
+from secops.chronicle.utils.format_utils import build_patch_body
+from secops.chronicle.utils.request_utils import (
+ chronicle_paginated_request,
+ chronicle_request_bytes,
+ chronicle_request,
+)
+
+if TYPE_CHECKING:
+ from secops.chronicle.client import ChronicleClient
+
+
+def list_integrations(
+ client: "ChronicleClient",
+ page_size: int | None = None,
+ page_token: str | None = None,
+ filter_string: str | None = None,
+ order_by: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ as_list: bool = False,
+) -> dict[str, Any] | list[dict[str, Any]]:
+ """Get a list of integrations.
+
+ Args:
+ client: ChronicleClient instance
+ page_size: Number of results to return per page
+ page_token: Token for the page to retrieve
+ filter_string: Filter expression to filter integrations
+ order_by: Field to sort the integrations by
+ api_version: API version to use for the request. Default is V1BETA.
+ as_list: If True, return a list of integrations instead
+ of a dict with integrations list and nextPageToken.
+
+ Returns:
+ If as_list is True: List of integrations.
+ If as_list is False: Dict with integrations list and
+ nextPageToken.
+
+ Raises:
+ APIError: If the API request fails
+ """
+ param_fields = {
+ "filter": filter_string,
+ "orderBy": order_by,
+ }
+
+ # Remove keys with None values
+ param_fields = {k: v for k, v in param_fields.items() if v is not None}
+
+ return chronicle_paginated_request(
+ client,
+ api_version=api_version,
+ path="integrations",
+ items_key="integrations",
+ page_size=page_size,
+ page_token=page_token,
+ extra_params=param_fields,
+ as_list=as_list,
+ )
+
+
+def get_integration(
+ client: "ChronicleClient",
+ integration_name: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Get details of a specific integration.
+
+ Args:
+ client: ChronicleClient instance
+ integration_name: name of the integration to retrieve
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing details of the specified integration
+
+ Raises:
+ APIError: If the API request fails
+ """
+ return chronicle_request(
+ client,
+ method="GET",
+ endpoint_path=f"integrations/{integration_name}",
+ api_version=api_version,
+ )
+
+
+def delete_integration(
+ client: "ChronicleClient",
+ integration_name: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> None:
+ """Deletes a specific custom Integration. Commercial integrations cannot
+ be deleted via this method.
+
+ Args:
+ client: ChronicleClient instance
+ integration_name: name of the integration to delete
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Raises:
+ APIError: If the API request fails
+ """
+ chronicle_request(
+ client,
+ method="DELETE",
+ endpoint_path=f"integrations/{integration_name}",
+ api_version=api_version,
+ )
+
+
+def create_integration(
+ client: "ChronicleClient",
+ display_name: str,
+ staging: bool,
+ description: str | None = None,
+ image_base64: str | None = None,
+ svg_icon: str | None = None,
+ python_version: PythonVersion | None = None,
+ parameters: list[IntegrationParam | dict[str, Any]] | None = None,
+ categories: list[str] | None = None,
+ integration_type: IntegrationType | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Creates a new custom SOAR Integration.
+
+ Args:
+ client: ChronicleClient instance
+ display_name: Required. The display name of the integration
+ (max 150 characters)
+ staging: Required. True if the integration is in staging mode
+ description: Optional. The integration's description
+ (max 1,500 characters)
+ image_base64: Optional. The integration's image encoded as
+ a base64 string (max 5 MB)
+ svg_icon: Optional. The integration's SVG icon (max 1 MB)
+ python_version: Optional. The integration's Python version
+ parameters: Optional. Integration parameters (max 50). Each entry may
+ be an IntegrationParam dataclass instance or a plain dict with
+ keys: id, defaultValue, displayName, propertyName, type,
+ description, mandatory.
+ categories: Optional. Integration categories (max 50)
+ integration_type: Optional. The integration's type (response/extension)
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the details of the newly created integration
+
+ Raises:
+ APIError: If the API request fails
+ """
+ serialised_params: list[dict[str, Any]] | None = None
+ if parameters is not None:
+ serialised_params = [
+ p.to_dict() if isinstance(p, IntegrationParam) else p
+ for p in parameters
+ ]
+
+ body_fields = {
+ "displayName": display_name,
+ "staging": staging,
+ "description": description,
+ "imageBase64": image_base64,
+ "svgIcon": svg_icon,
+ "pythonVersion": python_version,
+ "parameters": serialised_params,
+ "categories": categories,
+ "type": integration_type,
+ }
+
+ # Remove keys with None values
+ body_fields = {k: v for k, v in body_fields.items() if v is not None}
+
+ return chronicle_request(
+ client,
+ method="POST",
+ endpoint_path="integrations",
+ json=body_fields,
+ api_version=api_version,
+ )
+
+
+def download_integration(
+ client: "ChronicleClient",
+ integration_name: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> bytes:
+ """Exports the entire integration package as a ZIP file. Includes all
+ scripts, definitions, and the manifest file. Use this method for backup
+ or sharing.
+
+ Args:
+ client: ChronicleClient instance
+ integration_name: name of the integration to download
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Bytes of the ZIP file containing the integration package
+
+ Raises:
+ APIError: If the API request fails
+ """
+ return chronicle_request_bytes(
+ client,
+ method="GET",
+ endpoint_path=f"integrations/{integration_name}:export",
+ api_version=api_version,
+ params={"alt": "media"},
+ headers={"Accept": "application/zip"},
+ )
+
+
+def download_integration_dependency(
+ client: "ChronicleClient",
+ integration_name: str,
+ dependency_name: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Initiates the download of a Python dependency (e.g., a library from
+ PyPI) for a custom integration.
+
+ Args:
+ client: ChronicleClient instance
+ integration_name: name of the integration whose dependency to download
+ dependency_name: The dependency name to download. It can contain the
+ version or the repository.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Empty dict if the download was successful, or a dict containing error
+ details if the download failed
+
+ Raises:
+ APIError: If the API request fails
+ """
+ return chronicle_request(
+ client,
+ method="POST",
+ endpoint_path=f"integrations/{integration_name}:downloadDependency",
+ json={"dependency": dependency_name},
+ api_version=api_version,
+ )
+
+
+def export_integration_items(
+ client: "ChronicleClient",
+ integration_name: str,
+ actions: list[str] | None = None,
+ jobs: list[str] | None = None,
+ connectors: list[str] | None = None,
+ managers: list[str] | None = None,
+ transformers: list[str] | None = None,
+ logical_operators: list[str] | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> bytes:
+ """Exports specific items from an integration into a ZIP folder. Use
+ this method to extract only a subset of capabilities (e.g., just the
+ connectors) for reuse.
+
+ Args:
+ client: ChronicleClient instance
+ integration_name: name of the integration to export items from
+ actions: Optional. A list the ids of the actions to export. Format:
+ [1,2,3]
+ jobs: Optional. A list the ids of the jobs to export. Format:
+ [1,2,3]
+ connectors: Optional. A list the ids of the connectors to export.
+ Format: [1,2,3]
+ managers: Optional. A list the ids of the managers to export. Format:
+ [1,2,3]
+ transformers: Optional. A list the ids of the transformers to export.
+ Format: [1,2,3]
+ logical_operators: Optional. A list the ids of the logical
+ operators to export. Format: [1,2,3]
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Bytes of the ZIP file containing the exported integration items
+
+ Raises:
+ APIError: If the API request fails
+ """
+ export_items = {
+ "actions": ",".join(actions) if actions else None,
+ "jobs": jobs,
+ "connectors": connectors,
+ "managers": managers,
+ "transformers": transformers,
+ "logicalOperators": logical_operators,
+ "alt": "media",
+ }
+
+ # Remove keys with None values
+ export_items = {k: v for k, v in export_items.items() if v is not None}
+
+ return chronicle_request_bytes(
+ client,
+ method="GET",
+ endpoint_path=f"integrations/{integration_name}:exportItems",
+ params=export_items,
+ api_version=api_version,
+ headers={"Accept": "application/zip"},
+ )
+
+
+def get_integration_affected_items(
+ client: "ChronicleClient",
+ integration_name: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Identifies all system items (e.g., connector instances, job instances,
+ playbooks) that would be affected by a change to or deletion of this
+ integration. Use this method to conduct impact analysis before making
+ breaking changes.
+
+ Args:
+ client: ChronicleClient instance
+ integration_name: name of the integration to check for affected items
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the list of items affected by changes to the specified
+ integration
+
+ Raises:
+ APIError: If the API request fails
+ """
+ return chronicle_request(
+ client,
+ method="GET",
+ endpoint_path=f"integrations/{integration_name}:fetchAffectedItems",
+ api_version=api_version,
+ )
+
+
+def get_agent_integrations(
+ client: "ChronicleClient",
+ agent_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Returns the set of integrations currently installed and configured on
+ a specific agent.
+
+ Args:
+ client: ChronicleClient instance
+ agent_id: The agent identifier
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the list of agent-based integrations
+
+ Raises:
+ APIError: If the API request fails
+ """
+ return chronicle_request(
+ client,
+ method="GET",
+ endpoint_path="integrations:fetchAgentIntegrations",
+ params={"agentId": agent_id},
+ api_version=api_version,
+ )
+
+
+def get_integration_dependencies(
+ client: "ChronicleClient",
+ integration_name: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Returns the complete list of Python dependencies currently associated
+ with a custom integration.
+
+ Args:
+ client: ChronicleClient instance
+ integration_name: name of the integration to check for dependencies
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the list of dependencies for the specified integration
+
+ Raises:
+ APIError: If the API request fails
+ """
+ return chronicle_request(
+ client,
+ method="GET",
+ endpoint_path=f"integrations/{integration_name}:fetchDependencies",
+ api_version=api_version,
+ )
+
+
+def get_integration_restricted_agents(
+ client: "ChronicleClient",
+ integration_name: str,
+ required_python_version: PythonVersion,
+ push_request: bool = False,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Identifies remote agents that would be restricted from running an
+ updated version of the integration, typically due to environment
+ incompatibilities like unsupported Python versions.
+
+ Args:
+ client: ChronicleClient instance
+ integration_name: name of the integration to check for restricted agents
+ required_python_version: Python version required for the updated
+ integration.
+ push_request: Optional. Indicates whether the integration is
+ pushed to a different mode (production/staging). False by default.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the list of agents that would be restricted from running
+
+ Raises:
+ APIError: If the API request fails
+ """
+ params_fields = {
+ "requiredPythonVersion": required_python_version.value,
+ "pushRequest": push_request,
+ }
+
+ # Remove keys with None values
+ params_fields = {k: v for k, v in params_fields.items() if v is not None}
+
+ return chronicle_request(
+ client,
+ method="GET",
+ endpoint_path=f"integrations/{integration_name}:fetchRestrictedAgents",
+ params=params_fields,
+ api_version=api_version,
+ )
+
+
+def get_integration_diff(
+ client: "ChronicleClient",
+ integration_name: str,
+ diff_type: DiffType = DiffType.COMMERCIAL,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Get the configuration diff of a specific integration.
+
+ Args:
+ client: ChronicleClient instance
+ integration_name: ID of the integration to retrieve the diff for
+ diff_type: Type of diff to retrieve
+ (Commercial, Production, or Staging). Default is Commercial.
+ COMMERCIAL: Diff between the commercial version of the
+ integration and the current version in the environment.
+ PRODUCTION: Returns the difference between the staging
+ integration and its matching production version.
+ STAGING: Returns the difference between the production
+ integration and its corresponding staging version.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the configuration diff of the specified integration
+
+ Raises:
+ APIError: If the API request fails
+ """
+ return chronicle_request(
+ client,
+ method="GET",
+ endpoint_path=f"integrations/{integration_name}"
+ f":fetch{diff_type.value}Diff",
+ api_version=api_version,
+ )
+
+
+def transition_integration(
+ client: "ChronicleClient",
+ integration_name: str,
+ target_mode: TargetMode,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Transition an integration to a different environment
+ (e.g. staging to production).
+
+ Args:
+ client: ChronicleClient instance
+ integration_name: ID of the integration to transition
+ target_mode: Target mode to transition the integration to:
+ PRODUCTION: Transition the integration to production environment.
+ STAGING: Transition the integration to staging environment.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the details of the transitioned integration
+
+ Raises:
+ APIError: If the API request fails
+ """
+ return chronicle_request(
+ client,
+ method="POST",
+ endpoint_path=f"integrations/{integration_name}"
+ f":pushTo{target_mode.value}",
+ api_version=api_version,
+ )
+
+
+def update_integration(
+ client: "ChronicleClient",
+ integration_name: str,
+ display_name: str | None = None,
+ description: str | None = None,
+ image_base64: str | None = None,
+ svg_icon: str | None = None,
+ python_version: PythonVersion | None = None,
+ parameters: list[dict[str, Any]] | None = None,
+ categories: list[str] | None = None,
+ integration_type: IntegrationType | None = None,
+ staging: bool | None = None,
+ dependencies_to_remove: list[str] | None = None,
+ update_mask: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Update an existing integration.
+
+ Args:
+ client: ChronicleClient instance
+ integration_name: ID of the integration to update
+ display_name: Optional. The display name of the integration
+ (max 150 characters)
+ description: Optional. The integration's description
+ (max 1,500 characters)
+ image_base64: Optional. The integration's image encoded as a
+ base64 string (max 5 MB)
+ svg_icon: Optional. The integration's SVG icon (max 1 MB)
+ python_version: Optional. The integration's Python version
+ parameters: Optional. Integration parameters (max 50)
+ categories: Optional. Integration categories (max 50)
+ integration_type: Optional. The integration's type (response/extension)
+ staging: Optional. True if the integration is in staging mode
+ dependencies_to_remove: Optional. List of dependencies to
+ remove from the integration.
+ update_mask: Optional. Comma-separated list of fields to update.
+ If not provided, all non-None fields will be updated.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the details of the updated integration
+
+ Raises:
+ APIError: If the API request fails
+ """
+ body, params = build_patch_body(
+ field_map=[
+ ("displayName", "display_name", display_name),
+ ("description", "description", description),
+ ("imageBase64", "image_base64", image_base64),
+ ("svgIcon", "svg_icon", svg_icon),
+ ("pythonVersion", "python_version", python_version),
+ ("parameters", "parameters", parameters),
+ ("categories", "categories", categories),
+ ("integrationType", "integration_type", integration_type),
+ ("staging", "staging", staging),
+ ],
+ update_mask=update_mask,
+ )
+
+ if dependencies_to_remove is not None:
+ params = params or {}
+ params["dependenciesToRemove"] = ",".join(dependencies_to_remove)
+
+ return chronicle_request(
+ client,
+ method="PATCH",
+ endpoint_path=f"integrations/{integration_name}",
+ json=body,
+ params=params,
+ api_version=api_version,
+ )
+
+
+def update_custom_integration(
+ client: "ChronicleClient",
+ integration_name: str,
+ display_name: str | None = None,
+ description: str | None = None,
+ image_base64: str | None = None,
+ svg_icon: str | None = None,
+ python_version: PythonVersion | None = None,
+ parameters: list[dict[str, Any]] | None = None,
+ categories: list[str] | None = None,
+ integration_type: IntegrationType | None = None,
+ staging: bool | None = None,
+ dependencies_to_remove: list[str] | None = None,
+ update_mask: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Updates a custom integration definition, including its parameters and
+ dependencies. Use this method to refine the operational behavior of a
+ locally developed integration.
+
+ Args:
+ client: ChronicleClient instance
+ integration_name: Name of the integration to update
+ display_name: Optional. The display name of the integration
+ (max 150 characters)
+ description: Optional. The integration's description
+ (max 1,500 characters)
+ image_base64: Optional. The integration's image encoded as a
+ base64 string (max 5 MB)
+ svg_icon: Optional. The integration's SVG icon (max 1 MB)
+ python_version: Optional. The integration's Python version
+ parameters: Optional. Integration parameters (max 50)
+ categories: Optional. Integration categories (max 50)
+ integration_type: Optional. The integration's type (response/extension)
+ staging: Optional. True if the integration is in staging mode
+ dependencies_to_remove: Optional. List of dependencies to remove from
+ the integration
+ update_mask: Optional. Comma-separated list of fields to update.
+ If not provided, all non-None fields will be updated.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing:
+ - successful: Whether the integration was updated successfully
+ - integration: The updated integration (populated if successful)
+ - dependencies: Dependency installation statuses
+ (populated if failed)
+
+ Raises:
+ APIError: If the API request fails
+ """
+ integration_fields = {
+ "name": integration_name,
+ "displayName": display_name,
+ "description": description,
+ "imageBase64": image_base64,
+ "svgIcon": svg_icon,
+ "pythonVersion": python_version,
+ "parameters": parameters,
+ "categories": categories,
+ "type": integration_type,
+ "staging": staging,
+ }
+
+ # Remove keys with None values
+ integration_fields = {
+ k: v for k, v in integration_fields.items() if v is not None
+ }
+
+ body = {"integration": integration_fields}
+
+ if dependencies_to_remove is not None:
+ body["dependenciesToRemove"] = dependencies_to_remove
+
+ params = {"updateMask": update_mask} if update_mask else None
+
+ return chronicle_request(
+ client,
+ method="POST",
+ endpoint_path=f"integrations/"
+ f"{integration_name}:updateCustomIntegration",
+ json=body,
+ params=params,
+ api_version=api_version,
+ )
diff --git a/src/secops/chronicle/integration/job_context_properties.py b/src/secops/chronicle/integration/job_context_properties.py
new file mode 100644
index 00000000..de40a6f8
--- /dev/null
+++ b/src/secops/chronicle/integration/job_context_properties.py
@@ -0,0 +1,298 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Integration job context property functionality for Chronicle."""
+
+from typing import Any, TYPE_CHECKING
+
+from secops.chronicle.models import APIVersion
+from secops.chronicle.utils.format_utils import (
+ format_resource_id,
+ build_patch_body,
+)
+from secops.chronicle.utils.request_utils import (
+ chronicle_paginated_request,
+ chronicle_request,
+)
+
+if TYPE_CHECKING:
+ from secops.chronicle.client import ChronicleClient
+
+
+def list_job_context_properties(
+ client: "ChronicleClient",
+ integration_name: str,
+ job_id: str,
+ page_size: int | None = None,
+ page_token: str | None = None,
+ filter_string: str | None = None,
+ order_by: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ as_list: bool = False,
+) -> dict[str, Any] | list[dict[str, Any]]:
+ """List all context properties for a specific integration job.
+
+ Use this method to discover all custom data points associated with a job.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the job belongs to.
+ job_id: ID of the job to list context properties for.
+ page_size: Maximum number of context properties to return.
+ page_token: Page token from a previous call to retrieve the next page.
+ filter_string: Filter expression to filter context properties.
+ order_by: Field to sort the context properties by.
+ api_version: API version to use for the request. Default is V1BETA.
+ as_list: If True, return a list of context properties instead of a
+ dict with context properties list and nextPageToken.
+
+ Returns:
+ If as_list is True: List of context properties.
+ If as_list is False: Dict with context properties list and
+ nextPageToken.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ extra_params = {
+ "filter": filter_string,
+ "orderBy": order_by,
+ }
+
+ # Remove keys with None values
+ extra_params = {k: v for k, v in extra_params.items() if v is not None}
+
+ return chronicle_paginated_request(
+ client,
+ api_version=api_version,
+ path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"jobs/{job_id}/contextProperties"
+ ),
+ items_key="contextProperties",
+ page_size=page_size,
+ page_token=page_token,
+ extra_params=extra_params,
+ as_list=as_list,
+ )
+
+
+def get_job_context_property(
+ client: "ChronicleClient",
+ integration_name: str,
+ job_id: str,
+ context_property_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Get a single context property for a specific integration job.
+
+ Use this method to retrieve the value of a specific key within a job's
+ context.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the job belongs to.
+ job_id: ID of the job the context property belongs to.
+ context_property_id: ID of the context property to retrieve.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing details of the specified ContextProperty.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return chronicle_request(
+ client,
+ method="GET",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"jobs/{job_id}/contextProperties/{context_property_id}"
+ ),
+ api_version=api_version,
+ )
+
+
+def delete_job_context_property(
+ client: "ChronicleClient",
+ integration_name: str,
+ job_id: str,
+ context_property_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> None:
+ """Delete a specific context property for a given integration job.
+
+ Use this method to remove a custom data point that is no longer relevant
+ to the job's context.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the job belongs to.
+ job_id: ID of the job the context property belongs to.
+ context_property_id: ID of the context property to delete.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ None
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ chronicle_request(
+ client,
+ method="DELETE",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"jobs/{job_id}/contextProperties/{context_property_id}"
+ ),
+ api_version=api_version,
+ )
+
+
+def create_job_context_property(
+ client: "ChronicleClient",
+ integration_name: str,
+ job_id: str,
+ value: str,
+ key: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Create a new context property for a specific integration job.
+
+ Use this method to attach custom data to a job's context. Property keys
+ must be unique within their context. Key values must be 4-63 characters
+ and match /[a-z][0-9]-/.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the job belongs to.
+ job_id: ID of the job to create the context property for.
+ value: The property value. Required.
+ key: The context property ID to use. Must be 4-63 characters and
+ match /[a-z][0-9]-/. Optional.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the newly created ContextProperty resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ body = {"value": value}
+
+ if key is not None:
+ body["key"] = key
+
+ return chronicle_request(
+ client,
+ method="POST",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"jobs/{job_id}/contextProperties"
+ ),
+ api_version=api_version,
+ json=body,
+ )
+
+
+def update_job_context_property(
+ client: "ChronicleClient",
+ integration_name: str,
+ job_id: str,
+ context_property_id: str,
+ value: str,
+ update_mask: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Update an existing context property for a given integration job.
+
+ Use this method to modify the value of a previously saved key.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the job belongs to.
+ job_id: ID of the job the context property belongs to.
+ context_property_id: ID of the context property to update.
+ value: The new property value. Required.
+ update_mask: Comma-separated list of fields to update. Only "value"
+ is supported. If omitted, defaults to "value".
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the updated ContextProperty resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ body, params = build_patch_body(
+ field_map=[
+ ("value", "value", value),
+ ],
+ update_mask=update_mask,
+ )
+
+ return chronicle_request(
+ client,
+ method="PATCH",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"jobs/{job_id}/contextProperties/{context_property_id}"
+ ),
+ api_version=api_version,
+ json=body,
+ params=params,
+ )
+
+
+def delete_all_job_context_properties(
+ client: "ChronicleClient",
+ integration_name: str,
+ job_id: str,
+ context_id: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> None:
+ """Delete all context properties for a specific integration job.
+
+ Use this method to quickly clear all supplemental data from a job's
+ context.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the job belongs to.
+ job_id: ID of the job to clear context properties from.
+ context_id: The context ID to remove context properties from. Must be
+ 4-63 characters and match /[a-z][0-9]-/. Optional.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ None
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ body = {}
+
+ if context_id is not None:
+ body["contextId"] = context_id
+
+ chronicle_request(
+ client,
+ method="POST",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"jobs/{job_id}/contextProperties:clearAll"
+ ),
+ api_version=api_version,
+ json=body,
+ )
diff --git a/src/secops/chronicle/integration/job_instance_logs.py b/src/secops/chronicle/integration/job_instance_logs.py
new file mode 100644
index 00000000..f58d568f
--- /dev/null
+++ b/src/secops/chronicle/integration/job_instance_logs.py
@@ -0,0 +1,125 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Integration job instances functionality for Chronicle."""
+
+from typing import Any, TYPE_CHECKING
+
+from secops.chronicle.models import APIVersion
+from secops.chronicle.utils.format_utils import format_resource_id
+from secops.chronicle.utils.request_utils import (
+ chronicle_paginated_request,
+ chronicle_request,
+)
+
+if TYPE_CHECKING:
+ from secops.chronicle.client import ChronicleClient
+
+
+def list_job_instance_logs(
+ client: "ChronicleClient",
+ integration_name: str,
+ job_id: str,
+ job_instance_id: str,
+ page_size: int | None = None,
+ page_token: str | None = None,
+ filter_string: str | None = None,
+ order_by: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ as_list: bool = False,
+) -> dict[str, Any] | list[dict[str, Any]]:
+ """List all execution logs for a specific job instance.
+
+ Use this method to browse the historical performance and reliability of a
+ background automation schedule.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the job belongs to.
+ job_id: ID of the job the instance belongs to.
+ job_instance_id: ID of the job instance to list logs for.
+ page_size: Maximum number of logs to return.
+ page_token: Page token from a previous call to retrieve the next page.
+ filter_string: Filter expression to filter logs.
+ order_by: Field to sort the logs by.
+ api_version: API version to use for the request. Default is V1BETA.
+ as_list: If True, return a list of logs instead of a dict with logs
+ list and nextPageToken.
+
+ Returns:
+ If as_list is True: List of logs.
+ If as_list is False: Dict with logs list and nextPageToken.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ extra_params = {
+ "filter": filter_string,
+ "orderBy": order_by,
+ }
+
+ # Remove keys with None values
+ extra_params = {k: v for k, v in extra_params.items() if v is not None}
+
+ return chronicle_paginated_request(
+ client,
+ api_version=api_version,
+ path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"jobs/{job_id}/jobInstances/{job_instance_id}/logs"
+ ),
+ items_key="logs",
+ page_size=page_size,
+ page_token=page_token,
+ extra_params=extra_params,
+ as_list=as_list,
+ )
+
+
+def get_job_instance_log(
+ client: "ChronicleClient",
+ integration_name: str,
+ job_id: str,
+ job_instance_id: str,
+ log_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Get a single log entry for a specific job instance.
+
+ Use this method to retrieve the detailed output message, start/end times,
+ and final status of a specific background task execution.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the job belongs to.
+ job_id: ID of the job the instance belongs to.
+ job_instance_id: ID of the job instance the log belongs to.
+ log_id: ID of the log entry to retrieve.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing details of the specified JobInstanceLog.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return chronicle_request(
+ client,
+ method="GET",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"jobs/{job_id}/jobInstances/{job_instance_id}/logs/{log_id}"
+ ),
+ api_version=api_version,
+ )
diff --git a/src/secops/chronicle/integration/job_instances.py b/src/secops/chronicle/integration/job_instances.py
new file mode 100644
index 00000000..c64705ee
--- /dev/null
+++ b/src/secops/chronicle/integration/job_instances.py
@@ -0,0 +1,399 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Integration job instances functionality for Chronicle."""
+
+from typing import Any, TYPE_CHECKING
+
+from secops.chronicle.models import (
+ APIVersion,
+ AdvancedConfig,
+ IntegrationJobInstanceParameter,
+)
+from secops.chronicle.utils.format_utils import (
+ format_resource_id,
+ build_patch_body,
+)
+from secops.chronicle.utils.request_utils import (
+ chronicle_paginated_request,
+ chronicle_request,
+)
+
+if TYPE_CHECKING:
+ from secops.chronicle.client import ChronicleClient
+
+
+def list_integration_job_instances(
+ client: "ChronicleClient",
+ integration_name: str,
+ job_id: str,
+ page_size: int | None = None,
+ page_token: str | None = None,
+ filter_string: str | None = None,
+ order_by: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ as_list: bool = False,
+) -> dict[str, Any] | list[dict[str, Any]]:
+ """List all job instances for a specific integration job.
+
+ Use this method to browse the active job instances and their last
+ execution status.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the job belongs to.
+ job_id: ID of the job to list instances for.
+ page_size: Maximum number of job instances to return.
+ page_token: Page token from a previous call to retrieve the next page.
+ filter_string: Filter expression to filter job instances.
+ order_by: Field to sort the job instances by.
+ api_version: API version to use for the request. Default is V1BETA.
+ as_list: If True, return a list of job instances instead of a dict
+ with job instances list and nextPageToken.
+
+ Returns:
+ If as_list is True: List of job instances.
+ If as_list is False: Dict with job instances list and nextPageToken.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ extra_params = {
+ "filter": filter_string,
+ "orderBy": order_by,
+ }
+
+ # Remove keys with None values
+ extra_params = {k: v for k, v in extra_params.items() if v is not None}
+
+ return chronicle_paginated_request(
+ client,
+ api_version=api_version,
+ path=(
+ f"integrations/{format_resource_id(integration_name)}/jobs/"
+ f"{job_id}/jobInstances"
+ ),
+ items_key="jobInstances",
+ page_size=page_size,
+ page_token=page_token,
+ extra_params=extra_params,
+ as_list=as_list,
+ )
+
+
+def get_integration_job_instance(
+ client: "ChronicleClient",
+ integration_name: str,
+ job_id: str,
+ job_instance_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Get a single job instance for a specific integration job.
+
+ Use this method to retrieve the execution status, last run time, and
+ active schedule for a specific background task.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the job belongs to.
+ job_id: ID of the job the instance belongs to.
+ job_instance_id: ID of the job instance to retrieve.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing details of the specified IntegrationJobInstance.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return chronicle_request(
+ client,
+ method="GET",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/jobs/"
+ f"{job_id}/jobInstances/{job_instance_id}"
+ ),
+ api_version=api_version,
+ )
+
+
+def delete_integration_job_instance(
+ client: "ChronicleClient",
+ integration_name: str,
+ job_id: str,
+ job_instance_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> None:
+ """Delete a specific job instance for a given integration job.
+
+ Use this method to permanently stop and remove a scheduled background task.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the job belongs to.
+ job_id: ID of the job the instance belongs to.
+ job_instance_id: ID of the job instance to delete.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ None
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ chronicle_request(
+ client,
+ method="DELETE",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/jobs/"
+ f"{job_id}/jobInstances/{job_instance_id}"
+ ),
+ api_version=api_version,
+ )
+
+
+# pylint: disable=line-too-long
+def create_integration_job_instance(
+ client: "ChronicleClient",
+ integration_name: str,
+ job_id: str,
+ display_name: str,
+ interval_seconds: int,
+ enabled: bool,
+ advanced: bool,
+ description: str | None = None,
+ parameters: (
+ list[dict[str, Any] | IntegrationJobInstanceParameter] | None
+ ) = None,
+ advanced_config: dict[str, Any] | AdvancedConfig | None = None,
+ agent: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ # pylint: enable=line-too-long
+ """Create a new job instance for a specific integration job.
+
+ Use this method to schedule a new recurring background job. You must
+ provide a valid execution interval and any required script parameters.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the job belongs to.
+ job_id: ID of the job to create an instance for.
+ display_name: Job instance display name. Required.
+ interval_seconds: Job execution interval in seconds. Minimum 60.
+ Required.
+ enabled: Whether the job instance is enabled. Required.
+ advanced: Whether the job instance uses advanced scheduling. Required.
+ description: Job instance description. Optional.
+ parameters: List of IntegrationJobInstanceParameter instances or
+ dicts. Optional.
+ advanced_config: Advanced scheduling configuration. Accepts an
+ AdvancedConfig instance or a raw dict. Optional.
+ agent: Agent identifier for remote job execution. Cannot be patched
+ after creation. Optional.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the newly created IntegrationJobInstance resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ resolved_parameters = (
+ [
+ p.to_dict() if isinstance(p, IntegrationJobInstanceParameter) else p
+ for p in parameters
+ ]
+ if parameters is not None
+ else None
+ )
+ resolved_advanced_config = (
+ advanced_config.to_dict()
+ if isinstance(advanced_config, AdvancedConfig)
+ else advanced_config
+ )
+
+ body = {
+ "displayName": display_name,
+ "intervalSeconds": interval_seconds,
+ "enabled": enabled,
+ "advanced": advanced,
+ "description": description,
+ "parameters": resolved_parameters,
+ "advancedConfig": resolved_advanced_config,
+ "agent": agent,
+ }
+
+ # Remove keys with None values
+ body = {k: v for k, v in body.items() if v is not None}
+
+ return chronicle_request(
+ client,
+ method="POST",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}"
+ f"/jobs/{job_id}/jobInstances"
+ ),
+ api_version=api_version,
+ json=body,
+ )
+
+
+# pylint: disable=line-too-long
+def update_integration_job_instance(
+ client: "ChronicleClient",
+ integration_name: str,
+ job_id: str,
+ job_instance_id: str,
+ display_name: str | None = None,
+ interval_seconds: int | None = None,
+ enabled: bool | None = None,
+ advanced: bool | None = None,
+ description: str | None = None,
+ parameters: (
+ list[dict[str, Any] | IntegrationJobInstanceParameter] | None
+ ) = None,
+ advanced_config: dict[str, Any] | AdvancedConfig | None = None,
+ update_mask: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ # pylint: enable=line-too-long
+ """Update an existing job instance for a given integration job.
+
+ Use this method to modify the execution interval, enable/disable the job
+ instance, or adjust the parameters passed to the background script.
+
+ Note: The agent field cannot be updated after creation.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the job belongs to.
+ job_id: ID of the job the instance belongs to.
+ job_instance_id: ID of the job instance to update.
+ display_name: Job instance display name.
+ interval_seconds: Job execution interval in seconds. Minimum 60.
+ enabled: Whether the job instance is enabled.
+ advanced: Whether the job instance uses advanced scheduling.
+ description: Job instance description.
+ parameters: List of IntegrationJobInstanceParameter instances or
+ dicts.
+ advanced_config: Advanced scheduling configuration. Accepts an
+ AdvancedConfig instance or a raw dict.
+ update_mask: Comma-separated list of fields to update. If omitted,
+ the mask is auto-generated from whichever fields are provided.
+ Example: "displayName,intervalSeconds".
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the updated IntegrationJobInstance resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ resolved_parameters = (
+ [
+ p.to_dict() if isinstance(p, IntegrationJobInstanceParameter) else p
+ for p in parameters
+ ]
+ if parameters is not None
+ else None
+ )
+ resolved_advanced_config = (
+ advanced_config.to_dict()
+ if isinstance(advanced_config, AdvancedConfig)
+ else advanced_config
+ )
+
+ body, params = build_patch_body(
+ field_map=[
+ ("displayName", "displayName", display_name),
+ ("intervalSeconds", "intervalSeconds", interval_seconds),
+ ("enabled", "enabled", enabled),
+ ("advanced", "advanced", advanced),
+ ("description", "description", description),
+ ("parameters", "parameters", resolved_parameters),
+ ("advancedConfig", "advancedConfig", resolved_advanced_config),
+ ],
+ update_mask=update_mask,
+ )
+
+ return chronicle_request(
+ client,
+ method="PATCH",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"jobs/{job_id}/jobInstances/{job_instance_id}"
+ ),
+ api_version=api_version,
+ json=body,
+ params=params,
+ )
+
+
+# pylint: disable=line-too-long
+def run_integration_job_instance_on_demand(
+ client: "ChronicleClient",
+ integration_name: str,
+ job_id: str,
+ job_instance_id: str,
+ parameters: (
+ list[dict[str, Any] | IntegrationJobInstanceParameter] | None
+ ) = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ # pylint: enable=line-too-long
+ """Execute a job instance immediately, bypassing its normal schedule.
+
+ Use this method to trigger an on-demand run of a job for synchronization
+ or troubleshooting purposes.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the job belongs to.
+ job_id: ID of the job the instance belongs to.
+ job_instance_id: ID of the job instance to run on demand.
+ parameters: List of IntegrationJobInstanceParameter instances or
+ dicts. Optional.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing a success boolean indicating whether the job run
+ completed successfully.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ resolved_parameters = (
+ [
+ p.to_dict() if isinstance(p, IntegrationJobInstanceParameter) else p
+ for p in parameters
+ ]
+ if parameters is not None
+ else None
+ )
+
+ body = {}
+ if resolved_parameters is not None:
+ body["parameters"] = resolved_parameters
+
+ return chronicle_request(
+ client,
+ method="POST",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}"
+ f"/jobs/{job_id}/jobInstances/{job_instance_id}:runOnDemand"
+ ),
+ api_version=api_version,
+ json=body,
+ )
diff --git a/src/secops/chronicle/integration/job_revisions.py b/src/secops/chronicle/integration/job_revisions.py
new file mode 100644
index 00000000..391daacb
--- /dev/null
+++ b/src/secops/chronicle/integration/job_revisions.py
@@ -0,0 +1,204 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Integration job revisions functionality for Chronicle."""
+
+from typing import Any, TYPE_CHECKING
+
+from secops.chronicle.models import APIVersion
+from secops.chronicle.utils.format_utils import (
+ format_resource_id,
+)
+from secops.chronicle.utils.request_utils import (
+ chronicle_paginated_request,
+ chronicle_request,
+)
+
+if TYPE_CHECKING:
+ from secops.chronicle.client import ChronicleClient
+
+
+def list_integration_job_revisions(
+ client: "ChronicleClient",
+ integration_name: str,
+ job_id: str,
+ page_size: int | None = None,
+ page_token: str | None = None,
+ filter_string: str | None = None,
+ order_by: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ as_list: bool = False,
+) -> dict[str, Any] | list[dict[str, Any]]:
+ """List all revisions for a specific integration job.
+
+ Use this method to browse the version history and identify previous
+ configurations of a recurring job.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the job belongs to.
+ job_id: ID of the job to list revisions for.
+ page_size: Maximum number of revisions to return.
+ page_token: Page token from a previous call to retrieve the next page.
+ filter_string: Filter expression to filter revisions.
+ order_by: Field to sort the revisions by.
+ api_version: API version to use for the request. Default is V1BETA.
+ as_list: If True, return a list of revisions instead of a dict with
+ revisions list and nextPageToken.
+
+ Returns:
+ If as_list is True: List of revisions.
+ If as_list is False: Dict with revisions list and nextPageToken.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ extra_params = {
+ "filter": filter_string,
+ "orderBy": order_by,
+ }
+
+ # Remove keys with None values
+ extra_params = {k: v for k, v in extra_params.items() if v is not None}
+
+ return chronicle_paginated_request(
+ client,
+ api_version=api_version,
+ path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"jobs/{job_id}/revisions"
+ ),
+ items_key="revisions",
+ page_size=page_size,
+ page_token=page_token,
+ extra_params=extra_params,
+ as_list=as_list,
+ )
+
+
+def delete_integration_job_revision(
+ client: "ChronicleClient",
+ integration_name: str,
+ job_id: str,
+ revision_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> None:
+ """Delete a specific revision for a given integration job.
+
+ Use this method to clean up obsolete snapshots and manage the historical
+ record of background automation tasks.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the job belongs to.
+ job_id: ID of the job the revision belongs to.
+ revision_id: ID of the revision to delete.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ None
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ chronicle_request(
+ client,
+ method="DELETE",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"jobs/{job_id}/revisions/{revision_id}"
+ ),
+ api_version=api_version,
+ )
+
+
+def create_integration_job_revision(
+ client: "ChronicleClient",
+ integration_name: str,
+ job_id: str,
+ job: dict[str, Any],
+ comment: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Create a new revision snapshot of the current integration job.
+
+ Use this method to establish a recovery point before making significant
+ changes to a background job's script or parameters.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the job belongs to.
+ job_id: ID of the job to create a revision for.
+ job: Dict containing the IntegrationJob to snapshot.
+ comment: Comment describing the revision. Maximum 400 characters.
+ Optional.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the newly created IntegrationJobRevision resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ body = {"job": job}
+
+ if comment is not None:
+ body["comment"] = comment
+
+ return chronicle_request(
+ client,
+ method="POST",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"jobs/{job_id}/revisions"
+ ),
+ api_version=api_version,
+ json=body,
+ )
+
+
+def rollback_integration_job_revision(
+ client: "ChronicleClient",
+ integration_name: str,
+ job_id: str,
+ revision_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Revert the current job definition to a previously saved revision.
+
+ Use this method to rapidly recover a functional automation state if an
+ update causes operational issues.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the job belongs to.
+ job_id: ID of the job to rollback.
+ revision_id: ID of the revision to rollback to.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the IntegrationJobRevision rolled back to.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return chronicle_request(
+ client,
+ method="POST",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"jobs/{job_id}/revisions/{revision_id}:rollback"
+ ),
+ api_version=api_version,
+ )
diff --git a/src/secops/chronicle/integration/jobs.py b/src/secops/chronicle/integration/jobs.py
new file mode 100644
index 00000000..b7600a76
--- /dev/null
+++ b/src/secops/chronicle/integration/jobs.py
@@ -0,0 +1,371 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Integration jobs functionality for Chronicle."""
+
+from typing import Any, TYPE_CHECKING
+
+from secops.chronicle.models import APIVersion, JobParameter
+from secops.chronicle.utils.format_utils import (
+ format_resource_id,
+ build_patch_body,
+)
+from secops.chronicle.utils.request_utils import (
+ chronicle_paginated_request,
+ chronicle_request,
+)
+
+if TYPE_CHECKING:
+ from secops.chronicle.client import ChronicleClient
+
+
+def list_integration_jobs(
+ client: "ChronicleClient",
+ integration_name: str,
+ page_size: int | None = None,
+ page_token: str | None = None,
+ filter_string: str | None = None,
+ order_by: str | None = None,
+ exclude_staging: bool | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ as_list: bool = False,
+) -> dict[str, Any] | list[dict[str, Any]]:
+ """List all jobs defined for a specific integration.
+
+ Use this method to browse the available background and scheduled automation
+ capabilities provided by a third-party connection.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration to list jobs for.
+ page_size: Maximum number of jobs to return.
+ page_token: Page token from a previous call to retrieve the next page.
+ filter_string: Filter expression to filter jobs. Allowed filters are:
+ id, custom, system, author, version, integration.
+ order_by: Field to sort the jobs by.
+ exclude_staging: Whether to exclude staging jobs from the response.
+ By default, staging jobs are included.
+ api_version: API version to use for the request. Default is V1BETA.
+ as_list: If True, return a list of jobs instead of a dict with jobs
+ list and nextPageToken.
+
+ Returns:
+ If as_list is True: List of jobs.
+ If as_list is False: Dict with jobs list and nextPageToken.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ extra_params = {
+ "filter": filter_string,
+ "orderBy": order_by,
+ "excludeStaging": exclude_staging,
+ }
+
+ # Remove keys with None values
+ extra_params = {k: v for k, v in extra_params.items() if v is not None}
+
+ return chronicle_paginated_request(
+ client,
+ api_version=api_version,
+ path=f"integrations/{format_resource_id(integration_name)}/jobs",
+ items_key="jobs",
+ page_size=page_size,
+ page_token=page_token,
+ extra_params=extra_params,
+ as_list=as_list,
+ )
+
+
+def get_integration_job(
+ client: "ChronicleClient",
+ integration_name: str,
+ job_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Get a single job for a given integration.
+
+ Use this method to retrieve the Python script, execution parameters, and
+ versioning information for a background automation task.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the job belongs to.
+ job_id: ID of the job to retrieve.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing details of the specified IntegrationJob.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return chronicle_request(
+ client,
+ method="GET",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"jobs/{job_id}"
+ ),
+ api_version=api_version,
+ )
+
+
+def delete_integration_job(
+ client: "ChronicleClient",
+ integration_name: str,
+ job_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> None:
+ """Delete a specific custom job from a given integration.
+
+ Only custom jobs can be deleted; commercial and system jobs are immutable.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the job belongs to.
+ job_id: ID of the job to delete.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ None
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ chronicle_request(
+ client,
+ method="DELETE",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"jobs/{job_id}"
+ ),
+ api_version=api_version,
+ )
+
+
+def create_integration_job(
+ client: "ChronicleClient",
+ integration_name: str,
+ display_name: str,
+ script: str,
+ version: int,
+ enabled: bool,
+ custom: bool,
+ description: str | None = None,
+ parameters: list[dict[str, Any] | JobParameter] | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Create a new custom job for a given integration.
+
+ Each job must have a unique display name and a functional Python script
+ for its background execution.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration to create the job for.
+ display_name: Job's display name. Maximum 400 characters. Required.
+ script: Job's Python script. Required.
+ version: Job's version. Required.
+ enabled: Whether the job is enabled. Required.
+ custom: Whether the job is custom or commercial. Required.
+ description: Job's description. Optional.
+ parameters: List of JobParameter instances or dicts. Optional.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the newly created IntegrationJob resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ resolved_parameters = (
+ [p.to_dict() if isinstance(p, JobParameter) else p for p in parameters]
+ if parameters is not None
+ else None
+ )
+
+ body = {
+ "displayName": display_name,
+ "script": script,
+ "version": version,
+ "enabled": enabled,
+ "custom": custom,
+ "description": description,
+ "parameters": resolved_parameters,
+ }
+
+ # Remove keys with None values
+ body = {k: v for k, v in body.items() if v is not None}
+
+ return chronicle_request(
+ client,
+ method="POST",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/jobs"
+ ),
+ api_version=api_version,
+ json=body,
+ )
+
+
+def update_integration_job(
+ client: "ChronicleClient",
+ integration_name: str,
+ job_id: str,
+ display_name: str | None = None,
+ script: str | None = None,
+ version: int | None = None,
+ enabled: bool | None = None,
+ custom: bool | None = None,
+ description: str | None = None,
+ parameters: list[dict[str, Any] | JobParameter] | None = None,
+ update_mask: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Update an existing custom job for a given integration.
+
+ Use this method to modify the Python script or adjust the parameter
+ definitions for a job.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the job belongs to.
+ job_id: ID of the job to update.
+ display_name: Job's display name. Maximum 400 characters.
+ script: Job's Python script.
+ version: Job's version.
+ enabled: Whether the job is enabled.
+ custom: Whether the job is custom or commercial.
+ description: Job's description.
+ parameters: List of JobParameter instances or dicts.
+ update_mask: Comma-separated list of fields to update. If omitted,
+ the mask is auto-generated from whichever fields are provided.
+ Example: "displayName,script".
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the updated IntegrationJob resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ resolved_parameters = (
+ [p.to_dict() if isinstance(p, JobParameter) else p for p in parameters]
+ if parameters is not None
+ else None
+ )
+
+ body, params = build_patch_body(
+ field_map=[
+ ("displayName", "displayName", display_name),
+ ("script", "script", script),
+ ("version", "version", version),
+ ("enabled", "enabled", enabled),
+ ("custom", "custom", custom),
+ ("description", "description", description),
+ ("parameters", "parameters", resolved_parameters),
+ ],
+ update_mask=update_mask,
+ )
+
+ return chronicle_request(
+ client,
+ method="PATCH",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"jobs/{job_id}"
+ ),
+ api_version=api_version,
+ json=body,
+ params=params,
+ )
+
+
+def execute_integration_job_test(
+ client: "ChronicleClient",
+ integration_name: str,
+ job: dict[str, Any],
+ agent_identifier: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Execute a test run of an integration job's Python script.
+
+ Use this method to verify background automation logic and connectivity
+ before deploying the job to an instance for recurring execution.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the job belongs to.
+ job: Dict containing the IntegrationJob to test.
+ agent_identifier: Agent identifier for remote testing. Optional.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the test execution results with the following fields:
+ - output: The script output.
+ - debugOutput: The script debug output.
+ - resultObjectJson: The result JSON if it exists (optional).
+ - resultName: The script result name (optional).
+ - resultValue: The script result value (optional).
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ body = {"job": job}
+
+ if agent_identifier is not None:
+ body["agentIdentifier"] = agent_identifier
+
+ return chronicle_request(
+ client,
+ method="POST",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"jobs:executeTest"
+ ),
+ api_version=api_version,
+ json=body,
+ )
+
+
+def get_integration_job_template(
+ client: "ChronicleClient",
+ integration_name: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Retrieve a default Python script template for a new integration job.
+
+ Use this method to rapidly initialize the development of a new job.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration to fetch the template for.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the IntegrationJob template.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return chronicle_request(
+ client,
+ method="GET",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"jobs:fetchTemplate"
+ ),
+ api_version=api_version,
+ )
diff --git a/src/secops/chronicle/integration/logical_operator_revisions.py b/src/secops/chronicle/integration/logical_operator_revisions.py
new file mode 100644
index 00000000..f7f00cee
--- /dev/null
+++ b/src/secops/chronicle/integration/logical_operator_revisions.py
@@ -0,0 +1,212 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Integration logical operator revisions functionality for Chronicle."""
+
+from typing import Any, TYPE_CHECKING
+
+from secops.chronicle.models import APIVersion
+from secops.chronicle.utils.format_utils import format_resource_id
+from secops.chronicle.utils.request_utils import (
+ chronicle_paginated_request,
+ chronicle_request,
+)
+
+if TYPE_CHECKING:
+ from secops.chronicle.client import ChronicleClient
+
+
+def list_integration_logical_operator_revisions(
+ client: "ChronicleClient",
+ integration_name: str,
+ logical_operator_id: str,
+ page_size: int | None = None,
+ page_token: str | None = None,
+ filter_string: str | None = None,
+ order_by: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1ALPHA,
+ as_list: bool = False,
+) -> dict[str, Any] | list[dict[str, Any]]:
+ """List all revisions for a specific integration logical operator.
+
+ Use this method to browse through the version history of a custom logical
+ operator definition.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the logical operator
+ belongs to.
+ logical_operator_id: ID of the logical operator to list revisions for.
+ page_size: Maximum number of revisions to return.
+ page_token: Page token from a previous call to retrieve the next page.
+ filter_string: Filter expression to filter revisions.
+ order_by: Field to sort the revisions by.
+ api_version: API version to use for the request. Default is V1ALPHA.
+ as_list: If True, return a list of revisions instead of a dict with
+ revisions list and nextPageToken.
+
+ Returns:
+ If as_list is True: List of revisions.
+ If as_list is False: Dict with revisions list and nextPageToken.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ extra_params = {
+ "filter": filter_string,
+ "orderBy": order_by,
+ }
+
+ # Remove keys with None values
+ extra_params = {k: v for k, v in extra_params.items() if v is not None}
+
+ return chronicle_paginated_request(
+ client,
+ api_version=api_version,
+ path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"logicalOperators/{logical_operator_id}/revisions"
+ ),
+ items_key="revisions",
+ page_size=page_size,
+ page_token=page_token,
+ extra_params=extra_params,
+ as_list=as_list,
+ )
+
+
+def delete_integration_logical_operator_revision(
+ client: "ChronicleClient",
+ integration_name: str,
+ logical_operator_id: str,
+ revision_id: str,
+ api_version: APIVersion | None = APIVersion.V1ALPHA,
+) -> None:
+ """Delete a specific revision for a given integration logical operator.
+
+ Permanently removes the versioned snapshot from the logical operator's
+ history.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the logical operator
+ belongs to.
+ logical_operator_id: ID of the logical operator the revision belongs
+ to.
+ revision_id: ID of the revision to delete.
+ api_version: API version to use for the request. Default is V1ALPHA.
+
+ Returns:
+ None
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ chronicle_request(
+ client,
+ method="DELETE",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"logicalOperators/{logical_operator_id}/revisions/{revision_id}"
+ ),
+ api_version=api_version,
+ )
+
+
+def create_integration_logical_operator_revision(
+ client: "ChronicleClient",
+ integration_name: str,
+ logical_operator_id: str,
+ logical_operator: dict[str, Any],
+ comment: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1ALPHA,
+) -> dict[str, Any]:
+ """Create a new revision snapshot of the current integration
+ logical operator.
+
+ Use this method to save the current state of a logical operator
+ definition. Revisions can only be created for custom logical operators.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the logical operator
+ belongs to.
+ logical_operator_id: ID of the logical operator to create a revision
+ for.
+ logical_operator: Dict containing the IntegrationLogicalOperator to
+ snapshot.
+ comment: Comment describing the revision. Maximum 400 characters.
+ Optional.
+ api_version: API version to use for the request. Default is V1ALPHA.
+
+ Returns:
+ Dict containing the newly created IntegrationLogicalOperatorRevision
+ resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ body = {"logicalOperator": logical_operator}
+
+ if comment is not None:
+ body["comment"] = comment
+
+ return chronicle_request(
+ client,
+ method="POST",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"logicalOperators/{logical_operator_id}/revisions"
+ ),
+ api_version=api_version,
+ json=body,
+ )
+
+
+def rollback_integration_logical_operator_revision(
+ client: "ChronicleClient",
+ integration_name: str,
+ logical_operator_id: str,
+ revision_id: str,
+ api_version: APIVersion | None = APIVersion.V1ALPHA,
+) -> dict[str, Any]:
+ """Roll back the current logical operator to a previously saved revision.
+
+ This updates the active logical operator definition with the configuration
+ stored in the specified revision.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the logical operator
+ belongs to.
+ logical_operator_id: ID of the logical operator to rollback.
+ revision_id: ID of the revision to rollback to.
+ api_version: API version to use for the request. Default is V1ALPHA.
+
+ Returns:
+ Dict containing the IntegrationLogicalOperatorRevision rolled back to.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return chronicle_request(
+ client,
+ method="POST",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"logicalOperators/{logical_operator_id}/revisions/"
+ f"{revision_id}:rollback"
+ ),
+ api_version=api_version,
+ )
diff --git a/src/secops/chronicle/integration/logical_operators.py b/src/secops/chronicle/integration/logical_operators.py
new file mode 100644
index 00000000..fe5da103
--- /dev/null
+++ b/src/secops/chronicle/integration/logical_operators.py
@@ -0,0 +1,411 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Integration logical operators functionality for Chronicle."""
+
+from typing import Any, TYPE_CHECKING
+
+from secops.chronicle.models import (
+ APIVersion,
+ IntegrationLogicalOperatorParameter,
+)
+from secops.chronicle.utils.format_utils import (
+ format_resource_id,
+ build_patch_body,
+)
+from secops.chronicle.utils.request_utils import (
+ chronicle_paginated_request,
+ chronicle_request,
+)
+
+if TYPE_CHECKING:
+ from secops.chronicle.client import ChronicleClient
+
+
+def list_integration_logical_operators(
+ client: "ChronicleClient",
+ integration_name: str,
+ page_size: int | None = None,
+ page_token: str | None = None,
+ filter_string: str | None = None,
+ order_by: str | None = None,
+ exclude_staging: bool | None = None,
+ expand: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1ALPHA,
+ as_list: bool = False,
+) -> dict[str, Any] | list[dict[str, Any]]:
+ """List all logical operator definitions for a specific integration.
+
+ Use this method to discover the custom logic operators available for use
+ within playbook decision steps.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration to list logical operators
+ for.
+ page_size: Maximum number of logical operators to return. Defaults
+ to 100, maximum is 200.
+ page_token: Page token from a previous call to retrieve the next page.
+ filter_string: Filter expression to filter logical operators.
+ order_by: Field to sort the logical operators by.
+ exclude_staging: Whether to exclude staging logical operators from
+ the response. By default, staging logical operators are included.
+ expand: Expand the response with the full logical operator details.
+ api_version: API version to use for the request. Default is V1ALPHA.
+ as_list: If True, return a list of logical operators instead of a
+ dict with logical operators list and nextPageToken.
+
+ Returns:
+ If as_list is True: List of logical operators.
+ If as_list is False: Dict with logical operators list and
+ nextPageToken.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ extra_params = {
+ "filter": filter_string,
+ "orderBy": order_by,
+ "excludeStaging": exclude_staging,
+ "expand": expand,
+ }
+
+ # Remove keys with None values
+ extra_params = {k: v for k, v in extra_params.items() if v is not None}
+
+ return chronicle_paginated_request(
+ client,
+ api_version=api_version,
+ path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"logicalOperators"
+ ),
+ items_key="logicalOperators",
+ page_size=page_size,
+ page_token=page_token,
+ extra_params=extra_params,
+ as_list=as_list,
+ )
+
+
+def get_integration_logical_operator(
+ client: "ChronicleClient",
+ integration_name: str,
+ logical_operator_id: str,
+ expand: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1ALPHA,
+) -> dict[str, Any]:
+ """Get a single logical operator definition for a specific integration.
+
+ Use this method to retrieve the Python script, evaluation parameters,
+ and description for a custom logical operator.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the logical operator
+ belongs to.
+ logical_operator_id: ID of the logical operator to retrieve.
+ expand: Expand the response with the full logical operator details.
+ Optional.
+ api_version: API version to use for the request. Default is V1ALPHA.
+
+ Returns:
+ Dict containing details of the specified IntegrationLogicalOperator.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ params = {}
+ if expand is not None:
+ params["expand"] = expand
+
+ return chronicle_request(
+ client,
+ method="GET",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"logicalOperators/{logical_operator_id}"
+ ),
+ api_version=api_version,
+ params=params if params else None,
+ )
+
+
+def delete_integration_logical_operator(
+ client: "ChronicleClient",
+ integration_name: str,
+ logical_operator_id: str,
+ api_version: APIVersion | None = APIVersion.V1ALPHA,
+) -> None:
+ """Delete a specific custom logical operator from a given integration.
+
+ Only custom logical operators can be deleted; predefined built-in
+ operators are immutable.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the logical operator
+ belongs to.
+ logical_operator_id: ID of the logical operator to delete.
+ api_version: API version to use for the request. Default is V1ALPHA.
+
+ Returns:
+ None
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ chronicle_request(
+ client,
+ method="DELETE",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"logicalOperators/{logical_operator_id}"
+ ),
+ api_version=api_version,
+ )
+
+
+def create_integration_logical_operator(
+ client: "ChronicleClient",
+ integration_name: str,
+ display_name: str,
+ script: str,
+ script_timeout: str,
+ enabled: bool,
+ description: str | None = None,
+ parameters: (
+ list[dict[str, Any] | IntegrationLogicalOperatorParameter] | None
+ ) = None,
+ api_version: APIVersion | None = APIVersion.V1ALPHA,
+) -> dict[str, Any]:
+ """Create a new custom logical operator for a given integration.
+
+ Each operator must have a unique display name and a functional Python
+ script that returns a boolean result.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration to create the logical
+ operator for.
+ display_name: Logical operator's display name. Maximum 150
+ characters. Required.
+ script: Logical operator's Python script. Required.
+ script_timeout: Timeout in seconds for a single script run. Default
+ is 60. Required.
+ enabled: Whether the logical operator is enabled or disabled.
+ Required.
+ description: Logical operator's description. Maximum 2050 characters.
+ Optional.
+ parameters: List of IntegrationLogicalOperatorParameter instances or
+ dicts. Optional.
+ api_version: API version to use for the request. Default is V1ALPHA.
+
+ Returns:
+ Dict containing the newly created IntegrationLogicalOperator resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ resolved_parameters = (
+ [
+ (
+ p.to_dict()
+ if isinstance(p, IntegrationLogicalOperatorParameter)
+ else p
+ )
+ for p in parameters
+ ]
+ if parameters is not None
+ else None
+ )
+
+ body = {
+ "displayName": display_name,
+ "script": script,
+ "scriptTimeout": script_timeout,
+ "enabled": enabled,
+ "description": description,
+ "parameters": resolved_parameters,
+ }
+
+ # Remove keys with None values
+ body = {k: v for k, v in body.items() if v is not None}
+
+ return chronicle_request(
+ client,
+ method="POST",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"logicalOperators"
+ ),
+ api_version=api_version,
+ json=body,
+ )
+
+
+def update_integration_logical_operator(
+ client: "ChronicleClient",
+ integration_name: str,
+ logical_operator_id: str,
+ display_name: str | None = None,
+ script: str | None = None,
+ script_timeout: str | None = None,
+ enabled: bool | None = None,
+ description: str | None = None,
+ parameters: (
+ list[dict[str, Any] | IntegrationLogicalOperatorParameter] | None
+ ) = None,
+ update_mask: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1ALPHA,
+) -> dict[str, Any]:
+ """Update an existing custom logical operator for a given integration.
+
+ Use this method to modify the logical operator script, refine parameter
+ descriptions, or adjust the timeout for a logical operator.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the logical operator
+ belongs to.
+ logical_operator_id: ID of the logical operator to update.
+ display_name: Logical operator's display name. Maximum 150 characters.
+ script: Logical operator's Python script.
+ script_timeout: Timeout in seconds for a single script run.
+ enabled: Whether the logical operator is enabled or disabled.
+ description: Logical operator's description. Maximum 2050 characters.
+ parameters: List of IntegrationLogicalOperatorParameter instances or
+ dicts. When updating existing parameters, id must be provided
+ in each IntegrationLogicalOperatorParameter.
+ update_mask: Comma-separated list of fields to update. If omitted,
+ the mask is auto-generated from whichever fields are provided.
+ Example: "displayName,script".
+ api_version: API version to use for the request. Default is V1ALPHA.
+
+ Returns:
+ Dict containing the updated IntegrationLogicalOperator resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ resolved_parameters = (
+ [
+ (
+ p.to_dict()
+ if isinstance(p, IntegrationLogicalOperatorParameter)
+ else p
+ )
+ for p in parameters
+ ]
+ if parameters is not None
+ else None
+ )
+
+ body, params = build_patch_body(
+ field_map=[
+ ("displayName", "displayName", display_name),
+ ("script", "script", script),
+ ("scriptTimeout", "scriptTimeout", script_timeout),
+ ("enabled", "enabled", enabled),
+ ("description", "description", description),
+ ("parameters", "parameters", resolved_parameters),
+ ],
+ update_mask=update_mask,
+ )
+
+ return chronicle_request(
+ client,
+ method="PATCH",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"logicalOperators/{logical_operator_id}"
+ ),
+ api_version=api_version,
+ json=body,
+ params=params,
+ )
+
+
+def execute_integration_logical_operator_test(
+ client: "ChronicleClient",
+ integration_name: str,
+ logical_operator: dict[str, Any],
+ api_version: APIVersion | None = APIVersion.V1ALPHA,
+) -> dict[str, Any]:
+ """Execute a test run of a logical operator's evaluation script.
+
+ Use this method to verify decision logic and ensure it correctly handles
+ various input data before deployment in a playbook. The full logical
+ operator object is required as the test can be run without saving the
+ logical operator first.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the logical operator
+ belongs to.
+ logical_operator: Dict containing the IntegrationLogicalOperator to
+ test.
+ api_version: API version to use for the request. Default is V1ALPHA.
+
+ Returns:
+ Dict containing the test execution results with the following fields:
+ - outputMessage: Human-readable output message set by the script.
+ - debugOutputMessage: The script debug output.
+ - resultValue: The script result value.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return chronicle_request(
+ client,
+ method="POST",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"logicalOperators:executeTest"
+ ),
+ api_version=api_version,
+ json={"logicalOperator": logical_operator},
+ )
+
+
+def get_integration_logical_operator_template(
+ client: "ChronicleClient",
+ integration_name: str,
+ api_version: APIVersion | None = APIVersion.V1ALPHA,
+) -> dict[str, Any]:
+ """Retrieve a default Python script template for a new logical operator.
+
+ Use this method to rapidly initialize the development of a new logical
+ operator.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration to fetch the template for.
+ api_version: API version to use for the request. Default is V1ALPHA.
+
+ Returns:
+ Dict containing the IntegrationLogicalOperator template.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return chronicle_request(
+ client,
+ method="GET",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"logicalOperators:fetchTemplate"
+ ),
+ api_version=api_version,
+ )
diff --git a/src/secops/chronicle/integration/manager_revisions.py b/src/secops/chronicle/integration/manager_revisions.py
new file mode 100644
index 00000000..644a8490
--- /dev/null
+++ b/src/secops/chronicle/integration/manager_revisions.py
@@ -0,0 +1,243 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Integration manager revisions functionality for Chronicle."""
+
+from typing import Any, TYPE_CHECKING
+
+from secops.chronicle.models import APIVersion
+from secops.chronicle.utils.format_utils import (
+ format_resource_id,
+)
+from secops.chronicle.utils.request_utils import (
+ chronicle_paginated_request,
+ chronicle_request,
+)
+
+if TYPE_CHECKING:
+ from secops.chronicle.client import ChronicleClient
+
+
+def list_integration_manager_revisions(
+ client: "ChronicleClient",
+ integration_name: str,
+ manager_id: str,
+ page_size: int | None = None,
+ page_token: str | None = None,
+ filter_string: str | None = None,
+ order_by: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ as_list: bool = False,
+) -> dict[str, Any] | list[dict[str, Any]]:
+ """List all revisions for a specific integration manager.
+
+ Use this method to browse the version history and identify previous
+ functional states of a manager.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the manager belongs to.
+ manager_id: ID of the manager to list revisions for.
+ page_size: Maximum number of revisions to return.
+ page_token: Page token from a previous call to retrieve the next page.
+ filter_string: Filter expression to filter revisions.
+ order_by: Field to sort the revisions by.
+ api_version: API version to use for the request. Default is V1BETA.
+ as_list: If True, return a list of revisions instead of a dict with
+ revisions list and nextPageToken.
+
+ Returns:
+ If as_list is True: List of revisions.
+ If as_list is False: Dict with revisions list and nextPageToken.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ extra_params = {
+ "filter": filter_string,
+ "orderBy": order_by,
+ }
+
+ # Remove keys with None values
+ extra_params = {k: v for k, v in extra_params.items() if v is not None}
+
+ return chronicle_paginated_request(
+ client,
+ api_version=api_version,
+ path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"managers/{manager_id}/revisions"
+ ),
+ items_key="revisions",
+ page_size=page_size,
+ page_token=page_token,
+ extra_params=extra_params,
+ as_list=as_list,
+ )
+
+
+def get_integration_manager_revision(
+ client: "ChronicleClient",
+ integration_name: str,
+ manager_id: str,
+ revision_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Get a single revision for a specific integration manager.
+
+ Use this method to retrieve a specific snapshot of an
+ IntegrationManagerRevision for comparison or review.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the manager belongs to.
+ manager_id: ID of the manager the revision belongs to.
+ revision_id: ID of the revision to retrieve.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing details of the specified IntegrationManagerRevision.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return chronicle_request(
+ client,
+ method="GET",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"managers/{manager_id}/revisions/"
+ f"{format_resource_id(revision_id)}"
+ ),
+ api_version=api_version,
+ )
+
+
+def delete_integration_manager_revision(
+ client: "ChronicleClient",
+ integration_name: str,
+ manager_id: str,
+ revision_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> None:
+ """Delete a specific revision for a given integration manager.
+
+ Use this method to clean up obsolete snapshots and manage the historical
+ record of managers.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the manager belongs to.
+ manager_id: ID of the manager the revision belongs to.
+ revision_id: ID of the revision to delete.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ None
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ chronicle_request(
+ client,
+ method="DELETE",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"managers/{manager_id}/revisions/"
+ f"{format_resource_id(revision_id)}"
+ ),
+ api_version=api_version,
+ )
+
+
+def create_integration_manager_revision(
+ client: "ChronicleClient",
+ integration_name: str,
+ manager_id: str,
+ manager: dict[str, Any],
+ comment: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Create a new revision snapshot of the current integration manager.
+
+ Use this method to establish a recovery point before making significant
+ updates to a manager.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the manager belongs to.
+ manager_id: ID of the manager to create a revision for.
+ manager: Dict containing the IntegrationManager to snapshot.
+ comment: Comment describing the revision. Maximum 400 characters.
+ Optional.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the newly created IntegrationManagerRevision resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ body = {"manager": manager}
+
+ if comment is not None:
+ body["comment"] = comment
+
+ return chronicle_request(
+ client,
+ method="POST",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"managers/{manager_id}/revisions"
+ ),
+ api_version=api_version,
+ json=body,
+ )
+
+
+def rollback_integration_manager_revision(
+ client: "ChronicleClient",
+ integration_name: str,
+ manager_id: str,
+ revision_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Revert the current manager definition to a previously saved revision.
+
+ Use this method to rapidly recover a functional state for common code if
+ an update causes operational issues in dependent actions or jobs.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the manager belongs to.
+ manager_id: ID of the manager to rollback.
+ revision_id: ID of the revision to rollback to.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the IntegrationManagerRevision rolled back to.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return chronicle_request(
+ client,
+ method="POST",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"managers/{manager_id}/revisions/"
+ f"{format_resource_id(revision_id)}:rollback"
+ ),
+ api_version=api_version,
+ )
diff --git a/src/secops/chronicle/integration/managers.py b/src/secops/chronicle/integration/managers.py
new file mode 100644
index 00000000..ced5b199
--- /dev/null
+++ b/src/secops/chronicle/integration/managers.py
@@ -0,0 +1,285 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Integration manager functionality for Chronicle."""
+
+from typing import Any, TYPE_CHECKING
+
+from secops.chronicle.models import APIVersion
+from secops.chronicle.utils.format_utils import (
+ format_resource_id,
+ build_patch_body,
+)
+from secops.chronicle.utils.request_utils import (
+ chronicle_paginated_request,
+ chronicle_request,
+)
+
+if TYPE_CHECKING:
+ from secops.chronicle.client import ChronicleClient
+
+
+def list_integration_managers(
+ client: "ChronicleClient",
+ integration_name: str,
+ page_size: int | None = None,
+ page_token: str | None = None,
+ filter_string: str | None = None,
+ order_by: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ as_list: bool = False,
+) -> dict[str, Any] | list[dict[str, Any]]:
+ """List all managers defined for a specific integration.
+
+ Use this method to discover the library of managers available within a
+ particular integration's scope.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration to list managers for.
+ page_size: Maximum number of managers to return. Defaults to 100,
+ maximum is 100.
+ page_token: Page token from a previous call to retrieve the next page.
+ filter_string: Filter expression to filter managers.
+ order_by: Field to sort the managers by.
+ api_version: API version to use for the request. Default is V1BETA.
+ as_list: If True, return a list of managers instead of a dict with
+ managers list and nextPageToken.
+
+ Returns:
+ If as_list is True: List of managers.
+ If as_list is False: Dict with managers list and nextPageToken.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ extra_params = {
+ "filter": filter_string,
+ "orderBy": order_by,
+ }
+
+ # Remove keys with None values
+ extra_params = {k: v for k, v in extra_params.items() if v is not None}
+
+ return chronicle_paginated_request(
+ client,
+ api_version=api_version,
+ path=f"integrations/{format_resource_id(integration_name)}/managers",
+ items_key="managers",
+ page_size=page_size,
+ page_token=page_token,
+ extra_params=extra_params,
+ as_list=as_list,
+ )
+
+
+def get_integration_manager(
+ client: "ChronicleClient",
+ integration_name: str,
+ manager_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Get a single manager for a given integration.
+
+ Use this method to retrieve the manager script and its metadata for
+ review or reference.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the manager belongs to.
+ manager_id: ID of the manager to retrieve.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing details of the specified IntegrationManager.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return chronicle_request(
+ client,
+ method="GET",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"managers/{manager_id}"
+ ),
+ api_version=api_version,
+ )
+
+
+def delete_integration_manager(
+ client: "ChronicleClient",
+ integration_name: str,
+ manager_id: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> None:
+ """Delete a specific custom manager from a given integration.
+
+ Note that deleting a manager may break components (actions, jobs) that
+ depend on its code.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the manager belongs to.
+ manager_id: ID of the manager to delete.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ None
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ chronicle_request(
+ client,
+ method="DELETE",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"managers/{manager_id}"
+ ),
+ api_version=api_version,
+ )
+
+
+def create_integration_manager(
+ client: "ChronicleClient",
+ integration_name: str,
+ display_name: str,
+ script: str,
+ description: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Create a new custom manager for a given integration.
+
+ Use this method to add a new shared code utility. Each manager must have
+ a unique display name and a script containing valid Python logic for reuse
+ across actions, jobs, and connectors.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration to create the manager for.
+ display_name: Manager's display name. Maximum 150 characters. Required.
+ script: Manager's Python script. Maximum 5MB. Required.
+ description: Manager's description. Maximum 400 characters. Optional.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the newly created IntegrationManager resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ body = {
+ "displayName": display_name,
+ "script": script,
+ }
+
+ if description is not None:
+ body["description"] = description
+
+ return chronicle_request(
+ client,
+ method="POST",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/managers"
+ ),
+ api_version=api_version,
+ json=body,
+ )
+
+
+def update_integration_manager(
+ client: "ChronicleClient",
+ integration_name: str,
+ manager_id: str,
+ display_name: str | None = None,
+ script: str | None = None,
+ description: str | None = None,
+ update_mask: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Update an existing custom manager for a given integration.
+
+ Use this method to modify the shared code, adjust its description, or
+ refine its logic across all components that import it.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the manager belongs to.
+ manager_id: ID of the manager to update.
+ display_name: Manager's display name. Maximum 150 characters.
+ script: Manager's Python script. Maximum 5MB.
+ description: Manager's description. Maximum 400 characters.
+ update_mask: Comma-separated list of fields to update. If omitted,
+ the mask is auto-generated from whichever fields are provided.
+ Example: "displayName,script".
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the updated IntegrationManager resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ body, params = build_patch_body(
+ field_map=[
+ ("displayName", "displayName", display_name),
+ ("script", "script", script),
+ ("description", "description", description),
+ ],
+ update_mask=update_mask,
+ )
+
+ return chronicle_request(
+ client,
+ method="PATCH",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"managers/{manager_id}"
+ ),
+ api_version=api_version,
+ json=body,
+ params=params,
+ )
+
+
+def get_integration_manager_template(
+ client: "ChronicleClient",
+ integration_name: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Retrieve a default Python script template for a new integration manager.
+
+ Use this method to quickly start developing new managers.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration to fetch the template for.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Dict containing the IntegrationManager template.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return chronicle_request(
+ client,
+ method="GET",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ "managers:fetchTemplate"
+ ),
+ api_version=api_version,
+ )
diff --git a/src/secops/chronicle/integration/marketplace_integrations.py b/src/secops/chronicle/integration/marketplace_integrations.py
new file mode 100644
index 00000000..fb9006cc
--- /dev/null
+++ b/src/secops/chronicle/integration/marketplace_integrations.py
@@ -0,0 +1,199 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Integrations functionality for Chronicle."""
+
+from typing import Any, TYPE_CHECKING
+
+from secops.chronicle.models import APIVersion
+from secops.chronicle.utils.request_utils import (
+ chronicle_paginated_request,
+ chronicle_request,
+)
+
+if TYPE_CHECKING:
+ from secops.chronicle.client import ChronicleClient
+
+
+def list_marketplace_integrations(
+ client: "ChronicleClient",
+ page_size: int | None = None,
+ page_token: str | None = None,
+ filter_string: str | None = None,
+ order_by: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+ as_list: bool = False,
+) -> dict[str, Any] | list[dict[str, Any]]:
+ """Get a list of marketplace integrations.
+
+ Args:
+ client: ChronicleClient instance
+ page_size: Number of results to return per page
+ page_token: Token for the page to retrieve
+ filter_string: Filter expression to filter marketplace integrations
+ order_by: Field to sort the marketplace integrations by
+ api_version: API version to use for the request. Default is V1BETA.
+ as_list: If True, return a list of marketplace integrations instead
+ of a dict with marketplace integrations list and nextPageToken.
+
+ Returns:
+ If as_list is True: List of marketplace integrations.
+ If as_list is False: Dict with marketplace integrations list and
+ nextPageToken.
+
+ Raises:
+ APIError: If the API request fails
+ """
+ field_map = {
+ "filter": filter_string,
+ "orderBy": order_by,
+ }
+
+ return chronicle_paginated_request(
+ client,
+ api_version=api_version,
+ path="marketplaceIntegrations",
+ items_key="marketplaceIntegrations",
+ page_size=page_size,
+ page_token=page_token,
+ extra_params={k: v for k, v in field_map.items() if v is not None},
+ as_list=as_list,
+ )
+
+
+def get_marketplace_integration(
+ client: "ChronicleClient",
+ integration_name: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Get a marketplace integration by integration name
+
+ Args:
+ client: ChronicleClient instance
+ integration_name: Name of the marketplace integration to retrieve
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Marketplace integration details
+
+ Raises:
+ APIError: If the API request fails
+ """
+ return chronicle_request(
+ client,
+ method="GET",
+ endpoint_path=f"marketplaceIntegrations/{integration_name}",
+ api_version=api_version,
+ )
+
+
+def get_marketplace_integration_diff(
+ client: "ChronicleClient",
+ integration_name: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Get the differences between the currently installed version of
+ an integration and the commercial version available in the marketplace.
+
+ Args:
+ client: ChronicleClient instance
+ integration_name: Name of the marketplace integration to compare
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Marketplace integration diff details
+
+ Raises:
+ APIError: If the API request fails
+ """
+ return chronicle_request(
+ client,
+ method="GET",
+ endpoint_path=f"marketplaceIntegrations/{integration_name}"
+ f":fetchCommercialDiff",
+ api_version=api_version,
+ )
+
+
+def install_marketplace_integration(
+ client: "ChronicleClient",
+ integration_name: str,
+ override_mapping: bool | None = None,
+ staging: bool | None = None,
+ version: str | None = None,
+ restore_from_snapshot: bool | None = None,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Install a marketplace integration by integration name
+
+ Args:
+ client: ChronicleClient instance
+ integration_name: Name of the marketplace integration to install
+ override_mapping: Optional. Determines if the integration should
+ override the ontology if already installed, if not provided, set to
+ false by default.
+ staging: Optional. Determines if the integration should be installed
+ as staging or production, if not provided, installed as production.
+ version: Optional. Determines which version of the integration
+ should be installed.
+ restore_from_snapshot: Optional. Determines if the integration
+ should be installed from existing integration snapshot.
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Installed marketplace integration details
+
+ Raises:
+ APIError: If the API request fails
+ """
+ field_map = {
+ "overrideMapping": override_mapping,
+ "staging": staging,
+ "version": version,
+ "restoreFromSnapshot": restore_from_snapshot,
+ }
+
+ return chronicle_request(
+ client,
+ method="POST",
+ endpoint_path=f"marketplaceIntegrations/{integration_name}:install",
+ json={k: v for k, v in field_map.items() if v is not None},
+ api_version=api_version,
+ )
+
+
+def uninstall_marketplace_integration(
+ client: "ChronicleClient",
+ integration_name: str,
+ api_version: APIVersion | None = APIVersion.V1BETA,
+) -> dict[str, Any]:
+ """Uninstall a marketplace integration by integration name
+
+ Args:
+ client: ChronicleClient instance
+ integration_name: Name of the marketplace integration to uninstall
+ api_version: API version to use for the request. Default is V1BETA.
+
+ Returns:
+ Empty dictionary if uninstallation is successful
+
+ Raises:
+ APIError: If the API request fails
+ """
+ return chronicle_request(
+ client,
+ method="POST",
+ endpoint_path=f"marketplaceIntegrations/{integration_name}:uninstall",
+ api_version=api_version,
+ )
diff --git a/src/secops/chronicle/integration/transformer_revisions.py b/src/secops/chronicle/integration/transformer_revisions.py
new file mode 100644
index 00000000..397d3fbf
--- /dev/null
+++ b/src/secops/chronicle/integration/transformer_revisions.py
@@ -0,0 +1,202 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Integration transformer revisions functionality for Chronicle."""
+
+from typing import Any, TYPE_CHECKING
+
+from secops.chronicle.models import APIVersion
+from secops.chronicle.utils.format_utils import format_resource_id
+from secops.chronicle.utils.request_utils import (
+ chronicle_paginated_request,
+ chronicle_request,
+)
+
+if TYPE_CHECKING:
+ from secops.chronicle.client import ChronicleClient
+
+
+def list_integration_transformer_revisions(
+ client: "ChronicleClient",
+ integration_name: str,
+ transformer_id: str,
+ page_size: int | None = None,
+ page_token: str | None = None,
+ filter_string: str | None = None,
+ order_by: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1ALPHA,
+ as_list: bool = False,
+) -> dict[str, Any] | list[dict[str, Any]]:
+ """List all revisions for a specific integration transformer.
+
+ Use this method to browse through the version history of a custom
+ transformer definition.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the transformer belongs to.
+ transformer_id: ID of the transformer to list revisions for.
+ page_size: Maximum number of revisions to return.
+ page_token: Page token from a previous call to retrieve the next page.
+ filter_string: Filter expression to filter revisions.
+ order_by: Field to sort the revisions by.
+ api_version: API version to use for the request. Default is V1ALPHA.
+ as_list: If True, return a list of revisions instead of a dict with
+ revisions list and nextPageToken.
+
+ Returns:
+ If as_list is True: List of revisions.
+ If as_list is False: Dict with revisions list and nextPageToken.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ extra_params = {
+ "filter": filter_string,
+ "orderBy": order_by,
+ }
+
+ # Remove keys with None values
+ extra_params = {k: v for k, v in extra_params.items() if v is not None}
+
+ return chronicle_paginated_request(
+ client,
+ api_version=api_version,
+ path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"transformers/{transformer_id}/revisions"
+ ),
+ items_key="revisions",
+ page_size=page_size,
+ page_token=page_token,
+ extra_params=extra_params,
+ as_list=as_list,
+ )
+
+
+def delete_integration_transformer_revision(
+ client: "ChronicleClient",
+ integration_name: str,
+ transformer_id: str,
+ revision_id: str,
+ api_version: APIVersion | None = APIVersion.V1ALPHA,
+) -> None:
+ """Delete a specific revision for a given integration transformer.
+
+ Permanently removes the versioned snapshot from the transformer's history.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the transformer belongs to.
+ transformer_id: ID of the transformer the revision belongs to.
+ revision_id: ID of the revision to delete.
+ api_version: API version to use for the request. Default is V1ALPHA.
+
+ Returns:
+ None
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ chronicle_request(
+ client,
+ method="DELETE",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"transformers/{transformer_id}/revisions/{revision_id}"
+ ),
+ api_version=api_version,
+ )
+
+
+def create_integration_transformer_revision(
+ client: "ChronicleClient",
+ integration_name: str,
+ transformer_id: str,
+ transformer: dict[str, Any],
+ comment: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1ALPHA,
+) -> dict[str, Any]:
+ """Create a new revision snapshot of the current integration transformer.
+
+ Use this method to save the current state of a transformer definition.
+ Revisions can only be created for custom transformers.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the transformer belongs to.
+ transformer_id: ID of the transformer to create a revision for.
+ transformer: Dict containing the TransformerDefinition to snapshot.
+ comment: Comment describing the revision. Maximum 400 characters.
+ Optional.
+ api_version: API version to use for the request. Default is V1ALPHA.
+
+ Returns:
+ Dict containing the newly created TransformerRevision resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ body = {"transformer": transformer}
+
+ if comment is not None:
+ body["comment"] = comment
+
+ return chronicle_request(
+ client,
+ method="POST",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"transformers/{transformer_id}/revisions"
+ ),
+ api_version=api_version,
+ json=body,
+ )
+
+
+def rollback_integration_transformer_revision(
+ client: "ChronicleClient",
+ integration_name: str,
+ transformer_id: str,
+ revision_id: str,
+ api_version: APIVersion | None = APIVersion.V1ALPHA,
+) -> dict[str, Any]:
+ """Roll back the current transformer definition to
+ a previously saved revision.
+
+ This updates the active transformer definition with the configuration
+ stored in the specified revision.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the transformer belongs to.
+ transformer_id: ID of the transformer to rollback.
+ revision_id: ID of the revision to rollback to.
+ api_version: API version to use for the request. Default is V1ALPHA.
+
+ Returns:
+ Dict containing the TransformerRevision rolled back to.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return chronicle_request(
+ client,
+ method="POST",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"transformers/{transformer_id}/revisions/{revision_id}:rollback"
+ ),
+ api_version=api_version,
+ )
diff --git a/src/secops/chronicle/integration/transformers.py b/src/secops/chronicle/integration/transformers.py
new file mode 100644
index 00000000..a2a0b817
--- /dev/null
+++ b/src/secops/chronicle/integration/transformers.py
@@ -0,0 +1,406 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Integration transformers functionality for Chronicle."""
+
+from typing import Any, TYPE_CHECKING
+
+from secops.chronicle.models import APIVersion, TransformerDefinitionParameter
+from secops.chronicle.utils.format_utils import (
+ format_resource_id,
+ build_patch_body,
+)
+from secops.chronicle.utils.request_utils import (
+ chronicle_paginated_request,
+ chronicle_request,
+)
+
+if TYPE_CHECKING:
+ from secops.chronicle.client import ChronicleClient
+
+
+def list_integration_transformers(
+ client: "ChronicleClient",
+ integration_name: str,
+ page_size: int | None = None,
+ page_token: str | None = None,
+ filter_string: str | None = None,
+ order_by: str | None = None,
+ exclude_staging: bool | None = None,
+ expand: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1ALPHA,
+) -> dict[str, Any] | list[dict[str, Any]]:
+ """List all transformer definitions for a specific integration.
+
+ Use this method to browse the available transformers.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration to list transformers for.
+ page_size: Maximum number of transformers to return. Defaults to 100,
+ maximum is 200.
+ page_token: Page token from a previous call to retrieve the next page.
+ filter_string: Filter expression to filter transformers.
+ order_by: Field to sort the transformers by.
+ exclude_staging: Whether to exclude staging transformers from the
+ response. By default, staging transformers are included.
+ expand: Expand the response with the full transformer details.
+ api_version: API version to use for the request. Default is V1ALPHA.
+
+ Returns:
+ If as_list is True: List of transformers.
+ If as_list is False: Dict with transformers list and nextPageToken.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ extra_params = {
+ "filter": filter_string,
+ "orderBy": order_by,
+ "excludeStaging": exclude_staging,
+ "expand": expand,
+ }
+
+ # Remove keys with None values
+ extra_params = {k: v for k, v in extra_params.items() if v is not None}
+
+ return chronicle_paginated_request(
+ client,
+ api_version=api_version,
+ path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"transformers"
+ ),
+ items_key="transformers",
+ page_size=page_size,
+ page_token=page_token,
+ extra_params=extra_params,
+ as_list=False,
+ )
+
+
+def get_integration_transformer(
+ client: "ChronicleClient",
+ integration_name: str,
+ transformer_id: str,
+ expand: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1ALPHA,
+) -> dict[str, Any]:
+ """Get a single transformer definition for a specific integration.
+
+ Use this method to retrieve the Python script, input parameters, and
+ expected input, output and usage example schema for a specific data
+ transformation logic within an integration.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the transformer belongs to.
+ transformer_id: ID of the transformer to retrieve.
+ expand: Expand the response with the full transformer details.
+ Optional.
+ api_version: API version to use for the request. Default is V1ALPHA.
+
+ Returns:
+ Dict containing details of the specified TransformerDefinition.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ params = {}
+ if expand is not None:
+ params["expand"] = expand
+
+ return chronicle_request(
+ client,
+ method="GET",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"transformers/{transformer_id}"
+ ),
+ api_version=api_version,
+ params=params if params else None,
+ )
+
+
+def delete_integration_transformer(
+ client: "ChronicleClient",
+ integration_name: str,
+ transformer_id: str,
+ api_version: APIVersion | None = APIVersion.V1ALPHA,
+) -> None:
+ """Delete a custom transformer definition from a given integration.
+
+ Use this method to permanently remove an obsolete transformer from an
+ integration.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the transformer belongs to.
+ transformer_id: ID of the transformer to delete.
+ api_version: API version to use for the request. Default is V1ALPHA.
+
+ Returns:
+ None
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ chronicle_request(
+ client,
+ method="DELETE",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"transformers/{transformer_id}"
+ ),
+ api_version=api_version,
+ )
+
+
+def create_integration_transformer(
+ client: "ChronicleClient",
+ integration_name: str,
+ display_name: str,
+ script: str,
+ script_timeout: str,
+ enabled: bool,
+ description: str | None = None,
+ parameters: (
+ list[dict[str, Any] | TransformerDefinitionParameter] | None
+ ) = None,
+ usage_example: str | None = None,
+ expected_output: str | None = None,
+ expected_input: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1ALPHA,
+) -> dict[str, Any]:
+ """Create a new transformer definition for a given integration.
+
+ Use this method to define a transformer, specifying its functional Python
+ script and necessary input parameters.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration to create the transformer
+ for.
+ display_name: Transformer's display name. Maximum 150 characters.
+ Required.
+ script: Transformer's Python script. Required.
+ script_timeout: Timeout in seconds for a single script run. Default
+ is 60. Required.
+ enabled: Whether the transformer is enabled or disabled. Required.
+ description: Transformer's description. Maximum 2050 characters.
+ Optional.
+ parameters: List of TransformerDefinitionParameter instances or
+ dicts. Optional.
+ usage_example: Transformer's usage example. Optional.
+ expected_output: Transformer's expected output. Optional.
+ expected_input: Transformer's expected input. Optional.
+ api_version: API version to use for the request. Default is V1ALPHA.
+
+ Returns:
+ Dict containing the newly created TransformerDefinition resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ resolved_parameters = (
+ [
+ p.to_dict() if isinstance(p, TransformerDefinitionParameter) else p
+ for p in parameters
+ ]
+ if parameters is not None
+ else None
+ )
+
+ body = {
+ "displayName": display_name,
+ "script": script,
+ "scriptTimeout": script_timeout,
+ "enabled": enabled,
+ "description": description,
+ "parameters": resolved_parameters,
+ "usageExample": usage_example,
+ "expectedOutput": expected_output,
+ "expectedInput": expected_input,
+ }
+
+ # Remove keys with None values
+ body = {k: v for k, v in body.items() if v is not None}
+
+ return chronicle_request(
+ client,
+ method="POST",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/transformers"
+ ),
+ api_version=api_version,
+ json=body,
+ )
+
+
+def update_integration_transformer(
+ client: "ChronicleClient",
+ integration_name: str,
+ transformer_id: str,
+ display_name: str | None = None,
+ script: str | None = None,
+ script_timeout: str | None = None,
+ enabled: bool | None = None,
+ description: str | None = None,
+ parameters: (
+ list[dict[str, Any] | TransformerDefinitionParameter] | None
+ ) = None,
+ usage_example: str | None = None,
+ expected_output: str | None = None,
+ expected_input: str | None = None,
+ update_mask: str | None = None,
+ api_version: APIVersion | None = APIVersion.V1ALPHA,
+) -> dict[str, Any]:
+ """Update an existing transformer definition for a given integration.
+
+ Use this method to modify a transformation's Python script, adjust its
+ description, or refine its parameter definitions.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the transformer belongs to.
+ transformer_id: ID of the transformer to update.
+ display_name: Transformer's display name. Maximum 150 characters.
+ script: Transformer's Python script.
+ script_timeout: Timeout in seconds for a single script run.
+ enabled: Whether the transformer is enabled or disabled.
+ description: Transformer's description. Maximum 2050 characters.
+ parameters: List of TransformerDefinitionParameter instances or
+ dicts. When updating existing parameters, id must be provided
+ in each TransformerDefinitionParameter.
+ usage_example: Transformer's usage example.
+ expected_output: Transformer's expected output.
+ expected_input: Transformer's expected input.
+ update_mask: Comma-separated list of fields to update. If omitted,
+ the mask is auto-generated from whichever fields are provided.
+ Example: "displayName,script".
+ api_version: API version to use for the request. Default is V1ALPHA.
+
+ Returns:
+ Dict containing the updated TransformerDefinition resource.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ resolved_parameters = (
+ [
+ p.to_dict() if isinstance(p, TransformerDefinitionParameter) else p
+ for p in parameters
+ ]
+ if parameters is not None
+ else None
+ )
+
+ body, params = build_patch_body(
+ field_map=[
+ ("displayName", "displayName", display_name),
+ ("script", "script", script),
+ ("scriptTimeout", "scriptTimeout", script_timeout),
+ ("enabled", "enabled", enabled),
+ ("description", "description", description),
+ ("parameters", "parameters", resolved_parameters),
+ ("usageExample", "usageExample", usage_example),
+ ("expectedOutput", "expectedOutput", expected_output),
+ ("expectedInput", "expectedInput", expected_input),
+ ],
+ update_mask=update_mask,
+ )
+
+ return chronicle_request(
+ client,
+ method="PATCH",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"transformers/{transformer_id}"
+ ),
+ api_version=api_version,
+ json=body,
+ params=params,
+ )
+
+
+def execute_integration_transformer_test(
+ client: "ChronicleClient",
+ integration_name: str,
+ transformer: dict[str, Any],
+ api_version: APIVersion | None = APIVersion.V1ALPHA,
+) -> dict[str, Any]:
+ """Execute a test run of a transformer's Python script.
+
+ Use this method to verify transformation logic and ensure data is being
+ parsed and formatted correctly before saving or deploying the transformer.
+ The full transformer object is required as the test can be run without
+ saving the transformer first.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration the transformer belongs to.
+ transformer: Dict containing the TransformerDefinition to test.
+ api_version: API version to use for the request. Default is V1ALPHA.
+
+ Returns:
+ Dict containing the test execution results with the following fields:
+ - outputMessage: Human-readable output message set by the script.
+ - debugOutputMessage: The script debug output.
+ - resultValue: The script result value.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return chronicle_request(
+ client,
+ method="POST",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"transformers:executeTest"
+ ),
+ api_version=api_version,
+ json={"transformer": transformer},
+ )
+
+
+def get_integration_transformer_template(
+ client: "ChronicleClient",
+ integration_name: str,
+ api_version: APIVersion | None = APIVersion.V1ALPHA,
+) -> dict[str, Any]:
+ """Retrieve a default Python script template for a new transformer.
+
+ Use this method to jumpstart the development of a custom data
+ transformation logic by providing boilerplate code.
+
+ Args:
+ client: ChronicleClient instance.
+ integration_name: Name of the integration to fetch the template for.
+ api_version: API version to use for the request. Default is V1ALPHA.
+
+ Returns:
+ Dict containing the TransformerDefinition template.
+
+ Raises:
+ APIError: If the API request fails.
+ """
+ return chronicle_request(
+ client,
+ method="GET",
+ endpoint_path=(
+ f"integrations/{format_resource_id(integration_name)}/"
+ f"transformers:fetchTemplate"
+ ),
+ api_version=api_version,
+ )
diff --git a/src/secops/chronicle/models.py b/src/secops/chronicle/models.py
index 0074bc53..06d81da9 100644
--- a/src/secops/chronicle/models.py
+++ b/src/secops/chronicle/models.py
@@ -13,6 +13,7 @@
# limitations under the License.
#
"""Data models for Chronicle API responses."""
+
import json
import sys
from dataclasses import asdict, dataclass, field
@@ -73,6 +74,686 @@ class DetectionType(StrEnum):
CASE = "DETECTION_TYPE_CASE"
+class PythonVersion(str, Enum):
+ """Python version for compatibility checks."""
+
+ UNSPECIFIED = "PYTHON_VERSION_UNSPECIFIED"
+ PYTHON_2_7 = "V2_7"
+ PYTHON_3_7 = "V3_7"
+ PYTHON_3_11 = "V3_11"
+
+
+class DiffType(str, Enum):
+ """Type of diff to retrieve."""
+
+ COMMERCIAL = "Commercial"
+ PRODUCTION = "Production"
+ STAGING = "Staging"
+
+
+class TargetMode(str, Enum):
+ """Target mode for integration transition."""
+
+ PRODUCTION = "Production"
+ STAGING = "Staging"
+
+
+class IntegrationType(str, Enum):
+ """Type of integration."""
+
+ UNSPECIFIED = "INTEGRATION_TYPE_UNSPECIFIED"
+ RESPONSE = "RESPONSE"
+ EXTENSION = "EXTENSION"
+
+
+class IntegrationParamType(str, Enum):
+ """Type of integration parameter."""
+
+ PARAM_TYPE_UNSPECIFIED = "PARAM_TYPE_UNSPECIFIED"
+ BOOLEAN = "BOOLEAN"
+ INT = "INT"
+ STRING = "STRING"
+ PASSWORD = "PASSWORD"
+ IP = "IP"
+ IP_OR_HOST = "IP_OR_HOST"
+ URL = "URL"
+ DOMAIN = "DOMAIN"
+ EMAIL = "EMAIL"
+ VALUES_LIST = "VALUES_LIST"
+ VALUES_AS_SEMICOLON_SEPARATED_STRING = (
+ "VALUES_AS_SEMICOLON_SEPARATED_STRING"
+ )
+ MULTI_VALUES_SELECTION = "MULTI_VALUES_SELECTION"
+ SCRIPT = "SCRIPT"
+ FILTER_LIST = "FILTER_LIST"
+
+
+@dataclass
+class IntegrationParam:
+ """A parameter definition for a Chronicle SOAR integration.
+
+ Attributes:
+ display_name: Human-readable label shown in the UI.
+ property_name: The programmatic key used in code/config.
+ type: The data type of the parameter (see IntegrationParamType).
+ description: Optional. Explanation of what the parameter is for.
+ mandatory: Whether the parameter must be supplied. Defaults to False.
+ default_value: Optional. Pre-filled value shown in the UI.
+ """
+
+ display_name: str
+ property_name: str
+ type: IntegrationParamType
+ mandatory: bool
+ description: str | None = None
+ default_value: str | None = None
+
+ def to_dict(self) -> dict:
+ """Serialize to the dict shape expected by the Chronicle API."""
+ data: dict = {
+ "displayName": self.display_name,
+ "propertyName": self.property_name,
+ "type": str(self.type.value),
+ "mandatory": self.mandatory,
+ }
+ if self.description is not None:
+ data["description"] = self.description
+ if self.default_value is not None:
+ data["defaultValue"] = self.default_value
+ return data
+
+
+class ActionParamType(str, Enum):
+ """Action parameter types for Chronicle SOAR integration actions."""
+
+ STRING = "STRING"
+ BOOLEAN = "BOOLEAN"
+ WFS_REPOSITORY = "WFS_REPOSITORY"
+ USER_REPOSITORY = "USER_REPOSITORY"
+ STAGES_REPOSITORY = "STAGES_REPOSITORY"
+ CLOSE_CASE_REASON_REPOSITORY = "CLOSE_CASE_REASON_REPOSITORY"
+ CLOSE_CASE_ROOT_CAUSE_REPOSITORY = "CLOSE_CASE_ROOT_CAUSE_REPOSITORY"
+ PRIORITIES_REPOSITORY = "PRIORITIES_REPOSITORY"
+ EMAIL_CONTENT = "EMAIL_CONTENT"
+ CONTENT = "CONTENT"
+ PASSWORD = "PASSWORD"
+ ENTITY_TYPE = "ENTITY_TYPE"
+ MULTI_VALUES = "MULTI_VALUES"
+ LIST = "LIST"
+ CODE = "CODE"
+ MULTIPLE_CHOICE_PARAMETER = "MULTIPLE_CHOICE_PARAMETER"
+
+
+class ActionType(str, Enum):
+ """Action types for Chronicle SOAR integration actions."""
+
+ UNSPECIFIED = "ACTION_TYPE_UNSPECIFIED"
+ STANDARD = "STANDARD"
+ AI_AGENT = "AI_AGENT"
+
+
+@dataclass
+class ActionParameter:
+ """A parameter definition for a Chronicle SOAR integration action.
+
+ Attributes:
+ display_name: The parameter's display name. Maximum 150 characters.
+ type: The parameter's type.
+ description: The parameter's description. Maximum 150 characters.
+ mandatory: Whether the parameter is mandatory.
+ default_value: The default value of the parameter.
+ Maximum 150 characters.
+ optional_values: Parameter's optional values. Maximum 50 items.
+ """
+
+ display_name: str
+ type: ActionParamType
+ description: str
+ mandatory: bool
+ default_value: str | None = None
+ optional_values: list[str] | None = None
+
+ def to_dict(self) -> dict:
+ """Serialize to the dict shape expected by the Chronicle API."""
+ data: dict = {
+ "displayName": self.display_name,
+ "type": str(self.type.value),
+ "description": self.description,
+ "mandatory": self.mandatory,
+ }
+ if self.default_value is not None:
+ data["defaultValue"] = self.default_value
+ if self.optional_values is not None:
+ data["optionalValues"] = self.optional_values
+ return data
+
+
+class ParamType(str, Enum):
+ """Parameter types for Chronicle SOAR integration functions."""
+
+ UNSPECIFIED = "PARAM_TYPE_UNSPECIFIED"
+ BOOLEAN = "BOOLEAN"
+ INT = "INT"
+ STRING = "STRING"
+ PASSWORD = "PASSWORD"
+ IP = "IP"
+ IP_OR_HOST = "IP_OR_HOST"
+ URL = "URL"
+ DOMAIN = "DOMAIN"
+ EMAIL = "EMAIL"
+ VALUES_LIST = "VALUES_LIST"
+ VALUES_AS_SEMICOLON_SEPARATED_STRING = (
+ "VALUES_AS_SEMICOLON_SEPARATED_STRING"
+ )
+ MULTI_VALUES_SELECTION = "MULTI_VALUES_SELECTION"
+ SCRIPT = "SCRIPT"
+ FILTER_LIST = "FILTER_LIST"
+ NUMERICAL_VALUES = "NUMERICAL_VALUES"
+
+
+class ConnectorParamMode(str, Enum):
+ """Parameter modes for Chronicle SOAR integration connectors."""
+
+ UNSPECIFIED = "PARAM_MODE_UNSPECIFIED"
+ REGULAR = "REGULAR"
+ CONNECTIVITY = "CONNECTIVITY"
+ SCRIPT = "SCRIPT"
+
+
+class ConnectorRuleType(str, Enum):
+ """Rule types for Chronicle SOAR integration connectors."""
+
+ UNSPECIFIED = "RULE_TYPE_UNSPECIFIED"
+ ALLOW_LIST = "ALLOW_LIST"
+ BLOCK_LIST = "BLOCK_LIST"
+
+
+@dataclass
+class ConnectorParameter:
+ """A parameter definition for a Chronicle SOAR integration connector.
+
+ Attributes:
+ display_name: The parameter's display name.
+ type: The parameter's type.
+ mode: The parameter's mode.
+ mandatory: Whether the parameter is mandatory for configuring a
+ connector instance.
+ default_value: The default value of the parameter. Required for
+ boolean and mandatory parameters.
+ description: The parameter's description.
+ advanced: The parameter's advanced flag.
+ """
+
+ display_name: str
+ type: ParamType
+ mode: ConnectorParamMode
+ mandatory: bool
+ default_value: str | None = None
+ description: str | None = None
+ advanced: bool | None = None
+
+ def to_dict(self) -> dict:
+ """Serialize to the dict shape expected by the Chronicle API."""
+ data: dict = {
+ "displayName": self.display_name,
+ "type": str(self.type.value),
+ "mode": str(self.mode.value),
+ "mandatory": self.mandatory,
+ }
+ if self.default_value is not None:
+ data["defaultValue"] = self.default_value
+ if self.description is not None:
+ data["description"] = self.description
+ if self.advanced is not None:
+ data["advanced"] = self.advanced
+ return data
+
+
+@dataclass
+class IntegrationJobInstanceParameter:
+ """A parameter instance for a Chronicle SOAR integration job instance.
+
+ Note: Most fields are output-only and will be populated by the API.
+ Only value needs to be provided when configuring a job instance.
+
+ Attributes:
+ value: The value of the parameter.
+ """
+
+ value: str | None = None
+
+ def to_dict(self) -> dict:
+ """Serialize to the dict shape expected by the Chronicle API."""
+ data: dict = {}
+ if self.value is not None:
+ data["value"] = self.value
+ return data
+
+
+class ScheduleType(str, Enum):
+ """Schedule types for Chronicle SOAR integration job
+ instance advanced config."""
+
+ UNSPECIFIED = "SCHEDULE_TYPE_UNSPECIFIED"
+ ONCE = "ONCE"
+ DAILY = "DAILY"
+ WEEKLY = "WEEKLY"
+ MONTHLY = "MONTHLY"
+
+
+class DayOfWeek(str, Enum):
+ """Days of the week for Chronicle SOAR weekly schedule details."""
+
+ UNSPECIFIED = "DAY_OF_WEEK_UNSPECIFIED"
+ MONDAY = "MONDAY"
+ TUESDAY = "TUESDAY"
+ WEDNESDAY = "WEDNESDAY"
+ THURSDAY = "THURSDAY"
+ FRIDAY = "FRIDAY"
+ SATURDAY = "SATURDAY"
+ SUNDAY = "SUNDAY"
+
+
+@dataclass
+class Date:
+ """A calendar date for Chronicle SOAR schedule details.
+
+ Attributes:
+ year: The year.
+ month: The month of the year (1-12).
+ day: The day of the month (1-31).
+ """
+
+ year: int
+ month: int
+ day: int
+
+ def to_dict(self) -> dict:
+ """Serialize to the dict shape expected by the Chronicle API."""
+ return {"year": self.year, "month": self.month, "day": self.day}
+
+
+@dataclass
+class TimeOfDay:
+ """A time of day for Chronicle SOAR schedule details.
+
+ Attributes:
+ hours: The hour of the day (0-23).
+ minutes: The minute of the hour (0-59).
+ seconds: The second of the minute (0-59).
+ nanos: The nanoseconds of the second (0-999999999).
+ """
+
+ hours: int
+ minutes: int
+ seconds: int = 0
+ nanos: int = 0
+
+ def to_dict(self) -> dict:
+ """Serialize to the dict shape expected by the Chronicle API."""
+ return {
+ "hours": self.hours,
+ "minutes": self.minutes,
+ "seconds": self.seconds,
+ "nanos": self.nanos,
+ }
+
+
+@dataclass
+class OneTimeScheduleDetails:
+ """One-time schedule details for a Chronicle SOAR job instance.
+
+ Attributes:
+ start_date: The date to run the job.
+ time: The time to run the job.
+ """
+
+ start_date: Date
+ time: TimeOfDay
+
+ def to_dict(self) -> dict:
+ """Serialize to the dict shape expected by the Chronicle API."""
+ return {
+ "startDate": self.start_date.to_dict(),
+ "time": self.time.to_dict(),
+ }
+
+
+@dataclass
+class DailyScheduleDetails:
+ """Daily schedule details for a Chronicle SOAR job instance.
+
+ Attributes:
+ start_date: The start date.
+ time: The time to run the job.
+ interval: The day interval.
+ """
+
+ start_date: Date
+ time: TimeOfDay
+ interval: int
+
+ def to_dict(self) -> dict:
+ """Serialize to the dict shape expected by the Chronicle API."""
+ return {
+ "startDate": self.start_date.to_dict(),
+ "time": self.time.to_dict(),
+ "interval": self.interval,
+ }
+
+
+@dataclass
+class WeeklyScheduleDetails:
+ """Weekly schedule details for a Chronicle SOAR job instance.
+
+ Attributes:
+ start_date: The start date.
+ days: The days of the week to run the job.
+ time: The time to run the job.
+ interval: The week interval.
+ """
+
+ start_date: Date
+ days: list[DayOfWeek]
+ time: TimeOfDay
+ interval: int
+
+ def to_dict(self) -> dict:
+ """Serialize to the dict shape expected by the Chronicle API."""
+ return {
+ "startDate": self.start_date.to_dict(),
+ "days": [d.value for d in self.days],
+ "time": self.time.to_dict(),
+ "interval": self.interval,
+ }
+
+
+@dataclass
+class MonthlyScheduleDetails:
+ """Monthly schedule details for a Chronicle SOAR job instance.
+
+ Attributes:
+ start_date: The start date.
+ day: The day of the month to run the job.
+ time: The time to run the job.
+ interval: The month interval.
+ """
+
+ start_date: Date
+ day: int
+ time: TimeOfDay
+ interval: int
+
+ def to_dict(self) -> dict:
+ """Serialize to the dict shape expected by the Chronicle API."""
+ return {
+ "startDate": self.start_date.to_dict(),
+ "day": self.day,
+ "time": self.time.to_dict(),
+ "interval": self.interval,
+ }
+
+
+@dataclass
+class AdvancedConfig:
+ """Advanced scheduling configuration for a Chronicle SOAR job instance.
+
+ Exactly one of the schedule detail fields should be provided, corresponding
+ to the schedule_type.
+
+ Attributes:
+ time_zone: The zone id.
+ schedule_type: The schedule type.
+ one_time_schedule: One-time schedule details. Use with ONCE.
+ daily_schedule: Daily schedule details. Use with DAILY.
+ weekly_schedule: Weekly schedule details. Use with WEEKLY.
+ monthly_schedule: Monthly schedule details. Use with MONTHLY.
+ """
+
+ time_zone: str
+ schedule_type: ScheduleType
+ one_time_schedule: OneTimeScheduleDetails | None = None
+ daily_schedule: DailyScheduleDetails | None = None
+ weekly_schedule: WeeklyScheduleDetails | None = None
+ monthly_schedule: MonthlyScheduleDetails | None = None
+
+ def to_dict(self) -> dict:
+ """Serialize to the dict shape expected by the Chronicle API."""
+ data: dict = {
+ "timeZone": self.time_zone,
+ "scheduleType": str(self.schedule_type.value),
+ }
+ if self.one_time_schedule is not None:
+ data["oneTimeSchedule"] = self.one_time_schedule.to_dict()
+ if self.daily_schedule is not None:
+ data["dailySchedule"] = self.daily_schedule.to_dict()
+ if self.weekly_schedule is not None:
+ data["weeklySchedule"] = self.weekly_schedule.to_dict()
+ if self.monthly_schedule is not None:
+ data["monthlySchedule"] = self.monthly_schedule.to_dict()
+ return data
+
+
+@dataclass
+class JobParameter:
+ """A parameter definition for a Chronicle SOAR integration job.
+
+ Attributes:
+ id: The parameter's id.
+ display_name: The parameter's display name.
+ description: The parameter's description.
+ mandatory: Whether the parameter is mandatory.
+ type: The parameter's type.
+ default_value: The default value of the parameter.
+ """
+
+ id: int
+ display_name: str
+ description: str
+ mandatory: bool
+ type: ParamType
+ default_value: str | None = None
+
+ def to_dict(self) -> dict:
+ """Serialize to the dict shape expected by the Chronicle API."""
+ data: dict = {
+ "id": self.id,
+ "displayName": self.display_name,
+ "description": self.description,
+ "mandatory": self.mandatory,
+ "type": str(self.type.value),
+ }
+ if self.default_value is not None:
+ data["defaultValue"] = self.default_value
+ return data
+
+
+class IntegrationParameterType(str, Enum):
+ """Parameter types for Chronicle SOAR integration instances."""
+
+ UNSPECIFIED = "INTEGRATION_PARAMETER_TYPE_UNSPECIFIED"
+ BOOLEAN = "BOOLEAN"
+ INT = "INT"
+ STRING = "STRING"
+ PASSWORD = "PASSWORD"
+ IP = "IP"
+ IP_OR_HOST = "IP_OR_HOST"
+ URL = "URL"
+ DOMAIN = "DOMAIN"
+ EMAIL = "EMAIL"
+ VALUES_LIST = "VALUES_LIST"
+ VALUES_AS_SEMICOLON_SEPARATED_STRING = (
+ "VALUES_AS_SEMICOLON_SEPARATED_STRING"
+ )
+ MULTI_VALUES_SELECTION = "MULTI_VALUES_SELECTION"
+ SCRIPT = "SCRIPT"
+ FILTER_LIST = "FILTER_LIST"
+
+
+@dataclass
+class IntegrationInstanceParameter:
+ """A parameter instance for a Chronicle SOAR integration instance.
+
+ Note: Most fields are output-only and will be populated by the API.
+ Only value needs to be provided when configuring an integration instance.
+
+ Attributes:
+ value: The parameter's value.
+ """
+
+ value: str | None = None
+
+ def to_dict(self) -> dict:
+ """Serialize to the dict shape expected by the Chronicle API."""
+ data: dict = {}
+ if self.value is not None:
+ data["value"] = self.value
+ return data
+
+
+class ConnectorConnectivityStatus(str, Enum):
+ """Connectivity status for Chronicle SOAR connector instances."""
+
+ LIVE = "LIVE"
+ NOT_LIVE = "NOT_LIVE"
+
+
+@dataclass
+class ConnectorInstanceParameter:
+ """A parameter instance for a Chronicle SOAR connector instance.
+
+ Note: Most fields are output-only and will be populated by the API.
+ Only value needs to be provided when configuring a connector instance.
+
+ Attributes:
+ value: The value of the parameter.
+ """
+
+ value: str | None = None
+
+ def to_dict(self) -> dict:
+ """Serialize to the dict shape expected by the Chronicle API."""
+ data: dict = {}
+ if self.value is not None:
+ data["value"] = self.value
+ return data
+
+
+class TransformerType(str, Enum):
+ """Transformer types for Chronicle SOAR integration transformers."""
+
+ UNSPECIFIED = "TRANSFORMER_TYPE_UNSPECIFIED"
+ BUILT_IN = "BUILT_IN"
+ CUSTOM = "CUSTOM"
+
+
+@dataclass
+class TransformerDefinitionParameter:
+ """A parameter definition for a Chronicle SOAR transformer definition.
+
+ Attributes:
+ display_name: The parameter's display name. May contain letters,
+ numbers, and underscores. Maximum 150 characters.
+ mandatory: Whether the parameter is mandatory for configuring a
+ transformer instance.
+ id: The parameter's id. Server-generated on creation; must be
+ provided when updating an existing parameter.
+ default_value: The default value of the parameter. Required for
+ boolean and mandatory parameters.
+ description: The parameter's description. Maximum 2050 characters.
+ """
+
+ display_name: str
+ mandatory: bool
+ id: str | None = None
+ default_value: str | None = None
+ description: str | None = None
+
+ def to_dict(self) -> dict:
+ """Serialize to the dict shape expected by the Chronicle API."""
+ data: dict = {
+ "displayName": self.display_name,
+ "mandatory": self.mandatory,
+ }
+ if self.id is not None:
+ data["id"] = self.id
+ if self.default_value is not None:
+ data["defaultValue"] = self.default_value
+ if self.description is not None:
+ data["description"] = self.description
+ return data
+
+
+class LogicalOperatorType(str, Enum):
+ """Logical operator types for Chronicle SOAR
+ integration logical operators."""
+
+ UNSPECIFIED = "LOGICAL_OPERATOR_TYPE_UNSPECIFIED"
+ BUILT_IN = "BUILT_IN"
+ CUSTOM = "CUSTOM"
+
+
+@dataclass
+class IntegrationLogicalOperatorParameter:
+ """A parameter definition for a Chronicle SOAR logical operator.
+
+ Attributes:
+ display_name: The parameter's display name. May contain letters,
+ numbers, and underscores. Maximum 150 characters.
+ mandatory: Whether the parameter is mandatory for configuring a
+ logical operator instance.
+ id: The parameter's id. Server-generated on creation; must be
+ provided when updating an existing parameter.
+ default_value: The default value of the parameter. Required for
+ boolean and mandatory parameters.
+ order: The parameter's order in the parameters list.
+ description: The parameter's description. Maximum 2050 characters.
+ """
+
+ display_name: str
+ mandatory: bool
+ id: str | None = None
+ default_value: str | None = None
+ order: int | None = None
+ description: str | None = None
+
+ def to_dict(self) -> dict:
+ """Serialize to the dict shape expected by the Chronicle API."""
+ data: dict = {
+ "displayName": self.display_name,
+ "mandatory": self.mandatory,
+ }
+ if self.id is not None:
+ data["id"] = self.id
+ if self.default_value is not None:
+ data["defaultValue"] = self.default_value
+ if self.order is not None:
+ data["order"] = self.order
+ if self.description is not None:
+ data["description"] = self.description
+ return data
+
+
+@dataclass
+class ConnectorRule:
+ """A rule definition for a Chronicle SOAR integration connector.
+
+ Attributes:
+ display_name: Connector's rule data name.
+ type: Connector's rule data type.
+ """
+
+ display_name: str
+ type: ConnectorRuleType
+
+ def to_dict(self) -> dict:
+ """Serialize to the dict shape expected by the Chronicle API."""
+ return {
+ "displayName": self.display_name,
+ "type": str(self.type.value),
+ }
+
+
@dataclass
class TimeInterval:
"""Time interval with start and end times."""
diff --git a/src/secops/chronicle/stats.py b/src/secops/chronicle/stats.py
index 99b46309..42e31aba 100644
--- a/src/secops/chronicle/stats.py
+++ b/src/secops/chronicle/stats.py
@@ -13,6 +13,7 @@
# limitations under the License.
#
"""Statistics functionality for Chronicle searches."""
+
from datetime import datetime
from typing import Any, TYPE_CHECKING
diff --git a/src/secops/chronicle/utils/format_utils.py b/src/secops/chronicle/utils/format_utils.py
index b6567528..126ae503 100644
--- a/src/secops/chronicle/utils/format_utils.py
+++ b/src/secops/chronicle/utils/format_utils.py
@@ -65,3 +65,34 @@ def parse_json_list(
except ValueError as e:
raise APIError(f"Invalid {field_name} JSON") from e
return value
+
+
+# pylint: disable=line-too-long
+def build_patch_body(
+ field_map: list[tuple[str, str, Any]],
+ update_mask: str | None = None,
+) -> tuple[dict[str, Any], dict[str, Any] | None]:
+ """Build a request body and params dict for a PATCH request.
+
+ Args:
+ field_map: List of (api_key, mask_key, value) tuples for
+ each optional field.
+ update_mask: Explicit update mask. If provided,
+ overrides the auto-generated mask.
+
+ Returns:
+ Tuple of (body, params) where params contains the updateMask or is None.
+ """
+ body = {
+ api_key: value for api_key, _, value in field_map if value is not None
+ }
+ mask_fields = [
+ mask_key for _, mask_key, value in field_map if value is not None
+ ]
+
+ resolved_mask = update_mask or (
+ ",".join(mask_fields) if mask_fields else None
+ )
+ params = {"updateMask": resolved_mask} if resolved_mask else None
+
+ return body, params
diff --git a/src/secops/chronicle/utils/request_utils.py b/src/secops/chronicle/utils/request_utils.py
index 43f2d885..70395141 100644
--- a/src/secops/chronicle/utils/request_utils.py
+++ b/src/secops/chronicle/utils/request_utils.py
@@ -14,7 +14,7 @@
#
"""Helper functions for Chronicle."""
-from typing import TYPE_CHECKING, Any
+from typing import TYPE_CHECKING, Any, Optional
import requests
from google.auth.exceptions import GoogleAuthError
@@ -297,3 +297,66 @@ def chronicle_request(
)
return data
+
+
+def chronicle_request_bytes(
+ client: "ChronicleClient",
+ method: str,
+ endpoint_path: str,
+ *,
+ api_version: str = APIVersion.V1,
+ params: Optional[dict[str, Any]] = None,
+ headers: Optional[dict[str, Any]] = None,
+ expected_status: int | set[int] | tuple[int, ...] | list[int] = 200,
+ error_message: str | None = None,
+ timeout: int | None = None,
+) -> bytes:
+ base = f"{client.base_url(api_version)}/{client.instance_id}"
+
+ if endpoint_path.startswith(":"):
+ url = f"{base}{endpoint_path}"
+ else:
+ url = f'{base}/{endpoint_path.lstrip("/")}'
+
+ try:
+ response = client.session.request(
+ method=method,
+ url=url,
+ params=params,
+ headers=headers,
+ timeout=timeout,
+ stream=True,
+ )
+ except GoogleAuthError as exc:
+ base_msg = error_message or "Google authentication failed"
+ raise APIError(f"{base_msg}: authentication_error={exc}") from exc
+ except requests.RequestException as exc:
+ base_msg = error_message or "API request failed"
+ raise APIError(
+ f"{base_msg}: method={method}, url={url}, "
+ f"request_error={exc.__class__.__name__}, detail={exc}"
+ ) from exc
+
+ if isinstance(expected_status, (set, tuple, list)):
+ status_ok = response.status_code in expected_status
+ else:
+ status_ok = response.status_code == expected_status
+
+ if not status_ok:
+ # try json for detail, else preview text
+ try:
+ data = response.json()
+ raise APIError(
+ f"{error_message or "API request failed"}: method={method}, url={url}, "
+ f"status={response.status_code}, response={data}"
+ ) from None
+ except ValueError:
+ preview = _safe_body_preview(
+ getattr(response, "text", ""), limit=MAX_BODY_CHARS
+ )
+ raise APIError(
+ f"{error_message or "API request failed"}: method={method}, url={url}, "
+ f"status={response.status_code}, response_text={preview}"
+ ) from None
+
+ return response.content
diff --git a/src/secops/cli/cli_client.py b/src/secops/cli/cli_client.py
index 4c483656..65b787f2 100644
--- a/src/secops/cli/cli_client.py
+++ b/src/secops/cli/cli_client.py
@@ -39,6 +39,9 @@
from secops.cli.commands.udm_search import setup_udm_search_view_command
from secops.cli.commands.watchlist import setup_watchlist_command
from secops.cli.commands.rule_retrohunt import setup_rule_retrohunt_command
+from secops.cli.commands.integration.integration_client import (
+ setup_integrations_command,
+)
from secops.cli.utils.common_args import add_chronicle_args, add_common_args
from secops.cli.utils.config_utils import load_config
from secops.exceptions import AuthenticationError, SecOpsError
@@ -189,6 +192,7 @@ def build_parser() -> argparse.ArgumentParser:
setup_dashboard_query_command(subparsers)
setup_watchlist_command(subparsers)
setup_rule_retrohunt_command(subparsers)
+ setup_integrations_command(subparsers)
return parser
diff --git a/src/secops/cli/commands/__init__.py b/src/secops/cli/commands/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/src/secops/cli/commands/integration/__init__.py b/src/secops/cli/commands/integration/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/src/secops/cli/commands/integration/action_revisions.py b/src/secops/cli/commands/integration/action_revisions.py
new file mode 100644
index 00000000..d6999bac
--- /dev/null
+++ b/src/secops/cli/commands/integration/action_revisions.py
@@ -0,0 +1,215 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Google SecOps CLI integration action revisions commands"""
+
+import sys
+
+from secops.cli.utils.formatters import output_formatter
+from secops.cli.utils.common_args import (
+ add_pagination_args,
+ add_as_list_arg,
+)
+
+
+def setup_action_revisions_command(subparsers):
+ """Setup integration action revisions command"""
+ revisions_parser = subparsers.add_parser(
+ "action-revisions",
+ help="Manage integration action revisions",
+ )
+ lvl1 = revisions_parser.add_subparsers(
+ dest="action_revisions_command",
+ help="Integration action revisions command",
+ )
+
+ # list command
+ list_parser = lvl1.add_parser(
+ "list", help="List integration action revisions"
+ )
+ list_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ list_parser.add_argument(
+ "--action-id",
+ type=str,
+ help="ID of the action",
+ dest="action_id",
+ required=True,
+ )
+ add_pagination_args(list_parser)
+ add_as_list_arg(list_parser)
+ list_parser.add_argument(
+ "--filter-string",
+ type=str,
+ help="Filter string for listing revisions",
+ dest="filter_string",
+ )
+ list_parser.add_argument(
+ "--order-by",
+ type=str,
+ help="Order by string for listing revisions",
+ dest="order_by",
+ )
+ list_parser.set_defaults(func=handle_action_revisions_list_command)
+
+ # delete command
+ delete_parser = lvl1.add_parser(
+ "delete", help="Delete an integration action revision"
+ )
+ delete_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ delete_parser.add_argument(
+ "--action-id",
+ type=str,
+ help="ID of the action",
+ dest="action_id",
+ required=True,
+ )
+ delete_parser.add_argument(
+ "--revision-id",
+ type=str,
+ help="ID of the revision to delete",
+ dest="revision_id",
+ required=True,
+ )
+ delete_parser.set_defaults(func=handle_action_revisions_delete_command)
+
+ # create command
+ create_parser = lvl1.add_parser(
+ "create", help="Create a new integration action revision"
+ )
+ create_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--action-id",
+ type=str,
+ help="ID of the action",
+ dest="action_id",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--comment",
+ type=str,
+ help="Comment describing the revision",
+ dest="comment",
+ )
+ create_parser.set_defaults(func=handle_action_revisions_create_command)
+
+ # rollback command
+ rollback_parser = lvl1.add_parser(
+ "rollback", help="Rollback action to a previous revision"
+ )
+ rollback_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ rollback_parser.add_argument(
+ "--action-id",
+ type=str,
+ help="ID of the action",
+ dest="action_id",
+ required=True,
+ )
+ rollback_parser.add_argument(
+ "--revision-id",
+ type=str,
+ help="ID of the revision to rollback to",
+ dest="revision_id",
+ required=True,
+ )
+ rollback_parser.set_defaults(func=handle_action_revisions_rollback_command)
+
+
+def handle_action_revisions_list_command(args, chronicle):
+ """Handle integration action revisions list command"""
+ try:
+ out = chronicle.list_integration_action_revisions(
+ integration_name=args.integration_name,
+ action_id=args.action_id,
+ page_size=args.page_size,
+ page_token=args.page_token,
+ filter_string=args.filter_string,
+ order_by=args.order_by,
+ as_list=args.as_list,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error listing action revisions: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_action_revisions_delete_command(args, chronicle):
+ """Handle integration action revision delete command"""
+ try:
+ chronicle.delete_integration_action_revision(
+ integration_name=args.integration_name,
+ action_id=args.action_id,
+ revision_id=args.revision_id,
+ )
+ print(f"Action revision {args.revision_id} deleted successfully")
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error deleting action revision: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_action_revisions_create_command(args, chronicle):
+ """Handle integration action revision create command"""
+ try:
+ # Get the current action to create a revision
+ action = chronicle.get_integration_action(
+ integration_name=args.integration_name,
+ action_id=args.action_id,
+ )
+ out = chronicle.create_integration_action_revision(
+ integration_name=args.integration_name,
+ action_id=args.action_id,
+ action=action,
+ comment=args.comment,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error creating action revision: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_action_revisions_rollback_command(args, chronicle):
+ """Handle integration action revision rollback command"""
+ try:
+ out = chronicle.rollback_integration_action_revision(
+ integration_name=args.integration_name,
+ action_id=args.action_id,
+ revision_id=args.revision_id,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error rolling back action revision: {e}", file=sys.stderr)
+ sys.exit(1)
diff --git a/src/secops/cli/commands/integration/actions.py b/src/secops/cli/commands/integration/actions.py
new file mode 100644
index 00000000..a389c8aa
--- /dev/null
+++ b/src/secops/cli/commands/integration/actions.py
@@ -0,0 +1,382 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Google SecOps CLI integration actions commands"""
+
+import sys
+
+from secops.cli.utils.formatters import output_formatter
+from secops.cli.utils.common_args import (
+ add_pagination_args,
+ add_as_list_arg,
+)
+
+
+def setup_actions_command(subparsers):
+ """Setup integration actions command"""
+ actions_parser = subparsers.add_parser(
+ "actions",
+ help="Manage integration actions",
+ )
+ lvl1 = actions_parser.add_subparsers(
+ dest="actions_command", help="Integration actions command"
+ )
+
+ # list command
+ list_parser = lvl1.add_parser("list", help="List integration actions")
+ list_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ add_pagination_args(list_parser)
+ add_as_list_arg(list_parser)
+ list_parser.add_argument(
+ "--filter-string",
+ type=str,
+ help="Filter string for listing actions",
+ dest="filter_string",
+ )
+ list_parser.add_argument(
+ "--order-by",
+ type=str,
+ help="Order by string for listing actions",
+ dest="order_by",
+ )
+ list_parser.set_defaults(func=handle_actions_list_command)
+
+ # get command
+ get_parser = lvl1.add_parser("get", help="Get integration action details")
+ get_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ get_parser.add_argument(
+ "--action-id",
+ type=str,
+ help="ID of the action to get",
+ dest="action_id",
+ required=True,
+ )
+ get_parser.set_defaults(func=handle_actions_get_command)
+
+ # delete command
+ delete_parser = lvl1.add_parser(
+ "delete",
+ help="Delete an integration action",
+ )
+ delete_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ delete_parser.add_argument(
+ "--action-id",
+ type=str,
+ help="ID of the action to delete",
+ dest="action_id",
+ required=True,
+ )
+ delete_parser.set_defaults(func=handle_actions_delete_command)
+
+ # create command
+ create_parser = lvl1.add_parser(
+ "create", help="Create a new integration action"
+ )
+ create_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--display-name",
+ type=str,
+ help="Display name for the action",
+ dest="display_name",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--code",
+ type=str,
+ help="Python code for the action",
+ dest="code",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--is-async",
+ action="store_true",
+ help="Whether the action is asynchronous",
+ dest="is_async",
+ )
+ create_parser.add_argument(
+ "--description",
+ type=str,
+ help="Description of the action",
+ dest="description",
+ )
+ create_parser.add_argument(
+ "--action-id",
+ type=str,
+ help="Custom ID for the action",
+ dest="action_id",
+ )
+ create_parser.set_defaults(func=handle_actions_create_command)
+
+ # update command
+ update_parser = lvl1.add_parser(
+ "update", help="Update an integration action"
+ )
+ update_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ update_parser.add_argument(
+ "--action-id",
+ type=str,
+ help="ID of the action to update",
+ dest="action_id",
+ required=True,
+ )
+ update_parser.add_argument(
+ "--display-name",
+ type=str,
+ help="New display name for the action",
+ dest="display_name",
+ )
+ update_parser.add_argument(
+ "--script",
+ type=str,
+ help="New Python script for the action",
+ dest="script",
+ )
+ update_parser.add_argument(
+ "--description",
+ type=str,
+ help="New description for the action",
+ dest="description",
+ )
+ update_parser.add_argument(
+ "--update-mask",
+ type=str,
+ help="Comma-separated list of fields to update",
+ dest="update_mask",
+ )
+ update_parser.set_defaults(func=handle_actions_update_command)
+
+ # test command
+ test_parser = lvl1.add_parser(
+ "test", help="Execute an integration action test"
+ )
+ test_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ test_parser.add_argument(
+ "--action-id",
+ type=str,
+ help="ID of the action to test",
+ dest="action_id",
+ required=True,
+ )
+ test_parser.set_defaults(func=handle_actions_test_command)
+
+ # by-environment command
+ by_env_parser = lvl1.add_parser(
+ "by-environment",
+ help="Get integration actions by environment",
+ )
+ by_env_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ by_env_parser.add_argument(
+ "--environments",
+ type=str,
+ nargs="+",
+ help="List of environments to filter by",
+ dest="environments",
+ required=True,
+ )
+ by_env_parser.add_argument(
+ "--include-widgets",
+ action="store_true",
+ help="Whether to include widgets in the response",
+ dest="include_widgets",
+ )
+ by_env_parser.set_defaults(func=handle_actions_by_environment_command)
+
+ # template command
+ template_parser = lvl1.add_parser(
+ "template",
+ help="Get a template for creating an action",
+ )
+ template_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ template_parser.add_argument(
+ "--is-async",
+ action="store_true",
+ help="Whether to fetch template for async action",
+ dest="is_async",
+ )
+ template_parser.set_defaults(func=handle_actions_template_command)
+
+
+def handle_actions_list_command(args, chronicle):
+ """Handle integration actions list command"""
+ try:
+ out = chronicle.list_integration_actions(
+ integration_name=args.integration_name,
+ page_size=args.page_size,
+ page_token=args.page_token,
+ filter_string=args.filter_string,
+ order_by=args.order_by,
+ as_list=args.as_list,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error listing integration actions: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_actions_get_command(args, chronicle):
+ """Handle integration action get command"""
+ try:
+ out = chronicle.get_integration_action(
+ integration_name=args.integration_name,
+ action_id=args.action_id,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error getting integration action: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_actions_delete_command(args, chronicle):
+ """Handle integration action delete command"""
+ try:
+ chronicle.delete_integration_action(
+ integration_name=args.integration_name,
+ action_id=args.action_id,
+ )
+ print(f"Action {args.action_id} deleted successfully")
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error deleting integration action: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_actions_create_command(args, chronicle):
+ """Handle integration action create command"""
+ try:
+ out = chronicle.create_integration_action(
+ integration_name=args.integration_name,
+ display_name=args.display_name,
+ script=args.code, # CLI uses --code flag but API expects script
+ timeout_seconds=300, # Default 5 minutes
+ enabled=True, # Default to enabled
+ script_result_name="result", # Default result field name
+ is_async=args.is_async,
+ description=args.description,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error creating integration action: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_actions_update_command(args, chronicle):
+ """Handle integration action update command"""
+ try:
+ out = chronicle.update_integration_action(
+ integration_name=args.integration_name,
+ action_id=args.action_id,
+ display_name=args.display_name,
+ script=(
+ args.script if args.script else None
+ ), # CLI uses --code flag but API expects script
+ description=args.description,
+ update_mask=args.update_mask,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error updating integration action: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_actions_test_command(args, chronicle):
+ """Handle integration action test command"""
+ try:
+ # First get the action to test
+ action = chronicle.get_integration_action(
+ integration_name=args.integration_name,
+ action_id=args.action_id,
+ )
+ out = chronicle.execute_integration_action_test(
+ integration_name=args.integration_name,
+ action_id=args.action_id,
+ action=action,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error testing integration action: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_actions_by_environment_command(args, chronicle):
+ """Handle get actions by environment command"""
+ try:
+ out = chronicle.get_integration_actions_by_environment(
+ integration_name=args.integration_name,
+ environments=args.environments,
+ include_widgets=args.include_widgets,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error getting actions by environment: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_actions_template_command(args, chronicle):
+ """Handle get action template command"""
+ try:
+ out = chronicle.get_integration_action_template(
+ integration_name=args.integration_name,
+ is_async=args.is_async,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error getting action template: {e}", file=sys.stderr)
+ sys.exit(1)
diff --git a/src/secops/cli/commands/integration/connector_context_properties.py b/src/secops/cli/commands/integration/connector_context_properties.py
new file mode 100644
index 00000000..46b2d936
--- /dev/null
+++ b/src/secops/cli/commands/integration/connector_context_properties.py
@@ -0,0 +1,375 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Google SecOps CLI connector context properties commands"""
+
+import sys
+
+from secops.cli.utils.formatters import output_formatter
+from secops.cli.utils.common_args import (
+ add_pagination_args,
+ add_as_list_arg,
+)
+
+
+def setup_connector_context_properties_command(subparsers):
+ """Setup connector context properties command"""
+ properties_parser = subparsers.add_parser(
+ "connector-context-properties",
+ help="Manage connector context properties",
+ )
+ lvl1 = properties_parser.add_subparsers(
+ dest="connector_context_properties_command",
+ help="Connector context properties command",
+ )
+
+ # list command
+ list_parser = lvl1.add_parser(
+ "list", help="List connector context properties"
+ )
+ list_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ list_parser.add_argument(
+ "--connector-id",
+ type=str,
+ help="ID of the connector",
+ dest="connector_id",
+ required=True,
+ )
+ list_parser.add_argument(
+ "--context-id",
+ type=str,
+ help="Context ID to filter properties",
+ dest="context_id",
+ )
+ add_pagination_args(list_parser)
+ add_as_list_arg(list_parser)
+ list_parser.add_argument(
+ "--filter-string",
+ type=str,
+ help="Filter string for listing properties",
+ dest="filter_string",
+ )
+ list_parser.add_argument(
+ "--order-by",
+ type=str,
+ help="Order by string for listing properties",
+ dest="order_by",
+ )
+ list_parser.set_defaults(
+ func=handle_connector_context_properties_list_command,
+ )
+
+ # get command
+ get_parser = lvl1.add_parser(
+ "get", help="Get a specific connector context property"
+ )
+ get_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ get_parser.add_argument(
+ "--connector-id",
+ type=str,
+ help="ID of the connector",
+ dest="connector_id",
+ required=True,
+ )
+ get_parser.add_argument(
+ "--context-id",
+ type=str,
+ help="Context ID of the property",
+ dest="context_id",
+ required=True,
+ )
+ get_parser.add_argument(
+ "--property-id",
+ type=str,
+ help="ID of the property to get",
+ dest="property_id",
+ required=True,
+ )
+ get_parser.set_defaults(
+ func=handle_connector_context_properties_get_command
+ )
+
+ # delete command
+ delete_parser = lvl1.add_parser(
+ "delete", help="Delete a connector context property"
+ )
+ delete_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ delete_parser.add_argument(
+ "--connector-id",
+ type=str,
+ help="ID of the connector",
+ dest="connector_id",
+ required=True,
+ )
+ delete_parser.add_argument(
+ "--context-id",
+ type=str,
+ help="Context ID of the property",
+ dest="context_id",
+ required=True,
+ )
+ delete_parser.add_argument(
+ "--property-id",
+ type=str,
+ help="ID of the property to delete",
+ dest="property_id",
+ required=True,
+ )
+ delete_parser.set_defaults(
+ func=handle_connector_context_properties_delete_command
+ )
+
+ # create command
+ create_parser = lvl1.add_parser(
+ "create", help="Create a new connector context property"
+ )
+ create_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--connector-id",
+ type=str,
+ help="ID of the connector",
+ dest="connector_id",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--context-id",
+ type=str,
+ help="Context ID for the property",
+ dest="context_id",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--key",
+ type=str,
+ help="Key for the property",
+ dest="key",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--value",
+ type=str,
+ help="Value for the property",
+ dest="value",
+ required=True,
+ )
+ create_parser.set_defaults(
+ func=handle_connector_context_properties_create_command
+ )
+
+ # update command
+ update_parser = lvl1.add_parser(
+ "update", help="Update a connector context property"
+ )
+ update_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ update_parser.add_argument(
+ "--connector-id",
+ type=str,
+ help="ID of the connector",
+ dest="connector_id",
+ required=True,
+ )
+ update_parser.add_argument(
+ "--context-id",
+ type=str,
+ help="Context ID of the property",
+ dest="context_id",
+ required=True,
+ )
+ update_parser.add_argument(
+ "--property-id",
+ type=str,
+ help="ID of the property to update",
+ dest="property_id",
+ required=True,
+ )
+ update_parser.add_argument(
+ "--value",
+ type=str,
+ help="New value for the property",
+ dest="value",
+ required=True,
+ )
+ update_parser.set_defaults(
+ func=handle_connector_context_properties_update_command
+ )
+
+ # clear-all command
+ clear_parser = lvl1.add_parser(
+ "clear-all", help="Delete all connector context properties"
+ )
+ clear_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ clear_parser.add_argument(
+ "--connector-id",
+ type=str,
+ help="ID of the connector",
+ dest="connector_id",
+ required=True,
+ )
+ clear_parser.add_argument(
+ "--context-id",
+ type=str,
+ help="Context ID to clear all properties for",
+ dest="context_id",
+ required=True,
+ )
+ clear_parser.set_defaults(
+ func=handle_connector_context_properties_clear_command
+ )
+
+
+def handle_connector_context_properties_list_command(args, chronicle):
+ """Handle connector context properties list command"""
+ try:
+ out = chronicle.list_connector_context_properties(
+ integration_name=args.integration_name,
+ connector_id=args.connector_id,
+ context_id=args.context_id,
+ page_size=args.page_size,
+ page_token=args.page_token,
+ filter_string=args.filter_string,
+ order_by=args.order_by,
+ as_list=args.as_list,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(
+ f"Error listing connector context properties: {e}", file=sys.stderr
+ )
+ sys.exit(1)
+
+
+def handle_connector_context_properties_get_command(args, chronicle):
+ """Handle connector context property get command"""
+ try:
+ out = chronicle.get_connector_context_property(
+ integration_name=args.integration_name,
+ connector_id=args.connector_id,
+ context_id=args.context_id,
+ context_property_id=args.property_id,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error getting connector context property: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_connector_context_properties_delete_command(args, chronicle):
+ """Handle connector context property delete command"""
+ try:
+ chronicle.delete_connector_context_property(
+ integration_name=args.integration_name,
+ connector_id=args.connector_id,
+ context_id=args.context_id,
+ context_property_id=args.property_id,
+ )
+ print(
+ f"Connector context property "
+ f"{args.property_id} deleted successfully"
+ )
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(
+ f"Error deleting connector context property: {e}", file=sys.stderr
+ )
+ sys.exit(1)
+
+
+def handle_connector_context_properties_create_command(args, chronicle):
+ """Handle connector context property create command"""
+ try:
+ out = chronicle.create_connector_context_property(
+ integration_name=args.integration_name,
+ connector_id=args.connector_id,
+ context_id=args.context_id,
+ key=args.key,
+ value=args.value,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(
+ f"Error creating connector context property: {e}", file=sys.stderr
+ )
+ sys.exit(1)
+
+
+def handle_connector_context_properties_update_command(args, chronicle):
+ """Handle connector context property update command"""
+ try:
+ out = chronicle.update_connector_context_property(
+ integration_name=args.integration_name,
+ connector_id=args.connector_id,
+ context_id=args.context_id,
+ context_property_id=args.property_id,
+ value=args.value,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(
+ f"Error updating connector context property: {e}", file=sys.stderr
+ )
+ sys.exit(1)
+
+
+def handle_connector_context_properties_clear_command(args, chronicle):
+ """Handle clear all connector context properties command"""
+ try:
+ chronicle.delete_all_connector_context_properties(
+ integration_name=args.integration_name,
+ connector_id=args.connector_id,
+ context_id=args.context_id,
+ )
+ print(
+ f"All connector context properties for context "
+ f"{args.context_id} cleared successfully"
+ )
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(
+ f"Error clearing connector context properties: {e}", file=sys.stderr
+ )
+ sys.exit(1)
diff --git a/src/secops/cli/commands/integration/connector_instance_logs.py b/src/secops/cli/commands/integration/connector_instance_logs.py
new file mode 100644
index 00000000..b67e35f2
--- /dev/null
+++ b/src/secops/cli/commands/integration/connector_instance_logs.py
@@ -0,0 +1,142 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Google SecOps CLI connector instance logs commands"""
+
+import sys
+
+from secops.cli.utils.formatters import output_formatter
+from secops.cli.utils.common_args import (
+ add_pagination_args,
+ add_as_list_arg,
+)
+
+
+def setup_connector_instance_logs_command(subparsers):
+ """Setup connector instance logs command"""
+ logs_parser = subparsers.add_parser(
+ "connector-instance-logs",
+ help="View connector instance logs",
+ )
+ lvl1 = logs_parser.add_subparsers(
+ dest="connector_instance_logs_command",
+ help="Connector instance logs command",
+ )
+
+ # list command
+ list_parser = lvl1.add_parser("list", help="List connector instance logs")
+ list_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ list_parser.add_argument(
+ "--connector-id",
+ type=str,
+ help="ID of the connector",
+ dest="connector_id",
+ required=True,
+ )
+ list_parser.add_argument(
+ "--connector-instance-id",
+ type=str,
+ help="ID of the connector instance",
+ dest="connector_instance_id",
+ required=True,
+ )
+ add_pagination_args(list_parser)
+ add_as_list_arg(list_parser)
+ list_parser.add_argument(
+ "--filter-string",
+ type=str,
+ help="Filter string for listing logs",
+ dest="filter_string",
+ )
+ list_parser.add_argument(
+ "--order-by",
+ type=str,
+ help="Order by string for listing logs",
+ dest="order_by",
+ )
+ list_parser.set_defaults(func=handle_connector_instance_logs_list_command)
+
+ # get command
+ get_parser = lvl1.add_parser(
+ "get", help="Get a specific connector instance log"
+ )
+ get_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ get_parser.add_argument(
+ "--connector-id",
+ type=str,
+ help="ID of the connector",
+ dest="connector_id",
+ required=True,
+ )
+ get_parser.add_argument(
+ "--connector-instance-id",
+ type=str,
+ help="ID of the connector instance",
+ dest="connector_instance_id",
+ required=True,
+ )
+ get_parser.add_argument(
+ "--log-id",
+ type=str,
+ help="ID of the log to get",
+ dest="log_id",
+ required=True,
+ )
+ get_parser.set_defaults(func=handle_connector_instance_logs_get_command)
+
+
+def handle_connector_instance_logs_list_command(args, chronicle):
+ """Handle connector instance logs list command"""
+ try:
+ out = chronicle.list_connector_instance_logs(
+ integration_name=args.integration_name,
+ connector_id=args.connector_id,
+ connector_instance_id=args.connector_instance_id,
+ page_size=args.page_size,
+ page_token=args.page_token,
+ filter_string=args.filter_string,
+ order_by=args.order_by,
+ as_list=args.as_list,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error listing connector instance logs: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_connector_instance_logs_get_command(args, chronicle):
+ """Handle connector instance log get command"""
+ try:
+ out = chronicle.get_connector_instance_log(
+ integration_name=args.integration_name,
+ connector_id=args.connector_id,
+ connector_instance_id=args.connector_instance_id,
+ connector_instance_log_id=args.log_id,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error getting connector instance log: {e}", file=sys.stderr)
+ sys.exit(1)
diff --git a/src/secops/cli/commands/integration/connector_instances.py b/src/secops/cli/commands/integration/connector_instances.py
new file mode 100644
index 00000000..df68bfde
--- /dev/null
+++ b/src/secops/cli/commands/integration/connector_instances.py
@@ -0,0 +1,473 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Google SecOps CLI connector instances commands"""
+
+import sys
+
+from secops.cli.utils.formatters import output_formatter
+from secops.cli.utils.common_args import (
+ add_pagination_args,
+ add_as_list_arg,
+)
+
+
+def setup_connector_instances_command(subparsers):
+ """Setup connector instances command"""
+ instances_parser = subparsers.add_parser(
+ "connector-instances",
+ help="Manage connector instances",
+ )
+ lvl1 = instances_parser.add_subparsers(
+ dest="connector_instances_command",
+ help="Connector instances command",
+ )
+
+ # list command
+ list_parser = lvl1.add_parser("list", help="List connector instances")
+ list_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ list_parser.add_argument(
+ "--connector-id",
+ type=str,
+ help="ID of the connector",
+ dest="connector_id",
+ required=True,
+ )
+ add_pagination_args(list_parser)
+ add_as_list_arg(list_parser)
+ list_parser.add_argument(
+ "--filter-string",
+ type=str,
+ help="Filter string for listing instances",
+ dest="filter_string",
+ )
+ list_parser.add_argument(
+ "--order-by",
+ type=str,
+ help="Order by string for listing instances",
+ dest="order_by",
+ )
+ list_parser.set_defaults(func=handle_connector_instances_list_command)
+
+ # get command
+ get_parser = lvl1.add_parser(
+ "get", help="Get a specific connector instance"
+ )
+ get_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ get_parser.add_argument(
+ "--connector-id",
+ type=str,
+ help="ID of the connector",
+ dest="connector_id",
+ required=True,
+ )
+ get_parser.add_argument(
+ "--connector-instance-id",
+ type=str,
+ help="ID of the connector instance to get",
+ dest="connector_instance_id",
+ required=True,
+ )
+ get_parser.set_defaults(func=handle_connector_instances_get_command)
+
+ # delete command
+ delete_parser = lvl1.add_parser(
+ "delete", help="Delete a connector instance"
+ )
+ delete_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ delete_parser.add_argument(
+ "--connector-id",
+ type=str,
+ help="ID of the connector",
+ dest="connector_id",
+ required=True,
+ )
+ delete_parser.add_argument(
+ "--connector-instance-id",
+ type=str,
+ help="ID of the connector instance to delete",
+ dest="connector_instance_id",
+ required=True,
+ )
+ delete_parser.set_defaults(func=handle_connector_instances_delete_command)
+
+ # create command
+ create_parser = lvl1.add_parser(
+ "create", help="Create a new connector instance"
+ )
+ create_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--connector-id",
+ type=str,
+ help="ID of the connector",
+ dest="connector_id",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--environment",
+ type=str,
+ help="Environment for the connector instance",
+ dest="environment",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--display-name",
+ type=str,
+ help="Display name for the connector instance",
+ dest="display_name",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--interval-seconds",
+ type=int,
+ help="Interval in seconds for connector execution",
+ dest="interval_seconds",
+ )
+ create_parser.add_argument(
+ "--timeout-seconds",
+ type=int,
+ help="Timeout in seconds for connector execution",
+ dest="timeout_seconds",
+ )
+ create_parser.add_argument(
+ "--enabled",
+ action="store_true",
+ help="Enable the connector instance",
+ dest="enabled",
+ )
+ create_parser.set_defaults(func=handle_connector_instances_create_command)
+
+ # update command
+ update_parser = lvl1.add_parser(
+ "update", help="Update a connector instance"
+ )
+ update_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ update_parser.add_argument(
+ "--connector-id",
+ type=str,
+ help="ID of the connector",
+ dest="connector_id",
+ required=True,
+ )
+ update_parser.add_argument(
+ "--connector-instance-id",
+ type=str,
+ help="ID of the connector instance to update",
+ dest="connector_instance_id",
+ required=True,
+ )
+ update_parser.add_argument(
+ "--display-name",
+ type=str,
+ help="New display name for the connector instance",
+ dest="display_name",
+ )
+ update_parser.add_argument(
+ "--interval-seconds",
+ type=int,
+ help="New interval in seconds for connector execution",
+ dest="interval_seconds",
+ )
+ update_parser.add_argument(
+ "--timeout-seconds",
+ type=int,
+ help="New timeout in seconds for connector execution",
+ dest="timeout_seconds",
+ )
+ update_parser.add_argument(
+ "--enabled",
+ type=str,
+ choices=["true", "false"],
+ help="Enable or disable the connector instance",
+ dest="enabled",
+ )
+ update_parser.add_argument(
+ "--update-mask",
+ type=str,
+ help="Comma-separated list of fields to update",
+ dest="update_mask",
+ )
+ update_parser.set_defaults(func=handle_connector_instances_update_command)
+
+ # fetch-latest command
+ fetch_parser = lvl1.add_parser(
+ "fetch-latest",
+ help="Get the latest definition of a connector instance",
+ )
+ fetch_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ fetch_parser.add_argument(
+ "--connector-id",
+ type=str,
+ help="ID of the connector",
+ dest="connector_id",
+ required=True,
+ )
+ fetch_parser.add_argument(
+ "--connector-instance-id",
+ type=str,
+ help="ID of the connector instance",
+ dest="connector_instance_id",
+ required=True,
+ )
+ fetch_parser.set_defaults(
+ func=handle_connector_instances_fetch_latest_command
+ )
+
+ # set-logs command
+ logs_parser = lvl1.add_parser(
+ "set-logs",
+ help="Enable or disable log collection for a connector instance",
+ )
+ logs_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ logs_parser.add_argument(
+ "--connector-id",
+ type=str,
+ help="ID of the connector",
+ dest="connector_id",
+ required=True,
+ )
+ logs_parser.add_argument(
+ "--connector-instance-id",
+ type=str,
+ help="ID of the connector instance",
+ dest="connector_instance_id",
+ required=True,
+ )
+ logs_parser.add_argument(
+ "--enabled",
+ type=str,
+ choices=["true", "false"],
+ help="Enable or disable log collection",
+ dest="enabled",
+ required=True,
+ )
+ logs_parser.set_defaults(func=handle_connector_instances_set_logs_command)
+
+ # run-ondemand command
+ run_parser = lvl1.add_parser(
+ "run-ondemand",
+ help="Run a connector instance on demand",
+ )
+ run_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ run_parser.add_argument(
+ "--connector-id",
+ type=str,
+ help="ID of the connector",
+ dest="connector_id",
+ required=True,
+ )
+ run_parser.add_argument(
+ "--connector-instance-id",
+ type=str,
+ help="ID of the connector instance to run",
+ dest="connector_instance_id",
+ required=True,
+ )
+ run_parser.set_defaults(
+ func=handle_connector_instances_run_ondemand_command
+ )
+
+
+def handle_connector_instances_list_command(args, chronicle):
+ """Handle connector instances list command"""
+ try:
+ out = chronicle.list_connector_instances(
+ integration_name=args.integration_name,
+ connector_id=args.connector_id,
+ page_size=args.page_size,
+ page_token=args.page_token,
+ filter_string=args.filter_string,
+ order_by=args.order_by,
+ as_list=args.as_list,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error listing connector instances: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_connector_instances_get_command(args, chronicle):
+ """Handle connector instance get command"""
+ try:
+ out = chronicle.get_connector_instance(
+ integration_name=args.integration_name,
+ connector_id=args.connector_id,
+ connector_instance_id=args.connector_instance_id,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error getting connector instance: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_connector_instances_delete_command(args, chronicle):
+ """Handle connector instance delete command"""
+ try:
+ chronicle.delete_connector_instance(
+ integration_name=args.integration_name,
+ connector_id=args.connector_id,
+ connector_instance_id=args.connector_instance_id,
+ )
+ print(
+ f"Connector instance {args.connector_instance_id}"
+ f" deleted successfully"
+ )
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error deleting connector instance: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_connector_instances_create_command(args, chronicle):
+ """Handle connector instance create command"""
+ try:
+ out = chronicle.create_connector_instance(
+ integration_name=args.integration_name,
+ connector_id=args.connector_id,
+ environment=args.environment,
+ display_name=args.display_name,
+ interval_seconds=args.interval_seconds,
+ timeout_seconds=args.timeout_seconds,
+ enabled=args.enabled,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error creating connector instance: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_connector_instances_update_command(args, chronicle):
+ """Handle connector instance update command"""
+ try:
+ # Convert enabled string to boolean if provided
+ enabled = None
+ if args.enabled:
+ enabled = args.enabled.lower() == "true"
+
+ out = chronicle.update_connector_instance(
+ integration_name=args.integration_name,
+ connector_id=args.connector_id,
+ connector_instance_id=args.connector_instance_id,
+ display_name=args.display_name,
+ interval_seconds=args.interval_seconds,
+ timeout_seconds=args.timeout_seconds,
+ enabled=enabled,
+ update_mask=args.update_mask,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error updating connector instance: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_connector_instances_fetch_latest_command(args, chronicle):
+ """Handle fetch latest connector instance definition command"""
+ try:
+ out = chronicle.get_connector_instance_latest_definition(
+ integration_name=args.integration_name,
+ connector_id=args.connector_id,
+ connector_instance_id=args.connector_instance_id,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error fetching latest connector instance: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_connector_instances_set_logs_command(args, chronicle):
+ """Handle set connector instance logs collection command"""
+ try:
+ enabled = args.enabled.lower() == "true"
+ out = chronicle.set_connector_instance_logs_collection(
+ integration_name=args.integration_name,
+ connector_id=args.connector_id,
+ connector_instance_id=args.connector_instance_id,
+ enabled=enabled,
+ )
+ status = "enabled" if enabled else "disabled"
+ print(f"Log collection {status} for connector instance successfully")
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error setting connector instance logs: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_connector_instances_run_ondemand_command(args, chronicle):
+ """Handle run connector instance on demand command"""
+ try:
+ # Get the connector instance first
+ connector_instance = chronicle.get_connector_instance(
+ integration_name=args.integration_name,
+ connector_id=args.connector_id,
+ connector_instance_id=args.connector_instance_id,
+ )
+ out = chronicle.run_connector_instance_on_demand(
+ integration_name=args.integration_name,
+ connector_id=args.connector_id,
+ connector_instance_id=args.connector_instance_id,
+ connector_instance=connector_instance,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(
+ f"Error running connector instance on demand: {e}", file=sys.stderr
+ )
+ sys.exit(1)
diff --git a/src/secops/cli/commands/integration/connector_revisions.py b/src/secops/cli/commands/integration/connector_revisions.py
new file mode 100644
index 00000000..779888c9
--- /dev/null
+++ b/src/secops/cli/commands/integration/connector_revisions.py
@@ -0,0 +1,217 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Google SecOps CLI integration connector revisions commands"""
+
+import sys
+
+from secops.cli.utils.formatters import output_formatter
+from secops.cli.utils.common_args import (
+ add_pagination_args,
+ add_as_list_arg,
+)
+
+
+def setup_connector_revisions_command(subparsers):
+ """Setup integration connector revisions command"""
+ revisions_parser = subparsers.add_parser(
+ "connector-revisions",
+ help="Manage integration connector revisions",
+ )
+ lvl1 = revisions_parser.add_subparsers(
+ dest="connector_revisions_command",
+ help="Integration connector revisions command",
+ )
+
+ # list command
+ list_parser = lvl1.add_parser(
+ "list", help="List integration connector revisions"
+ )
+ list_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ list_parser.add_argument(
+ "--connector-id",
+ type=str,
+ help="ID of the connector",
+ dest="connector_id",
+ required=True,
+ )
+ add_pagination_args(list_parser)
+ add_as_list_arg(list_parser)
+ list_parser.add_argument(
+ "--filter-string",
+ type=str,
+ help="Filter string for listing revisions",
+ dest="filter_string",
+ )
+ list_parser.add_argument(
+ "--order-by",
+ type=str,
+ help="Order by string for listing revisions",
+ dest="order_by",
+ )
+ list_parser.set_defaults(func=handle_connector_revisions_list_command)
+
+ # delete command
+ delete_parser = lvl1.add_parser(
+ "delete", help="Delete an integration connector revision"
+ )
+ delete_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ delete_parser.add_argument(
+ "--connector-id",
+ type=str,
+ help="ID of the connector",
+ dest="connector_id",
+ required=True,
+ )
+ delete_parser.add_argument(
+ "--revision-id",
+ type=str,
+ help="ID of the revision to delete",
+ dest="revision_id",
+ required=True,
+ )
+ delete_parser.set_defaults(func=handle_connector_revisions_delete_command)
+
+ # create command
+ create_parser = lvl1.add_parser(
+ "create", help="Create a new integration connector revision"
+ )
+ create_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--connector-id",
+ type=str,
+ help="ID of the connector",
+ dest="connector_id",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--comment",
+ type=str,
+ help="Comment describing the revision",
+ dest="comment",
+ )
+ create_parser.set_defaults(func=handle_connector_revisions_create_command)
+
+ # rollback command
+ rollback_parser = lvl1.add_parser(
+ "rollback", help="Rollback connector to a previous revision"
+ )
+ rollback_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ rollback_parser.add_argument(
+ "--connector-id",
+ type=str,
+ help="ID of the connector",
+ dest="connector_id",
+ required=True,
+ )
+ rollback_parser.add_argument(
+ "--revision-id",
+ type=str,
+ help="ID of the revision to rollback to",
+ dest="revision_id",
+ required=True,
+ )
+ rollback_parser.set_defaults(
+ func=handle_connector_revisions_rollback_command,
+ )
+
+
+def handle_connector_revisions_list_command(args, chronicle):
+ """Handle integration connector revisions list command"""
+ try:
+ out = chronicle.list_integration_connector_revisions(
+ integration_name=args.integration_name,
+ connector_id=args.connector_id,
+ page_size=args.page_size,
+ page_token=args.page_token,
+ filter_string=args.filter_string,
+ order_by=args.order_by,
+ as_list=args.as_list,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error listing connector revisions: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_connector_revisions_delete_command(args, chronicle):
+ """Handle integration connector revision delete command"""
+ try:
+ chronicle.delete_integration_connector_revision(
+ integration_name=args.integration_name,
+ connector_id=args.connector_id,
+ revision_id=args.revision_id,
+ )
+ print(f"Connector revision {args.revision_id} deleted successfully")
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error deleting connector revision: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_connector_revisions_create_command(args, chronicle):
+ """Handle integration connector revision create command"""
+ try:
+ # Get the current connector to create a revision
+ connector = chronicle.get_integration_connector(
+ integration_name=args.integration_name,
+ connector_id=args.connector_id,
+ )
+ out = chronicle.create_integration_connector_revision(
+ integration_name=args.integration_name,
+ connector_id=args.connector_id,
+ connector=connector,
+ comment=args.comment,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error creating connector revision: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_connector_revisions_rollback_command(args, chronicle):
+ """Handle integration connector revision rollback command"""
+ try:
+ out = chronicle.rollback_integration_connector_revision(
+ integration_name=args.integration_name,
+ connector_id=args.connector_id,
+ revision_id=args.revision_id,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error rolling back connector revision: {e}", file=sys.stderr)
+ sys.exit(1)
diff --git a/src/secops/cli/commands/integration/connectors.py b/src/secops/cli/commands/integration/connectors.py
new file mode 100644
index 00000000..fe8e03ef
--- /dev/null
+++ b/src/secops/cli/commands/integration/connectors.py
@@ -0,0 +1,325 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Google SecOps CLI integration connectors commands"""
+
+import sys
+
+from secops.cli.utils.formatters import output_formatter
+from secops.cli.utils.common_args import (
+ add_pagination_args,
+ add_as_list_arg,
+)
+
+
+def setup_connectors_command(subparsers):
+ """Setup integration connectors command"""
+ connectors_parser = subparsers.add_parser(
+ "connectors",
+ help="Manage integration connectors",
+ )
+ lvl1 = connectors_parser.add_subparsers(
+ dest="connectors_command", help="Integration connectors command"
+ )
+
+ # list command
+ list_parser = lvl1.add_parser("list", help="List integration connectors")
+ list_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ add_pagination_args(list_parser)
+ add_as_list_arg(list_parser)
+ list_parser.add_argument(
+ "--filter-string",
+ type=str,
+ help="Filter string for listing connectors",
+ dest="filter_string",
+ )
+ list_parser.add_argument(
+ "--order-by",
+ type=str,
+ help="Order by string for listing connectors",
+ dest="order_by",
+ )
+ list_parser.set_defaults(
+ func=handle_connectors_list_command,
+ )
+
+ # get command
+ get_parser = lvl1.add_parser(
+ "get", help="Get integration connector details"
+ )
+ get_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ get_parser.add_argument(
+ "--connector-id",
+ type=str,
+ help="ID of the connector to get",
+ dest="connector_id",
+ required=True,
+ )
+ get_parser.set_defaults(func=handle_connectors_get_command)
+
+ # delete command
+ delete_parser = lvl1.add_parser(
+ "delete", help="Delete an integration connector"
+ )
+ delete_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ delete_parser.add_argument(
+ "--connector-id",
+ type=str,
+ help="ID of the connector to delete",
+ dest="connector_id",
+ required=True,
+ )
+ delete_parser.set_defaults(func=handle_connectors_delete_command)
+
+ # create command
+ create_parser = lvl1.add_parser(
+ "create", help="Create a new integration connector"
+ )
+ create_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--display-name",
+ type=str,
+ help="Display name for the connector",
+ dest="display_name",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--code",
+ type=str,
+ help="Python code for the connector",
+ dest="code",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--description",
+ type=str,
+ help="Description of the connector",
+ dest="description",
+ )
+ create_parser.add_argument(
+ "--connector-id",
+ type=str,
+ help="Custom ID for the connector",
+ dest="connector_id",
+ )
+ create_parser.set_defaults(func=handle_connectors_create_command)
+
+ # update command
+ update_parser = lvl1.add_parser(
+ "update", help="Update an integration connector"
+ )
+ update_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ update_parser.add_argument(
+ "--connector-id",
+ type=str,
+ help="ID of the connector to update",
+ dest="connector_id",
+ required=True,
+ )
+ update_parser.add_argument(
+ "--display-name",
+ type=str,
+ help="New display name for the connector",
+ dest="display_name",
+ )
+ update_parser.add_argument(
+ "--code",
+ type=str,
+ help="New Python code for the connector",
+ dest="code",
+ )
+ update_parser.add_argument(
+ "--description",
+ type=str,
+ help="New description for the connector",
+ dest="description",
+ )
+ update_parser.add_argument(
+ "--update-mask",
+ type=str,
+ help="Comma-separated list of fields to update",
+ dest="update_mask",
+ )
+ update_parser.set_defaults(func=handle_connectors_update_command)
+
+ # test command
+ test_parser = lvl1.add_parser(
+ "test", help="Execute an integration connector test"
+ )
+ test_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ test_parser.add_argument(
+ "--connector-id",
+ type=str,
+ help="ID of the connector to test",
+ dest="connector_id",
+ required=True,
+ )
+ test_parser.set_defaults(func=handle_connectors_test_command)
+
+ # template command
+ template_parser = lvl1.add_parser(
+ "template",
+ help="Get a template for creating a connector",
+ )
+ template_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ template_parser.set_defaults(func=handle_connectors_template_command)
+
+
+def handle_connectors_list_command(args, chronicle):
+ """Handle integration connectors list command"""
+ try:
+ out = chronicle.list_integration_connectors(
+ integration_name=args.integration_name,
+ page_size=args.page_size,
+ page_token=args.page_token,
+ filter_string=args.filter_string,
+ order_by=args.order_by,
+ as_list=args.as_list,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error listing integration connectors: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_connectors_get_command(args, chronicle):
+ """Handle integration connector get command"""
+ try:
+ out = chronicle.get_integration_connector(
+ integration_name=args.integration_name,
+ connector_id=args.connector_id,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error getting integration connector: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_connectors_delete_command(args, chronicle):
+ """Handle integration connector delete command"""
+ try:
+ chronicle.delete_integration_connector(
+ integration_name=args.integration_name,
+ connector_id=args.connector_id,
+ )
+ print(f"Connector {args.connector_id} deleted successfully")
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error deleting integration connector: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_connectors_create_command(args, chronicle):
+ """Handle integration connector create command"""
+ try:
+ out = chronicle.create_integration_connector(
+ integration_name=args.integration_name,
+ display_name=args.display_name,
+ code=args.code,
+ description=args.description,
+ connector_id=args.connector_id,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error creating integration connector: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_connectors_update_command(args, chronicle):
+ """Handle integration connector update command"""
+ try:
+ out = chronicle.update_integration_connector(
+ integration_name=args.integration_name,
+ connector_id=args.connector_id,
+ display_name=args.display_name,
+ code=args.code,
+ description=args.description,
+ update_mask=args.update_mask,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error updating integration connector: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_connectors_test_command(args, chronicle):
+ """Handle integration connector test command"""
+ try:
+ # First get the connector to test
+ connector = chronicle.get_integration_connector(
+ integration_name=args.integration_name,
+ connector_id=args.connector_id,
+ )
+ out = chronicle.execute_integration_connector_test(
+ integration_name=args.integration_name,
+ connector_id=args.connector_id,
+ connector=connector,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error testing integration connector: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_connectors_template_command(args, chronicle):
+ """Handle get connector template command"""
+ try:
+ out = chronicle.get_integration_connector_template(
+ integration_name=args.integration_name,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error getting connector template: {e}", file=sys.stderr)
+ sys.exit(1)
diff --git a/src/secops/cli/commands/integration/integration.py b/src/secops/cli/commands/integration/integration.py
new file mode 100644
index 00000000..dd73a600
--- /dev/null
+++ b/src/secops/cli/commands/integration/integration.py
@@ -0,0 +1,775 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Google SecOps CLI integration commands"""
+
+import sys
+
+from secops.chronicle.models import (
+ DiffType,
+ IntegrationType,
+ PythonVersion,
+ TargetMode,
+)
+from secops.cli.utils.formatters import output_formatter
+from secops.cli.utils.common_args import (
+ add_pagination_args,
+ add_as_list_arg,
+)
+
+
+def setup_integrations_command(subparsers):
+ """Setup integrations command"""
+ integrations_parser = subparsers.add_parser(
+ "integrations", help="Manage SecOps integrations"
+ )
+ lvl1 = integrations_parser.add_subparsers(
+ dest="integrations_command", help="Integrations command"
+ )
+
+ # list command
+ list_parser = lvl1.add_parser("list", help="List integrations")
+ add_pagination_args(list_parser)
+ add_as_list_arg(list_parser)
+ list_parser.add_argument(
+ "--filter-string",
+ type=str,
+ help="Filter string for listing integrations",
+ dest="filter_string",
+ )
+ list_parser.add_argument(
+ "--order-by",
+ type=str,
+ help="Order by string for listing integrations",
+ dest="order_by",
+ )
+ list_parser.set_defaults(func=handle_integration_list_command)
+
+ # get command
+ get_parser = lvl1.add_parser("get", help="Get integration details")
+ get_parser.add_argument(
+ "--integration-id",
+ type=str,
+ help="ID of the integration to get details for",
+ dest="integration_id",
+ required=True,
+ )
+ get_parser.set_defaults(func=handle_integration_get_command)
+
+ # delete command
+ delete_parser = lvl1.add_parser("delete", help="Delete an integration")
+ delete_parser.add_argument(
+ "--integration-id",
+ type=str,
+ help="ID of the integration to delete",
+ dest="integration_id",
+ required=True,
+ )
+ delete_parser.set_defaults(func=handle_integration_delete_command)
+
+ # create command
+ create_parser = lvl1.add_parser(
+ "create", help="Create a new custom integration"
+ )
+ create_parser.add_argument(
+ "--display-name",
+ type=str,
+ help="Display name for the integration (max 150 characters)",
+ dest="display_name",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--staging",
+ action="store_true",
+ help="Create the integration in staging mode",
+ dest="staging",
+ )
+ create_parser.add_argument(
+ "--description",
+ type=str,
+ help="Description of the integration (max 1,500 characters)",
+ dest="description",
+ )
+ create_parser.add_argument(
+ "--image-base64",
+ type=str,
+ help="Integration image encoded as a base64 string (max 5 MB)",
+ dest="image_base64",
+ )
+ create_parser.add_argument(
+ "--svg-icon",
+ type=str,
+ help="Integration SVG icon string (max 1 MB)",
+ dest="svg_icon",
+ )
+ create_parser.add_argument(
+ "--python-version",
+ type=str,
+ choices=[v.value for v in PythonVersion],
+ help="Python version for the integration",
+ dest="python_version",
+ )
+ create_parser.add_argument(
+ "--integration-type",
+ type=str,
+ choices=[t.value for t in IntegrationType],
+ help="Integration type",
+ dest="integration_type",
+ )
+ create_parser.set_defaults(func=handle_integration_create_command)
+
+ # download command
+ download_parser = lvl1.add_parser(
+ "download",
+ help="Download an integration package as a ZIP file",
+ )
+ download_parser.add_argument(
+ "--integration-id",
+ type=str,
+ help="ID of the integration to download",
+ dest="integration_id",
+ required=True,
+ )
+ download_parser.add_argument(
+ "--output-file",
+ type=str,
+ help="Path to write the downloaded ZIP file to",
+ dest="output_file",
+ required=True,
+ )
+ download_parser.set_defaults(func=handle_integration_download_command)
+
+ # download-dependency command
+ download_dep_parser = lvl1.add_parser(
+ "download-dependency",
+ help="Download a Python dependency for a custom integration",
+ )
+ download_dep_parser.add_argument(
+ "--integration-id",
+ type=str,
+ help="ID of the integration",
+ dest="integration_id",
+ required=True,
+ )
+ download_dep_parser.add_argument(
+ "--dependency-name",
+ type=str,
+ help=(
+ "Dependency name to download. Can include version or "
+ "repository, e.g. 'requests==2.31.0'"
+ ),
+ dest="dependency_name",
+ required=True,
+ )
+ download_dep_parser.set_defaults(
+ func=handle_download_integration_dependency_command
+ )
+
+ # export-items command
+ export_items_parser = lvl1.add_parser(
+ "export-items",
+ help="Export specific items from an integration as a ZIP file",
+ )
+ export_items_parser.add_argument(
+ "--integration-id",
+ type=str,
+ help="ID of the integration to export items from",
+ dest="integration_id",
+ required=True,
+ )
+ export_items_parser.add_argument(
+ "--output-file",
+ type=str,
+ help="Path to write the exported ZIP file to",
+ dest="output_file",
+ required=True,
+ )
+ export_items_parser.add_argument(
+ "--actions",
+ type=str,
+ nargs="+",
+ help="IDs of actions to export",
+ dest="actions",
+ )
+ export_items_parser.add_argument(
+ "--jobs",
+ type=str,
+ nargs="+",
+ help="IDs of jobs to export",
+ dest="jobs",
+ )
+ export_items_parser.add_argument(
+ "--connectors",
+ type=str,
+ nargs="+",
+ help="IDs of connectors to export",
+ dest="connectors",
+ )
+ export_items_parser.add_argument(
+ "--managers",
+ type=str,
+ nargs="+",
+ help="IDs of managers to export",
+ dest="managers",
+ )
+ export_items_parser.add_argument(
+ "--transformers",
+ type=str,
+ nargs="+",
+ help="IDs of transformers to export",
+ dest="transformers",
+ )
+ export_items_parser.add_argument(
+ "--logical-operators",
+ type=str,
+ nargs="+",
+ help="IDs of logical operators to export",
+ dest="logical_operators",
+ )
+ export_items_parser.set_defaults(
+ func=handle_export_integration_items_command
+ )
+
+ # affected-items command
+ affected_parser = lvl1.add_parser(
+ "affected-items",
+ help="Get items affected by changes to an integration",
+ )
+ affected_parser.add_argument(
+ "--integration-id",
+ type=str,
+ help="ID of the integration to check",
+ dest="integration_id",
+ required=True,
+ )
+ affected_parser.set_defaults(
+ func=handle_get_integration_affected_items_command
+ )
+
+ # agent-integrations command
+ agent_parser = lvl1.add_parser(
+ "agent-integrations",
+ help="Get integrations installed on a specific agent",
+ )
+ agent_parser.add_argument(
+ "--agent-id",
+ type=str,
+ help="Identifier of the agent",
+ dest="agent_id",
+ required=True,
+ )
+ agent_parser.set_defaults(func=handle_get_agent_integrations_command)
+
+ # dependencies command
+ deps_parser = lvl1.add_parser(
+ "dependencies",
+ help="Get Python dependencies for a custom integration",
+ )
+ deps_parser.add_argument(
+ "--integration-id",
+ type=str,
+ help="ID of the integration",
+ dest="integration_id",
+ required=True,
+ )
+ deps_parser.set_defaults(func=handle_get_integration_dependencies_command)
+
+ # restricted-agents command
+ restricted_parser = lvl1.add_parser(
+ "restricted-agents",
+ help="Get agents restricted from running an updated integration",
+ )
+ restricted_parser.add_argument(
+ "--integration-id",
+ type=str,
+ help="ID of the integration",
+ dest="integration_id",
+ required=True,
+ )
+ restricted_parser.add_argument(
+ "--required-python-version",
+ type=str,
+ choices=[v.value for v in PythonVersion],
+ help="Python version required for the updated integration",
+ dest="required_python_version",
+ required=True,
+ )
+ restricted_parser.add_argument(
+ "--push-request",
+ action="store_true",
+ help="Indicates the integration is being pushed to a different mode",
+ dest="push_request",
+ )
+ restricted_parser.set_defaults(
+ func=handle_get_integration_restricted_agents_command
+ )
+
+ # diff command
+ diff_parser = lvl1.add_parser(
+ "diff", help="Get the configuration diff for an integration"
+ )
+ diff_parser.add_argument(
+ "--integration-id",
+ type=str,
+ help="ID of the integration",
+ dest="integration_id",
+ required=True,
+ )
+ diff_parser.add_argument(
+ "--diff-type",
+ type=str,
+ choices=[d.value for d in DiffType],
+ help=(
+ "Type of diff to retrieve. "
+ "COMMERCIAL: diff against the marketplace version. "
+ "PRODUCTION: diff between staging and production. "
+ "STAGING: diff between production and staging."
+ ),
+ dest="diff_type",
+ default=DiffType.COMMERCIAL.value,
+ )
+ diff_parser.set_defaults(func=handle_get_integration_diff_command)
+
+ # transition command
+ transition_parser = lvl1.add_parser(
+ "transition",
+ help="Transition an integration to production or staging",
+ )
+ transition_parser.add_argument(
+ "--integration-id",
+ type=str,
+ help="ID of the integration to transition",
+ dest="integration_id",
+ required=True,
+ )
+ transition_parser.add_argument(
+ "--target-mode",
+ type=str,
+ choices=[t.value for t in TargetMode],
+ help="Target mode to transition the integration to",
+ dest="target_mode",
+ required=True,
+ )
+ transition_parser.set_defaults(func=handle_transition_integration_command)
+
+ # update command
+ update_parser = lvl1.add_parser(
+ "update", help="Update an existing integration's metadata"
+ )
+ update_parser.add_argument(
+ "--integration-id",
+ type=str,
+ help="ID of the integration to update",
+ dest="integration_id",
+ required=True,
+ )
+ update_parser.add_argument(
+ "--display-name",
+ type=str,
+ help="New display name for the integration (max 150 characters)",
+ dest="display_name",
+ )
+ update_parser.add_argument(
+ "--description",
+ type=str,
+ help="New description for the integration (max 1,500 characters)",
+ dest="description",
+ )
+ update_parser.add_argument(
+ "--image-base64",
+ type=str,
+ help="New integration image encoded as a base64 string (max 5 MB)",
+ dest="image_base64",
+ )
+ update_parser.add_argument(
+ "--svg-icon",
+ type=str,
+ help="New integration SVG icon string (max 1 MB)",
+ dest="svg_icon",
+ )
+ update_parser.add_argument(
+ "--python-version",
+ type=str,
+ choices=[v.value for v in PythonVersion],
+ help="Python version for the integration",
+ dest="python_version",
+ )
+ update_parser.add_argument(
+ "--integration-type",
+ type=str,
+ choices=[t.value for t in IntegrationType],
+ help="Integration type",
+ dest="integration_type",
+ )
+ update_parser.add_argument(
+ "--staging",
+ action="store_true",
+ help="Set the integration to staging mode",
+ dest="staging",
+ )
+ update_parser.add_argument(
+ "--dependencies-to-remove",
+ type=str,
+ nargs="+",
+ help="List of dependency names to remove from the integration",
+ dest="dependencies_to_remove",
+ )
+ update_parser.add_argument(
+ "--update-mask",
+ type=str,
+ help=(
+ "Comma-separated list of fields to update. "
+ "If not provided, all supplied fields are updated."
+ ),
+ dest="update_mask",
+ )
+ update_parser.set_defaults(func=handle_update_integration_command)
+
+ # update-custom command
+ update_custom_parser = lvl1.add_parser(
+ "update-custom",
+ help=(
+ "Update a custom integration definition including "
+ "parameters and dependencies"
+ ),
+ )
+ update_custom_parser.add_argument(
+ "--integration-id",
+ type=str,
+ help="ID of the integration to update",
+ dest="integration_id",
+ required=True,
+ )
+ update_custom_parser.add_argument(
+ "--display-name",
+ type=str,
+ help="New display name for the integration (max 150 characters)",
+ dest="display_name",
+ )
+ update_custom_parser.add_argument(
+ "--description",
+ type=str,
+ help="New description for the integration (max 1,500 characters)",
+ dest="description",
+ )
+ update_custom_parser.add_argument(
+ "--image-base64",
+ type=str,
+ help="New integration image encoded as a base64 string (max 5 MB)",
+ dest="image_base64",
+ )
+ update_custom_parser.add_argument(
+ "--svg-icon",
+ type=str,
+ help="New integration SVG icon string (max 1 MB)",
+ dest="svg_icon",
+ )
+ update_custom_parser.add_argument(
+ "--python-version",
+ type=str,
+ choices=[v.value for v in PythonVersion],
+ help="Python version for the integration",
+ dest="python_version",
+ )
+ update_custom_parser.add_argument(
+ "--integration-type",
+ type=str,
+ choices=[t.value for t in IntegrationType],
+ help="Integration type",
+ dest="integration_type",
+ )
+ update_custom_parser.add_argument(
+ "--staging",
+ action="store_true",
+ help="Set the integration to staging mode",
+ dest="staging",
+ )
+ update_custom_parser.add_argument(
+ "--dependencies-to-remove",
+ type=str,
+ nargs="+",
+ help="List of dependency names to remove from the integration",
+ dest="dependencies_to_remove",
+ )
+ update_custom_parser.add_argument(
+ "--update-mask",
+ type=str,
+ help=(
+ "Comma-separated list of fields to update. "
+ "If not provided, all supplied fields are updated."
+ ),
+ dest="update_mask",
+ )
+ update_custom_parser.set_defaults(
+ func=handle_updated_custom_integration_command
+ )
+
+
+# ---------------------------------------------------------------------------
+# Handlers
+# ---------------------------------------------------------------------------
+
+
+def handle_integration_list_command(args, chronicle):
+ """Handle list integrations command"""
+ try:
+ out = chronicle.list_integrations(
+ page_size=args.page_size,
+ page_token=args.page_token,
+ filter_string=args.filter_string,
+ order_by=args.order_by,
+ as_list=args.as_list,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error listing integrations: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_integration_get_command(args, chronicle):
+ """Handle get integration command"""
+ try:
+ out = chronicle.get_integration(
+ integration_name=args.integration_id,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error getting integration: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_integration_delete_command(args, chronicle):
+ """Handle delete integration command"""
+ try:
+ chronicle.delete_integration(
+ integration_name=args.integration_id,
+ )
+ print(f"Integration {args.integration_id} deleted successfully.")
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error deleting integration: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_integration_create_command(args, chronicle):
+ """Handle create integration command"""
+ try:
+ out = chronicle.create_integration(
+ display_name=args.display_name,
+ staging=args.staging,
+ description=args.description,
+ image_base64=args.image_base64,
+ svg_icon=args.svg_icon,
+ python_version=(
+ PythonVersion(args.python_version)
+ if args.python_version
+ else None
+ ),
+ integration_type=(
+ IntegrationType(args.integration_type)
+ if args.integration_type
+ else None
+ ),
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error creating integration: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_integration_download_command(args, chronicle):
+ """Handle download integration command"""
+ try:
+ zip_bytes = chronicle.download_integration(
+ integration_name=args.integration_id,
+ )
+ with open(args.output_file, "wb") as f:
+ f.write(zip_bytes)
+ print(
+ f"Integration {args.integration_id} downloaded to "
+ f"{args.output_file}."
+ )
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error downloading integration: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_download_integration_dependency_command(args, chronicle):
+ """Handle download integration dependencies command"""
+ try:
+ out = chronicle.download_integration_dependency(
+ integration_name=args.integration_id,
+ dependency_name=args.dependency_name,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(
+ f"Error downloading integration dependencies: {e}", file=sys.stderr
+ )
+ sys.exit(1)
+
+
+def handle_export_integration_items_command(args, chronicle):
+ """Handle export integration items command"""
+ try:
+ zip_bytes = chronicle.export_integration_items(
+ integration_name=args.integration_id,
+ actions=args.actions,
+ jobs=args.jobs,
+ connectors=args.connectors,
+ managers=args.managers,
+ transformers=args.transformers,
+ logical_operators=args.logical_operators,
+ )
+ with open(args.output_file, "wb") as f:
+ f.write(zip_bytes)
+ print(f"Integration items exported to {args.output_file}.")
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error exporting integration items: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_get_integration_affected_items_command(args, chronicle):
+ """Handle get integration affected items command"""
+ try:
+ out = chronicle.get_integration_affected_items(
+ integration_name=args.integration_id,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error getting integration affected items: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_get_agent_integrations_command(args, chronicle):
+ """Handle get agent integration command"""
+ try:
+ out = chronicle.get_agent_integrations(
+ agent_id=args.agent_id,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error getting agent integration: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_get_integration_dependencies_command(args, chronicle):
+ """Handle get integration dependencies command"""
+ try:
+ out = chronicle.get_integration_dependencies(
+ integration_name=args.integration_id,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error getting integration dependencies: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_get_integration_restricted_agents_command(args, chronicle):
+ """Handle get integration restricted agent command"""
+ try:
+ out = chronicle.get_integration_restricted_agents(
+ integration_name=args.integration_id,
+ required_python_version=PythonVersion(args.required_python_version),
+ push_request=args.push_request,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(
+ f"Error getting integration restricted agent: {e}", file=sys.stderr
+ )
+ sys.exit(1)
+
+
+def handle_get_integration_diff_command(args, chronicle):
+ """Handle get integration diff command"""
+ try:
+ out = chronicle.get_integration_diff(
+ integration_name=args.integration_id,
+ diff_type=DiffType(args.diff_type),
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error getting integration diff: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_transition_integration_command(args, chronicle):
+ """Handle transition integration command"""
+ try:
+ out = chronicle.transition_integration(
+ integration_name=args.integration_id,
+ target_mode=TargetMode(args.target_mode),
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error transitioning integration: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_update_integration_command(args, chronicle):
+ """Handle update integration command"""
+ try:
+ out = chronicle.update_integration(
+ integration_name=args.integration_id,
+ display_name=args.display_name,
+ description=args.description,
+ image_base64=args.image_base64,
+ svg_icon=args.svg_icon,
+ python_version=(
+ PythonVersion(args.python_version)
+ if args.python_version
+ else None
+ ),
+ integration_type=(
+ IntegrationType(args.integration_type)
+ if args.integration_type
+ else None
+ ),
+ staging=args.staging or None,
+ dependencies_to_remove=args.dependencies_to_remove,
+ update_mask=args.update_mask,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error updating integration: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_updated_custom_integration_command(args, chronicle):
+ """Handle update custom integration command"""
+ try:
+ out = chronicle.update_custom_integration(
+ integration_name=args.integration_id,
+ display_name=args.display_name,
+ description=args.description,
+ image_base64=args.image_base64,
+ svg_icon=args.svg_icon,
+ python_version=(
+ PythonVersion(args.python_version)
+ if args.python_version
+ else None
+ ),
+ integration_type=(
+ IntegrationType(args.integration_type)
+ if args.integration_type
+ else None
+ ),
+ staging=args.staging or None,
+ dependencies_to_remove=args.dependencies_to_remove,
+ update_mask=args.update_mask,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error updating custom integration: {e}", file=sys.stderr)
+ sys.exit(1)
diff --git a/src/secops/cli/commands/integration/integration_client.py b/src/secops/cli/commands/integration/integration_client.py
new file mode 100644
index 00000000..d84bd383
--- /dev/null
+++ b/src/secops/cli/commands/integration/integration_client.py
@@ -0,0 +1,74 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Top level arguments for integration commands"""
+
+from secops.cli.commands.integration import (
+ marketplace_integration,
+ integration,
+ actions,
+ action_revisions,
+ connectors,
+ connector_revisions,
+ connector_context_properties,
+ connector_instance_logs,
+ connector_instances,
+ jobs,
+ job_revisions,
+ job_context_properties,
+ job_instance_logs,
+ job_instances,
+ managers,
+ manager_revisions,
+ integration_instances,
+ transformers,
+ transformer_revisions,
+ logical_operators,
+ logical_operator_revisions,
+)
+
+
+def setup_integrations_command(subparsers):
+ """Setup integration command"""
+ integrations_parser = subparsers.add_parser(
+ "integration", help="Manage SecOps integrations"
+ )
+ lvl1 = integrations_parser.add_subparsers(
+ dest="integrations_command", help="Integrations command"
+ )
+
+ # Setup all subcommands under `integration`
+ integration.setup_integrations_command(lvl1)
+ integration_instances.setup_integration_instances_command(lvl1)
+ transformers.setup_transformers_command(lvl1)
+ transformer_revisions.setup_transformer_revisions_command(lvl1)
+ logical_operators.setup_logical_operators_command(lvl1)
+ logical_operator_revisions.setup_logical_operator_revisions_command(lvl1)
+ actions.setup_actions_command(lvl1)
+ action_revisions.setup_action_revisions_command(lvl1)
+ connectors.setup_connectors_command(lvl1)
+ connector_revisions.setup_connector_revisions_command(lvl1)
+ connector_context_properties.setup_connector_context_properties_command(
+ lvl1
+ )
+ connector_instance_logs.setup_connector_instance_logs_command(lvl1)
+ connector_instances.setup_connector_instances_command(lvl1)
+ jobs.setup_jobs_command(lvl1)
+ job_revisions.setup_job_revisions_command(lvl1)
+ job_context_properties.setup_job_context_properties_command(lvl1)
+ job_instance_logs.setup_job_instance_logs_command(lvl1)
+ job_instances.setup_job_instances_command(lvl1)
+ managers.setup_managers_command(lvl1)
+ manager_revisions.setup_manager_revisions_command(lvl1)
+ marketplace_integration.setup_marketplace_integrations_command(lvl1)
diff --git a/src/secops/cli/commands/integration/integration_instances.py b/src/secops/cli/commands/integration/integration_instances.py
new file mode 100644
index 00000000..2d375346
--- /dev/null
+++ b/src/secops/cli/commands/integration/integration_instances.py
@@ -0,0 +1,392 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Google SecOps CLI integration instances commands"""
+
+import json
+import sys
+
+from secops.cli.utils.formatters import output_formatter
+from secops.cli.utils.common_args import (
+ add_pagination_args,
+ add_as_list_arg,
+)
+
+
+def setup_integration_instances_command(subparsers):
+ """Setup integration instances command"""
+ instances_parser = subparsers.add_parser(
+ "instances",
+ help="Manage integration instances",
+ )
+ lvl1 = instances_parser.add_subparsers(
+ dest="integration_instances_command",
+ help="Integration instances command",
+ )
+
+ # list command
+ list_parser = lvl1.add_parser("list", help="List integration instances")
+ list_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ add_pagination_args(list_parser)
+ add_as_list_arg(list_parser)
+ list_parser.add_argument(
+ "--filter-string",
+ type=str,
+ help="Filter string for listing instances",
+ dest="filter_string",
+ )
+ list_parser.add_argument(
+ "--order-by",
+ type=str,
+ help="Order by string for listing instances",
+ dest="order_by",
+ )
+ list_parser.set_defaults(func=handle_integration_instances_list_command)
+
+ # get command
+ get_parser = lvl1.add_parser("get", help="Get integration instance details")
+ get_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ get_parser.add_argument(
+ "--instance-id",
+ type=str,
+ help="ID of the instance to get",
+ dest="instance_id",
+ required=True,
+ )
+ get_parser.set_defaults(func=handle_integration_instances_get_command)
+
+ # delete command
+ delete_parser = lvl1.add_parser(
+ "delete", help="Delete an integration instance"
+ )
+ delete_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ delete_parser.add_argument(
+ "--instance-id",
+ type=str,
+ help="ID of the instance to delete",
+ dest="instance_id",
+ required=True,
+ )
+ delete_parser.set_defaults(func=handle_integration_instances_delete_command)
+
+ # create command
+ create_parser = lvl1.add_parser(
+ "create", help="Create a new integration instance"
+ )
+ create_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--display-name",
+ type=str,
+ help="Display name for the instance",
+ dest="display_name",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--environment",
+ type=str,
+ help="Environment name for the instance",
+ dest="environment",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--description",
+ type=str,
+ help="Description of the instance",
+ dest="description",
+ )
+ create_parser.add_argument(
+ "--instance-id",
+ type=str,
+ help="Custom ID for the instance",
+ dest="instance_id",
+ )
+ create_parser.add_argument(
+ "--config",
+ type=str,
+ help="JSON string of instance configuration",
+ dest="config",
+ )
+ create_parser.set_defaults(func=handle_integration_instances_create_command)
+
+ # update command
+ update_parser = lvl1.add_parser(
+ "update", help="Update an integration instance"
+ )
+ update_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ update_parser.add_argument(
+ "--instance-id",
+ type=str,
+ help="ID of the instance to update",
+ dest="instance_id",
+ required=True,
+ )
+ update_parser.add_argument(
+ "--display-name",
+ type=str,
+ help="New display name for the instance",
+ dest="display_name",
+ )
+ update_parser.add_argument(
+ "--description",
+ type=str,
+ help="New description for the instance",
+ dest="description",
+ )
+ update_parser.add_argument(
+ "--config",
+ type=str,
+ help="JSON string of new instance configuration",
+ dest="config",
+ )
+ update_parser.add_argument(
+ "--update-mask",
+ type=str,
+ help="Comma-separated list of fields to update",
+ dest="update_mask",
+ )
+ update_parser.set_defaults(func=handle_integration_instances_update_command)
+
+ # test command
+ test_parser = lvl1.add_parser(
+ "test", help="Execute an integration instance test"
+ )
+ test_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ test_parser.add_argument(
+ "--instance-id",
+ type=str,
+ help="ID of the instance to test",
+ dest="instance_id",
+ required=True,
+ )
+ test_parser.set_defaults(func=handle_integration_instances_test_command)
+
+ # get-affected-items command
+ affected_parser = lvl1.add_parser(
+ "get-affected-items",
+ help="Get items affected by an integration instance",
+ )
+ affected_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ affected_parser.add_argument(
+ "--instance-id",
+ type=str,
+ help="ID of the instance",
+ dest="instance_id",
+ required=True,
+ )
+ affected_parser.set_defaults(
+ func=handle_integration_instances_get_affected_items_command
+ )
+
+ # get-default command
+ default_parser = lvl1.add_parser(
+ "get-default",
+ help="Get the default integration instance",
+ )
+ default_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ default_parser.set_defaults(
+ func=handle_integration_instances_get_default_command
+ )
+
+
+def handle_integration_instances_list_command(args, chronicle):
+ """Handle integration instances list command"""
+ try:
+ out = chronicle.list_integration_instances(
+ integration_name=args.integration_name,
+ page_size=args.page_size,
+ page_token=args.page_token,
+ filter_string=args.filter_string,
+ order_by=args.order_by,
+ as_list=args.as_list,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error listing integration instances: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_integration_instances_get_command(args, chronicle):
+ """Handle integration instance get command"""
+ try:
+ out = chronicle.get_integration_instance(
+ integration_name=args.integration_name,
+ integration_instance_id=args.instance_id,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error getting integration instance: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_integration_instances_delete_command(args, chronicle):
+ """Handle integration instance delete command"""
+ try:
+ chronicle.delete_integration_instance(
+ integration_name=args.integration_name,
+ integration_instance_id=args.instance_id,
+ )
+ print(f"Integration instance {args.instance_id} deleted successfully")
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error deleting integration instance: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_integration_instances_create_command(args, chronicle):
+ """Handle integration instance create command"""
+ try:
+ # Parse config if provided
+
+ config = None
+ if args.config:
+ config = json.loads(args.config)
+
+ out = chronicle.create_integration_instance(
+ integration_name=args.integration_name,
+ display_name=args.display_name,
+ environment=args.environment,
+ description=args.description,
+ integration_instance_id=args.instance_id,
+ config=config,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except json.JSONDecodeError as e:
+ print(f"Error parsing config JSON: {e}", file=sys.stderr)
+ sys.exit(1)
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error creating integration instance: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_integration_instances_update_command(args, chronicle):
+ """Handle integration instance update command"""
+ try:
+ # Parse config if provided
+
+ config = None
+ if args.config:
+ config = json.loads(args.config)
+
+ out = chronicle.update_integration_instance(
+ integration_name=args.integration_name,
+ integration_instance_id=args.instance_id,
+ display_name=args.display_name,
+ description=args.description,
+ config=config,
+ update_mask=args.update_mask,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except json.JSONDecodeError as e:
+ print(f"Error parsing config JSON: {e}", file=sys.stderr)
+ sys.exit(1)
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error updating integration instance: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_integration_instances_test_command(args, chronicle):
+ """Handle integration instance test command"""
+ try:
+ # Get the instance first
+ instance = chronicle.get_integration_instance(
+ integration_name=args.integration_name,
+ integration_instance_id=args.instance_id,
+ )
+
+ out = chronicle.execute_integration_instance_test(
+ integration_name=args.integration_name,
+ integration_instance_id=args.instance_id,
+ integration_instance=instance,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error testing integration instance: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_integration_instances_get_affected_items_command(args, chronicle):
+ """Handle get integration instance affected items command"""
+ try:
+ out = chronicle.get_integration_instance_affected_items(
+ integration_name=args.integration_name,
+ integration_instance_id=args.instance_id,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(
+ f"Error getting integration instance affected items: {e}",
+ file=sys.stderr,
+ )
+ sys.exit(1)
+
+
+def handle_integration_instances_get_default_command(args, chronicle):
+ """Handle get default integration instance command"""
+ try:
+ out = chronicle.get_default_integration_instance(
+ integration_name=args.integration_name,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(
+ f"Error getting default integration instance: {e}", file=sys.stderr
+ )
+ sys.exit(1)
diff --git a/src/secops/cli/commands/integration/job_context_properties.py b/src/secops/cli/commands/integration/job_context_properties.py
new file mode 100644
index 00000000..5da5cdb3
--- /dev/null
+++ b/src/secops/cli/commands/integration/job_context_properties.py
@@ -0,0 +1,354 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Google SecOps CLI job context properties commands"""
+
+import sys
+
+from secops.cli.utils.formatters import output_formatter
+from secops.cli.utils.common_args import (
+ add_pagination_args,
+ add_as_list_arg,
+)
+
+
+def setup_job_context_properties_command(subparsers):
+ """Setup job context properties command"""
+ properties_parser = subparsers.add_parser(
+ "job-context-properties",
+ help="Manage job context properties",
+ )
+ lvl1 = properties_parser.add_subparsers(
+ dest="job_context_properties_command",
+ help="Job context properties command",
+ )
+
+ # list command
+ list_parser = lvl1.add_parser("list", help="List job context properties")
+ list_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ list_parser.add_argument(
+ "--job-id",
+ type=str,
+ help="ID of the job",
+ dest="job_id",
+ required=True,
+ )
+ list_parser.add_argument(
+ "--context-id",
+ type=str,
+ help="Context ID to filter properties",
+ dest="context_id",
+ )
+ add_pagination_args(list_parser)
+ add_as_list_arg(list_parser)
+ list_parser.add_argument(
+ "--filter-string",
+ type=str,
+ help="Filter string for listing properties",
+ dest="filter_string",
+ )
+ list_parser.add_argument(
+ "--order-by",
+ type=str,
+ help="Order by string for listing properties",
+ dest="order_by",
+ )
+ list_parser.set_defaults(func=handle_job_context_properties_list_command)
+
+ # get command
+ get_parser = lvl1.add_parser(
+ "get", help="Get a specific job context property"
+ )
+ get_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ get_parser.add_argument(
+ "--job-id",
+ type=str,
+ help="ID of the job",
+ dest="job_id",
+ required=True,
+ )
+ get_parser.add_argument(
+ "--context-id",
+ type=str,
+ help="Context ID of the property",
+ dest="context_id",
+ required=True,
+ )
+ get_parser.add_argument(
+ "--property-id",
+ type=str,
+ help="ID of the property to get",
+ dest="property_id",
+ required=True,
+ )
+ get_parser.set_defaults(func=handle_job_context_properties_get_command)
+
+ # delete command
+ delete_parser = lvl1.add_parser(
+ "delete", help="Delete a job context property"
+ )
+ delete_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ delete_parser.add_argument(
+ "--job-id",
+ type=str,
+ help="ID of the job",
+ dest="job_id",
+ required=True,
+ )
+ delete_parser.add_argument(
+ "--context-id",
+ type=str,
+ help="Context ID of the property",
+ dest="context_id",
+ required=True,
+ )
+ delete_parser.add_argument(
+ "--property-id",
+ type=str,
+ help="ID of the property to delete",
+ dest="property_id",
+ required=True,
+ )
+ delete_parser.set_defaults(
+ func=handle_job_context_properties_delete_command
+ )
+
+ # create command
+ create_parser = lvl1.add_parser(
+ "create", help="Create a new job context property"
+ )
+ create_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--job-id",
+ type=str,
+ help="ID of the job",
+ dest="job_id",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--context-id",
+ type=str,
+ help="Context ID for the property",
+ dest="context_id",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--key",
+ type=str,
+ help="Key for the property",
+ dest="key",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--value",
+ type=str,
+ help="Value for the property",
+ dest="value",
+ required=True,
+ )
+ create_parser.set_defaults(
+ func=handle_job_context_properties_create_command
+ )
+
+ # update command
+ update_parser = lvl1.add_parser(
+ "update", help="Update a job context property"
+ )
+ update_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ update_parser.add_argument(
+ "--job-id",
+ type=str,
+ help="ID of the job",
+ dest="job_id",
+ required=True,
+ )
+ update_parser.add_argument(
+ "--context-id",
+ type=str,
+ help="Context ID of the property",
+ dest="context_id",
+ required=True,
+ )
+ update_parser.add_argument(
+ "--property-id",
+ type=str,
+ help="ID of the property to update",
+ dest="property_id",
+ required=True,
+ )
+ update_parser.add_argument(
+ "--value",
+ type=str,
+ help="New value for the property",
+ dest="value",
+ required=True,
+ )
+ update_parser.set_defaults(
+ func=handle_job_context_properties_update_command
+ )
+
+ # clear-all command
+ clear_parser = lvl1.add_parser(
+ "clear-all", help="Delete all job context properties"
+ )
+ clear_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ clear_parser.add_argument(
+ "--job-id",
+ type=str,
+ help="ID of the job",
+ dest="job_id",
+ required=True,
+ )
+ clear_parser.add_argument(
+ "--context-id",
+ type=str,
+ help="Context ID to clear all properties for",
+ dest="context_id",
+ required=True,
+ )
+ clear_parser.set_defaults(func=handle_job_context_properties_clear_command)
+
+
+def handle_job_context_properties_list_command(args, chronicle):
+ """Handle job context properties list command"""
+ try:
+ out = chronicle.list_job_context_properties(
+ integration_name=args.integration_name,
+ job_id=args.job_id,
+ context_id=args.context_id,
+ page_size=args.page_size,
+ page_token=args.page_token,
+ filter_string=args.filter_string,
+ order_by=args.order_by,
+ as_list=args.as_list,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error listing job context properties: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_job_context_properties_get_command(args, chronicle):
+ """Handle job context property get command"""
+ try:
+ out = chronicle.get_job_context_property(
+ integration_name=args.integration_name,
+ job_id=args.job_id,
+ context_id=args.context_id,
+ context_property_id=args.property_id,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error getting job context property: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_job_context_properties_delete_command(args, chronicle):
+ """Handle job context property delete command"""
+ try:
+ chronicle.delete_job_context_property(
+ integration_name=args.integration_name,
+ job_id=args.job_id,
+ context_id=args.context_id,
+ context_property_id=args.property_id,
+ )
+ print(f"Job context property {args.property_id} deleted successfully")
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error deleting job context property: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_job_context_properties_create_command(args, chronicle):
+ """Handle job context property create command"""
+ try:
+ out = chronicle.create_job_context_property(
+ integration_name=args.integration_name,
+ job_id=args.job_id,
+ context_id=args.context_id,
+ key=args.key,
+ value=args.value,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error creating job context property: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_job_context_properties_update_command(args, chronicle):
+ """Handle job context property update command"""
+ try:
+ out = chronicle.update_job_context_property(
+ integration_name=args.integration_name,
+ job_id=args.job_id,
+ context_id=args.context_id,
+ context_property_id=args.property_id,
+ value=args.value,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error updating job context property: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_job_context_properties_clear_command(args, chronicle):
+ """Handle clear all job context properties command"""
+ try:
+ chronicle.delete_all_job_context_properties(
+ integration_name=args.integration_name,
+ job_id=args.job_id,
+ context_id=args.context_id,
+ )
+ print(
+ f"All job context properties for context "
+ f"{args.context_id} cleared successfully"
+ )
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error clearing job context properties: {e}", file=sys.stderr)
+ sys.exit(1)
diff --git a/src/secops/cli/commands/integration/job_instance_logs.py b/src/secops/cli/commands/integration/job_instance_logs.py
new file mode 100644
index 00000000..d18e2ad4
--- /dev/null
+++ b/src/secops/cli/commands/integration/job_instance_logs.py
@@ -0,0 +1,140 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Google SecOps CLI job instance logs commands"""
+
+import sys
+
+from secops.cli.utils.formatters import output_formatter
+from secops.cli.utils.common_args import (
+ add_pagination_args,
+ add_as_list_arg,
+)
+
+
+def setup_job_instance_logs_command(subparsers):
+ """Setup job instance logs command"""
+ logs_parser = subparsers.add_parser(
+ "job-instance-logs",
+ help="View job instance logs",
+ )
+ lvl1 = logs_parser.add_subparsers(
+ dest="job_instance_logs_command",
+ help="Job instance logs command",
+ )
+
+ # list command
+ list_parser = lvl1.add_parser("list", help="List job instance logs")
+ list_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ list_parser.add_argument(
+ "--job-id",
+ type=str,
+ help="ID of the job",
+ dest="job_id",
+ required=True,
+ )
+ list_parser.add_argument(
+ "--job-instance-id",
+ type=str,
+ help="ID of the job instance",
+ dest="job_instance_id",
+ required=True,
+ )
+ add_pagination_args(list_parser)
+ add_as_list_arg(list_parser)
+ list_parser.add_argument(
+ "--filter-string",
+ type=str,
+ help="Filter string for listing logs",
+ dest="filter_string",
+ )
+ list_parser.add_argument(
+ "--order-by",
+ type=str,
+ help="Order by string for listing logs",
+ dest="order_by",
+ )
+ list_parser.set_defaults(func=handle_job_instance_logs_list_command)
+
+ # get command
+ get_parser = lvl1.add_parser("get", help="Get a specific job instance log")
+ get_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ get_parser.add_argument(
+ "--job-id",
+ type=str,
+ help="ID of the job",
+ dest="job_id",
+ required=True,
+ )
+ get_parser.add_argument(
+ "--job-instance-id",
+ type=str,
+ help="ID of the job instance",
+ dest="job_instance_id",
+ required=True,
+ )
+ get_parser.add_argument(
+ "--log-id",
+ type=str,
+ help="ID of the log to get",
+ dest="log_id",
+ required=True,
+ )
+ get_parser.set_defaults(func=handle_job_instance_logs_get_command)
+
+
+def handle_job_instance_logs_list_command(args, chronicle):
+ """Handle job instance logs list command"""
+ try:
+ out = chronicle.list_job_instance_logs(
+ integration_name=args.integration_name,
+ job_id=args.job_id,
+ job_instance_id=args.job_instance_id,
+ page_size=args.page_size,
+ page_token=args.page_token,
+ filter_string=args.filter_string,
+ order_by=args.order_by,
+ as_list=args.as_list,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error listing job instance logs: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_job_instance_logs_get_command(args, chronicle):
+ """Handle job instance log get command"""
+ try:
+ out = chronicle.get_job_instance_log(
+ integration_name=args.integration_name,
+ job_id=args.job_id,
+ job_instance_id=args.job_instance_id,
+ job_instance_log_id=args.log_id,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error getting job instance log: {e}", file=sys.stderr)
+ sys.exit(1)
diff --git a/src/secops/cli/commands/integration/job_instances.py b/src/secops/cli/commands/integration/job_instances.py
new file mode 100644
index 00000000..53c9a202
--- /dev/null
+++ b/src/secops/cli/commands/integration/job_instances.py
@@ -0,0 +1,407 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Google SecOps CLI integration job instances commands"""
+
+import json
+import sys
+
+from secops.cli.utils.formatters import output_formatter
+from secops.cli.utils.common_args import (
+ add_pagination_args,
+ add_as_list_arg,
+)
+
+
+def setup_job_instances_command(subparsers):
+ """Setup integration job instances command"""
+ instances_parser = subparsers.add_parser(
+ "job-instances",
+ help="Manage job instances",
+ )
+ lvl1 = instances_parser.add_subparsers(
+ dest="job_instances_command",
+ help="Job instances command",
+ )
+
+ # list command
+ list_parser = lvl1.add_parser("list", help="List job instances")
+ list_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ list_parser.add_argument(
+ "--job-id",
+ type=str,
+ help="ID of the job",
+ dest="job_id",
+ required=True,
+ )
+ add_pagination_args(list_parser)
+ add_as_list_arg(list_parser)
+ list_parser.add_argument(
+ "--filter-string",
+ type=str,
+ help="Filter string for listing instances",
+ dest="filter_string",
+ )
+ list_parser.add_argument(
+ "--order-by",
+ type=str,
+ help="Order by string for listing instances",
+ dest="order_by",
+ )
+ list_parser.set_defaults(func=handle_job_instances_list_command)
+
+ # get command
+ get_parser = lvl1.add_parser("get", help="Get a specific job instance")
+ get_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ get_parser.add_argument(
+ "--job-id",
+ type=str,
+ help="ID of the job",
+ dest="job_id",
+ required=True,
+ )
+ get_parser.add_argument(
+ "--job-instance-id",
+ type=str,
+ help="ID of the job instance to get",
+ dest="job_instance_id",
+ required=True,
+ )
+ get_parser.set_defaults(func=handle_job_instances_get_command)
+
+ # delete command
+ delete_parser = lvl1.add_parser("delete", help="Delete a job instance")
+ delete_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ delete_parser.add_argument(
+ "--job-id",
+ type=str,
+ help="ID of the job",
+ dest="job_id",
+ required=True,
+ )
+ delete_parser.add_argument(
+ "--job-instance-id",
+ type=str,
+ help="ID of the job instance to delete",
+ dest="job_instance_id",
+ required=True,
+ )
+ delete_parser.set_defaults(func=handle_job_instances_delete_command)
+
+ # create command
+ create_parser = lvl1.add_parser("create", help="Create a new job instance")
+ create_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--job-id",
+ type=str,
+ help="ID of the job",
+ dest="job_id",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--environment",
+ type=str,
+ help="Environment for the job instance",
+ dest="environment",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--display-name",
+ type=str,
+ help="Display name for the job instance",
+ dest="display_name",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--schedule",
+ type=str,
+ help="Cron schedule for the job instance",
+ dest="schedule",
+ )
+ create_parser.add_argument(
+ "--timeout-seconds",
+ type=int,
+ help="Timeout in seconds for job execution",
+ dest="timeout_seconds",
+ )
+ create_parser.add_argument(
+ "--enabled",
+ action="store_true",
+ help="Enable the job instance",
+ dest="enabled",
+ )
+ create_parser.add_argument(
+ "--parameters",
+ type=str,
+ help="JSON string of job parameters",
+ dest="parameters",
+ )
+ create_parser.set_defaults(func=handle_job_instances_create_command)
+
+ # update command
+ update_parser = lvl1.add_parser("update", help="Update a job instance")
+ update_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ update_parser.add_argument(
+ "--job-id",
+ type=str,
+ help="ID of the job",
+ dest="job_id",
+ required=True,
+ )
+ update_parser.add_argument(
+ "--job-instance-id",
+ type=str,
+ help="ID of the job instance to update",
+ dest="job_instance_id",
+ required=True,
+ )
+ update_parser.add_argument(
+ "--display-name",
+ type=str,
+ help="New display name for the job instance",
+ dest="display_name",
+ )
+ update_parser.add_argument(
+ "--schedule",
+ type=str,
+ help="New cron schedule for the job instance",
+ dest="schedule",
+ )
+ update_parser.add_argument(
+ "--timeout-seconds",
+ type=int,
+ help="New timeout in seconds for job execution",
+ dest="timeout_seconds",
+ )
+ update_parser.add_argument(
+ "--enabled",
+ type=str,
+ choices=["true", "false"],
+ help="Enable or disable the job instance",
+ dest="enabled",
+ )
+ update_parser.add_argument(
+ "--parameters",
+ type=str,
+ help="JSON string of new job parameters",
+ dest="parameters",
+ )
+ update_parser.add_argument(
+ "--update-mask",
+ type=str,
+ help="Comma-separated list of fields to update",
+ dest="update_mask",
+ )
+ update_parser.set_defaults(func=handle_job_instances_update_command)
+
+ # run-ondemand command
+ run_parser = lvl1.add_parser(
+ "run-ondemand",
+ help="Run a job instance on demand",
+ )
+ run_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ run_parser.add_argument(
+ "--job-id",
+ type=str,
+ help="ID of the job",
+ dest="job_id",
+ required=True,
+ )
+ run_parser.add_argument(
+ "--job-instance-id",
+ type=str,
+ help="ID of the job instance to run",
+ dest="job_instance_id",
+ required=True,
+ )
+ run_parser.add_argument(
+ "--parameters",
+ type=str,
+ help="JSON string of parameters for this run",
+ dest="parameters",
+ )
+ run_parser.set_defaults(func=handle_job_instances_run_ondemand_command)
+
+
+def handle_job_instances_list_command(args, chronicle):
+ """Handle job instances list command"""
+ try:
+ out = chronicle.list_integration_job_instances(
+ integration_name=args.integration_name,
+ job_id=args.job_id,
+ page_size=args.page_size,
+ page_token=args.page_token,
+ filter_string=args.filter_string,
+ order_by=args.order_by,
+ as_list=args.as_list,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error listing job instances: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_job_instances_get_command(args, chronicle):
+ """Handle job instance get command"""
+ try:
+ out = chronicle.get_integration_job_instance(
+ integration_name=args.integration_name,
+ job_id=args.job_id,
+ job_instance_id=args.job_instance_id,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error getting job instance: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_job_instances_delete_command(args, chronicle):
+ """Handle job instance delete command"""
+ try:
+ chronicle.delete_integration_job_instance(
+ integration_name=args.integration_name,
+ job_id=args.job_id,
+ job_instance_id=args.job_instance_id,
+ )
+ print(f"Job instance {args.job_instance_id} deleted successfully")
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error deleting job instance: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_job_instances_create_command(args, chronicle):
+ """Handle job instance create command"""
+ try:
+ # Parse parameters if provided
+ parameters = None
+ if args.parameters:
+ parameters = json.loads(args.parameters)
+
+ out = chronicle.create_integration_job_instance(
+ integration_name=args.integration_name,
+ job_id=args.job_id,
+ environment=args.environment,
+ display_name=args.display_name,
+ schedule=args.schedule,
+ timeout_seconds=args.timeout_seconds,
+ enabled=args.enabled,
+ parameters=parameters,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except json.JSONDecodeError as e:
+ print(f"Error parsing parameters JSON: {e}", file=sys.stderr)
+ sys.exit(1)
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error creating job instance: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_job_instances_update_command(args, chronicle):
+ """Handle job instance update command"""
+ try:
+ # Parse parameters if provided
+ parameters = None
+ if args.parameters:
+ parameters = json.loads(args.parameters)
+
+ # Convert enabled string to boolean if provided
+ enabled = None
+ if args.enabled:
+ enabled = args.enabled.lower() == "true"
+
+ out = chronicle.update_integration_job_instance(
+ integration_name=args.integration_name,
+ job_id=args.job_id,
+ job_instance_id=args.job_instance_id,
+ display_name=args.display_name,
+ schedule=args.schedule,
+ timeout_seconds=args.timeout_seconds,
+ enabled=enabled,
+ parameters=parameters,
+ update_mask=args.update_mask,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except json.JSONDecodeError as e:
+ print(f"Error parsing parameters JSON: {e}", file=sys.stderr)
+ sys.exit(1)
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error updating job instance: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_job_instances_run_ondemand_command(args, chronicle):
+ """Handle run job instance on demand command"""
+ try:
+ # Parse parameters if provided
+ parameters = None
+ if args.parameters:
+ parameters = json.loads(args.parameters)
+
+ # Get the job instance first
+ job_instance = chronicle.get_integration_job_instance(
+ integration_name=args.integration_name,
+ job_id=args.job_id,
+ job_instance_id=args.job_instance_id,
+ )
+
+ out = chronicle.run_integration_job_instance_on_demand(
+ integration_name=args.integration_name,
+ job_id=args.job_id,
+ job_instance_id=args.job_instance_id,
+ job_instance=job_instance,
+ parameters=parameters,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except json.JSONDecodeError as e:
+ print(f"Error parsing parameters JSON: {e}", file=sys.stderr)
+ sys.exit(1)
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error running job instance on demand: {e}", file=sys.stderr)
+ sys.exit(1)
diff --git a/src/secops/cli/commands/integration/job_revisions.py b/src/secops/cli/commands/integration/job_revisions.py
new file mode 100644
index 00000000..36b24850
--- /dev/null
+++ b/src/secops/cli/commands/integration/job_revisions.py
@@ -0,0 +1,213 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Google SecOps CLI integration job revisions commands"""
+
+import sys
+
+from secops.cli.utils.formatters import output_formatter
+from secops.cli.utils.common_args import (
+ add_pagination_args,
+ add_as_list_arg,
+)
+
+
+def setup_job_revisions_command(subparsers):
+ """Setup integration job revisions command"""
+ revisions_parser = subparsers.add_parser(
+ "job-revisions",
+ help="Manage integration job revisions",
+ )
+ lvl1 = revisions_parser.add_subparsers(
+ dest="job_revisions_command",
+ help="Integration job revisions command",
+ )
+
+ # list command
+ list_parser = lvl1.add_parser("list", help="List integration job revisions")
+ list_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ list_parser.add_argument(
+ "--job-id",
+ type=str,
+ help="ID of the job",
+ dest="job_id",
+ required=True,
+ )
+ add_pagination_args(list_parser)
+ add_as_list_arg(list_parser)
+ list_parser.add_argument(
+ "--filter-string",
+ type=str,
+ help="Filter string for listing revisions",
+ dest="filter_string",
+ )
+ list_parser.add_argument(
+ "--order-by",
+ type=str,
+ help="Order by string for listing revisions",
+ dest="order_by",
+ )
+ list_parser.set_defaults(func=handle_job_revisions_list_command)
+
+ # delete command
+ delete_parser = lvl1.add_parser(
+ "delete", help="Delete an integration job revision"
+ )
+ delete_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ delete_parser.add_argument(
+ "--job-id",
+ type=str,
+ help="ID of the job",
+ dest="job_id",
+ required=True,
+ )
+ delete_parser.add_argument(
+ "--revision-id",
+ type=str,
+ help="ID of the revision to delete",
+ dest="revision_id",
+ required=True,
+ )
+ delete_parser.set_defaults(func=handle_job_revisions_delete_command)
+
+ # create command
+ create_parser = lvl1.add_parser(
+ "create", help="Create a new integration job revision"
+ )
+ create_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--job-id",
+ type=str,
+ help="ID of the job",
+ dest="job_id",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--comment",
+ type=str,
+ help="Comment describing the revision",
+ dest="comment",
+ )
+ create_parser.set_defaults(func=handle_job_revisions_create_command)
+
+ # rollback command
+ rollback_parser = lvl1.add_parser(
+ "rollback", help="Rollback job to a previous revision"
+ )
+ rollback_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ rollback_parser.add_argument(
+ "--job-id",
+ type=str,
+ help="ID of the job",
+ dest="job_id",
+ required=True,
+ )
+ rollback_parser.add_argument(
+ "--revision-id",
+ type=str,
+ help="ID of the revision to rollback to",
+ dest="revision_id",
+ required=True,
+ )
+ rollback_parser.set_defaults(func=handle_job_revisions_rollback_command)
+
+
+def handle_job_revisions_list_command(args, chronicle):
+ """Handle integration job revisions list command"""
+ try:
+ out = chronicle.list_integration_job_revisions(
+ integration_name=args.integration_name,
+ job_id=args.job_id,
+ page_size=args.page_size,
+ page_token=args.page_token,
+ filter_string=args.filter_string,
+ order_by=args.order_by,
+ as_list=args.as_list,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error listing job revisions: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_job_revisions_delete_command(args, chronicle):
+ """Handle integration job revision delete command"""
+ try:
+ chronicle.delete_integration_job_revision(
+ integration_name=args.integration_name,
+ job_id=args.job_id,
+ revision_id=args.revision_id,
+ )
+ print(f"Job revision {args.revision_id} deleted successfully")
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error deleting job revision: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_job_revisions_create_command(args, chronicle):
+ """Handle integration job revision create command"""
+ try:
+ # Get the current job to create a revision
+ job = chronicle.get_integration_job(
+ integration_name=args.integration_name,
+ job_id=args.job_id,
+ )
+ out = chronicle.create_integration_job_revision(
+ integration_name=args.integration_name,
+ job_id=args.job_id,
+ job=job,
+ comment=args.comment,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error creating job revision: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_job_revisions_rollback_command(args, chronicle):
+ """Handle integration job revision rollback command"""
+ try:
+ out = chronicle.rollback_integration_job_revision(
+ integration_name=args.integration_name,
+ job_id=args.job_id,
+ revision_id=args.revision_id,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error rolling back job revision: {e}", file=sys.stderr)
+ sys.exit(1)
diff --git a/src/secops/cli/commands/integration/jobs.py b/src/secops/cli/commands/integration/jobs.py
new file mode 100644
index 00000000..4cd04e8c
--- /dev/null
+++ b/src/secops/cli/commands/integration/jobs.py
@@ -0,0 +1,356 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Google SecOps CLI integration jobs commands"""
+
+import json
+import sys
+
+from secops.cli.utils.formatters import output_formatter
+from secops.cli.utils.common_args import (
+ add_pagination_args,
+ add_as_list_arg,
+)
+
+
+def setup_jobs_command(subparsers):
+ """Setup integration jobs command"""
+ jobs_parser = subparsers.add_parser(
+ "jobs",
+ help="Manage integration jobs",
+ )
+ lvl1 = jobs_parser.add_subparsers(
+ dest="jobs_command", help="Integration jobs command"
+ )
+
+ # list command
+ list_parser = lvl1.add_parser("list", help="List integration jobs")
+ list_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ add_pagination_args(list_parser)
+ add_as_list_arg(list_parser)
+ list_parser.add_argument(
+ "--filter-string",
+ type=str,
+ help="Filter string for listing jobs",
+ dest="filter_string",
+ )
+ list_parser.add_argument(
+ "--order-by",
+ type=str,
+ help="Order by string for listing jobs",
+ dest="order_by",
+ )
+ list_parser.add_argument(
+ "--exclude-staging",
+ action="store_true",
+ help="Exclude staging jobs from the list",
+ dest="exclude_staging",
+ )
+ list_parser.set_defaults(func=handle_jobs_list_command)
+
+ # get command
+ get_parser = lvl1.add_parser("get", help="Get integration job details")
+ get_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ get_parser.add_argument(
+ "--job-id",
+ type=str,
+ help="ID of the job to get",
+ dest="job_id",
+ required=True,
+ )
+ get_parser.set_defaults(func=handle_jobs_get_command)
+
+ # delete command
+ delete_parser = lvl1.add_parser("delete", help="Delete an integration job")
+ delete_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ delete_parser.add_argument(
+ "--job-id",
+ type=str,
+ help="ID of the job to delete",
+ dest="job_id",
+ required=True,
+ )
+ delete_parser.set_defaults(func=handle_jobs_delete_command)
+
+ # create command
+ create_parser = lvl1.add_parser(
+ "create",
+ help="Create a new integration job",
+ )
+ create_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--display-name",
+ type=str,
+ help="Display name for the job",
+ dest="display_name",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--code",
+ type=str,
+ help="Python code for the job",
+ dest="code",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--description",
+ type=str,
+ help="Description of the job",
+ dest="description",
+ )
+ create_parser.add_argument(
+ "--job-id",
+ type=str,
+ help="Custom ID for the job",
+ dest="job_id",
+ )
+ create_parser.add_argument(
+ "--parameters",
+ type=str,
+ help="JSON string of job parameters",
+ dest="parameters",
+ )
+ create_parser.set_defaults(func=handle_jobs_create_command)
+
+ # update command
+ update_parser = lvl1.add_parser("update", help="Update an integration job")
+ update_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ update_parser.add_argument(
+ "--job-id",
+ type=str,
+ help="ID of the job to update",
+ dest="job_id",
+ required=True,
+ )
+ update_parser.add_argument(
+ "--display-name",
+ type=str,
+ help="New display name for the job",
+ dest="display_name",
+ )
+ update_parser.add_argument(
+ "--code",
+ type=str,
+ help="New Python code for the job",
+ dest="code",
+ )
+ update_parser.add_argument(
+ "--description",
+ type=str,
+ help="New description for the job",
+ dest="description",
+ )
+ update_parser.add_argument(
+ "--parameters",
+ type=str,
+ help="JSON string of new job parameters",
+ dest="parameters",
+ )
+ update_parser.add_argument(
+ "--update-mask",
+ type=str,
+ help="Comma-separated list of fields to update",
+ dest="update_mask",
+ )
+ update_parser.set_defaults(func=handle_jobs_update_command)
+
+ # test command
+ test_parser = lvl1.add_parser(
+ "test", help="Execute an integration job test"
+ )
+ test_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ test_parser.add_argument(
+ "--job-id",
+ type=str,
+ help="ID of the job to test",
+ dest="job_id",
+ required=True,
+ )
+ test_parser.set_defaults(func=handle_jobs_test_command)
+
+ # template command
+ template_parser = lvl1.add_parser(
+ "template",
+ help="Get a template for creating a job",
+ )
+ template_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ template_parser.set_defaults(func=handle_jobs_template_command)
+
+
+def handle_jobs_list_command(args, chronicle):
+ """Handle integration jobs list command"""
+ try:
+ out = chronicle.list_integration_jobs(
+ integration_name=args.integration_name,
+ page_size=args.page_size,
+ page_token=args.page_token,
+ filter_string=args.filter_string,
+ order_by=args.order_by,
+ exclude_staging=args.exclude_staging,
+ as_list=args.as_list,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error listing integration jobs: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_jobs_get_command(args, chronicle):
+ """Handle integration job get command"""
+ try:
+ out = chronicle.get_integration_job(
+ integration_name=args.integration_name,
+ job_id=args.job_id,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error getting integration job: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_jobs_delete_command(args, chronicle):
+ """Handle integration job delete command"""
+ try:
+ chronicle.delete_integration_job(
+ integration_name=args.integration_name,
+ job_id=args.job_id,
+ )
+ print(f"Job {args.job_id} deleted successfully")
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error deleting integration job: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_jobs_create_command(args, chronicle):
+ """Handle integration job create command"""
+ try:
+ # Parse parameters if provided
+ parameters = None
+ if args.parameters:
+ parameters = json.loads(args.parameters)
+
+ out = chronicle.create_integration_job(
+ integration_name=args.integration_name,
+ display_name=args.display_name,
+ code=args.code,
+ description=args.description,
+ job_id=args.job_id,
+ parameters=parameters,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except json.JSONDecodeError as e:
+ print(f"Error parsing parameters JSON: {e}", file=sys.stderr)
+ sys.exit(1)
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error creating integration job: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_jobs_update_command(args, chronicle):
+ """Handle integration job update command"""
+ try:
+ # Parse parameters if provided
+ parameters = None
+ if args.parameters:
+ parameters = json.loads(args.parameters)
+
+ out = chronicle.update_integration_job(
+ integration_name=args.integration_name,
+ job_id=args.job_id,
+ display_name=args.display_name,
+ code=args.code,
+ description=args.description,
+ parameters=parameters,
+ update_mask=args.update_mask,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except json.JSONDecodeError as e:
+ print(f"Error parsing parameters JSON: {e}", file=sys.stderr)
+ sys.exit(1)
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error updating integration job: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_jobs_test_command(args, chronicle):
+ """Handle integration job test command"""
+ try:
+ # First get the job to test
+ job = chronicle.get_integration_job(
+ integration_name=args.integration_name,
+ job_id=args.job_id,
+ )
+ out = chronicle.execute_integration_job_test(
+ integration_name=args.integration_name,
+ job_id=args.job_id,
+ job=job,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error testing integration job: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_jobs_template_command(args, chronicle):
+ """Handle get job template command"""
+ try:
+ out = chronicle.get_integration_job_template(
+ integration_name=args.integration_name,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error getting job template: {e}", file=sys.stderr)
+ sys.exit(1)
diff --git a/src/secops/cli/commands/integration/logical_operator_revisions.py b/src/secops/cli/commands/integration/logical_operator_revisions.py
new file mode 100644
index 00000000..d09b9d70
--- /dev/null
+++ b/src/secops/cli/commands/integration/logical_operator_revisions.py
@@ -0,0 +1,239 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Google SecOps CLI integration logical operator revisions commands"""
+
+import sys
+
+from secops.cli.utils.formatters import output_formatter
+from secops.cli.utils.common_args import (
+ add_pagination_args,
+ add_as_list_arg,
+)
+
+
+def setup_logical_operator_revisions_command(subparsers):
+ """Setup integration logical operator revisions command"""
+ revisions_parser = subparsers.add_parser(
+ "logical-operator-revisions",
+ help="Manage integration logical operator revisions",
+ )
+ lvl1 = revisions_parser.add_subparsers(
+ dest="logical_operator_revisions_command",
+ help="Integration logical operator revisions command",
+ )
+
+ # list command
+ list_parser = lvl1.add_parser(
+ "list",
+ help="List integration logical operator revisions",
+ )
+ list_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ list_parser.add_argument(
+ "--logical-operator-id",
+ type=str,
+ help="ID of the logical operator",
+ dest="logical_operator_id",
+ required=True,
+ )
+ add_pagination_args(list_parser)
+ add_as_list_arg(list_parser)
+ list_parser.add_argument(
+ "--filter-string",
+ type=str,
+ help="Filter string for listing revisions",
+ dest="filter_string",
+ )
+ list_parser.add_argument(
+ "--order-by",
+ type=str,
+ help="Order by string for listing revisions",
+ dest="order_by",
+ )
+ list_parser.set_defaults(
+ func=handle_logical_operator_revisions_list_command,
+ )
+
+ # delete command
+ delete_parser = lvl1.add_parser(
+ "delete",
+ help="Delete an integration logical operator revision",
+ )
+ delete_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ delete_parser.add_argument(
+ "--logical-operator-id",
+ type=str,
+ help="ID of the logical operator",
+ dest="logical_operator_id",
+ required=True,
+ )
+ delete_parser.add_argument(
+ "--revision-id",
+ type=str,
+ help="ID of the revision to delete",
+ dest="revision_id",
+ required=True,
+ )
+ delete_parser.set_defaults(
+ func=handle_logical_operator_revisions_delete_command,
+ )
+
+ # create command
+ create_parser = lvl1.add_parser(
+ "create",
+ help="Create a new integration logical operator revision",
+ )
+ create_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--logical-operator-id",
+ type=str,
+ help="ID of the logical operator",
+ dest="logical_operator_id",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--comment",
+ type=str,
+ help="Comment describing the revision",
+ dest="comment",
+ )
+ create_parser.set_defaults(
+ func=handle_logical_operator_revisions_create_command,
+ )
+
+ # rollback command
+ rollback_parser = lvl1.add_parser(
+ "rollback",
+ help="Rollback logical operator to a previous revision",
+ )
+ rollback_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ rollback_parser.add_argument(
+ "--logical-operator-id",
+ type=str,
+ help="ID of the logical operator",
+ dest="logical_operator_id",
+ required=True,
+ )
+ rollback_parser.add_argument(
+ "--revision-id",
+ type=str,
+ help="ID of the revision to rollback to",
+ dest="revision_id",
+ required=True,
+ )
+ rollback_parser.set_defaults(
+ func=handle_logical_operator_revisions_rollback_command,
+ )
+
+
+def handle_logical_operator_revisions_list_command(args, chronicle):
+ """Handle integration logical operator revisions list command"""
+ try:
+ out = chronicle.list_integration_logical_operator_revisions(
+ integration_name=args.integration_name,
+ logical_operator_id=args.logical_operator_id,
+ page_size=args.page_size,
+ page_token=args.page_token,
+ filter_string=args.filter_string,
+ order_by=args.order_by,
+ as_list=args.as_list,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error listing logical operator revisions: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_logical_operator_revisions_delete_command(args, chronicle):
+ """Handle integration logical operator revision delete command"""
+ try:
+ chronicle.delete_integration_logical_operator_revision(
+ integration_name=args.integration_name,
+ logical_operator_id=args.logical_operator_id,
+ revision_id=args.revision_id,
+ )
+ print(
+ f"Logical operator revision {args.revision_id} deleted successfully"
+ )
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(
+ f"Error deleting logical operator revision: {e}",
+ file=sys.stderr,
+ )
+ sys.exit(1)
+
+
+def handle_logical_operator_revisions_create_command(args, chronicle):
+ """Handle integration logical operator revision create command"""
+ try:
+ # Get the current logical operator to create a revision
+ logical_operator = chronicle.get_integration_logical_operator(
+ integration_name=args.integration_name,
+ logical_operator_id=args.logical_operator_id,
+ )
+ out = chronicle.create_integration_logical_operator_revision(
+ integration_name=args.integration_name,
+ logical_operator_id=args.logical_operator_id,
+ logical_operator=logical_operator,
+ comment=args.comment,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(
+ f"Error creating logical operator revision: {e}",
+ file=sys.stderr,
+ )
+ sys.exit(1)
+
+
+def handle_logical_operator_revisions_rollback_command(args, chronicle):
+ """Handle integration logical operator revision rollback command"""
+ try:
+ out = chronicle.rollback_integration_logical_operator_revision(
+ integration_name=args.integration_name,
+ logical_operator_id=args.logical_operator_id,
+ revision_id=args.revision_id,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(
+ f"Error rolling back logical operator revision: {e}",
+ file=sys.stderr,
+ )
+ sys.exit(1)
+
diff --git a/src/secops/cli/commands/integration/logical_operators.py b/src/secops/cli/commands/integration/logical_operators.py
new file mode 100644
index 00000000..0bf65725
--- /dev/null
+++ b/src/secops/cli/commands/integration/logical_operators.py
@@ -0,0 +1,395 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Google SecOps CLI integration logical operators commands"""
+
+import sys
+
+from secops.cli.utils.formatters import output_formatter
+from secops.cli.utils.common_args import (
+ add_pagination_args,
+ add_as_list_arg,
+)
+
+
+def setup_logical_operators_command(subparsers):
+ """Setup integration logical operators command"""
+ operators_parser = subparsers.add_parser(
+ "logical-operators",
+ help="Manage integration logical operators",
+ )
+ lvl1 = operators_parser.add_subparsers(
+ dest="logical_operators_command",
+ help="Integration logical operators command",
+ )
+
+ # list command
+ list_parser = lvl1.add_parser(
+ "list",
+ help="List integration logical operators",
+ )
+ list_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ add_pagination_args(list_parser)
+ add_as_list_arg(list_parser)
+ list_parser.add_argument(
+ "--filter-string",
+ type=str,
+ help="Filter string for listing logical operators",
+ dest="filter_string",
+ )
+ list_parser.add_argument(
+ "--order-by",
+ type=str,
+ help="Order by string for listing logical operators",
+ dest="order_by",
+ )
+ list_parser.add_argument(
+ "--exclude-staging",
+ action="store_true",
+ help="Exclude staging logical operators from the response",
+ dest="exclude_staging",
+ )
+ list_parser.add_argument(
+ "--expand",
+ type=str,
+ help="Expand the response with full logical operator details",
+ dest="expand",
+ )
+ list_parser.set_defaults(func=handle_logical_operators_list_command)
+
+ # get command
+ get_parser = lvl1.add_parser(
+ "get",
+ help="Get integration logical operator details",
+ )
+ get_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ get_parser.add_argument(
+ "--logical-operator-id",
+ type=str,
+ help="ID of the logical operator to get",
+ dest="logical_operator_id",
+ required=True,
+ )
+ get_parser.add_argument(
+ "--expand",
+ type=str,
+ help="Expand the response with full logical operator details",
+ dest="expand",
+ )
+ get_parser.set_defaults(func=handle_logical_operators_get_command)
+
+ # delete command
+ delete_parser = lvl1.add_parser(
+ "delete",
+ help="Delete an integration logical operator",
+ )
+ delete_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ delete_parser.add_argument(
+ "--logical-operator-id",
+ type=str,
+ help="ID of the logical operator to delete",
+ dest="logical_operator_id",
+ required=True,
+ )
+ delete_parser.set_defaults(func=handle_logical_operators_delete_command)
+
+ # create command
+ create_parser = lvl1.add_parser(
+ "create",
+ help="Create a new integration logical operator",
+ )
+ create_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--display-name",
+ type=str,
+ help="Display name for the logical operator",
+ dest="display_name",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--script",
+ type=str,
+ help="Python script for the logical operator",
+ dest="script",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--script-timeout",
+ type=str,
+ help="Timeout for script execution (e.g., '60s')",
+ dest="script_timeout",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--enabled",
+ action="store_true",
+ help="Enable the logical operator (default: disabled)",
+ dest="enabled",
+ )
+ create_parser.add_argument(
+ "--description",
+ type=str,
+ help="Description of the logical operator",
+ dest="description",
+ )
+ create_parser.set_defaults(func=handle_logical_operators_create_command)
+
+ # update command
+ update_parser = lvl1.add_parser(
+ "update",
+ help="Update an integration logical operator",
+ )
+ update_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ update_parser.add_argument(
+ "--logical-operator-id",
+ type=str,
+ help="ID of the logical operator to update",
+ dest="logical_operator_id",
+ required=True,
+ )
+ update_parser.add_argument(
+ "--display-name",
+ type=str,
+ help="New display name for the logical operator",
+ dest="display_name",
+ )
+ update_parser.add_argument(
+ "--script",
+ type=str,
+ help="New Python script for the logical operator",
+ dest="script",
+ )
+ update_parser.add_argument(
+ "--script-timeout",
+ type=str,
+ help="New timeout for script execution",
+ dest="script_timeout",
+ )
+ update_parser.add_argument(
+ "--enabled",
+ type=lambda x: x.lower() == "true",
+ help="Enable or disable the logical operator (true/false)",
+ dest="enabled",
+ )
+ update_parser.add_argument(
+ "--description",
+ type=str,
+ help="New description for the logical operator",
+ dest="description",
+ )
+ update_parser.add_argument(
+ "--update-mask",
+ type=str,
+ help="Comma-separated list of fields to update",
+ dest="update_mask",
+ )
+ update_parser.set_defaults(func=handle_logical_operators_update_command)
+
+ # test command
+ test_parser = lvl1.add_parser(
+ "test",
+ help="Execute an integration logical operator test",
+ )
+ test_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ test_parser.add_argument(
+ "--logical-operator-id",
+ type=str,
+ help="ID of the logical operator to test",
+ dest="logical_operator_id",
+ required=True,
+ )
+ test_parser.set_defaults(func=handle_logical_operators_test_command)
+
+ # template command
+ template_parser = lvl1.add_parser(
+ "template",
+ help="Get logical operator template",
+ )
+ template_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ template_parser.set_defaults(func=handle_logical_operators_template_command)
+
+
+def handle_logical_operators_list_command(args, chronicle):
+ """Handle integration logical operators list command"""
+ try:
+ out = chronicle.list_integration_logical_operators(
+ integration_name=args.integration_name,
+ page_size=args.page_size,
+ page_token=args.page_token,
+ filter_string=args.filter_string,
+ order_by=args.order_by,
+ exclude_staging=args.exclude_staging,
+ expand=args.expand,
+ as_list=args.as_list,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(
+ f"Error listing integration logical operators: {e}", file=sys.stderr
+ )
+ sys.exit(1)
+
+
+def handle_logical_operators_get_command(args, chronicle):
+ """Handle integration logical operator get command"""
+ try:
+ out = chronicle.get_integration_logical_operator(
+ integration_name=args.integration_name,
+ logical_operator_id=args.logical_operator_id,
+ expand=args.expand,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(
+ f"Error getting integration logical operator: {e}", file=sys.stderr
+ )
+ sys.exit(1)
+
+
+def handle_logical_operators_delete_command(args, chronicle):
+ """Handle integration logical operator delete command"""
+ try:
+ chronicle.delete_integration_logical_operator(
+ integration_name=args.integration_name,
+ logical_operator_id=args.logical_operator_id,
+ )
+ print(
+ f"Logical operator {args.logical_operator_id} deleted successfully"
+ )
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(
+ f"Error deleting integration logical operator: {e}", file=sys.stderr
+ )
+ sys.exit(1)
+
+
+def handle_logical_operators_create_command(args, chronicle):
+ """Handle integration logical operator create command"""
+ try:
+ out = chronicle.create_integration_logical_operator(
+ integration_name=args.integration_name,
+ display_name=args.display_name,
+ script=args.script,
+ script_timeout=args.script_timeout,
+ enabled=args.enabled,
+ description=args.description,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(
+ f"Error creating integration logical operator: {e}",
+ file=sys.stderr,
+ )
+ sys.exit(1)
+
+
+def handle_logical_operators_update_command(args, chronicle):
+ """Handle integration logical operator update command"""
+ try:
+ out = chronicle.update_integration_logical_operator(
+ integration_name=args.integration_name,
+ logical_operator_id=args.logical_operator_id,
+ display_name=args.display_name,
+ script=args.script,
+ script_timeout=args.script_timeout,
+ enabled=args.enabled,
+ description=args.description,
+ update_mask=args.update_mask,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(
+ f"Error updating integration logical operator: {e}",
+ file=sys.stderr,
+ )
+ sys.exit(1)
+
+
+def handle_logical_operators_test_command(args, chronicle):
+ """Handle integration logical operator test command"""
+ try:
+ # Get the logical operator first
+ logical_operator = chronicle.get_integration_logical_operator(
+ integration_name=args.integration_name,
+ logical_operator_id=args.logical_operator_id,
+ )
+
+ out = chronicle.execute_integration_logical_operator_test(
+ integration_name=args.integration_name,
+ logical_operator=logical_operator,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(
+ f"Error testing integration logical operator: {e}",
+ file=sys.stderr,
+ )
+ sys.exit(1)
+
+
+def handle_logical_operators_template_command(args, chronicle):
+ """Handle integration logical operator template command"""
+ try:
+ out = chronicle.get_integration_logical_operator_template(
+ integration_name=args.integration_name,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(
+ f"Error getting logical operator template: {e}",
+ file=sys.stderr,
+ )
+ sys.exit(1)
diff --git a/src/secops/cli/commands/integration/manager_revisions.py b/src/secops/cli/commands/integration/manager_revisions.py
new file mode 100644
index 00000000..82116abe
--- /dev/null
+++ b/src/secops/cli/commands/integration/manager_revisions.py
@@ -0,0 +1,254 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Google SecOps CLI integration manager revisions commands"""
+
+import sys
+
+from secops.cli.utils.formatters import output_formatter
+from secops.cli.utils.common_args import (
+ add_pagination_args,
+ add_as_list_arg,
+)
+
+
+def setup_manager_revisions_command(subparsers):
+ """Setup integration manager revisions command"""
+ revisions_parser = subparsers.add_parser(
+ "manager-revisions",
+ help="Manage integration manager revisions",
+ )
+ lvl1 = revisions_parser.add_subparsers(
+ dest="manager_revisions_command",
+ help="Integration manager revisions command",
+ )
+
+ # list command
+ list_parser = lvl1.add_parser(
+ "list", help="List integration manager revisions"
+ )
+ list_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ list_parser.add_argument(
+ "--manager-id",
+ type=str,
+ help="ID of the manager",
+ dest="manager_id",
+ required=True,
+ )
+ add_pagination_args(list_parser)
+ add_as_list_arg(list_parser)
+ list_parser.add_argument(
+ "--filter-string",
+ type=str,
+ help="Filter string for listing revisions",
+ dest="filter_string",
+ )
+ list_parser.add_argument(
+ "--order-by",
+ type=str,
+ help="Order by string for listing revisions",
+ dest="order_by",
+ )
+ list_parser.set_defaults(func=handle_manager_revisions_list_command)
+
+ # get command
+ get_parser = lvl1.add_parser("get", help="Get a specific manager revision")
+ get_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ get_parser.add_argument(
+ "--manager-id",
+ type=str,
+ help="ID of the manager",
+ dest="manager_id",
+ required=True,
+ )
+ get_parser.add_argument(
+ "--revision-id",
+ type=str,
+ help="ID of the revision to get",
+ dest="revision_id",
+ required=True,
+ )
+ get_parser.set_defaults(func=handle_manager_revisions_get_command)
+
+ # delete command
+ delete_parser = lvl1.add_parser(
+ "delete", help="Delete an integration manager revision"
+ )
+ delete_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ delete_parser.add_argument(
+ "--manager-id",
+ type=str,
+ help="ID of the manager",
+ dest="manager_id",
+ required=True,
+ )
+ delete_parser.add_argument(
+ "--revision-id",
+ type=str,
+ help="ID of the revision to delete",
+ dest="revision_id",
+ required=True,
+ )
+ delete_parser.set_defaults(func=handle_manager_revisions_delete_command)
+
+ # create command
+ create_parser = lvl1.add_parser(
+ "create", help="Create a new integration manager revision"
+ )
+ create_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--manager-id",
+ type=str,
+ help="ID of the manager",
+ dest="manager_id",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--comment",
+ type=str,
+ help="Comment describing the revision",
+ dest="comment",
+ )
+ create_parser.set_defaults(func=handle_manager_revisions_create_command)
+
+ # rollback command
+ rollback_parser = lvl1.add_parser(
+ "rollback", help="Rollback manager to a previous revision"
+ )
+ rollback_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ rollback_parser.add_argument(
+ "--manager-id",
+ type=str,
+ help="ID of the manager",
+ dest="manager_id",
+ required=True,
+ )
+ rollback_parser.add_argument(
+ "--revision-id",
+ type=str,
+ help="ID of the revision to rollback to",
+ dest="revision_id",
+ required=True,
+ )
+ rollback_parser.set_defaults(func=handle_manager_revisions_rollback_command)
+
+
+def handle_manager_revisions_list_command(args, chronicle):
+ """Handle integration manager revisions list command"""
+ try:
+ out = chronicle.list_integration_manager_revisions(
+ integration_name=args.integration_name,
+ manager_id=args.manager_id,
+ page_size=args.page_size,
+ page_token=args.page_token,
+ filter_string=args.filter_string,
+ order_by=args.order_by,
+ as_list=args.as_list,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error listing manager revisions: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_manager_revisions_get_command(args, chronicle):
+ """Handle integration manager revision get command"""
+ try:
+ out = chronicle.get_integration_manager_revision(
+ integration_name=args.integration_name,
+ manager_id=args.manager_id,
+ revision_id=args.revision_id,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error getting manager revision: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_manager_revisions_delete_command(args, chronicle):
+ """Handle integration manager revision delete command"""
+ try:
+ chronicle.delete_integration_manager_revision(
+ integration_name=args.integration_name,
+ manager_id=args.manager_id,
+ revision_id=args.revision_id,
+ )
+ print(f"Manager revision {args.revision_id} deleted successfully")
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error deleting manager revision: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_manager_revisions_create_command(args, chronicle):
+ """Handle integration manager revision create command"""
+ try:
+ # Get the current manager to create a revision
+ manager = chronicle.get_integration_manager(
+ integration_name=args.integration_name,
+ manager_id=args.manager_id,
+ )
+ out = chronicle.create_integration_manager_revision(
+ integration_name=args.integration_name,
+ manager_id=args.manager_id,
+ manager=manager,
+ comment=args.comment,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error creating manager revision: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_manager_revisions_rollback_command(args, chronicle):
+ """Handle integration manager revision rollback command"""
+ try:
+ out = chronicle.rollback_integration_manager_revision(
+ integration_name=args.integration_name,
+ manager_id=args.manager_id,
+ revision_id=args.revision_id,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error rolling back manager revision: {e}", file=sys.stderr)
+ sys.exit(1)
diff --git a/src/secops/cli/commands/integration/managers.py b/src/secops/cli/commands/integration/managers.py
new file mode 100644
index 00000000..e5f202a0
--- /dev/null
+++ b/src/secops/cli/commands/integration/managers.py
@@ -0,0 +1,283 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Google SecOps CLI integration managers commands"""
+
+import sys
+
+from secops.cli.utils.formatters import output_formatter
+from secops.cli.utils.common_args import (
+ add_pagination_args,
+ add_as_list_arg,
+)
+
+
+def setup_managers_command(subparsers):
+ """Setup integration managers command"""
+ managers_parser = subparsers.add_parser(
+ "managers",
+ help="Manage integration managers",
+ )
+ lvl1 = managers_parser.add_subparsers(
+ dest="managers_command", help="Integration managers command"
+ )
+
+ # list command
+ list_parser = lvl1.add_parser("list", help="List integration managers")
+ list_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ add_pagination_args(list_parser)
+ add_as_list_arg(list_parser)
+ list_parser.add_argument(
+ "--filter-string",
+ type=str,
+ help="Filter string for listing managers",
+ dest="filter_string",
+ )
+ list_parser.add_argument(
+ "--order-by",
+ type=str,
+ help="Order by string for listing managers",
+ dest="order_by",
+ )
+ list_parser.set_defaults(func=handle_managers_list_command)
+
+ # get command
+ get_parser = lvl1.add_parser("get", help="Get integration manager details")
+ get_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ get_parser.add_argument(
+ "--manager-id",
+ type=str,
+ help="ID of the manager to get",
+ dest="manager_id",
+ required=True,
+ )
+ get_parser.set_defaults(func=handle_managers_get_command)
+
+ # delete command
+ delete_parser = lvl1.add_parser(
+ "delete",
+ help="Delete an integration manager",
+ )
+ delete_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ delete_parser.add_argument(
+ "--manager-id",
+ type=str,
+ help="ID of the manager to delete",
+ dest="manager_id",
+ required=True,
+ )
+ delete_parser.set_defaults(func=handle_managers_delete_command)
+
+ # create command
+ create_parser = lvl1.add_parser(
+ "create", help="Create a new integration manager"
+ )
+ create_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--display-name",
+ type=str,
+ help="Display name for the manager",
+ dest="display_name",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--code",
+ type=str,
+ help="Python code for the manager",
+ dest="code",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--description",
+ type=str,
+ help="Description of the manager",
+ dest="description",
+ )
+ create_parser.add_argument(
+ "--manager-id",
+ type=str,
+ help="Custom ID for the manager",
+ dest="manager_id",
+ )
+ create_parser.set_defaults(func=handle_managers_create_command)
+
+ # update command
+ update_parser = lvl1.add_parser(
+ "update", help="Update an integration manager"
+ )
+ update_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ update_parser.add_argument(
+ "--manager-id",
+ type=str,
+ help="ID of the manager to update",
+ dest="manager_id",
+ required=True,
+ )
+ update_parser.add_argument(
+ "--display-name",
+ type=str,
+ help="New display name for the manager",
+ dest="display_name",
+ )
+ update_parser.add_argument(
+ "--code",
+ type=str,
+ help="New Python code for the manager",
+ dest="code",
+ )
+ update_parser.add_argument(
+ "--description",
+ type=str,
+ help="New description for the manager",
+ dest="description",
+ )
+ update_parser.add_argument(
+ "--update-mask",
+ type=str,
+ help="Comma-separated list of fields to update",
+ dest="update_mask",
+ )
+ update_parser.set_defaults(func=handle_managers_update_command)
+
+ # template command
+ template_parser = lvl1.add_parser(
+ "template",
+ help="Get a template for creating a manager",
+ )
+ template_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ template_parser.set_defaults(func=handle_managers_template_command)
+
+
+def handle_managers_list_command(args, chronicle):
+ """Handle integration managers list command"""
+ try:
+ out = chronicle.list_integration_managers(
+ integration_name=args.integration_name,
+ page_size=args.page_size,
+ page_token=args.page_token,
+ filter_string=args.filter_string,
+ order_by=args.order_by,
+ as_list=args.as_list,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error listing integration managers: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_managers_get_command(args, chronicle):
+ """Handle integration manager get command"""
+ try:
+ out = chronicle.get_integration_manager(
+ integration_name=args.integration_name,
+ manager_id=args.manager_id,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error getting integration manager: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_managers_delete_command(args, chronicle):
+ """Handle integration manager delete command"""
+ try:
+ chronicle.delete_integration_manager(
+ integration_name=args.integration_name,
+ manager_id=args.manager_id,
+ )
+ print(f"Manager {args.manager_id} deleted successfully")
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error deleting integration manager: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_managers_create_command(args, chronicle):
+ """Handle integration manager create command"""
+ try:
+ out = chronicle.create_integration_manager(
+ integration_name=args.integration_name,
+ display_name=args.display_name,
+ code=args.code,
+ description=args.description,
+ manager_id=args.manager_id,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error creating integration manager: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_managers_update_command(args, chronicle):
+ """Handle integration manager update command"""
+ try:
+ out = chronicle.update_integration_manager(
+ integration_name=args.integration_name,
+ manager_id=args.manager_id,
+ display_name=args.display_name,
+ code=args.code,
+ description=args.description,
+ update_mask=args.update_mask,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error updating integration manager: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_managers_template_command(args, chronicle):
+ """Handle get manager template command"""
+ try:
+ out = chronicle.get_integration_manager_template(
+ integration_name=args.integration_name,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error getting manager template: {e}", file=sys.stderr)
+ sys.exit(1)
diff --git a/src/secops/cli/commands/integration/marketplace_integration.py b/src/secops/cli/commands/integration/marketplace_integration.py
new file mode 100644
index 00000000..f8b87aa2
--- /dev/null
+++ b/src/secops/cli/commands/integration/marketplace_integration.py
@@ -0,0 +1,204 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Google SecOps CLI marketplace integration commands"""
+
+import sys
+
+from secops.cli.utils.formatters import output_formatter
+from secops.cli.utils.common_args import (
+ add_pagination_args,
+ add_as_list_arg,
+)
+
+
+def setup_marketplace_integrations_command(subparsers):
+ """Setup marketplace integration command"""
+ mp_parser = subparsers.add_parser(
+ "marketplace",
+ help="Manage Chronicle marketplace integration",
+ )
+ lvl1 = mp_parser.add_subparsers(
+ dest="mp_command", help="Marketplace integration command"
+ )
+
+ # list command
+ list_parser = lvl1.add_parser("list", help="List marketplace integrations")
+ add_pagination_args(list_parser)
+ add_as_list_arg(list_parser)
+ list_parser.add_argument(
+ "--filter-string",
+ type=str,
+ help="Filter string for listing marketplace integrations",
+ dest="filter_string",
+ )
+ list_parser.add_argument(
+ "--order-by",
+ type=str,
+ help="Order by string for listing marketplace integrations",
+ dest="order_by",
+ )
+ list_parser.set_defaults(func=handle_mp_integration_list_command)
+
+ # get command
+ get_parser = lvl1.add_parser(
+ "get", help="Get marketplace integration details"
+ )
+ get_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the marketplace integration to get",
+ dest="integration_name",
+ required=True,
+ )
+ get_parser.set_defaults(func=handle_mp_integration_get_command)
+
+ # diff command
+ diff_parser = lvl1.add_parser(
+ "diff",
+ help="Get marketplace integration diff between "
+ "installed and latest version",
+ )
+ diff_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the marketplace integration to diff",
+ dest="integration_name",
+ required=True,
+ )
+ diff_parser.set_defaults(func=handle_mp_integration_diff_command)
+
+ # install command
+ install_parser = lvl1.add_parser(
+ "install", help="Install or update a marketplace integration"
+ )
+ install_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the marketplace integration to install or update",
+ dest="integration_name",
+ required=True,
+ )
+ install_parser.add_argument(
+ "--override-mapping",
+ action="store_true",
+ help="Override existing mapping",
+ dest="override_mapping",
+ )
+ install_parser.add_argument(
+ "--staging",
+ action="store_true",
+ help="Whether to install the integration in "
+ "staging environment (true/false)",
+ dest="staging",
+ )
+ install_parser.add_argument(
+ "--version",
+ type=str,
+ help="Version of the marketplace integration to install",
+ dest="version",
+ )
+ install_parser.add_argument(
+ "--restore-from-snapshot",
+ action="store_true",
+ help="Whether to restore the integration from existing snapshot "
+ "(true/false)",
+ dest="restore_from_snapshot",
+ )
+ install_parser.set_defaults(func=handle_mp_integration_install_command)
+
+ # uninstall command
+ uninstall_parser = lvl1.add_parser(
+ "uninstall", help="Uninstall a marketplace integration"
+ )
+ uninstall_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the marketplace integration to uninstall",
+ dest="integration_name",
+ required=True,
+ )
+ uninstall_parser.set_defaults(func=handle_mp_integration_uninstall_command)
+
+
+def handle_mp_integration_list_command(args, chronicle):
+ """Handle marketplace integration list command"""
+ try:
+ out = chronicle.list_marketplace_integrations(
+ page_size=args.page_size,
+ page_token=args.page_token,
+ filter_string=args.filter_string,
+ order_by=args.order_by,
+ as_list=args.as_list,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error listing marketplace integrations: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_mp_integration_get_command(args, chronicle):
+ """Handle marketplace integration get command"""
+ try:
+ out = chronicle.get_marketplace_integration(
+ integration_name=args.integration_name,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error getting marketplace integration: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_mp_integration_diff_command(args, chronicle):
+ """Handle marketplace integration diff command"""
+ try:
+ out = chronicle.get_marketplace_integration_diff(
+ integration_name=args.integration_name,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(
+ f"Error getting marketplace integration diff: {e}", file=sys.stderr
+ )
+ sys.exit(1)
+
+
+def handle_mp_integration_install_command(args, chronicle):
+ """Handle marketplace integration install command"""
+ try:
+ out = chronicle.install_marketplace_integration(
+ integration_name=args.integration_name,
+ override_mapping=args.override_mapping,
+ staging=args.staging,
+ version=args.version,
+ restore_from_snapshot=args.restore_from_snapshot,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error installing marketplace integration: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_mp_integration_uninstall_command(args, chronicle):
+ """Handle marketplace integration uninstall command"""
+ try:
+ out = chronicle.uninstall_marketplace_integration(
+ integration_name=args.integration_name,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(
+ f"Error uninstalling marketplace integration: {e}", file=sys.stderr
+ )
+ sys.exit(1)
diff --git a/src/secops/cli/commands/integration/transformer_revisions.py b/src/secops/cli/commands/integration/transformer_revisions.py
new file mode 100644
index 00000000..1075a696
--- /dev/null
+++ b/src/secops/cli/commands/integration/transformer_revisions.py
@@ -0,0 +1,236 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Google SecOps CLI integration transformer revisions commands"""
+
+import sys
+
+from secops.cli.utils.formatters import output_formatter
+from secops.cli.utils.common_args import (
+ add_pagination_args,
+ add_as_list_arg,
+)
+
+
+def setup_transformer_revisions_command(subparsers):
+ """Setup integration transformer revisions command"""
+ revisions_parser = subparsers.add_parser(
+ "transformer-revisions",
+ help="Manage integration transformer revisions",
+ )
+ lvl1 = revisions_parser.add_subparsers(
+ dest="transformer_revisions_command",
+ help="Integration transformer revisions command",
+ )
+
+ # list command
+ list_parser = lvl1.add_parser(
+ "list",
+ help="List integration transformer revisions",
+ )
+ list_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ list_parser.add_argument(
+ "--transformer-id",
+ type=str,
+ help="ID of the transformer",
+ dest="transformer_id",
+ required=True,
+ )
+ add_pagination_args(list_parser)
+ add_as_list_arg(list_parser)
+ list_parser.add_argument(
+ "--filter-string",
+ type=str,
+ help="Filter string for listing revisions",
+ dest="filter_string",
+ )
+ list_parser.add_argument(
+ "--order-by",
+ type=str,
+ help="Order by string for listing revisions",
+ dest="order_by",
+ )
+ list_parser.set_defaults(
+ func=handle_transformer_revisions_list_command,
+ )
+
+ # delete command
+ delete_parser = lvl1.add_parser(
+ "delete",
+ help="Delete an integration transformer revision",
+ )
+ delete_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ delete_parser.add_argument(
+ "--transformer-id",
+ type=str,
+ help="ID of the transformer",
+ dest="transformer_id",
+ required=True,
+ )
+ delete_parser.add_argument(
+ "--revision-id",
+ type=str,
+ help="ID of the revision to delete",
+ dest="revision_id",
+ required=True,
+ )
+ delete_parser.set_defaults(
+ func=handle_transformer_revisions_delete_command,
+ )
+
+ # create command
+ create_parser = lvl1.add_parser(
+ "create",
+ help="Create a new integration transformer revision",
+ )
+ create_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--transformer-id",
+ type=str,
+ help="ID of the transformer",
+ dest="transformer_id",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--comment",
+ type=str,
+ help="Comment describing the revision",
+ dest="comment",
+ )
+ create_parser.set_defaults(
+ func=handle_transformer_revisions_create_command,
+ )
+
+ # rollback command
+ rollback_parser = lvl1.add_parser(
+ "rollback",
+ help="Rollback transformer to a previous revision",
+ )
+ rollback_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ rollback_parser.add_argument(
+ "--transformer-id",
+ type=str,
+ help="ID of the transformer",
+ dest="transformer_id",
+ required=True,
+ )
+ rollback_parser.add_argument(
+ "--revision-id",
+ type=str,
+ help="ID of the revision to rollback to",
+ dest="revision_id",
+ required=True,
+ )
+ rollback_parser.set_defaults(
+ func=handle_transformer_revisions_rollback_command,
+ )
+
+
+def handle_transformer_revisions_list_command(args, chronicle):
+ """Handle integration transformer revisions list command"""
+ try:
+ out = chronicle.list_integration_transformer_revisions(
+ integration_name=args.integration_name,
+ transformer_id=args.transformer_id,
+ page_size=args.page_size,
+ page_token=args.page_token,
+ filter_string=args.filter_string,
+ order_by=args.order_by,
+ as_list=args.as_list,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error listing transformer revisions: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_transformer_revisions_delete_command(args, chronicle):
+ """Handle integration transformer revision delete command"""
+ try:
+ chronicle.delete_integration_transformer_revision(
+ integration_name=args.integration_name,
+ transformer_id=args.transformer_id,
+ revision_id=args.revision_id,
+ )
+ print(f"Transformer revision {args.revision_id} deleted successfully")
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(
+ f"Error deleting transformer revision: {e}",
+ file=sys.stderr,
+ )
+ sys.exit(1)
+
+
+def handle_transformer_revisions_create_command(args, chronicle):
+ """Handle integration transformer revision create command"""
+ try:
+ # Get the current transformer to create a revision
+ transformer = chronicle.get_integration_transformer(
+ integration_name=args.integration_name,
+ transformer_id=args.transformer_id,
+ )
+ out = chronicle.create_integration_transformer_revision(
+ integration_name=args.integration_name,
+ transformer_id=args.transformer_id,
+ transformer=transformer,
+ comment=args.comment,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(
+ f"Error creating transformer revision: {e}",
+ file=sys.stderr,
+ )
+ sys.exit(1)
+
+
+def handle_transformer_revisions_rollback_command(args, chronicle):
+ """Handle integration transformer revision rollback command"""
+ try:
+ out = chronicle.rollback_integration_transformer_revision(
+ integration_name=args.integration_name,
+ transformer_id=args.transformer_id,
+ revision_id=args.revision_id,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(
+ f"Error rolling back transformer revision: {e}",
+ file=sys.stderr,
+ )
+ sys.exit(1)
diff --git a/src/secops/cli/commands/integration/transformers.py b/src/secops/cli/commands/integration/transformers.py
new file mode 100644
index 00000000..65dcd32d
--- /dev/null
+++ b/src/secops/cli/commands/integration/transformers.py
@@ -0,0 +1,387 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Google SecOps CLI integration transformers commands"""
+
+import sys
+
+from secops.cli.utils.formatters import output_formatter
+from secops.cli.utils.common_args import (
+ add_pagination_args,
+ add_as_list_arg,
+)
+
+
+def setup_transformers_command(subparsers):
+ """Setup integration transformers command"""
+ transformers_parser = subparsers.add_parser(
+ "transformers",
+ help="Manage integration transformers",
+ )
+ lvl1 = transformers_parser.add_subparsers(
+ dest="transformers_command",
+ help="Integration transformers command",
+ )
+
+ # list command
+ list_parser = lvl1.add_parser(
+ "list",
+ help="List integration transformers",
+ )
+ list_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ add_pagination_args(list_parser)
+ add_as_list_arg(list_parser)
+ list_parser.add_argument(
+ "--filter-string",
+ type=str,
+ help="Filter string for listing transformers",
+ dest="filter_string",
+ )
+ list_parser.add_argument(
+ "--order-by",
+ type=str,
+ help="Order by string for listing transformers",
+ dest="order_by",
+ )
+ list_parser.add_argument(
+ "--exclude-staging",
+ action="store_true",
+ help="Exclude staging transformers from the response",
+ dest="exclude_staging",
+ )
+ list_parser.add_argument(
+ "--expand",
+ type=str,
+ help="Expand the response with full transformer details",
+ dest="expand",
+ )
+ list_parser.set_defaults(func=handle_transformers_list_command)
+
+ # get command
+ get_parser = lvl1.add_parser(
+ "get",
+ help="Get integration transformer details",
+ )
+ get_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ get_parser.add_argument(
+ "--transformer-id",
+ type=str,
+ help="ID of the transformer to get",
+ dest="transformer_id",
+ required=True,
+ )
+ get_parser.add_argument(
+ "--expand",
+ type=str,
+ help="Expand the response with full transformer details",
+ dest="expand",
+ )
+ get_parser.set_defaults(func=handle_transformers_get_command)
+
+ # delete command
+ delete_parser = lvl1.add_parser(
+ "delete",
+ help="Delete an integration transformer",
+ )
+ delete_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ delete_parser.add_argument(
+ "--transformer-id",
+ type=str,
+ help="ID of the transformer to delete",
+ dest="transformer_id",
+ required=True,
+ )
+ delete_parser.set_defaults(func=handle_transformers_delete_command)
+
+ # create command
+ create_parser = lvl1.add_parser(
+ "create",
+ help="Create a new integration transformer",
+ )
+ create_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--display-name",
+ type=str,
+ help="Display name for the transformer",
+ dest="display_name",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--script",
+ type=str,
+ help="Python script for the transformer",
+ dest="script",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--script-timeout",
+ type=str,
+ help="Timeout for script execution (e.g., '60s')",
+ dest="script_timeout",
+ required=True,
+ )
+ create_parser.add_argument(
+ "--enabled",
+ action="store_true",
+ help="Enable the transformer (default: disabled)",
+ dest="enabled",
+ )
+ create_parser.add_argument(
+ "--description",
+ type=str,
+ help="Description of the transformer",
+ dest="description",
+ )
+ create_parser.set_defaults(func=handle_transformers_create_command)
+
+ # update command
+ update_parser = lvl1.add_parser(
+ "update",
+ help="Update an integration transformer",
+ )
+ update_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ update_parser.add_argument(
+ "--transformer-id",
+ type=str,
+ help="ID of the transformer to update",
+ dest="transformer_id",
+ required=True,
+ )
+ update_parser.add_argument(
+ "--display-name",
+ type=str,
+ help="New display name for the transformer",
+ dest="display_name",
+ )
+ update_parser.add_argument(
+ "--script",
+ type=str,
+ help="New Python script for the transformer",
+ dest="script",
+ )
+ update_parser.add_argument(
+ "--script-timeout",
+ type=str,
+ help="New timeout for script execution",
+ dest="script_timeout",
+ )
+ update_parser.add_argument(
+ "--enabled",
+ type=lambda x: x.lower() == "true",
+ help="Enable or disable the transformer (true/false)",
+ dest="enabled",
+ )
+ update_parser.add_argument(
+ "--description",
+ type=str,
+ help="New description for the transformer",
+ dest="description",
+ )
+ update_parser.add_argument(
+ "--update-mask",
+ type=str,
+ help="Comma-separated list of fields to update",
+ dest="update_mask",
+ )
+ update_parser.set_defaults(func=handle_transformers_update_command)
+
+ # test command
+ test_parser = lvl1.add_parser(
+ "test",
+ help="Execute an integration transformer test",
+ )
+ test_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ test_parser.add_argument(
+ "--transformer-id",
+ type=str,
+ help="ID of the transformer to test",
+ dest="transformer_id",
+ required=True,
+ )
+ test_parser.set_defaults(func=handle_transformers_test_command)
+
+ # template command
+ template_parser = lvl1.add_parser(
+ "template",
+ help="Get transformer template",
+ )
+ template_parser.add_argument(
+ "--integration-name",
+ type=str,
+ help="Name of the integration",
+ dest="integration_name",
+ required=True,
+ )
+ template_parser.set_defaults(func=handle_transformers_template_command)
+
+
+def handle_transformers_list_command(args, chronicle):
+ """Handle integration transformers list command"""
+ try:
+ out = chronicle.list_integration_transformers(
+ integration_name=args.integration_name,
+ page_size=args.page_size,
+ page_token=args.page_token,
+ filter_string=args.filter_string,
+ order_by=args.order_by,
+ exclude_staging=args.exclude_staging,
+ expand=args.expand,
+ as_list=args.as_list,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error listing integration transformers: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_transformers_get_command(args, chronicle):
+ """Handle integration transformer get command"""
+ try:
+ out = chronicle.get_integration_transformer(
+ integration_name=args.integration_name,
+ transformer_id=args.transformer_id,
+ expand=args.expand,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error getting integration transformer: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_transformers_delete_command(args, chronicle):
+ """Handle integration transformer delete command"""
+ try:
+ chronicle.delete_integration_transformer(
+ integration_name=args.integration_name,
+ transformer_id=args.transformer_id,
+ )
+ print(f"Transformer {args.transformer_id} deleted successfully")
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(f"Error deleting integration transformer: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+def handle_transformers_create_command(args, chronicle):
+ """Handle integration transformer create command"""
+ try:
+ out = chronicle.create_integration_transformer(
+ integration_name=args.integration_name,
+ display_name=args.display_name,
+ script=args.script,
+ script_timeout=args.script_timeout,
+ enabled=args.enabled,
+ description=args.description,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(
+ f"Error creating integration transformer: {e}",
+ file=sys.stderr,
+ )
+ sys.exit(1)
+
+
+def handle_transformers_update_command(args, chronicle):
+ """Handle integration transformer update command"""
+ try:
+ out = chronicle.update_integration_transformer(
+ integration_name=args.integration_name,
+ transformer_id=args.transformer_id,
+ display_name=args.display_name,
+ script=args.script,
+ script_timeout=args.script_timeout,
+ enabled=args.enabled,
+ description=args.description,
+ update_mask=args.update_mask,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(
+ f"Error updating integration transformer: {e}",
+ file=sys.stderr,
+ )
+ sys.exit(1)
+
+
+def handle_transformers_test_command(args, chronicle):
+ """Handle integration transformer test command"""
+ try:
+ # Get the transformer first
+ transformer = chronicle.get_integration_transformer(
+ integration_name=args.integration_name,
+ transformer_id=args.transformer_id,
+ )
+
+ out = chronicle.execute_integration_transformer_test(
+ integration_name=args.integration_name,
+ transformer=transformer,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(
+ f"Error testing integration transformer: {e}",
+ file=sys.stderr,
+ )
+ sys.exit(1)
+
+
+def handle_transformers_template_command(args, chronicle):
+ """Handle integration transformer template command"""
+ try:
+ out = chronicle.get_integration_transformer_template(
+ integration_name=args.integration_name,
+ )
+ output_formatter(out, getattr(args, "output", "json"))
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print(
+ f"Error getting transformer template: {e}",
+ file=sys.stderr,
+ )
+ sys.exit(1)
diff --git a/tests/chronicle/integration/__init__.py b/tests/chronicle/integration/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/chronicle/integration/test_action_revisions.py b/tests/chronicle/integration/test_action_revisions.py
new file mode 100644
index 00000000..f9abd9bc
--- /dev/null
+++ b/tests/chronicle/integration/test_action_revisions.py
@@ -0,0 +1,409 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Tests for Chronicle marketplace integration action revisions functions."""
+
+from unittest.mock import Mock, patch
+
+import pytest
+
+from secops.chronicle.client import ChronicleClient
+from secops.chronicle.models import APIVersion
+from secops.chronicle.integration.action_revisions import (
+ list_integration_action_revisions,
+ delete_integration_action_revision,
+ create_integration_action_revision,
+ rollback_integration_action_revision,
+)
+from secops.exceptions import APIError
+
+
+@pytest.fixture
+def chronicle_client():
+ """Create a Chronicle client for testing."""
+ with patch("secops.auth.SecOpsAuth") as mock_auth:
+ mock_session = Mock()
+ mock_session.headers = {}
+ mock_auth.return_value.session = mock_session
+ return ChronicleClient(
+ customer_id="test-customer",
+ project_id="test-project",
+ default_api_version=APIVersion.V1BETA,
+ )
+
+
+# -- list_integration_action_revisions tests --
+
+
+def test_list_integration_action_revisions_success(chronicle_client):
+ """Test list_integration_action_revisions delegates to chronicle_paginated_request."""
+ expected = {
+ "revisions": [{"name": "r1"}, {"name": "r2"}],
+ "nextPageToken": "t",
+ }
+
+ with patch(
+ "secops.chronicle.integration.action_revisions.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated, patch(
+ "secops.chronicle.integration.action_revisions.format_resource_id",
+ return_value="My Integration",
+ ):
+ result = list_integration_action_revisions(
+ chronicle_client,
+ integration_name="My Integration",
+ action_id="a1",
+ page_size=10,
+ page_token="next-token",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_paginated.call_args
+ assert "actions/a1/revisions" in kwargs["path"]
+ assert kwargs["items_key"] == "revisions"
+ assert kwargs["page_size"] == 10
+ assert kwargs["page_token"] == "next-token"
+
+
+def test_list_integration_action_revisions_default_args(chronicle_client):
+ """Test list_integration_action_revisions with default args."""
+ expected = {"revisions": []}
+
+ with patch(
+ "secops.chronicle.integration.action_revisions.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_integration_action_revisions(
+ chronicle_client,
+ integration_name="test-integration",
+ action_id="a1",
+ )
+
+ assert result == expected
+
+
+def test_list_integration_action_revisions_with_filters(chronicle_client):
+ """Test list_integration_action_revisions with filter and order_by."""
+ expected = {"revisions": [{"name": "r1"}]}
+
+ with patch(
+ "secops.chronicle.integration.action_revisions.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_integration_action_revisions(
+ chronicle_client,
+ integration_name="test-integration",
+ action_id="a1",
+ filter_string='version = "1.0"',
+ order_by="createTime",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_paginated.call_args
+ assert kwargs["extra_params"] == {
+ "filter": 'version = "1.0"',
+ "orderBy": "createTime",
+ }
+
+
+def test_list_integration_action_revisions_as_list(chronicle_client):
+ """Test list_integration_action_revisions returns list when as_list=True."""
+ expected = [{"name": "r1"}, {"name": "r2"}]
+
+ with patch(
+ "secops.chronicle.integration.action_revisions.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_integration_action_revisions(
+ chronicle_client,
+ integration_name="test-integration",
+ action_id="a1",
+ as_list=True,
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_paginated.call_args
+ assert kwargs["as_list"] is True
+
+
+def test_list_integration_action_revisions_error(chronicle_client):
+ """Test list_integration_action_revisions raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.action_revisions.chronicle_paginated_request",
+ side_effect=APIError("Failed to list action revisions"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ list_integration_action_revisions(
+ chronicle_client,
+ integration_name="test-integration",
+ action_id="a1",
+ )
+ assert "Failed to list action revisions" in str(exc_info.value)
+
+
+# -- delete_integration_action_revision tests --
+
+
+def test_delete_integration_action_revision_success(chronicle_client):
+ """Test delete_integration_action_revision issues DELETE request."""
+ with patch(
+ "secops.chronicle.integration.action_revisions.chronicle_request",
+ return_value=None,
+ ) as mock_request:
+ delete_integration_action_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ action_id="a1",
+ revision_id="r1",
+ )
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["method"] == "DELETE"
+ assert "actions/a1/revisions/r1" in kwargs["endpoint_path"]
+ assert kwargs["api_version"] == APIVersion.V1BETA
+
+
+def test_delete_integration_action_revision_error(chronicle_client):
+ """Test delete_integration_action_revision raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.action_revisions.chronicle_request",
+ side_effect=APIError("Failed to delete action revision"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ delete_integration_action_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ action_id="a1",
+ revision_id="r1",
+ )
+ assert "Failed to delete action revision" in str(exc_info.value)
+
+
+# -- create_integration_action_revision tests --
+
+
+def test_create_integration_action_revision_success(chronicle_client):
+ """Test create_integration_action_revision issues POST request."""
+ expected = {
+ "name": "revisions/r1",
+ "comment": "Test revision",
+ }
+
+ action = {
+ "name": "actions/a1",
+ "displayName": "Test Action",
+ "code": "print('hello')",
+ }
+
+ with patch(
+ "secops.chronicle.integration.action_revisions.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = create_integration_action_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ action_id="a1",
+ action=action,
+ comment="Test revision",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["method"] == "POST"
+ assert "actions/a1/revisions" in kwargs["endpoint_path"]
+ assert kwargs["json"]["action"] == action
+ assert kwargs["json"]["comment"] == "Test revision"
+ assert kwargs["api_version"] == APIVersion.V1BETA
+
+
+def test_create_integration_action_revision_without_comment(chronicle_client):
+ """Test create_integration_action_revision without comment."""
+ expected = {"name": "revisions/r1"}
+
+ action = {
+ "name": "actions/a1",
+ "displayName": "Test Action",
+ }
+
+ with patch(
+ "secops.chronicle.integration.action_revisions.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = create_integration_action_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ action_id="a1",
+ action=action,
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["json"]["action"] == action
+ assert "comment" not in kwargs["json"]
+
+
+def test_create_integration_action_revision_error(chronicle_client):
+ """Test create_integration_action_revision raises APIError on failure."""
+ action = {"name": "actions/a1"}
+
+ with patch(
+ "secops.chronicle.integration.action_revisions.chronicle_request",
+ side_effect=APIError("Failed to create action revision"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ create_integration_action_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ action_id="a1",
+ action=action,
+ )
+ assert "Failed to create action revision" in str(exc_info.value)
+
+
+# -- rollback_integration_action_revision tests --
+
+
+def test_rollback_integration_action_revision_success(chronicle_client):
+ """Test rollback_integration_action_revision issues POST request."""
+ expected = {
+ "name": "revisions/r1",
+ "comment": "Rolled back",
+ }
+
+ with patch(
+ "secops.chronicle.integration.action_revisions.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = rollback_integration_action_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ action_id="a1",
+ revision_id="r1",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["method"] == "POST"
+ assert "actions/a1/revisions/r1:rollback" in kwargs["endpoint_path"]
+ assert kwargs["api_version"] == APIVersion.V1BETA
+
+
+def test_rollback_integration_action_revision_error(chronicle_client):
+ """Test rollback_integration_action_revision raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.action_revisions.chronicle_request",
+ side_effect=APIError("Failed to rollback action revision"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ rollback_integration_action_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ action_id="a1",
+ revision_id="r1",
+ )
+ assert "Failed to rollback action revision" in str(exc_info.value)
+
+
+# -- API version tests --
+
+
+def test_list_integration_action_revisions_custom_api_version(chronicle_client):
+ """Test list_integration_action_revisions with custom API version."""
+ expected = {"revisions": []}
+
+ with patch(
+ "secops.chronicle.integration.action_revisions.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_integration_action_revisions(
+ chronicle_client,
+ integration_name="test-integration",
+ action_id="a1",
+ api_version=APIVersion.V1ALPHA,
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_paginated.call_args
+ assert kwargs["api_version"] == APIVersion.V1ALPHA
+
+
+def test_delete_integration_action_revision_custom_api_version(chronicle_client):
+ """Test delete_integration_action_revision with custom API version."""
+ with patch(
+ "secops.chronicle.integration.action_revisions.chronicle_request",
+ return_value=None,
+ ) as mock_request:
+ delete_integration_action_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ action_id="a1",
+ revision_id="r1",
+ api_version=APIVersion.V1ALPHA,
+ )
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["api_version"] == APIVersion.V1ALPHA
+
+
+def test_create_integration_action_revision_custom_api_version(chronicle_client):
+ """Test create_integration_action_revision with custom API version."""
+ expected = {"name": "revisions/r1"}
+ action = {"name": "actions/a1"}
+
+ with patch(
+ "secops.chronicle.integration.action_revisions.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = create_integration_action_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ action_id="a1",
+ action=action,
+ api_version=APIVersion.V1ALPHA,
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["api_version"] == APIVersion.V1ALPHA
+
+
+def test_rollback_integration_action_revision_custom_api_version(chronicle_client):
+ """Test rollback_integration_action_revision with custom API version."""
+ expected = {"name": "revisions/r1"}
+
+ with patch(
+ "secops.chronicle.integration.action_revisions.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = rollback_integration_action_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ action_id="a1",
+ revision_id="r1",
+ api_version=APIVersion.V1ALPHA,
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["api_version"] == APIVersion.V1ALPHA
+
diff --git a/tests/chronicle/integration/test_actions.py b/tests/chronicle/integration/test_actions.py
new file mode 100644
index 00000000..6cd0a9ac
--- /dev/null
+++ b/tests/chronicle/integration/test_actions.py
@@ -0,0 +1,666 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Tests for Chronicle marketplace integration actions functions."""
+
+from unittest.mock import Mock, patch
+
+import pytest
+
+from secops.chronicle.client import ChronicleClient
+from secops.chronicle.models import APIVersion
+from secops.chronicle.integration.actions import (
+ list_integration_actions,
+ get_integration_action,
+ delete_integration_action,
+ create_integration_action,
+ update_integration_action,
+ execute_integration_action_test,
+ get_integration_actions_by_environment,
+ get_integration_action_template,
+)
+from secops.exceptions import APIError
+
+
+@pytest.fixture
+def chronicle_client():
+ """Create a Chronicle client for testing."""
+ with patch("secops.auth.SecOpsAuth") as mock_auth:
+ mock_session = Mock()
+ mock_session.headers = {}
+ mock_auth.return_value.session = mock_session
+ return ChronicleClient(
+ customer_id="test-customer",
+ project_id="test-project",
+ default_api_version=APIVersion.V1BETA,
+ )
+
+
+# -- list_integration_actions tests --
+
+
+def test_list_integration_actions_success(chronicle_client):
+ """Test list_integration_actions delegates to chronicle_paginated_request."""
+ expected = {"actions": [{"name": "a1"}, {"name": "a2"}], "nextPageToken": "t"}
+
+ with patch(
+ "secops.chronicle.integration.actions.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated, patch(
+ # Avoid assuming how format_resource_id encodes/cases values
+ "secops.chronicle.integration.actions.format_resource_id",
+ return_value="My Integration",
+ ):
+ result = list_integration_actions(
+ chronicle_client,
+ integration_name="My Integration",
+ page_size=10,
+ page_token="next-token",
+ )
+
+ assert result == expected
+
+ mock_paginated.assert_called_once_with(
+ chronicle_client,
+ api_version=APIVersion.V1BETA,
+ path="integrations/My Integration/actions",
+ items_key="actions",
+ page_size=10,
+ page_token="next-token",
+ extra_params={},
+ as_list=False,
+ )
+
+
+def test_list_integration_actions_default_args(chronicle_client):
+ """Test list_integration_actions with default args."""
+ expected = {"actions": []}
+
+ with patch(
+ "secops.chronicle.integration.actions.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_integration_actions(
+ chronicle_client,
+ integration_name="test-integration",
+ )
+
+ assert result == expected
+
+ mock_paginated.assert_called_once_with(
+ chronicle_client,
+ api_version=APIVersion.V1BETA,
+ path="integrations/test-integration/actions",
+ items_key="actions",
+ page_size=None,
+ page_token=None,
+ extra_params={},
+ as_list=False,
+ )
+
+
+def test_list_integration_actions_with_filter_order_expand(chronicle_client):
+ """Test list_integration_actions passes filter/orderBy/expand in extra_params."""
+ expected = {"actions": [{"name": "a1"}]}
+
+ with patch(
+ "secops.chronicle.integration.actions.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_integration_actions(
+ chronicle_client,
+ integration_name="test-integration",
+ filter_string='displayName = "My Action"',
+ order_by="displayName",
+ expand="parameters,dynamicResults",
+ )
+
+ assert result == expected
+
+ mock_paginated.assert_called_once_with(
+ chronicle_client,
+ api_version=APIVersion.V1BETA,
+ path="integrations/test-integration/actions",
+ items_key="actions",
+ page_size=None,
+ page_token=None,
+ extra_params={
+ "filter": 'displayName = "My Action"',
+ "orderBy": "displayName",
+ "expand": "parameters,dynamicResults",
+ },
+ as_list=False,
+ )
+
+
+def test_list_integration_actions_as_list(chronicle_client):
+ """Test list_integration_actions with as_list=True."""
+ expected = [{"name": "a1"}, {"name": "a2"}]
+
+ with patch(
+ "secops.chronicle.integration.actions.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_integration_actions(
+ chronicle_client,
+ integration_name="test-integration",
+ as_list=True,
+ )
+
+ assert result == expected
+
+ mock_paginated.assert_called_once_with(
+ chronicle_client,
+ api_version=APIVersion.V1BETA,
+ path="integrations/test-integration/actions",
+ items_key="actions",
+ page_size=None,
+ page_token=None,
+ extra_params={},
+ as_list=True,
+ )
+
+
+def test_list_integration_actions_error(chronicle_client):
+ """Test list_integration_actions propagates APIError from helper."""
+ with patch(
+ "secops.chronicle.integration.actions.chronicle_paginated_request",
+ side_effect=APIError("Failed to list integration actions"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ list_integration_actions(
+ chronicle_client,
+ integration_name="test-integration",
+ )
+
+ assert "Failed to list integration actions" in str(exc_info.value)
+
+
+# -- get_integration_action tests --
+
+
+def test_get_integration_action_success(chronicle_client):
+ """Test get_integration_action returns expected result."""
+ expected = {"name": "actions/a1", "displayName": "Action 1"}
+
+ with patch(
+ "secops.chronicle.integration.actions.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = get_integration_action(
+ chronicle_client,
+ integration_name="test-integration",
+ action_id="a1",
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="GET",
+ endpoint_path="integrations/test-integration/actions/a1",
+ api_version=APIVersion.V1BETA,
+ )
+
+
+def test_get_integration_action_error(chronicle_client):
+ """Test get_integration_action raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.actions.chronicle_request",
+ side_effect=APIError("Failed to get integration action"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ get_integration_action(
+ chronicle_client,
+ integration_name="test-integration",
+ action_id="a1",
+ )
+ assert "Failed to get integration action" in str(exc_info.value)
+
+
+# -- delete_integration_action tests --
+
+
+def test_delete_integration_action_success(chronicle_client):
+ """Test delete_integration_action issues DELETE request."""
+ with patch(
+ "secops.chronicle.integration.actions.chronicle_request",
+ return_value=None,
+ ) as mock_request:
+ delete_integration_action(
+ chronicle_client,
+ integration_name="test-integration",
+ action_id="a1",
+ )
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="DELETE",
+ endpoint_path="integrations/test-integration/actions/a1",
+ api_version=APIVersion.V1BETA,
+ )
+
+
+def test_delete_integration_action_error(chronicle_client):
+ """Test delete_integration_action raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.actions.chronicle_request",
+ side_effect=APIError("Failed to delete integration action"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ delete_integration_action(
+ chronicle_client,
+ integration_name="test-integration",
+ action_id="a1",
+ )
+ assert "Failed to delete integration action" in str(exc_info.value)
+
+
+# -- create_integration_action tests --
+
+
+def test_create_integration_action_required_fields_only(chronicle_client):
+ """Test create_integration_action sends only required fields when optionals omitted."""
+ expected = {"name": "actions/new", "displayName": "My Action"}
+
+ with patch(
+ "secops.chronicle.integration.actions.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = create_integration_action(
+ chronicle_client,
+ integration_name="test-integration",
+ display_name="My Action",
+ script="print('hi')",
+ timeout_seconds=120,
+ enabled=True,
+ script_result_name="result",
+ is_async=False,
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="POST",
+ endpoint_path="integrations/test-integration/actions",
+ api_version=APIVersion.V1BETA,
+ json={
+ "displayName": "My Action",
+ "script": "print('hi')",
+ "timeoutSeconds": 120,
+ "enabled": True,
+ "scriptResultName": "result",
+ "async": False,
+ },
+ )
+
+
+def test_create_integration_action_all_fields(chronicle_client):
+ """Test create_integration_action with all optional fields."""
+ expected = {"name": "actions/new"}
+
+ with patch(
+ "secops.chronicle.integration.actions.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = create_integration_action(
+ chronicle_client,
+ integration_name="test-integration",
+ display_name="My Action",
+ script="print('hi')",
+ timeout_seconds=120,
+ enabled=True,
+ script_result_name="result",
+ is_async=True,
+ description="desc",
+ default_result_value="default",
+ async_polling_interval_seconds=5,
+ async_total_timeout_seconds=60,
+ dynamic_results=[{"name": "dr1"}],
+ parameters=[{"name": "p1"}],
+ ai_generated=False,
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="POST",
+ endpoint_path="integrations/test-integration/actions",
+ api_version=APIVersion.V1BETA,
+ json={
+ "displayName": "My Action",
+ "script": "print('hi')",
+ "timeoutSeconds": 120,
+ "enabled": True,
+ "scriptResultName": "result",
+ "async": True,
+ "description": "desc",
+ "defaultResultValue": "default",
+ "asyncPollingIntervalSeconds": 5,
+ "asyncTotalTimeoutSeconds": 60,
+ "dynamicResults": [{"name": "dr1"}],
+ "parameters": [{"name": "p1"}],
+ "aiGenerated": False,
+ },
+ )
+
+
+def test_create_integration_action_none_fields_excluded(chronicle_client):
+ """Test that None optional fields are not included in request body."""
+ with patch(
+ "secops.chronicle.integration.actions.chronicle_request",
+ return_value={"name": "actions/new"},
+ ) as mock_request:
+ create_integration_action(
+ chronicle_client,
+ integration_name="test-integration",
+ display_name="My Action",
+ script="print('hi')",
+ timeout_seconds=120,
+ enabled=True,
+ script_result_name="result",
+ is_async=False,
+ description=None,
+ default_result_value=None,
+ async_polling_interval_seconds=None,
+ async_total_timeout_seconds=None,
+ dynamic_results=None,
+ parameters=None,
+ ai_generated=None,
+ )
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="POST",
+ endpoint_path="integrations/test-integration/actions",
+ api_version=APIVersion.V1BETA,
+ json={
+ "displayName": "My Action",
+ "script": "print('hi')",
+ "timeoutSeconds": 120,
+ "enabled": True,
+ "scriptResultName": "result",
+ "async": False,
+ },
+ )
+
+
+def test_create_integration_action_error(chronicle_client):
+ """Test create_integration_action raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.actions.chronicle_request",
+ side_effect=APIError("Failed to create integration action"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ create_integration_action(
+ chronicle_client,
+ integration_name="test-integration",
+ display_name="My Action",
+ script="print('hi')",
+ timeout_seconds=120,
+ enabled=True,
+ script_result_name="result",
+ is_async=False,
+ )
+ assert "Failed to create integration action" in str(exc_info.value)
+
+
+# -- update_integration_action tests --
+
+
+def test_update_integration_action_with_explicit_update_mask(chronicle_client):
+ """Test update_integration_action passes through explicit update_mask."""
+ expected = {"name": "actions/a1", "displayName": "New Name"}
+
+ with patch(
+ "secops.chronicle.integration.actions.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = update_integration_action(
+ chronicle_client,
+ integration_name="test-integration",
+ action_id="a1",
+ display_name="New Name",
+ update_mask="displayName",
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="PATCH",
+ endpoint_path="integrations/test-integration/actions/a1",
+ api_version=APIVersion.V1BETA,
+ json={"displayName": "New Name"},
+ params={"updateMask": "displayName"},
+ )
+
+
+def test_update_integration_action_auto_update_mask(chronicle_client):
+ """Test update_integration_action auto-generates updateMask based on fields.
+
+ build_patch_body ordering isn't guaranteed; assert order-insensitively.
+ """
+ expected = {"name": "actions/a1"}
+
+ with patch(
+ "secops.chronicle.integration.actions.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = update_integration_action(
+ chronicle_client,
+ integration_name="test-integration",
+ action_id="a1",
+ enabled=False,
+ timeout_seconds=300,
+ )
+
+ assert result == expected
+
+ # Assert the call happened once and inspect args to avoid ordering issues.
+ assert mock_request.call_count == 1
+ _, kwargs = mock_request.call_args
+
+ assert kwargs["method"] == "PATCH"
+ assert kwargs["endpoint_path"] == "integrations/test-integration/actions/a1"
+ assert kwargs["api_version"] == APIVersion.V1BETA
+
+ assert kwargs["json"] == {"enabled": False, "timeoutSeconds": 300}
+
+ update_mask = kwargs["params"]["updateMask"]
+ assert set(update_mask.split(",")) == {"enabled", "timeoutSeconds"}
+
+
+def test_update_integration_action_error(chronicle_client):
+ """Test update_integration_action raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.actions.chronicle_request",
+ side_effect=APIError("Failed to update integration action"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ update_integration_action(
+ chronicle_client,
+ integration_name="test-integration",
+ action_id="a1",
+ display_name="New Name",
+ )
+ assert "Failed to update integration action" in str(exc_info.value)
+
+
+# -- test_integration_action tests --
+
+
+def test_execute_test_integration_action_success(chronicle_client):
+ """Test test_integration_action issues executeTest POST with correct body."""
+ expected = {"output": "ok", "debugOutput": ""}
+
+ with patch(
+ "secops.chronicle.integration.actions.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ action = {"displayName": "My Action", "script": "print('hi')"}
+ result = execute_integration_action_test(
+ chronicle_client,
+ integration_name="test-integration",
+ test_case_id=123,
+ action=action,
+ scope="INTEGRATION_INSTANCE",
+ integration_instance_id="inst-1",
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="POST",
+ endpoint_path="integrations/test-integration/actions:executeTest",
+ api_version=APIVersion.V1BETA,
+ json={
+ "testCaseId": 123,
+ "action": action,
+ "scope": "INTEGRATION_INSTANCE",
+ "integrationInstanceId": "inst-1",
+ },
+ )
+
+
+def test_execute_test_integration_action_error(chronicle_client):
+ """Test test_integration_action raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.actions.chronicle_request",
+ side_effect=APIError("Failed to test integration action"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ execute_integration_action_test(
+ chronicle_client,
+ integration_name="test-integration",
+ test_case_id=123,
+ action={"displayName": "My Action"},
+ scope="INTEGRATION_INSTANCE",
+ integration_instance_id="inst-1",
+ )
+ assert "Failed to test integration action" in str(exc_info.value)
+
+
+# -- get_integration_actions_by_environment tests --
+
+
+def test_get_integration_actions_by_environment_success(chronicle_client):
+ """Test get_integration_actions_by_environment issues GET with correct params."""
+ expected = {"actions": [{"name": "a1"}]}
+
+ with patch(
+ "secops.chronicle.integration.actions.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = get_integration_actions_by_environment(
+ chronicle_client,
+ integration_name="test-integration",
+ environments=["prod", "dev"],
+ include_widgets=True,
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="GET",
+ endpoint_path="integrations/test-integration/actions:fetchActionsByEnvironment",
+ api_version=APIVersion.V1BETA,
+ params={"environments": ["prod", "dev"], "includeWidgets": True},
+ )
+
+
+def test_get_integration_actions_by_environment_error(chronicle_client):
+ """Test get_integration_actions_by_environment raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.actions.chronicle_request",
+ side_effect=APIError("Failed to fetch actions by environment"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ get_integration_actions_by_environment(
+ chronicle_client,
+ integration_name="test-integration",
+ environments=["prod"],
+ include_widgets=False,
+ )
+ assert "Failed to fetch actions by environment" in str(exc_info.value)
+
+
+# -- get_integration_action_template tests --
+
+
+def test_get_integration_action_template_default_async_false(chronicle_client):
+ """Test get_integration_action_template uses async=False by default."""
+ expected = {"script": "# template"}
+
+ with patch(
+ "secops.chronicle.integration.actions.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = get_integration_action_template(
+ chronicle_client,
+ integration_name="test-integration",
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="GET",
+ endpoint_path="integrations/test-integration/actions:fetchTemplate",
+ api_version=APIVersion.V1BETA,
+ params={"async": False},
+ )
+
+
+def test_get_integration_action_template_async_true(chronicle_client):
+ """Test get_integration_action_template with is_async=True."""
+ expected = {"script": "# async template"}
+
+ with patch(
+ "secops.chronicle.integration.actions.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = get_integration_action_template(
+ chronicle_client,
+ integration_name="test-integration",
+ is_async=True,
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="GET",
+ endpoint_path="integrations/test-integration/actions:fetchTemplate",
+ api_version=APIVersion.V1BETA,
+ params={"async": True},
+ )
+
+
+def test_get_integration_action_template_error(chronicle_client):
+ """Test get_integration_action_template raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.actions.chronicle_request",
+ side_effect=APIError("Failed to fetch action template"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ get_integration_action_template(
+ chronicle_client,
+ integration_name="test-integration",
+ )
+ assert "Failed to fetch action template" in str(exc_info.value)
\ No newline at end of file
diff --git a/tests/chronicle/integration/test_connector_context_properties.py b/tests/chronicle/integration/test_connector_context_properties.py
new file mode 100644
index 00000000..33941087
--- /dev/null
+++ b/tests/chronicle/integration/test_connector_context_properties.py
@@ -0,0 +1,561 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Tests for Chronicle integration connector context properties functions."""
+
+from unittest.mock import Mock, patch
+
+import pytest
+
+from secops.chronicle.client import ChronicleClient
+from secops.chronicle.models import APIVersion
+from secops.chronicle.integration.connector_context_properties import (
+ list_connector_context_properties,
+ get_connector_context_property,
+ delete_connector_context_property,
+ create_connector_context_property,
+ update_connector_context_property,
+ delete_all_connector_context_properties,
+)
+from secops.exceptions import APIError
+
+
+@pytest.fixture
+def chronicle_client():
+ """Create a Chronicle client for testing."""
+ with patch("secops.auth.SecOpsAuth") as mock_auth:
+ mock_session = Mock()
+ mock_session.headers = {}
+ mock_auth.return_value.session = mock_session
+ return ChronicleClient(
+ customer_id="test-customer",
+ project_id="test-project",
+ default_api_version=APIVersion.V1BETA,
+ )
+
+
+# -- list_connector_context_properties tests --
+
+
+def test_list_connector_context_properties_success(chronicle_client):
+ """Test list_connector_context_properties delegates to paginated request."""
+ expected = {
+ "contextProperties": [{"key": "prop1"}, {"key": "prop2"}],
+ "nextPageToken": "t",
+ }
+
+ with patch(
+ "secops.chronicle.integration.connector_context_properties.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated, patch(
+ "secops.chronicle.integration.connector_context_properties.format_resource_id",
+ return_value="My Integration",
+ ):
+ result = list_connector_context_properties(
+ chronicle_client,
+ integration_name="My Integration",
+ connector_id="c1",
+ page_size=10,
+ page_token="next-token",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_paginated.call_args
+ assert "connectors/c1/contextProperties" in kwargs["path"]
+ assert kwargs["items_key"] == "contextProperties"
+ assert kwargs["page_size"] == 10
+ assert kwargs["page_token"] == "next-token"
+
+
+def test_list_connector_context_properties_default_args(chronicle_client):
+ """Test list_connector_context_properties with default args."""
+ expected = {"contextProperties": []}
+
+ with patch(
+ "secops.chronicle.integration.connector_context_properties.chronicle_paginated_request",
+ return_value=expected,
+ ):
+ result = list_connector_context_properties(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ )
+
+ assert result == expected
+
+
+def test_list_connector_context_properties_with_filters(chronicle_client):
+ """Test list_connector_context_properties with filter and order_by."""
+ expected = {"contextProperties": [{"key": "prop1"}]}
+
+ with patch(
+ "secops.chronicle.integration.connector_context_properties.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_connector_context_properties(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ filter_string='key = "prop1"',
+ order_by="key",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_paginated.call_args
+ assert kwargs["extra_params"] == {
+ "filter": 'key = "prop1"',
+ "orderBy": "key",
+ }
+
+
+def test_list_connector_context_properties_as_list(chronicle_client):
+ """Test list_connector_context_properties returns list when as_list=True."""
+ expected = [{"key": "prop1"}, {"key": "prop2"}]
+
+ with patch(
+ "secops.chronicle.integration.connector_context_properties.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_connector_context_properties(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ as_list=True,
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_paginated.call_args
+ assert kwargs["as_list"] is True
+
+
+def test_list_connector_context_properties_error(chronicle_client):
+ """Test list_connector_context_properties raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.connector_context_properties.chronicle_paginated_request",
+ side_effect=APIError("Failed to list context properties"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ list_connector_context_properties(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ )
+ assert "Failed to list context properties" in str(exc_info.value)
+
+
+# -- get_connector_context_property tests --
+
+
+def test_get_connector_context_property_success(chronicle_client):
+ """Test get_connector_context_property issues GET request."""
+ expected = {
+ "name": "contextProperties/prop1",
+ "key": "prop1",
+ "value": "test-value",
+ }
+
+ with patch(
+ "secops.chronicle.integration.connector_context_properties.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = get_connector_context_property(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ context_property_id="prop1",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["method"] == "GET"
+ assert "connectors/c1/contextProperties/prop1" in kwargs["endpoint_path"]
+ assert kwargs["api_version"] == APIVersion.V1BETA
+
+
+def test_get_connector_context_property_error(chronicle_client):
+ """Test get_connector_context_property raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.connector_context_properties.chronicle_request",
+ side_effect=APIError("Failed to get context property"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ get_connector_context_property(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ context_property_id="prop1",
+ )
+ assert "Failed to get context property" in str(exc_info.value)
+
+
+# -- delete_connector_context_property tests --
+
+
+def test_delete_connector_context_property_success(chronicle_client):
+ """Test delete_connector_context_property issues DELETE request."""
+ with patch(
+ "secops.chronicle.integration.connector_context_properties.chronicle_request",
+ return_value=None,
+ ) as mock_request:
+ delete_connector_context_property(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ context_property_id="prop1",
+ )
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["method"] == "DELETE"
+ assert "connectors/c1/contextProperties/prop1" in kwargs["endpoint_path"]
+ assert kwargs["api_version"] == APIVersion.V1BETA
+
+
+def test_delete_connector_context_property_error(chronicle_client):
+ """Test delete_connector_context_property raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.connector_context_properties.chronicle_request",
+ side_effect=APIError("Failed to delete context property"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ delete_connector_context_property(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ context_property_id="prop1",
+ )
+ assert "Failed to delete context property" in str(exc_info.value)
+
+
+# -- create_connector_context_property tests --
+
+
+def test_create_connector_context_property_required_fields_only(chronicle_client):
+ """Test create_connector_context_property with required fields only."""
+ expected = {"name": "contextProperties/new", "value": "test-value"}
+
+ with patch(
+ "secops.chronicle.integration.connector_context_properties.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = create_connector_context_property(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ value="test-value",
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="POST",
+ endpoint_path="integrations/test-integration/connectors/c1/contextProperties",
+ api_version=APIVersion.V1BETA,
+ json={"value": "test-value"},
+ )
+
+
+def test_create_connector_context_property_with_key(chronicle_client):
+ """Test create_connector_context_property includes key when provided."""
+ expected = {"name": "contextProperties/custom-key", "value": "test-value"}
+
+ with patch(
+ "secops.chronicle.integration.connector_context_properties.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = create_connector_context_property(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ value="test-value",
+ key="custom-key",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["json"]["value"] == "test-value"
+ assert kwargs["json"]["key"] == "custom-key"
+
+
+def test_create_connector_context_property_error(chronicle_client):
+ """Test create_connector_context_property raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.connector_context_properties.chronicle_request",
+ side_effect=APIError("Failed to create context property"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ create_connector_context_property(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ value="test-value",
+ )
+ assert "Failed to create context property" in str(exc_info.value)
+
+
+# -- update_connector_context_property tests --
+
+
+def test_update_connector_context_property_success(chronicle_client):
+ """Test update_connector_context_property updates value."""
+ expected = {
+ "name": "contextProperties/prop1",
+ "value": "updated-value",
+ }
+
+ with patch(
+ "secops.chronicle.integration.connector_context_properties.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = update_connector_context_property(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ context_property_id="prop1",
+ value="updated-value",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["method"] == "PATCH"
+ assert "connectors/c1/contextProperties/prop1" in kwargs["endpoint_path"]
+ assert kwargs["json"]["value"] == "updated-value"
+ assert kwargs["params"]["updateMask"] == "value"
+
+
+def test_update_connector_context_property_with_custom_mask(chronicle_client):
+ """Test update_connector_context_property with custom update_mask."""
+ expected = {"name": "contextProperties/prop1"}
+
+ with patch(
+ "secops.chronicle.integration.connector_context_properties.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = update_connector_context_property(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ context_property_id="prop1",
+ value="updated-value",
+ update_mask="value",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["params"]["updateMask"] == "value"
+
+
+def test_update_connector_context_property_error(chronicle_client):
+ """Test update_connector_context_property raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.connector_context_properties.chronicle_request",
+ side_effect=APIError("Failed to update context property"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ update_connector_context_property(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ context_property_id="prop1",
+ value="updated-value",
+ )
+ assert "Failed to update context property" in str(exc_info.value)
+
+
+# -- delete_all_connector_context_properties tests --
+
+
+def test_delete_all_connector_context_properties_success(chronicle_client):
+ """Test delete_all_connector_context_properties issues POST request."""
+ with patch(
+ "secops.chronicle.integration.connector_context_properties.chronicle_request",
+ return_value=None,
+ ) as mock_request:
+ delete_all_connector_context_properties(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ )
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["method"] == "POST"
+ assert "connectors/c1/contextProperties:clearAll" in kwargs["endpoint_path"]
+ assert kwargs["api_version"] == APIVersion.V1BETA
+ assert kwargs["json"] == {}
+
+
+def test_delete_all_connector_context_properties_with_context_id(chronicle_client):
+ """Test delete_all_connector_context_properties with context_id."""
+ with patch(
+ "secops.chronicle.integration.connector_context_properties.chronicle_request",
+ return_value=None,
+ ) as mock_request:
+ delete_all_connector_context_properties(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ context_id="my-context",
+ )
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["json"]["contextId"] == "my-context"
+
+
+def test_delete_all_connector_context_properties_error(chronicle_client):
+ """Test delete_all_connector_context_properties raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.connector_context_properties.chronicle_request",
+ side_effect=APIError("Failed to clear context properties"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ delete_all_connector_context_properties(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ )
+ assert "Failed to clear context properties" in str(exc_info.value)
+
+
+# -- API version tests --
+
+
+def test_list_connector_context_properties_custom_api_version(chronicle_client):
+ """Test list_connector_context_properties with custom API version."""
+ expected = {"contextProperties": []}
+
+ with patch(
+ "secops.chronicle.integration.connector_context_properties.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_connector_context_properties(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ api_version=APIVersion.V1ALPHA,
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_paginated.call_args
+ assert kwargs["api_version"] == APIVersion.V1ALPHA
+
+
+def test_get_connector_context_property_custom_api_version(chronicle_client):
+ """Test get_connector_context_property with custom API version."""
+ expected = {"name": "contextProperties/prop1"}
+
+ with patch(
+ "secops.chronicle.integration.connector_context_properties.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = get_connector_context_property(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ context_property_id="prop1",
+ api_version=APIVersion.V1ALPHA,
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["api_version"] == APIVersion.V1ALPHA
+
+
+def test_delete_connector_context_property_custom_api_version(chronicle_client):
+ """Test delete_connector_context_property with custom API version."""
+ with patch(
+ "secops.chronicle.integration.connector_context_properties.chronicle_request",
+ return_value=None,
+ ) as mock_request:
+ delete_connector_context_property(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ context_property_id="prop1",
+ api_version=APIVersion.V1ALPHA,
+ )
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["api_version"] == APIVersion.V1ALPHA
+
+
+def test_create_connector_context_property_custom_api_version(chronicle_client):
+ """Test create_connector_context_property with custom API version."""
+ expected = {"name": "contextProperties/new"}
+
+ with patch(
+ "secops.chronicle.integration.connector_context_properties.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = create_connector_context_property(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ value="test-value",
+ api_version=APIVersion.V1ALPHA,
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["api_version"] == APIVersion.V1ALPHA
+
+
+def test_update_connector_context_property_custom_api_version(chronicle_client):
+ """Test update_connector_context_property with custom API version."""
+ expected = {"name": "contextProperties/prop1"}
+
+ with patch(
+ "secops.chronicle.integration.connector_context_properties.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = update_connector_context_property(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ context_property_id="prop1",
+ value="updated-value",
+ api_version=APIVersion.V1ALPHA,
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["api_version"] == APIVersion.V1ALPHA
+
+
+def test_delete_all_connector_context_properties_custom_api_version(chronicle_client):
+ """Test delete_all_connector_context_properties with custom API version."""
+ with patch(
+ "secops.chronicle.integration.connector_context_properties.chronicle_request",
+ return_value=None,
+ ) as mock_request:
+ delete_all_connector_context_properties(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ api_version=APIVersion.V1ALPHA,
+ )
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["api_version"] == APIVersion.V1ALPHA
+
diff --git a/tests/chronicle/integration/test_connector_instance_logs.py b/tests/chronicle/integration/test_connector_instance_logs.py
new file mode 100644
index 00000000..873264fc
--- /dev/null
+++ b/tests/chronicle/integration/test_connector_instance_logs.py
@@ -0,0 +1,256 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Tests for Chronicle integration connector instance logs functions."""
+
+from unittest.mock import Mock, patch
+
+import pytest
+
+from secops.chronicle.client import ChronicleClient
+from secops.chronicle.models import APIVersion
+from secops.chronicle.integration.connector_instance_logs import (
+ list_connector_instance_logs,
+ get_connector_instance_log,
+)
+from secops.exceptions import APIError
+
+
+@pytest.fixture
+def chronicle_client():
+ """Create a Chronicle client for testing."""
+ with patch("secops.auth.SecOpsAuth") as mock_auth:
+ mock_session = Mock()
+ mock_session.headers = {}
+ mock_auth.return_value.session = mock_session
+ return ChronicleClient(
+ customer_id="test-customer",
+ project_id="test-project",
+ default_api_version=APIVersion.V1BETA,
+ )
+
+
+# -- list_connector_instance_logs tests --
+
+
+def test_list_connector_instance_logs_success(chronicle_client):
+ """Test list_connector_instance_logs delegates to paginated request."""
+ expected = {
+ "logs": [{"name": "log1"}, {"name": "log2"}],
+ "nextPageToken": "t",
+ }
+
+ with patch(
+ "secops.chronicle.integration.connector_instance_logs.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated, patch(
+ "secops.chronicle.integration.connector_instance_logs.format_resource_id",
+ return_value="My Integration",
+ ):
+ result = list_connector_instance_logs(
+ chronicle_client,
+ integration_name="My Integration",
+ connector_id="c1",
+ connector_instance_id="ci1",
+ page_size=10,
+ page_token="next-token",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_paginated.call_args
+ assert "connectors/c1/connectorInstances/ci1/logs" in kwargs["path"]
+ assert kwargs["items_key"] == "logs"
+ assert kwargs["page_size"] == 10
+ assert kwargs["page_token"] == "next-token"
+
+
+def test_list_connector_instance_logs_default_args(chronicle_client):
+ """Test list_connector_instance_logs with default args."""
+ expected = {"logs": []}
+
+ with patch(
+ "secops.chronicle.integration.connector_instance_logs.chronicle_paginated_request",
+ return_value=expected,
+ ):
+ result = list_connector_instance_logs(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ connector_instance_id="ci1",
+ )
+
+ assert result == expected
+
+
+def test_list_connector_instance_logs_with_filters(chronicle_client):
+ """Test list_connector_instance_logs with filter and order_by."""
+ expected = {"logs": [{"name": "log1"}]}
+
+ with patch(
+ "secops.chronicle.integration.connector_instance_logs.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_connector_instance_logs(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ connector_instance_id="ci1",
+ filter_string='severity = "ERROR"',
+ order_by="timestamp desc",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_paginated.call_args
+ assert kwargs["extra_params"] == {
+ "filter": 'severity = "ERROR"',
+ "orderBy": "timestamp desc",
+ }
+
+
+def test_list_connector_instance_logs_as_list(chronicle_client):
+ """Test list_connector_instance_logs returns list when as_list=True."""
+ expected = [{"name": "log1"}, {"name": "log2"}]
+
+ with patch(
+ "secops.chronicle.integration.connector_instance_logs.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_connector_instance_logs(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ connector_instance_id="ci1",
+ as_list=True,
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_paginated.call_args
+ assert kwargs["as_list"] is True
+
+
+def test_list_connector_instance_logs_error(chronicle_client):
+ """Test list_connector_instance_logs raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.connector_instance_logs.chronicle_paginated_request",
+ side_effect=APIError("Failed to list connector instance logs"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ list_connector_instance_logs(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ connector_instance_id="ci1",
+ )
+ assert "Failed to list connector instance logs" in str(exc_info.value)
+
+
+# -- get_connector_instance_log tests --
+
+
+def test_get_connector_instance_log_success(chronicle_client):
+ """Test get_connector_instance_log issues GET request."""
+ expected = {
+ "name": "logs/log1",
+ "message": "Test log message",
+ "severity": "INFO",
+ "timestamp": "2026-03-09T10:00:00Z",
+ }
+
+ with patch(
+ "secops.chronicle.integration.connector_instance_logs.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = get_connector_instance_log(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ connector_instance_id="ci1",
+ log_id="log1",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["method"] == "GET"
+ assert "connectors/c1/connectorInstances/ci1/logs/log1" in kwargs["endpoint_path"]
+ assert kwargs["api_version"] == APIVersion.V1BETA
+
+
+def test_get_connector_instance_log_error(chronicle_client):
+ """Test get_connector_instance_log raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.connector_instance_logs.chronicle_request",
+ side_effect=APIError("Failed to get connector instance log"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ get_connector_instance_log(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ connector_instance_id="ci1",
+ log_id="log1",
+ )
+ assert "Failed to get connector instance log" in str(exc_info.value)
+
+
+# -- API version tests --
+
+
+def test_list_connector_instance_logs_custom_api_version(chronicle_client):
+ """Test list_connector_instance_logs with custom API version."""
+ expected = {"logs": []}
+
+ with patch(
+ "secops.chronicle.integration.connector_instance_logs.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_connector_instance_logs(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ connector_instance_id="ci1",
+ api_version=APIVersion.V1ALPHA,
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_paginated.call_args
+ assert kwargs["api_version"] == APIVersion.V1ALPHA
+
+
+def test_get_connector_instance_log_custom_api_version(chronicle_client):
+ """Test get_connector_instance_log with custom API version."""
+ expected = {"name": "logs/log1"}
+
+ with patch(
+ "secops.chronicle.integration.connector_instance_logs.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = get_connector_instance_log(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ connector_instance_id="ci1",
+ log_id="log1",
+ api_version=APIVersion.V1ALPHA,
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["api_version"] == APIVersion.V1ALPHA
+
diff --git a/tests/chronicle/integration/test_connector_instances.py b/tests/chronicle/integration/test_connector_instances.py
new file mode 100644
index 00000000..25bf3abe
--- /dev/null
+++ b/tests/chronicle/integration/test_connector_instances.py
@@ -0,0 +1,845 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Tests for Chronicle integration connector instances functions."""
+
+from unittest.mock import Mock, patch
+
+import pytest
+
+from secops.chronicle.client import ChronicleClient
+from secops.chronicle.models import (
+ APIVersion,
+ ConnectorInstanceParameter,
+)
+from secops.chronicle.integration.connector_instances import (
+ list_connector_instances,
+ get_connector_instance,
+ delete_connector_instance,
+ create_connector_instance,
+ update_connector_instance,
+ get_connector_instance_latest_definition,
+ set_connector_instance_logs_collection,
+ run_connector_instance_on_demand,
+)
+from secops.exceptions import APIError
+
+
+@pytest.fixture
+def chronicle_client():
+ """Create a Chronicle client for testing."""
+ with patch("secops.auth.SecOpsAuth") as mock_auth:
+ mock_session = Mock()
+ mock_session.headers = {}
+ mock_auth.return_value.session = mock_session
+ return ChronicleClient(
+ customer_id="test-customer",
+ project_id="test-project",
+ default_api_version=APIVersion.V1BETA,
+ )
+
+
+# -- list_connector_instances tests --
+
+
+def test_list_connector_instances_success(chronicle_client):
+ """Test list_connector_instances delegates to chronicle_paginated_request."""
+ expected = {
+ "connectorInstances": [{"name": "ci1"}, {"name": "ci2"}],
+ "nextPageToken": "t",
+ }
+
+ with patch(
+ "secops.chronicle.integration.connector_instances.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated, patch(
+ "secops.chronicle.integration.connector_instances.format_resource_id",
+ return_value="My Integration",
+ ):
+ result = list_connector_instances(
+ chronicle_client,
+ integration_name="My Integration",
+ connector_id="c1",
+ page_size=10,
+ page_token="next-token",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_paginated.call_args
+ assert "connectors/c1/connectorInstances" in kwargs["path"]
+ assert kwargs["items_key"] == "connectorInstances"
+ assert kwargs["page_size"] == 10
+ assert kwargs["page_token"] == "next-token"
+
+
+def test_list_connector_instances_default_args(chronicle_client):
+ """Test list_connector_instances with default args."""
+ expected = {"connectorInstances": []}
+
+ with patch(
+ "secops.chronicle.integration.connector_instances.chronicle_paginated_request",
+ return_value=expected,
+ ):
+ result = list_connector_instances(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ )
+
+ assert result == expected
+
+
+def test_list_connector_instances_with_filters(chronicle_client):
+ """Test list_connector_instances with filter and order_by."""
+ expected = {"connectorInstances": [{"name": "ci1"}]}
+
+ with patch(
+ "secops.chronicle.integration.connector_instances.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_connector_instances(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ filter_string='enabled = true',
+ order_by="displayName",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_paginated.call_args
+ assert kwargs["extra_params"] == {
+ "filter": 'enabled = true',
+ "orderBy": "displayName",
+ }
+
+
+def test_list_connector_instances_as_list(chronicle_client):
+ """Test list_connector_instances returns list when as_list=True."""
+ expected = [{"name": "ci1"}, {"name": "ci2"}]
+
+ with patch(
+ "secops.chronicle.integration.connector_instances.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_connector_instances(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ as_list=True,
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_paginated.call_args
+ assert kwargs["as_list"] is True
+
+
+def test_list_connector_instances_error(chronicle_client):
+ """Test list_connector_instances raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.connector_instances.chronicle_paginated_request",
+ side_effect=APIError("Failed to list connector instances"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ list_connector_instances(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ )
+ assert "Failed to list connector instances" in str(exc_info.value)
+
+
+# -- get_connector_instance tests --
+
+
+def test_get_connector_instance_success(chronicle_client):
+ """Test get_connector_instance issues GET request."""
+ expected = {
+ "name": "connectorInstances/ci1",
+ "displayName": "Test Instance",
+ "enabled": True,
+ }
+
+ with patch(
+ "secops.chronicle.integration.connector_instances.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = get_connector_instance(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ connector_instance_id="ci1",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["method"] == "GET"
+ assert "connectors/c1/connectorInstances/ci1" in kwargs["endpoint_path"]
+ assert kwargs["api_version"] == APIVersion.V1BETA
+
+
+def test_get_connector_instance_error(chronicle_client):
+ """Test get_connector_instance raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.connector_instances.chronicle_request",
+ side_effect=APIError("Failed to get connector instance"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ get_connector_instance(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ connector_instance_id="ci1",
+ )
+ assert "Failed to get connector instance" in str(exc_info.value)
+
+
+# -- delete_connector_instance tests --
+
+
+def test_delete_connector_instance_success(chronicle_client):
+ """Test delete_connector_instance issues DELETE request."""
+ with patch(
+ "secops.chronicle.integration.connector_instances.chronicle_request",
+ return_value=None,
+ ) as mock_request:
+ delete_connector_instance(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ connector_instance_id="ci1",
+ )
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["method"] == "DELETE"
+ assert "connectors/c1/connectorInstances/ci1" in kwargs["endpoint_path"]
+ assert kwargs["api_version"] == APIVersion.V1BETA
+
+
+def test_delete_connector_instance_error(chronicle_client):
+ """Test delete_connector_instance raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.connector_instances.chronicle_request",
+ side_effect=APIError("Failed to delete connector instance"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ delete_connector_instance(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ connector_instance_id="ci1",
+ )
+ assert "Failed to delete connector instance" in str(exc_info.value)
+
+
+# -- create_connector_instance tests --
+
+
+def test_create_connector_instance_required_fields_only(chronicle_client):
+ """Test create_connector_instance with required fields only."""
+ expected = {"name": "connectorInstances/new", "displayName": "New Instance"}
+
+ with patch(
+ "secops.chronicle.integration.connector_instances.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = create_connector_instance(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ environment="production",
+ display_name="New Instance",
+ interval_seconds=3600,
+ timeout_seconds=300,
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once()
+ _, kwargs = mock_request.call_args
+ assert kwargs["method"] == "POST"
+ assert "connectors/c1/connectorInstances" in kwargs["endpoint_path"]
+ assert kwargs["json"]["environment"] == "production"
+ assert kwargs["json"]["displayName"] == "New Instance"
+ assert kwargs["json"]["intervalSeconds"] == 3600
+ assert kwargs["json"]["timeoutSeconds"] == 300
+
+
+def test_create_connector_instance_with_optional_fields(chronicle_client):
+ """Test create_connector_instance includes optional fields when provided."""
+ expected = {"name": "connectorInstances/new"}
+
+ with patch(
+ "secops.chronicle.integration.connector_instances.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = create_connector_instance(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ environment="production",
+ display_name="New Instance",
+ interval_seconds=3600,
+ timeout_seconds=300,
+ description="Test description",
+ agent="agent-123",
+ allow_list=["192.168.1.0/24"],
+ product_field_name="product",
+ event_field_name="event",
+ integration_version="1.0.0",
+ version="2.0.0",
+ logging_enabled_until_unix_ms="1234567890000",
+ connector_instance_id="custom-id",
+ enabled=True,
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["json"]["description"] == "Test description"
+ assert kwargs["json"]["agent"] == "agent-123"
+ assert kwargs["json"]["allowList"] == ["192.168.1.0/24"]
+ assert kwargs["json"]["productFieldName"] == "product"
+ assert kwargs["json"]["eventFieldName"] == "event"
+ assert kwargs["json"]["integrationVersion"] == "1.0.0"
+ assert kwargs["json"]["version"] == "2.0.0"
+ assert kwargs["json"]["loggingEnabledUntilUnixMs"] == "1234567890000"
+ assert kwargs["json"]["id"] == "custom-id"
+ assert kwargs["json"]["enabled"] is True
+
+
+def test_create_connector_instance_with_parameters(chronicle_client):
+ """Test create_connector_instance with ConnectorInstanceParameter objects."""
+ expected = {"name": "connectorInstances/new"}
+
+ param = ConnectorInstanceParameter()
+ param.value = "secret-key"
+
+ with patch(
+ "secops.chronicle.integration.connector_instances.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = create_connector_instance(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ environment="production",
+ display_name="New Instance",
+ interval_seconds=3600,
+ timeout_seconds=300,
+ parameters=[param],
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert len(kwargs["json"]["parameters"]) == 1
+ assert kwargs["json"]["parameters"][0]["value"] == "secret-key"
+
+
+def test_create_connector_instance_with_dict_parameters(chronicle_client):
+ """Test create_connector_instance with dict parameters."""
+ expected = {"name": "connectorInstances/new"}
+
+ with patch(
+ "secops.chronicle.integration.connector_instances.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = create_connector_instance(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ environment="production",
+ display_name="New Instance",
+ interval_seconds=3600,
+ timeout_seconds=300,
+ parameters=[{"displayName": "API Key", "value": "secret-key"}],
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["json"]["parameters"][0]["displayName"] == "API Key"
+
+
+def test_create_connector_instance_error(chronicle_client):
+ """Test create_connector_instance raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.connector_instances.chronicle_request",
+ side_effect=APIError("Failed to create connector instance"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ create_connector_instance(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ environment="production",
+ display_name="New Instance",
+ interval_seconds=3600,
+ timeout_seconds=300,
+ )
+ assert "Failed to create connector instance" in str(exc_info.value)
+
+
+# -- update_connector_instance tests --
+
+
+def test_update_connector_instance_success(chronicle_client):
+ """Test update_connector_instance updates fields."""
+ expected = {"name": "connectorInstances/ci1", "displayName": "Updated"}
+
+ with patch(
+ "secops.chronicle.integration.connector_instances.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = update_connector_instance(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ connector_instance_id="ci1",
+ display_name="Updated",
+ enabled=True,
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["method"] == "PATCH"
+ assert "connectors/c1/connectorInstances/ci1" in kwargs["endpoint_path"]
+ assert kwargs["json"]["displayName"] == "Updated"
+ assert kwargs["json"]["enabled"] is True
+ # Check that update mask contains the expected fields
+ assert "displayName" in kwargs["params"]["updateMask"]
+ assert "enabled" in kwargs["params"]["updateMask"]
+
+
+def test_update_connector_instance_with_custom_mask(chronicle_client):
+ """Test update_connector_instance with custom update_mask."""
+ expected = {"name": "connectorInstances/ci1"}
+
+ with patch(
+ "secops.chronicle.integration.connector_instances.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = update_connector_instance(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ connector_instance_id="ci1",
+ display_name="Updated",
+ update_mask="displayName",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["params"]["updateMask"] == "displayName"
+
+
+def test_update_connector_instance_with_parameters(chronicle_client):
+ """Test update_connector_instance with parameters."""
+ expected = {"name": "connectorInstances/ci1"}
+
+ param = ConnectorInstanceParameter()
+ param.value = "new-key"
+
+ with patch(
+ "secops.chronicle.integration.connector_instances.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = update_connector_instance(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ connector_instance_id="ci1",
+ parameters=[param],
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert len(kwargs["json"]["parameters"]) == 1
+ assert kwargs["json"]["parameters"][0]["value"] == "new-key"
+
+
+def test_update_connector_instance_error(chronicle_client):
+ """Test update_connector_instance raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.connector_instances.chronicle_request",
+ side_effect=APIError("Failed to update connector instance"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ update_connector_instance(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ connector_instance_id="ci1",
+ display_name="Updated",
+ )
+ assert "Failed to update connector instance" in str(exc_info.value)
+
+
+# -- get_connector_instance_latest_definition tests --
+
+
+def test_get_connector_instance_latest_definition_success(chronicle_client):
+ """Test get_connector_instance_latest_definition issues GET request."""
+ expected = {
+ "name": "connectorInstances/ci1",
+ "displayName": "Refreshed Instance",
+ }
+
+ with patch(
+ "secops.chronicle.integration.connector_instances.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = get_connector_instance_latest_definition(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ connector_instance_id="ci1",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["method"] == "GET"
+ assert "connectorInstances/ci1:fetchLatestDefinition" in kwargs["endpoint_path"]
+ assert kwargs["api_version"] == APIVersion.V1BETA
+
+
+def test_get_connector_instance_latest_definition_error(chronicle_client):
+ """Test get_connector_instance_latest_definition raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.connector_instances.chronicle_request",
+ side_effect=APIError("Failed to fetch latest definition"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ get_connector_instance_latest_definition(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ connector_instance_id="ci1",
+ )
+ assert "Failed to fetch latest definition" in str(exc_info.value)
+
+
+# -- set_connector_instance_logs_collection tests --
+
+
+def test_set_connector_instance_logs_collection_enable(chronicle_client):
+ """Test set_connector_instance_logs_collection enables logs."""
+ expected = {"loggingEnabledUntilUnixMs": "1234567890000"}
+
+ with patch(
+ "secops.chronicle.integration.connector_instances.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = set_connector_instance_logs_collection(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ connector_instance_id="ci1",
+ enabled=True,
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["method"] == "POST"
+ assert "connectorInstances/ci1:setLogsCollection" in kwargs["endpoint_path"]
+ assert kwargs["json"]["enabled"] is True
+
+
+def test_set_connector_instance_logs_collection_disable(chronicle_client):
+ """Test set_connector_instance_logs_collection disables logs."""
+ expected = {}
+
+ with patch(
+ "secops.chronicle.integration.connector_instances.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = set_connector_instance_logs_collection(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ connector_instance_id="ci1",
+ enabled=False,
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["json"]["enabled"] is False
+
+
+def test_set_connector_instance_logs_collection_error(chronicle_client):
+ """Test set_connector_instance_logs_collection raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.connector_instances.chronicle_request",
+ side_effect=APIError("Failed to set logs collection"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ set_connector_instance_logs_collection(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ connector_instance_id="ci1",
+ enabled=True,
+ )
+ assert "Failed to set logs collection" in str(exc_info.value)
+
+
+# -- run_connector_instance_on_demand tests --
+
+
+def test_run_connector_instance_on_demand_success(chronicle_client):
+ """Test run_connector_instance_on_demand triggers execution."""
+ expected = {
+ "debugOutput": "Execution completed",
+ "success": True,
+ "sampleCases": [],
+ }
+
+ connector_instance = {
+ "name": "connectorInstances/ci1",
+ "displayName": "Test Instance",
+ "parameters": [{"displayName": "param1", "value": "value1"}],
+ }
+
+ with patch(
+ "secops.chronicle.integration.connector_instances.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = run_connector_instance_on_demand(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ connector_instance_id="ci1",
+ connector_instance=connector_instance,
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["method"] == "POST"
+ assert "connectorInstances/ci1:runOnDemand" in kwargs["endpoint_path"]
+ assert kwargs["json"]["connectorInstance"] == connector_instance
+
+
+def test_run_connector_instance_on_demand_error(chronicle_client):
+ """Test run_connector_instance_on_demand raises APIError on failure."""
+ connector_instance = {"name": "connectorInstances/ci1"}
+
+ with patch(
+ "secops.chronicle.integration.connector_instances.chronicle_request",
+ side_effect=APIError("Failed to run connector instance"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ run_connector_instance_on_demand(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ connector_instance_id="ci1",
+ connector_instance=connector_instance,
+ )
+ assert "Failed to run connector instance" in str(exc_info.value)
+
+
+# -- API version tests --
+
+
+def test_list_connector_instances_custom_api_version(chronicle_client):
+ """Test list_connector_instances with custom API version."""
+ expected = {"connectorInstances": []}
+
+ with patch(
+ "secops.chronicle.integration.connector_instances.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_connector_instances(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ api_version=APIVersion.V1ALPHA,
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_paginated.call_args
+ assert kwargs["api_version"] == APIVersion.V1ALPHA
+
+
+def test_get_connector_instance_custom_api_version(chronicle_client):
+ """Test get_connector_instance with custom API version."""
+ expected = {"name": "connectorInstances/ci1"}
+
+ with patch(
+ "secops.chronicle.integration.connector_instances.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = get_connector_instance(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ connector_instance_id="ci1",
+ api_version=APIVersion.V1ALPHA,
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["api_version"] == APIVersion.V1ALPHA
+
+
+def test_delete_connector_instance_custom_api_version(chronicle_client):
+ """Test delete_connector_instance with custom API version."""
+ with patch(
+ "secops.chronicle.integration.connector_instances.chronicle_request",
+ return_value=None,
+ ) as mock_request:
+ delete_connector_instance(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ connector_instance_id="ci1",
+ api_version=APIVersion.V1ALPHA,
+ )
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["api_version"] == APIVersion.V1ALPHA
+
+
+def test_create_connector_instance_custom_api_version(chronicle_client):
+ """Test create_connector_instance with custom API version."""
+ expected = {"name": "connectorInstances/new"}
+
+ with patch(
+ "secops.chronicle.integration.connector_instances.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = create_connector_instance(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ environment="production",
+ display_name="New Instance",
+ interval_seconds=3600,
+ timeout_seconds=300,
+ api_version=APIVersion.V1ALPHA,
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["api_version"] == APIVersion.V1ALPHA
+
+
+def test_update_connector_instance_custom_api_version(chronicle_client):
+ """Test update_connector_instance with custom API version."""
+ expected = {"name": "connectorInstances/ci1"}
+
+ with patch(
+ "secops.chronicle.integration.connector_instances.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = update_connector_instance(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ connector_instance_id="ci1",
+ display_name="Updated",
+ api_version=APIVersion.V1ALPHA,
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["api_version"] == APIVersion.V1ALPHA
+
+
+def test_get_connector_instance_latest_definition_custom_api_version(chronicle_client):
+ """Test get_connector_instance_latest_definition with custom API version."""
+ expected = {"name": "connectorInstances/ci1"}
+
+ with patch(
+ "secops.chronicle.integration.connector_instances.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = get_connector_instance_latest_definition(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ connector_instance_id="ci1",
+ api_version=APIVersion.V1ALPHA,
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["api_version"] == APIVersion.V1ALPHA
+
+
+def test_set_connector_instance_logs_collection_custom_api_version(chronicle_client):
+ """Test set_connector_instance_logs_collection with custom API version."""
+ expected = {}
+
+ with patch(
+ "secops.chronicle.integration.connector_instances.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = set_connector_instance_logs_collection(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ connector_instance_id="ci1",
+ enabled=True,
+ api_version=APIVersion.V1ALPHA,
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["api_version"] == APIVersion.V1ALPHA
+
+
+def test_run_connector_instance_on_demand_custom_api_version(chronicle_client):
+ """Test run_connector_instance_on_demand with custom API version."""
+ expected = {"success": True}
+ connector_instance = {"name": "connectorInstances/ci1"}
+
+ with patch(
+ "secops.chronicle.integration.connector_instances.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = run_connector_instance_on_demand(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ connector_instance_id="ci1",
+ connector_instance=connector_instance,
+ api_version=APIVersion.V1ALPHA,
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["api_version"] == APIVersion.V1ALPHA
+
+
+
+
diff --git a/tests/chronicle/integration/test_connector_revisions.py b/tests/chronicle/integration/test_connector_revisions.py
new file mode 100644
index 00000000..7b214bcb
--- /dev/null
+++ b/tests/chronicle/integration/test_connector_revisions.py
@@ -0,0 +1,385 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Tests for Chronicle marketplace integration connector revisions functions."""
+
+from unittest.mock import Mock, patch
+
+import pytest
+
+from secops.chronicle.client import ChronicleClient
+from secops.chronicle.models import APIVersion
+from secops.chronicle.integration.connector_revisions import (
+ list_integration_connector_revisions,
+ delete_integration_connector_revision,
+ create_integration_connector_revision,
+ rollback_integration_connector_revision,
+)
+from secops.exceptions import APIError
+
+
+@pytest.fixture
+def chronicle_client():
+ """Create a Chronicle client for testing."""
+ with patch("secops.auth.SecOpsAuth") as mock_auth:
+ mock_session = Mock()
+ mock_session.headers = {}
+ mock_auth.return_value.session = mock_session
+ return ChronicleClient(
+ customer_id="test-customer",
+ project_id="test-project",
+ default_api_version=APIVersion.V1BETA,
+ )
+
+
+# -- list_integration_connector_revisions tests --
+
+
+def test_list_integration_connector_revisions_success(chronicle_client):
+ """Test list_integration_connector_revisions delegates to chronicle_paginated_request."""
+ expected = {
+ "revisions": [{"name": "r1"}, {"name": "r2"}],
+ "nextPageToken": "t",
+ }
+
+ with patch(
+ "secops.chronicle.integration.connector_revisions.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated, patch(
+ "secops.chronicle.integration.connector_revisions.format_resource_id",
+ return_value="My Integration",
+ ):
+ result = list_integration_connector_revisions(
+ chronicle_client,
+ integration_name="My Integration",
+ connector_id="c1",
+ page_size=10,
+ page_token="next-token",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_paginated.call_args
+ assert "connectors/c1/revisions" in kwargs["path"]
+ assert kwargs["items_key"] == "revisions"
+ assert kwargs["page_size"] == 10
+ assert kwargs["page_token"] == "next-token"
+
+
+def test_list_integration_connector_revisions_default_args(chronicle_client):
+ """Test list_integration_connector_revisions with default args."""
+ expected = {"revisions": []}
+
+ with patch(
+ "secops.chronicle.integration.connector_revisions.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_integration_connector_revisions(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ )
+
+ assert result == expected
+
+
+def test_list_integration_connector_revisions_with_filters(chronicle_client):
+ """Test list_integration_connector_revisions with filter and order_by."""
+ expected = {"revisions": [{"name": "r1"}]}
+
+ with patch(
+ "secops.chronicle.integration.connector_revisions.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_integration_connector_revisions(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ filter_string='version = "1.0"',
+ order_by="createTime",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_paginated.call_args
+ assert kwargs["extra_params"] == {
+ "filter": 'version = "1.0"',
+ "orderBy": "createTime",
+ }
+
+
+def test_list_integration_connector_revisions_as_list(chronicle_client):
+ """Test list_integration_connector_revisions returns list when as_list=True."""
+ expected = [{"name": "r1"}, {"name": "r2"}]
+
+ with patch(
+ "secops.chronicle.integration.connector_revisions.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_integration_connector_revisions(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ as_list=True,
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_paginated.call_args
+ assert kwargs["as_list"] is True
+
+
+def test_list_integration_connector_revisions_error(chronicle_client):
+ """Test list_integration_connector_revisions raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.connector_revisions.chronicle_paginated_request",
+ side_effect=APIError("Failed to list connector revisions"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ list_integration_connector_revisions(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ )
+ assert "Failed to list connector revisions" in str(exc_info.value)
+
+
+# -- delete_integration_connector_revision tests --
+
+
+def test_delete_integration_connector_revision_success(chronicle_client):
+ """Test delete_integration_connector_revision issues DELETE request."""
+ with patch(
+ "secops.chronicle.integration.connector_revisions.chronicle_request",
+ return_value=None,
+ ) as mock_request:
+ delete_integration_connector_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ revision_id="r1",
+ )
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["method"] == "DELETE"
+ assert "connectors/c1/revisions/r1" in kwargs["endpoint_path"]
+ assert kwargs["api_version"] == APIVersion.V1BETA
+
+
+def test_delete_integration_connector_revision_error(chronicle_client):
+ """Test delete_integration_connector_revision raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.connector_revisions.chronicle_request",
+ side_effect=APIError("Failed to delete connector revision"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ delete_integration_connector_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ revision_id="r1",
+ )
+ assert "Failed to delete connector revision" in str(exc_info.value)
+
+
+# -- create_integration_connector_revision tests --
+
+
+def test_create_integration_connector_revision_required_fields_only(
+ chronicle_client,
+):
+ """Test create_integration_connector_revision with required fields only."""
+ expected = {
+ "name": "revisions/new",
+ "connector": {"displayName": "My Connector"},
+ }
+ connector_dict = {
+ "displayName": "My Connector",
+ "script": "print('hello')",
+ "version": 1,
+ "enabled": True,
+ "custom": True,
+ }
+
+ with patch(
+ "secops.chronicle.integration.connector_revisions.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = create_integration_connector_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ connector=connector_dict,
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="POST",
+ endpoint_path=(
+ "integrations/test-integration/connectors/c1/revisions"
+ ),
+ api_version=APIVersion.V1BETA,
+ json={"connector": connector_dict},
+ )
+
+
+def test_create_integration_connector_revision_with_comment(chronicle_client):
+ """Test create_integration_connector_revision includes comment when provided."""
+ expected = {"name": "revisions/new"}
+ connector_dict = {
+ "displayName": "My Connector",
+ "script": "print('hello')",
+ "version": 1,
+ "enabled": True,
+ "custom": True,
+ }
+
+ with patch(
+ "secops.chronicle.integration.connector_revisions.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = create_integration_connector_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ connector=connector_dict,
+ comment="Backup before major update",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["json"]["comment"] == "Backup before major update"
+ assert kwargs["json"]["connector"] == connector_dict
+
+
+def test_create_integration_connector_revision_error(chronicle_client):
+ """Test create_integration_connector_revision raises APIError on failure."""
+ connector_dict = {
+ "displayName": "My Connector",
+ "script": "print('hello')",
+ "version": 1,
+ "enabled": True,
+ "custom": True,
+ }
+
+ with patch(
+ "secops.chronicle.integration.connector_revisions.chronicle_request",
+ side_effect=APIError("Failed to create connector revision"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ create_integration_connector_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ connector=connector_dict,
+ )
+ assert "Failed to create connector revision" in str(exc_info.value)
+
+
+# -- rollback_integration_connector_revision tests --
+
+
+def test_rollback_integration_connector_revision_success(chronicle_client):
+ """Test rollback_integration_connector_revision issues POST request."""
+ expected = {
+ "name": "revisions/r1",
+ "connector": {
+ "displayName": "My Connector",
+ "script": "print('hello')",
+ },
+ }
+
+ with patch(
+ "secops.chronicle.integration.connector_revisions.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = rollback_integration_connector_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ revision_id="r1",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["method"] == "POST"
+ assert "connectors/c1/revisions/r1:rollback" in kwargs["endpoint_path"]
+ assert kwargs["api_version"] == APIVersion.V1BETA
+
+
+def test_rollback_integration_connector_revision_error(chronicle_client):
+ """Test rollback_integration_connector_revision raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.connector_revisions.chronicle_request",
+ side_effect=APIError("Failed to rollback connector revision"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ rollback_integration_connector_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ revision_id="r1",
+ )
+ assert "Failed to rollback connector revision" in str(exc_info.value)
+
+
+# -- API version tests --
+
+
+def test_list_integration_connector_revisions_custom_api_version(
+ chronicle_client,
+):
+ """Test list_integration_connector_revisions with custom API version."""
+ expected = {"revisions": []}
+
+ with patch(
+ "secops.chronicle.integration.connector_revisions.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_integration_connector_revisions(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ api_version=APIVersion.V1ALPHA,
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_paginated.call_args
+ assert kwargs["api_version"] == APIVersion.V1ALPHA
+
+
+def test_delete_integration_connector_revision_custom_api_version(
+ chronicle_client,
+):
+ """Test delete_integration_connector_revision with custom API version."""
+ with patch(
+ "secops.chronicle.integration.connector_revisions.chronicle_request",
+ return_value=None,
+ ) as mock_request:
+ delete_integration_connector_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ revision_id="r1",
+ api_version=APIVersion.V1ALPHA,
+ )
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["api_version"] == APIVersion.V1ALPHA
+
diff --git a/tests/chronicle/integration/test_connectors.py b/tests/chronicle/integration/test_connectors.py
new file mode 100644
index 00000000..3aca859a
--- /dev/null
+++ b/tests/chronicle/integration/test_connectors.py
@@ -0,0 +1,665 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Tests for Chronicle marketplace integration connectors functions."""
+
+from unittest.mock import Mock, patch
+
+import pytest
+
+from secops.chronicle.client import ChronicleClient
+from secops.chronicle.models import (
+ APIVersion,
+ ConnectorParameter,
+ ParamType,
+ ConnectorParamMode,
+ ConnectorRule,
+ ConnectorRuleType,
+)
+from secops.chronicle.integration.connectors import (
+ list_integration_connectors,
+ get_integration_connector,
+ delete_integration_connector,
+ create_integration_connector,
+ update_integration_connector,
+ execute_integration_connector_test,
+ get_integration_connector_template,
+)
+from secops.exceptions import APIError
+
+
+@pytest.fixture
+def chronicle_client():
+ """Create a Chronicle client for testing."""
+ with patch("secops.auth.SecOpsAuth") as mock_auth:
+ mock_session = Mock()
+ mock_session.headers = {}
+ mock_auth.return_value.session = mock_session
+ return ChronicleClient(
+ customer_id="test-customer",
+ project_id="test-project",
+ default_api_version=APIVersion.V1BETA,
+ )
+
+
+# -- list_integration_connectors tests --
+
+
+def test_list_integration_connectors_success(chronicle_client):
+ """Test list_integration_connectors delegates to chronicle_paginated_request."""
+ expected = {"connectors": [{"name": "c1"}, {"name": "c2"}], "nextPageToken": "t"}
+
+ with patch(
+ "secops.chronicle.integration.connectors.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated, patch(
+ "secops.chronicle.integration.connectors.format_resource_id",
+ return_value="My Integration",
+ ):
+ result = list_integration_connectors(
+ chronicle_client,
+ integration_name="My Integration",
+ page_size=10,
+ page_token="next-token",
+ )
+
+ assert result == expected
+
+ mock_paginated.assert_called_once_with(
+ chronicle_client,
+ api_version=APIVersion.V1BETA,
+ path="integrations/My Integration/connectors",
+ items_key="connectors",
+ page_size=10,
+ page_token="next-token",
+ extra_params={},
+ as_list=False,
+ )
+
+
+def test_list_integration_connectors_default_args(chronicle_client):
+ """Test list_integration_connectors with default args."""
+ expected = {"connectors": []}
+
+ with patch(
+ "secops.chronicle.integration.connectors.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_integration_connectors(
+ chronicle_client,
+ integration_name="test-integration",
+ )
+
+ assert result == expected
+
+
+def test_list_integration_connectors_with_filters(chronicle_client):
+ """Test list_integration_connectors with filter and order_by."""
+ expected = {"connectors": [{"name": "c1"}]}
+
+ with patch(
+ "secops.chronicle.integration.connectors.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_integration_connectors(
+ chronicle_client,
+ integration_name="test-integration",
+ filter_string="enabled=true",
+ order_by="displayName",
+ exclude_staging=True,
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_paginated.call_args
+ assert kwargs["extra_params"] == {
+ "filter": "enabled=true",
+ "orderBy": "displayName",
+ "excludeStaging": True,
+ }
+
+
+def test_list_integration_connectors_as_list(chronicle_client):
+ """Test list_integration_connectors returns list when as_list=True."""
+ expected = [{"name": "c1"}, {"name": "c2"}]
+
+ with patch(
+ "secops.chronicle.integration.connectors.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_integration_connectors(
+ chronicle_client,
+ integration_name="test-integration",
+ as_list=True,
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_paginated.call_args
+ assert kwargs["as_list"] is True
+
+
+def test_list_integration_connectors_error(chronicle_client):
+ """Test list_integration_connectors raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.connectors.chronicle_paginated_request",
+ side_effect=APIError("Failed to list integration connectors"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ list_integration_connectors(
+ chronicle_client,
+ integration_name="test-integration",
+ )
+ assert "Failed to list integration connectors" in str(exc_info.value)
+
+
+# -- get_integration_connector tests --
+
+
+def test_get_integration_connector_success(chronicle_client):
+ """Test get_integration_connector issues GET request."""
+ expected = {
+ "name": "connectors/c1",
+ "displayName": "My Connector",
+ "script": "print('hello')",
+ }
+
+ with patch(
+ "secops.chronicle.integration.connectors.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = get_integration_connector(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="GET",
+ endpoint_path="integrations/test-integration/connectors/c1",
+ api_version=APIVersion.V1BETA,
+ )
+
+
+def test_get_integration_connector_error(chronicle_client):
+ """Test get_integration_connector raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.connectors.chronicle_request",
+ side_effect=APIError("Failed to get integration connector"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ get_integration_connector(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ )
+ assert "Failed to get integration connector" in str(exc_info.value)
+
+
+# -- delete_integration_connector tests --
+
+
+def test_delete_integration_connector_success(chronicle_client):
+ """Test delete_integration_connector issues DELETE request."""
+ with patch(
+ "secops.chronicle.integration.connectors.chronicle_request",
+ return_value=None,
+ ) as mock_request:
+ delete_integration_connector(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ )
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="DELETE",
+ endpoint_path="integrations/test-integration/connectors/c1",
+ api_version=APIVersion.V1BETA,
+ )
+
+
+def test_delete_integration_connector_error(chronicle_client):
+ """Test delete_integration_connector raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.connectors.chronicle_request",
+ side_effect=APIError("Failed to delete integration connector"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ delete_integration_connector(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ )
+ assert "Failed to delete integration connector" in str(exc_info.value)
+
+
+# -- create_integration_connector tests --
+
+
+def test_create_integration_connector_required_fields_only(chronicle_client):
+ """Test create_integration_connector sends only required fields when optionals omitted."""
+ expected = {"name": "connectors/new", "displayName": "My Connector"}
+
+ with patch(
+ "secops.chronicle.integration.connectors.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = create_integration_connector(
+ chronicle_client,
+ integration_name="test-integration",
+ display_name="My Connector",
+ script="print('hi')",
+ timeout_seconds=300,
+ enabled=True,
+ product_field_name="product",
+ event_field_name="event",
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="POST",
+ endpoint_path="integrations/test-integration/connectors",
+ api_version=APIVersion.V1BETA,
+ json={
+ "displayName": "My Connector",
+ "script": "print('hi')",
+ "timeoutSeconds": 300,
+ "enabled": True,
+ "productFieldName": "product",
+ "eventFieldName": "event",
+ },
+ )
+
+
+def test_create_integration_connector_with_optional_fields(chronicle_client):
+ """Test create_integration_connector includes optional fields when provided."""
+ expected = {"name": "connectors/new"}
+
+ with patch(
+ "secops.chronicle.integration.connectors.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = create_integration_connector(
+ chronicle_client,
+ integration_name="test-integration",
+ display_name="My Connector",
+ script="print('hi')",
+ timeout_seconds=300,
+ enabled=True,
+ product_field_name="product",
+ event_field_name="event",
+ description="Test connector",
+ parameters=[{"name": "p1", "type": "STRING"}],
+ rules=[{"name": "r1", "type": "MAPPING"}],
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["json"]["description"] == "Test connector"
+ assert kwargs["json"]["parameters"] == [{"name": "p1", "type": "STRING"}]
+ assert kwargs["json"]["rules"] == [{"name": "r1", "type": "MAPPING"}]
+
+
+def test_create_integration_connector_with_dataclass_parameters(chronicle_client):
+ """Test create_integration_connector converts ConnectorParameter dataclasses."""
+ expected = {"name": "connectors/new"}
+
+ param = ConnectorParameter(
+ display_name="API Key",
+ type=ParamType.STRING,
+ mode=ConnectorParamMode.REGULAR,
+ mandatory=True,
+ description="API key for authentication",
+ )
+
+ with patch(
+ "secops.chronicle.integration.connectors.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = create_integration_connector(
+ chronicle_client,
+ integration_name="test-integration",
+ display_name="My Connector",
+ script="print('hi')",
+ timeout_seconds=300,
+ enabled=True,
+ product_field_name="product",
+ event_field_name="event",
+ parameters=[param],
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ params_sent = kwargs["json"]["parameters"]
+ assert len(params_sent) == 1
+ assert params_sent[0]["displayName"] == "API Key"
+ assert params_sent[0]["type"] == "STRING"
+
+
+def test_create_integration_connector_with_dataclass_rules(chronicle_client):
+ """Test create_integration_connector converts ConnectorRule dataclasses."""
+ expected = {"name": "connectors/new"}
+
+ rule = ConnectorRule(
+ display_name="Mapping Rule",
+ type=ConnectorRuleType.ALLOW_LIST,
+ )
+
+ with patch(
+ "secops.chronicle.integration.connectors.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = create_integration_connector(
+ chronicle_client,
+ integration_name="test-integration",
+ display_name="My Connector",
+ script="print('hi')",
+ timeout_seconds=300,
+ enabled=True,
+ product_field_name="product",
+ event_field_name="event",
+ rules=[rule],
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ rules_sent = kwargs["json"]["rules"]
+ assert len(rules_sent) == 1
+ assert rules_sent[0]["displayName"] == "Mapping Rule"
+ assert rules_sent[0]["type"] == "ALLOW_LIST"
+
+
+def test_create_integration_connector_error(chronicle_client):
+ """Test create_integration_connector raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.connectors.chronicle_request",
+ side_effect=APIError("Failed to create integration connector"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ create_integration_connector(
+ chronicle_client,
+ integration_name="test-integration",
+ display_name="My Connector",
+ script="print('hi')",
+ timeout_seconds=300,
+ enabled=True,
+ product_field_name="product",
+ event_field_name="event",
+ )
+ assert "Failed to create integration connector" in str(exc_info.value)
+
+
+# -- update_integration_connector tests --
+
+
+def test_update_integration_connector_with_explicit_update_mask(chronicle_client):
+ """Test update_integration_connector passes through explicit update_mask."""
+ expected = {"name": "connectors/c1", "displayName": "New Name"}
+
+ with patch(
+ "secops.chronicle.integration.connectors.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = update_integration_connector(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ display_name="New Name",
+ update_mask="displayName",
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="PATCH",
+ endpoint_path="integrations/test-integration/connectors/c1",
+ api_version=APIVersion.V1BETA,
+ json={"displayName": "New Name"},
+ params={"updateMask": "displayName"},
+ )
+
+
+def test_update_integration_connector_auto_update_mask(chronicle_client):
+ """Test update_integration_connector auto-generates updateMask based on fields."""
+ expected = {"name": "connectors/c1"}
+
+ with patch(
+ "secops.chronicle.integration.connectors.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = update_integration_connector(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ enabled=False,
+ timeout_seconds=600,
+ )
+
+ assert result == expected
+
+ assert mock_request.call_count == 1
+ _, kwargs = mock_request.call_args
+
+ assert kwargs["method"] == "PATCH"
+ assert kwargs["endpoint_path"] == "integrations/test-integration/connectors/c1"
+ assert kwargs["api_version"] == APIVersion.V1BETA
+
+ assert kwargs["json"] == {"enabled": False, "timeoutSeconds": 600}
+
+ update_mask = kwargs["params"]["updateMask"]
+ assert set(update_mask.split(",")) == {"enabled", "timeoutSeconds"}
+
+
+def test_update_integration_connector_with_parameters(chronicle_client):
+ """Test update_integration_connector with parameters field."""
+ expected = {"name": "connectors/c1"}
+
+ param = ConnectorParameter(
+ display_name="Auth Token",
+ type=ParamType.STRING,
+ mode=ConnectorParamMode.REGULAR,
+ mandatory=True,
+ )
+
+ with patch(
+ "secops.chronicle.integration.connectors.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = update_integration_connector(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ parameters=[param],
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ params_sent = kwargs["json"]["parameters"]
+ assert len(params_sent) == 1
+ assert params_sent[0]["displayName"] == "Auth Token"
+
+
+def test_update_integration_connector_with_rules(chronicle_client):
+ """Test update_integration_connector with rules field."""
+ expected = {"name": "connectors/c1"}
+
+ rule = ConnectorRule(
+ display_name="Filter Rule",
+ type=ConnectorRuleType.BLOCK_LIST,
+ )
+
+ with patch(
+ "secops.chronicle.integration.connectors.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = update_integration_connector(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ rules=[rule],
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ rules_sent = kwargs["json"]["rules"]
+ assert len(rules_sent) == 1
+ assert rules_sent[0]["displayName"] == "Filter Rule"
+
+
+def test_update_integration_connector_error(chronicle_client):
+ """Test update_integration_connector raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.connectors.chronicle_request",
+ side_effect=APIError("Failed to update integration connector"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ update_integration_connector(
+ chronicle_client,
+ integration_name="test-integration",
+ connector_id="c1",
+ display_name="New Name",
+ )
+ assert "Failed to update integration connector" in str(exc_info.value)
+
+
+# -- execute_integration_connector_test tests --
+
+
+def test_execute_integration_connector_test_success(chronicle_client):
+ """Test execute_integration_connector_test sends POST request with connector."""
+ expected = {
+ "outputMessage": "Success",
+ "debugOutputMessage": "Debug info",
+ "resultJson": {"status": "ok"},
+ }
+
+ connector = {
+ "displayName": "Test Connector",
+ "script": "print('test')",
+ "enabled": True,
+ }
+
+ with patch(
+ "secops.chronicle.integration.connectors.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = execute_integration_connector_test(
+ chronicle_client,
+ integration_name="test-integration",
+ connector=connector,
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="POST",
+ endpoint_path="integrations/test-integration/connectors:executeTest",
+ api_version=APIVersion.V1BETA,
+ json={"connector": connector},
+ )
+
+
+def test_execute_integration_connector_test_with_agent_identifier(chronicle_client):
+ """Test execute_integration_connector_test includes agent_identifier when provided."""
+ expected = {"outputMessage": "Success"}
+
+ connector = {"displayName": "Test", "script": "print('test')"}
+
+ with patch(
+ "secops.chronicle.integration.connectors.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = execute_integration_connector_test(
+ chronicle_client,
+ integration_name="test-integration",
+ connector=connector,
+ agent_identifier="agent-123",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["json"]["agentIdentifier"] == "agent-123"
+
+
+def test_execute_integration_connector_test_error(chronicle_client):
+ """Test execute_integration_connector_test raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.connectors.chronicle_request",
+ side_effect=APIError("Failed to execute connector test"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ execute_integration_connector_test(
+ chronicle_client,
+ integration_name="test-integration",
+ connector={"displayName": "Test"},
+ )
+ assert "Failed to execute connector test" in str(exc_info.value)
+
+
+# -- get_integration_connector_template tests --
+
+
+def test_get_integration_connector_template_success(chronicle_client):
+ """Test get_integration_connector_template issues GET request."""
+ expected = {
+ "script": "# Template script\nprint('hello')",
+ "displayName": "Template Connector",
+ }
+
+ with patch(
+ "secops.chronicle.integration.connectors.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = get_integration_connector_template(
+ chronicle_client,
+ integration_name="test-integration",
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="GET",
+ endpoint_path="integrations/test-integration/connectors:fetchTemplate",
+ api_version=APIVersion.V1BETA,
+ )
+
+
+def test_get_integration_connector_template_error(chronicle_client):
+ """Test get_integration_connector_template raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.connectors.chronicle_request",
+ side_effect=APIError("Failed to get connector template"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ get_integration_connector_template(
+ chronicle_client,
+ integration_name="test-integration",
+ )
+ assert "Failed to get connector template" in str(exc_info.value)
+
diff --git a/tests/chronicle/integration/test_integration_instances.py b/tests/chronicle/integration/test_integration_instances.py
new file mode 100644
index 00000000..153390ad
--- /dev/null
+++ b/tests/chronicle/integration/test_integration_instances.py
@@ -0,0 +1,623 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Tests for Chronicle marketplace integration instances functions."""
+
+from unittest.mock import Mock, patch
+
+import pytest
+
+from secops.chronicle.client import ChronicleClient
+from secops.chronicle.models import (
+ APIVersion,
+ IntegrationInstanceParameter,
+)
+from secops.chronicle.integration.integration_instances import (
+ list_integration_instances,
+ get_integration_instance,
+ delete_integration_instance,
+ create_integration_instance,
+ update_integration_instance,
+ execute_integration_instance_test,
+ get_integration_instance_affected_items,
+ get_default_integration_instance,
+)
+from secops.exceptions import APIError
+
+
+@pytest.fixture
+def chronicle_client():
+ """Create a Chronicle client for testing."""
+ with patch("secops.auth.SecOpsAuth") as mock_auth:
+ mock_session = Mock()
+ mock_session.headers = {}
+ mock_auth.return_value.session = mock_session
+ return ChronicleClient(
+ customer_id="test-customer",
+ project_id="test-project",
+ default_api_version=APIVersion.V1BETA,
+ )
+
+
+# -- list_integration_instances tests --
+
+
+def test_list_integration_instances_success(chronicle_client):
+ """Test list_integration_instances delegates to chronicle_paginated_request."""
+ expected = {
+ "integrationInstances": [{"name": "ii1"}, {"name": "ii2"}],
+ "nextPageToken": "t",
+ }
+
+ with patch(
+ "secops.chronicle.integration.integration_instances.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated, patch(
+ "secops.chronicle.integration.integration_instances.format_resource_id",
+ return_value="My Integration",
+ ):
+ result = list_integration_instances(
+ chronicle_client,
+ integration_name="My Integration",
+ page_size=10,
+ page_token="next-token",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_paginated.call_args
+ assert "integrationInstances" in kwargs["path"]
+ assert kwargs["items_key"] == "integrationInstances"
+ assert kwargs["page_size"] == 10
+ assert kwargs["page_token"] == "next-token"
+
+
+def test_list_integration_instances_default_args(chronicle_client):
+ """Test list_integration_instances with default args."""
+ expected = {"integrationInstances": []}
+
+ with patch(
+ "secops.chronicle.integration.integration_instances.chronicle_paginated_request",
+ return_value=expected,
+ ):
+ result = list_integration_instances(
+ chronicle_client,
+ integration_name="test-integration",
+ )
+
+ assert result == expected
+
+
+def test_list_integration_instances_with_filters(chronicle_client):
+ """Test list_integration_instances with filter and order_by."""
+ expected = {"integrationInstances": [{"name": "ii1"}]}
+
+ with patch(
+ "secops.chronicle.integration.integration_instances.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_integration_instances(
+ chronicle_client,
+ integration_name="test-integration",
+ filter_string="environment = 'prod'",
+ order_by="displayName",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_paginated.call_args
+ assert kwargs["extra_params"] == {
+ "filter": "environment = 'prod'",
+ "orderBy": "displayName",
+ }
+
+
+def test_list_integration_instances_as_list(chronicle_client):
+ """Test list_integration_instances returns list when as_list=True."""
+ expected = [{"name": "ii1"}, {"name": "ii2"}]
+
+ with patch(
+ "secops.chronicle.integration.integration_instances.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_integration_instances(
+ chronicle_client,
+ integration_name="test-integration",
+ as_list=True,
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_paginated.call_args
+ assert kwargs["as_list"] is True
+
+
+def test_list_integration_instances_error(chronicle_client):
+ """Test list_integration_instances raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.integration_instances.chronicle_paginated_request",
+ side_effect=APIError("Failed to list integration instances"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ list_integration_instances(
+ chronicle_client,
+ integration_name="test-integration",
+ )
+ assert "Failed to list integration instances" in str(exc_info.value)
+
+
+# -- get_integration_instance tests --
+
+
+def test_get_integration_instance_success(chronicle_client):
+ """Test get_integration_instance issues GET request."""
+ expected = {
+ "name": "integrationInstances/ii1",
+ "displayName": "My Instance",
+ "environment": "production",
+ }
+
+ with patch(
+ "secops.chronicle.integration.integration_instances.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = get_integration_instance(
+ chronicle_client,
+ integration_name="test-integration",
+ integration_instance_id="ii1",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["method"] == "GET"
+ assert "integrationInstances/ii1" in kwargs["endpoint_path"]
+ assert kwargs["api_version"] == APIVersion.V1BETA
+
+
+def test_get_integration_instance_error(chronicle_client):
+ """Test get_integration_instance raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.integration_instances.chronicle_request",
+ side_effect=APIError("Failed to get integration instance"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ get_integration_instance(
+ chronicle_client,
+ integration_name="test-integration",
+ integration_instance_id="ii1",
+ )
+ assert "Failed to get integration instance" in str(exc_info.value)
+
+
+# -- delete_integration_instance tests --
+
+
+def test_delete_integration_instance_success(chronicle_client):
+ """Test delete_integration_instance issues DELETE request."""
+ with patch(
+ "secops.chronicle.integration.integration_instances.chronicle_request",
+ return_value=None,
+ ) as mock_request:
+ delete_integration_instance(
+ chronicle_client,
+ integration_name="test-integration",
+ integration_instance_id="ii1",
+ )
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["method"] == "DELETE"
+ assert "integrationInstances/ii1" in kwargs["endpoint_path"]
+ assert kwargs["api_version"] == APIVersion.V1BETA
+
+
+def test_delete_integration_instance_error(chronicle_client):
+ """Test delete_integration_instance raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.integration_instances.chronicle_request",
+ side_effect=APIError("Failed to delete integration instance"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ delete_integration_instance(
+ chronicle_client,
+ integration_name="test-integration",
+ integration_instance_id="ii1",
+ )
+ assert "Failed to delete integration instance" in str(exc_info.value)
+
+
+# -- create_integration_instance tests --
+
+
+def test_create_integration_instance_required_fields_only(chronicle_client):
+ """Test create_integration_instance sends only required fields."""
+ expected = {"name": "integrationInstances/new", "displayName": "My Instance"}
+
+ with patch(
+ "secops.chronicle.integration.integration_instances.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = create_integration_instance(
+ chronicle_client,
+ integration_name="test-integration",
+ environment="production",
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="POST",
+ endpoint_path="integrations/test-integration/integrationInstances",
+ api_version=APIVersion.V1BETA,
+ json={
+ "environment": "production",
+ },
+ )
+
+
+def test_create_integration_instance_with_optional_fields(chronicle_client):
+ """Test create_integration_instance includes optional fields when provided."""
+ expected = {"name": "integrationInstances/new"}
+
+ with patch(
+ "secops.chronicle.integration.integration_instances.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = create_integration_instance(
+ chronicle_client,
+ integration_name="test-integration",
+ environment="production",
+ display_name="My Instance",
+ description="Test instance",
+ parameters=[{"id": 1, "value": "test"}],
+ agent="agent-123",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["json"]["environment"] == "production"
+ assert kwargs["json"]["displayName"] == "My Instance"
+ assert kwargs["json"]["description"] == "Test instance"
+ assert kwargs["json"]["parameters"] == [{"id": 1, "value": "test"}]
+ assert kwargs["json"]["agent"] == "agent-123"
+
+
+def test_create_integration_instance_with_dataclass_params(chronicle_client):
+ """Test create_integration_instance converts dataclass parameters."""
+ expected = {"name": "integrationInstances/new"}
+
+ param = IntegrationInstanceParameter(value="test-value")
+
+ with patch(
+ "secops.chronicle.integration.integration_instances.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = create_integration_instance(
+ chronicle_client,
+ integration_name="test-integration",
+ environment="production",
+ parameters=[param],
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ params_sent = kwargs["json"]["parameters"]
+ assert len(params_sent) == 1
+ assert params_sent[0]["value"] == "test-value"
+
+
+def test_create_integration_instance_error(chronicle_client):
+ """Test create_integration_instance raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.integration_instances.chronicle_request",
+ side_effect=APIError("Failed to create integration instance"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ create_integration_instance(
+ chronicle_client,
+ integration_name="test-integration",
+ environment="production",
+ )
+ assert "Failed to create integration instance" in str(exc_info.value)
+
+
+# -- update_integration_instance tests --
+
+
+def test_update_integration_instance_with_single_field(chronicle_client):
+ """Test update_integration_instance with single field updates updateMask."""
+ expected = {"name": "integrationInstances/ii1", "displayName": "Updated"}
+
+ with patch(
+ "secops.chronicle.integration.integration_instances.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = update_integration_instance(
+ chronicle_client,
+ integration_name="test-integration",
+ integration_instance_id="ii1",
+ display_name="Updated",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["method"] == "PATCH"
+ assert "integrationInstances/ii1" in kwargs["endpoint_path"]
+ assert kwargs["json"]["displayName"] == "Updated"
+ assert kwargs["params"]["updateMask"] == "displayName"
+
+
+def test_update_integration_instance_with_multiple_fields(chronicle_client):
+ """Test update_integration_instance with multiple fields."""
+ expected = {"name": "integrationInstances/ii1"}
+
+ with patch(
+ "secops.chronicle.integration.integration_instances.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = update_integration_instance(
+ chronicle_client,
+ integration_name="test-integration",
+ integration_instance_id="ii1",
+ display_name="Updated",
+ description="New description",
+ environment="staging",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["json"]["displayName"] == "Updated"
+ assert kwargs["json"]["description"] == "New description"
+ assert kwargs["json"]["environment"] == "staging"
+ assert "displayName" in kwargs["params"]["updateMask"]
+ assert "description" in kwargs["params"]["updateMask"]
+ assert "environment" in kwargs["params"]["updateMask"]
+
+
+def test_update_integration_instance_with_custom_update_mask(chronicle_client):
+ """Test update_integration_instance with explicitly provided update_mask."""
+ expected = {"name": "integrationInstances/ii1"}
+
+ with patch(
+ "secops.chronicle.integration.integration_instances.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = update_integration_instance(
+ chronicle_client,
+ integration_name="test-integration",
+ integration_instance_id="ii1",
+ display_name="Updated",
+ update_mask="displayName,environment",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["params"]["updateMask"] == "displayName,environment"
+
+
+def test_update_integration_instance_with_dataclass_params(chronicle_client):
+ """Test update_integration_instance converts dataclass parameters."""
+ expected = {"name": "integrationInstances/ii1"}
+
+ param = IntegrationInstanceParameter(value="test-value")
+
+ with patch(
+ "secops.chronicle.integration.integration_instances.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = update_integration_instance(
+ chronicle_client,
+ integration_name="test-integration",
+ integration_instance_id="ii1",
+ parameters=[param],
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ params_sent = kwargs["json"]["parameters"]
+ assert len(params_sent) == 1
+ assert params_sent[0]["value"] == "test-value"
+
+
+def test_update_integration_instance_error(chronicle_client):
+ """Test update_integration_instance raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.integration_instances.chronicle_request",
+ side_effect=APIError("Failed to update integration instance"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ update_integration_instance(
+ chronicle_client,
+ integration_name="test-integration",
+ integration_instance_id="ii1",
+ display_name="Updated",
+ )
+ assert "Failed to update integration instance" in str(exc_info.value)
+
+
+# -- execute_integration_instance_test tests --
+
+
+def test_execute_integration_instance_test_success(chronicle_client):
+ """Test execute_integration_instance_test issues POST request."""
+ expected = {
+ "successful": True,
+ "message": "Test successful",
+ }
+
+ with patch(
+ "secops.chronicle.integration.integration_instances.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = execute_integration_instance_test(
+ chronicle_client,
+ integration_name="test-integration",
+ integration_instance_id="ii1",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["method"] == "POST"
+ assert "integrationInstances/ii1:executeTest" in kwargs["endpoint_path"]
+ assert kwargs["api_version"] == APIVersion.V1BETA
+
+
+def test_execute_integration_instance_test_failure(chronicle_client):
+ """Test execute_integration_instance_test when test fails."""
+ expected = {
+ "successful": False,
+ "message": "Connection failed",
+ }
+
+ with patch(
+ "secops.chronicle.integration.integration_instances.chronicle_request",
+ return_value=expected,
+ ):
+ result = execute_integration_instance_test(
+ chronicle_client,
+ integration_name="test-integration",
+ integration_instance_id="ii1",
+ )
+
+ assert result == expected
+ assert result["successful"] is False
+
+
+def test_execute_integration_instance_test_error(chronicle_client):
+ """Test execute_integration_instance_test raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.integration_instances.chronicle_request",
+ side_effect=APIError("Failed to execute test"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ execute_integration_instance_test(
+ chronicle_client,
+ integration_name="test-integration",
+ integration_instance_id="ii1",
+ )
+ assert "Failed to execute test" in str(exc_info.value)
+
+
+# -- get_integration_instance_affected_items tests --
+
+
+def test_get_integration_instance_affected_items_success(chronicle_client):
+ """Test get_integration_instance_affected_items issues GET request."""
+ expected = {
+ "affectedPlaybooks": [
+ {"name": "playbook1", "displayName": "Playbook 1"},
+ {"name": "playbook2", "displayName": "Playbook 2"},
+ ]
+ }
+
+ with patch(
+ "secops.chronicle.integration.integration_instances.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = get_integration_instance_affected_items(
+ chronicle_client,
+ integration_name="test-integration",
+ integration_instance_id="ii1",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["method"] == "GET"
+ assert "integrationInstances/ii1:fetchAffectedItems" in kwargs["endpoint_path"]
+ assert kwargs["api_version"] == APIVersion.V1BETA
+
+
+def test_get_integration_instance_affected_items_empty(chronicle_client):
+ """Test get_integration_instance_affected_items with no affected items."""
+ expected = {"affectedPlaybooks": []}
+
+ with patch(
+ "secops.chronicle.integration.integration_instances.chronicle_request",
+ return_value=expected,
+ ):
+ result = get_integration_instance_affected_items(
+ chronicle_client,
+ integration_name="test-integration",
+ integration_instance_id="ii1",
+ )
+
+ assert result == expected
+ assert len(result["affectedPlaybooks"]) == 0
+
+
+def test_get_integration_instance_affected_items_error(chronicle_client):
+ """Test get_integration_instance_affected_items raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.integration_instances.chronicle_request",
+ side_effect=APIError("Failed to fetch affected items"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ get_integration_instance_affected_items(
+ chronicle_client,
+ integration_name="test-integration",
+ integration_instance_id="ii1",
+ )
+ assert "Failed to fetch affected items" in str(exc_info.value)
+
+
+# -- get_default_integration_instance tests --
+
+
+def test_get_default_integration_instance_success(chronicle_client):
+ """Test get_default_integration_instance issues GET request."""
+ expected = {
+ "name": "integrationInstances/default",
+ "displayName": "Default Instance",
+ "environment": "default",
+ }
+
+ with patch(
+ "secops.chronicle.integration.integration_instances.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = get_default_integration_instance(
+ chronicle_client,
+ integration_name="test-integration",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["method"] == "GET"
+ assert "integrationInstances:fetchDefaultInstance" in kwargs["endpoint_path"]
+ assert kwargs["api_version"] == APIVersion.V1BETA
+
+
+def test_get_default_integration_instance_error(chronicle_client):
+ """Test get_default_integration_instance raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.integration_instances.chronicle_request",
+ side_effect=APIError("Failed to get default instance"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ get_default_integration_instance(
+ chronicle_client,
+ integration_name="test-integration",
+ )
+ assert "Failed to get default instance" in str(exc_info.value)
+
diff --git a/tests/chronicle/integration/test_integrations.py b/tests/chronicle/integration/test_integrations.py
new file mode 100644
index 00000000..811ab052
--- /dev/null
+++ b/tests/chronicle/integration/test_integrations.py
@@ -0,0 +1,909 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Tests for Chronicle integration functions."""
+
+from unittest.mock import Mock, patch
+
+import pytest
+
+from secops.chronicle.client import ChronicleClient
+from secops.chronicle.models import (
+ APIVersion,
+ DiffType,
+ TargetMode,
+ PythonVersion,
+)
+from secops.chronicle.integration.integrations import (
+ list_integrations,
+ get_integration,
+ delete_integration,
+ create_integration,
+ download_integration,
+ download_integration_dependency,
+ export_integration_items,
+ get_integration_affected_items,
+ get_agent_integrations,
+ get_integration_dependencies,
+ get_integration_restricted_agents,
+ get_integration_diff,
+ transition_integration,
+ update_integration,
+ update_custom_integration,
+)
+from secops.exceptions import APIError
+
+
+@pytest.fixture
+def chronicle_client():
+ """Create a Chronicle client for testing."""
+ with patch("secops.auth.SecOpsAuth") as mock_auth:
+ mock_session = Mock()
+ mock_session.headers = {}
+ mock_auth.return_value.session = mock_session
+ return ChronicleClient(
+ customer_id="test-customer",
+ project_id="test-project",
+ default_api_version=APIVersion.V1BETA,
+ )
+
+
+@pytest.fixture
+def mock_response() -> Mock:
+ """Create a mock API response object."""
+ mock = Mock()
+ mock.status_code = 200
+ mock.json.return_value = {}
+ return mock
+
+
+@pytest.fixture
+def mock_error_response() -> Mock:
+ """Create a mock error API response object."""
+ mock = Mock()
+ mock.status_code = 400
+ mock.text = "Error message"
+ mock.raise_for_status.side_effect = Exception("API Error")
+ return mock
+
+
+# -- list_integrations tests --
+
+
+def test_list_integrations_success(chronicle_client):
+ """Test list_integrations delegates to chronicle_paginated_request."""
+ expected = {"integrations": [{"name": "i1"}, {"name": "i2"}]}
+
+ with patch(
+ "secops.chronicle.integration.integrations.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_integrations(
+ chronicle_client,
+ page_size=10,
+ page_token="next-token",
+ )
+
+ assert result == expected
+
+ mock_paginated.assert_called_once_with(
+ chronicle_client,
+ api_version=APIVersion.V1BETA,
+ path="integrations",
+ items_key="integrations",
+ page_size=10,
+ page_token="next-token",
+ extra_params={},
+ as_list=False,
+ )
+
+
+def test_list_integrations_with_filter_and_order_by(chronicle_client):
+ """Test list_integrations passes filter_string and order_by in extra_params."""
+ expected = {"integrations": []}
+
+ with patch(
+ "secops.chronicle.integration.integrations.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_integrations(
+ chronicle_client,
+ filter_string='displayName = "My Integration"',
+ order_by="displayName",
+ as_list=True,
+ )
+
+ assert result == expected
+
+ mock_paginated.assert_called_once_with(
+ chronicle_client,
+ api_version=APIVersion.V1BETA,
+ path="integrations",
+ items_key="integrations",
+ page_size=None,
+ page_token=None,
+ extra_params={
+ "filter": 'displayName = "My Integration"',
+ "orderBy": "displayName",
+ },
+ as_list=True,
+ )
+
+
+def test_list_integrations_error(chronicle_client):
+ """Test list_integrations propagates APIError from helper."""
+ with patch(
+ "secops.chronicle.integration.integrations.chronicle_paginated_request",
+ side_effect=APIError("Failed to list integrations"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ list_integrations(chronicle_client)
+
+ assert "Failed to list integrations" in str(exc_info.value)
+
+
+# -- get_integration tests --
+
+
+def test_get_integration_success(chronicle_client):
+ """Test get_integration returns expected result."""
+ expected = {"name": "integrations/test-integration", "displayName": "Test"}
+
+ with patch(
+ "secops.chronicle.integration.integrations.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = get_integration(chronicle_client, "test-integration")
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="GET",
+ endpoint_path="integrations/test-integration",
+ api_version=APIVersion.V1BETA,
+ )
+
+
+def test_get_integration_error(chronicle_client):
+ """Test get_integration raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.integrations.chronicle_request",
+ side_effect=APIError("Failed to get integration"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ get_integration(chronicle_client, "test-integration")
+
+ assert "Failed to get integration" in str(exc_info.value)
+
+
+# -- delete_integration tests --
+
+
+def test_delete_integration_success(chronicle_client):
+ """Test delete_integration delegates to chronicle_request."""
+ with patch("secops.chronicle.integration.integrations.chronicle_request") as mock_request:
+ delete_integration(chronicle_client, "test-integration")
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="DELETE",
+ endpoint_path="integrations/test-integration",
+ api_version=APIVersion.V1BETA,
+ )
+
+
+def test_delete_integration_error(chronicle_client):
+ """Test delete_integration propagates APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.integrations.chronicle_request",
+ side_effect=APIError("Failed to delete integration"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ delete_integration(chronicle_client, "test-integration")
+
+ assert "Failed to delete integration" in str(exc_info.value)
+
+
+# -- create_integration tests --
+
+
+def test_create_integration_required_fields_only(chronicle_client):
+ """Test create_integration with required fields only."""
+ expected = {"name": "integrations/test", "displayName": "Test"}
+
+ with patch(
+ "secops.chronicle.integration.integrations.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = create_integration(
+ chronicle_client,
+ display_name="Test",
+ staging=True,
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="POST",
+ endpoint_path="integrations",
+ json={"displayName": "Test", "staging": True},
+ api_version=APIVersion.V1BETA,
+ )
+
+
+def test_create_integration_all_optional_fields(chronicle_client):
+ """Test create_integration with all optional fields."""
+ expected = {"name": "integrations/test"}
+
+ python_version = list(PythonVersion)[0]
+ integration_type = Mock(name="integration_type")
+
+ with patch(
+ "secops.chronicle.integration.integrations.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = create_integration(
+ chronicle_client,
+ display_name="Test",
+ staging=False,
+ description="desc",
+ image_base64="b64",
+ svg_icon="",
+ python_version=python_version,
+ parameters=[{"id": "p1"}],
+ categories=["cat"],
+ integration_type=integration_type,
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="POST",
+ endpoint_path="integrations",
+ json={
+ "displayName": "Test",
+ "staging": False,
+ "description": "desc",
+ "imageBase64": "b64",
+ "svgIcon": "",
+ "pythonVersion": python_version,
+ "parameters": [{"id": "p1"}],
+ "categories": ["cat"],
+ "type": integration_type,
+ },
+ api_version=APIVersion.V1BETA,
+ )
+
+
+def test_create_integration_none_fields_excluded(chronicle_client):
+ """Test that None optional fields are excluded from create_integration body."""
+ with patch(
+ "secops.chronicle.integration.integrations.chronicle_request",
+ return_value={"name": "integrations/test"},
+ ) as mock_request:
+ create_integration(
+ chronicle_client,
+ display_name="Test",
+ staging=True,
+ description=None,
+ image_base64=None,
+ svg_icon=None,
+ python_version=None,
+ parameters=None,
+ categories=None,
+ integration_type=None,
+ )
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="POST",
+ endpoint_path="integrations",
+ json={"displayName": "Test", "staging": True},
+ api_version=APIVersion.V1BETA,
+ )
+
+
+def test_create_integration_error(chronicle_client):
+ """Test create_integration raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.integrations.chronicle_request",
+ side_effect=APIError("Failed to create integration"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ create_integration(chronicle_client, display_name="Test", staging=True)
+
+ assert "Failed to create integration" in str(exc_info.value)
+
+
+# -- download_integration tests --
+
+
+def test_download_integration_success(chronicle_client):
+ """Test download_integration uses chronicle_request_bytes with alt=media and zip accept."""
+ expected = b"ZIPBYTES"
+
+ with patch(
+ "secops.chronicle.integration.integrations.chronicle_request_bytes",
+ return_value=expected,
+ ) as mock_bytes:
+ result = download_integration(chronicle_client, "test-integration")
+
+ assert result == expected
+
+ mock_bytes.assert_called_once_with(
+ chronicle_client,
+ method="GET",
+ endpoint_path="integrations/test-integration:export",
+ api_version=APIVersion.V1BETA,
+ params={"alt": "media"},
+ headers={"Accept": "application/zip"},
+ )
+
+
+def test_download_integration_error(chronicle_client):
+ """Test download_integration propagates APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.integrations.chronicle_request_bytes",
+ side_effect=APIError("Failed to download integration"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ download_integration(chronicle_client, "test-integration")
+
+ assert "Failed to download integration" in str(exc_info.value)
+
+
+# -- download_integration_dependency tests --
+
+
+def test_download_integration_dependency_success(chronicle_client):
+ """Test download_integration_dependency posts dependency name."""
+ expected = {"dependency": "requests"}
+
+ with patch(
+ "secops.chronicle.integration.integrations.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = download_integration_dependency(
+ chronicle_client,
+ integration_name="test-integration",
+ dependency_name="requests==2.32.0",
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="POST",
+ endpoint_path="integrations/test-integration:downloadDependency",
+ json={"dependency": "requests==2.32.0"},
+ api_version=APIVersion.V1BETA,
+ )
+
+
+def test_download_integration_dependency_error(chronicle_client):
+ """Test download_integration_dependency raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.integrations.chronicle_request",
+ side_effect=APIError("Failed to download dependency"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ download_integration_dependency(
+ chronicle_client,
+ integration_name="test-integration",
+ dependency_name="requests",
+ )
+
+ assert "Failed to download dependency" in str(exc_info.value)
+
+
+# -- export_integration_items tests --
+
+
+def test_export_integration_items_success_some_fields(chronicle_client):
+ """Test export_integration_items builds params correctly and uses chronicle_request_bytes."""
+ expected = b"ZIPBYTES"
+
+ with patch(
+ "secops.chronicle.integration.integrations.chronicle_request_bytes",
+ return_value=expected,
+ ) as mock_bytes:
+ result = export_integration_items(
+ chronicle_client,
+ integration_name="test-integration",
+ actions=["1", "2"],
+ connectors=["10"],
+ logical_operators=["7"],
+ )
+
+ assert result == expected
+
+ mock_bytes.assert_called_once_with(
+ chronicle_client,
+ method="GET",
+ endpoint_path="integrations/test-integration:exportItems",
+ params={
+ "actions": "1,2",
+ "connectors": ["10"],
+ "logicalOperators": ["7"],
+ "alt": "media",
+ },
+ api_version=APIVersion.V1BETA,
+ headers={"Accept": "application/zip"},
+ )
+
+
+def test_export_integration_items_no_fields(chronicle_client):
+ """Test export_integration_items always includes alt=media."""
+ expected = b"ZIPBYTES"
+
+ with patch(
+ "secops.chronicle.integration.integrations.chronicle_request_bytes",
+ return_value=expected,
+ ) as mock_bytes:
+ result = export_integration_items(
+ chronicle_client,
+ integration_name="test-integration",
+ )
+
+ assert result == expected
+
+ mock_bytes.assert_called_once_with(
+ chronicle_client,
+ method="GET",
+ endpoint_path="integrations/test-integration:exportItems",
+ params={"alt": "media"},
+ api_version=APIVersion.V1BETA,
+ headers={"Accept": "application/zip"},
+ )
+
+
+def test_export_integration_items_error(chronicle_client):
+ """Test export_integration_items propagates APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.integrations.chronicle_request_bytes",
+ side_effect=APIError("Failed to export integration items"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ export_integration_items(chronicle_client, "test-integration")
+
+ assert "Failed to export integration items" in str(exc_info.value)
+
+
+# -- get_integration_affected_items tests --
+
+
+def test_get_integration_affected_items_success(chronicle_client):
+ """Test get_integration_affected_items delegates to chronicle_request."""
+ expected = {"affectedItems": []}
+
+ with patch(
+ "secops.chronicle.integration.integrations.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = get_integration_affected_items(chronicle_client, "test-integration")
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="GET",
+ endpoint_path="integrations/test-integration:fetchAffectedItems",
+ api_version=APIVersion.V1BETA,
+ )
+
+
+def test_get_integration_affected_items_error(chronicle_client):
+ """Test get_integration_affected_items raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.integrations.chronicle_request",
+ side_effect=APIError("Failed to fetch affected items"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ get_integration_affected_items(chronicle_client, "test-integration")
+
+ assert "Failed to fetch affected items" in str(exc_info.value)
+
+
+# -- get_agent_integrations tests --
+
+
+def test_get_agent_integrations_success(chronicle_client):
+ """Test get_agent_integrations passes agentId parameter."""
+ expected = {"integrations": []}
+
+ with patch(
+ "secops.chronicle.integration.integrations.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = get_agent_integrations(chronicle_client, agent_id="agent-123")
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="GET",
+ endpoint_path="integrations:fetchAgentIntegrations",
+ params={"agentId": "agent-123"},
+ api_version=APIVersion.V1BETA,
+ )
+
+
+def test_get_agent_integrations_error(chronicle_client):
+ """Test get_agent_integrations raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.integrations.chronicle_request",
+ side_effect=APIError("Failed to fetch agent integrations"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ get_agent_integrations(chronicle_client, agent_id="agent-123")
+
+ assert "Failed to fetch agent integrations" in str(exc_info.value)
+
+
+# -- get_integration_dependencies tests --
+
+
+def test_get_integration_dependencies_success(chronicle_client):
+ """Test get_integration_dependencies delegates to chronicle_request."""
+ expected = {"dependencies": []}
+
+ with patch(
+ "secops.chronicle.integration.integrations.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = get_integration_dependencies(chronicle_client, "test-integration")
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="GET",
+ endpoint_path="integrations/test-integration:fetchDependencies",
+ api_version=APIVersion.V1BETA,
+ )
+
+
+def test_get_integration_dependencies_error(chronicle_client):
+ """Test get_integration_dependencies raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.integrations.chronicle_request",
+ side_effect=APIError("Failed to fetch dependencies"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ get_integration_dependencies(chronicle_client, "test-integration")
+
+ assert "Failed to fetch dependencies" in str(exc_info.value)
+
+
+# -- get_integration_restricted_agents tests --
+
+
+def test_get_integration_restricted_agents_success(chronicle_client):
+ """Test get_integration_restricted_agents passes required python version and pushRequest."""
+ expected = {"restrictedAgents": []}
+
+ with patch(
+ "secops.chronicle.integration.integrations.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = get_integration_restricted_agents(
+ chronicle_client,
+ integration_name="test-integration",
+ required_python_version=PythonVersion.PYTHON_3_11,
+ push_request=True,
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="GET",
+ endpoint_path="integrations/test-integration:fetchRestrictedAgents",
+ params={
+ "requiredPythonVersion": PythonVersion.PYTHON_3_11.value,
+ "pushRequest": True,
+ },
+ api_version=APIVersion.V1BETA,
+ )
+
+
+def test_get_integration_restricted_agents_default_push_request(chronicle_client):
+ """Test get_integration_restricted_agents default push_request=False is sent."""
+ expected = {"restrictedAgents": []}
+
+ with patch(
+ "secops.chronicle.integration.integrations.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ get_integration_restricted_agents(
+ chronicle_client,
+ integration_name="test-integration",
+ required_python_version=PythonVersion.PYTHON_3_11,
+ )
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="GET",
+ endpoint_path="integrations/test-integration:fetchRestrictedAgents",
+ params={
+ "requiredPythonVersion": PythonVersion.PYTHON_3_11.value,
+ "pushRequest": False,
+ },
+ api_version=APIVersion.V1BETA,
+ )
+
+
+def test_get_integration_restricted_agents_error(chronicle_client):
+ """Test get_integration_restricted_agents raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.integrations.chronicle_request",
+ side_effect=APIError("Failed to fetch restricted agents"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ get_integration_restricted_agents(
+ chronicle_client,
+ integration_name="test-integration",
+ required_python_version=PythonVersion.PYTHON_3_11,
+ )
+
+ assert "Failed to fetch restricted agents" in str(exc_info.value)
+
+
+# -- get_integration_diff tests --
+
+
+def test_get_integration_diff_success(chronicle_client):
+ """Test get_integration_diff builds endpoint with diff type."""
+ expected = {"diff": {}}
+
+ with patch(
+ "secops.chronicle.integration.integrations.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = get_integration_diff(
+ chronicle_client,
+ integration_name="test-integration",
+ diff_type=DiffType.PRODUCTION,
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="GET",
+ endpoint_path="integrations/test-integration:fetchProductionDiff",
+ api_version=APIVersion.V1BETA,
+ )
+
+
+def test_get_integration_diff_error(chronicle_client):
+ """Test get_integration_diff raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.integrations.chronicle_request",
+ side_effect=APIError("Failed to fetch diff"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ get_integration_diff(chronicle_client, "test-integration")
+
+ assert "Failed to fetch diff" in str(exc_info.value)
+
+
+# -- transition_integration tests --
+
+
+def test_transition_integration_success(chronicle_client):
+ """Test transition_integration posts to pushTo{TargetMode}."""
+ expected = {"name": "integrations/test"}
+
+ with patch(
+ "secops.chronicle.integration.integrations.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = transition_integration(
+ chronicle_client,
+ integration_name="test-integration",
+ target_mode=TargetMode.PRODUCTION,
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="POST",
+ endpoint_path="integrations/test-integration:pushToProduction",
+ api_version=APIVersion.V1BETA,
+ )
+
+
+def test_transition_integration_error(chronicle_client):
+ """Test transition_integration raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.integrations.chronicle_request",
+ side_effect=APIError("Failed to transition integration"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ transition_integration(
+ chronicle_client,
+ integration_name="test-integration",
+ target_mode=TargetMode.STAGING,
+ )
+
+ assert "Failed to transition integration" in str(exc_info.value)
+
+
+# -- update_integration tests --
+
+
+def test_update_integration_uses_build_patch_body_and_passes_dependencies_to_remove(
+ chronicle_client,
+):
+ """Test update_integration uses build_patch_body and adds dependenciesToRemove."""
+ body = {"displayName": "New"}
+ params = {"updateMask": "displayName"}
+
+ with patch(
+ "secops.chronicle.integration.integrations.build_patch_body",
+ return_value=(body, params),
+ ) as mock_build_patch, patch(
+ "secops.chronicle.integration.integrations.chronicle_request",
+ return_value={"name": "integrations/test"},
+ ) as mock_request:
+ result = update_integration(
+ chronicle_client,
+ integration_name="test-integration",
+ display_name="New",
+ dependencies_to_remove=["dep1", "dep2"],
+ update_mask="displayName",
+ )
+
+ assert result == {"name": "integrations/test"}
+
+ mock_build_patch.assert_called_once()
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="PATCH",
+ endpoint_path="integrations/test-integration",
+ json=body,
+ params={"updateMask": "displayName", "dependenciesToRemove": "dep1,dep2"},
+ api_version=APIVersion.V1BETA,
+ )
+
+
+def test_update_integration_when_build_patch_body_returns_no_params(chronicle_client):
+ """Test update_integration handles params=None from build_patch_body."""
+ body = {"description": "New"}
+
+ with patch(
+ "secops.chronicle.integration.integrations.build_patch_body",
+ return_value=(body, None),
+ ), patch(
+ "secops.chronicle.integration.integrations.chronicle_request",
+ return_value={"name": "integrations/test"},
+ ) as mock_request:
+ update_integration(
+ chronicle_client,
+ integration_name="test-integration",
+ description="New",
+ dependencies_to_remove=["dep1"],
+ )
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="PATCH",
+ endpoint_path="integrations/test-integration",
+ json=body,
+ params={"dependenciesToRemove": "dep1"},
+ api_version=APIVersion.V1BETA,
+ )
+
+
+def test_update_integration_error(chronicle_client):
+ """Test update_integration raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.integrations.build_patch_body",
+ return_value=({}, None),
+ ), patch(
+ "secops.chronicle.integration.integrations.chronicle_request",
+ side_effect=APIError("Failed to update integration"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ update_integration(chronicle_client, "test-integration")
+
+ assert "Failed to update integration" in str(exc_info.value)
+
+
+# -- update_custom_integration tests --
+
+
+def test_update_custom_integration_builds_body_and_params(chronicle_client):
+ """Test update_custom_integration builds nested integration body and updateMask param."""
+ expected = {"successful": True, "integration": {"name": "integrations/test"}}
+
+ with patch(
+ "secops.chronicle.integration.integrations.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = update_custom_integration(
+ chronicle_client,
+ integration_name="test-integration",
+ display_name="New",
+ staging=False,
+ dependencies_to_remove=["dep1"],
+ update_mask="displayName,staging",
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="POST",
+ endpoint_path="integrations/test-integration:updateCustomIntegration",
+ json={
+ "integration": {
+ "name": "test-integration",
+ "displayName": "New",
+ "staging": False,
+ },
+ "dependenciesToRemove": ["dep1"],
+ },
+ params={"updateMask": "displayName,staging"},
+ api_version=APIVersion.V1BETA,
+ )
+
+
+def test_update_custom_integration_excludes_none_fields(chronicle_client):
+ """Test update_custom_integration excludes None fields from integration object."""
+ with patch(
+ "secops.chronicle.integration.integrations.chronicle_request",
+ return_value={"successful": True},
+ ) as mock_request:
+ update_custom_integration(
+ chronicle_client,
+ integration_name="test-integration",
+ display_name=None,
+ description=None,
+ image_base64=None,
+ svg_icon=None,
+ python_version=None,
+ parameters=None,
+ categories=None,
+ integration_type=None,
+ staging=None,
+ dependencies_to_remove=None,
+ update_mask=None,
+ )
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="POST",
+ endpoint_path="integrations/test-integration:updateCustomIntegration",
+ json={"integration": {"name": "test-integration"}},
+ params=None,
+ api_version=APIVersion.V1BETA,
+ )
+
+
+def test_update_custom_integration_error(chronicle_client):
+ """Test update_custom_integration raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.integrations.chronicle_request",
+ side_effect=APIError("Failed to update custom integration"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ update_custom_integration(chronicle_client, "test-integration")
+
+ assert "Failed to update custom integration" in str(exc_info.value)
diff --git a/tests/chronicle/integration/test_job_context_properties.py b/tests/chronicle/integration/test_job_context_properties.py
new file mode 100644
index 00000000..5fdce61c
--- /dev/null
+++ b/tests/chronicle/integration/test_job_context_properties.py
@@ -0,0 +1,506 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Tests for Chronicle integration job context properties functions."""
+
+from unittest.mock import Mock, patch
+
+import pytest
+
+from secops.chronicle.client import ChronicleClient
+from secops.chronicle.models import APIVersion
+from secops.chronicle.integration.job_context_properties import (
+ list_job_context_properties,
+ get_job_context_property,
+ delete_job_context_property,
+ create_job_context_property,
+ update_job_context_property,
+ delete_all_job_context_properties,
+)
+from secops.exceptions import APIError
+
+
+@pytest.fixture
+def chronicle_client():
+ """Create a Chronicle client for testing."""
+ with patch("secops.auth.SecOpsAuth") as mock_auth:
+ mock_session = Mock()
+ mock_session.headers = {}
+ mock_auth.return_value.session = mock_session
+ return ChronicleClient(
+ customer_id="test-customer",
+ project_id="test-project",
+ default_api_version=APIVersion.V1BETA,
+ )
+
+
+# -- list_job_context_properties tests --
+
+
+def test_list_job_context_properties_success(chronicle_client):
+ """Test list_job_context_properties delegates to paginated request."""
+ expected = {
+ "contextProperties": [{"key": "prop1"}, {"key": "prop2"}],
+ "nextPageToken": "t",
+ }
+
+ with patch(
+ "secops.chronicle.integration.job_context_properties.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated, patch(
+ "secops.chronicle.integration.job_context_properties.format_resource_id",
+ return_value="My Integration",
+ ):
+ result = list_job_context_properties(
+ chronicle_client,
+ integration_name="My Integration",
+ job_id="j1",
+ page_size=10,
+ page_token="next-token",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_paginated.call_args
+ assert "jobs/j1/contextProperties" in kwargs["path"]
+ assert kwargs["items_key"] == "contextProperties"
+ assert kwargs["page_size"] == 10
+ assert kwargs["page_token"] == "next-token"
+
+
+def test_list_job_context_properties_default_args(chronicle_client):
+ """Test list_job_context_properties with default args."""
+ expected = {"contextProperties": []}
+
+ with patch(
+ "secops.chronicle.integration.job_context_properties.chronicle_paginated_request",
+ return_value=expected,
+ ):
+ result = list_job_context_properties(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ )
+
+ assert result == expected
+
+
+def test_list_job_context_properties_with_filters(chronicle_client):
+ """Test list_job_context_properties with filter and order_by."""
+ expected = {"contextProperties": [{"key": "prop1"}]}
+
+ with patch(
+ "secops.chronicle.integration.job_context_properties.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_job_context_properties(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ filter_string='key = "prop1"',
+ order_by="key",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_paginated.call_args
+ assert kwargs["extra_params"] == {
+ "filter": 'key = "prop1"',
+ "orderBy": "key",
+ }
+
+
+def test_list_job_context_properties_as_list(chronicle_client):
+ """Test list_job_context_properties returns list when as_list=True."""
+ expected = [{"key": "prop1"}, {"key": "prop2"}]
+
+ with patch(
+ "secops.chronicle.integration.job_context_properties.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_job_context_properties(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ as_list=True,
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_paginated.call_args
+ assert kwargs["as_list"] is True
+
+
+def test_list_job_context_properties_error(chronicle_client):
+ """Test list_job_context_properties raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.job_context_properties.chronicle_paginated_request",
+ side_effect=APIError("Failed to list context properties"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ list_job_context_properties(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ )
+ assert "Failed to list context properties" in str(exc_info.value)
+
+
+# -- get_job_context_property tests --
+
+
+def test_get_job_context_property_success(chronicle_client):
+ """Test get_job_context_property issues GET request."""
+ expected = {
+ "name": "contextProperties/prop1",
+ "key": "prop1",
+ "value": "test-value",
+ }
+
+ with patch(
+ "secops.chronicle.integration.job_context_properties.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = get_job_context_property(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ context_property_id="prop1",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["method"] == "GET"
+ assert "jobs/j1/contextProperties/prop1" in kwargs["endpoint_path"]
+ assert kwargs["api_version"] == APIVersion.V1BETA
+
+
+def test_get_job_context_property_error(chronicle_client):
+ """Test get_job_context_property raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.job_context_properties.chronicle_request",
+ side_effect=APIError("Failed to get context property"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ get_job_context_property(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ context_property_id="prop1",
+ )
+ assert "Failed to get context property" in str(exc_info.value)
+
+
+# -- delete_job_context_property tests --
+
+
+def test_delete_job_context_property_success(chronicle_client):
+ """Test delete_job_context_property issues DELETE request."""
+ with patch(
+ "secops.chronicle.integration.job_context_properties.chronicle_request",
+ return_value=None,
+ ) as mock_request:
+ delete_job_context_property(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ context_property_id="prop1",
+ )
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["method"] == "DELETE"
+ assert "jobs/j1/contextProperties/prop1" in kwargs["endpoint_path"]
+ assert kwargs["api_version"] == APIVersion.V1BETA
+
+
+def test_delete_job_context_property_error(chronicle_client):
+ """Test delete_job_context_property raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.job_context_properties.chronicle_request",
+ side_effect=APIError("Failed to delete context property"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ delete_job_context_property(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ context_property_id="prop1",
+ )
+ assert "Failed to delete context property" in str(exc_info.value)
+
+
+# -- create_job_context_property tests --
+
+
+def test_create_job_context_property_value_only(chronicle_client):
+ """Test create_job_context_property with value only."""
+ expected = {"name": "contextProperties/new", "value": "test-value"}
+
+ with patch(
+ "secops.chronicle.integration.job_context_properties.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = create_job_context_property(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ value="test-value",
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="POST",
+ endpoint_path=(
+ "integrations/test-integration/jobs/j1/contextProperties"
+ ),
+ api_version=APIVersion.V1BETA,
+ json={"value": "test-value"},
+ )
+
+
+def test_create_job_context_property_with_key(chronicle_client):
+ """Test create_job_context_property with key specified."""
+ expected = {"name": "contextProperties/mykey", "value": "test-value"}
+
+ with patch(
+ "secops.chronicle.integration.job_context_properties.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = create_job_context_property(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ value="test-value",
+ key="mykey",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["json"]["value"] == "test-value"
+ assert kwargs["json"]["key"] == "mykey"
+
+
+def test_create_job_context_property_error(chronicle_client):
+ """Test create_job_context_property raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.job_context_properties.chronicle_request",
+ side_effect=APIError("Failed to create context property"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ create_job_context_property(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ value="test-value",
+ )
+ assert "Failed to create context property" in str(exc_info.value)
+
+
+# -- update_job_context_property tests --
+
+
+def test_update_job_context_property_success(chronicle_client):
+ """Test update_job_context_property issues PATCH request."""
+ expected = {"name": "contextProperties/prop1", "value": "updated-value"}
+
+ with patch(
+ "secops.chronicle.integration.job_context_properties.chronicle_request",
+ return_value=expected,
+ ) as mock_request, patch(
+ "secops.chronicle.integration.job_context_properties.build_patch_body",
+ return_value=(
+ {"value": "updated-value"},
+ {"updateMask": "value"},
+ ),
+ ):
+ result = update_job_context_property(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ context_property_id="prop1",
+ value="updated-value",
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="PATCH",
+ endpoint_path=(
+ "integrations/test-integration/jobs/j1/contextProperties/prop1"
+ ),
+ api_version=APIVersion.V1BETA,
+ json={"value": "updated-value"},
+ params={"updateMask": "value"},
+ )
+
+
+def test_update_job_context_property_with_update_mask(chronicle_client):
+ """Test update_job_context_property with explicit update_mask."""
+ expected = {"name": "contextProperties/prop1", "value": "updated-value"}
+
+ with patch(
+ "secops.chronicle.integration.job_context_properties.chronicle_request",
+ return_value=expected,
+ ) as mock_request, patch(
+ "secops.chronicle.integration.job_context_properties.build_patch_body",
+ return_value=(
+ {"value": "updated-value"},
+ {"updateMask": "value"},
+ ),
+ ):
+ result = update_job_context_property(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ context_property_id="prop1",
+ value="updated-value",
+ update_mask="value",
+ )
+
+ assert result == expected
+
+
+def test_update_job_context_property_error(chronicle_client):
+ """Test update_job_context_property raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.job_context_properties.chronicle_request",
+ side_effect=APIError("Failed to update context property"),
+ ), patch(
+ "secops.chronicle.integration.job_context_properties.build_patch_body",
+ return_value=({"value": "updated"}, {"updateMask": "value"}),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ update_job_context_property(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ context_property_id="prop1",
+ value="updated",
+ )
+ assert "Failed to update context property" in str(exc_info.value)
+
+
+# -- delete_all_job_context_properties tests --
+
+
+def test_delete_all_job_context_properties_success(chronicle_client):
+ """Test delete_all_job_context_properties issues POST request."""
+ with patch(
+ "secops.chronicle.integration.job_context_properties.chronicle_request",
+ return_value=None,
+ ) as mock_request:
+ delete_all_job_context_properties(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ )
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="POST",
+ endpoint_path=(
+ "integrations/test-integration/"
+ "jobs/j1/contextProperties:clearAll"
+ ),
+ api_version=APIVersion.V1BETA,
+ json={},
+ )
+
+
+def test_delete_all_job_context_properties_with_context_id(chronicle_client):
+ """Test delete_all_job_context_properties with context_id specified."""
+ with patch(
+ "secops.chronicle.integration.job_context_properties.chronicle_request",
+ return_value=None,
+ ) as mock_request:
+ delete_all_job_context_properties(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ context_id="mycontext",
+ )
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["method"] == "POST"
+ assert "contextProperties:clearAll" in kwargs["endpoint_path"]
+ assert kwargs["json"]["contextId"] == "mycontext"
+
+
+def test_delete_all_job_context_properties_error(chronicle_client):
+ """Test delete_all_job_context_properties raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.job_context_properties.chronicle_request",
+ side_effect=APIError("Failed to delete all context properties"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ delete_all_job_context_properties(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ )
+ assert "Failed to delete all context properties" in str(
+ exc_info.value
+ )
+
+
+# -- API version tests --
+
+
+def test_list_job_context_properties_custom_api_version(chronicle_client):
+ """Test list_job_context_properties with custom API version."""
+ expected = {"contextProperties": []}
+
+ with patch(
+ "secops.chronicle.integration.job_context_properties.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_job_context_properties(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ api_version=APIVersion.V1ALPHA,
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_paginated.call_args
+ assert kwargs["api_version"] == APIVersion.V1ALPHA
+
+
+def test_get_job_context_property_custom_api_version(chronicle_client):
+ """Test get_job_context_property with custom API version."""
+ expected = {"name": "contextProperties/prop1"}
+
+ with patch(
+ "secops.chronicle.integration.job_context_properties.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = get_job_context_property(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ context_property_id="prop1",
+ api_version=APIVersion.V1ALPHA,
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["api_version"] == APIVersion.V1ALPHA
+
diff --git a/tests/chronicle/integration/test_job_instance_logs.py b/tests/chronicle/integration/test_job_instance_logs.py
new file mode 100644
index 00000000..ad456e79
--- /dev/null
+++ b/tests/chronicle/integration/test_job_instance_logs.py
@@ -0,0 +1,256 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Tests for Chronicle integration job instance logs functions."""
+
+from unittest.mock import Mock, patch
+
+import pytest
+
+from secops.chronicle.client import ChronicleClient
+from secops.chronicle.models import APIVersion
+from secops.chronicle.integration.job_instance_logs import (
+ list_job_instance_logs,
+ get_job_instance_log,
+)
+from secops.exceptions import APIError
+
+
+@pytest.fixture
+def chronicle_client():
+ """Create a Chronicle client for testing."""
+ with patch("secops.auth.SecOpsAuth") as mock_auth:
+ mock_session = Mock()
+ mock_session.headers = {}
+ mock_auth.return_value.session = mock_session
+ return ChronicleClient(
+ customer_id="test-customer",
+ project_id="test-project",
+ default_api_version=APIVersion.V1BETA,
+ )
+
+
+# -- list_job_instance_logs tests --
+
+
+def test_list_job_instance_logs_success(chronicle_client):
+ """Test list_job_instance_logs delegates to paginated request."""
+ expected = {
+ "logs": [{"name": "log1"}, {"name": "log2"}],
+ "nextPageToken": "t",
+ }
+
+ with patch(
+ "secops.chronicle.integration.job_instance_logs.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated, patch(
+ "secops.chronicle.integration.job_instance_logs.format_resource_id",
+ return_value="My Integration",
+ ):
+ result = list_job_instance_logs(
+ chronicle_client,
+ integration_name="My Integration",
+ job_id="j1",
+ job_instance_id="ji1",
+ page_size=10,
+ page_token="next-token",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_paginated.call_args
+ assert "jobs/j1/jobInstances/ji1/logs" in kwargs["path"]
+ assert kwargs["items_key"] == "logs"
+ assert kwargs["page_size"] == 10
+ assert kwargs["page_token"] == "next-token"
+
+
+def test_list_job_instance_logs_default_args(chronicle_client):
+ """Test list_job_instance_logs with default args."""
+ expected = {"logs": []}
+
+ with patch(
+ "secops.chronicle.integration.job_instance_logs.chronicle_paginated_request",
+ return_value=expected,
+ ):
+ result = list_job_instance_logs(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ job_instance_id="ji1",
+ )
+
+ assert result == expected
+
+
+def test_list_job_instance_logs_with_filters(chronicle_client):
+ """Test list_job_instance_logs with filter and order_by."""
+ expected = {"logs": [{"name": "log1"}]}
+
+ with patch(
+ "secops.chronicle.integration.job_instance_logs.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_job_instance_logs(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ job_instance_id="ji1",
+ filter_string="status = SUCCESS",
+ order_by="startTime desc",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_paginated.call_args
+ assert kwargs["extra_params"] == {
+ "filter": "status = SUCCESS",
+ "orderBy": "startTime desc",
+ }
+
+
+def test_list_job_instance_logs_as_list(chronicle_client):
+ """Test list_job_instance_logs returns list when as_list=True."""
+ expected = [{"name": "log1"}, {"name": "log2"}]
+
+ with patch(
+ "secops.chronicle.integration.job_instance_logs.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_job_instance_logs(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ job_instance_id="ji1",
+ as_list=True,
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_paginated.call_args
+ assert kwargs["as_list"] is True
+
+
+def test_list_job_instance_logs_error(chronicle_client):
+ """Test list_job_instance_logs raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.job_instance_logs.chronicle_paginated_request",
+ side_effect=APIError("Failed to list job instance logs"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ list_job_instance_logs(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ job_instance_id="ji1",
+ )
+ assert "Failed to list job instance logs" in str(exc_info.value)
+
+
+# -- get_job_instance_log tests --
+
+
+def test_get_job_instance_log_success(chronicle_client):
+ """Test get_job_instance_log issues GET request."""
+ expected = {
+ "name": "logs/log1",
+ "status": "SUCCESS",
+ "startTime": "2026-03-08T10:00:00Z",
+ "endTime": "2026-03-08T10:05:00Z",
+ }
+
+ with patch(
+ "secops.chronicle.integration.job_instance_logs.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = get_job_instance_log(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ job_instance_id="ji1",
+ log_id="log1",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["method"] == "GET"
+ assert "jobs/j1/jobInstances/ji1/logs/log1" in kwargs["endpoint_path"]
+ assert kwargs["api_version"] == APIVersion.V1BETA
+
+
+def test_get_job_instance_log_error(chronicle_client):
+ """Test get_job_instance_log raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.job_instance_logs.chronicle_request",
+ side_effect=APIError("Failed to get job instance log"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ get_job_instance_log(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ job_instance_id="ji1",
+ log_id="log1",
+ )
+ assert "Failed to get job instance log" in str(exc_info.value)
+
+
+# -- API version tests --
+
+
+def test_list_job_instance_logs_custom_api_version(chronicle_client):
+ """Test list_job_instance_logs with custom API version."""
+ expected = {"logs": []}
+
+ with patch(
+ "secops.chronicle.integration.job_instance_logs.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_job_instance_logs(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ job_instance_id="ji1",
+ api_version=APIVersion.V1ALPHA,
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_paginated.call_args
+ assert kwargs["api_version"] == APIVersion.V1ALPHA
+
+
+def test_get_job_instance_log_custom_api_version(chronicle_client):
+ """Test get_job_instance_log with custom API version."""
+ expected = {"name": "logs/log1"}
+
+ with patch(
+ "secops.chronicle.integration.job_instance_logs.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = get_job_instance_log(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ job_instance_id="ji1",
+ log_id="log1",
+ api_version=APIVersion.V1ALPHA,
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["api_version"] == APIVersion.V1ALPHA
+
diff --git a/tests/chronicle/integration/test_job_instances.py b/tests/chronicle/integration/test_job_instances.py
new file mode 100644
index 00000000..3e8ca386
--- /dev/null
+++ b/tests/chronicle/integration/test_job_instances.py
@@ -0,0 +1,733 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Tests for Chronicle marketplace integration job instances functions."""
+
+from unittest.mock import Mock, patch
+
+import pytest
+
+from secops.chronicle.client import ChronicleClient
+from secops.chronicle.models import (
+ APIVersion,
+ IntegrationJobInstanceParameter,
+ AdvancedConfig,
+ ScheduleType,
+ DailyScheduleDetails,
+ Date,
+ TimeOfDay,
+)
+from secops.chronicle.integration.job_instances import (
+ list_integration_job_instances,
+ get_integration_job_instance,
+ delete_integration_job_instance,
+ create_integration_job_instance,
+ update_integration_job_instance,
+ run_integration_job_instance_on_demand,
+)
+from secops.exceptions import APIError
+
+
+@pytest.fixture
+def chronicle_client():
+ """Create a Chronicle client for testing."""
+ with patch("secops.auth.SecOpsAuth") as mock_auth:
+ mock_session = Mock()
+ mock_session.headers = {}
+ mock_auth.return_value.session = mock_session
+ return ChronicleClient(
+ customer_id="test-customer",
+ project_id="test-project",
+ default_api_version=APIVersion.V1BETA,
+ )
+
+
+# -- list_integration_job_instances tests --
+
+
+def test_list_integration_job_instances_success(chronicle_client):
+ """Test list_integration_job_instances delegates to chronicle_paginated_request."""
+ expected = {
+ "jobInstances": [{"name": "ji1"}, {"name": "ji2"}],
+ "nextPageToken": "t",
+ }
+
+ with patch(
+ "secops.chronicle.integration.job_instances.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated, patch(
+ "secops.chronicle.integration.job_instances.format_resource_id",
+ return_value="My Integration",
+ ):
+ result = list_integration_job_instances(
+ chronicle_client,
+ integration_name="My Integration",
+ job_id="j1",
+ page_size=10,
+ page_token="next-token",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_paginated.call_args
+ assert "jobs/j1/jobInstances" in kwargs["path"]
+ assert kwargs["items_key"] == "jobInstances"
+ assert kwargs["page_size"] == 10
+ assert kwargs["page_token"] == "next-token"
+
+
+def test_list_integration_job_instances_default_args(chronicle_client):
+ """Test list_integration_job_instances with default args."""
+ expected = {"jobInstances": []}
+
+ with patch(
+ "secops.chronicle.integration.job_instances.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_integration_job_instances(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ )
+
+ assert result == expected
+
+
+def test_list_integration_job_instances_with_filters(chronicle_client):
+ """Test list_integration_job_instances with filter and order_by."""
+ expected = {"jobInstances": [{"name": "ji1"}]}
+
+ with patch(
+ "secops.chronicle.integration.job_instances.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_integration_job_instances(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ filter_string="enabled = true",
+ order_by="displayName",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_paginated.call_args
+ assert kwargs["extra_params"] == {
+ "filter": "enabled = true",
+ "orderBy": "displayName",
+ }
+
+
+def test_list_integration_job_instances_as_list(chronicle_client):
+ """Test list_integration_job_instances returns list when as_list=True."""
+ expected = [{"name": "ji1"}, {"name": "ji2"}]
+
+ with patch(
+ "secops.chronicle.integration.job_instances.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_integration_job_instances(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ as_list=True,
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_paginated.call_args
+ assert kwargs["as_list"] is True
+
+
+def test_list_integration_job_instances_error(chronicle_client):
+ """Test list_integration_job_instances raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.job_instances.chronicle_paginated_request",
+ side_effect=APIError("Failed to list job instances"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ list_integration_job_instances(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ )
+ assert "Failed to list job instances" in str(exc_info.value)
+
+
+# -- get_integration_job_instance tests --
+
+
+def test_get_integration_job_instance_success(chronicle_client):
+ """Test get_integration_job_instance issues GET request."""
+ expected = {
+ "name": "jobInstances/ji1",
+ "displayName": "My Job Instance",
+ "intervalSeconds": 300,
+ "enabled": True,
+ }
+
+ with patch(
+ "secops.chronicle.integration.job_instances.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = get_integration_job_instance(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ job_instance_id="ji1",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["method"] == "GET"
+ assert "jobs/j1/jobInstances/ji1" in kwargs["endpoint_path"]
+ assert kwargs["api_version"] == APIVersion.V1BETA
+
+
+def test_get_integration_job_instance_error(chronicle_client):
+ """Test get_integration_job_instance raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.job_instances.chronicle_request",
+ side_effect=APIError("Failed to get job instance"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ get_integration_job_instance(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ job_instance_id="ji1",
+ )
+ assert "Failed to get job instance" in str(exc_info.value)
+
+
+# -- delete_integration_job_instance tests --
+
+
+def test_delete_integration_job_instance_success(chronicle_client):
+ """Test delete_integration_job_instance issues DELETE request."""
+ with patch(
+ "secops.chronicle.integration.job_instances.chronicle_request",
+ return_value=None,
+ ) as mock_request:
+ delete_integration_job_instance(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ job_instance_id="ji1",
+ )
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["method"] == "DELETE"
+ assert "jobs/j1/jobInstances/ji1" in kwargs["endpoint_path"]
+ assert kwargs["api_version"] == APIVersion.V1BETA
+
+
+def test_delete_integration_job_instance_error(chronicle_client):
+ """Test delete_integration_job_instance raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.job_instances.chronicle_request",
+ side_effect=APIError("Failed to delete job instance"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ delete_integration_job_instance(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ job_instance_id="ji1",
+ )
+ assert "Failed to delete job instance" in str(exc_info.value)
+
+
+# -- create_integration_job_instance tests --
+
+
+def test_create_integration_job_instance_required_fields_only(chronicle_client):
+ """Test create_integration_job_instance sends only required fields."""
+ expected = {"name": "jobInstances/new", "displayName": "My Job Instance"}
+
+ with patch(
+ "secops.chronicle.integration.job_instances.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = create_integration_job_instance(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ display_name="My Job Instance",
+ interval_seconds=300,
+ enabled=True,
+ advanced=False,
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="POST",
+ endpoint_path="integrations/test-integration/jobs/j1/jobInstances",
+ api_version=APIVersion.V1BETA,
+ json={
+ "displayName": "My Job Instance",
+ "intervalSeconds": 300,
+ "enabled": True,
+ "advanced": False,
+ },
+ )
+
+
+def test_create_integration_job_instance_with_optional_fields(chronicle_client):
+ """Test create_integration_job_instance includes optional fields when provided."""
+ expected = {"name": "jobInstances/new"}
+
+ with patch(
+ "secops.chronicle.integration.job_instances.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = create_integration_job_instance(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ display_name="My Job Instance",
+ interval_seconds=300,
+ enabled=True,
+ advanced=False,
+ description="Test job instance",
+ parameters=[{"id": 1, "value": "test"}],
+ agent="agent-123",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["json"]["description"] == "Test job instance"
+ assert kwargs["json"]["parameters"] == [{"id": 1, "value": "test"}]
+ assert kwargs["json"]["agent"] == "agent-123"
+
+
+def test_create_integration_job_instance_with_dataclass_params(chronicle_client):
+ """Test create_integration_job_instance converts dataclass parameters."""
+ expected = {"name": "jobInstances/new"}
+
+ param = IntegrationJobInstanceParameter(value="test-value")
+
+ with patch(
+ "secops.chronicle.integration.job_instances.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = create_integration_job_instance(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ display_name="My Job Instance",
+ interval_seconds=300,
+ enabled=True,
+ advanced=False,
+ parameters=[param],
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ params_sent = kwargs["json"]["parameters"]
+ assert len(params_sent) == 1
+ assert params_sent[0]["value"] == "test-value"
+
+
+def test_create_integration_job_instance_with_advanced_config(chronicle_client):
+ """Test create_integration_job_instance with AdvancedConfig dataclass."""
+ expected = {"name": "jobInstances/new"}
+
+ advanced_config = AdvancedConfig(
+ time_zone="America/New_York",
+ schedule_type=ScheduleType.DAILY,
+ daily_schedule=DailyScheduleDetails(
+ start_date=Date(year=2026, month=3, day=8),
+ time=TimeOfDay(hours=2, minutes=0),
+ interval=1
+ )
+ )
+
+ with patch(
+ "secops.chronicle.integration.job_instances.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = create_integration_job_instance(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ display_name="My Job Instance",
+ interval_seconds=300,
+ enabled=True,
+ advanced=True,
+ advanced_config=advanced_config,
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ config_sent = kwargs["json"]["advancedConfig"]
+ assert config_sent["timeZone"] == "America/New_York"
+ assert config_sent["scheduleType"] == "DAILY"
+ assert "dailySchedule" in config_sent
+
+
+def test_create_integration_job_instance_error(chronicle_client):
+ """Test create_integration_job_instance raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.job_instances.chronicle_request",
+ side_effect=APIError("Failed to create job instance"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ create_integration_job_instance(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ display_name="My Job Instance",
+ interval_seconds=300,
+ enabled=True,
+ advanced=False,
+ )
+ assert "Failed to create job instance" in str(exc_info.value)
+
+
+# -- update_integration_job_instance tests --
+
+
+def test_update_integration_job_instance_single_field(chronicle_client):
+ """Test update_integration_job_instance updates a single field."""
+ expected = {"name": "jobInstances/ji1", "displayName": "Updated Instance"}
+
+ with patch(
+ "secops.chronicle.integration.job_instances.chronicle_request",
+ return_value=expected,
+ ) as mock_request, patch(
+ "secops.chronicle.integration.job_instances.build_patch_body",
+ return_value=(
+ {"displayName": "Updated Instance"},
+ {"updateMask": "displayName"},
+ ),
+ ) as mock_build_patch:
+ result = update_integration_job_instance(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ job_instance_id="ji1",
+ display_name="Updated Instance",
+ )
+
+ assert result == expected
+
+ mock_build_patch.assert_called_once()
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="PATCH",
+ endpoint_path=(
+ "integrations/test-integration/jobs/j1/jobInstances/ji1"
+ ),
+ api_version=APIVersion.V1BETA,
+ json={"displayName": "Updated Instance"},
+ params={"updateMask": "displayName"},
+ )
+
+
+def test_update_integration_job_instance_multiple_fields(chronicle_client):
+ """Test update_integration_job_instance updates multiple fields."""
+ expected = {"name": "jobInstances/ji1"}
+
+ with patch(
+ "secops.chronicle.integration.job_instances.chronicle_request",
+ return_value=expected,
+ ) as mock_request, patch(
+ "secops.chronicle.integration.job_instances.build_patch_body",
+ return_value=(
+ {
+ "displayName": "Updated",
+ "intervalSeconds": 600,
+ "enabled": False,
+ },
+ {"updateMask": "displayName,intervalSeconds,enabled"},
+ ),
+ ):
+ result = update_integration_job_instance(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ job_instance_id="ji1",
+ display_name="Updated",
+ interval_seconds=600,
+ enabled=False,
+ )
+
+ assert result == expected
+
+
+def test_update_integration_job_instance_with_dataclass_params(chronicle_client):
+ """Test update_integration_job_instance converts dataclass parameters."""
+ expected = {"name": "jobInstances/ji1"}
+
+ param = IntegrationJobInstanceParameter(value="updated-value")
+
+ with patch(
+ "secops.chronicle.integration.job_instances.chronicle_request",
+ return_value=expected,
+ ) as mock_request, patch(
+ "secops.chronicle.integration.job_instances.build_patch_body",
+ return_value=(
+ {"parameters": [{"value": "updated-value"}]},
+ {"updateMask": "parameters"},
+ ),
+ ):
+ result = update_integration_job_instance(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ job_instance_id="ji1",
+ parameters=[param],
+ )
+
+ assert result == expected
+
+
+def test_update_integration_job_instance_with_advanced_config(chronicle_client):
+ """Test update_integration_job_instance with AdvancedConfig dataclass."""
+ expected = {"name": "jobInstances/ji1"}
+
+ advanced_config = AdvancedConfig(
+ time_zone="UTC",
+ schedule_type=ScheduleType.DAILY,
+ daily_schedule=DailyScheduleDetails(
+ start_date=Date(year=2026, month=3, day=8),
+ time=TimeOfDay(hours=0, minutes=0),
+ interval=1
+ )
+ )
+
+ with patch(
+ "secops.chronicle.integration.job_instances.chronicle_request",
+ return_value=expected,
+ ) as mock_request, patch(
+ "secops.chronicle.integration.job_instances.build_patch_body",
+ return_value=(
+ {
+ "advancedConfig": {
+ "timeZone": "UTC",
+ "scheduleType": "DAILY",
+ "dailySchedule": {
+ "startDate": {"year": 2026, "month": 3, "day": 8},
+ "time": {"hours": 0, "minutes": 0},
+ "interval": 1
+ }
+ }
+ },
+ {"updateMask": "advancedConfig"},
+ ),
+ ):
+ result = update_integration_job_instance(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ job_instance_id="ji1",
+ advanced_config=advanced_config,
+ )
+
+ assert result == expected
+
+
+def test_update_integration_job_instance_with_update_mask(chronicle_client):
+ """Test update_integration_job_instance respects explicit update_mask."""
+ expected = {"name": "jobInstances/ji1"}
+
+ with patch(
+ "secops.chronicle.integration.job_instances.chronicle_request",
+ return_value=expected,
+ ) as mock_request, patch(
+ "secops.chronicle.integration.job_instances.build_patch_body",
+ return_value=(
+ {"displayName": "Updated"},
+ {"updateMask": "displayName"},
+ ),
+ ):
+ result = update_integration_job_instance(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ job_instance_id="ji1",
+ display_name="Updated",
+ update_mask="displayName",
+ )
+
+ assert result == expected
+
+
+def test_update_integration_job_instance_error(chronicle_client):
+ """Test update_integration_job_instance raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.job_instances.chronicle_request",
+ side_effect=APIError("Failed to update job instance"),
+ ), patch(
+ "secops.chronicle.integration.job_instances.build_patch_body",
+ return_value=({"enabled": False}, {"updateMask": "enabled"}),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ update_integration_job_instance(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ job_instance_id="ji1",
+ enabled=False,
+ )
+ assert "Failed to update job instance" in str(exc_info.value)
+
+
+# -- run_integration_job_instance_on_demand tests --
+
+
+def test_run_integration_job_instance_on_demand_success(chronicle_client):
+ """Test run_integration_job_instance_on_demand issues POST request."""
+ expected = {"success": True}
+
+ with patch(
+ "secops.chronicle.integration.job_instances.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = run_integration_job_instance_on_demand(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ job_instance_id="ji1",
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="POST",
+ endpoint_path=(
+ "integrations/test-integration/jobs/j1/jobInstances/ji1:runOnDemand"
+ ),
+ api_version=APIVersion.V1BETA,
+ json={},
+ )
+
+
+def test_run_integration_job_instance_on_demand_with_params(chronicle_client):
+ """Test run_integration_job_instance_on_demand with parameters."""
+ expected = {"success": True}
+
+ with patch(
+ "secops.chronicle.integration.job_instances.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = run_integration_job_instance_on_demand(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ job_instance_id="ji1",
+ parameters=[{"id": 1, "value": "override"}],
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["json"]["parameters"] == [{"id": 1, "value": "override"}]
+
+
+def test_run_integration_job_instance_on_demand_with_dataclass(chronicle_client):
+ """Test run_integration_job_instance_on_demand converts dataclass parameters."""
+ expected = {"success": True}
+
+ param = IntegrationJobInstanceParameter(value="test")
+
+ with patch(
+ "secops.chronicle.integration.job_instances.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = run_integration_job_instance_on_demand(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ job_instance_id="ji1",
+ parameters=[param],
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ params_sent = kwargs["json"]["parameters"]
+ assert len(params_sent) == 1
+ assert params_sent[0]["value"] == "test"
+
+
+def test_run_integration_job_instance_on_demand_error(chronicle_client):
+ """Test run_integration_job_instance_on_demand raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.job_instances.chronicle_request",
+ side_effect=APIError("Failed to run job instance on demand"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ run_integration_job_instance_on_demand(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ job_instance_id="ji1",
+ )
+ assert "Failed to run job instance on demand" in str(exc_info.value)
+
+
+# -- API version tests --
+
+
+def test_list_integration_job_instances_custom_api_version(chronicle_client):
+ """Test list_integration_job_instances with custom API version."""
+ expected = {"jobInstances": []}
+
+ with patch(
+ "secops.chronicle.integration.job_instances.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_integration_job_instances(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ api_version=APIVersion.V1ALPHA,
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_paginated.call_args
+ assert kwargs["api_version"] == APIVersion.V1ALPHA
+
+
+def test_get_integration_job_instance_custom_api_version(chronicle_client):
+ """Test get_integration_job_instance with custom API version."""
+ expected = {"name": "jobInstances/ji1"}
+
+ with patch(
+ "secops.chronicle.integration.job_instances.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = get_integration_job_instance(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ job_instance_id="ji1",
+ api_version=APIVersion.V1ALPHA,
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["api_version"] == APIVersion.V1ALPHA
+
diff --git a/tests/chronicle/integration/test_job_revisions.py b/tests/chronicle/integration/test_job_revisions.py
new file mode 100644
index 00000000..3a81682c
--- /dev/null
+++ b/tests/chronicle/integration/test_job_revisions.py
@@ -0,0 +1,378 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Tests for Chronicle marketplace integration job revisions functions."""
+
+from unittest.mock import Mock, patch
+
+import pytest
+
+from secops.chronicle.client import ChronicleClient
+from secops.chronicle.models import APIVersion
+from secops.chronicle.integration.job_revisions import (
+ list_integration_job_revisions,
+ delete_integration_job_revision,
+ create_integration_job_revision,
+ rollback_integration_job_revision,
+)
+from secops.exceptions import APIError
+
+
+@pytest.fixture
+def chronicle_client():
+ """Create a Chronicle client for testing."""
+ with patch("secops.auth.SecOpsAuth") as mock_auth:
+ mock_session = Mock()
+ mock_session.headers = {}
+ mock_auth.return_value.session = mock_session
+ return ChronicleClient(
+ customer_id="test-customer",
+ project_id="test-project",
+ default_api_version=APIVersion.V1BETA,
+ )
+
+
+# -- list_integration_job_revisions tests --
+
+
+def test_list_integration_job_revisions_success(chronicle_client):
+ """Test list_integration_job_revisions delegates to chronicle_paginated_request."""
+ expected = {
+ "revisions": [{"name": "r1"}, {"name": "r2"}],
+ "nextPageToken": "t",
+ }
+
+ with patch(
+ "secops.chronicle.integration.job_revisions.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated, patch(
+ "secops.chronicle.integration.job_revisions.format_resource_id",
+ return_value="My Integration",
+ ):
+ result = list_integration_job_revisions(
+ chronicle_client,
+ integration_name="My Integration",
+ job_id="j1",
+ page_size=10,
+ page_token="next-token",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_paginated.call_args
+ assert "jobs/j1/revisions" in kwargs["path"]
+ assert kwargs["items_key"] == "revisions"
+ assert kwargs["page_size"] == 10
+ assert kwargs["page_token"] == "next-token"
+
+
+def test_list_integration_job_revisions_default_args(chronicle_client):
+ """Test list_integration_job_revisions with default args."""
+ expected = {"revisions": []}
+
+ with patch(
+ "secops.chronicle.integration.job_revisions.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_integration_job_revisions(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ )
+
+ assert result == expected
+
+
+def test_list_integration_job_revisions_with_filters(chronicle_client):
+ """Test list_integration_job_revisions with filter and order_by."""
+ expected = {"revisions": [{"name": "r1"}]}
+
+ with patch(
+ "secops.chronicle.integration.job_revisions.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_integration_job_revisions(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ filter_string='version = "1.0"',
+ order_by="createTime",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_paginated.call_args
+ assert kwargs["extra_params"] == {
+ "filter": 'version = "1.0"',
+ "orderBy": "createTime",
+ }
+
+
+def test_list_integration_job_revisions_as_list(chronicle_client):
+ """Test list_integration_job_revisions returns list when as_list=True."""
+ expected = [{"name": "r1"}, {"name": "r2"}]
+
+ with patch(
+ "secops.chronicle.integration.job_revisions.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_integration_job_revisions(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ as_list=True,
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_paginated.call_args
+ assert kwargs["as_list"] is True
+
+
+def test_list_integration_job_revisions_error(chronicle_client):
+ """Test list_integration_job_revisions raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.job_revisions.chronicle_paginated_request",
+ side_effect=APIError("Failed to list job revisions"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ list_integration_job_revisions(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ )
+ assert "Failed to list job revisions" in str(exc_info.value)
+
+
+# -- delete_integration_job_revision tests --
+
+
+def test_delete_integration_job_revision_success(chronicle_client):
+ """Test delete_integration_job_revision issues DELETE request."""
+ with patch(
+ "secops.chronicle.integration.job_revisions.chronicle_request",
+ return_value=None,
+ ) as mock_request:
+ delete_integration_job_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ revision_id="r1",
+ )
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["method"] == "DELETE"
+ assert "jobs/j1/revisions/r1" in kwargs["endpoint_path"]
+ assert kwargs["api_version"] == APIVersion.V1BETA
+
+
+def test_delete_integration_job_revision_error(chronicle_client):
+ """Test delete_integration_job_revision raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.job_revisions.chronicle_request",
+ side_effect=APIError("Failed to delete job revision"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ delete_integration_job_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ revision_id="r1",
+ )
+ assert "Failed to delete job revision" in str(exc_info.value)
+
+
+# -- create_integration_job_revision tests --
+
+
+def test_create_integration_job_revision_required_fields_only(
+ chronicle_client,
+):
+ """Test create_integration_job_revision with required fields only."""
+ expected = {"name": "revisions/new", "job": {"displayName": "My Job"}}
+ job_dict = {
+ "displayName": "My Job",
+ "script": "print('hello')",
+ "version": 1,
+ "enabled": True,
+ "custom": True,
+ }
+
+ with patch(
+ "secops.chronicle.integration.job_revisions.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = create_integration_job_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ job=job_dict,
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="POST",
+ endpoint_path=(
+ "integrations/test-integration/jobs/j1/revisions"
+ ),
+ api_version=APIVersion.V1BETA,
+ json={"job": job_dict},
+ )
+
+
+def test_create_integration_job_revision_with_comment(chronicle_client):
+ """Test create_integration_job_revision includes comment when provided."""
+ expected = {"name": "revisions/new"}
+ job_dict = {
+ "displayName": "My Job",
+ "script": "print('hello')",
+ "version": 1,
+ "enabled": True,
+ "custom": True,
+ }
+
+ with patch(
+ "secops.chronicle.integration.job_revisions.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = create_integration_job_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ job=job_dict,
+ comment="Backup before major update",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["json"]["comment"] == "Backup before major update"
+ assert kwargs["json"]["job"] == job_dict
+
+
+def test_create_integration_job_revision_error(chronicle_client):
+ """Test create_integration_job_revision raises APIError on failure."""
+ job_dict = {
+ "displayName": "My Job",
+ "script": "print('hello')",
+ "version": 1,
+ "enabled": True,
+ "custom": True,
+ }
+
+ with patch(
+ "secops.chronicle.integration.job_revisions.chronicle_request",
+ side_effect=APIError("Failed to create job revision"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ create_integration_job_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ job=job_dict,
+ )
+ assert "Failed to create job revision" in str(exc_info.value)
+
+
+# -- rollback_integration_job_revision tests --
+
+
+def test_rollback_integration_job_revision_success(chronicle_client):
+ """Test rollback_integration_job_revision issues POST request."""
+ expected = {
+ "name": "revisions/r1",
+ "job": {
+ "displayName": "My Job",
+ "script": "print('hello')",
+ },
+ }
+
+ with patch(
+ "secops.chronicle.integration.job_revisions.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = rollback_integration_job_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ revision_id="r1",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["method"] == "POST"
+ assert "jobs/j1/revisions/r1:rollback" in kwargs["endpoint_path"]
+ assert kwargs["api_version"] == APIVersion.V1BETA
+
+
+def test_rollback_integration_job_revision_error(chronicle_client):
+ """Test rollback_integration_job_revision raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.job_revisions.chronicle_request",
+ side_effect=APIError("Failed to rollback job revision"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ rollback_integration_job_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ revision_id="r1",
+ )
+ assert "Failed to rollback job revision" in str(exc_info.value)
+
+
+# -- API version tests --
+
+
+def test_list_integration_job_revisions_custom_api_version(chronicle_client):
+ """Test list_integration_job_revisions with custom API version."""
+ expected = {"revisions": []}
+
+ with patch(
+ "secops.chronicle.integration.job_revisions.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_integration_job_revisions(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ api_version=APIVersion.V1ALPHA,
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_paginated.call_args
+ assert kwargs["api_version"] == APIVersion.V1ALPHA
+
+
+def test_delete_integration_job_revision_custom_api_version(chronicle_client):
+ """Test delete_integration_job_revision with custom API version."""
+ with patch(
+ "secops.chronicle.integration.job_revisions.chronicle_request",
+ return_value=None,
+ ) as mock_request:
+ delete_integration_job_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ revision_id="r1",
+ api_version=APIVersion.V1ALPHA,
+ )
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["api_version"] == APIVersion.V1ALPHA
+
diff --git a/tests/chronicle/integration/test_jobs.py b/tests/chronicle/integration/test_jobs.py
new file mode 100644
index 00000000..a318a890
--- /dev/null
+++ b/tests/chronicle/integration/test_jobs.py
@@ -0,0 +1,594 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Tests for Chronicle marketplace integration jobs functions."""
+
+from unittest.mock import Mock, patch
+
+import pytest
+
+from secops.chronicle.client import ChronicleClient
+from secops.chronicle.models import APIVersion, JobParameter, ParamType
+from secops.chronicle.integration.jobs import (
+ list_integration_jobs,
+ get_integration_job,
+ delete_integration_job,
+ create_integration_job,
+ update_integration_job,
+ execute_integration_job_test,
+ get_integration_job_template,
+)
+from secops.exceptions import APIError
+
+
+@pytest.fixture
+def chronicle_client():
+ """Create a Chronicle client for testing."""
+ with patch("secops.auth.SecOpsAuth") as mock_auth:
+ mock_session = Mock()
+ mock_session.headers = {}
+ mock_auth.return_value.session = mock_session
+ return ChronicleClient(
+ customer_id="test-customer",
+ project_id="test-project",
+ default_api_version=APIVersion.V1BETA,
+ )
+
+
+# -- list_integration_jobs tests --
+
+
+def test_list_integration_jobs_success(chronicle_client):
+ """Test list_integration_jobs delegates to chronicle_paginated_request."""
+ expected = {"jobs": [{"name": "j1"}, {"name": "j2"}], "nextPageToken": "t"}
+
+ with patch(
+ "secops.chronicle.integration.jobs.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated, patch(
+ "secops.chronicle.integration.jobs.format_resource_id",
+ return_value="My Integration",
+ ):
+ result = list_integration_jobs(
+ chronicle_client,
+ integration_name="My Integration",
+ page_size=10,
+ page_token="next-token",
+ )
+
+ assert result == expected
+
+ mock_paginated.assert_called_once_with(
+ chronicle_client,
+ api_version=APIVersion.V1BETA,
+ path="integrations/My Integration/jobs",
+ items_key="jobs",
+ page_size=10,
+ page_token="next-token",
+ extra_params={},
+ as_list=False,
+ )
+
+
+def test_list_integration_jobs_default_args(chronicle_client):
+ """Test list_integration_jobs with default args."""
+ expected = {"jobs": []}
+
+ with patch(
+ "secops.chronicle.integration.jobs.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_integration_jobs(
+ chronicle_client,
+ integration_name="test-integration",
+ )
+
+ assert result == expected
+
+
+def test_list_integration_jobs_with_filters(chronicle_client):
+ """Test list_integration_jobs with filter and order_by."""
+ expected = {"jobs": [{"name": "j1"}]}
+
+ with patch(
+ "secops.chronicle.integration.jobs.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_integration_jobs(
+ chronicle_client,
+ integration_name="test-integration",
+ filter_string="custom=true",
+ order_by="displayName",
+ exclude_staging=True,
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_paginated.call_args
+ assert kwargs["extra_params"] == {
+ "filter": "custom=true",
+ "orderBy": "displayName",
+ "excludeStaging": True,
+ }
+
+
+def test_list_integration_jobs_as_list(chronicle_client):
+ """Test list_integration_jobs returns list when as_list=True."""
+ expected = [{"name": "j1"}, {"name": "j2"}]
+
+ with patch(
+ "secops.chronicle.integration.jobs.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_integration_jobs(
+ chronicle_client,
+ integration_name="test-integration",
+ as_list=True,
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_paginated.call_args
+ assert kwargs["as_list"] is True
+
+
+def test_list_integration_jobs_error(chronicle_client):
+ """Test list_integration_jobs raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.jobs.chronicle_paginated_request",
+ side_effect=APIError("Failed to list integration jobs"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ list_integration_jobs(
+ chronicle_client,
+ integration_name="test-integration",
+ )
+ assert "Failed to list integration jobs" in str(exc_info.value)
+
+
+# -- get_integration_job tests --
+
+
+def test_get_integration_job_success(chronicle_client):
+ """Test get_integration_job issues GET request."""
+ expected = {
+ "name": "jobs/j1",
+ "displayName": "My Job",
+ "script": "print('hello')",
+ }
+
+ with patch(
+ "secops.chronicle.integration.jobs.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = get_integration_job(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="GET",
+ endpoint_path="integrations/test-integration/jobs/j1",
+ api_version=APIVersion.V1BETA,
+ )
+
+
+def test_get_integration_job_error(chronicle_client):
+ """Test get_integration_job raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.jobs.chronicle_request",
+ side_effect=APIError("Failed to get integration job"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ get_integration_job(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ )
+ assert "Failed to get integration job" in str(exc_info.value)
+
+
+# -- delete_integration_job tests --
+
+
+def test_delete_integration_job_success(chronicle_client):
+ """Test delete_integration_job issues DELETE request."""
+ with patch(
+ "secops.chronicle.integration.jobs.chronicle_request",
+ return_value=None,
+ ) as mock_request:
+ delete_integration_job(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ )
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="DELETE",
+ endpoint_path="integrations/test-integration/jobs/j1",
+ api_version=APIVersion.V1BETA,
+ )
+
+
+def test_delete_integration_job_error(chronicle_client):
+ """Test delete_integration_job raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.jobs.chronicle_request",
+ side_effect=APIError("Failed to delete integration job"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ delete_integration_job(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ )
+ assert "Failed to delete integration job" in str(exc_info.value)
+
+
+# -- create_integration_job tests --
+
+
+def test_create_integration_job_required_fields_only(chronicle_client):
+ """Test create_integration_job sends only required fields when optionals omitted."""
+ expected = {"name": "jobs/new", "displayName": "My Job"}
+
+ with patch(
+ "secops.chronicle.integration.jobs.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = create_integration_job(
+ chronicle_client,
+ integration_name="test-integration",
+ display_name="My Job",
+ script="print('hi')",
+ version=1,
+ enabled=True,
+ custom=True,
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="POST",
+ endpoint_path="integrations/test-integration/jobs",
+ api_version=APIVersion.V1BETA,
+ json={
+ "displayName": "My Job",
+ "script": "print('hi')",
+ "version": 1,
+ "enabled": True,
+ "custom": True,
+ },
+ )
+
+
+def test_create_integration_job_with_optional_fields(chronicle_client):
+ """Test create_integration_job includes optional fields when provided."""
+ expected = {"name": "jobs/new"}
+
+ with patch(
+ "secops.chronicle.integration.jobs.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = create_integration_job(
+ chronicle_client,
+ integration_name="test-integration",
+ display_name="My Job",
+ script="print('hi')",
+ version=1,
+ enabled=True,
+ custom=True,
+ description="Test job",
+ parameters=[{"id": 1, "displayName": "p1", "type": "STRING"}],
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["json"]["description"] == "Test job"
+ assert kwargs["json"]["parameters"] == [
+ {"id": 1, "displayName": "p1", "type": "STRING"}
+ ]
+
+
+def test_create_integration_job_with_dataclass_parameters(chronicle_client):
+ """Test create_integration_job converts JobParameter dataclasses."""
+ expected = {"name": "jobs/new"}
+
+ param = JobParameter(
+ id=1,
+ display_name="API Key",
+ description="API key for authentication",
+ type=ParamType.STRING,
+ mandatory=True,
+ )
+
+ with patch(
+ "secops.chronicle.integration.jobs.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = create_integration_job(
+ chronicle_client,
+ integration_name="test-integration",
+ display_name="My Job",
+ script="print('hi')",
+ version=1,
+ enabled=True,
+ custom=True,
+ parameters=[param],
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ params_sent = kwargs["json"]["parameters"]
+ assert len(params_sent) == 1
+ assert params_sent[0]["id"] == 1
+ assert params_sent[0]["displayName"] == "API Key"
+ assert params_sent[0]["type"] == "STRING"
+
+
+def test_create_integration_job_error(chronicle_client):
+ """Test create_integration_job raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.jobs.chronicle_request",
+ side_effect=APIError("Failed to create integration job"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ create_integration_job(
+ chronicle_client,
+ integration_name="test-integration",
+ display_name="My Job",
+ script="print('hi')",
+ version=1,
+ enabled=True,
+ custom=True,
+ )
+ assert "Failed to create integration job" in str(exc_info.value)
+
+
+# -- update_integration_job tests --
+
+
+def test_update_integration_job_with_explicit_update_mask(chronicle_client):
+ """Test update_integration_job passes through explicit update_mask."""
+ expected = {"name": "jobs/j1", "displayName": "New Name"}
+
+ with patch(
+ "secops.chronicle.integration.jobs.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = update_integration_job(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ display_name="New Name",
+ update_mask="displayName",
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="PATCH",
+ endpoint_path="integrations/test-integration/jobs/j1",
+ api_version=APIVersion.V1BETA,
+ json={"displayName": "New Name"},
+ params={"updateMask": "displayName"},
+ )
+
+
+def test_update_integration_job_auto_update_mask(chronicle_client):
+ """Test update_integration_job auto-generates updateMask based on fields."""
+ expected = {"name": "jobs/j1"}
+
+ with patch(
+ "secops.chronicle.integration.jobs.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = update_integration_job(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ enabled=False,
+ version=2,
+ )
+
+ assert result == expected
+
+ assert mock_request.call_count == 1
+ _, kwargs = mock_request.call_args
+
+ assert kwargs["method"] == "PATCH"
+ assert kwargs["endpoint_path"] == "integrations/test-integration/jobs/j1"
+ assert kwargs["api_version"] == APIVersion.V1BETA
+
+ assert kwargs["json"] == {"enabled": False, "version": 2}
+
+ update_mask = kwargs["params"]["updateMask"]
+ assert set(update_mask.split(",")) == {"enabled", "version"}
+
+
+def test_update_integration_job_with_parameters(chronicle_client):
+ """Test update_integration_job with parameters field."""
+ expected = {"name": "jobs/j1"}
+
+ param = JobParameter(
+ id=2,
+ display_name="Auth Token",
+ description="Authentication token",
+ type=ParamType.PASSWORD,
+ mandatory=True,
+ )
+
+ with patch(
+ "secops.chronicle.integration.jobs.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = update_integration_job(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ parameters=[param],
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ params_sent = kwargs["json"]["parameters"]
+ assert len(params_sent) == 1
+ assert params_sent[0]["id"] == 2
+ assert params_sent[0]["displayName"] == "Auth Token"
+
+
+def test_update_integration_job_error(chronicle_client):
+ """Test update_integration_job raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.jobs.chronicle_request",
+ side_effect=APIError("Failed to update integration job"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ update_integration_job(
+ chronicle_client,
+ integration_name="test-integration",
+ job_id="j1",
+ display_name="New Name",
+ )
+ assert "Failed to update integration job" in str(exc_info.value)
+
+
+# -- execute_integration_job_test tests --
+
+
+def test_execute_integration_job_test_success(chronicle_client):
+ """Test execute_integration_job_test sends POST request with job."""
+ expected = {
+ "output": "Success",
+ "debugOutput": "Debug info",
+ "resultObjectJson": {"status": "ok"},
+ }
+
+ job = {
+ "displayName": "Test Job",
+ "script": "print('test')",
+ "enabled": True,
+ }
+
+ with patch(
+ "secops.chronicle.integration.jobs.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = execute_integration_job_test(
+ chronicle_client,
+ integration_name="test-integration",
+ job=job,
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="POST",
+ endpoint_path="integrations/test-integration/jobs:executeTest",
+ api_version=APIVersion.V1BETA,
+ json={"job": job},
+ )
+
+
+def test_execute_integration_job_test_with_agent_identifier(chronicle_client):
+ """Test execute_integration_job_test includes agent_identifier when provided."""
+ expected = {"output": "Success"}
+
+ job = {"displayName": "Test", "script": "print('test')"}
+
+ with patch(
+ "secops.chronicle.integration.jobs.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = execute_integration_job_test(
+ chronicle_client,
+ integration_name="test-integration",
+ job=job,
+ agent_identifier="agent-123",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["json"]["agentIdentifier"] == "agent-123"
+
+
+def test_execute_integration_job_test_error(chronicle_client):
+ """Test execute_integration_job_test raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.jobs.chronicle_request",
+ side_effect=APIError("Failed to execute job test"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ execute_integration_job_test(
+ chronicle_client,
+ integration_name="test-integration",
+ job={"displayName": "Test"},
+ )
+ assert "Failed to execute job test" in str(exc_info.value)
+
+
+# -- get_integration_job_template tests --
+
+
+def test_get_integration_job_template_success(chronicle_client):
+ """Test get_integration_job_template issues GET request."""
+ expected = {
+ "script": "# Template script\nprint('hello')",
+ "displayName": "Template Job",
+ }
+
+ with patch(
+ "secops.chronicle.integration.jobs.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = get_integration_job_template(
+ chronicle_client,
+ integration_name="test-integration",
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="GET",
+ endpoint_path="integrations/test-integration/jobs:fetchTemplate",
+ api_version=APIVersion.V1BETA,
+ )
+
+
+def test_get_integration_job_template_error(chronicle_client):
+ """Test get_integration_job_template raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.jobs.chronicle_request",
+ side_effect=APIError("Failed to get job template"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ get_integration_job_template(
+ chronicle_client,
+ integration_name="test-integration",
+ )
+ assert "Failed to get job template" in str(exc_info.value)
+
diff --git a/tests/chronicle/integration/test_logical_operator_revisions.py b/tests/chronicle/integration/test_logical_operator_revisions.py
new file mode 100644
index 00000000..29e912e6
--- /dev/null
+++ b/tests/chronicle/integration/test_logical_operator_revisions.py
@@ -0,0 +1,367 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Tests for Chronicle integration logical operator revisions functions."""
+
+from unittest.mock import Mock, patch
+
+import pytest
+
+from secops.chronicle.client import ChronicleClient
+from secops.chronicle.models import APIVersion
+from secops.chronicle.integration.logical_operator_revisions import (
+ list_integration_logical_operator_revisions,
+ delete_integration_logical_operator_revision,
+ create_integration_logical_operator_revision,
+ rollback_integration_logical_operator_revision,
+)
+from secops.exceptions import APIError
+
+
+@pytest.fixture
+def chronicle_client():
+ """Create a Chronicle client for testing."""
+ with patch("secops.auth.SecOpsAuth") as mock_auth:
+ mock_session = Mock()
+ mock_session.headers = {}
+ mock_auth.return_value.session = mock_session
+ return ChronicleClient(
+ customer_id="test-customer",
+ project_id="test-project",
+ default_api_version=APIVersion.V1ALPHA,
+ )
+
+
+# -- list_integration_logical_operator_revisions tests --
+
+
+def test_list_integration_logical_operator_revisions_success(chronicle_client):
+ """Test list_integration_logical_operator_revisions delegates to paginated request."""
+ expected = {
+ "revisions": [{"name": "r1"}, {"name": "r2"}],
+ "nextPageToken": "token",
+ }
+
+ with patch(
+ "secops.chronicle.integration.logical_operator_revisions.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated, patch(
+ "secops.chronicle.integration.logical_operator_revisions.format_resource_id",
+ return_value="My Integration",
+ ):
+ result = list_integration_logical_operator_revisions(
+ chronicle_client,
+ integration_name="My Integration",
+ logical_operator_id="lo1",
+ page_size=10,
+ page_token="next-token",
+ )
+
+ assert result == expected
+
+ mock_paginated.assert_called_once_with(
+ chronicle_client,
+ api_version=APIVersion.V1ALPHA,
+ path="integrations/My Integration/logicalOperators/lo1/revisions",
+ items_key="revisions",
+ page_size=10,
+ page_token="next-token",
+ extra_params={},
+ as_list=False,
+ )
+
+
+def test_list_integration_logical_operator_revisions_default_args(chronicle_client):
+ """Test list_integration_logical_operator_revisions with default args."""
+ expected = {"revisions": []}
+
+ with patch(
+ "secops.chronicle.integration.logical_operator_revisions.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_integration_logical_operator_revisions(
+ chronicle_client,
+ integration_name="test-integration",
+ logical_operator_id="lo1",
+ )
+
+ assert result == expected
+
+ mock_paginated.assert_called_once_with(
+ chronicle_client,
+ api_version=APIVersion.V1ALPHA,
+ path="integrations/test-integration/logicalOperators/lo1/revisions",
+ items_key="revisions",
+ page_size=None,
+ page_token=None,
+ extra_params={},
+ as_list=False,
+ )
+
+
+def test_list_integration_logical_operator_revisions_with_filter_order(
+ chronicle_client,
+):
+ """Test list passes filter/orderBy in extra_params."""
+ expected = {"revisions": [{"name": "r1"}]}
+
+ with patch(
+ "secops.chronicle.integration.logical_operator_revisions.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_integration_logical_operator_revisions(
+ chronicle_client,
+ integration_name="test-integration",
+ logical_operator_id="lo1",
+ filter_string='version = "1.0"',
+ order_by="createTime desc",
+ )
+
+ assert result == expected
+
+ mock_paginated.assert_called_once_with(
+ chronicle_client,
+ api_version=APIVersion.V1ALPHA,
+ path="integrations/test-integration/logicalOperators/lo1/revisions",
+ items_key="revisions",
+ page_size=None,
+ page_token=None,
+ extra_params={
+ "filter": 'version = "1.0"',
+ "orderBy": "createTime desc",
+ },
+ as_list=False,
+ )
+
+
+def test_list_integration_logical_operator_revisions_as_list(chronicle_client):
+ """Test list_integration_logical_operator_revisions with as_list=True."""
+ expected = [{"name": "r1"}, {"name": "r2"}]
+
+ with patch(
+ "secops.chronicle.integration.logical_operator_revisions.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_integration_logical_operator_revisions(
+ chronicle_client,
+ integration_name="test-integration",
+ logical_operator_id="lo1",
+ as_list=True,
+ )
+
+ assert result == expected
+
+ mock_paginated.assert_called_once_with(
+ chronicle_client,
+ api_version=APIVersion.V1ALPHA,
+ path="integrations/test-integration/logicalOperators/lo1/revisions",
+ items_key="revisions",
+ page_size=None,
+ page_token=None,
+ extra_params={},
+ as_list=True,
+ )
+
+
+# -- delete_integration_logical_operator_revision tests --
+
+
+def test_delete_integration_logical_operator_revision_success(chronicle_client):
+ """Test delete_integration_logical_operator_revision delegates to chronicle_request."""
+ with patch(
+ "secops.chronicle.integration.logical_operator_revisions.chronicle_request",
+ return_value=None,
+ ) as mock_request, patch(
+ "secops.chronicle.integration.logical_operator_revisions.format_resource_id",
+ return_value="test-integration",
+ ):
+ delete_integration_logical_operator_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ logical_operator_id="lo1",
+ revision_id="rev1",
+ )
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="DELETE",
+ endpoint_path=(
+ "integrations/test-integration/logicalOperators/lo1/revisions/rev1"
+ ),
+ api_version=APIVersion.V1ALPHA,
+ )
+
+
+# -- create_integration_logical_operator_revision tests --
+
+
+def test_create_integration_logical_operator_revision_minimal(chronicle_client):
+ """Test create_integration_logical_operator_revision with minimal fields."""
+ logical_operator = {
+ "displayName": "Test Operator",
+ "script": "def evaluate(a, b): return a == b",
+ }
+ expected = {"name": "rev1", "comment": ""}
+
+ with patch(
+ "secops.chronicle.integration.logical_operator_revisions.chronicle_request",
+ return_value=expected,
+ ) as mock_request, patch(
+ "secops.chronicle.integration.logical_operator_revisions.format_resource_id",
+ return_value="test-integration",
+ ):
+ result = create_integration_logical_operator_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ logical_operator_id="lo1",
+ logical_operator=logical_operator,
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="POST",
+ endpoint_path=(
+ "integrations/test-integration/logicalOperators/lo1/revisions"
+ ),
+ api_version=APIVersion.V1ALPHA,
+ json={"logicalOperator": logical_operator},
+ )
+
+
+def test_create_integration_logical_operator_revision_with_comment(chronicle_client):
+ """Test create_integration_logical_operator_revision with comment."""
+ logical_operator = {
+ "displayName": "Test Operator",
+ "script": "def evaluate(a, b): return a == b",
+ }
+ expected = {"name": "rev1", "comment": "Version 2.0"}
+
+ with patch(
+ "secops.chronicle.integration.logical_operator_revisions.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = create_integration_logical_operator_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ logical_operator_id="lo1",
+ logical_operator=logical_operator,
+ comment="Version 2.0",
+ )
+
+ assert result == expected
+
+ call_kwargs = mock_request.call_args[1]
+ assert call_kwargs["json"]["logicalOperator"] == logical_operator
+ assert call_kwargs["json"]["comment"] == "Version 2.0"
+
+
+# -- rollback_integration_logical_operator_revision tests --
+
+
+def test_rollback_integration_logical_operator_revision_success(chronicle_client):
+ """Test rollback_integration_logical_operator_revision delegates to chronicle_request."""
+ expected = {"name": "rev1", "comment": "Rolled back"}
+
+ with patch(
+ "secops.chronicle.integration.logical_operator_revisions.chronicle_request",
+ return_value=expected,
+ ) as mock_request, patch(
+ "secops.chronicle.integration.logical_operator_revisions.format_resource_id",
+ return_value="test-integration",
+ ):
+ result = rollback_integration_logical_operator_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ logical_operator_id="lo1",
+ revision_id="rev1",
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="POST",
+ endpoint_path=(
+ "integrations/test-integration/logicalOperators/lo1/"
+ "revisions/rev1:rollback"
+ ),
+ api_version=APIVersion.V1ALPHA,
+ )
+
+
+# -- Error handling tests --
+
+
+def test_list_integration_logical_operator_revisions_api_error(chronicle_client):
+ """Test list_integration_logical_operator_revisions handles API errors."""
+ with patch(
+ "secops.chronicle.integration.logical_operator_revisions.chronicle_paginated_request",
+ side_effect=APIError("API Error"),
+ ):
+ with pytest.raises(APIError, match="API Error"):
+ list_integration_logical_operator_revisions(
+ chronicle_client,
+ integration_name="test-integration",
+ logical_operator_id="lo1",
+ )
+
+
+def test_delete_integration_logical_operator_revision_api_error(chronicle_client):
+ """Test delete_integration_logical_operator_revision handles API errors."""
+ with patch(
+ "secops.chronicle.integration.logical_operator_revisions.chronicle_request",
+ side_effect=APIError("Delete failed"),
+ ):
+ with pytest.raises(APIError, match="Delete failed"):
+ delete_integration_logical_operator_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ logical_operator_id="lo1",
+ revision_id="rev1",
+ )
+
+
+def test_create_integration_logical_operator_revision_api_error(chronicle_client):
+ """Test create_integration_logical_operator_revision handles API errors."""
+ logical_operator = {"displayName": "Test"}
+
+ with patch(
+ "secops.chronicle.integration.logical_operator_revisions.chronicle_request",
+ side_effect=APIError("Creation failed"),
+ ):
+ with pytest.raises(APIError, match="Creation failed"):
+ create_integration_logical_operator_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ logical_operator_id="lo1",
+ logical_operator=logical_operator,
+ )
+
+
+def test_rollback_integration_logical_operator_revision_api_error(chronicle_client):
+ """Test rollback_integration_logical_operator_revision handles API errors."""
+ with patch(
+ "secops.chronicle.integration.logical_operator_revisions.chronicle_request",
+ side_effect=APIError("Rollback failed"),
+ ):
+ with pytest.raises(APIError, match="Rollback failed"):
+ rollback_integration_logical_operator_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ logical_operator_id="lo1",
+ revision_id="rev1",
+ )
+
diff --git a/tests/chronicle/integration/test_logical_operators.py b/tests/chronicle/integration/test_logical_operators.py
new file mode 100644
index 00000000..df495750
--- /dev/null
+++ b/tests/chronicle/integration/test_logical_operators.py
@@ -0,0 +1,547 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Tests for Chronicle integration logical operators functions."""
+
+from unittest.mock import Mock, patch
+
+import pytest
+
+from secops.chronicle.client import ChronicleClient
+from secops.chronicle.models import APIVersion
+from secops.chronicle.integration.logical_operators import (
+ list_integration_logical_operators,
+ get_integration_logical_operator,
+ delete_integration_logical_operator,
+ create_integration_logical_operator,
+ update_integration_logical_operator,
+ execute_integration_logical_operator_test,
+ get_integration_logical_operator_template,
+)
+from secops.exceptions import APIError
+
+
+@pytest.fixture
+def chronicle_client():
+ """Create a Chronicle client for testing."""
+ with patch("secops.auth.SecOpsAuth") as mock_auth:
+ mock_session = Mock()
+ mock_session.headers = {}
+ mock_auth.return_value.session = mock_session
+ return ChronicleClient(
+ customer_id="test-customer",
+ project_id="test-project",
+ default_api_version=APIVersion.V1ALPHA,
+ )
+
+
+# -- list_integration_logical_operators tests --
+
+
+def test_list_integration_logical_operators_success(chronicle_client):
+ """Test list_integration_logical_operators delegates to paginated request."""
+ expected = {
+ "logicalOperators": [{"name": "lo1"}, {"name": "lo2"}],
+ "nextPageToken": "token",
+ }
+
+ with patch(
+ "secops.chronicle.integration.logical_operators.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated, patch(
+ "secops.chronicle.integration.logical_operators.format_resource_id",
+ return_value="My Integration",
+ ):
+ result = list_integration_logical_operators(
+ chronicle_client,
+ integration_name="My Integration",
+ page_size=10,
+ page_token="next-token",
+ )
+
+ assert result == expected
+
+ mock_paginated.assert_called_once_with(
+ chronicle_client,
+ api_version=APIVersion.V1ALPHA,
+ path="integrations/My Integration/logicalOperators",
+ items_key="logicalOperators",
+ page_size=10,
+ page_token="next-token",
+ extra_params={},
+ as_list=False,
+ )
+
+
+def test_list_integration_logical_operators_default_args(chronicle_client):
+ """Test list_integration_logical_operators with default args."""
+ expected = {"logicalOperators": []}
+
+ with patch(
+ "secops.chronicle.integration.logical_operators.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_integration_logical_operators(
+ chronicle_client,
+ integration_name="test-integration",
+ )
+
+ assert result == expected
+
+ mock_paginated.assert_called_once_with(
+ chronicle_client,
+ api_version=APIVersion.V1ALPHA,
+ path="integrations/test-integration/logicalOperators",
+ items_key="logicalOperators",
+ page_size=None,
+ page_token=None,
+ extra_params={},
+ as_list=False,
+ )
+
+
+def test_list_integration_logical_operators_with_filter_order_expand(
+ chronicle_client,
+):
+ """Test list passes filter/orderBy/excludeStaging/expand in extra_params."""
+ expected = {"logicalOperators": [{"name": "lo1"}]}
+
+ with patch(
+ "secops.chronicle.integration.logical_operators.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_integration_logical_operators(
+ chronicle_client,
+ integration_name="test-integration",
+ filter_string='displayName = "My Operator"',
+ order_by="displayName",
+ exclude_staging=True,
+ expand="parameters",
+ )
+
+ assert result == expected
+
+ mock_paginated.assert_called_once_with(
+ chronicle_client,
+ api_version=APIVersion.V1ALPHA,
+ path="integrations/test-integration/logicalOperators",
+ items_key="logicalOperators",
+ page_size=None,
+ page_token=None,
+ extra_params={
+ "filter": 'displayName = "My Operator"',
+ "orderBy": "displayName",
+ "excludeStaging": True,
+ "expand": "parameters",
+ },
+ as_list=False,
+ )
+
+
+# -- get_integration_logical_operator tests --
+
+
+def test_get_integration_logical_operator_success(chronicle_client):
+ """Test get_integration_logical_operator delegates to chronicle_request."""
+ expected = {"name": "lo1", "displayName": "My Operator"}
+
+ with patch(
+ "secops.chronicle.integration.logical_operators.chronicle_request",
+ return_value=expected,
+ ) as mock_request, patch(
+ "secops.chronicle.integration.logical_operators.format_resource_id",
+ return_value="test-integration",
+ ):
+ result = get_integration_logical_operator(
+ chronicle_client,
+ integration_name="test-integration",
+ logical_operator_id="lo1",
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="GET",
+ endpoint_path="integrations/test-integration/logicalOperators/lo1",
+ api_version=APIVersion.V1ALPHA,
+ params=None,
+ )
+
+
+def test_get_integration_logical_operator_with_expand(chronicle_client):
+ """Test get_integration_logical_operator with expand parameter."""
+ expected = {"name": "lo1"}
+
+ with patch(
+ "secops.chronicle.integration.logical_operators.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = get_integration_logical_operator(
+ chronicle_client,
+ integration_name="test-integration",
+ logical_operator_id="lo1",
+ expand="parameters",
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="GET",
+ endpoint_path="integrations/test-integration/logicalOperators/lo1",
+ api_version=APIVersion.V1ALPHA,
+ params={"expand": "parameters"},
+ )
+
+
+# -- delete_integration_logical_operator tests --
+
+
+def test_delete_integration_logical_operator_success(chronicle_client):
+ """Test delete_integration_logical_operator delegates to chronicle_request."""
+ with patch(
+ "secops.chronicle.integration.logical_operators.chronicle_request",
+ return_value=None,
+ ) as mock_request, patch(
+ "secops.chronicle.integration.logical_operators.format_resource_id",
+ return_value="test-integration",
+ ):
+ delete_integration_logical_operator(
+ chronicle_client,
+ integration_name="test-integration",
+ logical_operator_id="lo1",
+ )
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="DELETE",
+ endpoint_path="integrations/test-integration/logicalOperators/lo1",
+ api_version=APIVersion.V1ALPHA,
+ )
+
+
+# -- create_integration_logical_operator tests --
+
+
+def test_create_integration_logical_operator_minimal(chronicle_client):
+ """Test create_integration_logical_operator with minimal required fields."""
+ expected = {"name": "lo1", "displayName": "New Operator"}
+
+ with patch(
+ "secops.chronicle.integration.logical_operators.chronicle_request",
+ return_value=expected,
+ ) as mock_request, patch(
+ "secops.chronicle.integration.logical_operators.format_resource_id",
+ return_value="test-integration",
+ ):
+ result = create_integration_logical_operator(
+ chronicle_client,
+ integration_name="test-integration",
+ display_name="New Operator",
+ script="def evaluate(a, b): return a == b",
+ script_timeout="60s",
+ enabled=True,
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once()
+ call_kwargs = mock_request.call_args[1]
+ assert call_kwargs["method"] == "POST"
+ assert (
+ call_kwargs["endpoint_path"]
+ == "integrations/test-integration/logicalOperators"
+ )
+ assert call_kwargs["api_version"] == APIVersion.V1ALPHA
+ assert call_kwargs["json"]["displayName"] == "New Operator"
+ assert call_kwargs["json"]["script"] == "def evaluate(a, b): return a == b"
+ assert call_kwargs["json"]["scriptTimeout"] == "60s"
+ assert call_kwargs["json"]["enabled"] is True
+
+
+def test_create_integration_logical_operator_with_all_fields(chronicle_client):
+ """Test create_integration_logical_operator with all optional fields."""
+ expected = {"name": "lo1"}
+
+ with patch(
+ "secops.chronicle.integration.logical_operators.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = create_integration_logical_operator(
+ chronicle_client,
+ integration_name="test-integration",
+ display_name="Full Operator",
+ script="def evaluate(a, b): return a > b",
+ script_timeout="120s",
+ enabled=False,
+ description="Test logical operator description",
+ parameters=[{"name": "param1", "type": "STRING"}],
+ )
+
+ assert result == expected
+
+ call_kwargs = mock_request.call_args[1]
+ body = call_kwargs["json"]
+ assert body["displayName"] == "Full Operator"
+ assert body["description"] == "Test logical operator description"
+ assert body["parameters"] == [{"name": "param1", "type": "STRING"}]
+
+
+# -- update_integration_logical_operator tests --
+
+
+def test_update_integration_logical_operator_display_name(chronicle_client):
+ """Test update_integration_logical_operator updates display name."""
+ expected = {"name": "lo1", "displayName": "Updated Name"}
+
+ with patch(
+ "secops.chronicle.integration.logical_operators.chronicle_request",
+ return_value=expected,
+ ) as mock_request, patch(
+ "secops.chronicle.integration.logical_operators.build_patch_body",
+ return_value=({"displayName": "Updated Name"}, {"updateMask": "displayName"}),
+ ) as mock_build:
+ result = update_integration_logical_operator(
+ chronicle_client,
+ integration_name="test-integration",
+ logical_operator_id="lo1",
+ display_name="Updated Name",
+ )
+
+ assert result == expected
+
+ mock_build.assert_called_once()
+ mock_request.assert_called_once()
+ call_kwargs = mock_request.call_args[1]
+ assert call_kwargs["method"] == "PATCH"
+ assert (
+ call_kwargs["endpoint_path"]
+ == "integrations/test-integration/logicalOperators/lo1"
+ )
+
+
+def test_update_integration_logical_operator_with_update_mask(chronicle_client):
+ """Test update_integration_logical_operator with explicit update mask."""
+ expected = {"name": "lo1"}
+
+ with patch(
+ "secops.chronicle.integration.logical_operators.chronicle_request",
+ return_value=expected,
+ ) as mock_request, patch(
+ "secops.chronicle.integration.logical_operators.build_patch_body",
+ return_value=(
+ {"displayName": "New Name", "enabled": True},
+ {"updateMask": "displayName,enabled"},
+ ),
+ ):
+ result = update_integration_logical_operator(
+ chronicle_client,
+ integration_name="test-integration",
+ logical_operator_id="lo1",
+ display_name="New Name",
+ enabled=True,
+ update_mask="displayName,enabled",
+ )
+
+ assert result == expected
+
+
+def test_update_integration_logical_operator_all_fields(chronicle_client):
+ """Test update_integration_logical_operator with all fields."""
+ expected = {"name": "lo1"}
+
+ with patch(
+ "secops.chronicle.integration.logical_operators.chronicle_request",
+ return_value=expected,
+ ) as mock_request, patch(
+ "secops.chronicle.integration.logical_operators.build_patch_body",
+ return_value=(
+ {
+ "displayName": "Updated",
+ "script": "new script",
+ "scriptTimeout": "90s",
+ "enabled": False,
+ "description": "Updated description",
+ "parameters": [{"name": "p1"}],
+ },
+ {"updateMask": "displayName,script,scriptTimeout,enabled,description"},
+ ),
+ ):
+ result = update_integration_logical_operator(
+ chronicle_client,
+ integration_name="test-integration",
+ logical_operator_id="lo1",
+ display_name="Updated",
+ script="new script",
+ script_timeout="90s",
+ enabled=False,
+ description="Updated description",
+ parameters=[{"name": "p1"}],
+ )
+
+ assert result == expected
+
+
+# -- execute_integration_logical_operator_test tests --
+
+
+def test_execute_integration_logical_operator_test_success(chronicle_client):
+ """Test execute_integration_logical_operator_test delegates to chronicle_request."""
+ logical_operator = {
+ "displayName": "Test Operator",
+ "script": "def evaluate(a, b): return a == b",
+ }
+ expected = {
+ "outputMessage": "Success",
+ "debugOutputMessage": "Debug info",
+ "resultValue": True,
+ }
+
+ with patch(
+ "secops.chronicle.integration.logical_operators.chronicle_request",
+ return_value=expected,
+ ) as mock_request, patch(
+ "secops.chronicle.integration.logical_operators.format_resource_id",
+ return_value="test-integration",
+ ):
+ result = execute_integration_logical_operator_test(
+ chronicle_client,
+ integration_name="test-integration",
+ logical_operator=logical_operator,
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="POST",
+ endpoint_path="integrations/test-integration/logicalOperators:executeTest",
+ api_version=APIVersion.V1ALPHA,
+ json={"logicalOperator": logical_operator},
+ )
+
+
+# -- get_integration_logical_operator_template tests --
+
+
+def test_get_integration_logical_operator_template_success(chronicle_client):
+ """Test get_integration_logical_operator_template delegates to chronicle_request."""
+ expected = {
+ "script": "def evaluate(a, b):\n # Template code\n return True",
+ "displayName": "Template Operator",
+ }
+
+ with patch(
+ "secops.chronicle.integration.logical_operators.chronicle_request",
+ return_value=expected,
+ ) as mock_request, patch(
+ "secops.chronicle.integration.logical_operators.format_resource_id",
+ return_value="test-integration",
+ ):
+ result = get_integration_logical_operator_template(
+ chronicle_client,
+ integration_name="test-integration",
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="GET",
+ endpoint_path=(
+ "integrations/test-integration/logicalOperators:fetchTemplate"
+ ),
+ api_version=APIVersion.V1ALPHA,
+ )
+
+
+# -- Error handling tests --
+
+
+def test_list_integration_logical_operators_api_error(chronicle_client):
+ """Test list_integration_logical_operators handles API errors."""
+ with patch(
+ "secops.chronicle.integration.logical_operators.chronicle_paginated_request",
+ side_effect=APIError("API Error"),
+ ):
+ with pytest.raises(APIError, match="API Error"):
+ list_integration_logical_operators(
+ chronicle_client,
+ integration_name="test-integration",
+ )
+
+
+def test_get_integration_logical_operator_api_error(chronicle_client):
+ """Test get_integration_logical_operator handles API errors."""
+ with patch(
+ "secops.chronicle.integration.logical_operators.chronicle_request",
+ side_effect=APIError("Not found"),
+ ):
+ with pytest.raises(APIError, match="Not found"):
+ get_integration_logical_operator(
+ chronicle_client,
+ integration_name="test-integration",
+ logical_operator_id="nonexistent",
+ )
+
+
+def test_create_integration_logical_operator_api_error(chronicle_client):
+ """Test create_integration_logical_operator handles API errors."""
+ with patch(
+ "secops.chronicle.integration.logical_operators.chronicle_request",
+ side_effect=APIError("Creation failed"),
+ ):
+ with pytest.raises(APIError, match="Creation failed"):
+ create_integration_logical_operator(
+ chronicle_client,
+ integration_name="test-integration",
+ display_name="New Operator",
+ script="def evaluate(a, b): return a == b",
+ script_timeout="60s",
+ enabled=True,
+ )
+
+
+def test_update_integration_logical_operator_api_error(chronicle_client):
+ """Test update_integration_logical_operator handles API errors."""
+ with patch(
+ "secops.chronicle.integration.logical_operators.chronicle_request",
+ side_effect=APIError("Update failed"),
+ ), patch(
+ "secops.chronicle.integration.logical_operators.build_patch_body",
+ return_value=({"displayName": "Updated"}, {"updateMask": "displayName"}),
+ ):
+ with pytest.raises(APIError, match="Update failed"):
+ update_integration_logical_operator(
+ chronicle_client,
+ integration_name="test-integration",
+ logical_operator_id="lo1",
+ display_name="Updated",
+ )
+
+
+def test_delete_integration_logical_operator_api_error(chronicle_client):
+ """Test delete_integration_logical_operator handles API errors."""
+ with patch(
+ "secops.chronicle.integration.logical_operators.chronicle_request",
+ side_effect=APIError("Delete failed"),
+ ):
+ with pytest.raises(APIError, match="Delete failed"):
+ delete_integration_logical_operator(
+ chronicle_client,
+ integration_name="test-integration",
+ logical_operator_id="lo1",
+ )
+
diff --git a/tests/chronicle/integration/test_manager_revisions.py b/tests/chronicle/integration/test_manager_revisions.py
new file mode 100644
index 00000000..7076bd54
--- /dev/null
+++ b/tests/chronicle/integration/test_manager_revisions.py
@@ -0,0 +1,417 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Tests for Chronicle marketplace integration manager revisions functions."""
+
+from unittest.mock import Mock, patch
+
+import pytest
+
+from secops.chronicle.client import ChronicleClient
+from secops.chronicle.models import APIVersion
+from secops.chronicle.integration.manager_revisions import (
+ list_integration_manager_revisions,
+ get_integration_manager_revision,
+ delete_integration_manager_revision,
+ create_integration_manager_revision,
+ rollback_integration_manager_revision,
+)
+from secops.exceptions import APIError
+
+
+@pytest.fixture
+def chronicle_client():
+ """Create a Chronicle client for testing."""
+ with patch("secops.auth.SecOpsAuth") as mock_auth:
+ mock_session = Mock()
+ mock_session.headers = {}
+ mock_auth.return_value.session = mock_session
+ return ChronicleClient(
+ customer_id="test-customer",
+ project_id="test-project",
+ default_api_version=APIVersion.V1BETA,
+ )
+
+
+# -- list_integration_manager_revisions tests --
+
+
+def test_list_integration_manager_revisions_success(chronicle_client):
+ """Test list_integration_manager_revisions delegates to chronicle_paginated_request."""
+ expected = {
+ "revisions": [{"name": "r1"}, {"name": "r2"}],
+ "nextPageToken": "t",
+ }
+
+ with patch(
+ "secops.chronicle.integration.manager_revisions.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated, patch(
+ "secops.chronicle.integration.manager_revisions.format_resource_id",
+ return_value="My Integration",
+ ):
+ result = list_integration_manager_revisions(
+ chronicle_client,
+ integration_name="My Integration",
+ manager_id="m1",
+ page_size=10,
+ page_token="next-token",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_paginated.call_args
+ assert "managers/m1/revisions" in kwargs["path"]
+ assert kwargs["items_key"] == "revisions"
+ assert kwargs["page_size"] == 10
+ assert kwargs["page_token"] == "next-token"
+
+
+def test_list_integration_manager_revisions_default_args(chronicle_client):
+ """Test list_integration_manager_revisions with default args."""
+ expected = {"revisions": []}
+
+ with patch(
+ "secops.chronicle.integration.manager_revisions.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_integration_manager_revisions(
+ chronicle_client,
+ integration_name="test-integration",
+ manager_id="m1",
+ )
+
+ assert result == expected
+
+
+def test_list_integration_manager_revisions_with_filters(chronicle_client):
+ """Test list_integration_manager_revisions with filter and order_by."""
+ expected = {"revisions": [{"name": "r1"}]}
+
+ with patch(
+ "secops.chronicle.integration.manager_revisions.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_integration_manager_revisions(
+ chronicle_client,
+ integration_name="test-integration",
+ manager_id="m1",
+ filter_string='version = "1.0"',
+ order_by="createTime",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_paginated.call_args
+ assert kwargs["extra_params"] == {
+ "filter": 'version = "1.0"',
+ "orderBy": "createTime",
+ }
+
+
+def test_list_integration_manager_revisions_as_list(chronicle_client):
+ """Test list_integration_manager_revisions returns list when as_list=True."""
+ expected = [{"name": "r1"}, {"name": "r2"}]
+
+ with patch(
+ "secops.chronicle.integration.manager_revisions.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_integration_manager_revisions(
+ chronicle_client,
+ integration_name="test-integration",
+ manager_id="m1",
+ as_list=True,
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_paginated.call_args
+ assert kwargs["as_list"] is True
+
+
+def test_list_integration_manager_revisions_error(chronicle_client):
+ """Test list_integration_manager_revisions raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.manager_revisions.chronicle_paginated_request",
+ side_effect=APIError("Failed to list manager revisions"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ list_integration_manager_revisions(
+ chronicle_client,
+ integration_name="test-integration",
+ manager_id="m1",
+ )
+ assert "Failed to list manager revisions" in str(exc_info.value)
+
+
+# -- get_integration_manager_revision tests --
+
+
+def test_get_integration_manager_revision_success(chronicle_client):
+ """Test get_integration_manager_revision issues GET request."""
+ expected = {
+ "name": "revisions/r1",
+ "manager": {
+ "displayName": "My Manager",
+ "script": "def helper(): pass",
+ },
+ "comment": "Initial version",
+ }
+
+ with patch(
+ "secops.chronicle.integration.manager_revisions.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = get_integration_manager_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ manager_id="m1",
+ revision_id="r1",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["method"] == "GET"
+ assert "managers/m1/revisions/r1" in kwargs["endpoint_path"]
+ assert kwargs["api_version"] == APIVersion.V1BETA
+
+
+def test_get_integration_manager_revision_error(chronicle_client):
+ """Test get_integration_manager_revision raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.manager_revisions.chronicle_request",
+ side_effect=APIError("Failed to get manager revision"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ get_integration_manager_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ manager_id="m1",
+ revision_id="r1",
+ )
+ assert "Failed to get manager revision" in str(exc_info.value)
+
+
+# -- delete_integration_manager_revision tests --
+
+
+def test_delete_integration_manager_revision_success(chronicle_client):
+ """Test delete_integration_manager_revision issues DELETE request."""
+ with patch(
+ "secops.chronicle.integration.manager_revisions.chronicle_request",
+ return_value=None,
+ ) as mock_request:
+ delete_integration_manager_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ manager_id="m1",
+ revision_id="r1",
+ )
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["method"] == "DELETE"
+ assert "managers/m1/revisions/r1" in kwargs["endpoint_path"]
+ assert kwargs["api_version"] == APIVersion.V1BETA
+
+
+def test_delete_integration_manager_revision_error(chronicle_client):
+ """Test delete_integration_manager_revision raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.manager_revisions.chronicle_request",
+ side_effect=APIError("Failed to delete manager revision"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ delete_integration_manager_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ manager_id="m1",
+ revision_id="r1",
+ )
+ assert "Failed to delete manager revision" in str(exc_info.value)
+
+
+# -- create_integration_manager_revision tests --
+
+
+def test_create_integration_manager_revision_required_fields_only(
+ chronicle_client,
+):
+ """Test create_integration_manager_revision with required fields only."""
+ expected = {"name": "revisions/new", "manager": {"displayName": "My Manager"}}
+ manager_dict = {
+ "displayName": "My Manager",
+ "script": "def helper(): pass",
+ }
+
+ with patch(
+ "secops.chronicle.integration.manager_revisions.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = create_integration_manager_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ manager_id="m1",
+ manager=manager_dict,
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="POST",
+ endpoint_path=(
+ "integrations/test-integration/managers/m1/revisions"
+ ),
+ api_version=APIVersion.V1BETA,
+ json={"manager": manager_dict},
+ )
+
+
+def test_create_integration_manager_revision_with_comment(chronicle_client):
+ """Test create_integration_manager_revision includes comment when provided."""
+ expected = {"name": "revisions/new"}
+ manager_dict = {"displayName": "My Manager", "script": "def helper(): pass"}
+
+ with patch(
+ "secops.chronicle.integration.manager_revisions.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = create_integration_manager_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ manager_id="m1",
+ manager=manager_dict,
+ comment="Backup before major refactor",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["json"]["comment"] == "Backup before major refactor"
+ assert kwargs["json"]["manager"] == manager_dict
+
+
+def test_create_integration_manager_revision_error(chronicle_client):
+ """Test create_integration_manager_revision raises APIError on failure."""
+ manager_dict = {"displayName": "My Manager", "script": "def helper(): pass"}
+
+ with patch(
+ "secops.chronicle.integration.manager_revisions.chronicle_request",
+ side_effect=APIError("Failed to create manager revision"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ create_integration_manager_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ manager_id="m1",
+ manager=manager_dict,
+ )
+ assert "Failed to create manager revision" in str(exc_info.value)
+
+
+# -- rollback_integration_manager_revision tests --
+
+
+def test_rollback_integration_manager_revision_success(chronicle_client):
+ """Test rollback_integration_manager_revision issues POST request."""
+ expected = {
+ "name": "revisions/r1",
+ "manager": {
+ "displayName": "My Manager",
+ "script": "def helper(): pass",
+ },
+ }
+
+ with patch(
+ "secops.chronicle.integration.manager_revisions.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = rollback_integration_manager_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ manager_id="m1",
+ revision_id="r1",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["method"] == "POST"
+ assert "managers/m1/revisions/r1:rollback" in kwargs["endpoint_path"]
+ assert kwargs["api_version"] == APIVersion.V1BETA
+
+
+def test_rollback_integration_manager_revision_error(chronicle_client):
+ """Test rollback_integration_manager_revision raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.manager_revisions.chronicle_request",
+ side_effect=APIError("Failed to rollback manager revision"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ rollback_integration_manager_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ manager_id="m1",
+ revision_id="r1",
+ )
+ assert "Failed to rollback manager revision" in str(exc_info.value)
+
+
+# -- API version tests --
+
+
+def test_list_integration_manager_revisions_custom_api_version(chronicle_client):
+ """Test list_integration_manager_revisions with custom API version."""
+ expected = {"revisions": []}
+
+ with patch(
+ "secops.chronicle.integration.manager_revisions.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_integration_manager_revisions(
+ chronicle_client,
+ integration_name="test-integration",
+ manager_id="m1",
+ api_version=APIVersion.V1ALPHA,
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_paginated.call_args
+ assert kwargs["api_version"] == APIVersion.V1ALPHA
+
+
+def test_get_integration_manager_revision_custom_api_version(chronicle_client):
+ """Test get_integration_manager_revision with custom API version."""
+ expected = {"name": "revisions/r1"}
+
+ with patch(
+ "secops.chronicle.integration.manager_revisions.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = get_integration_manager_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ manager_id="m1",
+ revision_id="r1",
+ api_version=APIVersion.V1ALPHA,
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["api_version"] == APIVersion.V1ALPHA
+
diff --git a/tests/chronicle/integration/test_managers.py b/tests/chronicle/integration/test_managers.py
new file mode 100644
index 00000000..c6bf5c4a
--- /dev/null
+++ b/tests/chronicle/integration/test_managers.py
@@ -0,0 +1,460 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Tests for Chronicle marketplace integration managers functions."""
+
+from unittest.mock import Mock, patch
+
+import pytest
+
+from secops.chronicle.client import ChronicleClient
+from secops.chronicle.models import APIVersion
+from secops.chronicle.integration.managers import (
+ list_integration_managers,
+ get_integration_manager,
+ delete_integration_manager,
+ create_integration_manager,
+ update_integration_manager,
+ get_integration_manager_template,
+)
+from secops.exceptions import APIError
+
+
+@pytest.fixture
+def chronicle_client():
+ """Create a Chronicle client for testing."""
+ with patch("secops.auth.SecOpsAuth") as mock_auth:
+ mock_session = Mock()
+ mock_session.headers = {}
+ mock_auth.return_value.session = mock_session
+ return ChronicleClient(
+ customer_id="test-customer",
+ project_id="test-project",
+ default_api_version=APIVersion.V1BETA,
+ )
+
+
+# -- list_integration_managers tests --
+
+
+def test_list_integration_managers_success(chronicle_client):
+ """Test list_integration_managers delegates to chronicle_paginated_request."""
+ expected = {"managers": [{"name": "m1"}, {"name": "m2"}], "nextPageToken": "t"}
+
+ with patch(
+ "secops.chronicle.integration.managers.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated, patch(
+ "secops.chronicle.integration.managers.format_resource_id",
+ return_value="My Integration",
+ ):
+ result = list_integration_managers(
+ chronicle_client,
+ integration_name="My Integration",
+ page_size=10,
+ page_token="next-token",
+ )
+
+ assert result == expected
+
+ mock_paginated.assert_called_once_with(
+ chronicle_client,
+ api_version=APIVersion.V1BETA,
+ path="integrations/My Integration/managers",
+ items_key="managers",
+ page_size=10,
+ page_token="next-token",
+ extra_params={},
+ as_list=False,
+ )
+
+
+def test_list_integration_managers_default_args(chronicle_client):
+ """Test list_integration_managers with default args."""
+ expected = {"managers": []}
+
+ with patch(
+ "secops.chronicle.integration.managers.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_integration_managers(
+ chronicle_client,
+ integration_name="test-integration",
+ )
+
+ assert result == expected
+
+
+def test_list_integration_managers_with_filters(chronicle_client):
+ """Test list_integration_managers with filter and order_by."""
+ expected = {"managers": [{"name": "m1"}]}
+
+ with patch(
+ "secops.chronicle.integration.managers.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_integration_managers(
+ chronicle_client,
+ integration_name="test-integration",
+ filter_string='displayName = "My Manager"',
+ order_by="displayName",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_paginated.call_args
+ assert kwargs["extra_params"] == {
+ "filter": 'displayName = "My Manager"',
+ "orderBy": "displayName",
+ }
+
+
+def test_list_integration_managers_as_list(chronicle_client):
+ """Test list_integration_managers returns list when as_list=True."""
+ expected = [{"name": "m1"}, {"name": "m2"}]
+
+ with patch(
+ "secops.chronicle.integration.managers.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_integration_managers(
+ chronicle_client,
+ integration_name="test-integration",
+ as_list=True,
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_paginated.call_args
+ assert kwargs["as_list"] is True
+
+
+def test_list_integration_managers_error(chronicle_client):
+ """Test list_integration_managers raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.managers.chronicle_paginated_request",
+ side_effect=APIError("Failed to list integration managers"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ list_integration_managers(
+ chronicle_client,
+ integration_name="test-integration",
+ )
+ assert "Failed to list integration managers" in str(exc_info.value)
+
+
+# -- get_integration_manager tests --
+
+
+def test_get_integration_manager_success(chronicle_client):
+ """Test get_integration_manager issues GET request."""
+ expected = {
+ "name": "managers/m1",
+ "displayName": "My Manager",
+ "script": "def helper(): pass",
+ }
+
+ with patch(
+ "secops.chronicle.integration.managers.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = get_integration_manager(
+ chronicle_client,
+ integration_name="test-integration",
+ manager_id="m1",
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="GET",
+ endpoint_path="integrations/test-integration/managers/m1",
+ api_version=APIVersion.V1BETA,
+ )
+
+
+def test_get_integration_manager_error(chronicle_client):
+ """Test get_integration_manager raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.managers.chronicle_request",
+ side_effect=APIError("Failed to get integration manager"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ get_integration_manager(
+ chronicle_client,
+ integration_name="test-integration",
+ manager_id="m1",
+ )
+ assert "Failed to get integration manager" in str(exc_info.value)
+
+
+# -- delete_integration_manager tests --
+
+
+def test_delete_integration_manager_success(chronicle_client):
+ """Test delete_integration_manager issues DELETE request."""
+ with patch(
+ "secops.chronicle.integration.managers.chronicle_request",
+ return_value=None,
+ ) as mock_request:
+ delete_integration_manager(
+ chronicle_client,
+ integration_name="test-integration",
+ manager_id="m1",
+ )
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="DELETE",
+ endpoint_path="integrations/test-integration/managers/m1",
+ api_version=APIVersion.V1BETA,
+ )
+
+
+def test_delete_integration_manager_error(chronicle_client):
+ """Test delete_integration_manager raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.managers.chronicle_request",
+ side_effect=APIError("Failed to delete integration manager"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ delete_integration_manager(
+ chronicle_client,
+ integration_name="test-integration",
+ manager_id="m1",
+ )
+ assert "Failed to delete integration manager" in str(exc_info.value)
+
+
+# -- create_integration_manager tests --
+
+
+def test_create_integration_manager_required_fields_only(chronicle_client):
+ """Test create_integration_manager sends only required fields when optionals omitted."""
+ expected = {"name": "managers/new", "displayName": "My Manager"}
+
+ with patch(
+ "secops.chronicle.integration.managers.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = create_integration_manager(
+ chronicle_client,
+ integration_name="test-integration",
+ display_name="My Manager",
+ script="def helper(): pass",
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="POST",
+ endpoint_path="integrations/test-integration/managers",
+ api_version=APIVersion.V1BETA,
+ json={
+ "displayName": "My Manager",
+ "script": "def helper(): pass",
+ },
+ )
+
+
+def test_create_integration_manager_with_description(chronicle_client):
+ """Test create_integration_manager includes description when provided."""
+ expected = {"name": "managers/new", "displayName": "My Manager"}
+
+ with patch(
+ "secops.chronicle.integration.managers.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = create_integration_manager(
+ chronicle_client,
+ integration_name="test-integration",
+ display_name="My Manager",
+ script="def helper(): pass",
+ description="A helpful manager",
+ )
+
+ assert result == expected
+
+ _, kwargs = mock_request.call_args
+ assert kwargs["json"]["description"] == "A helpful manager"
+
+
+def test_create_integration_manager_error(chronicle_client):
+ """Test create_integration_manager raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.managers.chronicle_request",
+ side_effect=APIError("Failed to create integration manager"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ create_integration_manager(
+ chronicle_client,
+ integration_name="test-integration",
+ display_name="My Manager",
+ script="def helper(): pass",
+ )
+ assert "Failed to create integration manager" in str(exc_info.value)
+
+
+# -- update_integration_manager tests --
+
+
+def test_update_integration_manager_single_field(chronicle_client):
+ """Test update_integration_manager updates a single field."""
+ expected = {"name": "managers/m1", "displayName": "Updated Manager"}
+
+ with patch(
+ "secops.chronicle.integration.managers.chronicle_request",
+ return_value=expected,
+ ) as mock_request, patch(
+ "secops.chronicle.integration.managers.build_patch_body",
+ return_value=({"displayName": "Updated Manager"}, {"updateMask": "displayName"}),
+ ) as mock_build_patch:
+ result = update_integration_manager(
+ chronicle_client,
+ integration_name="test-integration",
+ manager_id="m1",
+ display_name="Updated Manager",
+ )
+
+ assert result == expected
+
+ mock_build_patch.assert_called_once()
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="PATCH",
+ endpoint_path="integrations/test-integration/managers/m1",
+ api_version=APIVersion.V1BETA,
+ json={"displayName": "Updated Manager"},
+ params={"updateMask": "displayName"},
+ )
+
+
+def test_update_integration_manager_multiple_fields(chronicle_client):
+ """Test update_integration_manager updates multiple fields."""
+ expected = {"name": "managers/m1", "displayName": "Updated Manager"}
+
+ with patch(
+ "secops.chronicle.integration.managers.chronicle_request",
+ return_value=expected,
+ ) as mock_request, patch(
+ "secops.chronicle.integration.managers.build_patch_body",
+ return_value=(
+ {
+ "displayName": "Updated Manager",
+ "script": "def new_helper(): pass",
+ "description": "New description",
+ },
+ {"updateMask": "displayName,script,description"},
+ ),
+ ):
+ result = update_integration_manager(
+ chronicle_client,
+ integration_name="test-integration",
+ manager_id="m1",
+ display_name="Updated Manager",
+ script="def new_helper(): pass",
+ description="New description",
+ )
+
+ assert result == expected
+
+
+def test_update_integration_manager_with_update_mask(chronicle_client):
+ """Test update_integration_manager respects explicit update_mask."""
+ expected = {"name": "managers/m1", "displayName": "Updated Manager"}
+
+ with patch(
+ "secops.chronicle.integration.managers.chronicle_request",
+ return_value=expected,
+ ) as mock_request, patch(
+ "secops.chronicle.integration.managers.build_patch_body",
+ return_value=(
+ {"displayName": "Updated Manager"},
+ {"updateMask": "displayName"},
+ ),
+ ):
+ result = update_integration_manager(
+ chronicle_client,
+ integration_name="test-integration",
+ manager_id="m1",
+ display_name="Updated Manager",
+ update_mask="displayName",
+ )
+
+ assert result == expected
+
+
+def test_update_integration_manager_error(chronicle_client):
+ """Test update_integration_manager raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.managers.chronicle_request",
+ side_effect=APIError("Failed to update integration manager"),
+ ), patch(
+ "secops.chronicle.integration.managers.build_patch_body",
+ return_value=({"displayName": "Updated"}, {"updateMask": "displayName"}),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ update_integration_manager(
+ chronicle_client,
+ integration_name="test-integration",
+ manager_id="m1",
+ display_name="Updated",
+ )
+ assert "Failed to update integration manager" in str(exc_info.value)
+
+
+# -- get_integration_manager_template tests --
+
+
+def test_get_integration_manager_template_success(chronicle_client):
+ """Test get_integration_manager_template issues GET request."""
+ expected = {
+ "displayName": "Template Manager",
+ "script": "# Template script\ndef template(): pass",
+ }
+
+ with patch(
+ "secops.chronicle.integration.managers.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = get_integration_manager_template(
+ chronicle_client,
+ integration_name="test-integration",
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="GET",
+ endpoint_path="integrations/test-integration/managers:fetchTemplate",
+ api_version=APIVersion.V1BETA,
+ )
+
+
+def test_get_integration_manager_template_error(chronicle_client):
+ """Test get_integration_manager_template raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.managers.chronicle_request",
+ side_effect=APIError("Failed to get integration manager template"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ get_integration_manager_template(
+ chronicle_client,
+ integration_name="test-integration",
+ )
+ assert "Failed to get integration manager template" in str(exc_info.value)
+
diff --git a/tests/chronicle/integration/test_marketplace_integrations.py b/tests/chronicle/integration/test_marketplace_integrations.py
new file mode 100644
index 00000000..15216fd9
--- /dev/null
+++ b/tests/chronicle/integration/test_marketplace_integrations.py
@@ -0,0 +1,522 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Tests for Chronicle marketplace integration functions."""
+
+from unittest.mock import Mock, patch
+
+import pytest
+
+from secops.chronicle.client import ChronicleClient
+from secops.chronicle.models import APIVersion
+from secops.chronicle.integration.marketplace_integrations import (
+ list_marketplace_integrations,
+ get_marketplace_integration,
+ get_marketplace_integration_diff,
+ install_marketplace_integration,
+ uninstall_marketplace_integration,
+)
+from secops.exceptions import APIError
+
+
+@pytest.fixture
+def chronicle_client():
+ """Create a Chronicle client for testing."""
+ with patch("secops.auth.SecOpsAuth") as mock_auth:
+ mock_session = Mock()
+ mock_session.headers = {}
+ mock_auth.return_value.session = mock_session
+ return ChronicleClient(
+ customer_id="test-customer",
+ project_id="test-project",
+ default_api_version=APIVersion.V1BETA,
+ )
+
+
+@pytest.fixture
+def mock_response() -> Mock:
+ """Create a mock API response object."""
+ mock = Mock()
+ mock.status_code = 200
+ mock.json.return_value = {}
+ return mock
+
+
+@pytest.fixture
+def mock_error_response() -> Mock:
+ """Create a mock error API response object."""
+ mock = Mock()
+ mock.status_code = 400
+ mock.text = "Error message"
+ mock.raise_for_status.side_effect = Exception("API Error")
+ return mock
+
+
+# -- list_marketplace_integrations tests --
+
+
+def test_list_marketplace_integrations_success(chronicle_client):
+ """Test list_marketplace_integrations delegates to chronicle_paginated_request."""
+ expected = {
+ "marketplaceIntegrations": [
+ {"name": "integration1"},
+ {"name": "integration2"},
+ ]
+ }
+
+ with patch(
+ "secops.chronicle.integration.marketplace_integrations.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_marketplace_integrations(
+ chronicle_client,
+ page_size=10,
+ page_token="next-token",
+ )
+
+ assert result == expected
+
+ mock_paginated.assert_called_once_with(
+ chronicle_client,
+ api_version=APIVersion.V1BETA,
+ path="marketplaceIntegrations",
+ items_key="marketplaceIntegrations",
+ page_size=10,
+ page_token="next-token",
+ extra_params={},
+ as_list=False,
+ )
+
+
+def test_list_marketplace_integrations_default_args(chronicle_client):
+ """Test list_marketplace_integrations with default args."""
+ expected = {"marketplaceIntegrations": []}
+
+ with patch(
+ "secops.chronicle.integration.marketplace_integrations.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_marketplace_integrations(chronicle_client)
+
+ assert result == expected
+
+ mock_paginated.assert_called_once_with(
+ chronicle_client,
+ api_version=APIVersion.V1BETA,
+ path="marketplaceIntegrations",
+ items_key="marketplaceIntegrations",
+ page_size=None,
+ page_token=None,
+ extra_params={},
+ as_list=False,
+ )
+
+
+def test_list_marketplace_integrations_with_filter(chronicle_client):
+ """Test list_marketplace_integrations passes filter_string in extra_params."""
+ expected = {"marketplaceIntegrations": [{"name": "integration1"}]}
+
+ with patch(
+ "secops.chronicle.integration.marketplace_integrations.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_marketplace_integrations(
+ chronicle_client,
+ filter_string='displayName = "My Integration"',
+ )
+
+ assert result == expected
+
+ mock_paginated.assert_called_once_with(
+ chronicle_client,
+ api_version=APIVersion.V1BETA,
+ path="marketplaceIntegrations",
+ items_key="marketplaceIntegrations",
+ page_size=None,
+ page_token=None,
+ extra_params={"filter": 'displayName = "My Integration"'},
+ as_list=False,
+ )
+
+
+def test_list_marketplace_integrations_with_order_by(chronicle_client):
+ """Test list_marketplace_integrations passes order_by in extra_params."""
+ expected = {"marketplaceIntegrations": []}
+
+ with patch(
+ "secops.chronicle.integration.marketplace_integrations.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_marketplace_integrations(
+ chronicle_client,
+ order_by="displayName",
+ )
+
+ assert result == expected
+
+ mock_paginated.assert_called_once_with(
+ chronicle_client,
+ api_version=APIVersion.V1BETA,
+ path="marketplaceIntegrations",
+ items_key="marketplaceIntegrations",
+ page_size=None,
+ page_token=None,
+ extra_params={"orderBy": "displayName"},
+ as_list=False,
+ )
+
+
+def test_list_marketplace_integrations_with_filter_and_order_by(chronicle_client):
+ """Test list_marketplace_integrations with both filter_string and order_by."""
+ expected = {"marketplaceIntegrations": []}
+
+ with patch(
+ "secops.chronicle.integration.marketplace_integrations.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_marketplace_integrations(
+ chronicle_client,
+ filter_string='displayName = "My Integration"',
+ order_by="displayName",
+ )
+
+ assert result == expected
+
+ mock_paginated.assert_called_once_with(
+ chronicle_client,
+ api_version=APIVersion.V1BETA,
+ path="marketplaceIntegrations",
+ items_key="marketplaceIntegrations",
+ page_size=None,
+ page_token=None,
+ extra_params={
+ "filter": 'displayName = "My Integration"',
+ "orderBy": "displayName",
+ },
+ as_list=False,
+ )
+
+
+def test_list_marketplace_integrations_as_list(chronicle_client):
+ """Test list_marketplace_integrations with as_list=True."""
+ expected = [{"name": "integration1"}, {"name": "integration2"}]
+
+ with patch(
+ "secops.chronicle.integration.marketplace_integrations.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_marketplace_integrations(chronicle_client, as_list=True)
+
+ assert result == expected
+
+ mock_paginated.assert_called_once_with(
+ chronicle_client,
+ api_version=APIVersion.V1BETA,
+ path="marketplaceIntegrations",
+ items_key="marketplaceIntegrations",
+ page_size=None,
+ page_token=None,
+ extra_params={},
+ as_list=True,
+ )
+
+
+def test_list_marketplace_integrations_error(chronicle_client):
+ """Test list_marketplace_integrations propagates APIError from helper."""
+ with patch(
+ "secops.chronicle.integration.marketplace_integrations.chronicle_paginated_request",
+ side_effect=APIError("Failed to list marketplace integrations"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ list_marketplace_integrations(chronicle_client)
+
+ assert "Failed to list marketplace integrations" in str(exc_info.value)
+
+
+# -- get_marketplace_integration tests --
+
+
+def test_get_marketplace_integration_success(chronicle_client):
+ """Test get_marketplace_integration returns expected result."""
+ expected = {
+ "name": "test-integration",
+ "displayName": "Test Integration",
+ "version": "1.0.0",
+ }
+
+ with patch(
+ "secops.chronicle.integration.marketplace_integrations.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = get_marketplace_integration(chronicle_client, "test-integration")
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="GET",
+ endpoint_path="marketplaceIntegrations/test-integration",
+ api_version=APIVersion.V1BETA,
+ )
+
+
+def test_get_marketplace_integration_error(chronicle_client):
+ """Test get_marketplace_integration raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.marketplace_integrations.chronicle_request",
+ side_effect=APIError("Failed to get marketplace integration test-integration"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ get_marketplace_integration(chronicle_client, "test-integration")
+
+ assert "Failed to get marketplace integration" in str(exc_info.value)
+
+
+# -- get_marketplace_integration_diff tests --
+
+
+def test_get_marketplace_integration_diff_success(chronicle_client):
+ """Test get_marketplace_integration_diff returns expected result."""
+ expected = {
+ "name": "test-integration",
+ "diff": {"added": [], "removed": [], "modified": []},
+ }
+
+ with patch(
+ "secops.chronicle.integration.marketplace_integrations.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = get_marketplace_integration_diff(chronicle_client, "test-integration")
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="GET",
+ endpoint_path=(
+ "marketplaceIntegrations/test-integration"
+ ":fetchCommercialDiff"
+ ),
+ api_version=APIVersion.V1BETA,
+ )
+
+
+def test_get_marketplace_integration_diff_error(chronicle_client):
+ """Test get_marketplace_integration_diff raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.marketplace_integrations.chronicle_request",
+ side_effect=APIError("Failed to get marketplace integration diff"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ get_marketplace_integration_diff(chronicle_client, "test-integration")
+
+ assert "Failed to get marketplace integration diff" in str(exc_info.value)
+
+
+# -- install_marketplace_integration tests --
+
+
+def test_install_marketplace_integration_no_optional_fields(chronicle_client):
+ """Test install_marketplace_integration with no optional fields sends empty body."""
+ expected = {
+ "name": "test-integration",
+ "displayName": "Test Integration",
+ "installedVersion": "1.0.0",
+ }
+
+ with patch(
+ "secops.chronicle.integration.marketplace_integrations.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = install_marketplace_integration(
+ chronicle_client,
+ integration_name="test-integration",
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="POST",
+ endpoint_path="marketplaceIntegrations/test-integration:install",
+ json={},
+ api_version=APIVersion.V1BETA,
+ )
+
+
+def test_install_marketplace_integration_all_fields(chronicle_client):
+ """Test install_marketplace_integration with all optional fields."""
+ expected = {
+ "name": "test-integration",
+ "displayName": "Test Integration",
+ "installedVersion": "2.0.0",
+ }
+
+ with patch(
+ "secops.chronicle.integration.marketplace_integrations.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = install_marketplace_integration(
+ chronicle_client,
+ integration_name="test-integration",
+ override_mapping=True,
+ staging=False,
+ version="2.0.0",
+ restore_from_snapshot=True,
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="POST",
+ endpoint_path="marketplaceIntegrations/test-integration:install",
+ json={
+ "overrideMapping": True,
+ "staging": False,
+ "version": "2.0.0",
+ "restoreFromSnapshot": True,
+ },
+ api_version=APIVersion.V1BETA,
+ )
+
+
+def test_install_marketplace_integration_override_mapping_only(chronicle_client):
+ """Test install_marketplace_integration with only override_mapping set."""
+ expected = {"name": "test-integration"}
+
+ with patch(
+ "secops.chronicle.integration.marketplace_integrations.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = install_marketplace_integration(
+ chronicle_client,
+ integration_name="test-integration",
+ override_mapping=True,
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="POST",
+ endpoint_path="marketplaceIntegrations/test-integration:install",
+ json={"overrideMapping": True},
+ api_version=APIVersion.V1BETA,
+ )
+
+
+def test_install_marketplace_integration_version_only(chronicle_client):
+ """Test install_marketplace_integration with only version set."""
+ expected = {"name": "test-integration"}
+
+ with patch(
+ "secops.chronicle.integration.marketplace_integrations.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = install_marketplace_integration(
+ chronicle_client,
+ integration_name="test-integration",
+ version="1.2.3",
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="POST",
+ endpoint_path="marketplaceIntegrations/test-integration:install",
+ json={"version": "1.2.3"},
+ api_version=APIVersion.V1BETA,
+ )
+
+
+def test_install_marketplace_integration_none_fields_excluded(chronicle_client):
+ """Test that None optional fields are not included in the request body."""
+ with patch(
+ "secops.chronicle.integration.marketplace_integrations.chronicle_request",
+ return_value={"name": "test-integration"},
+ ) as mock_request:
+ install_marketplace_integration(
+ chronicle_client,
+ integration_name="test-integration",
+ override_mapping=None,
+ staging=None,
+ version=None,
+ restore_from_snapshot=None,
+ )
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="POST",
+ endpoint_path="marketplaceIntegrations/test-integration:install",
+ json={},
+ api_version=APIVersion.V1BETA,
+ )
+
+
+def test_install_marketplace_integration_error(chronicle_client):
+ """Test install_marketplace_integration raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.marketplace_integrations.chronicle_request",
+ side_effect=APIError("Failed to install marketplace integration"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ install_marketplace_integration(
+ chronicle_client,
+ integration_name="test-integration",
+ )
+
+ assert "Failed to install marketplace integration" in str(exc_info.value)
+
+
+# -- uninstall_marketplace_integration tests --
+
+
+def test_uninstall_marketplace_integration_success(chronicle_client):
+ """Test uninstall_marketplace_integration returns expected result."""
+ expected = {}
+
+ with patch(
+ "secops.chronicle.integration.marketplace_integrations.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = uninstall_marketplace_integration(
+ chronicle_client,
+ integration_name="test-integration",
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="POST",
+ endpoint_path="marketplaceIntegrations/test-integration:uninstall",
+ api_version=APIVersion.V1BETA,
+ )
+
+
+def test_uninstall_marketplace_integration_error(chronicle_client):
+ """Test uninstall_marketplace_integration raises APIError on failure."""
+ with patch(
+ "secops.chronicle.integration.marketplace_integrations.chronicle_request",
+ side_effect=APIError("Failed to uninstall marketplace integration"),
+ ):
+ with pytest.raises(APIError) as exc_info:
+ uninstall_marketplace_integration(
+ chronicle_client,
+ integration_name="test-integration",
+ )
+
+ assert "Failed to uninstall marketplace integration" in str(exc_info.value)
\ No newline at end of file
diff --git a/tests/chronicle/integration/test_transformer_revisions.py b/tests/chronicle/integration/test_transformer_revisions.py
new file mode 100644
index 00000000..8107891e
--- /dev/null
+++ b/tests/chronicle/integration/test_transformer_revisions.py
@@ -0,0 +1,366 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Tests for Chronicle integration transformer revisions functions."""
+
+from unittest.mock import Mock, patch
+
+import pytest
+
+from secops.chronicle.client import ChronicleClient
+from secops.chronicle.models import APIVersion
+from secops.chronicle.integration.transformer_revisions import (
+ list_integration_transformer_revisions,
+ delete_integration_transformer_revision,
+ create_integration_transformer_revision,
+ rollback_integration_transformer_revision,
+)
+from secops.exceptions import APIError
+
+
+@pytest.fixture
+def chronicle_client():
+ """Create a Chronicle client for testing."""
+ with patch("secops.auth.SecOpsAuth") as mock_auth:
+ mock_session = Mock()
+ mock_session.headers = {}
+ mock_auth.return_value.session = mock_session
+ return ChronicleClient(
+ customer_id="test-customer",
+ project_id="test-project",
+ default_api_version=APIVersion.V1ALPHA,
+ )
+
+
+# -- list_integration_transformer_revisions tests --
+
+
+def test_list_integration_transformer_revisions_success(chronicle_client):
+ """Test list_integration_transformer_revisions delegates to paginated request."""
+ expected = {
+ "revisions": [{"name": "r1"}, {"name": "r2"}],
+ "nextPageToken": "token",
+ }
+
+ with patch(
+ "secops.chronicle.integration.transformer_revisions.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated, patch(
+ "secops.chronicle.integration.transformer_revisions.format_resource_id",
+ return_value="My Integration",
+ ):
+ result = list_integration_transformer_revisions(
+ chronicle_client,
+ integration_name="My Integration",
+ transformer_id="t1",
+ page_size=10,
+ page_token="next-token",
+ )
+
+ assert result == expected
+
+ mock_paginated.assert_called_once_with(
+ chronicle_client,
+ api_version=APIVersion.V1ALPHA,
+ path="integrations/My Integration/transformers/t1/revisions",
+ items_key="revisions",
+ page_size=10,
+ page_token="next-token",
+ extra_params={},
+ as_list=False,
+ )
+
+
+def test_list_integration_transformer_revisions_default_args(chronicle_client):
+ """Test list_integration_transformer_revisions with default args."""
+ expected = {"revisions": []}
+
+ with patch(
+ "secops.chronicle.integration.transformer_revisions.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_integration_transformer_revisions(
+ chronicle_client,
+ integration_name="test-integration",
+ transformer_id="t1",
+ )
+
+ assert result == expected
+
+ mock_paginated.assert_called_once_with(
+ chronicle_client,
+ api_version=APIVersion.V1ALPHA,
+ path="integrations/test-integration/transformers/t1/revisions",
+ items_key="revisions",
+ page_size=None,
+ page_token=None,
+ extra_params={},
+ as_list=False,
+ )
+
+
+def test_list_integration_transformer_revisions_with_filter_order(
+ chronicle_client,
+):
+ """Test list passes filter/orderBy in extra_params."""
+ expected = {"revisions": [{"name": "r1"}]}
+
+ with patch(
+ "secops.chronicle.integration.transformer_revisions.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_integration_transformer_revisions(
+ chronicle_client,
+ integration_name="test-integration",
+ transformer_id="t1",
+ filter_string='version = "1.0"',
+ order_by="createTime desc",
+ )
+
+ assert result == expected
+
+ mock_paginated.assert_called_once_with(
+ chronicle_client,
+ api_version=APIVersion.V1ALPHA,
+ path="integrations/test-integration/transformers/t1/revisions",
+ items_key="revisions",
+ page_size=None,
+ page_token=None,
+ extra_params={
+ "filter": 'version = "1.0"',
+ "orderBy": "createTime desc",
+ },
+ as_list=False,
+ )
+
+
+def test_list_integration_transformer_revisions_as_list(chronicle_client):
+ """Test list_integration_transformer_revisions with as_list=True."""
+ expected = [{"name": "r1"}, {"name": "r2"}]
+
+ with patch(
+ "secops.chronicle.integration.transformer_revisions.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_integration_transformer_revisions(
+ chronicle_client,
+ integration_name="test-integration",
+ transformer_id="t1",
+ as_list=True,
+ )
+
+ assert result == expected
+
+ mock_paginated.assert_called_once_with(
+ chronicle_client,
+ api_version=APIVersion.V1ALPHA,
+ path="integrations/test-integration/transformers/t1/revisions",
+ items_key="revisions",
+ page_size=None,
+ page_token=None,
+ extra_params={},
+ as_list=True,
+ )
+
+
+# -- delete_integration_transformer_revision tests --
+
+
+def test_delete_integration_transformer_revision_success(chronicle_client):
+ """Test delete_integration_transformer_revision delegates to chronicle_request."""
+ with patch(
+ "secops.chronicle.integration.transformer_revisions.chronicle_request",
+ return_value=None,
+ ) as mock_request, patch(
+ "secops.chronicle.integration.transformer_revisions.format_resource_id",
+ return_value="test-integration",
+ ):
+ delete_integration_transformer_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ transformer_id="t1",
+ revision_id="rev1",
+ )
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="DELETE",
+ endpoint_path=(
+ "integrations/test-integration/transformers/t1/revisions/rev1"
+ ),
+ api_version=APIVersion.V1ALPHA,
+ )
+
+
+# -- create_integration_transformer_revision tests --
+
+
+def test_create_integration_transformer_revision_minimal(chronicle_client):
+ """Test create_integration_transformer_revision with minimal fields."""
+ transformer = {
+ "displayName": "Test Transformer",
+ "script": "def transform(data): return data",
+ }
+ expected = {"name": "rev1", "comment": ""}
+
+ with patch(
+ "secops.chronicle.integration.transformer_revisions.chronicle_request",
+ return_value=expected,
+ ) as mock_request, patch(
+ "secops.chronicle.integration.transformer_revisions.format_resource_id",
+ return_value="test-integration",
+ ):
+ result = create_integration_transformer_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ transformer_id="t1",
+ transformer=transformer,
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="POST",
+ endpoint_path=(
+ "integrations/test-integration/transformers/t1/revisions"
+ ),
+ api_version=APIVersion.V1ALPHA,
+ json={"transformer": transformer},
+ )
+
+
+def test_create_integration_transformer_revision_with_comment(chronicle_client):
+ """Test create_integration_transformer_revision with comment."""
+ transformer = {
+ "displayName": "Test Transformer",
+ "script": "def transform(data): return data",
+ }
+ expected = {"name": "rev1", "comment": "Version 2.0"}
+
+ with patch(
+ "secops.chronicle.integration.transformer_revisions.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = create_integration_transformer_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ transformer_id="t1",
+ transformer=transformer,
+ comment="Version 2.0",
+ )
+
+ assert result == expected
+
+ call_kwargs = mock_request.call_args[1]
+ assert call_kwargs["json"]["transformer"] == transformer
+ assert call_kwargs["json"]["comment"] == "Version 2.0"
+
+
+# -- rollback_integration_transformer_revision tests --
+
+
+def test_rollback_integration_transformer_revision_success(chronicle_client):
+ """Test rollback_integration_transformer_revision delegates to chronicle_request."""
+ expected = {"name": "rev1", "comment": "Rolled back"}
+
+ with patch(
+ "secops.chronicle.integration.transformer_revisions.chronicle_request",
+ return_value=expected,
+ ) as mock_request, patch(
+ "secops.chronicle.integration.transformer_revisions.format_resource_id",
+ return_value="test-integration",
+ ):
+ result = rollback_integration_transformer_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ transformer_id="t1",
+ revision_id="rev1",
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="POST",
+ endpoint_path=(
+ "integrations/test-integration/transformers/t1/revisions/rev1:rollback"
+ ),
+ api_version=APIVersion.V1ALPHA,
+ )
+
+
+# -- Error handling tests --
+
+
+def test_list_integration_transformer_revisions_api_error(chronicle_client):
+ """Test list_integration_transformer_revisions handles API errors."""
+ with patch(
+ "secops.chronicle.integration.transformer_revisions.chronicle_paginated_request",
+ side_effect=APIError("API Error"),
+ ):
+ with pytest.raises(APIError, match="API Error"):
+ list_integration_transformer_revisions(
+ chronicle_client,
+ integration_name="test-integration",
+ transformer_id="t1",
+ )
+
+
+def test_delete_integration_transformer_revision_api_error(chronicle_client):
+ """Test delete_integration_transformer_revision handles API errors."""
+ with patch(
+ "secops.chronicle.integration.transformer_revisions.chronicle_request",
+ side_effect=APIError("Delete failed"),
+ ):
+ with pytest.raises(APIError, match="Delete failed"):
+ delete_integration_transformer_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ transformer_id="t1",
+ revision_id="rev1",
+ )
+
+
+def test_create_integration_transformer_revision_api_error(chronicle_client):
+ """Test create_integration_transformer_revision handles API errors."""
+ transformer = {"displayName": "Test"}
+
+ with patch(
+ "secops.chronicle.integration.transformer_revisions.chronicle_request",
+ side_effect=APIError("Creation failed"),
+ ):
+ with pytest.raises(APIError, match="Creation failed"):
+ create_integration_transformer_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ transformer_id="t1",
+ transformer=transformer,
+ )
+
+
+def test_rollback_integration_transformer_revision_api_error(chronicle_client):
+ """Test rollback_integration_transformer_revision handles API errors."""
+ with patch(
+ "secops.chronicle.integration.transformer_revisions.chronicle_request",
+ side_effect=APIError("Rollback failed"),
+ ):
+ with pytest.raises(APIError, match="Rollback failed"):
+ rollback_integration_transformer_revision(
+ chronicle_client,
+ integration_name="test-integration",
+ transformer_id="t1",
+ revision_id="rev1",
+ )
+
diff --git a/tests/chronicle/integration/test_transformers.py b/tests/chronicle/integration/test_transformers.py
new file mode 100644
index 00000000..43d8687e
--- /dev/null
+++ b/tests/chronicle/integration/test_transformers.py
@@ -0,0 +1,555 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+#
+"""Tests for Chronicle integration transformers functions."""
+
+from unittest.mock import Mock, patch
+
+import pytest
+
+from secops.chronicle.client import ChronicleClient
+from secops.chronicle.models import APIVersion
+from secops.chronicle.integration.transformers import (
+ list_integration_transformers,
+ get_integration_transformer,
+ delete_integration_transformer,
+ create_integration_transformer,
+ update_integration_transformer,
+ execute_integration_transformer_test,
+ get_integration_transformer_template,
+)
+from secops.exceptions import APIError
+
+
+@pytest.fixture
+def chronicle_client():
+ """Create a Chronicle client for testing."""
+ with patch("secops.auth.SecOpsAuth") as mock_auth:
+ mock_session = Mock()
+ mock_session.headers = {}
+ mock_auth.return_value.session = mock_session
+ return ChronicleClient(
+ customer_id="test-customer",
+ project_id="test-project",
+ default_api_version=APIVersion.V1ALPHA,
+ )
+
+
+# -- list_integration_transformers tests --
+
+
+def test_list_integration_transformers_success(chronicle_client):
+ """Test list_integration_transformers delegates to chronicle_paginated_request."""
+ expected = {
+ "transformers": [{"name": "t1"}, {"name": "t2"}],
+ "nextPageToken": "token",
+ }
+
+ with patch(
+ "secops.chronicle.integration.transformers.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated, patch(
+ "secops.chronicle.integration.transformers.format_resource_id",
+ return_value="My Integration",
+ ):
+ result = list_integration_transformers(
+ chronicle_client,
+ integration_name="My Integration",
+ page_size=10,
+ page_token="next-token",
+ )
+
+ assert result == expected
+
+ mock_paginated.assert_called_once_with(
+ chronicle_client,
+ api_version=APIVersion.V1ALPHA,
+ path="integrations/My Integration/transformers",
+ items_key="transformers",
+ page_size=10,
+ page_token="next-token",
+ extra_params={},
+ as_list=False,
+ )
+
+
+def test_list_integration_transformers_default_args(chronicle_client):
+ """Test list_integration_transformers with default args."""
+ expected = {"transformers": []}
+
+ with patch(
+ "secops.chronicle.integration.transformers.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_integration_transformers(
+ chronicle_client,
+ integration_name="test-integration",
+ )
+
+ assert result == expected
+
+ mock_paginated.assert_called_once_with(
+ chronicle_client,
+ api_version=APIVersion.V1ALPHA,
+ path="integrations/test-integration/transformers",
+ items_key="transformers",
+ page_size=None,
+ page_token=None,
+ extra_params={},
+ as_list=False,
+ )
+
+
+def test_list_integration_transformers_with_filter_order_expand(chronicle_client):
+ """Test list passes filter/orderBy/excludeStaging/expand in extra_params."""
+ expected = {"transformers": [{"name": "t1"}]}
+
+ with patch(
+ "secops.chronicle.integration.transformers.chronicle_paginated_request",
+ return_value=expected,
+ ) as mock_paginated:
+ result = list_integration_transformers(
+ chronicle_client,
+ integration_name="test-integration",
+ filter_string='displayName = "My Transformer"',
+ order_by="displayName",
+ exclude_staging=True,
+ expand="parameters",
+ )
+
+ assert result == expected
+
+ mock_paginated.assert_called_once_with(
+ chronicle_client,
+ api_version=APIVersion.V1ALPHA,
+ path="integrations/test-integration/transformers",
+ items_key="transformers",
+ page_size=None,
+ page_token=None,
+ extra_params={
+ "filter": 'displayName = "My Transformer"',
+ "orderBy": "displayName",
+ "excludeStaging": True,
+ "expand": "parameters",
+ },
+ as_list=False,
+ )
+
+
+# -- get_integration_transformer tests --
+
+
+def test_get_integration_transformer_success(chronicle_client):
+ """Test get_integration_transformer delegates to chronicle_request."""
+ expected = {"name": "transformer1", "displayName": "My Transformer"}
+
+ with patch(
+ "secops.chronicle.integration.transformers.chronicle_request",
+ return_value=expected,
+ ) as mock_request, patch(
+ "secops.chronicle.integration.transformers.format_resource_id",
+ return_value="test-integration",
+ ):
+ result = get_integration_transformer(
+ chronicle_client,
+ integration_name="test-integration",
+ transformer_id="transformer1",
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="GET",
+ endpoint_path="integrations/test-integration/transformers/transformer1",
+ api_version=APIVersion.V1ALPHA,
+ params=None,
+ )
+
+
+def test_get_integration_transformer_with_expand(chronicle_client):
+ """Test get_integration_transformer with expand parameter."""
+ expected = {"name": "transformer1"}
+
+ with patch(
+ "secops.chronicle.integration.transformers.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = get_integration_transformer(
+ chronicle_client,
+ integration_name="test-integration",
+ transformer_id="transformer1",
+ expand="parameters",
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="GET",
+ endpoint_path="integrations/test-integration/transformers/transformer1",
+ api_version=APIVersion.V1ALPHA,
+ params={"expand": "parameters"},
+ )
+
+
+# -- delete_integration_transformer tests --
+
+
+def test_delete_integration_transformer_success(chronicle_client):
+ """Test delete_integration_transformer delegates to chronicle_request."""
+ with patch(
+ "secops.chronicle.integration.transformers.chronicle_request",
+ return_value=None,
+ ) as mock_request, patch(
+ "secops.chronicle.integration.transformers.format_resource_id",
+ return_value="test-integration",
+ ):
+ delete_integration_transformer(
+ chronicle_client,
+ integration_name="test-integration",
+ transformer_id="transformer1",
+ )
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="DELETE",
+ endpoint_path="integrations/test-integration/transformers/transformer1",
+ api_version=APIVersion.V1ALPHA,
+ )
+
+
+# -- create_integration_transformer tests --
+
+
+def test_create_integration_transformer_minimal(chronicle_client):
+ """Test create_integration_transformer with minimal required fields."""
+ expected = {"name": "transformer1", "displayName": "New Transformer"}
+
+ with patch(
+ "secops.chronicle.integration.transformers.chronicle_request",
+ return_value=expected,
+ ) as mock_request, patch(
+ "secops.chronicle.integration.transformers.format_resource_id",
+ return_value="test-integration",
+ ):
+ result = create_integration_transformer(
+ chronicle_client,
+ integration_name="test-integration",
+ display_name="New Transformer",
+ script="def transform(data): return data",
+ script_timeout="60s",
+ enabled=True,
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once()
+ call_kwargs = mock_request.call_args[1]
+ assert call_kwargs["method"] == "POST"
+ assert (
+ call_kwargs["endpoint_path"]
+ == "integrations/test-integration/transformers"
+ )
+ assert call_kwargs["api_version"] == APIVersion.V1ALPHA
+ assert call_kwargs["json"]["displayName"] == "New Transformer"
+ assert call_kwargs["json"]["script"] == "def transform(data): return data"
+ assert call_kwargs["json"]["scriptTimeout"] == "60s"
+ assert call_kwargs["json"]["enabled"] is True
+
+
+def test_create_integration_transformer_with_all_fields(chronicle_client):
+ """Test create_integration_transformer with all optional fields."""
+ expected = {"name": "transformer1"}
+
+ with patch(
+ "secops.chronicle.integration.transformers.chronicle_request",
+ return_value=expected,
+ ) as mock_request:
+ result = create_integration_transformer(
+ chronicle_client,
+ integration_name="test-integration",
+ display_name="Full Transformer",
+ script="def transform(data): return data",
+ script_timeout="120s",
+ enabled=False,
+ description="Test transformer description",
+ parameters=[{"name": "param1", "type": "STRING"}],
+ usage_example="Example usage",
+ expected_output="Output format",
+ expected_input="Input format",
+ )
+
+ assert result == expected
+
+ call_kwargs = mock_request.call_args[1]
+ body = call_kwargs["json"]
+ assert body["displayName"] == "Full Transformer"
+ assert body["description"] == "Test transformer description"
+ assert body["parameters"] == [{"name": "param1", "type": "STRING"}]
+ assert body["usageExample"] == "Example usage"
+ assert body["expectedOutput"] == "Output format"
+ assert body["expectedInput"] == "Input format"
+
+
+# -- update_integration_transformer tests --
+
+
+def test_update_integration_transformer_display_name(chronicle_client):
+ """Test update_integration_transformer updates display name."""
+ expected = {"name": "transformer1", "displayName": "Updated Name"}
+
+ with patch(
+ "secops.chronicle.integration.transformers.chronicle_request",
+ return_value=expected,
+ ) as mock_request, patch(
+ "secops.chronicle.integration.transformers.build_patch_body",
+ return_value=({"displayName": "Updated Name"}, {"updateMask": "displayName"}),
+ ) as mock_build:
+ result = update_integration_transformer(
+ chronicle_client,
+ integration_name="test-integration",
+ transformer_id="transformer1",
+ display_name="Updated Name",
+ )
+
+ assert result == expected
+
+ mock_build.assert_called_once()
+ mock_request.assert_called_once()
+ call_kwargs = mock_request.call_args[1]
+ assert call_kwargs["method"] == "PATCH"
+ assert (
+ call_kwargs["endpoint_path"]
+ == "integrations/test-integration/transformers/transformer1"
+ )
+
+
+def test_update_integration_transformer_with_update_mask(chronicle_client):
+ """Test update_integration_transformer with explicit update mask."""
+ expected = {"name": "transformer1"}
+
+ with patch(
+ "secops.chronicle.integration.transformers.chronicle_request",
+ return_value=expected,
+ ) as mock_request, patch(
+ "secops.chronicle.integration.transformers.build_patch_body",
+ return_value=(
+ {"displayName": "New Name", "enabled": True},
+ {"updateMask": "displayName,enabled"},
+ ),
+ ):
+ result = update_integration_transformer(
+ chronicle_client,
+ integration_name="test-integration",
+ transformer_id="transformer1",
+ display_name="New Name",
+ enabled=True,
+ update_mask="displayName,enabled",
+ )
+
+ assert result == expected
+
+
+def test_update_integration_transformer_all_fields(chronicle_client):
+ """Test update_integration_transformer with all fields."""
+ expected = {"name": "transformer1"}
+
+ with patch(
+ "secops.chronicle.integration.transformers.chronicle_request",
+ return_value=expected,
+ ) as mock_request, patch(
+ "secops.chronicle.integration.transformers.build_patch_body",
+ return_value=(
+ {
+ "displayName": "Updated",
+ "script": "new script",
+ "scriptTimeout": "90s",
+ "enabled": False,
+ "description": "Updated description",
+ "parameters": [{"name": "p1"}],
+ "usageExample": "New example",
+ "expectedOutput": "New output",
+ "expectedInput": "New input",
+ },
+ {"updateMask": "displayName,script,scriptTimeout,enabled,description"},
+ ),
+ ):
+ result = update_integration_transformer(
+ chronicle_client,
+ integration_name="test-integration",
+ transformer_id="transformer1",
+ display_name="Updated",
+ script="new script",
+ script_timeout="90s",
+ enabled=False,
+ description="Updated description",
+ parameters=[{"name": "p1"}],
+ usage_example="New example",
+ expected_output="New output",
+ expected_input="New input",
+ )
+
+ assert result == expected
+
+
+# -- execute_integration_transformer_test tests --
+
+
+def test_execute_integration_transformer_test_success(chronicle_client):
+ """Test execute_integration_transformer_test delegates to chronicle_request."""
+ transformer = {
+ "displayName": "Test Transformer",
+ "script": "def transform(data): return data",
+ }
+ expected = {
+ "outputMessage": "Success",
+ "debugOutputMessage": "Debug info",
+ "resultValue": {"status": "ok"},
+ }
+
+ with patch(
+ "secops.chronicle.integration.transformers.chronicle_request",
+ return_value=expected,
+ ) as mock_request, patch(
+ "secops.chronicle.integration.transformers.format_resource_id",
+ return_value="test-integration",
+ ):
+ result = execute_integration_transformer_test(
+ chronicle_client,
+ integration_name="test-integration",
+ transformer=transformer,
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="POST",
+ endpoint_path="integrations/test-integration/transformers:executeTest",
+ api_version=APIVersion.V1ALPHA,
+ json={"transformer": transformer},
+ )
+
+
+# -- get_integration_transformer_template tests --
+
+
+def test_get_integration_transformer_template_success(chronicle_client):
+ """Test get_integration_transformer_template delegates to chronicle_request."""
+ expected = {
+ "script": "def transform(data):\n # Template code\n return data",
+ "displayName": "Template Transformer",
+ }
+
+ with patch(
+ "secops.chronicle.integration.transformers.chronicle_request",
+ return_value=expected,
+ ) as mock_request, patch(
+ "secops.chronicle.integration.transformers.format_resource_id",
+ return_value="test-integration",
+ ):
+ result = get_integration_transformer_template(
+ chronicle_client,
+ integration_name="test-integration",
+ )
+
+ assert result == expected
+
+ mock_request.assert_called_once_with(
+ chronicle_client,
+ method="GET",
+ endpoint_path="integrations/test-integration/transformers:fetchTemplate",
+ api_version=APIVersion.V1ALPHA,
+ )
+
+
+# -- Error handling tests --
+
+
+def test_list_integration_transformers_api_error(chronicle_client):
+ """Test list_integration_transformers handles API errors."""
+ with patch(
+ "secops.chronicle.integration.transformers.chronicle_paginated_request",
+ side_effect=APIError("API Error"),
+ ):
+ with pytest.raises(APIError, match="API Error"):
+ list_integration_transformers(
+ chronicle_client,
+ integration_name="test-integration",
+ )
+
+
+def test_get_integration_transformer_api_error(chronicle_client):
+ """Test get_integration_transformer handles API errors."""
+ with patch(
+ "secops.chronicle.integration.transformers.chronicle_request",
+ side_effect=APIError("Not found"),
+ ):
+ with pytest.raises(APIError, match="Not found"):
+ get_integration_transformer(
+ chronicle_client,
+ integration_name="test-integration",
+ transformer_id="nonexistent",
+ )
+
+
+def test_create_integration_transformer_api_error(chronicle_client):
+ """Test create_integration_transformer handles API errors."""
+ with patch(
+ "secops.chronicle.integration.transformers.chronicle_request",
+ side_effect=APIError("Creation failed"),
+ ):
+ with pytest.raises(APIError, match="Creation failed"):
+ create_integration_transformer(
+ chronicle_client,
+ integration_name="test-integration",
+ display_name="New Transformer",
+ script="def transform(data): return data",
+ script_timeout="60s",
+ enabled=True,
+ )
+
+
+def test_update_integration_transformer_api_error(chronicle_client):
+ """Test update_integration_transformer handles API errors."""
+ with patch(
+ "secops.chronicle.integration.transformers.chronicle_request",
+ side_effect=APIError("Update failed"),
+ ), patch(
+ "secops.chronicle.integration.transformers.build_patch_body",
+ return_value=({"displayName": "Updated"}, {"updateMask": "displayName"}),
+ ):
+ with pytest.raises(APIError, match="Update failed"):
+ update_integration_transformer(
+ chronicle_client,
+ integration_name="test-integration",
+ transformer_id="transformer1",
+ display_name="Updated",
+ )
+
+
+def test_delete_integration_transformer_api_error(chronicle_client):
+ """Test delete_integration_transformer handles API errors."""
+ with patch(
+ "secops.chronicle.integration.transformers.chronicle_request",
+ side_effect=APIError("Delete failed"),
+ ):
+ with pytest.raises(APIError, match="Delete failed"):
+ delete_integration_transformer(
+ chronicle_client,
+ integration_name="test-integration",
+ transformer_id="transformer1",
+ )
+
diff --git a/tests/chronicle/utils/test_format_utils.py b/tests/chronicle/utils/test_format_utils.py
index c71bda40..5610c2da 100644
--- a/tests/chronicle/utils/test_format_utils.py
+++ b/tests/chronicle/utils/test_format_utils.py
@@ -18,6 +18,7 @@
import pytest
from secops.chronicle.utils.format_utils import (
+ build_patch_body,
format_resource_id,
parse_json_list,
)
@@ -98,3 +99,107 @@ def test_parse_json_list_handles_empty_json_array() -> None:
def test_parse_json_list_handles_empty_list_input() -> None:
result = parse_json_list([], "filters")
assert result == []
+
+
+def test_build_patch_body_all_fields_set_builds_body_and_mask() -> None:
+ # All three fields provided — body and mask should include all of them
+ body, params = build_patch_body([
+ ("displayName", "display_name", "My Rule"),
+ ("enabled", "enabled", True),
+ ("severity", "severity", "HIGH"),
+ ])
+
+ assert body == {"displayName": "My Rule", "enabled": True, "severity": "HIGH"}
+ assert params == {"updateMask": "display_name,enabled,severity"}
+
+
+def test_build_patch_body_partial_fields_omits_none_values() -> None:
+ # Only non-None values should appear in body and mask
+ body, params = build_patch_body([
+ ("displayName", "display_name", "New Name"),
+ ("enabled", "enabled", None),
+ ("severity", "severity", None),
+ ])
+
+ assert body == {"displayName": "New Name"}
+ assert params == {"updateMask": "display_name"}
+
+
+def test_build_patch_body_no_fields_set_returns_empty_body_and_none_params() -> None:
+ # All values are None — body should be empty and params should be None
+ body, params = build_patch_body([
+ ("displayName", "display_name", None),
+ ("enabled", "enabled", None),
+ ])
+
+ assert body == {}
+ assert params is None
+
+
+def test_build_patch_body_empty_field_map_returns_empty_body_and_none_params() -> None:
+ # Empty field_map — nothing to build
+ body, params = build_patch_body([])
+
+ assert body == {}
+ assert params is None
+
+
+def test_build_patch_body_explicit_update_mask_overrides_auto_generated() -> None:
+ # An explicit update_mask should always win, even when fields are set
+ body, params = build_patch_body(
+ [
+ ("displayName", "display_name", "Name"),
+ ("enabled", "enabled", True),
+ ],
+ update_mask="display_name",
+ )
+
+ assert body == {"displayName": "Name", "enabled": True}
+ assert params == {"updateMask": "display_name"}
+
+
+def test_build_patch_body_explicit_update_mask_with_no_fields_set_still_applies() -> None:
+ # Explicit mask should appear even when all values are None (caller's intent)
+ body, params = build_patch_body(
+ [
+ ("displayName", "display_name", None),
+ ],
+ update_mask="display_name",
+ )
+
+ assert body == {}
+ assert params == {"updateMask": "display_name"}
+
+
+def test_build_patch_body_false_and_zero_are_not_treated_as_none() -> None:
+ # False-like but non-None values (False, 0, "") should be included in the body
+ body, params = build_patch_body([
+ ("enabled", "enabled", False),
+ ("count", "count", 0),
+ ("label", "label", ""),
+ ])
+
+ assert body == {"enabled": False, "count": 0, "label": ""}
+ assert params == {"updateMask": "enabled,count,label"}
+
+
+def test_build_patch_body_single_field_produces_single_entry_mask() -> None:
+ body, params = build_patch_body([
+ ("severity", "severity", "LOW"),
+ ])
+
+ assert body == {"severity": "LOW"}
+ assert params == {"updateMask": "severity"}
+
+
+def test_build_patch_body_mask_order_matches_field_map_order() -> None:
+ # The mask field order should mirror the order of field_map entries
+ body, params = build_patch_body([
+ ("z", "z_key", "z_val"),
+ ("a", "a_key", "a_val"),
+ ("m", "m_key", "m_val"),
+ ])
+
+ assert params == {"updateMask": "z_key,a_key,m_key"}
+ assert list(body.keys()) == ["z", "a", "m"]
+
diff --git a/tests/chronicle/utils/test_request_utils.py b/tests/chronicle/utils/test_request_utils.py
index 6f8687a2..c4e8b5b9 100644
--- a/tests/chronicle/utils/test_request_utils.py
+++ b/tests/chronicle/utils/test_request_utils.py
@@ -26,6 +26,7 @@
from secops.chronicle.utils.request_utils import (
DEFAULT_PAGE_SIZE,
chronicle_request,
+ chronicle_request_bytes,
chronicle_paginated_request,
)
from secops.exceptions import APIError
@@ -655,3 +656,181 @@ def test_chronicle_request_non_json_error_body_is_truncated(client: Mock) -> Non
assert "status=500" in msg
# Should not include the full 5000 chars, should include truncation marker
assert "truncated" in msg
+
+
+# ---------------------------------------------------------------------------
+# chronicle_request_bytes() tests
+# ---------------------------------------------------------------------------
+
+def test_chronicle_request_bytes_success_returns_content_and_stream_true(client: Mock) -> None:
+ resp = _mock_response(status_code=200, json_value={"ignored": True})
+ resp.content = b"PK\x03\x04...zip-bytes..." # ZIP magic prefix in real life
+ client.session.request.return_value = resp
+
+ out = chronicle_request_bytes(
+ client=client,
+ method="GET",
+ endpoint_path="integrations/foo:export",
+ api_version=APIVersion.V1BETA,
+ params={"alt": "media"},
+ headers={"Accept": "application/zip"},
+ )
+
+ assert out == b"PK\x03\x04...zip-bytes..."
+
+ client.base_url.assert_called_once_with(APIVersion.V1BETA)
+ client.session.request.assert_called_once_with(
+ method="GET",
+ url="https://example.test/chronicle/instances/instance-1/integrations/foo:export",
+ params={"alt": "media"},
+ headers={"Accept": "application/zip"},
+ timeout=None,
+ stream=True,
+ )
+
+
+def test_chronicle_request_bytes_builds_url_for_rpc_colon_prefix(client: Mock) -> None:
+ resp = _mock_response(status_code=200, json_value={"ok": True})
+ resp.content = b"binary"
+ client.session.request.return_value = resp
+
+ out = chronicle_request_bytes(
+ client=client,
+ method="POST",
+ endpoint_path=":exportSomething",
+ api_version=APIVersion.V1ALPHA,
+ )
+
+ assert out == b"binary"
+
+ _, kwargs = client.session.request.call_args
+ assert kwargs["url"] == "https://example.test/chronicle/instances/instance-1:exportSomething"
+ assert kwargs["stream"] is True
+
+
+def test_chronicle_request_bytes_accepts_multiple_expected_statuses_set(client: Mock) -> None:
+ resp = _mock_response(status_code=204, json_value=None)
+ resp.content = b""
+ client.session.request.return_value = resp
+
+ out = chronicle_request_bytes(
+ client=client,
+ method="DELETE",
+ endpoint_path="something",
+ api_version=APIVersion.V1ALPHA,
+ expected_status={200, 204},
+ )
+
+ assert out == b""
+
+
+def test_chronicle_request_bytes_status_mismatch_with_json_includes_json(client: Mock) -> None:
+ resp = _mock_response(status_code=400, json_value={"error": "bad"})
+ resp.content = b""
+ client.session.request.return_value = resp
+
+ with pytest.raises(
+ APIError,
+ match=r"API request failed: method=GET, "
+ r"url=https://example\.test/chronicle/instances/instance-1/curatedRules"
+ r", status=400, response={'error': 'bad'}",
+ ):
+ chronicle_request_bytes(
+ client=client,
+ method="GET",
+ endpoint_path="curatedRules",
+ api_version=APIVersion.V1ALPHA,
+ )
+
+
+def test_chronicle_request_bytes_status_mismatch_non_json_includes_text(client: Mock) -> None:
+ resp = _mock_response(status_code=500, json_raises=True, text="boom")
+ resp.content = b""
+ client.session.request.return_value = resp
+
+ with pytest.raises(
+ APIError,
+ match=r"API request failed: method=GET, "
+ r"url=https://example\.test/chronicle/instances/instance-1/curatedRules, "
+ r"status=500, response_text=boom",
+ ):
+ chronicle_request_bytes(
+ client=client,
+ method="GET",
+ endpoint_path="curatedRules",
+ api_version=APIVersion.V1ALPHA,
+ )
+
+
+def test_chronicle_request_bytes_custom_error_message_used(client: Mock) -> None:
+ resp = _mock_response(status_code=404, json_value={"message": "not found"})
+ resp.content = b""
+ client.session.request.return_value = resp
+
+ with pytest.raises(
+ APIError,
+ match=r"Failed to download export: method=GET, "
+ r"url=https://example\.test/chronicle/instances/instance-1/integrations/foo:export, "
+ r"status=404, response={'message': 'not found'}",
+ ):
+ chronicle_request_bytes(
+ client=client,
+ method="GET",
+ endpoint_path="integrations/foo:export",
+ api_version=APIVersion.V1BETA,
+ error_message="Failed to download export",
+ )
+
+
+def test_chronicle_request_bytes_wraps_requests_exception(client: Mock) -> None:
+ client.session.request.side_effect = requests.RequestException("no route to host")
+
+ with pytest.raises(APIError) as exc_info:
+ chronicle_request_bytes(
+ client=client,
+ method="GET",
+ endpoint_path="curatedRules",
+ api_version=APIVersion.V1ALPHA,
+ )
+
+ msg = str(exc_info.value)
+ assert "API request failed" in msg
+ assert "method=GET" in msg
+ assert "url=https://example.test/chronicle/instances/instance-1/curatedRules" in msg
+ assert "request_error=RequestException" in msg
+
+
+def test_chronicle_request_bytes_wraps_google_auth_error(client: Mock) -> None:
+ client.session.request.side_effect = GoogleAuthError("invalid_grant")
+
+ with pytest.raises(APIError) as exc_info:
+ chronicle_request_bytes(
+ client=client,
+ method="GET",
+ endpoint_path="curatedRules",
+ api_version=APIVersion.V1ALPHA,
+ )
+
+ msg = str(exc_info.value)
+ assert "Google authentication failed" in msg
+ assert "authentication_error=" in msg
+
+
+def test_chronicle_request_bytes_non_json_error_body_is_truncated(client: Mock) -> None:
+ long_text = "x" * 5000
+ resp = _mock_response(status_code=500, json_raises=True, text=long_text)
+ resp.content = b""
+ resp.headers = {"Content-Type": "text/plain"}
+ client.session.request.return_value = resp
+
+ with pytest.raises(APIError) as exc_info:
+ chronicle_request_bytes(
+ client=client,
+ method="GET",
+ endpoint_path="curatedRules",
+ api_version=APIVersion.V1ALPHA,
+ )
+
+ msg = str(exc_info.value)
+ assert "status=500" in msg
+ assert "truncated" in msg
\ No newline at end of file