Pull request #48: Feat/coreos device

Merge in ICO/coreos from feat/coreos-device to master

* commit '57107f5cea3ff2e61701c18753cacdada8d1e04f':
  feat(swupdate): install swupdate-progress by default
  feat(coreos-device): add a coreos-device script and a devtool plugin
This commit is contained in:
Samuel Dolt 2023-02-06 10:03:29 +01:00
commit f086fe20de
6 changed files with 188 additions and 3 deletions

2
.gitignore vendored
View File

@ -3,4 +3,4 @@ vscode-bitbake-build/
documentation/_build/ documentation/_build/
documentation/oe-logs documentation/oe-logs
documentation/oe-workdir documentation/oe-workdir
__pycache__

View File

@ -46,7 +46,8 @@ Theses packages are needed on your build machine:
chrpath socat cpio python3 python3-pip python3-pexpect xz-utils \ chrpath socat cpio python3 python3-pip python3-pexpect xz-utils \
debianutils iputils-ping python3-git python3-jinja2 libegl1-mesa \ debianutils iputils-ping python3-git python3-jinja2 libegl1-mesa \
libsdl1.2-dev pylint3 xterm python3-subunit mesa-common-dev zstd \ libsdl1.2-dev pylint3 xterm python3-subunit mesa-common-dev zstd \
liblz4-tool bmap-tools efitools openssl sbsign liblz4-tool bmap-tools efitools openssl sbsign python3-click \
python3-aiohttp
Use Git to clone CoreOS Use Git to clone CoreOS
######################## ########################

View File

@ -14,7 +14,7 @@ FEATURE_PACKAGES_networkmanager = "networkmanager networkmanager-nmcli"
FEATURE_PACKAGES_networkmanager-dev-tools = "networkmanager-nmtui" FEATURE_PACKAGES_networkmanager-dev-tools = "networkmanager-nmtui"
FEATURE_PACKAGES_networkmanager-cockpit = "cockpit-networkmanager" FEATURE_PACKAGES_networkmanager-cockpit = "cockpit-networkmanager"
FEATURE_PACKAGES_swupdate = "swupdate swupdate-client swupdate-lua" FEATURE_PACKAGES_swupdate = "packagegroup-coreos-swupdate"
# The cockpit feature automatically install the corresponding # The cockpit feature automatically install the corresponding
# *-cockpit FEATURES_PACKAGES for any image features # *-cockpit FEATURES_PACKAGES for any image features

View File

@ -0,0 +1,57 @@
#!/usr/bin/env python
# SPDX-License-Identifier: GPL-2.0-only
# This scripts use bitbake api, so it has to be under the GPL-2.0 license
"""
devtool wrapper for the coreos-device script
"""
import subprocess
from devtool import setup_tinfoil, parse_recipe
class InvalidImage(Exception):
pass
def swupdate_www_push(args, config, basepath, workspace):
"""
devtool update-device command
Use devtool library to get information about the image from the
bitbake context, then call update_device()
"""
# Only use inside a bitbake environment, we can import some more libs here
image = args.imagename
tinfoil = setup_tinfoil(basepath=basepath)
rd = parse_recipe(config, tinfoil, image, True)
if not rd:
# Error already shown
return 1
if not bb.data.inherits_class('coreos-image', rd):
raise InvalidImage("Image should be based on the coreos-image class")
if rd.getVar('COREOS_IMAGE_GENERATE_SWU') != "1":
raise InvalidImage("COREOS_IMAGE_GENERATE_SWU should be set to '1'")
image_name = rd.getVar('IMAGE_LINK_NAME')
outputdir = rd.getVar('DEPLOY_DIR_IMAGE')
return run(["coreos-device", "swupdate-www-push", f"{outputdir}/{image_name}.swu", args.target, f"--port={args.port}"])
def register_commands(subparsers, context):
"""Register devtool subcommands from this plugin"""
subparsers.add_subparser_group('swupdate', 'Updating device using swupdate')
cmd_push = subparsers.add_parser('swupdate-www-push', help='Update device with image',
description='Deploy an image to a device using swupdate',
group='swupdate')
cmd_push.add_argument('imagename', help='Image recipe to deploy')
cmd_push.add_argument('target', help='IP address of the running target')
cmd_push.add_argument('-P', '--port', default=8080, help='Specify port to use for connection to the target')
cmd_push.set_defaults(func=swupdate_www_push, fixed_setup=context.fixed_setup)
def run(cmd):
return subprocess.run(cmd).returncode

View File

@ -0,0 +1,16 @@
SUMMARY = "Add CoreOS swupdate backends and integrations packages"
DESCRIPTION = "Install swupdate and related components"
inherit packagegroup
PACKAGES = "\
${PN} \
"
RDEPENDS:${PN} = "\
swupdate \
swupdate-progress \
swupdate-client \
swupdate-lua \
"

111
scripts/coreos-device Executable file
View File

@ -0,0 +1,111 @@
#!/usr/bin/env python3
"""
This is a low level script that communicate with a device over the network.
Currently only a swupdate-www-push subcommand is available.
It can be used this way:
coreos-device swupdate-www-push ~/coreos-image-demo-vm-x64.swu 192.168.122.21
This script doesn't interact with bitbake so it can't retrieve variables from
bitbake. For a more user friendly usage you can use devtool:
devtool swupdate-www-push coreos-image-demo 192.168.122.21
"""
import os
import sys
import pathlib
import ipaddress
import asyncio
import json
# We need some modules that are not part of the Python Standard Library
# If they are not found, let's fail with an appropriate error message
try:
import click
import aiohttp
except ModuleNotFoundError as e:
print(f"Error: {e}", file=sys.stderr)
print(f"Please install the python3-{e.name} package", file=sys.stderr)
sys.exit(1)
@click.group()
def cli():
pass
@cli.command()
@click.argument( 'image', type=click.Path(
exists=True, resolve_path=True, readable=True, dir_okay=False,
path_type=pathlib.Path,
))
@click.argument('device', type=ipaddress.ip_address)
@click.option('--port', type=click.IntRange(min=1, max=65535), default=8080)
def swupdate_www_push(image, device, port):
loop = asyncio.new_event_loop()
returncode = loop.run_until_complete(swupdate_www_push_async(image, device, port))
loop.close()
sys.exit(returncode)
async def swupdate_www_push_async(image, device, port):
url = f'http://{device}:{port}'
async with aiohttp.ClientSession() as session:
tasks = []
tasks.append(asyncio.ensure_future(swupdate_www_push_data(session, url, image)))
tasks.append(asyncio.ensure_future(swupdate_www_websocket_logger(session, url)))
responses = await asyncio.gather(*tasks)
# Return the sum of return value of all ours tasks
returncode = 0
for response in responses:
returncode += response
return returncode
async def swupdate_www_push_data(session, url, image):
with open(image, 'rb') as f:
data = {'file': f}
try:
async with session.post(f"{url}/upload", data=data) as resp:
if resp.status == 200:
click.secho("coreos-device: Update was successfull", bg="green")
return 0
else:
click.secho(f"coreos-device: status {resp.status}, {await resp.text}", bg="red")
return resp.status
except Exception as e:
click.secho(f"coreos-device: ERROR: {e}", bg="red")
return 1
async def swupdate_www_websocket_logger(session, url):
try:
async with session.ws_connect(f"{url}/ws") as ws:
async for msg in ws:
data = json.loads(msg.data)
if data["type"] == "message":
msg = data["text"]
if "Waiting for requests" in msg:
await ws.close()
return 1
elif "ERROR" in msg:
click.secho("swupdate: " + data["text"], fg='red')
else:
click.secho("swupdate: " + data["text"], fg='green')
if data["type"] == "status":
if data["status"] == "DONE":
await ws.close()
return 0
except Exception as e:
click.secho(f"coreos-device: ERROR: {e}", bg="red")
return 1
if __name__ == '__main__':
cli()