From 2dbc418f7df9eac8d7cd8650f4ddfe78ed67ebe8 Mon Sep 17 00:00:00 2001 From: Greg Neagle Date: Mon, 14 Apr 2025 11:06:01 -0700 Subject: [PATCH] manifestutil: implement more subcommands --- .../manifestutil/MUdisplayManifest.swift | 2 +- .../munki/manifestutil/MUlistManifests.swift | 8 +- .../MUmanifestFileOperations.swift | 192 ++++++++++++++++++ .../cli/munki/manifestutil/manifestutil.swift | 8 +- 4 files changed, 202 insertions(+), 8 deletions(-) create mode 100644 code/cli/munki/manifestutil/MUmanifestFileOperations.swift diff --git a/code/cli/munki/manifestutil/MUdisplayManifest.swift b/code/cli/munki/manifestutil/MUdisplayManifest.swift index 0979c309..b1abf97f 100644 --- a/code/cli/munki/manifestutil/MUdisplayManifest.swift +++ b/code/cli/munki/manifestutil/MUdisplayManifest.swift @@ -124,7 +124,7 @@ extension ManifestUtil { } } -/// Prints contents of a given manifest, expanding inlcuded maniifests +/// Prints contents of a given manifest, expanding included maniifests extension ManifestUtil { struct ExpandIncludedManifests: ParsableCommand { static var configuration = CommandConfiguration( diff --git a/code/cli/munki/manifestutil/MUlistManifests.swift b/code/cli/munki/manifestutil/MUlistManifests.swift index 5709baca..d700f8c4 100644 --- a/code/cli/munki/manifestutil/MUlistManifests.swift +++ b/code/cli/munki/manifestutil/MUlistManifests.swift @@ -21,13 +21,13 @@ import ArgumentParser import Foundation -func getManifestNames(repo: Repo) throws -> [String] { +func getManifestNames(repo: Repo) -> [String]? { do { let manifestNames = try repo.list("manifests") return manifestNames.sorted() } catch { printStderr("Could not retrieve manifests: \(error.localizedDescription)") - throw ExitCode(-1) + return nil } } @@ -44,7 +44,9 @@ extension ManifestUtil { func run() throws { let repo = try connectToRepo() - let manifestNames = try getManifestNames(repo: repo) + guard let manifestNames = getManifestNames(repo: repo) else { + return + } if globString.isEmpty { print(manifestNames.joined(separator: "\n")) } else { diff --git a/code/cli/munki/manifestutil/MUmanifestFileOperations.swift b/code/cli/munki/manifestutil/MUmanifestFileOperations.swift new file mode 100644 index 00000000..f50a7693 --- /dev/null +++ b/code/cli/munki/manifestutil/MUmanifestFileOperations.swift @@ -0,0 +1,192 @@ +// +// MUmanifestFileOperations.swift +// manifestutil +// +// Created by Greg Neagle on 4/14/25. +// +// Copyright 2024-2025 Greg Neagle. +// +// 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 ArgumentParser +import Foundation + +/// Saves a manifest +func saveManifest(repo: Repo, + manifest: PlistDict, + name: String, + overwrite: Bool = false) -> Bool +{ + if !overwrite { + let existingManifestNames = getManifestNames(repo: repo) ?? [] + if existingManifestNames.contains(name) { + printStderr("Manifest '\(name)' already exists") + return false + } + } + do { + let data = try plistToData(manifest) + try repo.put("manifests/\(name)", content: data) + return true + } catch { + printStderr("Saving \(name) failed: \(error.localizedDescription)") + return false + } +} + +/// Copies or renames a manifest. +/// (To rename we make a copy under the new name, then delete the original) +func copyOrRenameManifest(repo: Repo, sourceName: String, destinationName: String, overwrite: Bool = false, deleteSource: Bool = false) -> Bool { + if !overwrite { + let existingManifestNames = getManifestNames(repo: repo) ?? [] + if existingManifestNames.contains(destinationName) { + printStderr("Manifest '\(destinationName)' already exists") + return false + } + } + do { + let data = try repo.get("manifests/\(sourceName)") + try repo.put("manifests/\(destinationName)", content: data) + if deleteSource { + try repo.delete("manifests/\(sourceName)") + } + return true + } catch { + printStderr("Renaming \(sourceName) to \(destinationName) failed: \(error.localizedDescription)") + return false + } +} + +/// Creates a new, empty manifest +func newManifest(repo: Repo, name: String) -> Bool { + let manifest = [ + "catalogs": [String](), + "included_manifests": [String](), + "managed_installs": [String](), + "managed_uninstalls": [String](), + ] + return saveManifest(repo: repo, manifest: manifest, name: name) +} + +/// Deletes a manifest +func deleteManifest(repo: Repo, name: String) -> Bool { + let existingManifestNames = getManifestNames(repo: repo) ?? [] + if !existingManifestNames.contains(name) { + printStderr("No such manifest: \(name)") + return false + } + do { + try repo.delete("manifests/\(name)") + return true + } catch { + printStderr("Deleting \(name) failed: \(error.localizedDescription)") + return false + } +} + +/// Create a new empty manifest +extension ManifestUtil { + struct NewManifest: ParsableCommand { + static var configuration = CommandConfiguration( + abstract: "Creates a new empty manifest") + + @Argument(help: ArgumentHelp( + "Name for the newly-created manifest", + valueName: "manifest-name" + )) + var manifestName: String + + func run() throws { + let repo = try connectToRepo() + _ = newManifest(repo: repo, name: manifestName) + } + } +} + +/// Copy a manifest +extension ManifestUtil { + struct CopyManifest: ParsableCommand { + static var configuration = CommandConfiguration( + abstract: "Copies a manifest") + + @Argument(help: ArgumentHelp( + "Name of the source manifest", + valueName: "source-name" + )) + var sourceName: String + + @Argument(help: ArgumentHelp( + "Name of the destination manifest", + valueName: "destination-name" + )) + var destinationName: String + + func run() throws { + let repo = try connectToRepo() + _ = copyOrRenameManifest( + repo: repo, + sourceName: sourceName, + destinationName: destinationName + ) + } + } +} + +/// Rename a manifest +extension ManifestUtil { + struct RenameManifest: ParsableCommand { + static var configuration = CommandConfiguration( + abstract: "Renames a manifest") + + @Argument(help: ArgumentHelp( + "Name of the source manifest", + valueName: "source-name" + )) + var sourceName: String + + @Argument(help: ArgumentHelp( + "Name of the destination manifest", + valueName: "destination-name" + )) + var destinationName: String + + func run() throws { + let repo = try connectToRepo() + _ = copyOrRenameManifest( + repo: repo, + sourceName: sourceName, + destinationName: destinationName, + deleteSource: true + ) + } + } +} + +/// Delete a manifest +extension ManifestUtil { + struct DeleteManifest: ParsableCommand { + static var configuration = CommandConfiguration( + abstract: "Deletes a manifest") + + @Argument(help: ArgumentHelp( + "Name of the manifest to be deleted", + valueName: "manifest-name" + )) + var manifestName: String + + func run() throws { + let repo = try connectToRepo() + _ = deleteManifest(repo: repo, name: manifestName) + } + } +} diff --git a/code/cli/munki/manifestutil/manifestutil.swift b/code/cli/munki/manifestutil/manifestutil.swift index 418881e6..9949b921 100644 --- a/code/cli/munki/manifestutil/manifestutil.swift +++ b/code/cli/munki/manifestutil/manifestutil.swift @@ -60,10 +60,10 @@ struct ManifestUtil: AsyncParsableCommand { DisplayManifest.self, ExpandIncludedManifests.self, // Find.self, - // NewManifest.self, - // CopyManifest.self, - // RenameManifest.self, - // DeleteManifest.self, + NewManifest.self, + CopyManifest.self, + RenameManifest.self, + DeleteManifest.self, // RefreshCache.self, Exit.self, Help.self,