Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Options: migrated tools/git_commit.py to new parser #328

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
286 changes: 131 additions & 155 deletions tools/git_commit.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,179 +3,155 @@
back to git. To use this script, enable the Trigger plugin, put this
script in /var/lib/bcfg2/Trigger/, and create /etc/bcfg2-commit.conf.

The config file, /etc/bcfg2-commit.conf, may contain four options in
the [global] section:

* "config" is the path to the Bcfg2 server config file. (Default:
/etc/bcfg2.conf)
* "commit" is a comma-separated list of globs giving the paths that
should be committed back to the repository. Default is 'SSLCA/*,
SSHbase/*, Cfg/*', which will commit data back for SSLCA, SSHbase,
Cfg, FileProbes, etc., but not, for instance, Probes/probed.xml.
The config file, /etc/bcfg2-commit.conf, may contain four options:

* "config" in [global] is the path to the Bcfg2 server config file.
(Default: /etc/bcfg2.conf)
* "commit" in [global] is a comma-separated list of globs giving the
paths that should be committed back to the repository. Default is
'SSLCA/*, SSHbase/*, Cfg/*', which will commit data back for SSLCA,
SSHbase, Cfg, FileProbes, etc., but not, for instance, Probes/probed.xml.
You may wish to add Metadata/clients.xml to the commit list.
* "debug" and "verbose" let you set the log level for git_commit.py
itself.
* "debug" and "verbose" in [logging] let you set the log level for
git_commit.py itself.
"""


import os
import sys
import git
import logging
import Bcfg2.Logger
import Bcfg2.Options
from Bcfg2.Compat import ConfigParser
import Bcfg2.Logger
from fnmatch import fnmatch

# config file path
CONFIG = "/etc/bcfg2-commit.conf"

# config defaults. all config options are in the [global] section
DEFAULTS = dict(config='/etc/bcfg2.conf',
commit="SSLCA/*, SSHbase/*, Cfg/*")


def list_changed_files(repo):
return [d for d in repo.index.diff(None)
if (d.a_blob is not None and not d.deleted_file and
not d.renamed and not d.new_file)]


def add_to_commit(patterns, path, repo, relpath):
progname = os.path.basename(sys.argv[0])
logger = logging.getLogger(progname)
for pattern in patterns:
if fnmatch(path, os.path.join(relpath, pattern)):
logger.debug("%s: Adding %s to commit" % (progname, path))
repo.index.add([path])
return True
return False


def parse_options():
config = ConfigParser.SafeConfigParser(DEFAULTS)
config.read(CONFIG)

optinfo = dict(
profile=Bcfg2.Options.CLIENT_PROFILE,
dryrun=Bcfg2.Options.CLIENT_DRYRUN,
groups=Bcfg2.Options.Option("Groups",
default=[],
cmd="-g",
odesc='<group>:<group>',
cook=Bcfg2.Options.colon_split))
optinfo.update(Bcfg2.Options.CLI_COMMON_OPTIONS)
optinfo.update(Bcfg2.Options.SERVER_COMMON_OPTIONS)
argv = [Bcfg2.Options.CFILE.cmd, config.get("global", "config")]
argv.extend(sys.argv[1:])
setup = Bcfg2.Options.OptionParser(optinfo, argv=argv)
setup.parse(argv)

setup['commit'] = Bcfg2.Options.list_split(config.get("global",
"commit"))
for opt in ['debug', 'verbose']:
try:
setup[opt] = config.getboolean("global", opt)
except ConfigParser.NoOptionError:
pass

try:
hostname = setup['args'][0]
except IndexError:
print(setup.hm)
raise SystemExit(1)
return (setup, hostname)


def setup_logging(setup):
progname = os.path.basename(sys.argv[0])
log_args = dict(to_syslog=setup['syslog'], to_console=sys.stdout.isatty(),
to_file=setup['logging'], level=logging.WARNING)
if setup['debug']:
log_args['level'] = logging.DEBUG
elif setup['verbose']:
log_args['level'] = logging.INFO
Bcfg2.Logger.setup_logging(progname, **log_args)
return logging.getLogger(progname)


def main():
progname = os.path.basename(sys.argv[0])
setup, hostname = parse_options()
logger = setup_logging(setup)
if setup['dryrun']:
logger.info("%s: In dry-run mode, changes will not be committed" %
progname)

if setup['vcs_root']:
gitroot = os.path.realpath(setup['vcs_root'])
else:
gitroot = os.path.realpath(setup['repo'])
logger.info("%s: Using Git repo at %s" % (progname, gitroot))
try:
repo = git.Repo(gitroot)
except: # pylint: disable=W0702
logger.error("%s: Error setting up Git repo at %s: %s" %
(progname, gitroot, sys.exc_info()[1]))
return 1

# canonicalize the repo path so that git will recognize it as
# being inside the git repo
bcfg2root = os.path.realpath(setup['repo'])

if not bcfg2root.startswith(gitroot):
logger.error("%s: Bcfg2 repo %s is not inside Git repo %s" %
(progname, bcfg2root, gitroot))
return 1

# relative path to Bcfg2 root from VCS root
if gitroot == bcfg2root:
relpath = ''
else:
relpath = bcfg2root[len(gitroot) + 1:]

new = 0
changed = 0
logger.debug("%s: Untracked files: %s" % (progname, repo.untracked_files))
for path in repo.untracked_files:
if add_to_commit(setup['commit'], path, repo, relpath):
new += 1
class CLI(object):
options = [
Bcfg2.Options.BooleanOption(
'-n', '--dry-run', help='Do not actually commit something'),
Bcfg2.Options.PathOption(
'-c', '--config', action=Bcfg2.Options.ConfigFileAction,
help='Configuration file', default='/etc/bcfg2-commit.conf'),
Bcfg2.Options.Common.repository,
Bcfg2.Options.Common.syslog,

Bcfg2.Options.PathOption(
cf=('server', 'vcs_root'), default='<repository>',
help='VCS repository root'),
Bcfg2.Options.Option(
cf=('global', 'commit'), type=Bcfg2.Options.Types.comma_list,
default=['SSLCA/*', 'SSHbase/*', 'Cfg/*'], help=''),
Bcfg2.Options.PathOption(
cf=('global', 'config'), action=Bcfg2.Options.ConfigFileAction,
default='/etc/bcfg2.conf'),

# Trigger arguments
Bcfg2.Options.PositionalArgument('hostname', help='Client hostname'),
Bcfg2.Options.Option('-p', '--profile', metavar='<profile>',
help='Client profile'),
Bcfg2.Options.Option('-g', '--groups', metavar='<group1:...:groupN>',
help='All client groups'),
]

def __init__(self):
Bcfg2.Options.get_parser(
description='Trigger script to commit selected changes to a local '
'repository back to git.',
components=[self, Bcfg2.Logger._OptionContainer]).parse()
self.progname = os.path.basename(sys.argv[0])
self.logger = logging.getLogger(self.progname)

def add_to_commit(self, repo, path, relpath):
for pattern in Bcfg2.Options.setup.commit:
if fnmatch(path, os.path.join(relpath, pattern)):
self.logger.debug('Adding %s to commit' % path)
repo.index.add([path])
return True
return False

def run(self):
if Bcfg2.Options.setup.dry_run:
self.logger.info('In dry-run mode, changes will not be committed')

if 'vcs_root' in Bcfg2.Options.setup:
gitroot = Bcfg2.Options.setup.vcs_root

if not Bcfg2.Options.setup.repository.startswith(gitroot):
logger.error('Bcfg2 repo %s is not inside Git repo %s' %
(Bcfg2.Options.setup.repository, gitroot))
return 1

relpath = Bcfg2.Options.setup.repository[len(gitroot) + 1:]
else:
logger.debug("%s: Not adding %s to commit" % (progname, path))
logger.debug("%s: Untracked files after building commit: %s" %
(progname, repo.untracked_files))

changes = list_changed_files(repo)
logger.info("%s: Changed files: %s" % (progname,
[d.a_blob.path for d in changes]))
for diff in changes:
if add_to_commit(setup['commit'], diff.a_blob.path, repo, relpath):
changed += 1
else:
logger.debug("%s: Not adding %s to commit" % (progname,
diff.a_blob.path))
logger.info("%s: Changed files after building commit: %s" %
(progname, [d.a_blob.path for d in list_changed_files(repo)]))

if new + changed > 0:
logger.debug("%s: Committing %s new files and %s changed files" %
(progname, new, changed))
if setup['dryrun']:
logger.warning("%s: In dry-run mode, skipping commit and push" %
progname)
gitroot = Bcfg2.Options.setup.repository
relpath = ''

self.logger.info('Using Git repo at %s' % gitroot)

try:
repo = git.Repo(gitroot)
except: # pylint: disable=W0702
self.logger.error('Error setting up Git repo at %s: %s' %
(gitroot, sys.exc_info()[1]))
return 1

new = 0
changed = 0
self.logger.debug('Untracked files: %s' % repo.untracked_files)
for path in repo.untracked_files:
if self.add_to_commit(repo, path, relpath):
new += 1
else:
self.logger.debug('Not adding %s to commit' % path)

self.logger.debug('Untracked files after building commit: %s' %
repo.untracked_files)

changes = list_changed_files(repo)
self.logger.info('Changed files: %s' %
[d.a_blob.path for d in changes])

for diff in changes:
if self.add_to_commit(repo, diff.a_blob.path, relpath):
changed += 1
else:
self.logger.debug('Not adding %s to commit' %
diff.a_blob.path)
self.logger.info('Changed files after building commit: %s' %
[d.a_blob.path for d in list_changed_files(repo)])

if new + changed > 0:
self.logger.debug('Committing %s new files and %s changed files' %
(new, changed))

if Bcfg2.Options.setup.dry_run:
self.logger.warning('In dry-run mode, skipping commit '
'and push')
else:
output = repo.index.commit('Auto-commit with %s from %s run' %
(self.progname,
Bcfg2.Options.setup.hostname))
if output:
self.logger.debug(output)

if 'origin' in repo.remotes:
remote = repo.remote()
self.logger.debug('Pushing to remote %s at %s' %
(remote, remote.url))
output = remote.push()
if output:
self.logger.debug(output)
else:
self.logger.info('Not pushing because there is no origin')
else:
output = repo.index.commit("Auto-commit with %s from %s run" %
(progname, hostname))
if output:
logger.debug("%s: %s" % (progname, output))
remote = repo.remote()
logger.debug("%s: Pushing to remote %s at %s" % (progname, remote,
remote.url))
output = remote.push()
if output:
logger.debug("%s: %s" % (progname, output))
else:
logger.info("%s: No changes to commit" % progname)
self.logger.info('No changes to commit')


if __name__ == '__main__':
sys.exit(main())
sys.exit(CLI().run())