Resolve conflict.

This commit is contained in:
Darko Poljak 2016-06-22 12:56:53 +02:00
commit a12b74b2f5
10 changed files with 261 additions and 19 deletions

View file

@ -41,8 +41,8 @@ BANNER = """
"P' "" "" "P' "" ""
""" """
REMOTE_COPY = "scp -o User=root -q" REMOTE_COPY = "scp -o User=root"
REMOTE_EXEC = "ssh -o User=root -q" REMOTE_EXEC = "ssh -o User=root"
class Error(Exception): class Error(Exception):
"""Base exception class for this project""" """Base exception class for this project"""

View file

@ -77,6 +77,9 @@ class Emulator(object):
self.type_name = os.path.basename(argv[0]) self.type_name = os.path.basename(argv[0])
self.cdist_type = core.CdistType(self.type_base_path, self.type_name) self.cdist_type = core.CdistType(self.type_base_path, self.type_name)
# if set then object already exists and this var holds existing
# requirements
self._existing_reqs = None
self.__init_log() self.__init_log()
@ -150,10 +153,18 @@ class Emulator(object):
self.parameters[key] = value self.parameters[key] = value
if self.cdist_object.exists and not 'CDIST_OVERRIDE' in self.env: if self.cdist_object.exists and not 'CDIST_OVERRIDE' in self.env:
# make existing requirements a set, so we can compare it
# later with new requirements
self._existing_reqs = set(self.cdist_object.requirements)
if self.cdist_object.parameters != self.parameters: if self.cdist_object.parameters != self.parameters:
raise cdist.Error("Object %s already exists with conflicting parameters:\n%s: %s\n%s: %s" errmsg = ("Object %s already exists with conflicting "
% (self.cdist_object.name, " ".join(self.cdist_object.source), self.cdist_object.parameters, self.object_source, self.parameters) "parameters:\n%s: %s\n%s: %s" % (self.cdist_object.name,
) " ".join(self.cdist_object.source),
self.cdist_object.parameters,
self.object_source,
self.parameters))
self.log.error(errmsg)
raise cdist.Error(errmsg)
else: else:
if self.cdist_object.exists: if self.cdist_object.exists:
self.log.debug('Object %s override forced with CDIST_OVERRIDE',self.cdist_object.name) self.log.debug('Object %s override forced with CDIST_OVERRIDE',self.cdist_object.name)
@ -210,7 +221,7 @@ class Emulator(object):
# if no second last line, we are on the first type, so do not set a requirement # if no second last line, we are on the first type, so do not set a requirement
pass pass
reqs = set()
if "require" in self.env: if "require" in self.env:
requirements = self.env['require'] requirements = self.env['require']
self.log.debug("reqs = " + requirements) self.log.debug("reqs = " + requirements)
@ -234,6 +245,24 @@ class Emulator(object):
# (__file//bar => __file/bar) # (__file//bar => __file/bar)
# This ensures pattern matching is done against sanitised list # This ensures pattern matching is done against sanitised list
self.cdist_object.requirements.append(cdist_object.name) self.cdist_object.requirements.append(cdist_object.name)
reqs.add(cdist_object.name)
if self._existing_reqs is not None:
# if object exists then compare existing and new requirements
self.log.debug("OBJ: {} {}".format(self.cdist_type, self.object_id))
self.log.debug("EXISTING REQS: {}".format(self._existing_reqs))
self.log.debug("REQS: {}".format(reqs))
if self._existing_reqs != reqs:
errmsg = ("Object {} already exists with conflicting "
"requirements:\n{}: {}\n{}: {}".format(
self.cdist_object.name,
" ".join(self.cdist_object.source),
self._existing_reqs,
self.object_source,
reqs))
self.log.error(errmsg)
raise cdist.Error(errmsg)
def record_auto_requirements(self): def record_auto_requirements(self):
"""An object shall automatically depend on all objects that it defined in it's type manifest. """An object shall automatically depend on all objects that it defined in it's type manifest.

View file

@ -33,6 +33,7 @@ import tempfile
import cdist import cdist
import cdist.message import cdist.message
from cdist import core from cdist import core
import cdist.exec.util as exec_util
CONF_SUBDIRS_LINKED = [ "explorer", "files", "manifest", "type" ] CONF_SUBDIRS_LINKED = [ "explorer", "files", "manifest", "type" ]
@ -206,12 +207,14 @@ class Local(object):
env.update(message.env) env.update(message.env)
try: try:
output = exec_util.call_get_output(command, env=env)
self.log.debug("Local output: {}".format(output))
if return_output: if return_output:
return subprocess.check_output(command, env=env).decode() return output.decode()
else: except subprocess.CalledProcessError as e:
subprocess.check_call(command, env=env) raise cdist.Error("Command failed: " + " ".join(command)
except subprocess.CalledProcessError: + " with returncode: {} and output: {}".format(
raise cdist.Error("Command failed: " + " ".join(command)) e.returncode, e.output))
except OSError as error: except OSError as error:
raise cdist.Error(" ".join(command) + ": " + error.args[1]) raise cdist.Error(" ".join(command) + ": " + error.args[1])
finally: finally:

View file

@ -26,6 +26,7 @@ import sys
import glob import glob
import subprocess import subprocess
import logging import logging
import cdist.exec.util as exec_util
import cdist import cdist
@ -167,12 +168,14 @@ class Remote(object):
self.log.debug("Remote run: %s", command) self.log.debug("Remote run: %s", command)
try: try:
output = exec_util.call_get_output(command, env=os_environ)
self.log.debug("Remote output: {}".format(output))
if return_output: if return_output:
return subprocess.check_output(command, env=os_environ).decode() return output.decode()
else: except subprocess.CalledProcessError as e:
subprocess.check_call(command, env=os_environ) raise cdist.Error("Command failed: " + " ".join(command)
except subprocess.CalledProcessError: + " with returncode: {} and output: {}".format(
raise cdist.Error("Command failed: " + " ".join(command)) e.returncode, e.output))
except OSError as error: except OSError as error:
raise cdist.Error(" ".join(command) + ": " + error.args[1]) raise cdist.Error(" ".join(command) + ": " + error.args[1])
except UnicodeDecodeError: except UnicodeDecodeError:

39
cdist/exec/util.py Normal file
View file

@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
#
# 2016 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 subprocess
from tempfile import TemporaryFile
import cdist
def call_get_output(command, env=None):
"""Run the given command with the given environment.
Return the stdout and stderr output as a byte string.
"""
assert isinstance(command, (list, tuple)), "list or tuple argument expected, got: {}".format(command)
with TemporaryFile() as fout:
subprocess.check_call(command, env=env,
stdout=fout, stderr=subprocess.STDOUT)
fout.seek(0)
output = fout.read()
return output

View file

@ -133,6 +133,56 @@ class EmulatorTestCase(test.CdistTestCase):
self.assertEqual(list(file_object.requirements), ['__planet/mars']) self.assertEqual(list(file_object.requirements), ['__planet/mars'])
# if we get here all is fine # if we get here all is fine
class EmulatorConflictingRequirementsTestCase(test.CdistTestCase):
def setUp(self):
self.temp_dir = self.mkdtemp()
handle, self.script = self.mkstemp(dir=self.temp_dir)
os.close(handle)
base_path = self.temp_dir
self.local = local.Local(
target_host=self.target_host,
base_path=base_path,
exec_path=test.cdist_exec_path,
add_conf_dirs=[conf_dir])
self.local.create_files_dirs()
self.manifest = core.Manifest(self.target_host, self.local)
self.env = self.manifest.env_initial_manifest(self.script)
self.env['__cdist_object_marker'] = self.local.object_marker_name
def tearDown(self):
shutil.rmtree(self.temp_dir)
def test_object_conflicting_requirements_req_none(self):
argv = ['__directory', 'spam']
emu = emulator.Emulator(argv, env=self.env)
emu.run()
argv = ['__file', 'eggs']
self.env['require'] = '__directory/spam'
emu = emulator.Emulator(argv, env=self.env)
emu.run()
argv = ['__file', 'eggs']
if 'require' in self.env:
del self.env['require']
emu = emulator.Emulator(argv, env=self.env)
self.assertRaises(cdist.Error, emu.run)
def test_object_conflicting_requirements_none_req(self):
argv = ['__directory', 'spam']
emu = emulator.Emulator(argv, env=self.env)
emu.run()
argv = ['__file', 'eggs']
if 'require' in self.env:
del self.env['require']
emu = emulator.Emulator(argv, env=self.env)
emu.run()
argv = ['__file', 'eggs']
self.env['require'] = '__directory/spam'
emu = emulator.Emulator(argv, env=self.env)
self.assertRaises(cdist.Error, emu.run)
class AutoRequireEmulatorTestCase(test.CdistTestCase): class AutoRequireEmulatorTestCase(test.CdistTestCase):
@ -366,3 +416,8 @@ class StdinTestCase(test.CdistTestCase):
stdin_saved_by_emulator = fd.read() stdin_saved_by_emulator = fd.read()
self.assertEqual(random_string, stdin_saved_by_emulator) self.assertEqual(random_string, stdin_saved_by_emulator)
if __name__ == '__main__':
import unittest
unittest.main()

View file

@ -0,0 +1,59 @@
_cdist()
{
local cur prev prevprev opts cmds projects
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
prevprev="${COMP_WORDS[COMP_CWORD-2]}"
opts="-h --help -d --debug -v --verbose -V --version"
cmds="banner shell config"
case "${prevprev}" in
shell)
case "${prev}" in
-s|--shell)
shells=$(grep -v '^#' /etc/shells)
COMPREPLY=( $(compgen -W "${shells}" -- ${cur}) )
return 0
;;
esac
;;
esac
case "${prev}" in
-*)
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
;;
banner)
opts="-h --help -d --debug -v --verbose"
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
;;
shell)
opts="-h --help -d --debug -v --verbose -s --shell"
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
;;
config)
opts="-h --help -d --debug -v --verbose \
-c --conf-dir -f --file -i --initial-manifest -n --dry-run \
-o --out-dir -p --parallel -s --sequential --remote-copy \
--remote-exec"
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
;;
*)
;;
esac
if [[ ${cur} == -* ]]; then
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
fi
COMPREPLY=( $(compgen -W "${cmds}" -- ${cur}) )
return 0
}
complete -F _cdist cdist

48
completions/zsh/_cdist Normal file
View file

@ -0,0 +1,48 @@
#compdef cdist
_cdist()
{
local curcontext="$curcontext" state line
typeset -A opt_args
_arguments \
'1: :->opts_cmds'\
'*: :->opts'
case $state in
opts_cmds)
_arguments '1:Options and commands:(banner config shell -h --help -d --debug -v --verbose -V --version)'
;;
*)
case $words[2] in
-*)
opts=(-h --help -d --debug -v --verbose -V --version)
compadd "$@" -- $opts
;;
banner)
opts=(-h --help -d --debug -v --verbose)
compadd "$@" -- $opts
;;
shell)
case $words[3] in
-s|--shell)
shells=($(grep -v '^#' /etc/shells))
compadd "$@" -- $shells
;;
*)
opts=(-h --help -d --debug -v --verbose -s --shell)
compadd "$@" -- $opts
;;
esac
;;
config)
opts=(-h --help -d --debug -v --verbose -c --conf-dir -f --file -i --initial-manifest -n --dry-run -o --out-dir -p --parallel -s --sequential --remote-copy --remote-exec)
compadd "$@" -- $opts
;;
*)
;;
esac
esac
}
_cdist "$@"

View file

@ -3,6 +3,9 @@ Changelog
next: next:
* Core: Add files directory for static files (Darko Poljak) * Core: Add files directory for static files (Darko Poljak)
* Core: Fix conflicting requirements (Darko Poljak)
* Custom: Add bash and zsh completions (Darko Poljak)
* Core: Improve error reporting for local and remote run command (Darko Poljak)
* New type: __jail_freebsd9: Handle jail management on FreeBSD <= 9.X (Jake Guffey) * New type: __jail_freebsd9: Handle jail management on FreeBSD <= 9.X (Jake Guffey)
* New type: __jail_freebsd10: Handle jail management on FreeBSD >= 10.0 (Jake Guffey) * New type: __jail_freebsd10: Handle jail management on FreeBSD >= 10.0 (Jake Guffey)
* Type __jail: Dynamically select the correct jail subtype based on target host OS (Jake Guffey) * Type __jail: Dynamically select the correct jail subtype based on target host OS (Jake Guffey)

View file

@ -26,8 +26,11 @@ def inspect_ssh_mux_opts(control_path_dir="~/.ssh/"):
import subprocess import subprocess
import os import os
control_path = os.path.join(control_path_dir, # socket is always local to each cdist run, it is created in
"cdist.socket.master-%l-%r@%h:%p") # temp directory that is created at starting cdist, so
# control_path file name can be static, it will always be
# unique due to unique temp directory name
control_path = os.path.join(control_path_dir, "ssh-control-path")
wanted_mux_opts = { wanted_mux_opts = {
"ControlPath": control_path, "ControlPath": control_path,
"ControlMaster": "auto", "ControlMaster": "auto",
@ -147,7 +150,7 @@ def commandline():
# didn't specify command line options nor env vars: # didn't specify command line options nor env vars:
# inspect multiplexing options for default cdist.REMOTE_COPY/EXEC # inspect multiplexing options for default cdist.REMOTE_COPY/EXEC
if args_dict['remote_copy'] is None or args_dict['remote_exec'] is None: if args_dict['remote_copy'] is None or args_dict['remote_exec'] is None:
control_path_dir = tempfile.mkdtemp() control_path_dir = tempfile.mkdtemp(prefix="cdist")
import atexit import atexit
atexit.register(lambda: shutil.rmtree(control_path_dir)) atexit.register(lambda: shutil.rmtree(control_path_dir))
mux_opts = inspect_ssh_mux_opts(control_path_dir) mux_opts = inspect_ssh_mux_opts(control_path_dir)