diff --git a/stages/org.osbuild.chattr b/stages/org.osbuild.chattr new file mode 100755 index 00000000..0bc8aa0e --- /dev/null +++ b/stages/org.osbuild.chattr @@ -0,0 +1,95 @@ +#!/usr/bin/python3 +""" +Runs `chattr` to set file/directory attributes. +""" + +import os +import subprocess +import sys +from typing import Dict +from urllib.parse import ParseResult, urlparse + +import osbuild.api + +SCHEMA_2 = r""" +"options": { + "additionalProperties": false, + "properties": { + "items": { + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^mount:\/\/[^\/]+\/|^tree:\/\/\/": { + "type": "object", + "required": ["immutable"], + "properties": { + "immutable": { + "type": "boolean", + "description": "Make the file/directory immutable", + "default": true + } + } + } + } + } + } +}, +"devices": { + "type": "object", + "additionalProperties": true +}, +"mounts": { + "type": "array" +} +""" + + +def parse_mount(url: ParseResult, args: Dict): + name = url.netloc + if name: + root = args["mounts"].get(name, {}).get("path") + if not root: + raise ValueError(f"Unknown mount '{name}'") + else: + root = args["paths"]["mounts"] + + return root + + +def parse_location(location, args): + url = urlparse(location) + + scheme = url.scheme + if scheme == "tree": + root = args["tree"] + elif scheme == "mount": + root = parse_mount(url, args) + else: + raise ValueError(f"Unsupported scheme '{scheme}'") + + assert url.path.startswith("/") + + path = os.path.relpath(url.path, "/") + path = os.path.join(root, path) + path = os.path.normpath(path) + + if url.path.endswith("/"): + path = os.path.join(path, ".") + + return path + + +def main(args, options): + for path, cmdargs in options["items"].items(): + immutable = cmdargs["immutable"] + dst = parse_location(path, args) + op = '+' if immutable else '-' + subprocess.run(["chattr", f"{op}i", dst], check=True) + + return 0 + + +if __name__ == '__main__': + _args = osbuild.api.arguments() + r = main(_args, _args["options"]) + sys.exit(r) diff --git a/test/data/manifests/fedora-coreos-container.json b/test/data/manifests/fedora-coreos-container.json index ad9a2461..fb56b861 100644 --- a/test/data/manifests/fedora-coreos-container.json +++ b/test/data/manifests/fedora-coreos-container.json @@ -736,6 +736,45 @@ "target": "/boot/efi" } ] + }, + { + "type": "org.osbuild.chattr", + "options": { + "items": { + "mount://root/": { + "immutable": true + } + } + }, + "devices": { + "disk": { + "type": "org.osbuild.loopback", + "options": { + "filename": "disk.img", + "partscan": true + } + } + }, + "mounts": [ + { + "name": "root", + "type": "org.osbuild.xfs", + "source": "disk", + "partition": 4, + "target": "/" + }, + { + "name": "ostree.deployment", + "type": "org.osbuild.ostree.deployment", + "options": { + "source": "mount", + "deployment": { + "ref": "ostree/1/1/0", + "osname": "fedora-coreos" + } + } + } + ] } ] }, @@ -945,6 +984,46 @@ "target": "/boot/efi" } ] + }, + { + "type": "org.osbuild.chattr", + "options": { + "items": { + "mount://root/": { + "immutable": true + } + } + }, + "devices": { + "disk": { + "type": "org.osbuild.loopback", + "options": { + "filename": "disk.img", + "partscan": true, + "sector-size": 4096 + } + } + }, + "mounts": [ + { + "name": "root", + "type": "org.osbuild.xfs", + "source": "disk", + "partition": 4, + "target": "/" + }, + { + "name": "ostree.deployment", + "type": "org.osbuild.ostree.deployment", + "options": { + "source": "mount", + "deployment": { + "ref": "ostree/1/1/0", + "osname": "fedora-coreos" + } + } + } + ] } ] }, diff --git a/test/data/manifests/fedora-coreos-container.mpp.yaml b/test/data/manifests/fedora-coreos-container.mpp.yaml index c50ab964..2494b867 100644 --- a/test/data/manifests/fedora-coreos-container.mpp.yaml +++ b/test/data/manifests/fedora-coreos-container.mpp.yaml @@ -265,6 +265,32 @@ pipelines: partition: mpp-format-int: '{image.layout[''EFI-SYSTEM''].partnum}' target: /boot/efi + - type: org.osbuild.chattr + options: + items: + mount://root/: + immutable: true + devices: + disk: + type: org.osbuild.loopback + options: + filename: disk.img + partscan: true + mounts: + - name: root + type: org.osbuild.xfs + source: disk + partition: + mpp-format-int: '{image.layout[''root''].partnum}' + target: / + - name: ostree.deployment + type: org.osbuild.ostree.deployment + options: + source: mount + deployment: + ref: ostree/1/1/0 + osname: + mpp-format-string: '{osname}' - name: raw-4k-image build: name:build stages: @@ -407,6 +433,34 @@ pipelines: partition: mpp-format-int: '{image4k.layout[''EFI-SYSTEM''].partnum}' target: /boot/efi + - type: org.osbuild.chattr + options: + items: + mount://root/: + immutable: true + devices: + disk: + type: org.osbuild.loopback + options: + filename: disk.img + partscan: true + sector-size: + mpp-format-int: "{four_k_sector_size}" + mounts: + - name: root + type: org.osbuild.xfs + source: disk + partition: + mpp-format-int: '{image4k.layout[''root''].partnum}' + target: / + - name: ostree.deployment + type: org.osbuild.ostree.deployment + options: + source: mount + deployment: + ref: ostree/1/1/0 + osname: + mpp-format-string: '{osname}' - name: raw-metal-image build: name:build stages: