Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

scripting: add export command #6716

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
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
9 changes: 9 additions & 0 deletions Scripting/UTM.sdef
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,15 @@
</parameter>
</command>

<command name="export" code="coreexpo" description="Export a virtual machine to a specified location.">
<cocoa class="UTMScriptingExportCommand"/>
<access-group identifier="*"/>
<direct-parameter type="virtual machine" requires-access="r" description="The virtual machine to export."/>
<parameter name="to" code="efil" type="file" description="Location to export the VM to.">
<cocoa key="file"/>
</parameter>
</command>

<class name="virtual machine" code="UTMv" description="A virtual machine registered in UTM." plural="virtual machines">
<cocoa class="UTMScriptingVirtualMachineImpl"/>

Expand Down
1 change: 1 addition & 0 deletions Scripting/UTMScripting.swift
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ extension SBObject: UTMScriptingWindow {}
@objc optional func stopBy(_ by: UTMScriptingStopMethod) // Shuts down a running virtual machine.
@objc optional func delete() // Delete a virtual machine. All data will be deleted, there is no confirmation!
@objc optional func duplicateWithProperties(_ withProperties: [AnyHashable : Any]!) // Copy an virtual machine and all its data.
@objc optional func exportTo(_ to: URL!) // Export a virtual machine to a specified location.
@objc optional func openFileAt(_ at: String!, for for_: UTMScriptingOpenMode, updating: Bool) -> UTMScriptingGuestFile // Open a file on the guest. You must close the file when you are done to prevent leaking guest resources.
@objc optional func executeAt(_ at: String!, withArguments: [String]!, withEnvironment: [String]!, usingInput: String!, base64Encoding: Bool, outputCapturing: Bool) -> UTMScriptingGuestProcess // Execute a command or script on the guest.
@objc optional func queryIp() -> [Any] // Query the guest for all IP addresses on its network interfaces (excluding loopback).
Expand Down
31 changes: 31 additions & 0 deletions Scripting/UTMScriptingExportCommand.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// Copyright © 2024 naveenrajm7. All rights reserved.
//
// 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
//
// http://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 Foundation

@MainActor

@objc(UTMScriptingExportCommand)
class UTMScriptingExportCommand: NSCloneCommand, UTMScriptable {
override func performDefaultImplementation() -> Any? {
if let scriptingVM = keySpecifier.objectsByEvaluatingSpecifier as? UTMScriptingVirtualMachineImpl {
scriptingVM.export(self)
return nil
} else {
return super.performDefaultImplementation()
}
}
}
10 changes: 10 additions & 0 deletions Scripting/UTMScriptingVirtualMachineImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,16 @@ class UTMScriptingVirtualMachineImpl: NSObject, UTMScriptable {
}
}
}

@objc func export(_ command: NSCloneCommand) {
let exportUrl = command.evaluatedArguments?["file"] as? URL
withScriptCommand(command) { [self] in
guard vm.state == .stopped else {
throw ScriptingError.notStopped
}
try await data.export(vm: box, to: exportUrl!)
}
}
}

// MARK: - Guest agent suite
Expand Down
4 changes: 4 additions & 0 deletions UTM.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@
85EC516627CC8D10004A51DE /* VMConfigAdvancedNetworkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85EC516327CC8C98004A51DE /* VMConfigAdvancedNetworkView.swift */; };
B329049C270FE136002707AC /* AltKit in Frameworks */ = {isa = PBXBuildFile; productRef = B329049B270FE136002707AC /* AltKit */; };
B3DDF57226E9BBA300CE47F0 /* AltKit in Frameworks */ = {isa = PBXBuildFile; productRef = B3DDF57126E9BBA300CE47F0 /* AltKit */; };
CD77BE422CAB51B40074ADD2 /* UTMScriptingExportCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD77BE412CAB519F0074ADD2 /* UTMScriptingExportCommand.swift */; };
CE020BA324AEDC7C00B44AB6 /* UTMData.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE020BA224AEDC7C00B44AB6 /* UTMData.swift */; };
CE020BA424AEDC7C00B44AB6 /* UTMData.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE020BA224AEDC7C00B44AB6 /* UTMData.swift */; };
CE020BA724AEDEF000B44AB6 /* Logging in Frameworks */ = {isa = PBXBuildFile; productRef = CE020BA624AEDEF000B44AB6 /* Logging */; };
Expand Down Expand Up @@ -1762,6 +1763,7 @@
C03453AF2709E35100AD51AD /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
C03453B02709E35200AD51AD /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = "<group>"; };
C8958B6D243634DA002D86B4 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = "<group>"; };
CD77BE412CAB519F0074ADD2 /* UTMScriptingExportCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMScriptingExportCommand.swift; sourceTree = "<group>"; };
CE020BA224AEDC7C00B44AB6 /* UTMData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMData.swift; sourceTree = "<group>"; };
CE020BAA24AEE00000B44AB6 /* UTMLoggingSwift.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMLoggingSwift.swift; sourceTree = "<group>"; };
CE020BB524B14F8400B44AB6 /* UTMVirtualMachine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMVirtualMachine.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3006,6 +3008,7 @@
CE25125429C80CD4000790AB /* UTMScriptingCreateCommand.swift */,
CE25125029C806AF000790AB /* UTMScriptingDeleteCommand.swift */,
CE25125229C80A18000790AB /* UTMScriptingCloneCommand.swift */,
CD77BE412CAB519F0074ADD2 /* UTMScriptingExportCommand.swift */,
);
path = Scripting;
sourceTree = "<group>";
Expand Down Expand Up @@ -3811,6 +3814,7 @@
CEF01DB52B6724A300725A0F /* UTMSpiceVirtualMachine.swift in Sources */,
8432329A28C3084A00CFBC97 /* GlobalFileImporter.swift in Sources */,
CE19392826DCB094005CEC17 /* RAMSlider.swift in Sources */,
CD77BE422CAB51B40074ADD2 /* UTMScriptingExportCommand.swift in Sources */,
2C33B3AA2566C9B100A954A6 /* VMContextMenuModifier.swift in Sources */,
84BB993A2899E8D500DF28B2 /* VMHeadlessSessionState.swift in Sources */,
CE2D955A24AD4F980059923A /* VMToolbarModifier.swift in Sources */,
Expand Down
22 changes: 22 additions & 0 deletions utmctl/UTMCtl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ struct UTMCtl: ParsableCommand {
IPAddress.self,
Clone.self,
Delete.self,
Export.self,
USB.self
]
)
Expand Down Expand Up @@ -522,6 +523,27 @@ extension UTMCtl {
}
}

extension UTMCtl {
struct Export: UTMAPICommand {
static var configuration = CommandConfiguration(
abstract: "Export a virtual machine and all its data to a specified location."
)

@OptionGroup var environment: EnvironmentOptions

@OptionGroup var identifer: VMIdentifier

@Option var path: String

func run(with application: UTMScriptingApplication) throws {
let vm = try virtualMachine(forIdentifier: identifer, in: application)
// TODO: Make sure the URL is writable as required by data.export
let exportUrl = URL(fileURLWithPath: path)
vm.exportTo!(exportUrl)
}
}
}

extension UTMCtl {
struct USB: ParsableCommand {
static var configuration = CommandConfiguration(
Expand Down
2 changes: 1 addition & 1 deletion utmctl/utmctl-unsigned.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<false/>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.scripting-targets</key>
Expand Down
Loading