Add "status" command

Add status command to display information the current deployment
of the OSTree repository when OSTree is deployed. This command
will not work from a non-OSTree deployed system.

The status command will display the current running branch,
in green. The current commit that is deployed and the Debian
version has been deployed.

Also update tox.ini and bindep.txt to support ostree.

Test Plan
PASSED Installed apt-ostree from git repo.
PASSED Run sudo apt-ostree compose create \
           --base config/debian/bookworm \
           --repo /repo debian/bookworm
PASSED Run "apt-ostree compose image --base config/debian/image \
        --repo=/repo test"
PASSED Start VM.
PASSED Run the apt-ostree status command.

Story: 2010867
Task: 48556

Change-Id: Ie55007e83869f5c491f97c1828508e7c8085f47a
Signed-off-by: Charles Short <charles.short@windriver.com>
This commit is contained in:
Charles Short 2023-09-12 20:32:11 -04:00
parent 3ef2dde60f
commit 03f0c8267f
7 changed files with 171 additions and 4 deletions

View File

@ -11,6 +11,8 @@ class State:
def __init__(self):
self.debug = False
self.workspace = None
self.repo = None
self.branch = None
# pass state between command and apt-ostree sub-commands

View File

@ -14,6 +14,7 @@ from apt_ostree.cmd.options import debug_option
from apt_ostree.cmd.options import workspace_option
from apt_ostree.cmd import pass_state_context
from apt_ostree.cmd.repo import repo
from apt_ostree.cmd.status import status
from apt_ostree.cmd.version import version
from apt_ostree.log import setup_log
@ -41,4 +42,5 @@ def main():
cli.add_command(compose)
cli.add_command(repo)
cli.add_command(status)
cli.add_command(version)

29
apt_ostree/cmd/status.py Normal file
View File

@ -0,0 +1,29 @@
"""
Copyright (c) 2023 Wind River Systems, Inc.
SPDX-License-Identifier: Apache-2.0
"""
import errno
import sys
import click
from apt_ostree.cmd import pass_state_context
from apt_ostree.status import Status
@click.command(help="Get the status of the booted system.")
@pass_state_context
def status(state):
try:
Status(state).get_deployment()
except KeyboardInterrupt:
click.secho("\n" + ("Exiting at your request."))
sys.exit(130)
except BrokenPipeError:
sys.exit()
except OSError as error:
if error.errno == errno.ENOSPC:
sys.exit("errror - No space left on device.")

View File

@ -6,9 +6,17 @@ SPDX-License-Identifier: Apache-2.0
"""
import subprocess
import sys
import click
from apt_ostree.utils import run_command
# pylint: disable=wrong-import-position
import gi
gi.require_version("OSTree", "1.0")
from gi.repository import Gio, GLib, OSTree # noqa: H301
class Ostree:
def __init__(self, state):
@ -36,3 +44,48 @@ class Ostree:
cmd += [str(root)]
r = run_command(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return r
def get_sysroot(self):
"""Load the /ostree directory (sysroot)."""
sysroot = OSTree.Sysroot()
if not sysroot.load():
click.secho("Unable to load /sysroot", fg="red")
sys.exit(1)
return sysroot
def open_ostree(self):
""""Open the ostree repository."""
if self.state.repo:
repo = OSTree.Repo.new(Gio.File.new_for_path(str(self.state.repo)))
if not repo.open(None):
click.secho(
"Opening the archive OSTree repository failed.", fg="red")
sys.exit(1)
else:
sysroot = self.get_sysroot()
_, repo = sysroot.get_repo()
if not repo.open():
click.secho(
"Opening the archive OSTree repository failed.", fg="red")
sys.exit(1)
return repo
def get_branch(self):
"""Get a branch in a current deployment."""
if self.state.branch:
return self.state.branch
else:
sysroot = self.get_sysroot()
deployment = sysroot.get_booted_deployment()
origin = deployment.get_origin()
try:
refspec = origin.get_string("origin", "refspec")
except GLib.Error as e:
# If not a "key not found" error then raise the exception
if not e.matches(GLib.KeyFile.error_quark(),
GLib.KeyFileError.KEY_NOT_FOUND):
raise (e)
# Fallback to `baserefspec`
refspec = origin.get_string('origin', 'baserefspec')
return refspec

72
apt_ostree/status.py Normal file
View File

@ -0,0 +1,72 @@
"""
Copyright (c) 2023 Wind River Systems, Inc.
SPDX-License-Identifier: Apache-2.0
"""
import pathlib
import shlex
from rich.console import Console
from rich.table import Table
from apt_ostree.ostree import Ostree
class Status:
def __init__(self, state):
self.state = state
self.ostree = Ostree(state)
self.console = Console()
self.sysroot = None
def get_deployment(self):
"""Get information about the current deployment"""
self.sysroot = self.ostree.get_sysroot()
deployment = self.sysroot.get_booted_deployment()
table = Table(box=None)
table.add_row("Current Deployment:")
table.add_row()
table.add_row("Branch:", f"[green]{self.ostree.get_branch()}[/green]")
table.add_row("Commit:", f"{deployment.get_csum()}")
root = self._get_deployment_path(deployment)
os_release = self._get_os_release(root)
if deployment.get_osname() == "debian":
release = os_release.get("PRETTY_NAME").replace('"', '')
table.add_row("Debian Release:", release)
table.add_row()
if table.columns:
self.console.print(table)
def _get_deployment_path(self, target_deployment):
"""Get the path for the /sysroot"""
return pathlib.Path("/" + self.sysroot.get_deployment_dirpath(
target_deployment))
def _get_os_release(self, rootfs):
"""Parse the /etc/os-release file."""
try:
file = open(rootfs.joinpath("/etc/os-release"), encoding="utf-8")
except FileNotFoundError:
try:
file = open(rootfs.joinpath(
"/usr/lib/os-release"), encoding="utf-8")
except FileNotFoundError:
return {}
os_release = {}
for line in file.readlines():
line = line.strip()
if not line or line.startswith("#"):
continue
try:
k, v = line.split("=")
(v_parsed, ) = shlex.split(v) # expect only one token
except ValueError:
continue
os_release[k] = v
return os_release

View File

@ -1 +1,7 @@
python3-apt [platform:dpkg]
ostree [platform:dpkg]
libostree-dev [platform:dpkg]
gir1.2-glib-2.0 [platform:dpkg]
gir1.2-ostree-1.0 [platform:dpkg]
libcairo2-dev [platform:dpkg]
libgirepository1.0-dev [platform:dpkg]

11
tox.ini
View File

@ -7,8 +7,8 @@ ignore_basepython_conflict = true
[testenv]
basepython = python3
usedevelop = false
sitepacages = True
usedevelop = true
sitepacages = False
setenv =
PYTHONWARNINGS=default::DeprecationWarning
OS_STDOUT_CAPTURE=1
@ -39,8 +39,6 @@ commands =
coverage html -d cover
coverage xml -o cover/coverage.xml
[testenv:docs]
deps = -r{toxinidir}/doc/requirements.txt
commands = sphinx-build -W -b html doc/source doc/build/html
@ -66,3 +64,8 @@ commands =
skip_install = True
deps = bindep
commands = bindep test
[stestr]
test_path=./apt_ostree/tests
top_dir=./
group_regex=([^\.]*\.)*