mirror of
https://github.com/cirruslabs/tart.git
synced 2026-10-02 04:01:12 +02:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
15754bcc8d |
+147
-47
@@ -1,64 +1,164 @@
|
||||
use_compute_credits: true
|
||||
|
||||
task:
|
||||
name: Test
|
||||
alias: test
|
||||
persistent_worker:
|
||||
labels:
|
||||
name: dev-mini
|
||||
resources:
|
||||
tart-vms: 1
|
||||
build_script:
|
||||
- swift build
|
||||
test_script:
|
||||
# Add /usr/sbin to PATH, otherwise testDiskutilInfo() fails to locate "diskutil"
|
||||
- export PATH=$PATH:/usr/sbin
|
||||
- swift test
|
||||
integration_test_script:
|
||||
- codesign --sign - --entitlements Resources/tart-dev.entitlements --force .build/debug/tart
|
||||
- export PATH=$(pwd)/.build/arm64-apple-macosx/debug:$PATH
|
||||
# Run integration tests
|
||||
- cd integration-tests
|
||||
- python3 -m venv --symlinks venv
|
||||
- source venv/bin/activate
|
||||
- pip install -r requirements.txt
|
||||
- pytest --verbose --junit-xml=pytest-junit.xml
|
||||
- go test -v ./...
|
||||
pytest_junit_result_artifacts:
|
||||
path: "integration-tests/pytest-junit.xml"
|
||||
format: junit
|
||||
|
||||
task:
|
||||
name: Markdown Lint
|
||||
only_if: $CIRRUS_BRANCH != 'gh-pages' && changesInclude('**.md')
|
||||
container:
|
||||
image: node:latest
|
||||
install_script: npm install -g markdownlint-cli
|
||||
lint_script: markdownlint --config=docs/.markdownlint.yml docs/
|
||||
|
||||
task:
|
||||
name: Lint
|
||||
alias: lint
|
||||
macos_instance:
|
||||
image: ghcr.io/cirruslabs/macos-runner:sequoia
|
||||
lint_script:
|
||||
- swift package plugin --allow-writing-to-package-directory swiftformat --cache ignore --lint --report swiftformat.json .
|
||||
always:
|
||||
swiftformat_report_artifacts:
|
||||
path: swiftformat.json
|
||||
format: swiftformat
|
||||
|
||||
task:
|
||||
only_if: $CIRRUS_TAG == ''
|
||||
env:
|
||||
matrix:
|
||||
BUILD_ARCH: arm64
|
||||
BUILD_ARCH: x86_64
|
||||
name: Build ($BUILD_ARCH)
|
||||
alias: build
|
||||
macos_instance:
|
||||
image: ghcr.io/cirruslabs/macos-runner:sequoia
|
||||
build_script: swift build --arch $BUILD_ARCH --product tart
|
||||
sign_script: codesign --sign - --entitlements Resources/tart-dev.entitlements --force .build/$BUILD_ARCH-apple-macosx/debug/tart
|
||||
binary_artifacts:
|
||||
path: .build/$BUILD_ARCH-apple-macosx/debug/tart
|
||||
|
||||
task:
|
||||
only_if: $CIRRUS_TAG == '' && ($CIRRUS_USER_PERMISSION == 'write' || $CIRRUS_USER_PERMISSION == 'admin')
|
||||
name: Release (Dry Run)
|
||||
depends_on:
|
||||
- lint
|
||||
- build
|
||||
macos_instance:
|
||||
image: ghcr.io/cirruslabs/macos-runner:tahoe
|
||||
image: ghcr.io/cirruslabs/macos-runner:sequoia
|
||||
env:
|
||||
MACOS_SIGN_P12: ENCRYPTED[!183482723ca1a95f9c4439f7a79c9d3b115472bb18c739ed1586e12d3914ccf94ade8169eeda7332fc204f8be9c27d9f!]
|
||||
MACOS_SIGN_PASSWORD: ENCRYPTED[!417423346c567f12007f42d084bff1cfee30ee14f7e8258550157679a269c70d541c9f19224224ab0293b10f2c6d4c5e!]
|
||||
KEYCHAIN_PASSWORD: password101
|
||||
MACOS_NOTARY_PROFILE_NAME: notarytool
|
||||
MACOS_NOTARY_ISSUER_ID: ENCRYPTED[!74076906e9fa36bca3c1da1637b0759b58bb009eb1a707446896eefad3767e8dba1d0f87e71106b98cde98ac4b037a2a!]
|
||||
MACOS_NOTARY_KEY_ID: ENCRYPTED[!af9e5da1010a6b04e548ef494acc77a6e0ce176549de98f81c5b5cdd72856de09f77e51cf0849e3c4b7a2d2c22f25ca8!]
|
||||
MACOS_NOTARY_KEY: ENCRYPTED[!c70c53f3e6c163931c7cdf9d90aff8934ef21d5dd1090158688e00b94e97c68257d9cf4ae1df873e6ae0d949866aee72!]
|
||||
CERTIFICATE_PATH: "${CIRRUS_WORKING_DIR}/goreleaser.p12"
|
||||
KEY_PATH: "${CIRRUS_WORKING_DIR}/goreleaser.p8"
|
||||
KEYCHAIN_PATH: "${CIRRUS_WORKING_DIR}/goreleaser.keychain-db"
|
||||
MACOS_CERTIFICATE: ENCRYPTED[552b9d275d1c2bdbc1bff778b104a8f9a53cbd0d59344d4b7f6d0ca3c811a5cefb97bef9ba0ef31c219cb07bdacdd2c2]
|
||||
AC_PASSWORD: ENCRYPTED[4a761023e7e06fe2eb350c8b6e8e7ca961af193cb9ba47605f25f1d353abc3142606f412e405be48fd897a78787ea8c2]
|
||||
GITHUB_TOKEN: ENCRYPTED[!98ace8259c6024da912c14d5a3c5c6aac186890a8d4819fad78f3e0c41a4e0cd3a2537dd6e91493952fb056fa434be7c!]
|
||||
GORELEASER_KEY: ENCRYPTED[!9b80b6ef684ceaf40edd4c7af93014ee156c8aba7e6e5795f41c482729887b5c31f36b651491d790f1f668670888d9fd!]
|
||||
setup_script:
|
||||
- cd $HOME
|
||||
- echo $MACOS_CERTIFICATE | base64 --decode > certificate.p12
|
||||
- security create-keychain -p password101 build.keychain
|
||||
- security default-keychain -s build.keychain
|
||||
- security unlock-keychain -p password101 build.keychain
|
||||
- security import certificate.p12 -k build.keychain -P password101 -T /usr/bin/codesign -T /usr/bin/pkgbuild
|
||||
- security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k password101 build.keychain
|
||||
- xcrun notarytool store-credentials "notarytool" --apple-id "hello@cirruslabs.org" --team-id "9M2P8L4D89" --password $AC_PASSWORD
|
||||
install_script:
|
||||
- brew install go
|
||||
- brew install mitchellh/gon/gon
|
||||
- brew install --cask goreleaser/tap/goreleaser-pro
|
||||
info_script:
|
||||
- security find-identity -v
|
||||
- xcodebuild -version
|
||||
- swift -version
|
||||
goreleaser_script: |
|
||||
# import certificate and key from secrets
|
||||
echo -n "$MACOS_SIGN_P12" | base64 --decode -o $CERTIFICATE_PATH
|
||||
echo -n "$MACOS_NOTARY_KEY" | base64 --decode -o $KEY_PATH
|
||||
|
||||
# create temporary keychain
|
||||
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
|
||||
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
|
||||
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
|
||||
|
||||
# import certificate to keychain
|
||||
security import $CERTIFICATE_PATH -P "$MACOS_SIGN_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
|
||||
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
|
||||
security list-keychain -d user -s $KEYCHAIN_PATH
|
||||
|
||||
# create notary profile
|
||||
xcrun notarytool store-credentials "notarytool" \
|
||||
--key "$KEY_PATH" \
|
||||
--key-id "$MACOS_NOTARY_KEY_ID" \
|
||||
--issuer "$MACOS_NOTARY_ISSUER_ID" \
|
||||
--keychain $KEYCHAIN_PATH
|
||||
|
||||
security find-identity -v
|
||||
|
||||
echo $KEYCHAIN_PATH
|
||||
|
||||
security default-keychain -s "$KEYCHAIN_PATH"
|
||||
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||||
|
||||
goreleaser release --skip=publish --snapshot --clean
|
||||
goreleaser_script: goreleaser release --skip=publish --snapshot --clean
|
||||
always:
|
||||
check_dist_script:
|
||||
- find dist/
|
||||
- du -hs dist/
|
||||
dist_artifacts:
|
||||
paths:
|
||||
- dist/dmg/tart_all/Tart.dmg
|
||||
- dist/homebrew/Casks/tart.rb
|
||||
path: "dist/*"
|
||||
|
||||
task:
|
||||
name: Release
|
||||
only_if: $CIRRUS_TAG != ''
|
||||
depends_on:
|
||||
- lint
|
||||
- test
|
||||
- build
|
||||
macos_instance:
|
||||
image: ghcr.io/cirruslabs/macos-runner:sequoia
|
||||
env:
|
||||
MACOS_CERTIFICATE: ENCRYPTED[552b9d275d1c2bdbc1bff778b104a8f9a53cbd0d59344d4b7f6d0ca3c811a5cefb97bef9ba0ef31c219cb07bdacdd2c2]
|
||||
AC_PASSWORD: ENCRYPTED[4a761023e7e06fe2eb350c8b6e8e7ca961af193cb9ba47605f25f1d353abc3142606f412e405be48fd897a78787ea8c2]
|
||||
GITHUB_TOKEN: ENCRYPTED[!98ace8259c6024da912c14d5a3c5c6aac186890a8d4819fad78f3e0c41a4e0cd3a2537dd6e91493952fb056fa434be7c!]
|
||||
GORELEASER_KEY: ENCRYPTED[!9b80b6ef684ceaf40edd4c7af93014ee156c8aba7e6e5795f41c482729887b5c31f36b651491d790f1f668670888d9fd!]
|
||||
SENTRY_ORG: cirrus-labs
|
||||
SENTRY_PROJECT: persistent-workers
|
||||
SENTRY_AUTH_TOKEN: ENCRYPTED[!9eaf2875d51b113e2f68598441ff8e6b2e53242e48fcb93633bd75a373fbe2e7caa900d837cc92f0b142b65579731644!]
|
||||
setup_script:
|
||||
- cd $HOME
|
||||
- echo $MACOS_CERTIFICATE | base64 --decode > certificate.p12
|
||||
- security create-keychain -p password101 build.keychain
|
||||
- security default-keychain -s build.keychain
|
||||
- security unlock-keychain -p password101 build.keychain
|
||||
- security import certificate.p12 -k build.keychain -P password101 -T /usr/bin/codesign -T /usr/bin/pkgbuild
|
||||
- security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k password101 build.keychain
|
||||
- xcrun notarytool store-credentials "notarytool" --apple-id "hello@cirruslabs.org" --team-id "9M2P8L4D89" --password $AC_PASSWORD
|
||||
install_script:
|
||||
- brew install go getsentry/tools/sentry-cli
|
||||
- brew install mitchellh/gon/gon
|
||||
- brew install --cask goreleaser/tap/goreleaser-pro
|
||||
info_script:
|
||||
- security find-identity -v
|
||||
- xcodebuild -version
|
||||
- swift -version
|
||||
release_script: goreleaser
|
||||
upload_sentry_debug_files_script:
|
||||
- cd .build/arm64-apple-macosx/release/
|
||||
# Generate and upload symbols
|
||||
- dsymutil tart
|
||||
- sentry-cli debug-files upload tart.dSYM/
|
||||
- SENTRY_PROJECT=tart sentry-cli debug-files upload tart.dSYM/
|
||||
# Bundle and upload sources
|
||||
- sentry-cli debug-files bundle-sources tart.dSYM
|
||||
- sentry-cli debug-files upload tart.src.zip
|
||||
- SENTRY_PROJECT=tart sentry-cli debug-files upload tart.src.zip
|
||||
create_sentry_release_script:
|
||||
- export SENTRY_RELEASE="tart@$CIRRUS_TAG"
|
||||
- sentry-cli releases new $SENTRY_RELEASE
|
||||
- sentry-cli releases set-commits $SENTRY_RELEASE --auto
|
||||
- sentry-cli releases finalize $SENTRY_RELEASE
|
||||
|
||||
task:
|
||||
name: Deploy Documentation
|
||||
only_if: $CIRRUS_BRANCH == 'main'
|
||||
container:
|
||||
image: ghcr.io/cirruslabs/mkdocs-material-insiders:latest
|
||||
registry_config: ENCRYPTED[!cf1a0f25325aa75bad3ce6ebc890bc53eb0044c02efa70d8cefb83ba9766275a994b4831706c52630a0692b2fa9cfb9e!]
|
||||
env:
|
||||
DEPLOY_TOKEN: ENCRYPTED[!45ed45666558902ed1c2400add734ec063103bec31841847e8c8764802fca229bfa6d85c690e16ad159e047574b48793!]
|
||||
deploy_script:
|
||||
- git config --global user.name "Cirrus CI"
|
||||
- git config --global user.name "hello@cirruslabs.org"
|
||||
- git remote set-url origin https://$DEPLOY_TOKEN@github.com/cirruslabs/tart/
|
||||
- mkdocs --verbose gh-deploy --force --remote-branch gh-pages
|
||||
|
||||
+9
-30
@@ -1,4 +1,3 @@
|
||||
# yaml-language-server: $schema=https://goreleaser.com/static/schema-pro.json
|
||||
version: 2
|
||||
|
||||
project_name: tart
|
||||
@@ -18,38 +17,15 @@ builds:
|
||||
goarch:
|
||||
- arm64
|
||||
- amd64
|
||||
binary: tart
|
||||
binary: tart.app/Contents/MacOS/tart
|
||||
prebuilt:
|
||||
path: '.build/{{- if eq .Arch "arm64" }}arm64{{- else }}x86_64{{ end }}-apple-macosx/release/tart'
|
||||
|
||||
universal_binaries:
|
||||
- name_template: tart.app/Contents/MacOS/tart
|
||||
replace: true
|
||||
|
||||
app_bundles:
|
||||
- name: Tart
|
||||
bundle: com.github.cirruslabs.tart
|
||||
icon: Resources/AppIcon.icns
|
||||
extra_files:
|
||||
- src: Resources/embedded.provisionprofile
|
||||
dst: Contents/embedded.provisionprofile
|
||||
|
||||
dmg:
|
||||
- name: Tart
|
||||
use: appbundle
|
||||
replace: true
|
||||
|
||||
notarize:
|
||||
macos_native:
|
||||
- enabled: "true"
|
||||
sign:
|
||||
keychain: "{{.Env.KEYCHAIN_PATH}}"
|
||||
identity: "Developer ID Application: Cirrus Labs, Inc."
|
||||
options: [runtime]
|
||||
entitlements: ./Resources/tart-prod.entitlements
|
||||
notarize:
|
||||
profile_name: "notarytool"
|
||||
wait: true
|
||||
hooks:
|
||||
post: gon gon.hcl
|
||||
|
||||
archives:
|
||||
- name_template: "{{ .ProjectName }}"
|
||||
@@ -68,9 +44,8 @@ archives:
|
||||
release:
|
||||
prerelease: auto
|
||||
|
||||
homebrew_casks:
|
||||
brews:
|
||||
- name: tart
|
||||
app: Tart.app
|
||||
repository:
|
||||
owner: cirruslabs
|
||||
name: homebrew-cli
|
||||
@@ -86,6 +61,10 @@ homebrew_casks:
|
||||
description: Run macOS and Linux VMs on Apple Hardware
|
||||
skip_upload: auto
|
||||
dependencies:
|
||||
- formula: "cirruslabs/cli/softnet"
|
||||
- "cirruslabs/cli/softnet"
|
||||
install: |
|
||||
libexec.install Dir["*"]
|
||||
bin.write_exec_script "#{libexec}/tart.app/Contents/MacOS/tart"
|
||||
generate_completions_from_executable(libexec/"tart.app/Contents/MacOS/tart", "--generate-completion-script")
|
||||
custom_block: |
|
||||
depends_on :macos => :ventura
|
||||
|
||||
Binary file not shown.
@@ -7,7 +7,7 @@
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Tart</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.github.cirruslabs.tart</string>
|
||||
<string>org.cirruslabs.tart</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>tart</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
|
||||
@@ -243,6 +243,13 @@ struct Run: AsyncParsableCommand {
|
||||
@Flag(help: ArgumentHelp("Restrict network access to the host-only network"))
|
||||
var netHost: Bool = false
|
||||
|
||||
@Option(help: ArgumentHelp("Use externally managed connected datagram socket file descriptor for VM networking (e.g. --net-fd=3)", discussion: """
|
||||
This option allows integrating Tart with externally launched networking helpers.
|
||||
|
||||
The provided file descriptor must reference a connected datagram socket.
|
||||
""", valueName: "fd", visibility: .hidden))
|
||||
var netFd: Int32?
|
||||
|
||||
@Option(help: ArgumentHelp("Set the root disk options (e.g. --root-disk-opts=\"ro\" or --root-disk-opts=\"caching=cached,sync=none\")",
|
||||
discussion: """
|
||||
Options are comma-separated and are as follows:
|
||||
@@ -295,14 +302,19 @@ struct Run: AsyncParsableCommand {
|
||||
netSoftnet = true
|
||||
}
|
||||
|
||||
if let netFd = netFd, netFd < 0 {
|
||||
throw ValidationError("--net-fd must be greater than or equal to 0")
|
||||
}
|
||||
|
||||
// Check that no more than one network option is specified
|
||||
var netFlags = 0
|
||||
if netBridged.count > 0 { netFlags += 1 }
|
||||
if netSoftnet { netFlags += 1 }
|
||||
if netHost { netFlags += 1 }
|
||||
if netFd != nil { netFlags += 1 }
|
||||
|
||||
if netFlags > 1 {
|
||||
throw ValidationError("--net-bridged, --net-softnet and --net-host are mutually exclusive")
|
||||
throw ValidationError("--net-bridged, --net-softnet, --net-host and --net-fd are mutually exclusive")
|
||||
}
|
||||
|
||||
if graphics && noGraphics {
|
||||
@@ -620,6 +632,10 @@ struct Run: AsyncParsableCommand {
|
||||
}
|
||||
|
||||
func userSpecifiedNetwork(vmDir: VMDirectory) throws -> Network? {
|
||||
if let netFd = netFd {
|
||||
return try NetworkFD(fd: netFd)
|
||||
}
|
||||
|
||||
var softnetExtraArguments: [String] = []
|
||||
|
||||
if let netSoftnetAllow = netSoftnetAllow {
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import Darwin
|
||||
import Foundation
|
||||
import Semaphore
|
||||
import Virtualization
|
||||
|
||||
class NetworkFD: Network {
|
||||
private let fd: Int32
|
||||
|
||||
init(fd: Int32) throws {
|
||||
self.fd = fd
|
||||
|
||||
try Self.validateFD(fd)
|
||||
try Self.validateSocketType(fd)
|
||||
try Self.validateConnected(fd)
|
||||
}
|
||||
|
||||
func attachments() -> [VZNetworkDeviceAttachment] {
|
||||
[VZFileHandleNetworkDeviceAttachment(fileHandle: FileHandle(fileDescriptor: fd))]
|
||||
}
|
||||
|
||||
func run(_ sema: AsyncSemaphore) throws {
|
||||
// no-op, only used for Softnet
|
||||
}
|
||||
|
||||
func stop() async throws {
|
||||
// no-op, only used for Softnet
|
||||
}
|
||||
|
||||
private static func validateFD(_ fd: Int32) throws {
|
||||
if fcntl(fd, F_GETFD) == -1 {
|
||||
throw RuntimeError.VMConfigurationError(
|
||||
"invalid --net-fd \(fd): file descriptor is not open (\(errnoDescription(errno)))"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private static func validateSocketType(_ fd: Int32) throws {
|
||||
var socketType: Int32 = 0
|
||||
var optionLength = socklen_t(MemoryLayout<Int32>.size)
|
||||
|
||||
if getsockopt(fd, SOL_SOCKET, SO_TYPE, &socketType, &optionLength) == -1 {
|
||||
throw RuntimeError.VMConfigurationError(
|
||||
"invalid --net-fd \(fd): file descriptor must reference a socket (\(errnoDescription(errno)))"
|
||||
)
|
||||
}
|
||||
|
||||
if socketType != SOCK_DGRAM {
|
||||
throw RuntimeError.VMConfigurationError(
|
||||
"invalid --net-fd \(fd): expected SOCK_DGRAM socket, got \(socketType)"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private static func validateConnected(_ fd: Int32) throws {
|
||||
var address = sockaddr_storage()
|
||||
var addressLength = socklen_t(MemoryLayout<sockaddr_storage>.size)
|
||||
|
||||
let result = withUnsafeMutablePointer(to: &address) { pointer in
|
||||
pointer.withMemoryRebound(to: sockaddr.self, capacity: 1) { sockaddrPointer in
|
||||
getpeername(fd, sockaddrPointer, &addressLength)
|
||||
}
|
||||
}
|
||||
|
||||
if result == -1 {
|
||||
throw RuntimeError.VMConfigurationError(
|
||||
"invalid --net-fd \(fd): socket must be connected (\(errnoDescription(errno)))"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private static func errnoDescription(_ code: CInt) -> String {
|
||||
String(cString: strerror(code))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import Darwin
|
||||
import Foundation
|
||||
import XCTest
|
||||
@testable import tart
|
||||
|
||||
final class NetworkFDTests: XCTestCase {
|
||||
func testAcceptsConnectedDatagramSocket() throws {
|
||||
let (fdLeft, fdRight) = try makeDatagramSocketPair()
|
||||
defer {
|
||||
_ = close(fdLeft)
|
||||
_ = close(fdRight)
|
||||
}
|
||||
|
||||
let network = try NetworkFD(fd: fdLeft)
|
||||
|
||||
XCTAssertEqual(network.attachments().count, 1)
|
||||
}
|
||||
|
||||
func testRejectsClosedFileDescriptor() throws {
|
||||
let (fdLeft, fdRight) = try makeDatagramSocketPair()
|
||||
defer { _ = close(fdRight) }
|
||||
|
||||
_ = close(fdLeft)
|
||||
|
||||
XCTAssertThrowsError(try NetworkFD(fd: fdLeft)) { error in
|
||||
self.assertVMConfigurationError(error, contains: "file descriptor is not open")
|
||||
}
|
||||
}
|
||||
|
||||
func testRejectsNonSocketFileDescriptor() throws {
|
||||
let fileURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
|
||||
XCTAssertTrue(FileManager.default.createFile(atPath: fileURL.path, contents: Data()))
|
||||
defer { try? FileManager.default.removeItem(at: fileURL) }
|
||||
|
||||
let fd = open(fileURL.path, O_RDONLY)
|
||||
XCTAssertGreaterThanOrEqual(fd, 0)
|
||||
defer { _ = close(fd) }
|
||||
|
||||
XCTAssertThrowsError(try NetworkFD(fd: fd)) { error in
|
||||
self.assertVMConfigurationError(error, contains: "must reference a socket")
|
||||
}
|
||||
}
|
||||
|
||||
func testRejectsUnconnectedDatagramSocket() throws {
|
||||
let fd = socket(AF_UNIX, SOCK_DGRAM, 0)
|
||||
XCTAssertGreaterThanOrEqual(fd, 0)
|
||||
defer { _ = close(fd) }
|
||||
|
||||
XCTAssertThrowsError(try NetworkFD(fd: fd)) { error in
|
||||
self.assertVMConfigurationError(error, contains: "socket must be connected")
|
||||
}
|
||||
}
|
||||
|
||||
private func makeDatagramSocketPair() throws -> (Int32, Int32) {
|
||||
var fds: [Int32] = [-1, -1]
|
||||
let result = socketpair(AF_UNIX, SOCK_DGRAM, 0, &fds)
|
||||
|
||||
if result == -1 {
|
||||
throw RuntimeError.VMConfigurationError("failed to create a datagram socketpair for tests")
|
||||
}
|
||||
|
||||
return (fds[0], fds[1])
|
||||
}
|
||||
|
||||
private func assertVMConfigurationError(
|
||||
_ error: Error,
|
||||
contains expectedSubstring: String,
|
||||
file: StaticString = #filePath,
|
||||
line: UInt = #line
|
||||
) {
|
||||
guard case RuntimeError.VMConfigurationError(let message) = error else {
|
||||
XCTFail("Expected RuntimeError.VMConfigurationError, got \(error)", file: file, line: line)
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertTrue(
|
||||
message.contains(expectedSubstring),
|
||||
"Expected message to contain \"\(expectedSubstring)\", got \"\(message)\"",
|
||||
file: file,
|
||||
line: line
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import XCTest
|
||||
@testable import tart
|
||||
|
||||
final class RunNetworkValidationTests: XCTestCase {
|
||||
func testNetFdRejectsNegativeValue() throws {
|
||||
XCTAssertThrowsError(try Run.parseAsRoot(["unused", "--net-fd=-1"])) { error in
|
||||
self.assertError(error, contains: "--net-fd must be greater than or equal to 0")
|
||||
}
|
||||
}
|
||||
|
||||
func testNetFdConflictsWithNetBridged() throws {
|
||||
XCTAssertThrowsError(try Run.parseAsRoot(["unused", "--net-fd", "3", "--net-bridged=en0"])) { error in
|
||||
self.assertError(error, contains: "--net-bridged, --net-softnet, --net-host and --net-fd are mutually exclusive")
|
||||
}
|
||||
}
|
||||
|
||||
func testNetFdConflictsWithNetSoftnet() throws {
|
||||
XCTAssertThrowsError(try Run.parseAsRoot(["unused", "--net-fd", "3", "--net-softnet"])) { error in
|
||||
self.assertError(error, contains: "--net-bridged, --net-softnet, --net-host and --net-fd are mutually exclusive")
|
||||
}
|
||||
}
|
||||
|
||||
func testNetFdConflictsWithNetHost() throws {
|
||||
XCTAssertThrowsError(try Run.parseAsRoot(["unused", "--net-fd", "3", "--net-host"])) { error in
|
||||
self.assertError(error, contains: "--net-bridged, --net-softnet, --net-host and --net-fd are mutually exclusive")
|
||||
}
|
||||
}
|
||||
|
||||
private func assertError(
|
||||
_ error: Error,
|
||||
contains expectedSubstring: String,
|
||||
file: StaticString = #filePath,
|
||||
line: UInt = #line
|
||||
) {
|
||||
XCTAssertTrue(
|
||||
String(describing: error).contains(expectedSubstring),
|
||||
"Expected error to contain \"\(expectedSubstring)\", got \"\(error)\"",
|
||||
file: file,
|
||||
line: line
|
||||
)
|
||||
}
|
||||
}
|
||||
+14
@@ -77,6 +77,20 @@ Note: that accessing host is only possible with the default NAT network. If you
|
||||
[Softnet](https://github.com/cirruslabs/softnet) (via `tart run --net-softnet <VM NAME>)`, then the network isolation
|
||||
is stricter and it's not possible to access the host.
|
||||
|
||||
## Using externally managed networking (`--net-fd`)
|
||||
|
||||
For advanced integrations, `tart run` can consume a pre-opened connected datagram socket via `--net-fd`.
|
||||
|
||||
Unlike `--net-softnet`, Tart will not launch Softnet or configure Softnet permissions in this mode.
|
||||
|
||||
External launcher is responsible for:
|
||||
|
||||
* creating a connected datagram socketpair (for example, `socketpair(AF_UNIX, SOCK_DGRAM, ...)`)
|
||||
* starting `softnet --vm-fd ...` (or another networking helper) with one end of that socketpair
|
||||
* starting `tart run --net-fd <FD> <VM NAME>` with the other end inherited into Tart
|
||||
|
||||
If the file descriptor is invalid, not a datagram socket, or not connected, `tart run` fails fast.
|
||||
|
||||
## Changing the default NAT subnet
|
||||
|
||||
To change the default network to `192.168.77.1`:
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
source = [ "dist/tart_darwin_all/tart.app/Contents/MacOS/tart" ]
|
||||
bundle_id = "com.github.cirruslabs.tart"
|
||||
|
||||
apple_id {
|
||||
username = "hello@cirruslabs.org"
|
||||
password = "@env:AC_PASSWORD"
|
||||
}
|
||||
|
||||
sign {
|
||||
application_identity = "Developer ID Application: Cirrus Labs, Inc."
|
||||
entitlements_file = "Resources/tart-prod.entitlements"
|
||||
}
|
||||
Reference in New Issue
Block a user