-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Split incus specific code to incuslib
- Loading branch information
1 parent
c59b3ba
commit 6ef8ef9
Showing
5 changed files
with
171 additions
and
133 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
#!/usr/bin/env python3 | ||
|
||
from .incus import Incus | ||
from .simplestreams import SimpleStreams | ||
|
||
__all__ = ["Incus", "SimpleStreams"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
#!/usr/bin/env python3 | ||
|
||
import logging | ||
import os | ||
import platform | ||
import subprocess | ||
from pathlib import Path | ||
|
||
import yaml | ||
|
||
|
||
class Incus: | ||
def __init__(self) -> None: | ||
pass | ||
|
||
def arch(self) -> str: | ||
plat = platform.machine() | ||
if plat in ["x86_64", "amd64"]: | ||
return "amd64" | ||
if plat in ["arm64", "aarch64"]: | ||
return "arm64" | ||
if plat in ["armhf"]: | ||
return "armhf" | ||
raise RuntimeError(f"Unknown platform {plat}!") | ||
|
||
def _run(self, *args: str, **kwargs) -> str: | ||
command = ["incus"] + [*args] | ||
return subprocess.check_output(command, **kwargs).decode("utf-8") | ||
|
||
def _run_logged_prefixed(self, *args: str, prefix: str = "", **kwargs) -> None: | ||
command = ["incus"] + [*args] | ||
|
||
process = subprocess.Popen( | ||
command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, **kwargs | ||
) | ||
assert process.stdout | ||
with process.stdout: | ||
for line in iter(process.stdout.readline, b""): # b'\n'-separated lines | ||
logging.debug("%s%s", prefix, line.decode("utf-8").rstrip("\n")) | ||
exitcode = process.wait() # 0 means success | ||
if exitcode: | ||
raise RuntimeError(f"Could not run {' '.join(command)}") | ||
|
||
def instance_stopped(self, name: str) -> bool: | ||
assert self.instance_exists(name) | ||
res = yaml.safe_load(self._run("info", name)) | ||
return res["Status"] == "STOPPED" | ||
|
||
def instance_exists(self, name: str) -> bool: | ||
res = yaml.safe_load(self._run("list", "-f", "yaml")) | ||
instance_names = [instance["name"] for instance in res] | ||
return name in instance_names | ||
|
||
def instance_start(self, name: str) -> None: | ||
self._run("start", name) | ||
|
||
def instance_stop(self, name: str) -> None: | ||
self._run("stop", name) | ||
|
||
def instance_delete(self, name: str) -> None: | ||
self._run("delete", name) | ||
|
||
def launch(self, image_name: str, instance_name: str) -> None: | ||
self._run("launch", image_name, instance_name) | ||
|
||
def push_file(self, instance_name: str, file: Path, target: str) -> None: | ||
self._run("file", "push", str(file), f"{instance_name}{target}") | ||
os.sync() | ||
|
||
def execute(self, instance_name: str, *args: str) -> None: | ||
self._run_logged_prefixed( | ||
"exec", instance_name, "--", *args, prefix=" In container |\t" | ||
) | ||
|
||
def publish( | ||
self, instance_name: str, image_alias: str, properties: dict[str, str] | ||
) -> None: | ||
properties_list = [f"{key}={value}" for key, value in properties.items()] | ||
self._run("publish", instance_name, "--alias", image_alias, *properties_list) | ||
|
||
def image_export( | ||
self, image_alias: str, image_target: str, target_dir: Path | ||
) -> None: | ||
self._run("image", "export", image_alias, image_target, cwd=target_dir) | ||
|
||
def image_exists(self, alias: str) -> bool: | ||
res = yaml.safe_load(self._run("image", "list", "-f", "yaml")) | ||
image_aliases = [alias["name"] for image in res for alias in image["aliases"]] | ||
return alias in image_aliases | ||
|
||
def image_delete(self, alias: str) -> None: | ||
self._run("image", "delete", alias) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
#!/usr/bin/env python3 | ||
|
||
import json | ||
import logging | ||
import subprocess | ||
from pathlib import Path | ||
|
||
from .incus import Incus | ||
|
||
|
||
class SimpleStreams: | ||
def __init__(self, incus: Incus, path: Path, cachedir: Path) -> None: | ||
self.incus = incus | ||
self.path = path | ||
self.path.mkdir(exist_ok=True) | ||
self.cachedir = cachedir | ||
self.cachedir.mkdir(exist_ok=True) | ||
|
||
def import_from_incus(self, name: str, alias: str) -> None: | ||
image_alias_underscorified = alias.replace("/", "_") | ||
image_file = self.cachedir / f"{image_alias_underscorified}.tar.gz" | ||
|
||
self.incus.image_export(name, image_alias_underscorified, self.cachedir) | ||
|
||
subprocess.run( | ||
["incus-simplestreams", "add", image_file], | ||
cwd=self.path, | ||
) | ||
image_file.unlink() | ||
|
||
def images_paths(self) -> list[str]: | ||
images_data = self.images_data() | ||
images = [ | ||
self.path / item["path"] | ||
for product in images_data["products"].values() | ||
for version in product["versions"].values() | ||
for item in version["items"].values() | ||
] | ||
return images | ||
|
||
def images_data(self) -> dict: | ||
images_file = self.path / "streams" / "v1" / "images.json" | ||
images_data = json.load(images_file.open(encoding="utf-8")) | ||
return images_data | ||
|
||
def prune_images(self) -> None: | ||
images_dir: Path = self.path / "images" | ||
|
||
images = self.images_paths() | ||
for file in images_dir.iterdir(): | ||
if file not in images: | ||
logging.info(f"Pruning {file.name}...") | ||
file.unlink() | ||
|
||
def clean_previous_versions(self) -> None: | ||
for product_name, product in self.images_data()["products"].items(): | ||
versions = sorted(product["versions"].keys()) | ||
for version in versions[:-1]: | ||
for item in product["versions"][version]["items"].values(): | ||
sha = item["sha256"] | ||
print(f"Pruning {product_name} / {sha}...") | ||
subprocess.run( | ||
["incus-simplestreams", "remove", sha], cwd=self.path | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters