Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RSDK-9775] slack integration for ota config update #398

Merged
merged 5 commits into from
Jan 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions .github/workflows/canary_ota.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,8 @@ on:
otaTag:
description: 'OtaService Config Version tag'
required: true
type: choice
default: 'latest'
options:
- latest
- v0.4.0 # earlier versions do not support OTA
type: string
default: "testing"

env:
ESP32_CANARY_ROBOT: ${{ secrets.ESP32_CANARY_ROBOT }}
Expand Down
82 changes: 53 additions & 29 deletions canary/canary_ota.py
Original file line number Diff line number Diff line change
@@ -1,77 +1,101 @@
import asyncio
import time
import datetime
import copy
import os

import slack_sdk
import slack_sdk.errors
from dateutil import tz

from viam.robot.client import DialOptions
from viam.app.viam_client import ViamClient

async def connect(robot_address: str, api_key: str, api_key_id: str) -> ViamClient:
dial_options = DialOptions.with_api_key(api_key_id=api_key_id,api_key=api_key)
return await ViamClient.create_from_dial_options(dial_options)

async def main():
async def connect(api_key: str, api_key_id: str, num_attempts: int) -> ViamClient:
for i in range(num_attempts):
try:
dial_options = DialOptions.with_api_key(api_key_id=api_key_id, api_key=api_key)
viam_client = await ViamClient.create_from_dial_options(dial_options)
return viam_client
except Exception as e:
if i == num_attempts-1:
raise e
print(e)
time.sleep(0.5)

robot_address = os.environ["ESP32_CANARY_ROBOT"]
async def main():
api_key = os.environ["ESP32_CANARY_API_KEY"]
api_key_id = os.environ["ESP32_CANARY_API_KEY_ID"]
part_id = os.environ["ESP32_CANARY_ROBOT_PART_ID"]
tag_name = os.environ["ESP32_CANARY_OTA_VERSION_TAG"]

bin_name = "micro-rdk-server-esp32-ota.bin"
url_base = "https://github.com/viamrobotics/micro-rdk/releases"
bucket_url = os.environ["GCP_BUCKET_URL"]
bucket_name = os.environ["GCP_BUCKET_NAME"]
bin_name = os.environ["ESP32_OTA_BINARY_NAME"]

if tag_name == "latest":
url_target = f"{url_base}/latest/download/{bin_name}"
else:
url_target = f"{url_base}/download/{tag_name}/{bin_name}"
url = f"{bucket_url}/{bucket_name}/{tag_name}/{bin_name}"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should the binary name also be an env variable?


print(f"connecting to robot at {robot_address} ...")

for i in range(5):
try:
viam_client = await connect(robot_address, api_key, api_key_id)
break
except Exception as e:
if i == 4:
raise e
print(e)
time.sleep(0.5)
print(f"connecting ViamClient ...")

viam_client = await connect(api_key, api_key_id, 5)

cloud = viam_client.app_client

print(f"getting robot part...")
robot_part = await cloud.get_robot_part(robot_part_id=part_id)

service_updated = False
updated_config = copy.copy(robot_part.robot_config)
for service in updated_config["services"]:
# assumes only one such service exists
if service["model"] == "ota_service":
service["attributes"]["url"] = url_target
service["attributes"]["url"] = url
service["attributes"]["version"] = tag_name
service_updated = True
print(f"updating OtaServiceConfig to `{service}`")
print(f"updating OtaServiceConfig version to `{tag_name}`")
break

if not service_updated:
viam_client.close()
raise Exception("failed to find or update ota service config")
msg = f"failed to find or update ota service config to `{tag_name}`"
post_to_slack(msg, True)
raise Exception(msg)

print("updating robot part config...")
await cloud.update_robot_part(
robot_part_id=robot_part.id,
name=robot_part.name,
robot_config=updated_config
)

# retrieve new config to verify
print("verifying config change...")
robot_part = await cloud.get_robot_part(robot_part_id=part_id)
for service in robot_part.robot_config["services"]:
if service["model"] == "ota_service":
print(f"OtaServiceConfig after updating: `{service}`")
if service["attributes"]["url"] != url_target or service["attributes"]["version"] != tag_name:
raise Exception("ota service config does not reflect update")
if service["attributes"]["url"] != url or service["attributes"]["version"] != tag_name:
msg = f"ota service config does not reflect update to `{tag_name}`"
post_to_slack(msg, True)
raise Exception(msg)

viam_client.close()
post_to_slack(f"OtaService config successfully updated to `{tag_name}`", False)

def post_to_slack(msg: str, is_error: bool):
today = datetime.datetime.now(tz=tz.UTC).date()
msg = f"{today}: {msg}"
slack_token = os.environ["CANARY_SLACKBOT_TOKEN"]
channel_id = os.environ["MICRO_RDK_TEAM_CHANNEL_ID"]
client = slack_sdk.WebClient(token=slack_token)
api_result = client.chat_postMessage(channel=channel_id, text=msg)

try:
api_result.validate()
if is_error:
raise Exception(msg)
except Exception as e:
raise Exception(f"failure to post to Slack, error message was '{msg}'") from e


if __name__ == '__main__':
asyncio.run(main())