API usage
This page describes how to configure the following in the AutoPatcher API:
- managing machines,
- managing plans and pipelines,
- managing notification groups,
- managing RBAC (permissions, roles).
Introduction
AutoPatcher provides GraphQL API that allows to automate operations. To interact with the
API you need to generate an API key (see here) and provide it in the x-api-key
header.
Below is an example GraphQL query using cURL tool (provide your API key and adjust the data using prepared query):
curl --location --request POST 'https://api.autopatcher.nordcloudapp.com/' \--header 'x-api-key: <<YOUR_API_KEY>>' \--header 'Content-Type: application/json' \--data-raw '{"query":"mutation createMachine {\n createMachine(\n input: {\n customer_id: \"<<CUSTOMER_ID>>\"\n name: \"Test machine\"\n access: {\n ssm_machine_id: \"i-123123123\"\n }\n }\n ) {\n id\n }\n}","variables":{}}'
Machines
Adding a new machine
To add a new machine, run the createMachine mutation:
mutation createMachine {createMachine(input: {customer_id: "<<CUSTOMER_ID>>"name: "Test machine"access: {ssm_machine_id: "i-123123123"iam_role: "arn:aws:iam::12345:role/test-role"region: "eu-west-1"}description: "Test description"reboot_policy: IF_NEEDEDpublished_days_old: "5"s3_custom_script: "s3://bucket-name/example_script.sh"metadata: [{key: "key1"value: "value1"}{key: "key2"value: "value2"}{key: "key3"value: "value3"}]}) {id}}
Listing machines
To list machines page (the number of returned machines depend on limit
field.
To get the next page pass the continuation_token
value from the previous API call to the input
field),
run the machinesBatch query:
query machines {machinesBatch(input: {customer_id: "<<CUSTOMER_ID>>",limit: 1}) {machines {idcustomer_idprovidernamedescriptions3_custom_scriptcustom_document_arnupdate_severitypublished_days_oldupdate_categoryping_statusping_errorinclude_kbsexclude_kbscreated_atupdated_atreboot_policyplanspipelineslast_patching {event_idstatus}access {ssm_machine_idregion}azure_info {subscription_idinstance_id}ssm_metadata {activation_idresource_type}added_dynamicallyassigneeinstance_idhost_collector_invokedlast_package_scan {timeerror}metadata {keyvalue}}continuation_token}}
Removing a machine
To remove single machines, run the deleteMachine mutation as follows:
mutation deleteMachine {deleteMachine(customer_id: "<<CUSTOMER_ID>>", id: "<<MACHINE_ID>>")}
Plans
Adding a new plan with specified machines list
To add a new plan with predefined machines list, use createPlan mutation with machines
field filled:
mutation createPlan {createPlan(input: {customer_id: "<<CUSTOMER_ID>>"cron_window_start: "0 0 6 2 *"window_length: 10name: "Plan example"description: "Test description"s3_custom_script: "s3://bucket-name/example_script.sh"on_hold_start: "2022-02-08"on_hold_end: "2022-02-09"dry_run: truelinux_security_only: trueupcoming_notification_time: 10manual_approval: trueparallel: 7notification_groups: ["<<NOTIFICATION_GROUP_1_ID>>","<<NOTIFICATION_GROUP_2_ID>>"]pre_hooks: [{type: awssource: "arn:aws:lambda:eu-west-1:123456789012:function:lambda1"method: GET}{type: azuresource: "https://azure-prehook.azurewebsites.net/api/prehook2"method: POST}]post_hooks: [{type: azuresource: "https://azure-posthook.azurewebsites.net/api/posthook2"method: POST}]webhook_inputs: [{trigger_type: eventpayload: "{}"payload_type: "itsm_project_key"}{trigger_type: reportpayload: "apiKey"payload_type: "itsm_project_key"}]metadata: [{key: "tag1"value: "value1"}{key: "tag2"value: "random metadata"}]machines: [{id: "<<MACHINE_ID_1>>"name: "ExampleName"order: 1exclude: trues3_custom_script: "s3://bucket-name/example_script.sh"pre_host_hooks: [{type: awssource: "arn:aws:lambda:eu-west-1:123456789012:function:lambda1"}{type: azuresource: "https://azure-prehook.azurewebsites.net/api/prehook2"}]post_host_hooks: [{type: scriptsource: "s3://bucket-name/posthook_script.sh"}{type: azuresource: "https://azure-posthook.azurewebsites.net/api/posthook2"}]}{id: "<<MACHINE_ID_2>>"name: "SecondMachine"order: 2exclude: false}]}) {id}}
Adding a new plan with tags list
To add a new plan with dynamic machines list based on tags list, use createPlan mutation with tag_list
field filled:
mutation createPlan {createPlan(input: {customer_id: "<<CUSTOMER_ID>>"cron_window_start: "0 0 6 2 *"window_length: 10name: "Plan example"description: "Test description"s3_custom_script: "s3://bucket-name/example_script.sh"on_hold_start: "2022-02-08"on_hold_end: "2022-02-09"dry_run: truelinux_security_only: trueupcoming_notification_time: 10manual_approval: trueparallel: 7notification_groups: ["<<NOTIFICATION_GROUP_1_ID>>","<<NOTIFICATION_GROUP_2_ID>>"]pre_hooks: [{type: awssource: "arn:aws:lambda:eu-west-1:123456789012:function:lambda1"method: GET}{type: azuresource: "https://azure-prehook.azurewebsites.net/api/prehook2"method: POST}]post_hooks: [{type: azuresource: "https://azure-posthook.azurewebsites.net/api/posthook2"method: POST}]webhook_inputs: [{trigger_type: eventpayload: "{}"payload_type: "itsm_project_key"}{trigger_type: reportpayload: "apiKey"payload_type: "itsm_project_key"}]metadata: [{key: "tag1"value: "value1"}{key: "tag2"value: "random metadata"}]machines_tag: {tag_list: {key: "tagKey"values: ["value1""value2""value3"]}iam_role_list: ["arn:aws:iam:us-east-1:123456789:role/my-role"]pre_host_hooks: [{type: awssource: "arn:aws:lambda:eu-west-1:123456789012:function:lambda1"}{type: azuresource: "https://azure-prehook.azurewebsites.net/api/prehook2"}]post_host_hooks: [{type: scriptsource: "s3://bucket-name/posthook_script.sh"}{type: azuresource: "https://azure-posthook.azurewebsites.net/api/posthook2"}]reboot_policy: IF_NEEDEDregions: ["us-east-1"]s3_custom_script: "s3://bucket/key"update_existing: falseinclude_stopped: false}}) {id}}
Adding a new plan with tag conditions
To add a new plan with dynamic machines list based on tag conditions, use createPlan mutation with tag_condition
field filled:
mutation createPlan {createPlan(input: {customer_id: "47f7678ac58849ecabe0c07d2c2faa2e"cron_window_start: "0 0 6 2 *"window_length: 10name: "Plan example"description: "Test description"s3_custom_script: "s3://bucket-name/example_script.sh"on_hold_start: "2022-02-08"on_hold_end: "2022-02-09"dry_run: truelinux_security_only: trueupcoming_notification_time: 10manual_approval: trueparallel: 7notification_groups: ["<<NOTIFICATION_GROUP_1_ID>>","<<NOTIFICATION_GROUP_2_ID>>"]pre_hooks: [{type: awssource: "arn:aws:lambda:eu-west-1:123456789012:function:lambda1"method: GET}{type: azuresource: "https://azure-prehook.azurewebsites.net/api/prehook2"method: POST}]post_hooks: [{type: azuresource: "https://azure-posthook.azurewebsites.net/api/posthook2"method: POST}]webhook_inputs: [{trigger_type: eventpayload: "{}"payload_type: "itsm_project_key"}{trigger_type: reportpayload: "apiKey"payload_type: "itsm_project_key"}]metadata: [{key: "tag1"value: "value1"}{key: "tag2"value: "random metadata"}]machines_tag: {tag_condition: {expression: "key1=group-a AND (key2=:v1 OR !:k3=:v2,v4,:v5)"placeholders: [{key: "v1"value: "product1"}{key: "k3"value: "stage"}{key: "v2"value: "dev"}{key: "v5"value: "prod"}]}iam_role_list: ["arn:aws:iam:us-east-1:123456789:role/my-role"]pre_host_hooks: [{type: awssource: "arn:aws:lambda:eu-west-1:123456789012:function:lambda1"}{type: azuresource: "https://azure-prehook.azurewebsites.net/api/prehook2"}]post_host_hooks: [{type: scriptsource: "s3://bucket-name/posthook_script.sh"}{type: azuresource: "https://azure-posthook.azurewebsites.net/api/posthook2"}]reboot_policy: IF_NEEDEDregions: ["us-east-1"]s3_custom_script: "s3://bucket/key"update_existing: falseinclude_stopped: false}}) {id}}
Listing plans
To list plans page (the number of returned plans depend on limit
field.
To get the next page pass the continuation_token
value from the previous API call to the input
field),
use plansBatch query as follows:
query plans {plansBatch(input: { customer_id: "<<CUSTOMER_ID>>", limit: 1 }) {plans {idcustomer_idcron_window_startnamedescriptionmanual_approvaldry_runlinux_security_onlyon_hold_starton_hold_endupcoming_notification_timeparallelwindow_lengthcreated_atupdated_atnotification_groupsmachines {id}pre_hooks {typename}post_hooks {typename}s3_custom_scriptwebhook_inputs {trigger_typepayload}metadata {keyvalue}}continuation_token}}
Pipelines
Adding a new pipeline
To add a new pipeline with different steps, use a createPipeline mutation as follows:
mutation createPipeline {createPipeline(input: {customer_id: "<<CUSTOMER_ID>>"name: "Example pipeline"description: "Also optional description"cron_window_start: "0 0 */7 * *"notification_groups: ["<<NOTIFICATION_GROUP_1>>""<<NOTIFICATION_GROUP_2>>"]metadata: [{ key: "tag1", value: "value1" }{ key: "tag2", value: "value2" }]enable_baseline: trueupcoming_notification_time: 10steps: [{description: "First step of the pipeline"stage: "DEV"plans: [{id: "<<PLAN_ID_1>>"window_length: 90name: "First step first plan"description: "Just description"dry_run: truemachines_tag: {tag_condition: {placeholders: [{ key: "tag1", value: "value1" }{ key: "tag2", value: "value2" }]expression: "SLA=gold,GOLD,SILVER,BRONZE AND NOT APPLICATION-NAME=:tag1,APP1,:tag2,APP2"}iam_role: "arn:aws:iam:123456789012::policy/test-role-policy"}}]}{description: "Second step"step_delay: 10080policy: patch_anywaystage: "PROD"plans: [{id: "<<PLAN_ID_2>>"window_length: 150name: "First plan of second step"description: "Example description"machines: [{ id: "<<MACHINE_ID_1>>", name: "Machine 1", order: 1 }{ id: "<<MACHINE_ID_2>>", name: "Machine 2", order: 2 }]}]}{step_delay: 2plans: [{id: "<<PLAN_ID_3>>"window_length: 180name: "CRON"parallel: 3machines: [{ id: "<<MACHINE_ID_3>>", name: "Machine 3", order: 1 }{id: "<<MACHINE_ID_4>>"name: "Machine 4"order: 2pre_host_hooks: [{type: awssource: "arn:aws:lambda:eu-west-1:123456789012:function:lambda1"}{type: azuresource: "https://azure-prehook.azurewebsites.net/api/prehook2"}]s3_custom_script: "s3://bucket-name/example_script.sh"}]pre_hooks: [{type: awssource: "arn:aws:lambda:eu-west-1:123456789012:function:lambda1"method: GET}{type: azuresource: "https://azure-prehook.azurewebsites.net/api/prehook2"method: POST}]post_hooks: [{type: azuresource: "https://azure-posthook.azurewebsites.net/api/posthook2"method: POST}]}]}]}) {id}}
Listing all pipelines
To list pipelines (the number of returned pipelines depend on limit
field.
To get the next page pass the continuation_token
value from the previous API call to the input
field),
use pipelinesBatch query:
query pipelines {pipelinesBatch(input: { customer_id: "<<CUSTOMER_ID>>", limit: 100 }) {pipelines {idcustomer_idnamedescriptionmetadata {keyvalue}notification_groupscron_window_startupcoming_notification_timeenable_baselinesteps {descriptionstep_delayrun_immediatelypolicystageplans {idwindow_lengthnamedescriptionmetadata {keyvalue}paralleldry_runlinux_security_onlymachines {idnameorderpre_host_hooks {typesourcemethodstatus_code}post_host_hooks {typesourcemethodstatus_code}s3_custom_scriptexclude}pre_hooks {typesourcemethodstatus_code}post_hooks {typesourcemethodstatus_code}machines_tag {tag_list {keyvalues}tag_condition {placeholders {keyvalue}expression}pre_host_hooks {typesourcemethodstatus_code}post_host_hooks {typesourcemethodstatus_code}update_existingreboot_policyregionss3_custom_script}s3_custom_script}}}}}
Notification groups
Adding a new notification group
To add a new notification group with different notification channels, use createNotificationGroup mutation:
mutation createNotificationGroup {createNotificationGroup(input: {customer_id: "<<CUSTOMER_ID>>"name: "Test notification group"levels: [INFO]notifications: [{type: SLACKweb_hook_url: "https://api.example.com/v1/webhooks"channel: "Notification-channel"}{type: PAGER_DUTYrouting_key: "12345"events: [ERROR]levels: [INFO]override_settings: true}{type: EMAILemail_config: {address: "first@example.com"}}{type: EMAILemail_config: {address: "second@example.com"is_external: true}events: [ERRORINCOMING_PATCHING]levels: [INFODEBUG]}]}) {id}}
Listing notification groups
To list notification groups (the number of returned notification groups depend on limit
field.
To get the next page pass the continuation_token
value from the previous API call to the input
field),
run notificationGroupsBatch query:
query notificationGroups {notificationGroupsBatch(input: {customer_id: "<<CUSTOMER_ID>>",limit: 1}) {notification_groups {idcustomer_idnamecreated_atupdated_ateventslevelsnotifications {typelevels}}continuation_token}}
RBAC
Adding a new role
To add a new role (adjust rules to your desired permissions), use createRbacRole mutation:
mutation createRole {createRbacRole(input: {customer_id: "<<CUSTOMER_ID>>",name: "CustomRole",description: "Custom role example"rules: [{resources: [eventplan]actions: [getlist]resource_ids: ["<<EVENT_ID_1>>""<<PLAN_ID_1>>"]}{resources: [machine]actions: [any]}] }) {id}}
Adding a new roles binding to user
To attach created role to the user, use createRbacRoleBinding mutation:
mutation attachRoles {createRbacRoleBinding(input: {customer_id: "<<CUSTOMER_ID>>"type: USERsubject: "first@example.com"roles: [{id: "<<ROLE_ID_1>>"}{id: "<<ROLE_ID_2>>"}]}) {id}}
Adding a new roles binding to API key
To attach created role to the API key, use createRbacRoleBinding mutation:
mutation attachRoles {createRbacRoleBinding(input: {customer_id: "<<CUSTOMER_ID>>"type: API_KEYsubject: "<<API_KEY_ID>>"roles: [{id: "<<ROLE_ID_1>>"}{id: "<<ROLE_ID_2>>"}]}) {id}}
Complex cases
This section describes more complex API use cases where the usage of a programming language (currently only Python examples are provided)
Print outputs from all host hooks in an event
#!/usr/bin/python3import osimport sysimport requestsAPI_URL = os.getenv("API_URL") or "https://api.autopatcher.nordcloudapp.com"API_KEY = os.getenv("API_KEY")CUSTOMER_ID = os.getenv("CUSTOMER_ID")# A query for retrieving details of a single eventEVENT_QUERY = """query($customer_id: String!, $event_id: String!) {event(customer_id: $customer_id, event_id: $event_id) {idmachines {idpost_host_hooks {status_code}}}}"""# A query for retrieving host hook output (the "message" field contains the actual output)HOOK_QUERY = """query($customer_id: String!, $event_id: String!, $machine_id: String!) {hostHookOutput(customer_id: $customer_idmachine_id: $machine_idevent_id: $event_idhook_name: "post_host_0") {codemessage}}"""def run(event_id):event = _get_event(event_id)_print_hook_outputs(event)def _run_gql(query, variables):data = {"query": query,"variables": variables,}headers = {"x-api-key": API_KEY}resp = requests.post(API_URL, headers=headers, json=data)resp.raise_for_status()return resp.json()def _get_event(event_id):resp_data = _run_gql(EVENT_QUERY, {"customer_id": CUSTOMER_ID,"event_id": event_id,})if resp_data.get("errors"):raise ValueError(resp_data["errors"] if len(resp_data["errors"]) > 1 else resp_data["errors"][0])return resp_data["data"]["event"]def _is_not_found(errors):return len(errors) == 1 and errors[0]["message"] == "Item was not found"def _print_hook_outputs(event):for machine in event["machines"]:print(f'Checking machine {machine["id"]}')if not machine["post_host_hooks"]:print("No post host hooks")continueresp_data = _run_gql(HOOK_QUERY, {"customer_id": CUSTOMER_ID,"event_id": event["id"],"machine_id": machine["id"],})if resp_data.get("errors"):if _is_not_found(resp_data["errors"]):print("Empty output")else:print(f'API error: {resp_data["errors"]}')continueoutput = resp_data["data"]["hostHookOutput"]["message"]if machine["post_host_hooks"][0]["status_code"] <= 299:print(f'Success:\n{output}')else:print(f'Hook error:\n{output}')if __name__ == "__main__":run(sys.argv[1])