mirror of
https://github.com/munki/munki.git
synced 2026-05-07 12:59:32 -05:00
Implement more of manifestutil
This commit is contained in:
@@ -1,46 +0,0 @@
|
||||
//
|
||||
// ListCatalogs.swift
|
||||
// manifestutil
|
||||
//
|
||||
// Created by Greg Neagle on 4/13/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
|
||||
|
||||
/// Returns a list of available catalogs
|
||||
func getCatalogNames(repo: Repo) -> [String] {
|
||||
do {
|
||||
let catalogNames = try repo.list("catalogs")
|
||||
return catalogNames.sorted()
|
||||
} catch let error {
|
||||
printStderr("Could not retrieve catalogs: \(error.localizedDescription)")
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
extension ManifestUtil {
|
||||
struct ListCatalogs: ParsableCommand {
|
||||
static var configuration = CommandConfiguration(
|
||||
abstract: "Lists available catalogs in Munki repo.")
|
||||
|
||||
func run() throws {
|
||||
let repo = try connectToRepo()
|
||||
let catalogNames = getCatalogNames(repo: repo)
|
||||
print(catalogNames.joined(separator: "\n"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
//
|
||||
// MUcatalogs.swift
|
||||
// manifestutil
|
||||
//
|
||||
// Created by Greg Neagle on 4/13/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
|
||||
|
||||
/// Returns a list of available catalogs
|
||||
func getCatalogNames(repo: Repo) throws -> [String] {
|
||||
do {
|
||||
let catalogNames = try repo.list("catalogs")
|
||||
return catalogNames.sorted()
|
||||
} catch let error {
|
||||
printStderr("Could not retrieve catalogs: \(error.localizedDescription)")
|
||||
throw ExitCode(-1)
|
||||
}
|
||||
}
|
||||
|
||||
/// Prints the names of the available catalogs
|
||||
extension ManifestUtil {
|
||||
struct ListCatalogs: ParsableCommand {
|
||||
static var configuration = CommandConfiguration(
|
||||
abstract: "Lists available catalogs in Munki repo.")
|
||||
|
||||
func run() throws {
|
||||
let repo = try connectToRepo()
|
||||
let catalogNames = try getCatalogNames(repo: repo)
|
||||
print(catalogNames.joined(separator: "\n"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a list of unique installer item (pkg) names from the given list of catalogs
|
||||
func getInstallerItemNames(repo: Repo, catalogs: [String]) throws -> [String] {
|
||||
var itemList = [String]()
|
||||
let catalogNames = try getCatalogNames(repo: repo)
|
||||
for catalogName in catalogNames {
|
||||
if catalogs.contains(catalogName) {
|
||||
do {
|
||||
let data = try repo.get("catalogs/\(catalogName)")
|
||||
if let catalog = try readPlist(fromData: data) as? [PlistDict] {
|
||||
let itemNames = catalog.filter {
|
||||
($0["update_for"] as? String ?? "").isEmpty &&
|
||||
!(($0["name"] as? String ?? "").isEmpty)
|
||||
}.map {
|
||||
$0["name"] as? String ?? ""
|
||||
}
|
||||
itemList.append(contentsOf: itemNames)
|
||||
} else {
|
||||
printStderr("Catalog \(catalogName) is malformed")
|
||||
}
|
||||
} catch let error {
|
||||
printStderr("Could not retrieve catalog: \(catalogName): \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
return Array(Set(itemList)).sorted()
|
||||
}
|
||||
|
||||
/// Lists items in the given catalogs
|
||||
extension ManifestUtil {
|
||||
struct ListCatalogItems: ParsableCommand {
|
||||
static var configuration = CommandConfiguration(
|
||||
abstract: "Lists items in the given catalogs.")
|
||||
|
||||
@Argument(help: ArgumentHelp(
|
||||
"Catalog name",
|
||||
valueName: "catalog-name"
|
||||
))
|
||||
var catalogNames: [String] = []
|
||||
|
||||
func validate() throws {
|
||||
if catalogNames.isEmpty {
|
||||
throw ValidationError("At least one catalog name must be provided.")
|
||||
}
|
||||
}
|
||||
|
||||
func run() throws {
|
||||
let repo = try connectToRepo()
|
||||
let avaliableCatalogs = try getCatalogNames(repo: repo)
|
||||
|
||||
for catalogName in catalogNames {
|
||||
if !avaliableCatalogs.contains(catalogName) {
|
||||
printStderr("Catalog '\(catalogName)' does not exist.")
|
||||
throw ExitCode(-1)
|
||||
}
|
||||
}
|
||||
let installerItemNames = try getInstallerItemNames(repo: repo, catalogs: catalogNames)
|
||||
print(installerItemNames.joined(separator: "\n"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
//
|
||||
// MUdisplayManifest.swift
|
||||
// manifestutil
|
||||
//
|
||||
// Created by Greg Neagle on 4/13/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
|
||||
|
||||
func getManifest(repo: Repo, name: String) -> PlistDict? {
|
||||
do {
|
||||
let data = try repo.get("manifests/\(name)")
|
||||
return try readPlist(fromData: data) as? PlistDict
|
||||
} catch {
|
||||
printStderr("Could not retrieve manifest: \(error.localizedDescription)")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/// Prints a plist item in an 'attractive' way
|
||||
func printPlistItem(_ label: String, _ value: Any?, indent: Int = 0) {
|
||||
let INDENTSPACE = String(repeating: " ", count: indent * 4)
|
||||
if let value {
|
||||
if let array = value as? [Any] {
|
||||
if !label.isEmpty {
|
||||
print("\(INDENTSPACE)\(label):")
|
||||
}
|
||||
for item in array {
|
||||
printPlistItem("", item, indent: indent + 1)
|
||||
}
|
||||
} else if let dict = value as? PlistDict {
|
||||
if !label.isEmpty {
|
||||
print("\(INDENTSPACE)\(label):")
|
||||
}
|
||||
for subkey in dict.keys.sorted() {
|
||||
printPlistItem(subkey, dict[subkey], indent: indent + 1)
|
||||
}
|
||||
} else {
|
||||
if !label.isEmpty {
|
||||
print("\(INDENTSPACE)\(label): \(value)")
|
||||
} else {
|
||||
print("\(INDENTSPACE)\(value)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Prints plist dictionary in a pretty(?) way
|
||||
func printPlist(_ plist: PlistDict) {
|
||||
for key in plist.keys.sorted() {
|
||||
printPlistItem(key, plist[key])
|
||||
}
|
||||
}
|
||||
|
||||
/// Prints contents of a given manifest
|
||||
extension ManifestUtil {
|
||||
struct DisplayManifest: ParsableCommand {
|
||||
static var configuration = CommandConfiguration(
|
||||
abstract: "Displays a manifest.")
|
||||
|
||||
@Flag(name: [.long, .customShort("X")],
|
||||
help: "Display manifest in XML format.")
|
||||
var xml: Bool = false
|
||||
|
||||
@Argument(help: ArgumentHelp(
|
||||
"Prints the contents of the specified manifest",
|
||||
valueName: "manifest-name"
|
||||
))
|
||||
var manifestName: String
|
||||
|
||||
func run() throws {
|
||||
let repo = try connectToRepo()
|
||||
if let manifest = getManifest(repo: repo, name: manifestName) {
|
||||
if xml {
|
||||
print((try? plistToString(manifest)) ?? "")
|
||||
} else {
|
||||
printPlist(manifest)
|
||||
}
|
||||
} else {
|
||||
printStderr("Manifest data was malformed or not found.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
//
|
||||
// MUlistManifests.swift
|
||||
// manifestutil
|
||||
//
|
||||
// Created by Greg Neagle on 4/13/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
|
||||
|
||||
func getManifestNames(repo: Repo) throws -> [String] {
|
||||
do {
|
||||
let manifestNames = try repo.list("manifests")
|
||||
return manifestNames.sorted()
|
||||
} catch let error {
|
||||
printStderr("Could not retrieve manifests: \(error.localizedDescription)")
|
||||
throw ExitCode(-1)
|
||||
}
|
||||
}
|
||||
|
||||
extension ManifestUtil {
|
||||
struct ListManifests: ParsableCommand {
|
||||
static var configuration = CommandConfiguration(
|
||||
abstract: "Lists available manifest in Munki repo.")
|
||||
|
||||
@Argument(help: ArgumentHelp(
|
||||
"String to match manifest names similar to file name globbing. To avoid the shell expanding wildcards, wrap the string in quotes.",
|
||||
valueName: "match-string"
|
||||
))
|
||||
var globString: String = ""
|
||||
|
||||
func run() throws {
|
||||
let repo = try connectToRepo()
|
||||
let manifestNames = try getManifestNames(repo: repo)
|
||||
if globString.isEmpty {
|
||||
print(manifestNames.joined(separator: "\n"))
|
||||
} else {
|
||||
for name in manifestNames {
|
||||
if fnmatch(globString, name, 0) == 0 {
|
||||
print(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// main.swift
|
||||
// manifestutil.swift
|
||||
// manifestutil
|
||||
//
|
||||
// Created by Greg Neagle on 4/13/25.
|
||||
@@ -28,7 +28,7 @@ func connectToRepo() throws -> Repo {
|
||||
}
|
||||
var plugin = adminPref("plugin") as? String ?? "FileRepo"
|
||||
if plugin.isEmpty {
|
||||
plugin = "FileRepo"git
|
||||
plugin = "FileRepo"
|
||||
}
|
||||
// connect to the repo
|
||||
var repo: Repo
|
||||
@@ -55,8 +55,9 @@ struct ManifestUtil: AsyncParsableCommand {
|
||||
//RemoveCatalog.self,
|
||||
//RemoveIncludedManifest.self,
|
||||
ListCatalogs.self,
|
||||
//ListCatalogItems.self,
|
||||
//DisplayManifest.self,
|
||||
ListCatalogItems.self,
|
||||
ListManifests.self,
|
||||
DisplayManifest.self,
|
||||
//ExpandIncludedManifests.self,
|
||||
//Find.self,
|
||||
//NewManifest.self,
|
||||
|
||||
Reference in New Issue
Block a user