Skip to content

Commit 2b229be

Browse files
committed
add get_cft_upload_url() and update client to use deleteEntityV2
1 parent 84fe389 commit 2b229be

File tree

3 files changed

+234
-16
lines changed

3 files changed

+234
-16
lines changed

examples/uploadData.py

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
import json
2+
from pathlib import Path
3+
import requests
4+
import os
5+
import time
6+
from colorama import init, Fore, Style
7+
from prompt_toolkit import prompt
8+
from prompt_toolkit.completion import WordCompleter
9+
10+
# Initialize colorama
11+
init(autoreset=True)
12+
13+
# Load configuration
14+
15+
script_dir = Path(__file__).parent.parent
16+
17+
with open(f'{script_dir}/infra/integrations/integration_outputs.json', 'r') as f:
18+
config = json.load(f)
19+
20+
# JupiterOne API details
21+
API_URL = 'https://api.us.jupiterone.io/graphql'
22+
HEADERS = {
23+
'Content-Type': 'application/json',
24+
'Authorization': f'Bearer {os.environ.get("JUPITERONE_API_KEY")}',
25+
'JupiterOne-Account': os.environ.get("JUPITERONE_ACCOUNT")
26+
}
27+
28+
def get_upload_url(integration_instance_id, filename, dataset_id):
29+
query = """
30+
mutation integrationFileTransferUploadUrl(
31+
$integrationInstanceId: String!
32+
$filename: String!
33+
$datasetId: String!
34+
) {
35+
integrationFileTransferUploadUrl(
36+
integrationInstanceId: $integrationInstanceId
37+
filename: $filename
38+
datasetId: $datasetId
39+
) {
40+
uploadUrl
41+
expiresIn
42+
}
43+
}
44+
"""
45+
variables = {
46+
"integrationInstanceId": integration_instance_id,
47+
"filename": filename,
48+
"datasetId": dataset_id
49+
}
50+
response = requests.post(API_URL, json={"query": query, "variables": variables}, headers=HEADERS)
51+
return response.json()['data']['integrationFileTransferUploadUrl']['uploadUrl']
52+
53+
def upload_file(upload_url, file_path):
54+
with open(file_path, 'rb') as f:
55+
response = requests.put(upload_url, data=f, headers={'Content-Type': 'text/csv'})
56+
return response.status_code
57+
58+
def invoke_integration(integration_instance_id):
59+
query = """
60+
mutation InvokeIntegrationInstance(
61+
$id: String!
62+
) {
63+
invokeIntegrationInstance(
64+
id: $id
65+
) {
66+
success
67+
integrationJobId
68+
}
69+
}
70+
"""
71+
variables = {"id": integration_instance_id}
72+
response = requests.post(API_URL, json={"query": query, "variables": variables}, headers=HEADERS)
73+
response_json = response.json()
74+
75+
if 'errors' in response_json:
76+
error = response_json['errors'][0]
77+
if error.get('extensions', {}).get('code') == 'ALREADY_EXECUTING_ERROR':
78+
return 'ALREADY_RUNNING'
79+
else:
80+
print(f"GraphQL error: {error['message']}")
81+
return False
82+
elif 'data' in response_json and response_json['data'] is not None:
83+
if 'invokeIntegrationInstance' in response_json['data']:
84+
return response_json['data']['invokeIntegrationInstance']['success']
85+
else:
86+
print(f"Unexpected response format: 'invokeIntegrationInstance' not found in data")
87+
return False
88+
else:
89+
print(f"Unexpected response format: {response_json}")
90+
return False
91+
92+
def print_colored(message, color=Fore.WHITE, style=Style.NORMAL):
93+
print(f"{style}{color}{message}")
94+
95+
def select_integrations():
96+
integration_names = list(config.keys())
97+
integration_names_lower = [name.lower() for name in integration_names]
98+
completer = WordCompleter(integration_names + ['all'])
99+
100+
print_colored("Available integrations:", Fore.CYAN, Style.BRIGHT)
101+
for name in integration_names:
102+
print_colored(f" - {name}", Fore.CYAN)
103+
104+
while True:
105+
selection = prompt(
106+
"Enter integration names to run (comma-separated) or 'all': ",
107+
completer=completer
108+
).strip().lower()
109+
110+
if selection == 'all':
111+
return integration_names
112+
113+
selected = [name.strip() for name in selection.split(',')]
114+
valid_selections = []
115+
invalid = []
116+
117+
for name in selected:
118+
if name in integration_names_lower:
119+
valid_selections.append(integration_names[integration_names_lower.index(name)])
120+
else:
121+
invalid.append(name)
122+
123+
if invalid:
124+
print_colored(f"Invalid integrations: {', '.join(invalid)}. Please try again.", Fore.RED)
125+
else:
126+
return valid_selections
127+
128+
def main():
129+
# Check if environment variables are set
130+
if not os.environ.get("JUPITERONE_API_KEY") or not os.environ.get("JUPITERONE_ACCOUNT"):
131+
print_colored("Error: JUPITERONE_API_KEY and JUPITERONE_ACCOUNT environment variables must be set.", Fore.RED, Style.BRIGHT)
132+
return
133+
134+
selected_integrations = select_integrations()
135+
136+
for integration_name in selected_integrations:
137+
integration_data = config[integration_name]
138+
integration_instance_id = integration_data['integrationInstanceId']
139+
source_files = integration_data['sourceFiles'].split(',')
140+
dataset_ids = integration_data['dataSetIds'].split(',')
141+
142+
print_colored(f"\nProcessing integration: {integration_name}", Fore.GREEN, Style.BRIGHT)
143+
144+
for filename, dataset_id in zip(source_files, dataset_ids):
145+
print_colored(f" Uploading {filename} for dataset {dataset_id}", Fore.YELLOW)
146+
upload_url = get_upload_url(integration_instance_id, filename, dataset_id)
147+
148+
file_path = os.path.join(script_dir, 'data', filename)
149+
150+
status_code = upload_file(upload_url, file_path)
151+
if status_code == 200:
152+
print_colored(f" ✔ Successfully uploaded {filename}", Fore.GREEN)
153+
else:
154+
print_colored(f" ✘ Failed to upload {filename}. Status code: {status_code}", Fore.RED)
155+
156+
print_colored(f" Invoking integration: {integration_name}", Fore.YELLOW)
157+
try:
158+
result = invoke_integration(integration_instance_id)
159+
if result == True:
160+
print_colored(f" ✔ Successfully invoked integration: {integration_name}", Fore.GREEN)
161+
elif result == 'ALREADY_RUNNING':
162+
print_colored(f" ⚠ Integration {integration_name} is already running. Skipping.", Fore.YELLOW)
163+
else:
164+
print_colored(f" ✘ Failed to invoke integration: {integration_name}", Fore.RED)
165+
except Exception as e:
166+
print_colored(f" ✘ Error invoking integration {integration_name}: {str(e)}", Fore.RED)
167+
168+
print() # Empty line for readability
169+
time.sleep(5) # Wait for 5 seconds before the next integration
170+
171+
if __name__ == "__main__":
172+
main()

jupiterone/client.py

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -490,15 +490,19 @@ def create_entity(self, **kwargs) -> Dict:
490490
response = self._execute_query(query=CREATE_ENTITY, variables=variables)
491491
return response["data"]["createEntity"]
492492

493-
def delete_entity(self, entity_id: str = None) -> Dict:
494-
"""Deletes an entity from the graph. Note this is a hard delete.
493+
def delete_entity(self, entity_id: str = None, timestamp: int = None, hard_delete: bool = True) -> Dict:
494+
"""Deletes an entity from the graph.
495495
496496
args:
497497
entity_id (str): Entity ID for entity to delete
498+
timestamp (int, optional): Timestamp for the deletion. Defaults to None.
499+
hard_delete (bool): Whether to perform a hard delete. Defaults to True.
498500
"""
499-
variables = {"entityId": entity_id}
501+
variables = {"entityId": entity_id, "hardDelete": hard_delete}
502+
if timestamp:
503+
variables["timestamp"] = timestamp
500504
response = self._execute_query(DELETE_ENTITY, variables=variables)
501-
return response["data"]["deleteEntity"]
505+
return response["data"]["deleteEntityV2"]
502506

503507
def update_entity(self, entity_id: str = None, properties: Dict = None) -> Dict:
504508
"""
@@ -1517,3 +1521,49 @@ def update_entity_v2(self, entity_id: str = None, properties: Dict = None) -> Di
15171521

15181522
response = self._execute_query(UPDATE_ENTITYV2, variables=variables)
15191523
return response["data"]["updateEntityV2"]
1524+
1525+
def get_cft_upload_url(self, integration_instance_id: str, filename: str, dataset_id: str) -> Dict:
1526+
"""
1527+
Get an upload URL for Custom File Transfer integration.
1528+
1529+
args:
1530+
integration_instance_id (str): The integration instance ID
1531+
filename (str): The filename to upload
1532+
dataset_id (str): The dataset ID for the upload
1533+
1534+
Returns:
1535+
Dict: Response containing uploadUrl and expiresIn
1536+
1537+
Example:
1538+
upload_info = j1_client.get_cft_upload_url(
1539+
integration_instance_id="123e4567-e89b-12d3-a456-426614174000",
1540+
filename="data.csv",
1541+
dataset_id="dataset-123"
1542+
)
1543+
upload_url = upload_info['uploadUrl']
1544+
"""
1545+
query = """
1546+
mutation integrationFileTransferUploadUrl(
1547+
$integrationInstanceId: String!
1548+
$filename: String!
1549+
$datasetId: String!
1550+
) {
1551+
integrationFileTransferUploadUrl(
1552+
integrationInstanceId: $integrationInstanceId
1553+
filename: $filename
1554+
datasetId: $datasetId
1555+
) {
1556+
uploadUrl
1557+
expiresIn
1558+
}
1559+
}
1560+
"""
1561+
1562+
variables = {
1563+
"integrationInstanceId": integration_instance_id,
1564+
"filename": filename,
1565+
"datasetId": dataset_id
1566+
}
1567+
1568+
response = self._execute_query(query, variables)
1569+
return response["data"]["integrationFileTransferUploadUrl"]

jupiterone/constants.py

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,18 +28,14 @@
2828
}
2929
"""
3030
DELETE_ENTITY = """
31-
mutation DeleteEntity($entityId: String!, $timestamp: Long) {
32-
deleteEntity(entityId: $entityId, timestamp: $timestamp) {
33-
entity {
34-
_id
35-
}
36-
vertex {
37-
id
38-
entity {
39-
_id
40-
}
41-
properties
42-
}
31+
mutation DeleteEntity($entityId: String!, $timestamp: Long, $hardDelete: Boolean) {
32+
deleteEntityV2(
33+
entityId: $entityId
34+
timestamp: $timestamp
35+
hardDelete: $hardDelete
36+
) {
37+
entity
38+
__typename
4339
}
4440
}
4541
"""

0 commit comments

Comments
 (0)