From c4d195386d39d933cbd6a8df2cf7c2417723c7f7 Mon Sep 17 00:00:00 2001 From: SeaBlooms Date: Thu, 2 Oct 2025 11:32:59 -0600 Subject: [PATCH 1/3] removing deprecated 'CREATE_OR_UPDATE' sync_mode --- README.md | 14 ++- examples/04_integration_management.py | 2 +- examples/bulk_upload.py | 72 ++++++++++++++ examples/examples.py | 4 +- examples/sync_job_workflow.py | 136 ++++++++++++++++++++++++++ jupiterone/client.py | 2 +- 6 files changed, 222 insertions(+), 8 deletions(-) create mode 100644 examples/bulk_upload.py create mode 100644 examples/sync_job_workflow.py diff --git a/README.md b/README.md index a043abb..ef67a8a 100644 --- a/README.md +++ b/README.md @@ -357,11 +357,17 @@ instance = j1.create_integration_instance( ```python # Start sync job for an integration instance -sync_job = j1.start_sync_job(instance_id='') -print(f"Started sync job: {sync_job['job']['_id']}") +sync_job = j1.start_sync_job( + instance_id=instance_id, + sync_mode="PATCH", + source="integration-external" +) + +sync_job_id = sync_job['job'].get('id') +print(f"Started sync job: {sync_job_id}") # The returned job ID is used for subsequent operations -job_id = sync_job['job']['_id'] +job_id = sync_job_id ``` ##### Upload Batch of Entities @@ -514,7 +520,7 @@ print(f"Uploaded {len(combined_payload['entities'])} entities and {len(combined_ ```python # Finalize the sync job result = j1.finalize_sync_job(instance_job_id='') -print(f"Finalized sync job: {result['job']['_id']}") +print(f"Finalized sync job: {result['job'].get('id')}") # Check job status if result['job']['status'] == 'COMPLETED': diff --git a/examples/04_integration_management.py b/examples/04_integration_management.py index d35c751..8e96f19 100644 --- a/examples/04_integration_management.py +++ b/examples/04_integration_management.py @@ -149,7 +149,7 @@ def sync_job_examples(j1, instance_id): try: sync_job = j1.start_sync_job( instance_id=instance_id, - sync_mode="CREATE_OR_UPDATE", + sync_mode="PATCH", source="api" ) job_id = sync_job['job']['_id'] diff --git a/examples/bulk_upload.py b/examples/bulk_upload.py new file mode 100644 index 0000000..b4327b5 --- /dev/null +++ b/examples/bulk_upload.py @@ -0,0 +1,72 @@ +from jupiterone.client import JupiterOneClient +import random +import time +import os +import json + +account = os.environ.get("JUPITERONE_ACCOUNT") +token = os.environ.get("JUPITERONE_TOKEN") +url = "https://graphql.us.jupiterone.io" + +j1 = JupiterOneClient(account=account, token=token, url=url) + +instance_id = "e7113c37-1ea8-4d00-9b82-c24952e70916" + +sync_job = j1.start_sync_job( + instance_id=instance_id, + sync_mode="PATCH", + source="integration-external" + ) + +print(sync_job) +sync_job_id = sync_job['job'].get('id') + +# Prepare entities payload +entities_payload = [ + { + "_key": "server-001", + "_type": "aws_ec2_instance", + "_class": "Host", + "displayName": "web-server-001", + "instanceId": "i-1234567890abcdef0", + "instanceType": "t3.micro", + "state": "running", + "tag.Environment": "production", + "tag.Team": "engineering" + }, + { + "_key": "server-002", + "_type": "aws_ec2_instance", + "_class": "Host", + "displayName": "web-server-002", + "instanceId": "i-0987654321fedcba0", + "instanceType": "t3.small", + "state": "running", + "tag.Environment": "staging", + "tag.Team": "engineering" + }, + { + "_key": "database-001", + "_type": "aws_rds_instance", + "_class": "Database", + "displayName": "prod-database", + "dbInstanceIdentifier": "prod-db", + "engine": "postgres", + "dbInstanceClass": "db.t3.micro", + "tag.Environment": "production", + "tag.Team": "data" + } +] + +# Upload entities batch +result = j1.upload_entities_batch_json( + instance_job_id=sync_job_id, + entities_list=entities_payload +) +print(f"Uploaded {len(entities_payload)} entities") +print(result) + +# Finalize the sync job +result = j1.finalize_sync_job(instance_job_id=sync_job_id) +print(f"Finalized sync job: {result['job']['id']}") + diff --git a/examples/examples.py b/examples/examples.py index 7e31763..061e509 100644 --- a/examples/examples.py +++ b/examples/examples.py @@ -124,9 +124,9 @@ integration_instance_id = "" # start_sync_job -# sync_mode can be "DIFF", "CREATE_OR_UPDATE", or "PATCH" +# sync_mode can be "DIFF" or "PATCH" start_sync_job_r = j1.start_sync_job(instance_id=integration_instance_id, - sync_mode='CREATE_OR_UPDATE', + sync_mode='PATCH', source='integration-external') print("start_sync_job()") print(start_sync_job_r) diff --git a/examples/sync_job_workflow.py b/examples/sync_job_workflow.py new file mode 100644 index 0000000..693d860 --- /dev/null +++ b/examples/sync_job_workflow.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 +""" +JupiterOne Sync Job Workflow Example + +This example demonstrates the core synchronization job workflow: +1. Start a sync job +2. Upload entities batch +3. Finalize the sync job + +This is a standalone script that can be run independently to test +the sync job functionality. +""" + +import os +import sys +from jupiterone.client import JupiterOneClient + +def main(): + """Main function to demonstrate sync job workflow.""" + + # Initialize JupiterOne client + # You can set these as environment variables or replace with your values + api_token = os.getenv('JUPITERONE_API_TOKEN') + account_id = os.getenv('JUPITERONE_ACCOUNT_ID') + + if not api_token or not account_id: + print("Error: Please set JUPITERONE_API_TOKEN and JUPITERONE_ACCOUNT_ID environment variables") + print("Example:") + print(" export JUPITERONE_API_TOKEN='your-api-token'") + print(" export JUPITERONE_ACCOUNT_ID='your-account-id'") + sys.exit(1) + + # Create JupiterOne client + j1 = JupiterOneClient(api_token=api_token, account_id=account_id) + + print("=== JupiterOne Sync Job Workflow Example ===\n") + + # You'll need to replace this with an actual integration instance ID + instance_id = os.getenv('JUPITERONE_INSTANCE_ID') + if not instance_id: + print("Error: Please set JUPITERONE_INSTANCE_ID environment variable") + print("Example:") + print(" export JUPITERONE_INSTANCE_ID='your-integration-instance-id'") + sys.exit(1) + + try: + # Step 1: Start sync job + print("1. Starting synchronization job...") + sync_job = j1.start_sync_job( + instance_id=instance_id, + sync_mode="PATCH", + source="integration-external" + ) + + sync_job_id = sync_job['job'].get('id') + print(f"✓ Started sync job: {sync_job_id}") + print(f" Status: {sync_job['job']['status']}") + print() + + # Step 2: Upload entities batch + print("2. Uploading entities batch...") + + # Sample entities payload + entities_payload = [ + { + "_key": "example-server-001", + "_type": "example_server", + "_class": "Host", + "displayName": "Example Server 001", + "hostname": "server-001.example.com", + "ipAddress": "192.168.1.100", + "operatingSystem": "Linux", + "tag.Environment": "development", + "tag.Team": "engineering", + "tag.Purpose": "web_server" + }, + { + "_key": "example-server-002", + "_type": "example_server", + "_class": "Host", + "displayName": "Example Server 002", + "hostname": "server-002.example.com", + "ipAddress": "192.168.1.101", + "operatingSystem": "Linux", + "tag.Environment": "staging", + "tag.Team": "engineering", + "tag.Purpose": "database_server" + }, + { + "_key": "example-database-001", + "_type": "example_database", + "_class": "Database", + "displayName": "Example Database 001", + "databaseName": "app_db", + "engine": "postgresql", + "version": "13.4", + "tag.Environment": "development", + "tag.Team": "data" + } + ] + + # Upload entities + upload_result = j1.upload_entities_batch_json( + instance_job_id=sync_job_id, + entities_list=entities_payload + ) + print(f"✓ Uploaded {len(entities_payload)} entities successfully") + print(f" Upload result: {upload_result}") + print() + + # Step 3: Finalize sync job + print("3. Finalizing synchronization job...") + finalize_result = j1.finalize_sync_job(instance_job_id=sync_job_id) + + finalize_job_id = finalize_result['job'].get('id') + print(f"✓ Finalized sync job: {finalize_job_id}") + print(f" Status: {finalize_result['job']['status']}") + + # Check final status + if finalize_result['job']['status'] == 'COMPLETED': + print("✓ Sync job completed successfully!") + elif finalize_result['job']['status'] == 'FAILED': + error_msg = finalize_result['job'].get('error', 'Unknown error') + print(f"✗ Sync job failed: {error_msg}") + else: + print(f"ℹ Sync job status: {finalize_result['job']['status']}") + + print("\n=== Sync Job Workflow Complete ===") + + except Exception as e: + print(f"✗ Error during sync job workflow: {e}") + sys.exit(1) + +if __name__ == "__main__": + main() + diff --git a/jupiterone/client.py b/jupiterone/client.py index 721c843..e13cecf 100644 --- a/jupiterone/client.py +++ b/jupiterone/client.py @@ -662,7 +662,7 @@ def start_sync_job( args: instance_id (str): The "integrationInstanceId" request param for synchronization job - sync_mode (str): The "syncMode" request body property for synchronization job. "DIFF", "CREATE_OR_UPDATE", or "PATCH" + sync_mode (str): The "syncMode" request body property for synchronization job. "DIFF" or "PATCH" source (str): The "source" request body property for synchronization job. "api" or "integration-external" """ endpoint = "/persister/synchronization/jobs" From ccd685d28d0919a1a0bc35cb839180db43804548 Mon Sep 17 00:00:00 2001 From: SeaBlooms Date: Thu, 2 Oct 2025 11:43:19 -0600 Subject: [PATCH 2/3] fix validation --- examples/bulk_upload.py | 3 --- examples/sync_job_workflow.py | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/examples/bulk_upload.py b/examples/bulk_upload.py index b4327b5..a8ac5ea 100644 --- a/examples/bulk_upload.py +++ b/examples/bulk_upload.py @@ -1,8 +1,5 @@ from jupiterone.client import JupiterOneClient -import random -import time import os -import json account = os.environ.get("JUPITERONE_ACCOUNT") token = os.environ.get("JUPITERONE_TOKEN") diff --git a/examples/sync_job_workflow.py b/examples/sync_job_workflow.py index 693d860..650fc12 100644 --- a/examples/sync_job_workflow.py +++ b/examples/sync_job_workflow.py @@ -31,7 +31,7 @@ def main(): sys.exit(1) # Create JupiterOne client - j1 = JupiterOneClient(api_token=api_token, account_id=account_id) + j1 = JupiterOneClient(token=api_token, account=account_id) print("=== JupiterOne Sync Job Workflow Example ===\n") From 06156f8b5528bc4ee88ad3f9464e1e097d873852 Mon Sep 17 00:00:00 2001 From: SeaBlooms Date: Thu, 2 Oct 2025 13:14:20 -0600 Subject: [PATCH 3/3] PATCH sync jobs cannot target relationships. --- examples/sync_job_workflow.py | 5 +++++ jupiterone/client.py | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/examples/sync_job_workflow.py b/examples/sync_job_workflow.py index 650fc12..a487583 100644 --- a/examples/sync_job_workflow.py +++ b/examples/sync_job_workflow.py @@ -9,6 +9,10 @@ This is a standalone script that can be run independently to test the sync job functionality. + +Note: This example uses PATCH sync mode, which is suitable for entity-only +uploads. If you need to upload relationships, use DIFF sync mode instead. +See: https://docs.jupiterone.io/reference/pipeline-upgrade#patch-sync-jobs-cannot-target-relationships """ import os @@ -46,6 +50,7 @@ def main(): try: # Step 1: Start sync job print("1. Starting synchronization job...") + print(" Note: Using PATCH mode (entities only). Use DIFF mode if uploading relationships.") sync_job = j1.start_sync_job( instance_id=instance_id, sync_mode="PATCH", diff --git a/jupiterone/client.py b/jupiterone/client.py index e13cecf..076fd04 100644 --- a/jupiterone/client.py +++ b/jupiterone/client.py @@ -664,6 +664,13 @@ def start_sync_job( instance_id (str): The "integrationInstanceId" request param for synchronization job sync_mode (str): The "syncMode" request body property for synchronization job. "DIFF" or "PATCH" source (str): The "source" request body property for synchronization job. "api" or "integration-external" + + Note: + IMPORTANT: PATCH sync jobs cannot target relationships. If your sync job involves creating + or updating relationships, you must use "DIFF" sync_mode instead of "PATCH". This is due + to the JupiterOne data pipeline upgrade. + + For more information, see: https://docs.jupiterone.io/reference/pipeline-upgrade#patch-sync-jobs-cannot-target-relationships """ endpoint = "/persister/synchronization/jobs"