Begin reorg of repo plugins code

This commit is contained in:
Greg Neagle
2017-03-04 08:19:29 -08:00
parent d6efe8b75f
commit 0fb1d3cefa
7 changed files with 169 additions and 149 deletions
+15 -24
View File
@@ -30,11 +30,11 @@ from optparse import OptionParser
# our libs
from munkilib import dmgutils
from munkilib import iconutils
from munkilib import munkirepo
from munkilib import osutils
from munkilib import pkgutils
from munkilib import FoundationPlist
from munkilib import Repo
# PyLint cannot properly find names inside Cocoa libraries, so issues bogus
# No name 'Foo' in module 'Bar' warnings. Disable them.
@@ -45,7 +45,8 @@ from Foundation import CFPreferencesCopyAppValue
def generate_png_from_dmg_item(install_item, repo):
'''Generate a PNG from a disk image containing an application'''
dmgpath = repo.join('pkgs', install_item['installer_item_location'])
mountpoints = dmgutils.mountdmg(dmgpath)
dmg_handle = repo.open(dmgpath, 'r')
mountpoints = dmgutils.mountdmg(dmg_handle.local_path)
if mountpoints:
mountpoint = mountpoints[0]
apps = [item for item in install_item.get('items_to_copy', [])
@@ -78,7 +79,8 @@ def generate_pngs_from_pkg(install_item, repo):
item_path = repo.join(u'pkgs', install_item['installer_item_location'])
if pkgutils.hasValidDiskImageExt(item_path):
dmg_path = item_path
mountpoints = dmgutils.mountdmg(dmg_path)
dmg_handle = repo.open(dmg_path, 'r')
mountpoints = dmgutils.mountdmg(dmg_handle.local_path)
if mountpoints:
mountpoint = mountpoints[0]
if install_item.get('package_path'):
@@ -198,7 +200,7 @@ def pref(prefname):
def main():
'''Main'''
usage = "usage: %prog [options] [/path/to/repo_root] [repo_url] [plugin]"
usage = "usage: %prog [options] [/path/to/repo_root]"
parser = OptionParser(usage=usage)
parser.add_option(
'--force', '-f', action='store_true', dest='force',
@@ -206,13 +208,14 @@ def main():
parser.add_option(
'--item', '-i', action='append', type='string', dest='items',
help='Only run for given pkginfo item name(s).')
parser.add_option('--plugin', '--plugin', default=pref('plugin'),
help='Optional. Custom plugin to connect to repo.')
parser.add_option('--repo_url', '--repo-url', default=pref('repo_url'),
help='Optional repo fileshare URL used by repo plugin.')
parser.set_defaults(force=False)
options, arguments = parser.parse_args()
# Make sure we have a path to work with
repo_path = None
repo_url = None
plugin = None
if len(arguments) == 0:
repo_path = pref('repo_path')
if not repo_path:
@@ -223,26 +226,14 @@ def main():
else:
repo_path = arguments[0].rstrip("/")
# Make sure the repo path exists
if len(arguments) > 1:
repo_url = arguments[1].rstrip("/")
else:
repo_url = pref('repo_url')
if not repo_url:
print_err_utf8("Need to specify a URL for the repo!")
exit(-1)
else:
print_utf8("Using repo url: %s" % repo_url)
if len(arguments) > 2:
plugin = arguments[2]
else:
plugin = pref('plugin')
if options.plugin is None:
options.plugin = 'FileRepo'
# Make sure the repo exists
repo = Repo.Open(repo_path, repo_url, plugin)
repo = munkirepo.connect(repo_path, options.repo_url, options.plugin)
if not repo.available():
print_err_utf8("Repo root path %s doesn't exist!" % repo_path)
print_err_utf8('Could not connect to munki repo. Check the '
'configuration and try again.')
exit(-1)
# generate icons!
+16 -8
View File
@@ -31,7 +31,8 @@ import sys
import os
import optparse
import hashlib
from munkilib import Repo
from munkilib import munkirepo
try:
from munkilib import FoundationPlist as plistlib
@@ -381,8 +382,8 @@ def pref(prefname):
PREFSNAME = 'com.googlecode.munki.munkiimport.plist'
PREFSPATH = os.path.expanduser(os.path.join('~/Library/Preferences',
PREFSNAME))
PREFSPATH = os.path.expanduser(os.path.join('~/Library/Preferences', PREFSNAME))
def main():
'''Main'''
usage = "usage: %prog [options] [/path/to/repo_root]"
@@ -396,7 +397,7 @@ def main():
'over the default repo_url specified via '
'--configure.')
parser.add_option('--plugin', '--plugin', default=pref('plugin'),
help='Specify a custom plugin to run for munkiimport Repo.')
help='Specify a custom plugin to connect to repo.')
parser.set_defaults(force=False)
options, arguments = parser.parse_args()
@@ -416,11 +417,18 @@ def main():
else:
repopath = arguments[0].rstrip("/")
# Make sure the repo path exists
repo = Repo.Open(repopath, options.repo_url, options.plugin)
if not repo.exists():
print_err_utf8("Repo root path %s doesn't exist!" % repopath)
if options.plugin is None:
options.plugin = 'FileRepo'
# Make sure the repo exists
repo = munkirepo.connect(repopath, options.repo_url, options.plugin)
if not repo.available():
print_err_utf8('Could not connect to munki repo. Check the '
'configuration and try again.')
exit(-1)
#if not repo.exists():
# print_err_utf8("Repo root path %s doesn't exist!" % repopath)
# exit(-1)
# Make the catalogs
makecatalogs(repo, options)
+5 -4
View File
@@ -42,10 +42,11 @@ from munkilib import info
from munkilib import display
from munkilib import dmgutils
from munkilib import munkihash
from munkilib import munkirepo
from munkilib import osutils
from munkilib import pkgutils
from munkilib import FoundationPlist
from munkilib import Repo
# PyLint cannot properly find names inside Cocoa libraries, so issues bogus
# No name 'Foo' in module 'Bar' warnings. Disable them.
@@ -902,7 +903,7 @@ def main():
'over the default repo_url specified via '
'--configure.')
parser.add_option('--plugin', '--plugin', default='',
help='Optional custom plugin to run for munkiimport Repo.')
help='Specify a custom plugin to connect to repo.')
parser.add_option('--icon_path', '--icon-path', default='', type='string',
help='Path to an icon file for the package. '
'Will overwrite an existing icon.')
@@ -927,7 +928,7 @@ def main():
#default is what user put in munkiimport --configure
REPO_PATH = pref('repo_path')
REPO_URL = pref('repo_url')
REPO_PLUGIN = pref('plugin')
REPO_PLUGIN = pref('plugin') or 'FileRepo'
if options.repo_path:
if not os.path.exists(options.repo_path) and not options.repo_url:
@@ -999,7 +1000,7 @@ def main():
'tool, or provide with --repo-path')
exit(-1)
repo = Repo.Open(REPO_PATH, REPO_URL, REPO_PLUGIN)
repo = munkirepo.connect(REPO_PATH, REPO_URL, REPO_PLUGIN)
if not repo.available():
print >> sys.stderr, ('Could not connect to munki repo. Check the '
'configuration and try again.')
-74
View File
@@ -1,74 +0,0 @@
#!/usr/bin/python
# encoding: utf-8
#
# Copyright 2016 Centrify Corporation.
#
# 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.
"""
Repo
Created by Centrify Corporation 2016-06-02.
Interface for accessing a repo.
"""
import re
import sys
import imp
import os
def Open(path, url, plugin):
#looks for installtion path for munki
# first look for a plugin in the same dir as us in munkilib/plugins
munkilib_path = os.path.dirname(os.path.abspath(__file__))
munkilib_path = os.path.join(munkilib_path, 'plugins')
if not os.path.exists(munkilib_path):
# didn't find it; assume the default install path
command = "munkiimport"
commandPath = os.popen("/usr/bin/which %s" % command).read().strip()
commandPath = os.path.split(commandPath)
munkilib_path = commandPath[0]
#use default munki location if munki installation path is not found
if munkilib_path == None or munkilib_path == "":
munkilib_path = '/usr/local/munki/munkilib/plugins'
else:
munkilib_path = munkilib_path + '/munkilib/plugins'
#looks for plugin in /usr/local/munki/munkilib/plugins (installation of munki)
if plugin == None or plugin == "":
#default is FileRepo
plugin = 'FileRepo'
module = imp.load_source(plugin, munkilib_path + "/" + plugin + ".py")
import_class = getattr(module, plugin)
parent = import_class
class Repo(parent):
mounted = False
def available(self):
#if path does not exist, mount to local filesystem
if not self.exists():
retcode = self.mount()
if retcode == 0:
self.mounted = True
#if path still doesn't exist, then cannot find munki_repo
if not self.exists():
print >> sys.stderr, "repo is missing"
return False
#checks if all subdirectories are there
for subdir in ['catalogs', 'manifests', 'pkgs', 'pkgsinfo']:
if not self.exists(subdir):
print >> sys.stderr, "repo is missing %s" % subdir
return False
# if we get this far, the repo path looks OK
return True
return Repo(path, url)
@@ -22,13 +22,15 @@ Implementation for accessing a repo via direct file access, including
a remote repo mounted via AFP, SMB, or NFS.
"""
from munkilib.munkicommon import listdir
import os
import sys
import subprocess
import objc
import glob
from munkilib.munkicommon import listdir
from munkilib.munkirepo import Repo
# NetFS share mounting code borrowed and liberally adapted from Michael Lynn's
# work here: https://gist.github.com/pudquick/1362a8908be01e23041d
try:
@@ -126,16 +128,13 @@ if NETFSMOUNTURLSYNC_AVAILABLE:
password = getpass.getpass()
mount_share_with_credentials(share_url, username, password)
class FileRepo(object):
class FileRepo(Repo):
WE_MOUNTED_THE_REPO = False
'''Repo implementation that access a local or locally-mounted repo.'''
def __init__(self, path, url):
self.path = path
self.url = url
def exists(self, subdir = None):
def exists(self, subdir=None):
'''Returns true if the specified path exists in the repo'''
full_path = self.path
full_path = self.root
if subdir:
full_path = os.path.join(full_path, subdir)
return os.path.exists(full_path)
@@ -143,12 +142,12 @@ class FileRepo(object):
def isdir(self, path):
'''Returns true if the specified path exists in the repo
and is a directory.'''
return os.path.isdir(os.path.join(self.path, path))
return os.path.isdir(os.path.join(self.root, path))
def isfile(self, path):
'''Returns true if the specified path exists in the repo
and is a regular file.'''
return os.path.isfile(os.path.join(self.path, path))
return os.path.isfile(os.path.join(self.root, path))
def join(self, *args):
'''Combines path elements within the repo.'''
@@ -168,32 +167,32 @@ class FileRepo(object):
def mkdir(self, path, mode=0777):
'''Creates a directory within the repo.'''
return os.mkdir(os.path.join(self.path, path), mode)
return os.mkdir(os.path.join(self.root, path), mode)
def makedirs(self, path, mode=0777):
'''Creates a directory within the repo, including parent directories.'''
return os.makedirs(os.path.join(self.path, path), mode)
return os.makedirs(os.path.join(self.root, path), mode)
def listdir(self, path):
'''Lists the contents of a repo directory.'''
return listdir(os.path.join(self.path, path))
return listdir(os.path.join(self.root, path))
def remove(self, path):
'''Removes a file from the repo.'''
return os.remove(os.path.join(self.path, path))
return os.remove(os.path.join(self.root, path))
def unlink(self, path):
'''Removes a file from the repo.'''
return os.unlink(os.path.join(self.path, path))
return os.unlink(os.path.join(self.root, path))
def get(self, src, dest):
'''Copies a file from the repo to a local file.'''
cmd = ['/bin/cp', os.path.join(self.path, src), dest]
cmd = ['/bin/cp', os.path.join(self.root, src), dest]
return subprocess.call(cmd)
def put(self, src, dest):
'''Copies a local file to the repo.'''
cmd = ['/bin/cp', src, os.path.join(self.path, dest)]
cmd = ['/bin/cp', src, os.path.join(self.root, dest)]
return subprocess.call(cmd)
#
@@ -205,24 +204,24 @@ class FileRepo(object):
# will be a local temporary file that was copied from the remote
# repo and/or will be copied to the remote repo on close.
#
class RepoFile(object):
def __init__(self, repo, repo_path, mode):
self.repo = repo
self.repo_path = repo_path
self.repo_mode = mode
self.file = open(self.repo_path, mode)
self.local_path = self.repo_path
def read(self):
return self.file.read()
def open(self, path, mode='r'):
'''Opens a file in the repo.'''
class RepoFile(object):
def __init__(self, repo, repo_path, mode):
self.repo = repo
self.repo_path = repo_path
self.repo_mode = mode
self.file = open(self.repo_path, mode)
self.local_path = self.repo_path
def read(self):
return self.file.read()
return RepoFile(self, os.path.join(self.path, path), mode)
return self.RepoFile(self, os.path.join(self.root, path), mode)
def mount(self):
'''Mounts the repo locally.'''
if os.path.exists(self.path):
if os.path.exists(self.root):
return
print 'Attempting to mount fileshare %s:' % self.url
if NETFSMOUNTURLSYNC_AVAILABLE:
@@ -235,38 +234,38 @@ class FileRepo(object):
self.WE_MOUNTED_THE_REPO = True
return 0
else:
os.mkdir(self.path)
os.mkdir(self.root)
if self.url.startswith('afp:'):
cmd = ['/sbin/mount_afp', '-i', self.url, self.path]
cmd = ['/sbin/mount_afp', '-i', self.url, self.root]
elif self.url.startswith('smb:'):
cmd = ['/sbin/mount_smbfs', self.url[4:], self.path]
cmd = ['/sbin/mount_smbfs', self.url[4:], self.root]
elif self.url.startswith('nfs://'):
cmd = ['/sbin/mount_nfs', self.url[6:], self.path]
cmd = ['/sbin/mount_nfs', self.url[6:], self.root]
else:
print >> sys.stderr, 'Unsupported filesystem URL!'
return
retcode = subprocess.call(cmd)
if retcode:
os.rmdir(self.path)
os.rmdir(self.root)
else:
self.WE_MOUNTED_THE_REPO = True
return retcode
def unmount(self):
'''Unmounts the repo.'''
if not os.path.exists(self.path):
if not os.path.exists(self.root):
return
retcode = 0
if os.path.exists(self.path):
cmd = ['/sbin/umount', self.path]
if os.path.exists(self.root):
cmd = ['/sbin/umount', self.root]
retcode = subprocess.call(cmd)
return retcode
def walk(self, path, **kwargs):
'''Walks a path in the repo, returning all files and subdirectories.
Only a subset of the features of os.walk() are supported.'''
for (dirpath, dirnames, filenames) in os.walk(os.path.join(self.path, path), **kwargs):
dirpath = dirpath[len(self.path) + 1:]
for (dirpath, dirnames, filenames) in os.walk(os.path.join(self.root, path), **kwargs):
dirpath = dirpath[len(self.root) + 1:]
yield (dirpath, dirnames, filenames)
def glob(self, path, *args):
@@ -0,0 +1,21 @@
import os
from munkilib.munkirepo.FileRepo import FileRepo
class GitFileRepo(FileRepo):
'''A subclass of FileRepo that does git commits for pkginfo files'''
class RepoFile(object):
def __init__(self, repo, repo_path, mode):
self.repo = repo
self.repo_path = repo_path
self.repo_mode = mode
self.file = open(self.repo_path, mode)
self.local_path = self.repo_path
def __del__(self):
if 'w' in self.repo_mode:
print "Pretending to do a git commit on %s" % self.repo_path
def read(self):
return self.file.read()
@@ -0,0 +1,74 @@
import imp
import os
import sys
class Repo(object):
'''Abstract base class for repo'''
mounted = False
def __init__(self, path, url):
self.root = path
self.url = url
def available(self):
'''if path does not exist, mount to local filesystem'''
if not self.exists():
retcode = self.mount()
if retcode == 0:
self.mounted = True
#if path still doesn't exist, then cannot find munki_repo
if not self.exists():
print >> sys.stderr, "repo is missing"
return False
#check if all subdirectories are there
for subdir in ['catalogs', 'manifests', 'pkgs', 'pkgsinfo']:
if not self.exists(subdir):
print >> sys.stderr, "repo is missing %s" % subdir
return False
# if we get this far, the repo path looks OK
return True
def exists(self):
'''Must be overriden in subclass'''
return False
def mount(self):
'''Must be overridden in subclasses'''
return -1
class MissingRepo(Repo):
'''Stub object to return when we can't find the one requsted'''
def available(self):
return False
def plugin_named(name):
'''Returns a plugin object given a name'''
try:
module = globals()[name]
return getattr(module, name)
except (KeyError, AttributeError):
print >> sys.stderr, (
"ERROR: %s repo plugin not found." % name)
return None
def connect(repo_path, repo_url, plugin_name):
'''Return a repo object for operations on our Munki repo'''
if plugin_name is None:
plugin_name = 'FileRepo'
plugin = plugin_named(plugin_name)
if plugin:
return plugin(repo_path, repo_url)
else:
return MissingRepo(repo_path, repo_url)
# yes, having this at the end is weird. But it allows us to dynamically import
# additional modules from our directory
__all__ = [os.path.splitext(name)[0]
for name in os.listdir(os.path.dirname(os.path.abspath(__file__)))
if name.endswith('.py') and not name == '__init__.py']
from . import *