From f7cbba03a8ab769ea63fb218fb9494fdf48d6813 Mon Sep 17 00:00:00 2001 From: Greg Neagle Date: Wed, 11 Sep 2024 14:23:40 -0700 Subject: [PATCH] Convert more comments to documentation --- code/cli/munki/shared/admin/admincommon.swift | 20 +++--- .../munki/shared/admin/makecatalogslib.swift | 24 ++++--- .../munki/shared/admin/munkiimportlib.swift | 62 +++++++++---------- .../munki/shared/admin/pkginfoOptions.swift | 26 ++++---- code/cli/munki/shared/admin/pkginfolib.swift | 36 +++++------ 5 files changed, 84 insertions(+), 84 deletions(-) diff --git a/code/cli/munki/shared/admin/admincommon.swift b/code/cli/munki/shared/admin/admincommon.swift index 73846fc2..7e423930 100644 --- a/code/cli/munki/shared/admin/admincommon.swift +++ b/code/cli/munki/shared/admin/admincommon.swift @@ -22,20 +22,20 @@ import Foundation let ADMIN_BUNDLE_ID = "com.googlecode.munki.munkiimport" as CFString +/// Return an admin preference. Since this uses CFPreferencesCopyAppValue, +/// Preferences can be defined several places. Precedence is: +/// - MCX/configuration profile +/// - ~/Library/Preferences/ByHost/com.googlecode.munki.munkiimport.XXXXXX.plist +/// - ~/Library/Preferences/com.googlecode.munki.munkiimport.plist +/// - /Library/Preferences/com.googlecode.munki.munkiimport.plist +/// - .GlobalPreferences defined at various levels (ByHost, user, system) +/// But typically these preferences are _not_ managed and are stored in the +/// user's preferences (~/Library/Preferences/com.googlecode.munki.munkiimport.plist) func adminPref(_ pref_name: String) -> Any? { - /* Return an admin preference. Since this uses CFPreferencesCopyAppValue, - Preferences can be defined several places. Precedence is: - - MCX/configuration profile - - ~/Library/Preferences/ByHost/com.googlecode.munki.munkiimport.XXXXXX.plist - - ~/Library/Preferences/com.googlecode.munki.munkiimport.plist - - /Library/Preferences/com.googlecode.munki.munkiimport.plist - - .GlobalPreferences defined at various levels (ByHost, user, system) - But typically these preferences are _not_ managed and are stored in the - user's preferences (~/Library/Preferences/com.googlecode.munki.munkiimport.plist) - */ return CFPreferencesCopyAppValue(pref_name as CFString, ADMIN_BUNDLE_ID) } +/// Adds `count` spaces to the start of `str` func leftPad(_ str: String, _ count: Int) -> String { if str.count < count { return String(repeating: " ", count: count - str.count) + str diff --git a/code/cli/munki/shared/admin/makecatalogslib.swift b/code/cli/munki/shared/admin/makecatalogslib.swift index 8eed2e16..1687b394 100644 --- a/code/cli/munki/shared/admin/makecatalogslib.swift +++ b/code/cli/munki/shared/admin/makecatalogslib.swift @@ -31,6 +31,7 @@ struct MakeCatalogOptions { var verbose: Bool = false } +/// Struct that handles building catalogs struct CatalogsMaker { var repo: Repo var options: MakeCatalogOptions @@ -52,8 +53,8 @@ struct CatalogsMaker { try getPkgsList() } + /// Returns a list of pkginfo identifiers mutating func getPkgsinfoList() throws { - // returns a list of pkginfo identifiers do { pkgsinfoList = try listItemsOfKind(repo, "pkgsinfo") } catch is MunkiError { @@ -62,8 +63,8 @@ struct CatalogsMaker { } } + /// Returns a list of pkg identifiers mutating func getPkgsList() throws { - // returns a list of pkg identifiers do { pkgsList = try listItemsOfKind(repo, "pkgs") } catch is MunkiError { @@ -72,8 +73,8 @@ struct CatalogsMaker { } } + /// Builds a dictionary containing hashes for all our repo icons mutating func hashIcons() -> [String: String] { - // Builds a dictionary containing hashes for all our repo icons if options.verbose { print("Getting list of icons...") } @@ -100,8 +101,8 @@ struct CatalogsMaker { return iconHashes } + /// Returns a case-insentitive match for installer_item from pkgsList, if any func caseInsensitivePkgsListContains(_ installer_item: String) -> String? { - // returns a case-insentitive match for installer_item from pkgsList, if any for repo_pkg in pkgsList { if installer_item.lowercased() == repo_pkg.lowercased() { return repo_pkg @@ -110,9 +111,8 @@ struct CatalogsMaker { return nil } + /// Returns true if referenced installer items are present, false otherwise. Updates list of errors. mutating func verify(_ identifier: String, _ pkginfo: PlistDict) -> Bool { - // Returns true if referenced installer items are present, - // false otherwise. Updates list of errors. if let installer_type = pkginfo["installer_type"] as? String { if ["nopkg", "apple_update_metadata"].contains(installer_type) { // no associated installer item (pkg) for these types @@ -179,8 +179,8 @@ struct CatalogsMaker { return true } + /// Processes pkginfo files and updates catalogs and errors instance variables mutating func processPkgsinfo() { - // Processes pkginfo files and updates catalogs and errors instance variables catalogs["all"] = [PlistDict]() // Walk through the pkginfo files for pkginfoIdentifier in pkgsinfoList { @@ -251,8 +251,8 @@ struct CatalogsMaker { } } + /// Clear out old catalogs mutating func cleanupCatalogs() { - // clear out old catalogs do { let catalogList = try repo.list("catalogs") for catalogName in catalogList { @@ -270,12 +270,10 @@ struct CatalogsMaker { } } + /// Assembles all pkginfo files into catalogs. + /// User calling this needs to be able to write to the repo/catalogs directory. + /// Returns a list of any errors it encountered mutating func makecatalogs() -> [String] { - // Assembles all pkginfo files into catalogs. - // User calling this needs to be able to write to the repo/catalogs - // directory. - // Returns a list of any errors it encountered - // process pkgsinfo items processPkgsinfo() diff --git a/code/cli/munki/shared/admin/munkiimportlib.swift b/code/cli/munki/shared/admin/munkiimportlib.swift index 8910f20e..06812b7f 100644 --- a/code/cli/munki/shared/admin/munkiimportlib.swift +++ b/code/cli/munki/shared/admin/munkiimportlib.swift @@ -21,9 +21,9 @@ import Darwin.C import Foundation +/// If there is exactly one supported architecture, return a string with it +/// Otherwise return empty string func getSingleArch(_ pkginfo: PlistDict) -> String { - // If there is exactly one supported architecture, return a string with it - // Otherwise return empty string if let archList = pkginfo["supported_architectures"] as? [String], archList.count == 1 { @@ -32,12 +32,11 @@ func getSingleArch(_ pkginfo: PlistDict) -> String { return "" } +/// Copies an item to the appropriate place in the repo. +/// If itempath is a path within the repo/pkgs directory, copies nothing. +/// Renames the item if an item already exists with that name. +/// Returns the identifier for the item in the repo. func copyInstallerItemToRepo(_ repo: Repo, itempath: String, version: String, subdirectory: String = "") throws -> String { - // Copies an item to the appropriate place in the repo. - // If itempath is a path within the repo/pkgs directory, copies nothing. - // Renames the item if an item already exists with that name. - // Returns the identifier for the item in the repo. - let destPath = ("pkgs" as NSString).appendingPathComponent(subdirectory) var itemName = (itempath as NSString).lastPathComponent var destIdentifier = (destPath as NSString).appendingPathComponent(itemName) @@ -86,9 +85,9 @@ func copyInstallerItemToRepo(_ repo: Repo, itempath: String, version: String, su } } +/// Saves pkginfo to /pkgsinfo/subdirectory +/// Can throw PlistError.writeError, RepoError, or RepoCopyError func copyPkgInfoToRepo(_ repo: Repo, pkginfo: PlistDict, subdirectory: String = "") throws -> String { - // Saves pkginfo to /pkgsinfo/subdirectory - // Can throw PlistError.writeError, RepoError, or RepoCopyError let pkginfoData = try plistToData(pkginfo) let destinationPath = ("pkgsinfo" as NSString).appendingPathComponent(subdirectory) var pkginfoExt = adminPref("pkginfo_extension") as? String ?? "" @@ -143,8 +142,8 @@ struct CatalogDatabase { var items: [PlistDict] } +/// Builds a dictionary we use like a database to look up info func makeCatalogDB(_ repo: Repo) throws -> CatalogDatabase { - // Builds a dictionary we use like a database to look up info let allCatalog: Data let catalogItems: [PlistDict] do { @@ -261,9 +260,9 @@ func makeCatalogDB(_ repo: Repo) throws -> CatalogDatabase { return catalogDB } +/// Looks through repo catalogs looking for matching pkginfo +/// Returns a pkginfo dictionary, or nil func findMatchingPkginfo(_ repo: Repo, _ pkginfo: PlistDict) -> PlistDict? { - // Looks through repo catalogs looking for matching pkginfo - // Returns a pkginfo dictionary, or nil var catalogDB: CatalogDatabase do { @@ -348,8 +347,8 @@ func findMatchingPkginfo(_ repo: Repo, _ pkginfo: PlistDict) -> PlistDict? { return nil } +/// Return repo identifier for icon func getIconIdentifier(_ pkginfo: PlistDict) -> String { - // Return repo identifier for icon var iconName = pkginfo["icon_name"] as? String ?? pkginfo["name"] as? String ?? "" if (iconName as NSString).pathExtension.isEmpty { iconName += ".png" @@ -357,8 +356,8 @@ func getIconIdentifier(_ pkginfo: PlistDict) -> String { return ("icons" as NSString).appendingPathComponent(iconName) } +/// Returns true if there is an icon for this item in the repo func iconIsInRepo(_ repo: Repo, pkginfo: PlistDict) -> Bool { - // Returns true if there is an icon for this item in the repo let iconIdentifer = getIconIdentifier(pkginfo) do { let iconList = try listItemsOfKind(repo, "icons") @@ -372,9 +371,9 @@ func iconIsInRepo(_ repo: Repo, pkginfo: PlistDict) -> Bool { } } +/// Convert icon file to png and save to repo icon path. +/// Returns resource path to icon in repo func convertAndInstallIcon(_ repo: Repo, name: String, iconPath: String) throws -> String { - // Convert icon file to png and save to repo icon path. - // Returns resource path to icon in repo guard let tmpDir = TempDir.shared.makeTempDir() else { throw MunkiError("Could not create a temp directory") } @@ -397,9 +396,9 @@ func convertAndInstallIcon(_ repo: Repo, name: String, iconPath: String) throws throw MunkiError("Could not create icon \(pngName) in repo: failed to convert icon to png") } +/// Generates a product icon from a startosinstall item +/// and uploads to the repo. Returns repo identifier for icon func generatePNGFromStartOSInstallItem(_ repo: Repo, installerDMG: String, itemname: String) throws -> String { - // Generates a product icon from a startosinstall item - // and uploads to the repo. Returns repo identifier for icon do { let mountpoint = try mountdmg(installerDMG) defer { @@ -421,9 +420,9 @@ func generatePNGFromStartOSInstallItem(_ repo: Repo, installerDMG: String, itemn } } +/// Generates a product icon from a copy_from_dmg item +/// and uploads to the repo. Returns repo path to icon func generatePNGFromDMGitem(_ repo: Repo, dmgPath: String, pkginfo: PlistDict) throws -> String { - // Generates a product icon from a copy_from_dmg item - // and uploads to the repo. Returns repo path to icon guard let itemname = pkginfo["name"] as? String else { throw MunkiError("pkginfo is missing 'name'") } @@ -457,10 +456,10 @@ func generatePNGFromDMGitem(_ repo: Repo, dmgPath: String, pkginfo: PlistDict) t } } +/// Generates a product icon (or candidate icons) from an installer pkg +/// and uploads to the repo. Returns repo path(s) to icon(s) +/// itemPath can be a path to a disk image or to a package func generatePNGsFromPkg(_ repo: Repo, itemPath: String, pkginfo: PlistDict, importMultiple: Bool = true) throws -> [String] { - // Generates a product icon (or candidate icons) from an installer pkg - // and uploads to the repo. Returns repo path(s) to icon(s) - // itemPath can be a path to a disk image or to a package guard let itemname = pkginfo["name"] as? String else { // this should essentially never happen throw MunkiError("Pkginfo is missing 'name': \(pkginfo)") @@ -522,8 +521,8 @@ func generatePNGsFromPkg(_ repo: Repo, itemPath: String, pkginfo: PlistDict, imp return importedPaths } +/// Saves a product icon to the repo. Returns repo path. func copyIconToRepo(_ repo: Repo, iconPath: String) throws -> String { - // Saves a product icon to the repo. Returns repo path. let destPath = "icons" let iconName = (iconPath as NSString).lastPathComponent let repoIdentifier = (destPath as NSString).appendingPathComponent(iconName) @@ -555,9 +554,9 @@ func copyIconToRepo(_ repo: Repo, iconPath: String) throws -> String { } } +/// Extracts an icon (or icons) from an installer item, converts to png, and +/// copies to repo. Returns repo path to imported icon(s) func extractAndCopyIcon(_ repo: Repo, installerItem: String, pkginfo: PlistDict, importMultiple: Bool = true) throws -> [String] { - // Extracts an icon (or icons) from an installer item, converts to png, and - // copies to repo. Returns repo path to imported icon(s) let installerType = pkginfo["installer_type"] as? String ?? "" switch installerType { case "copy_from_dmg", "stage_os_installer": @@ -580,6 +579,7 @@ func extractAndCopyIcon(_ repo: Repo, installerItem: String, pkginfo: PlistDict, return [String]() } +/// A subclass of AsyncProcessRunner to create disk images class HdiUtilCreateFromFolderRunner: AsyncProcessRunner { init(sourceDir: String, outputPath: String) { let tool = "/usr/bin/hdiutil" @@ -600,10 +600,10 @@ class HdiUtilCreateFromFolderRunner: AsyncProcessRunner { } } +/// Wraps dirPath (generally an app bundle or bundle-style pkg into a disk image. +/// Returns path to the created dmg file +/// async because it can take a while, depending on the size of the item func makeDmg(_ dirPath: String) async -> String { - // Wraps dirPath (generally an app bundle or bundle-style pkg - // into a disk image. Returns path to the created dmg file - // async because it can take a while, depending on the size of the item let itemname = (dirPath as NSString).lastPathComponent print("Making disk image containing \(itemname)...") let dmgName = (itemname as NSString).deletingPathExtension + ".dmg" @@ -622,8 +622,8 @@ func makeDmg(_ dirPath: String) async -> String { return dmgPath } +/// Prompts the user for a subdirectory for the pkg and pkginfo func promptForSubdirectory(_ repo: Repo, _ subdirectory: String?) -> String { - // Prompts the user for a subdirectory for the pkg and pkginfo var existingSubdirs: Set = [] let pkgsinfoList = (try? repo.list("pkgsinfo")) ?? [String]() for item in pkgsinfoList { @@ -646,8 +646,8 @@ func promptForSubdirectory(_ repo: Repo, _ subdirectory: String?) -> String { } } +/// Opens pkginfo list in the user's chosen editor. func editPkgInfoInExternalEditor(_ pkginfo: PlistDict) -> PlistDict { - // Opens pkginfo list in the user's chosen editor. guard let editor = adminPref("editor") as? String else { return pkginfo } diff --git a/code/cli/munki/shared/admin/pkginfoOptions.swift b/code/cli/munki/shared/admin/pkginfoOptions.swift index 29403c2a..8431abc6 100644 --- a/code/cli/munki/shared/admin/pkginfoOptions.swift +++ b/code/cli/munki/shared/admin/pkginfoOptions.swift @@ -21,13 +21,12 @@ import ArgumentParser import Foundation -// Defines option groups for makepkginfo -// These are also used by munkiimport +/// Defines option groups for makepkginfo +/// These are also used by munkiimport +/// Collect all our OptionGroups into a single struct +/// I don't love this because if options move into different groups we have to change other stuff struct PkginfoOptions { - // collect all our OptionGroups into a single struct - // I don't love this because if options move into different groups - // we have to change other stuff var override: OverrideOptions var script: ScriptOptions var dmg: DragNDropOptions @@ -39,25 +38,28 @@ struct PkginfoOptions { var hidden: HiddenPkginfoOptions } +/// Supported restart actions enum RestartAction: String, CaseIterable, ExpressibleByArgument { case RequireRestart case RecommendRestart case RequireLogout } +/// Supported installer types for --installer-type argument enum InstallerType: String, CaseIterable, ExpressibleByArgument { case copy_from_dmg case startosinstall case stage_os_installer } +/// Supported values for architecture enum SupportedArchitecture: String, CaseIterable, ExpressibleByArgument { case x86_64 case arm64 } +/// Pkginfo Override Options struct OverrideOptions: ParsableArguments { - // Pkginfo Override Options @Option(help: "Name to be used to refer to the installer item.") var name: String? = nil @@ -92,8 +94,8 @@ struct OverrideOptions: ParsableArguments { } } +/// Options related to scripts struct ScriptOptions: ParsableArguments { - // Script options @Option(name: [.long, .customLong("installcheck_script")], help: ArgumentHelp("Path to an optional script to be run to determine if item should be installed. An exit code of 0 indicates installation should occur. Takes precedence over installs items and receipts.", valueName: "path")) var installcheckScript: String? = nil @@ -123,8 +125,8 @@ struct ScriptOptions: ParsableArguments { var uninstallScript: String? = nil } +/// "Drag-n-drop" Disk Image Options struct DragNDropOptions: ParsableArguments { - // "Drag-n-drop" Disk Image Options @Option(name: [.short, .long, .customShort("a"), .customLong("app")], help: "Name or relative path of the item to be installed. Useful if there is more than one item at the root of the dmg or the item is located in a subdirectory. Absolute paths can be provided as well but they must point to an item located within the dmg.") var item: String? = nil @@ -158,8 +160,8 @@ struct DragNDropOptions: ParsableArguments { } } +/// Apple package specific options struct ApplePackageOptions: ParsableArguments { - // Apple package specific options @Option(name: .shortAndLong, help: "If the installer item is a disk image containing multiple packages, or the package to be installed is not at the root of the mounted disk image, is a relative path from the root of the mounted disk image to the specific package to be installed.") var pkgname: String? = nil @@ -202,8 +204,8 @@ struct ApplePackageOptions: ParsableArguments { } } +/// Forced/Unattended (install) options struct UnattendedInstallOptions: ParsableArguments { - // Forced/Unattended (install) options @Flag(name: [.long, .customLong("unattended_install")], help: "Item can be installed without notifying the user.") var unattendedInstall = false @@ -227,15 +229,15 @@ struct UnattendedInstallOptions: ParsableArguments { } } +/// Options for generating `installs` items struct GeneratingInstallsOptions: ParsableArguments { - // 'installs' generation options @Option(name: .shortAndLong, help: "Path to a filesystem item installed by this installer item, typically an application. This generates an 'installs' item for the pkginfo, to be used to determine if this software has been installed. Can be specified multiple times.") var file = [String]() } +/// installer type options struct InstallerTypeOptions: ParsableArguments { - // installer type options @Option(name: [.long, .customLong("installer_type")], help: "Specify an intended installer_type when the installer item could be one of multiple types. Currently supported only to specify the intended type when importing a macOS installer.") var installerType: InstallerType? = nil diff --git a/code/cli/munki/shared/admin/pkginfolib.swift b/code/cli/munki/shared/admin/pkginfolib.swift index ee39ea26..73f55c8e 100644 --- a/code/cli/munki/shared/admin/pkginfolib.swift +++ b/code/cli/munki/shared/admin/pkginfolib.swift @@ -26,9 +26,9 @@ import Foundation +/// Helps us record information about the environment in which the pkginfo was +/// created so we have a bit of an audit trail. Returns a dictionary. func pkginfoMetadata() -> PlistDict { - // Helps us record information about the environment in which the pkginfo was - // created so we have a bit of an audit trail. Returns a dictionary. var metadata = PlistDict() metadata["created_by"] = NSUserName() metadata["creation_date"] = Date() @@ -37,11 +37,11 @@ func pkginfoMetadata() -> PlistDict { return metadata } +/// Gets package metadata for the package at pkgpath. +/// Returns pkginfo func createPkgInfoFromPkg(_ pkgpath: String, options: PkginfoOptions) throws -> PlistDict { - // Gets package metadata for the package at pkgpath. - // Returns pkginfo var info = PlistDict() if hasValidPackageExt(pkgpath) { @@ -63,10 +63,10 @@ func createPkgInfoFromPkg(_ pkgpath: String, return info } +/// Creates an item for a pkginfo "installs" array +/// Determines if the item is an application, bundle, Info.plist, or a file or +/// directory and gets additional metadata for later comparison. func createInstallsItem(_ itempath: String) -> PlistDict { - // Creates an item for a pkginfo "installs" array - // Determines if the item is an application, bundle, Info.plist, or a file or - // directory and gets additional metadata for later comparison. var info = PlistDict() if isApplication(itempath) { info["type"] = "application" @@ -136,8 +136,8 @@ func createInstallsItem(_ itempath: String) -> PlistDict { return info } +/// Processes a drag-n-drop dmg to build pkginfo func createPkgInfoForDragNDrop(_ mountpoint: String, options: PkginfoOptions) throws -> PlistDict { - // processes a drag-n-drop dmg to build pkginfo var info = PlistDict() var dragNDropItem = "" var installsitem = PlistDict() @@ -248,13 +248,13 @@ func createPkgInfoForDragNDrop(_ mountpoint: String, options: PkginfoOptions) th return info } +/// Mounts a disk image if it"s not already mounted +/// Builds pkginfo for the first installer item found at the root level, +/// or a specific one if specified by options.pkgname or options.item +/// Unmounts the disk image if it wasn"t already mounted func createPkgInfoFromDmg(_ dmgpath: String, options: PkginfoOptions) throws -> PlistDict { - // Mounts a disk image if it"s not already mounted - // Builds pkginfo for the first installer item found at the root level, - // or a specific one if specified by options.pkgname or options.item - // Unmounts the disk image if it wasn"t already mounted var info = PlistDict() let wasAlreadyMounted = diskImageIsMounted(dmgpath) var mountpoint = "" @@ -306,19 +306,19 @@ func createPkgInfoFromDmg(_ dmgpath: String, return info } +/// Attempt to read a file with the same name as the input string and return its text, +/// otherwise return the input string func readFileOrString(_ fileNameOrString: String) -> String { - // attempt to read a file with the same name as the input string and return its text, - // otherwise return the input string - if let fileText = try? String(contentsOfFile: fileNameOrString, encoding: .utf8) { - return fileText + if !pathExists(fileNameOrString) { + return fileNameOrString } - return fileNameOrString + return (try? String(contentsOfFile: fileNameOrString, encoding: .utf8)) ?? fileNameOrString } +/// Return a pkginfo dictionary for installeritem func makepkginfo(_ filepath: String?, options: PkginfoOptions) throws -> PlistDict { - // Return a pkginfo dictionary for installeritem var installeritem = filepath ?? "" var pkginfo = PlistDict()