Skip to content

Commit

Permalink
Merge pull request #6716 from naveenrajm7/utmctl-export
Browse files Browse the repository at this point in the history
scripting: add export command
  • Loading branch information
osy authored Nov 20, 2024
2 parents b4f6030 + 0506562 commit 28a14b9
Show file tree
Hide file tree
Showing 7 changed files with 78 additions and 1 deletion.
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 @@ -1775,6 +1776,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 @@ -3027,6 +3029,7 @@
CE25125429C80CD4000790AB /* UTMScriptingCreateCommand.swift */,
CE25125029C806AF000790AB /* UTMScriptingDeleteCommand.swift */,
CE25125229C80A18000790AB /* UTMScriptingCloneCommand.swift */,
CD77BE412CAB519F0074ADD2 /* UTMScriptingExportCommand.swift */,
);
path = Scripting;
sourceTree = "<group>";
Expand Down Expand Up @@ -3832,6 +3835,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

0 comments on commit 28a14b9

Please sign in to comment.