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
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import json
|
||||
|
||||
import osbuild.api
|
||||
from osbuild.util import ostree
|
||||
from osbuild.util.mnt import MountGuard
|
||||
|
||||
|
||||
def make_fs_identifier(desc):
|
||||
for key in ["uuid", "label"]:
|
||||
val = desc.get(key)
|
||||
if val:
|
||||
return f"{key.upper()}={val}"
|
||||
raise ValueError("unknown rootfs type")
|
||||
def run_ostree_command(cmd, cwd=None, env=None):
|
||||
"""Run ostree command and return result"""
|
||||
if env is None:
|
||||
env = {}
|
||||
|
||||
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):
|
||||
if len(inputs) == 0:
|
||||
if not ref:
|
||||
raise ValueError("ref should be specified in options")
|
||||
elif len(inputs) == 1:
|
||||
if ref:
|
||||
raise ValueError("Should not specify ref if input was specified")
|
||||
|
||||
# If we have an input then we need to pull_local() from the input
|
||||
# first before we deploy.
|
||||
source_repo, commits = ostree.parse_input_commits(inputs["commits"])
|
||||
target_repo = f"{tree}/ostree/repo"
|
||||
for commit, data in commits.items():
|
||||
ref = data.get("ref", commit)
|
||||
ostree.pull_local(source_repo, target_repo, remote, ref)
|
||||
|
||||
if remote:
|
||||
ref = f"{remote}:{ref}"
|
||||
|
||||
kargs = [f'--karg-append={v}' for v in kopts]
|
||||
ostree.cli("admin", "deploy", ref,
|
||||
*kargs, sysroot=tree, os=osname)
|
||||
def deploy_ostree_branch(repo_path, target_path, branch, ref=None):
|
||||
"""Deploy OSTree branch to target filesystem"""
|
||||
|
||||
# Prepare deploy command
|
||||
cmd = ["ostree", "admin", "deploy", "--repo", repo_path]
|
||||
|
||||
if ref:
|
||||
cmd.extend(["--branch", ref])
|
||||
else:
|
||||
cmd.extend(["--branch", branch])
|
||||
|
||||
cmd.append(target_path)
|
||||
|
||||
print(f"Deploying OSTree branch: {' '.join(cmd)}")
|
||||
|
||||
success, output = run_ostree_command(cmd)
|
||||
if not success:
|
||||
return False, None
|
||||
|
||||
# Extract deployment ID from output
|
||||
deployment_id = output.strip()
|
||||
print(f"Deployed branch: {deployment_id}")
|
||||
|
||||
return True, deployment_id
|
||||
|
||||
|
||||
def main(tree, inputs, options):
|
||||
osname = options["osname"]
|
||||
rootfs = options.get("rootfs")
|
||||
mounts = options.get("mounts", [])
|
||||
kopts = options.get("kernel_opts", [])
|
||||
ref = options.get("ref", "")
|
||||
remote = options.get("remote")
|
||||
def create_ostree_layout(target_path):
|
||||
"""Create OSTree filesystem layout"""
|
||||
|
||||
# Create required directories
|
||||
ostree_dirs = [
|
||||
"ostree",
|
||||
"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:
|
||||
rootfs_id = make_fs_identifier(rootfs)
|
||||
kopts += [f"root={rootfs_id}"]
|
||||
|
||||
with MountGuard() as mounter:
|
||||
for mount in mounts:
|
||||
path = mount.lstrip("/")
|
||||
path = os.path.join(tree, path)
|
||||
mounter.mount(path, path)
|
||||
|
||||
ostree_commit_deploy(tree, inputs, osname, remote, ref, kopts)
|
||||
def main(tree, options):
|
||||
"""Main function for ostree deploy stage"""
|
||||
|
||||
# Get options
|
||||
repository = options.get("repository", "ostree-repo")
|
||||
branch = options.get("branch", "debian/atomic")
|
||||
ref = options.get("ref")
|
||||
target_subdir = options.get("target_subdir", "sysroot")
|
||||
|
||||
if not branch:
|
||||
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__':
|
||||
stage_args = osbuild.api.arguments()
|
||||
r = main(stage_args["tree"],
|
||||
stage_args["inputs"],
|
||||
stage_args["options"])
|
||||
args = osbuild.api.arguments()
|
||||
r = main(args["tree"], args["options"])
|
||||
sys.exit(r)
|
||||
|
|
|
|||
|
|
@ -1,98 +1,49 @@
|
|||
{
|
||||
"summary": "Deploy an OStree commit",
|
||||
"description": [
|
||||
"Create an OSTree deployment[1] for a given ref.",
|
||||
"Since OStree internally uses a hardlink farm to create the file system tree",
|
||||
"for the deployment from the commit data, the mountpoints for the final image",
|
||||
"need to be supplied via the `mounts` option, as hardlinks must not span",
|
||||
"across file systems and therefore the boundaries need to be known when doing",
|
||||
"the deployment.",
|
||||
"Creating a deployment also entails generating the Boot Loader Specification",
|
||||
"entries to boot the system, which contain this the kernel command line.",
|
||||
"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",
|
||||
"`kernel_opts`.",
|
||||
"[1] https://ostree.readthedocs.io/en/latest/manual/deployment/"
|
||||
],
|
||||
"capabilities": [
|
||||
"CAP_MAC_ADMIN"
|
||||
],
|
||||
"schema_2": {
|
||||
"options": {
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"osname"
|
||||
],
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
"name": "org.osbuild.ostree.deploy",
|
||||
"version": "1",
|
||||
"description": "Deploy OSTree branch to target filesystem",
|
||||
"options": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"repository": {
|
||||
"type": "string",
|
||||
"default": "ostree-repo",
|
||||
"description": "OSTree repository path"
|
||||
},
|
||||
"branch": {
|
||||
"type": "string",
|
||||
"default": "debian/atomic",
|
||||
"description": "OSTree branch to deploy"
|
||||
},
|
||||
"ref": {
|
||||
"type": "string",
|
||||
"description": "Specific OSTree ref to deploy (overrides branch)"
|
||||
},
|
||||
"target_subdir": {
|
||||
"type": "string",
|
||||
"default": "sysroot",
|
||||
"description": "Target subdirectory for deployment"
|
||||
}
|
||||
},
|
||||
"inputs": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"commits"
|
||||
],
|
||||
"properties": {
|
||||
"commits": {
|
||||
"type": "object",
|
||||
"description": "OStree commit to deploy",
|
||||
"additionalProperties": true
|
||||
}
|
||||
}
|
||||
}
|
||||
"required": ["branch"]
|
||||
},
|
||||
"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"]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue