Add Debian apt config and ostree deploy stages, update apt-cacher-ng to 192.168.1.101:3142
This commit is contained in:
parent
85e0c04d21
commit
6a17af5a62
4 changed files with 302 additions and 149 deletions
96
stages/org.osbuild.apt.config
Normal file
96
stages/org.osbuild.apt.config
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import configparser
|
||||||
|
|
||||||
|
import osbuild.api
|
||||||
|
|
||||||
|
|
||||||
|
def make_apt_config(tree, config_options):
|
||||||
|
"""Create or update apt configuration files"""
|
||||||
|
|
||||||
|
# Create apt configuration directory
|
||||||
|
apt_conf_dir = f"{tree}/etc/apt/apt.conf.d"
|
||||||
|
os.makedirs(apt_conf_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# Create main apt configuration
|
||||||
|
apt_conf_path = f"{tree}/etc/apt/apt.conf"
|
||||||
|
|
||||||
|
config = configparser.ConfigParser()
|
||||||
|
|
||||||
|
# Try to read existing config
|
||||||
|
try:
|
||||||
|
with open(apt_conf_path, "r", encoding="utf8") as f:
|
||||||
|
config.read_file(f)
|
||||||
|
except FileNotFoundError:
|
||||||
|
print(f"Creating new apt configuration file: {apt_conf_path}")
|
||||||
|
|
||||||
|
# Add or update configuration sections
|
||||||
|
for section, items in config_options.items():
|
||||||
|
if not config.has_section(section):
|
||||||
|
config.add_section(section)
|
||||||
|
|
||||||
|
for option, value in items.items():
|
||||||
|
config.set(section, option, str(value))
|
||||||
|
|
||||||
|
# Write configuration
|
||||||
|
with open(apt_conf_path, "w", encoding="utf8") as f:
|
||||||
|
config.write(f)
|
||||||
|
|
||||||
|
print(f"Updated apt configuration: {apt_conf_path}")
|
||||||
|
|
||||||
|
|
||||||
|
def make_apt_sources_config(tree, sources_options):
|
||||||
|
"""Create or update apt sources configuration"""
|
||||||
|
|
||||||
|
sources_list_dir = f"{tree}/etc/apt/sources.list.d"
|
||||||
|
os.makedirs(sources_list_dir, exist_ok=True)
|
||||||
|
|
||||||
|
for source_name, source_config in sources_options.items():
|
||||||
|
source_file = f"{sources_list_dir}/{source_name}.list"
|
||||||
|
|
||||||
|
with open(source_file, "w", encoding="utf8") as f:
|
||||||
|
for line in source_config:
|
||||||
|
f.write(f"{line}\n")
|
||||||
|
|
||||||
|
print(f"Created source file: {source_file}")
|
||||||
|
|
||||||
|
|
||||||
|
def main(tree, options):
|
||||||
|
"""Main function for apt config stage"""
|
||||||
|
|
||||||
|
# Get options
|
||||||
|
config_options = options.get("config", {})
|
||||||
|
sources_options = options.get("sources", {})
|
||||||
|
preferences = options.get("preferences", {})
|
||||||
|
|
||||||
|
# Create apt configuration
|
||||||
|
if config_options:
|
||||||
|
make_apt_config(tree, config_options)
|
||||||
|
|
||||||
|
# Create sources configuration
|
||||||
|
if sources_options:
|
||||||
|
make_apt_sources_config(tree, sources_options)
|
||||||
|
|
||||||
|
# Create preferences file
|
||||||
|
if preferences:
|
||||||
|
pref_dir = f"{tree}/etc/apt/preferences.d"
|
||||||
|
os.makedirs(pref_dir, exist_ok=True)
|
||||||
|
|
||||||
|
for pref_name, pref_rules in preferences.items():
|
||||||
|
pref_file = f"{pref_dir}/{pref_name}"
|
||||||
|
|
||||||
|
with open(pref_file, "w", encoding="utf8") as f:
|
||||||
|
for rule in pref_rules:
|
||||||
|
f.write(f"{rule}\n")
|
||||||
|
|
||||||
|
print(f"Created preferences file: {pref_file}")
|
||||||
|
|
||||||
|
print("apt configuration completed successfully")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
args = osbuild.api.arguments()
|
||||||
|
r = main(args["tree"], args["options"])
|
||||||
|
sys.exit(r)
|
||||||
63
stages/org.osbuild.apt.config.meta.json
Normal file
63
stages/org.osbuild.apt.config.meta.json
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
{
|
||||||
|
"name": "org.osbuild.apt.config",
|
||||||
|
"version": "1",
|
||||||
|
"description": "Configure apt package manager settings and sources",
|
||||||
|
"options": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"config": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"oneOf": [
|
||||||
|
{"type": "string"},
|
||||||
|
{"type": "number"},
|
||||||
|
{"type": "boolean"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "apt.conf configuration sections and options"
|
||||||
|
},
|
||||||
|
"sources": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Additional sources.list.d files"
|
||||||
|
},
|
||||||
|
"preferences": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Package preference rules for apt"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"inputs": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"devices": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"mounts": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"capabilities": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"default": ["CAP_CHOWN", "CAP_DAC_OVERRIDE", "CAP_FOWNER", "CAP_FSETID", "CAP_MKNOD", "CAP_SETGID", "CAP_SETUID"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,73 +1,116 @@
|
||||||
#!/usr/bin/python3
|
#!/usr/bin/python3
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import subprocess
|
||||||
|
import json
|
||||||
|
|
||||||
import osbuild.api
|
import osbuild.api
|
||||||
from osbuild.util import ostree
|
|
||||||
from osbuild.util.mnt import MountGuard
|
|
||||||
|
|
||||||
|
|
||||||
def make_fs_identifier(desc):
|
def run_ostree_command(cmd, cwd=None, env=None):
|
||||||
for key in ["uuid", "label"]:
|
"""Run ostree command and return result"""
|
||||||
val = desc.get(key)
|
if env is None:
|
||||||
if val:
|
env = {}
|
||||||
return f"{key.upper()}={val}"
|
|
||||||
raise ValueError("unknown rootfs type")
|
result = subprocess.run(cmd, cwd=cwd, env=env, capture_output=True, text=True)
|
||||||
|
|
||||||
|
if result.returncode != 0:
|
||||||
|
print(f"Error running ostree command: {' '.join(cmd)}")
|
||||||
|
print(f"stdout: {result.stdout}")
|
||||||
|
print(f"stderr: {result.stderr}")
|
||||||
|
return False, result.stderr
|
||||||
|
|
||||||
|
return True, result.stdout
|
||||||
|
|
||||||
|
|
||||||
def ostree_commit_deploy(tree, inputs, osname, remote, ref, kopts):
|
def deploy_ostree_branch(repo_path, target_path, branch, ref=None):
|
||||||
if len(inputs) == 0:
|
"""Deploy OSTree branch to target filesystem"""
|
||||||
if not ref:
|
|
||||||
raise ValueError("ref should be specified in options")
|
# Prepare deploy command
|
||||||
elif len(inputs) == 1:
|
cmd = ["ostree", "admin", "deploy", "--repo", repo_path]
|
||||||
if ref:
|
|
||||||
raise ValueError("Should not specify ref if input was specified")
|
if ref:
|
||||||
|
cmd.extend(["--branch", ref])
|
||||||
# If we have an input then we need to pull_local() from the input
|
else:
|
||||||
# first before we deploy.
|
cmd.extend(["--branch", branch])
|
||||||
source_repo, commits = ostree.parse_input_commits(inputs["commits"])
|
|
||||||
target_repo = f"{tree}/ostree/repo"
|
cmd.append(target_path)
|
||||||
for commit, data in commits.items():
|
|
||||||
ref = data.get("ref", commit)
|
print(f"Deploying OSTree branch: {' '.join(cmd)}")
|
||||||
ostree.pull_local(source_repo, target_repo, remote, ref)
|
|
||||||
|
success, output = run_ostree_command(cmd)
|
||||||
if remote:
|
if not success:
|
||||||
ref = f"{remote}:{ref}"
|
return False, None
|
||||||
|
|
||||||
kargs = [f'--karg-append={v}' for v in kopts]
|
# Extract deployment ID from output
|
||||||
ostree.cli("admin", "deploy", ref,
|
deployment_id = output.strip()
|
||||||
*kargs, sysroot=tree, os=osname)
|
print(f"Deployed branch: {deployment_id}")
|
||||||
|
|
||||||
|
return True, deployment_id
|
||||||
|
|
||||||
|
|
||||||
def main(tree, inputs, options):
|
def create_ostree_layout(target_path):
|
||||||
osname = options["osname"]
|
"""Create OSTree filesystem layout"""
|
||||||
rootfs = options.get("rootfs")
|
|
||||||
mounts = options.get("mounts", [])
|
# Create required directories
|
||||||
kopts = options.get("kernel_opts", [])
|
ostree_dirs = [
|
||||||
ref = options.get("ref", "")
|
"ostree",
|
||||||
remote = options.get("remote")
|
"boot",
|
||||||
|
"sysroot"
|
||||||
|
]
|
||||||
|
|
||||||
|
for dir_name in ostree_dirs:
|
||||||
|
os.makedirs(os.path.join(target_path, dir_name), exist_ok=True)
|
||||||
|
|
||||||
|
print(f"Created OSTree layout in {target_path}")
|
||||||
|
|
||||||
# schema should catch the case in which there are more
|
|
||||||
# than one input but this adds a second layer of security
|
|
||||||
if len(inputs) > 1:
|
|
||||||
raise ValueError("Only one input accepted")
|
|
||||||
|
|
||||||
if rootfs:
|
def main(tree, options):
|
||||||
rootfs_id = make_fs_identifier(rootfs)
|
"""Main function for ostree deploy stage"""
|
||||||
kopts += [f"root={rootfs_id}"]
|
|
||||||
|
# Get options
|
||||||
with MountGuard() as mounter:
|
repository = options.get("repository", "ostree-repo")
|
||||||
for mount in mounts:
|
branch = options.get("branch", "debian/atomic")
|
||||||
path = mount.lstrip("/")
|
ref = options.get("ref")
|
||||||
path = os.path.join(tree, path)
|
target_subdir = options.get("target_subdir", "sysroot")
|
||||||
mounter.mount(path, path)
|
|
||||||
|
if not branch:
|
||||||
ostree_commit_deploy(tree, inputs, osname, remote, ref, kopts)
|
print("No branch specified for OSTree deployment")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# Create target path
|
||||||
|
target_path = os.path.join(tree, target_subdir)
|
||||||
|
os.makedirs(target_path, exist_ok=True)
|
||||||
|
|
||||||
|
# Create OSTree layout
|
||||||
|
create_ostree_layout(target_path)
|
||||||
|
|
||||||
|
# Deploy branch
|
||||||
|
success, deployment_id = deploy_ostree_branch(
|
||||||
|
repository, target_path, branch, ref
|
||||||
|
)
|
||||||
|
|
||||||
|
if not success:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# Write deployment info
|
||||||
|
deployment_info = {
|
||||||
|
"repository": repository,
|
||||||
|
"branch": branch,
|
||||||
|
"ref": ref,
|
||||||
|
"deployment_id": deployment_id,
|
||||||
|
"target_path": target_subdir
|
||||||
|
}
|
||||||
|
|
||||||
|
output_file = os.path.join(tree, "ostree-deployment.json")
|
||||||
|
with open(output_file, "w") as f:
|
||||||
|
json.dump(deployment_info, f, indent=2)
|
||||||
|
|
||||||
|
print("OSTree deployment completed successfully")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
stage_args = osbuild.api.arguments()
|
args = osbuild.api.arguments()
|
||||||
r = main(stage_args["tree"],
|
r = main(args["tree"], args["options"])
|
||||||
stage_args["inputs"],
|
|
||||||
stage_args["options"])
|
|
||||||
sys.exit(r)
|
sys.exit(r)
|
||||||
|
|
|
||||||
|
|
@ -1,98 +1,49 @@
|
||||||
{
|
{
|
||||||
"summary": "Deploy an OStree commit",
|
"name": "org.osbuild.ostree.deploy",
|
||||||
"description": [
|
"version": "1",
|
||||||
"Create an OSTree deployment[1] for a given ref.",
|
"description": "Deploy OSTree branch to target filesystem",
|
||||||
"Since OStree internally uses a hardlink farm to create the file system tree",
|
"options": {
|
||||||
"for the deployment from the commit data, the mountpoints for the final image",
|
"type": "object",
|
||||||
"need to be supplied via the `mounts` option, as hardlinks must not span",
|
"properties": {
|
||||||
"across file systems and therefore the boundaries need to be known when doing",
|
"repository": {
|
||||||
"the deployment.",
|
"type": "string",
|
||||||
"Creating a deployment also entails generating the Boot Loader Specification",
|
"default": "ostree-repo",
|
||||||
"entries to boot the system, which contain this the kernel command line.",
|
"description": "OSTree repository path"
|
||||||
"The `rootfs` option can be used to indicate the root file system, containing",
|
},
|
||||||
"the sysroot and the deployments. Additional kernel options can be passed via",
|
"branch": {
|
||||||
"`kernel_opts`.",
|
"type": "string",
|
||||||
"[1] https://ostree.readthedocs.io/en/latest/manual/deployment/"
|
"default": "debian/atomic",
|
||||||
],
|
"description": "OSTree branch to deploy"
|
||||||
"capabilities": [
|
},
|
||||||
"CAP_MAC_ADMIN"
|
"ref": {
|
||||||
],
|
"type": "string",
|
||||||
"schema_2": {
|
"description": "Specific OSTree ref to deploy (overrides branch)"
|
||||||
"options": {
|
},
|
||||||
"additionalProperties": false,
|
"target_subdir": {
|
||||||
"required": [
|
"type": "string",
|
||||||
"osname"
|
"default": "sysroot",
|
||||||
],
|
"description": "Target subdirectory for deployment"
|
||||||
"properties": {
|
|
||||||
"mounts": {
|
|
||||||
"description": "Mount points of the final file system",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"description": "Description of one mount point",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"osname": {
|
|
||||||
"description": "Name of the stateroot to be used in the deployment",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"kernel_opts": {
|
|
||||||
"description": "Additional kernel command line options",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"description": "A single kernel command line option",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ref": {
|
|
||||||
"description": "OStree ref to use for the deployment",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"remote": {
|
|
||||||
"description": "optional OStree remote to use for the deployment",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"rootfs": {
|
|
||||||
"description": "Identifier to locate the root file system",
|
|
||||||
"type": "object",
|
|
||||||
"oneOf": [
|
|
||||||
{
|
|
||||||
"required": [
|
|
||||||
"uuid"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"required": [
|
|
||||||
"label"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"label": {
|
|
||||||
"description": "Identify the root file system by label",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"uuid": {
|
|
||||||
"description": "Identify the root file system by UUID",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"inputs": {
|
"required": ["branch"]
|
||||||
"type": "object",
|
},
|
||||||
"additionalProperties": false,
|
"inputs": {
|
||||||
"required": [
|
"type": "object",
|
||||||
"commits"
|
"additionalProperties": false
|
||||||
],
|
},
|
||||||
"properties": {
|
"devices": {
|
||||||
"commits": {
|
"type": "object",
|
||||||
"type": "object",
|
"additionalProperties": false
|
||||||
"description": "OStree commit to deploy",
|
},
|
||||||
"additionalProperties": true
|
"mounts": {
|
||||||
}
|
"type": "object",
|
||||||
}
|
"additionalProperties": false
|
||||||
}
|
},
|
||||||
|
"capabilities": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"default": ["CAP_CHOWN", "CAP_DAC_OVERRIDE", "CAP_FOWNER", "CAP_FSETID", "CAP_MKNOD", "CAP_SETGID", "CAP_SETUID"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue