AutoPatcher
Logout
What is AutoPatcherQuickstartSetting up permissionsSetting up SSM agentAdding a machine to AutoPatcherScheduling a patching planBaseline patchingPatching eventsNotificationsPre & Post patching actions - HooksReportsPatching logsFirewall configurationCommand line interfaceManaging permissionsManaging API keysBootcamp videosAPI UsageIntroductionMachinesPlansPipelinesNotification groupsRBACComplex cases

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_NEEDED
published_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 {
id
customer_id
provider
name
description
s3_custom_script
custom_document_arn
update_severity
published_days_old
update_category
ping_status
ping_error
include_kbs
exclude_kbs
created_at
updated_at
reboot_policy
plans
pipelines
last_patching {
event_id
status
}
access {
ssm_machine_id
region
}
azure_info {
subscription_id
instance_id
}
ssm_metadata {
activation_id
resource_type
}
added_dynamically
assignee
instance_id
host_collector_invoked
last_package_scan {
time
error
}
metadata {
key
value
}
}
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: 10
name: "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: true
linux_security_only: true
upcoming_notification_time: 10
manual_approval: true
parallel: 7
notification_groups: [
"<<NOTIFICATION_GROUP_1_ID>>",
"<<NOTIFICATION_GROUP_2_ID>>"
]
pre_hooks: [
{
type: aws
source: "arn:aws:lambda:eu-west-1:123456789012:function:lambda1"
method: GET
}
{
type: azure
source: "https://azure-prehook.azurewebsites.net/api/prehook2"
method: POST
}
]
post_hooks: [
{
type: azure
source: "https://azure-posthook.azurewebsites.net/api/posthook2"
method: POST
}
]
webhook_inputs: [
{
trigger_type: event
payload: "{}"
payload_type: "itsm_project_key"
}
{
trigger_type: report
payload: "apiKey"
payload_type: "itsm_project_key"
}
]
metadata: [
{
key: "tag1"
value: "value1"
}
{
key: "tag2"
value: "random metadata"
}
]
machines: [
{
id: "<<MACHINE_ID_1>>"
name: "ExampleName"
order: 1
exclude: true
s3_custom_script: "s3://bucket-name/example_script.sh"
pre_host_hooks: [
{
type: aws
source: "arn:aws:lambda:eu-west-1:123456789012:function:lambda1"
}
{
type: azure
source: "https://azure-prehook.azurewebsites.net/api/prehook2"
}
]
post_host_hooks: [
{
type: script
source: "s3://bucket-name/posthook_script.sh"
}
{
type: azure
source: "https://azure-posthook.azurewebsites.net/api/posthook2"
}
]
}
{
id: "<<MACHINE_ID_2>>"
name: "SecondMachine"
order: 2
exclude: 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: 10
name: "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: true
linux_security_only: true
upcoming_notification_time: 10
manual_approval: true
parallel: 7
notification_groups: [
"<<NOTIFICATION_GROUP_1_ID>>",
"<<NOTIFICATION_GROUP_2_ID>>"
]
pre_hooks: [
{
type: aws
source: "arn:aws:lambda:eu-west-1:123456789012:function:lambda1"
method: GET
}
{
type: azure
source: "https://azure-prehook.azurewebsites.net/api/prehook2"
method: POST
}
]
post_hooks: [
{
type: azure
source: "https://azure-posthook.azurewebsites.net/api/posthook2"
method: POST
}
]
webhook_inputs: [
{
trigger_type: event
payload: "{}"
payload_type: "itsm_project_key"
}
{
trigger_type: report
payload: "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: aws
source: "arn:aws:lambda:eu-west-1:123456789012:function:lambda1"
}
{
type: azure
source: "https://azure-prehook.azurewebsites.net/api/prehook2"
}
]
post_host_hooks: [
{
type: script
source: "s3://bucket-name/posthook_script.sh"
}
{
type: azure
source: "https://azure-posthook.azurewebsites.net/api/posthook2"
}
]
reboot_policy: IF_NEEDED
regions: [
"us-east-1"
]
s3_custom_script: "s3://bucket/key"
update_existing: false
include_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: 10
name: "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: true
linux_security_only: true
upcoming_notification_time: 10
manual_approval: true
parallel: 7
notification_groups: [
"<<NOTIFICATION_GROUP_1_ID>>",
"<<NOTIFICATION_GROUP_2_ID>>"
]
pre_hooks: [
{
type: aws
source: "arn:aws:lambda:eu-west-1:123456789012:function:lambda1"
method: GET
}
{
type: azure
source: "https://azure-prehook.azurewebsites.net/api/prehook2"
method: POST
}
]
post_hooks: [
{
type: azure
source: "https://azure-posthook.azurewebsites.net/api/posthook2"
method: POST
}
]
webhook_inputs: [
{
trigger_type: event
payload: "{}"
payload_type: "itsm_project_key"
}
{
trigger_type: report
payload: "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: aws
source: "arn:aws:lambda:eu-west-1:123456789012:function:lambda1"
}
{
type: azure
source: "https://azure-prehook.azurewebsites.net/api/prehook2"
}
]
post_host_hooks: [
{
type: script
source: "s3://bucket-name/posthook_script.sh"
}
{
type: azure
source: "https://azure-posthook.azurewebsites.net/api/posthook2"
}
]
reboot_policy: IF_NEEDED
regions: [
"us-east-1"
]
s3_custom_script: "s3://bucket/key"
update_existing: false
include_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 {
id
customer_id
cron_window_start
name
description
manual_approval
dry_run
linux_security_only
on_hold_start
on_hold_end
upcoming_notification_time
parallel
window_length
created_at
updated_at
notification_groups
machines {
id
}
pre_hooks {
type
name
}
post_hooks {
type
name
}
s3_custom_script
webhook_inputs {
trigger_type
payload
}
metadata {
key
value
}
}
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: true
upcoming_notification_time: 10
steps: [
{
description: "First step of the pipeline"
stage: "DEV"
plans: [
{
id: "<<PLAN_ID_1>>"
window_length: 90
name: "First step first plan"
description: "Just description"
dry_run: true
machines_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: 10080
policy: patch_anyway
stage: "PROD"
plans: [
{
id: "<<PLAN_ID_2>>"
window_length: 150
name: "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: 2
plans: [
{
id: "<<PLAN_ID_3>>"
window_length: 180
name: "CRON"
parallel: 3
machines: [
{ id: "<<MACHINE_ID_3>>", name: "Machine 3", order: 1 }
{
id: "<<MACHINE_ID_4>>"
name: "Machine 4"
order: 2
pre_host_hooks: [
{
type: aws
source: "arn:aws:lambda:eu-west-1:123456789012:function:lambda1"
}
{
type: azure
source: "https://azure-prehook.azurewebsites.net/api/prehook2"
}
]
s3_custom_script: "s3://bucket-name/example_script.sh"
}
]
pre_hooks: [
{
type: aws
source: "arn:aws:lambda:eu-west-1:123456789012:function:lambda1"
method: GET
}
{
type: azure
source: "https://azure-prehook.azurewebsites.net/api/prehook2"
method: POST
}
]
post_hooks: [
{
type: azure
source: "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 {
id
customer_id
name
description
metadata {
key
value
}
notification_groups
cron_window_start
upcoming_notification_time
enable_baseline
steps {
description
step_delay
run_immediately
policy
stage
plans {
id
window_length
name
description
metadata {
key
value
}
parallel
dry_run
linux_security_only
machines {
id
name
order
pre_host_hooks {
type
source
method
status_code
}
post_host_hooks {
type
source
method
status_code
}
s3_custom_script
exclude
}
pre_hooks {
type
source
method
status_code
}
post_hooks {
type
source
method
status_code
}
machines_tag {
tag_list {
key
values
}
tag_condition {
placeholders {
key
value
}
expression
}
pre_host_hooks {
type
source
method
status_code
}
post_host_hooks {
type
source
method
status_code
}
update_existing
reboot_policy
regions
s3_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: SLACK
web_hook_url: "https://api.example.com/v1/webhooks"
channel: "Notification-channel"
}
{
type: PAGER_DUTY
routing_key: "12345"
events: [
ERROR
]
levels: [
INFO
]
override_settings: true
}
{
type: EMAIL
email_config: {
address: "first@example.com"
}
}
{
type: EMAIL
email_config: {
address: "second@example.com"
is_external: true
}
events: [
ERROR
INCOMING_PATCHING
]
levels: [
INFO
DEBUG
]
}
]
}) {
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 {
id
customer_id
name
created_at
updated_at
events
levels
notifications {
type
levels
}
}
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: [
event
plan
]
actions: [
get
list
]
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: USER
subject: "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_KEY
subject: "<<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)

#!/usr/bin/python3
import os
import sys
import requests
API_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 event
EVENT_QUERY = """query($customer_id: String!, $event_id: String!) {
event(customer_id: $customer_id, event_id: $event_id) {
id
machines {
id
post_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_id
machine_id: $machine_id
event_id: $event_id
hook_name: "post_host_0"
) {
code
message
}
}
"""
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")
continue
resp_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"]}')
continue
output = 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])