Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/build-test-images.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,16 +61,16 @@ jobs:
- name: Checkout repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
if: ${{ inputs.useBuildx }}
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
- name: Build dockerfile and push image
uses: docker/build-push-action@v6
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
with:
push: ${{ inputs.publish }}
context: Tests/TestImages/${{ inputs.image }}
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/containerization-build-template.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
contents: read
packages: write
env:
DEVELOPER_DIR: "/Applications/Xcode-latest.app/Contents/Developer"
DEVELOPER_DIR: "/Applications/Xcode_swift_6.3.app/Contents/Developer"

steps:
- name: Checkout repository
Expand All @@ -50,9 +50,9 @@ jobs:
make protos
if ! git diff --quiet ; then echo the following files require formatting or license headers: ; git diff --name-only ; false ; fi

- name: Make containerization and docs
- name: Make containerization, examples, and docs
run: |
make clean containerization docs
make clean containerization examples docs
tar cfz _site.tgz _site
env:
BUILD_CONFIGURATION: ${{ inputs.release && 'release' || 'debug' }}
Expand Down Expand Up @@ -113,7 +113,7 @@ jobs:

steps:
- name: Setup Pages
uses: actions/configure-pages@v5
uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5

- name: Download a single artifact
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/docs-release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,4 @@ jobs:
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4

release:
if: startsWith(github.ref, 'refs/tags/')
Expand All @@ -47,7 +47,7 @@ jobs:
packages: read
steps:
- name: Create release
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2
with:
token: ${{ github.token }}
name: ${{ github.ref_name }}-prerelease
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ test_results/
*.swp
*.tar.gz
*.tar.xz
vmlinux
vmlinux*

# API docs for local preview only.
_site/
_serve/

41 changes: 29 additions & 12 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ SWIFT_CONFIGURATION := $(if $(filter-out false,$(WARNINGS_AS_ERRORS)),-Xswiftc -

# Commonly used locations
UNAME_S := $(shell uname -s)
UNAME_M := $(shell uname -m)
KERNEL_ARCH := $(if $(filter $(UNAME_M),aarch64 arm64),arm64,$(UNAME_M))
# Candidate kernel filenames in bin/ (compiled vmlinuz first, kata-fetched vmlinux fallback).
ifeq ($(KERNEL_ARCH),x86_64)
KERNEL_CANDIDATES := bin/vmlinuz-x86_64 bin/vmlinux-x86_64
else
KERNEL_CANDIDATES := bin/vmlinux-$(KERNEL_ARCH)
endif
ifeq ($(UNAME_S),Darwin)
SWIFT ?= /usr/bin/swift
else
Expand Down Expand Up @@ -169,25 +177,26 @@ coverage: test

.PHONY: integration
integration:
ifeq (,$(wildcard bin/vmlinux))
@echo No bin/vmlinux kernel found. See fetch-default-kernel target.
@exit 1
endif
@echo Running the integration tests...
@./bin/containerization-integration
@kernel="$$(for f in $(KERNEL_CANDIDATES); do [ -f $$f ] && echo $$f && break; done)"; \
if [ -z "$$kernel" ]; then \
echo "No kernel found. Looked for: $(KERNEL_CANDIDATES). See fetch-default-kernel target or build via kernel/Makefile."; \
exit 1; \
fi; \
echo "Running the integration tests with kernel $$kernel..."; \
./bin/containerization-integration --kernel "$$kernel"

.PHONY: fetch-default-kernel
fetch-default-kernel:
@mkdir -p .local/ bin/
ifeq (,$(wildcard .local/kata.tar.gz))
@curl -SsL -o .local/kata.tar.gz ${KATA_BINARY_PACKAGE}
endif
ifeq (,$(wildcard .local/vmlinux))
ifeq (,$(wildcard .local/vmlinux-$(KERNEL_ARCH)))
@tar -zxf .local/kata.tar.gz -C .local/ --strip-components=1
@cp -L .local/opt/kata/share/kata-containers/vmlinux.container .local/vmlinux
@cp -L .local/opt/kata/share/kata-containers/vmlinux.container .local/vmlinux-$(KERNEL_ARCH)
endif
ifeq (,$(wildcard bin/vmlinux))
@cp .local/vmlinux bin/vmlinux
ifeq (,$(wildcard bin/vmlinux-$(KERNEL_ARCH)))
@cp .local/vmlinux-$(KERNEL_ARCH) bin/vmlinux-$(KERNEL_ARCH)
endif

.PHONY: check
Expand All @@ -203,8 +212,8 @@ swift-fmt:
@$(SWIFT) format --recursive --configuration .swift-format -i $(SWIFT_SRC)

swift-fmt-check:
@echo Checking code formatting compliance...
@$(SWIFT) format lint --recursive --strict --configuration .swift-format-nolint $(SWIFT_SRC)
@echo Checking code formatting compliance...
@$(SWIFT) format lint --recursive --strict --configuration .swift-format-nolint $(SWIFT_SRC)

.PHONY: update-licenses
update-licenses:
Expand Down Expand Up @@ -246,6 +255,14 @@ cleancontent:
@echo Cleaning the content...
@rm -rf ~/Library/Application\ Support/com.apple.containerization

.PHONY: examples
examples:
@echo Building examples...
@mkdir -p bin
@"$(MAKE)" -C examples/sandboxy build BUILD_CONFIGURATION=$(BUILD_CONFIGURATION)
@install examples/sandboxy/bin/sandboxy ./bin/
@codesign --force --sign - --timestamp=none --entitlements=signing/vz.entitlements bin/sandboxy

.PHONY: clean
clean:
@echo Cleaning build files...
Expand Down
25 changes: 17 additions & 8 deletions Sources/Containerization/ContainerManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -302,15 +302,17 @@ public struct ContainerManager: Sendable {
if let imageConfig {
config.process = .init(from: imageConfig)
}
if networking, let interface = try self.network?.createInterface(id) {
config.interfaces = [interface]
guard let gateway = interface.ipv4Gateway else {
throw ContainerizationError(
.invalidState,
message: "missing ipv4 gateway for container \(id)"
)
if networking {
if let interface = try self.network?.createInterface(id) {
config.interfaces = [interface]
guard let gateway = interface.ipv4Gateway else {
throw ContainerizationError(
.invalidState,
message: "missing ipv4 gateway for container \(id)"
)
}
config.dns = .init(nameservers: [gateway.description])
}
config.dns = .init(nameservers: [gateway.description])
}
config.bootLog = BootLog.file(path: self.containerRoot.appendingPathComponent(id).appendingPathComponent("bootlog.log"))
try configuration(&config)
Expand Down Expand Up @@ -378,4 +380,11 @@ extension CIDRv4 {
}
}

extension CIDRv6 {
/// The gateway address of the network.
public var gateway: IPv6Address {
IPv6Address(self.lower.value + 1)
}
}

#endif
11 changes: 10 additions & 1 deletion Sources/Containerization/Interface.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,16 @@ public protocol Interface: Sendable {
/// Example: `192.168.64.3/24`
var ipv4Address: CIDRv4 { get }

/// The IP address for the default route, or nil for no default route.
/// The IPv4 gateway address for the default route, or nil for no IPv4 default route.
var ipv4Gateway: IPv4Address? { get }

/// The interface IPv6 address and subnet prefix length, as a CIDRv6 address, or nil for no IPv6 address.
/// Example: `fd00::1/64`
var ipv6Address: CIDRv6? { get }

/// The IPv6 gateway address for the default route, or nil for no IPv6 default route.
var ipv6Gateway: IPv6Address? { get }

/// The interface MAC address, or nil to auto-configure the address.
var macAddress: MACAddress? { get }

Expand All @@ -34,4 +41,6 @@ public protocol Interface: Sendable {

extension Interface {
public var mtu: UInt32 { 1500 }
public var ipv6Address: CIDRv6? { nil }
public var ipv6Gateway: IPv6Address? { nil }
}
6 changes: 6 additions & 0 deletions Sources/Containerization/Kernel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
//===----------------------------------------------------------------------===//

import Foundation
import Logging

/// An object representing a Linux kernel used to boot a virtual machine.
/// In addition to a path to the kernel itself, this type stores relevant
Expand All @@ -37,6 +38,11 @@ public struct Kernel: Sendable, Codable {
self.kernelArgs.append("panic=\(level)")
}

// Sets the log level for the Agent
mutating public func setAgentLogLevel(level: Logger.Level) {
self.initArgs.append(contentsOf: ["--log-level", level.description])
}

/// Additional kernel arguments.
public var kernelArgs: [String]
/// Additional arguments passed to the Initial Process / Agent.
Expand Down
125 changes: 125 additions & 0 deletions Sources/Containerization/LinuxBlockIO.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
//===----------------------------------------------------------------------===//
// Copyright © 2026 Apple Inc. and the Containerization project authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//===----------------------------------------------------------------------===//

import ContainerizationOCI

/// Block I/O resource limits applied to the container cgroup.
public struct LinuxBlockIO: Sendable {
/// The relative weight of the cgroup for block I/O. Valid range is 10 to 1000.
public var weight: UInt16?
/// The relative weight applied to tasks of the cgroup but not their descendant cgroups.
public var leafWeight: UInt16?
/// Per-device weight overrides.
public var weightDevice: [LinuxWeightDevice]
/// Per-device read rate limits in bytes per second.
public var throttleReadBpsDevice: [LinuxThrottleDevice]
/// Per-device write rate limits in bytes per second.
public var throttleWriteBpsDevice: [LinuxThrottleDevice]
/// Per-device read rate limits in IO operations per second.
public var throttleReadIOPSDevice: [LinuxThrottleDevice]
/// Per-device write rate limits in IO operations per second.
public var throttleWriteIOPSDevice: [LinuxThrottleDevice]

public init(
weight: UInt16? = nil,
leafWeight: UInt16? = nil,
weightDevice: [LinuxWeightDevice] = [],
throttleReadBpsDevice: [LinuxThrottleDevice] = [],
throttleWriteBpsDevice: [LinuxThrottleDevice] = [],
throttleReadIOPSDevice: [LinuxThrottleDevice] = [],
throttleWriteIOPSDevice: [LinuxThrottleDevice] = []
) {
self.weight = weight
self.leafWeight = leafWeight
self.weightDevice = weightDevice
self.throttleReadBpsDevice = throttleReadBpsDevice
self.throttleWriteBpsDevice = throttleWriteBpsDevice
self.throttleReadIOPSDevice = throttleReadIOPSDevice
self.throttleWriteIOPSDevice = throttleWriteIOPSDevice
}

/// Convert to OCI format for transport.
public func toOCI() -> ContainerizationOCI.LinuxBlockIO {
ContainerizationOCI.LinuxBlockIO(
weight: self.weight,
leafWeight: self.leafWeight,
weightDevice: self.weightDevice.map { $0.toOCI() },
throttleReadBpsDevice: self.throttleReadBpsDevice.map { $0.toOCI() },
throttleWriteBpsDevice: self.throttleWriteBpsDevice.map { $0.toOCI() },
throttleReadIOPSDevice: self.throttleReadIOPSDevice.map { $0.toOCI() },
throttleWriteIOPSDevice: self.throttleWriteIOPSDevice.map { $0.toOCI() }
)
}
}

/// A per-device block I/O weight override.
public struct LinuxWeightDevice: Sendable {
/// The major device number.
public var major: Int64
/// The minor device number.
public var minor: Int64
/// The relative weight applied to the device. Valid range is 10 to 1000.
public var weight: UInt16?
/// The relative weight applied to tasks of the cgroup but not their descendant cgroups.
public var leafWeight: UInt16?

public init(
major: Int64,
minor: Int64,
weight: UInt16? = nil,
leafWeight: UInt16? = nil
) {
self.major = major
self.minor = minor
self.weight = weight
self.leafWeight = leafWeight
}

/// Convert to OCI format for transport.
public func toOCI() -> ContainerizationOCI.LinuxWeightDevice {
ContainerizationOCI.LinuxWeightDevice(
major: self.major,
minor: self.minor,
weight: self.weight,
leafWeight: self.leafWeight
)
}
}

/// A per-device block I/O throughput limit.
public struct LinuxThrottleDevice: Sendable {
/// The major device number.
public var major: Int64
/// The minor device number.
public var minor: Int64
/// The rate limit applied to the device.
public var rate: UInt64

public init(major: Int64, minor: Int64, rate: UInt64) {
self.major = major
self.minor = minor
self.rate = rate
}

/// Convert to OCI format for transport.
public func toOCI() -> ContainerizationOCI.LinuxThrottleDevice {
ContainerizationOCI.LinuxThrottleDevice(
major: self.major,
minor: self.minor,
rate: self.rate
)
}
}
Loading