Add helpers for cdist config/install integration. (#551)
Implement simple integration API.
This commit is contained in:
parent
feb221c5df
commit
136f2ecd87
8 changed files with 238 additions and 13 deletions
|
@ -73,8 +73,6 @@ def check_beta(args_dict):
|
|||
|
||||
|
||||
def check_positive_int(value):
|
||||
import argparse
|
||||
|
||||
try:
|
||||
val = int(value)
|
||||
except ValueError:
|
||||
|
@ -416,10 +414,10 @@ def handle_loglevel(args):
|
|||
logging.root.setLevel(_verbosity_level[args.verbose])
|
||||
|
||||
|
||||
def parse_and_configure(argv):
|
||||
def parse_and_configure(argv, singleton=True):
|
||||
parser = get_parsers()
|
||||
parser_args = parser['main'].parse_args(argv)
|
||||
cfg = cdist.configuration.Configuration(parser_args)
|
||||
cfg = cdist.configuration.Configuration(parser_args, singleton=singleton)
|
||||
args = cfg.get_args()
|
||||
# Loglevels are handled globally in here
|
||||
handle_loglevel(args)
|
||||
|
|
|
@ -45,7 +45,7 @@ class Config(object):
|
|||
"""Cdist main class to hold arbitrary data"""
|
||||
|
||||
def __init__(self, local, remote, dry_run=False, jobs=None,
|
||||
cleanup_cmds=None):
|
||||
cleanup_cmds=None, remove_remote_files_dirs=False):
|
||||
|
||||
self.local = local
|
||||
self.remote = remote
|
||||
|
@ -56,6 +56,7 @@ class Config(object):
|
|||
self.cleanup_cmds = cleanup_cmds
|
||||
else:
|
||||
self.cleanup_cmds = []
|
||||
self.remove_remote_files_dirs = remove_remote_files_dirs
|
||||
|
||||
self.explorer = core.Explorer(self.local.target_host, self.local,
|
||||
self.remote, jobs=self.jobs)
|
||||
|
@ -67,6 +68,15 @@ class Config(object):
|
|||
self.local.create_files_dirs()
|
||||
self.remote.create_files_dirs()
|
||||
|
||||
def _remove_remote_files_dirs(self):
|
||||
"""Remove remote files and directories for the run"""
|
||||
self.remote.remove_files_dirs()
|
||||
|
||||
def _remove_files_dirs(self):
|
||||
"""Remove files and directories for the run"""
|
||||
if self.remove_remote_files_dirs:
|
||||
self._remove_remote_files_dirs()
|
||||
|
||||
@staticmethod
|
||||
def hosts(source):
|
||||
try:
|
||||
|
@ -283,7 +293,7 @@ class Config(object):
|
|||
|
||||
@classmethod
|
||||
def onehost(cls, host, host_tags, host_base_path, host_dir_name, args,
|
||||
parallel, configuration):
|
||||
parallel, configuration, remove_remote_files_dirs=False):
|
||||
"""Configure ONE system.
|
||||
If operating in parallel then return tuple (host, True|False, )
|
||||
so that main process knows for which host function was successful.
|
||||
|
@ -311,7 +321,8 @@ class Config(object):
|
|||
add_conf_dirs=args.conf_dir,
|
||||
cache_path_pattern=args.cache_path_pattern,
|
||||
quiet_mode=args.quiet,
|
||||
configuration=configuration)
|
||||
configuration=configuration,
|
||||
exec_path=sys.argv[0])
|
||||
|
||||
remote = cdist.exec.remote.Remote(
|
||||
target_host=target_host,
|
||||
|
@ -326,7 +337,8 @@ class Config(object):
|
|||
if cleanup_cmd:
|
||||
cleanup_cmds.append(cleanup_cmd)
|
||||
c = cls(local, remote, dry_run=args.dry_run, jobs=args.jobs,
|
||||
cleanup_cmds=cleanup_cmds)
|
||||
cleanup_cmds=cleanup_cmds,
|
||||
remove_remote_files_dirs=remove_remote_files_dirs)
|
||||
c.run()
|
||||
|
||||
except cdist.Error as e:
|
||||
|
@ -367,6 +379,7 @@ class Config(object):
|
|||
self.manifest.run_initial_manifest(self.local.initial_manifest)
|
||||
self.iterate_until_finished()
|
||||
self.cleanup()
|
||||
self._remove_files_dirs()
|
||||
|
||||
self.local.save_cache(start_time)
|
||||
self.log.info("Finished successful run in {:.2f} seconds".format(
|
||||
|
|
|
@ -32,9 +32,12 @@ class Singleton(type):
|
|||
instance = None
|
||||
|
||||
def __call__(cls, *args, **kwargs):
|
||||
if not cls.instance:
|
||||
cls.instance = super(Singleton, cls).__call__(*args, **kwargs)
|
||||
return cls.instance
|
||||
if 'singleton' in kwargs and kwargs['singleton'] == False:
|
||||
return super(Singleton, cls).__call__(*args, **kwargs)
|
||||
else:
|
||||
if not cls.instance:
|
||||
cls.instance = super(Singleton, cls).__call__(*args, **kwargs)
|
||||
return cls.instance
|
||||
|
||||
|
||||
_VERBOSITY_VALUES = (
|
||||
|
@ -294,7 +297,7 @@ class Configuration(metaclass=Singleton):
|
|||
return None
|
||||
|
||||
def __init__(self, command_line_args, env=os.environ,
|
||||
config_files=default_config_files):
|
||||
config_files=default_config_files, singleton=True):
|
||||
self.command_line_args = command_line_args
|
||||
self.args = self._convert_args(command_line_args)
|
||||
self.env = env
|
||||
|
|
|
@ -116,6 +116,9 @@ class Remote(object):
|
|||
self.run(["chmod", "0700", self.base_path])
|
||||
self.mkdir(self.conf_path)
|
||||
|
||||
def remove_files_dirs(self):
|
||||
self.rmdir(self.base_path)
|
||||
|
||||
def rmfile(self, path):
|
||||
"""Remove file on the remote side."""
|
||||
self.log.trace("Remote rm: %s", path)
|
||||
|
|
153
cdist/integration.py
Normal file
153
cdist/integration.py
Normal file
|
@ -0,0 +1,153 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# 2017 Darko Poljak (darko.poljak at gmail.com)
|
||||
#
|
||||
# This file is part of cdist.
|
||||
#
|
||||
# cdist is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# cdist is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with cdist. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
#
|
||||
|
||||
import cdist
|
||||
# needed for cdist.argparse
|
||||
import cdist.banner
|
||||
import cdist.config
|
||||
import cdist.install
|
||||
import cdist.shell
|
||||
import cdist.inventory
|
||||
import cdist.argparse
|
||||
import cdist.log
|
||||
import cdist.config
|
||||
import cdist.install
|
||||
import sys
|
||||
import os
|
||||
import os.path
|
||||
import collections
|
||||
import uuid
|
||||
|
||||
|
||||
def find_cdist_exec_in_path():
|
||||
"""Search cdist executable in os.get_exec_path() entries.
|
||||
"""
|
||||
for path in os.get_exec_path():
|
||||
cdist_path = os.path.join(path, 'cdist')
|
||||
if os.access(cdist_path, os.X_OK):
|
||||
return cdist_path
|
||||
return None
|
||||
|
||||
|
||||
_mydir = os.path.dirname(__file__)
|
||||
|
||||
|
||||
def find_cdist_exec():
|
||||
"""Search cdist executable starting from local lib directory.
|
||||
|
||||
Detect if ../scripts/cdist (from local lib direcotry) exists and
|
||||
if it is executable. If not then try to find cdist exec path in
|
||||
os.get_exec_path() entries. If no cdist path is found rasie
|
||||
cdist.Error.
|
||||
"""
|
||||
cdist_path = os.path.abspath(os.path.join(_mydir, '..', 'scripts',
|
||||
'cdist'))
|
||||
if os.access(cdist_path, os.X_OK):
|
||||
return cdist_path
|
||||
cdist_path = find_cdist_exec_in_path()
|
||||
if not cdist_path:
|
||||
raise cdist.Error('Cannot find cdist executable from local lib '
|
||||
'directory: {}, nor in PATH: {}.'.format(
|
||||
_mydir, os.environ.get('PATH')))
|
||||
return cdist_path
|
||||
|
||||
|
||||
ACTION_CONFIG = 'config'
|
||||
ACTION_INSTALL = 'install'
|
||||
|
||||
|
||||
def _process_hosts_simple(action, host, manifest, verbose,
|
||||
cdist_path=None):
|
||||
"""Perform cdist action ('config' or 'install') on hosts with specified
|
||||
manifest using default other cdist options. host parameter can be a
|
||||
string or iterbale of hosts. verbose is a desired verbosity level
|
||||
which defaults to VERBOSE_INFO. cdist_path is path to cdist executable,
|
||||
if it is None then integration lib tries to find it.
|
||||
"""
|
||||
if isinstance(host, str):
|
||||
hosts = [host, ]
|
||||
elif isinstance(host, collections.Iterable):
|
||||
hosts = host
|
||||
else:
|
||||
raise cdist.Error('Invalid host argument: {}'.format(host))
|
||||
|
||||
# Setup sys.argv[0] since cdist relies on command line invocation.
|
||||
if not cdist_path:
|
||||
cdist_path = find_cdist_exec()
|
||||
sys.argv[0] = cdist_path
|
||||
|
||||
cname = action.title()
|
||||
module = getattr(cdist, action)
|
||||
theclass = getattr(module, cname)
|
||||
|
||||
# Build argv for cdist and use argparse for argument parsing.
|
||||
remote_out_dir_base = os.path.join('/', 'var', 'lib', 'cdist')
|
||||
uid = str(uuid.uuid1())
|
||||
out_dir = remote_out_dir_base + uid
|
||||
cache_path_pattern = '%h-' + uid
|
||||
argv = [action, '-i', manifest, '-r', out_dir, '-C', cache_path_pattern, ]
|
||||
for i in range(verbose):
|
||||
argv.append('-v')
|
||||
for x in hosts:
|
||||
argv.append(x)
|
||||
|
||||
parser, cfg = cdist.argparse.parse_and_configure(argv, singleton=False)
|
||||
args = cfg.get_args()
|
||||
configuration = cfg.get_config(section='GLOBAL')
|
||||
|
||||
theclass.construct_remote_exec_copy_patterns(args)
|
||||
base_root_path = theclass.create_base_root_path(None)
|
||||
|
||||
for target_host in args.host:
|
||||
host_base_path, hostdir = theclass.create_host_base_dirs(
|
||||
target_host, base_root_path)
|
||||
theclass.onehost(target_host, None, host_base_path, hostdir, args,
|
||||
parallel=False, configuration=configuration,
|
||||
remove_remote_files_dirs=True)
|
||||
|
||||
|
||||
def configure_hosts_simple(host, manifest,
|
||||
verbose=cdist.argparse.VERBOSE_INFO,
|
||||
cdist_path=None):
|
||||
"""Configure hosts with specified manifest using default other cdist
|
||||
options. host parameter can be a string or iterbale of hosts. verbose
|
||||
is a desired verbosity level which defaults to VERBOSE_INFO.
|
||||
cdist_path is path to cdist executable, if it is None then integration
|
||||
lib tries to find it.
|
||||
"""
|
||||
_process_hosts_simple(action=ACTION_CONFIG, host=host,
|
||||
manifest=manifest, verbose=verbose,
|
||||
cdist_path=cdist_path)
|
||||
|
||||
|
||||
def install_hosts_simple(host, manifest,
|
||||
verbose=cdist.argparse.VERBOSE_INFO,
|
||||
cdist_path=None):
|
||||
"""Install hosts with specified manifest using default other cdist
|
||||
options. host parameter can be a string or iterbale of hosts. verbose
|
||||
is a desired verbosity level which defaults to VERBOSE_INFO.
|
||||
cdist_path is path to cdist executable, if it is None then integration
|
||||
lib tries to find it.
|
||||
"""
|
||||
_process_hosts_simple(action=ACTION_INSTALL, host=host,
|
||||
manifest=manifest, verbose=verbose,
|
||||
cdist_path=cdist_path)
|
|
@ -236,9 +236,16 @@ class ConfigurationTestCase(test.CdistTestCase):
|
|||
x = cc.Configuration(None)
|
||||
args = argparse.Namespace()
|
||||
args.a = 'a'
|
||||
y = cc.Configuration()
|
||||
y = cc.Configuration(args)
|
||||
self.assertIs(x, y)
|
||||
|
||||
def test_non_singleton(self):
|
||||
x = cc.Configuration(None, singleton=False)
|
||||
args = argparse.Namespace()
|
||||
args.a = 'a'
|
||||
y = cc.Configuration(args, singleton=False)
|
||||
self.assertIsNot(x, y)
|
||||
|
||||
def test_read_config_file(self):
|
||||
config = cc.Configuration(None, env={}, config_files=())
|
||||
d = config._read_config_file(self.config_file)
|
||||
|
|
47
docs/src/cdist-integration.rst
Normal file
47
docs/src/cdist-integration.rst
Normal file
|
@ -0,0 +1,47 @@
|
|||
cdist integration / using cdist as library
|
||||
==========================================
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
cdist can be integrate with other applications by importing cdist and other
|
||||
cdist modules and setting all by hand. There are also helper functions which
|
||||
aim to ease this integration. Just import **cdist.integration** and use its
|
||||
functions:
|
||||
|
||||
* :strong:`cdist.integration.configure_hosts_simple` for configuration
|
||||
* :strong:`cdist.integration.install_hosts_simple` for installation.
|
||||
|
||||
Functions require `host` and `manifest` parameters.
|
||||
`host` can be specified as a string representing host or as iterable
|
||||
of hosts. `manifest` is a path to initial manifest. For other cdist
|
||||
options default values will be used. `verbose` is a desired verbosity
|
||||
level which defaults to VERBOSE_INFO. `cdist_path` parameter specifies
|
||||
path to cdist executable, if it is `None` then functions will try to
|
||||
find it first from local lib directory and then in PATH.
|
||||
|
||||
In case of cdist error :strong:`cdist.Error` exception is raised.
|
||||
|
||||
:strong:`WARNING`: cdist integration helper functions are not yet stable!
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
# configure host from python interactive shell
|
||||
>>> import cdist.integration
|
||||
>>> cdist.integration.configure_hosts_simple('185.203.114.185',
|
||||
... '~/.cdist/manifest/init')
|
||||
|
||||
# configure host from python interactive shell, specifying verbosity level
|
||||
>>> import cdist.integration
|
||||
>>> cdist.integration.configure_hosts_simple(
|
||||
... '185.203.114.185', '~/.cdist/manifest/init',
|
||||
... verbose=cdist.argparse.VERBOSE_TRACE)
|
||||
|
||||
# configure specified dns hosts from python interactive shell
|
||||
>>> import cdist.integration
|
||||
>>> hosts = ('dns1.ungleich.ch', 'dns2.ungleich.ch', 'dns3.ungleich.ch', )
|
||||
>>> cdist.integration.configure_hosts_simple(hosts,
|
||||
... '~/.cdist/manifest/init')
|
|
@ -26,6 +26,7 @@ Contents:
|
|||
cdist-messaging
|
||||
cdist-parallelization
|
||||
cdist-inventory
|
||||
cdist-integration
|
||||
cdist-reference
|
||||
cdist-best-practice
|
||||
cdist-stages
|
||||
|
|
Loading…
Reference in a new issue