Skip to content

Instantly share code, notes, and snippets.

@reubenmiller
Last active October 16, 2024 13:00
Show Gist options
  • Save reubenmiller/28bd31abd7ca2c2d45b012abac174cf8 to your computer and use it in GitHub Desktop.
Save reubenmiller/28bd31abd7ca2c2d45b012abac174cf8 to your computer and use it in GitHub Desktop.

README

Create a custom operation which will map the operation to a thin-edge.io workflow (operation).

Pre-requisites

The following components must be installed for the scripts to work.

  • jq
  • tedge (>= 1.3.1-88-g09c1d4c)

Summary

The following steps describe the flow from the cloud to the device:

  1. tedge-mapper-c8y receives an operation on the c8y/devicecontrol/notifications topic (as json)
  2. tedge-mapper-c8y executes the exec.command command on any cloud operation definitions that match on the given on_fragment (e.g. in this example, and operation with the c8y_Command fragment will trigger the /etc/tedge/operations/c8y/c8y_Command operation definition)
  3. create_workflow_command.sh is executed (passing the operation json payload as the first argument). The script then maps the cloud operation data format to a local format (which is defined by the given workflow, e.g. /etc/tedge/operations/shell_execute.toml. create_workflow_command.sh will wait until the workflow has finished processing, and then exit.

Future changes

  • In thin-edge.io 1.4.0, the need for the create_workflow_command.sh script will go away, as thin-edge.io will be capable of mapping the incoming cloud operation to a workflow for the targeted device (so it works for both main and child devices).
# file: /etc/tedge/operations/c8y/c8y_Command
[exec]
topic = "c8y/devicecontrol/notifications"
on_fragment = "c8y_Command"
command = "create_workflow_command.sh ${.payload}"
# file: /etc/tedge/operations/shell_execute.toml
operation = "shell_execute"
[init]
action = "proceed"
on_success = "executing"
[executing]
script = "echo hello"
on_success = "successful"
on_error = "handle_rollback"
[handle_rollback]
script = "echo doing rollback"
on_success = { status = "failed", reason = "rollback was successful, but the overall update was not" }
on_error = { status = "failed", reason = "rollback was NOT successful. You may have a broken system" }
[successful]
action = "proceed"
[failed]
action = "proceed"
#!/bin/sh
# file: /usr/bin/create_workflow_command.sh
set -e
info() {
echo "$(date --iso-8601=seconds 2>/dev/null || date -Iseconds) INFO $*" >&2
}
PAYLOAD="$1"
get_device_topic_id() {
external_id="$1"
DEVICE_ID=$(tedge config get device.id)
if [ "$external_id" = "$DEVICE_ID" ]; then
TARGET="device/main//"
else
TARGET=$(echo "$external_id" | cut -d: -f2- | sed 's/:/\//g')
case "$TARGET" in
*/*/*/*)
# Do nothing
;;
*/*/*)
TARGET="$TARGET/"
;;
*/*)
TARGET="$TARGET//"
;;
*)
TARGET="$TARGET///"
;;
esac
fi
echo "$TARGET"
}
# Convert the cloud's external id to the local thin-edge.io topic id
EXTERNAL_ID=$(printf '%s' "$PAYLOAD" | jq -r '.externalSource.externalId // ""')
TOPIC_ROOT=$(tedge config get mqtt.topic_root)
DEVICE_TOPIC_ID=$(get_device_topic_id "$EXTERNAL_ID")
CMD_ID=$(printf '%s' "$PAYLOAD" | jq -r '.id')
TOPIC="$TOPIC_ROOT/$DEVICE_TOPIC_ID/cmd/shell_execute/c8y-mapper-$CMD_ID"
COMMAND_PAYLOAD=$(printf '%s' "$PAYLOAD" | jq -c '{"status":"init", "command": (.c8y_Command.text // "")}')
info "Publishing operation (to trigger workflow): $TOPIC $COMMAND_PAYLOAD"
tedge mqtt pub -q 1 -r "$TOPIC" "$COMMAND_PAYLOAD"
info "Waiting for operation to finish: $TOPIC"
# FIXME: Remove once the operations can also trigger workflows
# Wait for operation to complete
while :; do
# TODO: Fixme this assumes messages are only 1 line! it breaks on pretty printed json
LAST_MESSAGE=$(timeout 2 tedge mqtt sub "$TOPIC" --no-topic ||:)
STATUS=$(printf '%s' "$LAST_MESSAGE" | jq -r '.status // ""' ||:)
info "Waiting for operation to finish: $TOPIC, status=$STATUS"
# Clear the operation on a terminal state
# Note: use jq -j to exclude the newline character which is normally added to the output
case "$STATUS" in
successful)
info "Operation successful"
printf '%s' "$LAST_MESSAGE" | jq -jr '.result // ""'
tedge mqtt pub -q 1 -r "$TOPIC" ''
exit 0
;;
failed)
info "Operation failed"
printf '%s' "$LAST_MESSAGE" | jq -jr '.result // ""'
tedge mqtt pub -q 1 -r "$TOPIC" ''
exit 1
;;
*)
info "Waiting for operation to finish: topic=$TOPIC, status=$STATUS"
;;
esac
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment