forked from ungleich-public/cdist
		
	Resolve conflict.
This commit is contained in:
		
				commit
				
					
						a12b74b2f5
					
				
			
		
					 10 changed files with 261 additions and 19 deletions
				
			
		|  | @ -41,8 +41,8 @@ BANNER = """ | |||
|    "P'        ""         "" | ||||
| """ | ||||
| 
 | ||||
| REMOTE_COPY = "scp -o User=root -q" | ||||
| REMOTE_EXEC = "ssh -o User=root -q" | ||||
| REMOTE_COPY = "scp -o User=root" | ||||
| REMOTE_EXEC = "ssh -o User=root" | ||||
| 
 | ||||
| class Error(Exception): | ||||
|     """Base exception class for this project""" | ||||
|  |  | |||
|  | @ -77,6 +77,9 @@ class Emulator(object): | |||
| 
 | ||||
|         self.type_name      = os.path.basename(argv[0]) | ||||
|         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() | ||||
| 
 | ||||
|  | @ -150,10 +153,18 @@ class Emulator(object): | |||
|                 self.parameters[key] = value | ||||
| 
 | ||||
|         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: | ||||
|                 raise cdist.Error("Object %s already exists with conflicting 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) | ||||
|             ) | ||||
|                 errmsg = ("Object %s already exists with conflicting " | ||||
|                     "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: | ||||
|             if self.cdist_object.exists: | ||||
|                 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 | ||||
|                     pass | ||||
| 
 | ||||
| 
 | ||||
|         reqs = set() | ||||
|         if "require" in self.env: | ||||
|             requirements = self.env['require'] | ||||
|             self.log.debug("reqs = " + requirements) | ||||
|  | @ -234,6 +245,24 @@ class Emulator(object): | |||
|                 # (__file//bar => __file/bar) | ||||
|                 # This ensures pattern matching is done against sanitised list | ||||
|                 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): | ||||
|         """An object shall automatically depend on all objects that it defined in it's type manifest. | ||||
|  |  | |||
|  | @ -33,6 +33,7 @@ import tempfile | |||
| import cdist | ||||
| import cdist.message | ||||
| from cdist import core | ||||
| import cdist.exec.util as exec_util | ||||
| 
 | ||||
| CONF_SUBDIRS_LINKED = [ "explorer", "files", "manifest", "type" ] | ||||
| 
 | ||||
|  | @ -206,12 +207,14 @@ class Local(object): | |||
|             env.update(message.env) | ||||
| 
 | ||||
|         try: | ||||
|             output = exec_util.call_get_output(command, env=env) | ||||
|             self.log.debug("Local output: {}".format(output)) | ||||
|             if return_output: | ||||
|                 return subprocess.check_output(command, env=env).decode() | ||||
|             else: | ||||
|                 subprocess.check_call(command, env=env) | ||||
|         except subprocess.CalledProcessError: | ||||
|             raise cdist.Error("Command failed: " + " ".join(command)) | ||||
|                 return output.decode() | ||||
|         except subprocess.CalledProcessError as e: | ||||
|             raise cdist.Error("Command failed: " + " ".join(command) | ||||
|                     + " with returncode: {} and output: {}".format( | ||||
|                         e.returncode, e.output)) | ||||
|         except OSError as error: | ||||
|             raise cdist.Error(" ".join(command) + ": " + error.args[1]) | ||||
|         finally: | ||||
|  |  | |||
|  | @ -26,6 +26,7 @@ import sys | |||
| import glob | ||||
| import subprocess | ||||
| import logging | ||||
| import cdist.exec.util as exec_util | ||||
| 
 | ||||
| import cdist | ||||
| 
 | ||||
|  | @ -167,12 +168,14 @@ class Remote(object): | |||
| 
 | ||||
|         self.log.debug("Remote run: %s", command) | ||||
|         try: | ||||
|             output = exec_util.call_get_output(command, env=os_environ) | ||||
|             self.log.debug("Remote output: {}".format(output)) | ||||
|             if return_output: | ||||
|                 return subprocess.check_output(command, env=os_environ).decode() | ||||
|             else: | ||||
|                 subprocess.check_call(command, env=os_environ) | ||||
|         except subprocess.CalledProcessError: | ||||
|             raise cdist.Error("Command failed: " + " ".join(command)) | ||||
|                 return output.decode() | ||||
|         except subprocess.CalledProcessError as e: | ||||
|             raise cdist.Error("Command failed: " + " ".join(command) | ||||
|                     + " with returncode: {} and output: {}".format( | ||||
|                         e.returncode, e.output)) | ||||
|         except OSError as error: | ||||
|             raise cdist.Error(" ".join(command) + ": " + error.args[1]) | ||||
|         except UnicodeDecodeError: | ||||
|  |  | |||
							
								
								
									
										39
									
								
								cdist/exec/util.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								cdist/exec/util.py
									
										
									
									
									
										Normal 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 | ||||
|  | @ -133,6 +133,56 @@ class EmulatorTestCase(test.CdistTestCase): | |||
|         self.assertEqual(list(file_object.requirements), ['__planet/mars']) | ||||
|         # 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): | ||||
| 
 | ||||
|  | @ -366,3 +416,8 @@ class StdinTestCase(test.CdistTestCase): | |||
|             stdin_saved_by_emulator = fd.read() | ||||
| 
 | ||||
|         self.assertEqual(random_string, stdin_saved_by_emulator) | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == '__main__': | ||||
|     import unittest | ||||
|     unittest.main() | ||||
|  |  | |||
							
								
								
									
										59
									
								
								completions/bash/cdist-completion.bash
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								completions/bash/cdist-completion.bash
									
										
									
									
									
										Normal 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
									
								
							
							
						
						
									
										48
									
								
								completions/zsh/_cdist
									
										
									
									
									
										Normal 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 "$@" | ||||
|  | @ -3,6 +3,9 @@ Changelog | |||
| 
 | ||||
| next: | ||||
| 	* 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_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) | ||||
|  |  | |||
|  | @ -26,8 +26,11 @@ def inspect_ssh_mux_opts(control_path_dir="~/.ssh/"): | |||
|     import subprocess | ||||
|     import os | ||||
| 
 | ||||
|     control_path = os.path.join(control_path_dir, | ||||
|             "cdist.socket.master-%l-%r@%h:%p") | ||||
|     # socket is always local to each cdist run, it is created in | ||||
|     # 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 = { | ||||
|         "ControlPath": control_path, | ||||
|         "ControlMaster": "auto", | ||||
|  | @ -147,7 +150,7 @@ def commandline(): | |||
|         # didn't specify command line options nor env vars: | ||||
|         # inspect multiplexing options for default cdist.REMOTE_COPY/EXEC | ||||
|         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 | ||||
|             atexit.register(lambda: shutil.rmtree(control_path_dir)) | ||||
|             mux_opts = inspect_ssh_mux_opts(control_path_dir) | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue