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'        ""         "" |    "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""" | ||||||
|  |  | ||||||
|  | @ -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. | ||||||
|  |  | ||||||
|  | @ -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: | ||||||
|  |  | ||||||
|  | @ -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
									
								
							
							
						
						
									
										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']) |         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() | ||||||
|  |  | ||||||
							
								
								
									
										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: | 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) | ||||||
|  |  | ||||||
|  | @ -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) | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue