# Rotate SSL Certificates

import { Badge } from "zudoku/ui/Badge";

<div className="flex flex-wrap gap-2 not-prose mb-4">
  <Badge variant="outline">Automation</Badge>
  <Badge variant="secondary" className="font-mono text-xs">
    GET /v1/devices
  </Badge>
  <Badge variant="secondary" className="font-mono text-xs">
    POST /v1/tasks
  </Badge>
</div>

Rotate the SSL certificates on the devices that support the task, and track the task to completion.

:::tip

You can adjust the script to your needs. Change the conditions, variables, and values to
produce the output you want.

:::

<Recipe
  language={["python", "javascript"]}
  steps={[
    {
      title: "Set up the request",
      body: "Use the access token returned by the [Authenticate](/recipes/authentication) recipe.",
      code: {
        python:
          'import json\nimport time\n\nimport requests\n\nBASE = "https://api.securithings.com"\nheaders = {"Authorization": f"Bearer {access_token}"}',
        javascript:
          'const BASE = "https://api.securithings.com";\nconst headers = {\n  Authorization: `Bearer ${accessToken}`,\n};\nconst sleep = (ms) =>\n  new Promise((resolve) => setTimeout(resolve, ms));',
      },
    },
    {
      title: "Select target devices",
      body: "`availableTask.rotateSslCertificate` reports whether the task can be executed on the device.",
      code: {
        python:
          'devices = requests.get(\n    f"{BASE}/v1/devices",\n    params={"status": "online", "limit": 100},\n    headers=headers,\n).json()["devices"]\n\ntargets = [\n    device["id"]\n    for device in devices\n    if device["availableTask"]["rotateSslCertificate"]\n]',
        javascript:
          'const query = new URLSearchParams({\n  status: "online",\n  limit: "100",\n});\nconst devicesResponse = await fetch(\n  `${BASE}/v1/devices?${query}`,\n  { headers },\n);\nconst { devices } = await devicesResponse.json();\n\nconst targets = [];\nfor (const device of devices) {\n  if (device.availableTask.rotateSslCertificate) {\n    targets.push(device.id);\n  }\n}',
      },
    },
    {
      title: "Execute the task",
      body: "The payload fields for `rotateSslCertificate` are documented on the [Execute Task](/api/tasks) endpoint.",
      code: {
        python:
          'response = requests.post(\n    f"{BASE}/v1/tasks",\n    json={\n        "taskType": "rotateSslCertificate",\n        "devices": [\n            {"deviceId": device_id}\n            for device_id in targets\n        ],\n    },\n    headers=headers,\n)\ntask_id = response.json()["taskId"]',
        javascript:
          'const taskResponse = await fetch(`${BASE}/v1/tasks`, {\n  method: "POST",\n  headers: {\n    ...headers,\n    "Content-Type": "application/json",\n  },\n  body: JSON.stringify({\n    taskType: "rotateSslCertificate",\n    devices: targets.map((deviceId) => ({\n      deviceId,\n    })),\n  }),\n});\nconst { taskId } = await taskResponse.json();',
      },
    },
    {
      title: "Poll for completion",
      body: "The task is done once every device task reaches `completed`.",
      code: {
        python:
          'while True:\n    task = requests.get(\n        f"{BASE}/v1/tasks/{task_id}",\n        headers=headers,\n    ).json()\n    pending = [\n        device_task\n        for device_task in task["deviceTasks"]\n        if device_task["status"] != "completed"\n    ]\n    if not pending:\n        break\n    time.sleep(30)',
        javascript:
          'let task;\nwhile (true) {\n  const statusResponse = await fetch(\n    `${BASE}/v1/tasks/${taskId}`,\n    { headers },\n  );\n  task = await statusResponse.json();\n  const pending = task.deviceTasks.filter(\n    (deviceTask) => deviceTask.status !== "completed",\n  );\n  if (pending.length === 0) break;\n  await sleep(30000);\n}',
      },
    },
    {
      title: "Build the result",
      body: "A device task is `completed` whether or not it succeeded, so read `result`.",
      code: {
        python:
          'result = {\n    "taskId": task_id,\n    "succeeded": [\n        {\n            "deviceId": device_task["deviceId"],\n            "deviceName": device_task["deviceName"],\n        }\n        for device_task in task["deviceTasks"]\n        if device_task["result"] == "succeeded"\n    ],\n    "failed": [\n        {\n            "deviceId": device_task["deviceId"],\n            "deviceName": device_task["deviceName"],\n            "result": device_task["result"],\n        }\n        for device_task in task["deviceTasks"]\n        if device_task["result"] != "succeeded"\n    ],\n}\n\nprint(json.dumps(result, indent=2))',
        javascript:
          'const succeeded = [];\nconst failed = [];\n\nfor (const deviceTask of task.deviceTasks) {\n  const entry = {\n    deviceId: deviceTask.deviceId,\n    deviceName: deviceTask.deviceName,\n  };\n  if (deviceTask.result === "succeeded") {\n    succeeded.push(entry);\n  } else {\n    failed.push({\n      ...entry,\n      result: deviceTask.result,\n    });\n  }\n}\n\nconst result = { taskId, succeeded, failed };\n\nconsole.log(JSON.stringify(result, null, 2));',
      },
    },
  ]}
  response={`{
  "taskId": "19rs171mrlx9j3s",
  "succeeded": [
    {
      "deviceId": "dccfe2c1-7f92-11f1-a1d3-da3df9dc2a6d",
      "deviceName": "192.168.2.69 - Unit"
    }
  ],
  "failed": [
    {
      "deviceId": "dc7d4ef1-6bf9-11f1-b083-d27bafcacdf8",
      "deviceName": "192.168.2.70 - Unit",
      "result": "rolledBack"
    }
  ]
}`}
/>
