From dc9092dbef79ef1b4afbd5989e61ccb912d6f6be Mon Sep 17 00:00:00 2001 From: Steven Armstrong Date: Tue, 11 Oct 2011 16:56:59 +0200 Subject: [PATCH 01/14] --os.environ Signed-off-by: Steven Armstrong --- lib/cdist/exec.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/cdist/exec.py b/lib/cdist/exec.py index 13cec499..a2282347 100644 --- a/lib/cdist/exec.py +++ b/lib/cdist/exec.py @@ -20,7 +20,6 @@ # import logging -import os import subprocess import cdist @@ -44,7 +43,7 @@ class Wrapper(object): def transfer_path(self, source, destination): """Transfer directory and previously delete the remote destination""" self.remove_remote_path(destination) - self.run_or_fail(os.environ['__remote_copy'].split() + + self.run_or_fail(self.remote_copy.split() + ["-r", source, self.target_host + ":" + destination]) def shell_run_or_debug_fail(self, script, *args, remote=False, **kargs): From 7da3a3c30552db6359abc5771a3260bceecdcb1a Mon Sep 17 00:00:00 2001 From: Steven Armstrong Date: Wed, 12 Oct 2011 14:30:10 +0200 Subject: [PATCH 02/14] remote code execution and tests Signed-off-by: Steven Armstrong --- lib/cdist/exec/__init__.py | 0 lib/cdist/exec/remote.py | 132 +++++++++++++++++++++++++++++ lib/cdist/test/exec/__init__.py | 0 lib/cdist/test/exec/test_remote.py | 111 ++++++++++++++++++++++++ 4 files changed, 243 insertions(+) create mode 100644 lib/cdist/exec/__init__.py create mode 100644 lib/cdist/exec/remote.py create mode 100644 lib/cdist/test/exec/__init__.py create mode 100644 lib/cdist/test/exec/test_remote.py diff --git a/lib/cdist/exec/__init__.py b/lib/cdist/exec/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lib/cdist/exec/remote.py b/lib/cdist/exec/remote.py new file mode 100644 index 00000000..a5ad0845 --- /dev/null +++ b/lib/cdist/exec/remote.py @@ -0,0 +1,132 @@ +# -*- coding: utf-8 -*- +# +# 2011 Steven Armstrong (steven-cdist at armstrong.cc) +# 2011 Nico Schottelius (nico-cdist at schottelius.org) +# +# 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 . +# +# + +import io +import os +import sys +import subprocess +import logging + +import cdist + + +class RemoteScriptError(cdist.Error): + def __init__(self, script, command, script_content): + self.script = script + self.command = command + self.script_content = script_content + + def __str__(self): + return "Remote script execution failed: %s %s" % (self.script, self.command) + + +class Remote(object): + """Execute commands remotely. + + All interaction with the remote side should be done through this class. + Directly accessing the remote side from python code is a bug. + + """ + def __init__(self, target_host, remote_base_path, remote_exec, remote_copy): + self.target_host = target_host + self.base_path = remote_base_path + self._exec = remote_exec + self._copy = remote_copy + + self.conf_path = os.path.join(self.base_path, "conf") + self.object_path = os.path.join(self.base_path, "object") + + self.type_path = os.path.join(self.conf_path, "type") + self.global_explorer_path = os.path.join(self.conf_path, "explorer") + + self.log = logging.getLogger(self.target_host) + + def create_directories(self): + self.rmdir(self.base_path) + self.mkdir(self.base_path) + self.mkdir(self.conf_path) + + def rmdir(self, path): + """Remove directory on the remote side.""" + self.log.debug("Remote rmdir: %s", path) + self.run(["rm", "-rf", path]) + + def mkdir(self, path): + """Create directory on the remote side.""" + self.log.debug("Remote mkdir: %s", path) + self.run(["mkdir", "-p", path]) + + def transfer(self, source, destination): + """Transfer a file or directory to the remote side.""" + self.log.debug("Remote transfer: %s -> %s", source, destination) + self.rmdir(destination) + command = self._copy.split() + command.extend(["-r", source, self.target_host + ":" + destination]) + self.run_command(command) + + def run(self, command, env=None): + """Run the given command with the given environment on the remote side. + Return the output as a string. + + """ + # prefix given command with remote_exec + cmd = self._exec.split() + cmd.append(self.target_host) + cmd.extend(command) + return self.run_command(cmd, env=None) + + def run_command(self, command, env=None): + """Run the given command with the given environment. + Return the output as a string. + + """ + assert isinstance(command, (list, tuple)), "list or tuple argument expected, got: %s" % command + self.log.debug("Remote run: %s", command) + try: + return subprocess.check_output(command, env=env) + except subprocess.CalledProcessError: + raise cdist.Error("Command failed: " + " ".join(command)) + except OSError as error: + raise cdist.Error(" ".join(*args) + ": " + error.args[1]) + + def run_script(self, script, env=None): + """Run the given script with the given environment on the remote side. + Return the output as a string. + + """ + command = self._exec.split() + command.append(self.target_host) + command.extend(["/bin/sh", "-e"]) + command.append(script) + + self.log.debug("Remote run script: %s", command) + if env: + self.log.debug("Remote run script env: %s", env) + + try: + return subprocess.check_output(command, env=env) + except subprocess.CalledProcessError as error: + script_content = self.run(["cat", script]) + self.log.error("Code that raised the error:\n%s", script_content) + raise RemoteScriptError(script, command, script_content) + except EnvironmentError as error: + raise cdist.Error(" ".join(command) + ": " + error.args[1]) diff --git a/lib/cdist/test/exec/__init__.py b/lib/cdist/test/exec/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lib/cdist/test/exec/test_remote.py b/lib/cdist/test/exec/test_remote.py new file mode 100644 index 00000000..ae5e7aed --- /dev/null +++ b/lib/cdist/test/exec/test_remote.py @@ -0,0 +1,111 @@ +# -*- coding: utf-8 -*- +# +# 2010-2011 Steven Armstrong (steven-cdist at armstrong.cc) +# +# 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 . +# +# + +import unittest +import os +import tempfile +import getpass +import shutil +import string +import random + +import cdist +from cdist.exec import remote + + +class RemoteTestCase(unittest.TestCase): + + def mkdtemp(self, **kwargs): + return tempfile.mkdtemp(prefix='tmp.cdist.test.', **kwargs) + + def mkstemp(self, **kwargs): + return tempfile.mkstemp(prefix='tmp.cdist.test.', **kwargs) + + def setUp(self): + self.temp_dir = self.mkdtemp() + target_host = 'localhost' + remote_base_path = self.temp_dir + user = getpass.getuser() + remote_exec = "ssh -o User=%s -q" % user + remote_copy = "scp -o User=%s -q" % user + self.remote = remote.Remote(target_host, remote_base_path, remote_exec, remote_copy) + + def tearDown(self): + shutil.rmtree(self.temp_dir) + + def test_run_success(self): + self.remote.run(['/bin/true']) + + def test_run_fail(self): + self.assertRaises(cdist.Error, self.remote.run, ['/bin/false']) + + def test_run_script_success(self): + handle, script = self.mkstemp(dir=self.temp_dir) + fd = open(script, "w") + fd.writelines(["#!/bin/sh\n", "/bin/true"]) + fd.close() + self.remote.run_script(script) + + def test_run_script_fail(self): + handle, script = self.mkstemp(dir=self.temp_dir) + fd = open(script, "w") + fd.writelines(["#!/bin/sh\n", "/bin/false"]) + fd.close() + self.assertRaises(remote.RemoteScriptError, self.remote.run_script, script) + + def test_run_script_get_output(self): + handle, script = self.mkstemp(dir=self.temp_dir) + fd = open(script, "w") + fd.writelines(["#!/bin/sh\n", "echo foobar"]) + fd.close() + self.assertEqual(self.remote.run_script(script), b"foobar\n") + + def test_mkdir(self): + temp_dir = self.mkdtemp(dir=self.temp_dir) + os.rmdir(temp_dir) + self.remote.mkdir(temp_dir) + self.assertTrue(os.path.isdir(temp_dir)) + + def test_rmdir(self): + temp_dir = self.mkdtemp(dir=self.temp_dir) + self.remote.rmdir(temp_dir) + self.assertFalse(os.path.isdir(temp_dir)) + + def test_transfer_file(self): + handle, source = self.mkstemp(dir=self.temp_dir) + target = self.mkdtemp(dir=self.temp_dir) + self.remote.transfer(source, target) + self.assertTrue(os.path.isfile(target)) + + def test_transfer_dir(self): + source = self.mkdtemp(dir=self.temp_dir) + # put a file in the directory as payload + handle, source_file = self.mkstemp(dir=source) + source_file_name = os.path.split(source_file)[-1] + target = self.mkdtemp(dir=self.temp_dir) + self.remote.transfer(source, target) + # test if the payload file is in the target directory + self.assertTrue(os.path.isfile(os.path.join(target, source_file_name))) + + def test_create_directories(self): + self.remote.create_directories() + self.assertTrue(os.path.isdir(self.remote.base_path)) + self.assertTrue(os.path.isdir(self.remote.conf_path)) From 84e044407e036aa7747a82a0af5f4556f5d69163 Mon Sep 17 00:00:00 2001 From: Steven Armstrong Date: Wed, 12 Oct 2011 15:16:33 +0200 Subject: [PATCH 03/14] +FIXME Signed-off-by: Steven Armstrong --- lib/cdist/exec/remote.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/cdist/exec/remote.py b/lib/cdist/exec/remote.py index a5ad0845..6ec9c46f 100644 --- a/lib/cdist/exec/remote.py +++ b/lib/cdist/exec/remote.py @@ -20,6 +20,8 @@ # # +# FIXME: common base class with Local? + import io import os import sys From 985ed2669a7aae0acd6e6bd6f7b136251a96d328 Mon Sep 17 00:00:00 2001 From: Steven Armstrong Date: Wed, 12 Oct 2011 15:17:06 +0200 Subject: [PATCH 04/14] local code execution and tests Signed-off-by: Steven Armstrong --- lib/cdist/exec/local.py | 118 ++++++++++++++++++++++++++++++ lib/cdist/test/exec/test_local.py | 102 ++++++++++++++++++++++++++ 2 files changed, 220 insertions(+) create mode 100644 lib/cdist/exec/local.py create mode 100644 lib/cdist/test/exec/test_local.py diff --git a/lib/cdist/exec/local.py b/lib/cdist/exec/local.py new file mode 100644 index 00000000..b9c511f4 --- /dev/null +++ b/lib/cdist/exec/local.py @@ -0,0 +1,118 @@ +# -*- coding: utf-8 -*- +# +# 2011 Steven Armstrong (steven-cdist at armstrong.cc) +# 2011 Nico Schottelius (nico-cdist at schottelius.org) +# +# 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 . +# +# + +import io +import os +import sys +import subprocess +import shutil +import logging + +import cdist + + +class LocalScriptError(cdist.Error): + def __init__(self, script, command, script_content): + self.script = script + self.command = command + self.script_content = script_content + + def __str__(self): + return "Remote script execution failed: %s %s" % (self.script, self.command) + + +class Local(object): + """Execute commands locally. + + All interaction with the local side should be done through this class. + Directly accessing the local side from python code is a bug. + + """ + def __init__(self, target_host, local_base_path, out_path): + self.target_host = target_host + self.base_path = local_base_path + + # Local input + self.cache_path = os.path.join(self.base_path, "cache") + self.conf_path = os.path.join(self.base_path, "conf") + self.global_explorer_path = os.path.join(self.conf_path, "explorer") + self.manifest_path = os.path.join(self.conf_path, "manifest") + self.type_base_path = os.path.join(self.conf_path, "type") + # FIXME: should not be needed anywhere + self.lib_path = os.path.join(self.base_path, "lib") + + # Local output + self.out_path = out_path + self.bin_path = os.path.join(self.out_path, "bin") + self.global_explorer_out_path = os.path.join(self.out_path, "explorer") + self.object_base_path = os.path.join(self.out_path, "object") + + self.log = logging.getLogger(self.target_host) + + def create_directories(self): + self.mkdir(self.out_path) + self.mkdir(self.bin_path) + + def rmdir(self, path): + """Remove directory on the local side.""" + self.log.debug("Local rmdir: %s", path) + shutil.rmtree(path) + + def mkdir(self, path): + """Create directory on the local side.""" + self.log.debug("Local mkdir: %s", path) + os.makedirs(path, mode=0o700, exist_ok=True) + + def run(self, command, env=None): + """Run the given command with the given environment. + Return the output as a string. + + """ + assert isinstance(command, (list, tuple)), "list or tuple argument expected, got: %s" % command + self.log.debug("Local run: %s", command) + try: + return subprocess.check_output(command, env=env) + except subprocess.CalledProcessError: + raise cdist.Error("Command failed: " + " ".join(command)) + except OSError as error: + raise cdist.Error(" ".join(*args) + ": " + error.args[1]) + + def run_script(self, script, env=None): + """Run the given script with the given environment. + Return the output as a string. + + """ + command = ["/bin/sh", "-e"] + command.append(script) + + self.log.debug("Local run script: %s", command) + if env: + self.log.debug("Local run script env: %s", env) + + try: + return subprocess.check_output(command, env=env) + except subprocess.CalledProcessError as error: + script_content = self.run(["cat", script]) + self.log.error("Code that raised the error:\n%s", script_content) + raise LocalScriptError(script, command, script_content) + except EnvironmentError as error: + raise cdist.Error(" ".join(command) + ": " + error.args[1]) diff --git a/lib/cdist/test/exec/test_local.py b/lib/cdist/test/exec/test_local.py new file mode 100644 index 00000000..2c021404 --- /dev/null +++ b/lib/cdist/test/exec/test_local.py @@ -0,0 +1,102 @@ +# -*- coding: utf-8 -*- +# +# 2010-2011 Steven Armstrong (steven-cdist at armstrong.cc) +# +# 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 . +# +# + +import unittest +import os +import tempfile +import getpass +import shutil +import string +import random + +#import logging +#logging.basicConfig(level=logging.DEBUG, format='%(levelname)s: %(message)s') + +import cdist +from cdist.exec import local + +import os.path as op +my_dir = op.abspath(op.dirname(__file__)) +fixtures = op.join(my_dir, 'fixtures') +local_base_path = fixtures + + +class LocalTestCase(unittest.TestCase): + + def mkdtemp(self, **kwargs): + return tempfile.mkdtemp(prefix='tmp.cdist.test.', **kwargs) + + def mkstemp(self, **kwargs): + return tempfile.mkstemp(prefix='tmp.cdist.test.', **kwargs) + + def setUp(self): + self.temp_dir = self.mkdtemp() + target_host = 'localhost' + out_path = self.temp_dir + self.local = local.Local(target_host, local_base_path, out_path) + + def tearDown(self): + #shutil.rmtree(self.temp_dir) + pass + + def test_run_success(self): + self.local.run(['/bin/true']) + + def test_run_fail(self): + self.assertRaises(cdist.Error, self.local.run, ['/bin/false']) + + def test_run_script_success(self): + handle, script = self.mkstemp(dir=self.temp_dir) + fd = open(script, "w") + fd.writelines(["#!/bin/sh\n", "/bin/true"]) + fd.close() + self.local.run_script(script) + + def test_run_script_fail(self): + handle, script = self.mkstemp(dir=self.temp_dir) + fd = open(script, "w") + fd.writelines(["#!/bin/sh\n", "/bin/false"]) + fd.close() + self.assertRaises(local.LocalScriptError, self.local.run_script, script) + + def test_run_script_get_output(self): + handle, script = self.mkstemp(dir=self.temp_dir) + fd = open(script, "w") + fd.writelines(["#!/bin/sh\n", "echo foobar"]) + fd.close() + self.assertEqual(self.local.run_script(script), b"foobar\n") + + def test_mkdir(self): + temp_dir = self.mkdtemp(dir=self.temp_dir) + os.rmdir(temp_dir) + self.local.mkdir(temp_dir) + self.assertTrue(os.path.isdir(temp_dir)) + + def test_rmdir(self): + temp_dir = self.mkdtemp(dir=self.temp_dir) + self.local.rmdir(temp_dir) + self.assertFalse(os.path.isdir(temp_dir)) + + def test_create_directories(self): + self.local.create_directories() + print('self.local.bin_path: %s' % self.local.bin_path) + self.assertTrue(os.path.isdir(self.local.out_path)) + self.assertTrue(os.path.isdir(self.local.bin_path)) From 424c06093415d06a20a5a3c2b47fb09d5a0fef6e Mon Sep 17 00:00:00 2001 From: Steven Armstrong Date: Wed, 12 Oct 2011 15:21:25 +0200 Subject: [PATCH 05/14] +FIXME Signed-off-by: Steven Armstrong --- lib/cdist/exec/local.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/cdist/exec/local.py b/lib/cdist/exec/local.py index b9c511f4..18454b8f 100644 --- a/lib/cdist/exec/local.py +++ b/lib/cdist/exec/local.py @@ -20,6 +20,8 @@ # # +# FIXME: common base class with Remote? + import io import os import sys From 117ccf94d311d618b08932c2c485af41151a889a Mon Sep 17 00:00:00 2001 From: Steven Armstrong Date: Wed, 12 Oct 2011 16:13:22 +0200 Subject: [PATCH 06/14] --debug Signed-off-by: Steven Armstrong --- lib/cdist/test/exec/test_local.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/cdist/test/exec/test_local.py b/lib/cdist/test/exec/test_local.py index 2c021404..40611a0b 100644 --- a/lib/cdist/test/exec/test_local.py +++ b/lib/cdist/test/exec/test_local.py @@ -54,8 +54,7 @@ class LocalTestCase(unittest.TestCase): self.local = local.Local(target_host, local_base_path, out_path) def tearDown(self): - #shutil.rmtree(self.temp_dir) - pass + shutil.rmtree(self.temp_dir) def test_run_success(self): self.local.run(['/bin/true']) From 5f358a5ef124bacbed18ab046b8ea4c3ee22d274 Mon Sep 17 00:00:00 2001 From: Steven Armstrong Date: Wed, 12 Oct 2011 16:46:54 +0200 Subject: [PATCH 07/14] move link_emulator to local Signed-off-by: Steven Armstrong --- lib/cdist/exec/local.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/cdist/exec/local.py b/lib/cdist/exec/local.py index 18454b8f..aec65e68 100644 --- a/lib/cdist/exec/local.py +++ b/lib/cdist/exec/local.py @@ -30,6 +30,7 @@ import shutil import logging import cdist +from cdist import core class LocalScriptError(cdist.Error): @@ -118,3 +119,13 @@ class Local(object): raise LocalScriptError(script, command, script_content) except EnvironmentError as error: raise cdist.Error(" ".join(command) + ": " + error.args[1]) + + def link_emulator(self, exec_path): + """Link emulator to types""" + src = os.path.abspath(exec_path) + for cdist_type in core.Type.list_types(self.type_base_path): + dst = os.path.join(self.bin_path, cdist_type.name) + self.log.debug("Linking emulator: %s to %s", src, dst) + + # FIXME: handle exceptions + os.symlink(src, dst) From d2878e931ed572820da507b17761a03ad82a97ed Mon Sep 17 00:00:00 2001 From: Steven Armstrong Date: Wed, 12 Oct 2011 16:50:21 +0200 Subject: [PATCH 08/14] /Remote/Local/ Signed-off-by: Steven Armstrong --- lib/cdist/exec/local.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cdist/exec/local.py b/lib/cdist/exec/local.py index aec65e68..b346bf7d 100644 --- a/lib/cdist/exec/local.py +++ b/lib/cdist/exec/local.py @@ -40,7 +40,7 @@ class LocalScriptError(cdist.Error): self.script_content = script_content def __str__(self): - return "Remote script execution failed: %s %s" % (self.script, self.command) + return "Local script execution failed: %s %s" % (self.script, self.command) class Local(object): From 78fd611bb02faa5d552bf4efbf94f7ed6f0e67bb Mon Sep 17 00:00:00 2001 From: Steven Armstrong Date: Wed, 12 Oct 2011 16:56:16 +0200 Subject: [PATCH 09/14] fix cdist_base_path Signed-off-by: Steven Armstrong --- lib/cdist/test/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cdist/test/__init__.py b/lib/cdist/test/__init__.py index 06c87917..2f88bf7e 100644 --- a/lib/cdist/test/__init__.py +++ b/lib/cdist/test/__init__.py @@ -28,7 +28,7 @@ import unittest cdist_commands=["banner", "config", "install"] cdist_base_path = os.path.abspath( - os.path.join(os.path.dirname(os.path.realpath(__file__)), "../../")) + os.path.join(os.path.dirname(os.path.realpath(__file__)), "../../../")) cdist_exec_path = os.path.join(cdist_base_path, "bin/cdist") From a254e1f31e5f34cc4944de922131688a692c0fd5 Mon Sep 17 00:00:00 2001 From: Steven Armstrong Date: Wed, 12 Oct 2011 17:12:22 +0200 Subject: [PATCH 10/14] return output of command execution as string instead of bytestring Signed-off-by: Steven Armstrong --- lib/cdist/exec/local.py | 4 ++-- lib/cdist/exec/remote.py | 4 ++-- lib/cdist/test/exec/test_local.py | 2 +- lib/cdist/test/exec/test_remote.py | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/cdist/exec/local.py b/lib/cdist/exec/local.py index b346bf7d..521d56c3 100644 --- a/lib/cdist/exec/local.py +++ b/lib/cdist/exec/local.py @@ -93,7 +93,7 @@ class Local(object): assert isinstance(command, (list, tuple)), "list or tuple argument expected, got: %s" % command self.log.debug("Local run: %s", command) try: - return subprocess.check_output(command, env=env) + return subprocess.check_output(command, env=env).decode() except subprocess.CalledProcessError: raise cdist.Error("Command failed: " + " ".join(command)) except OSError as error: @@ -112,7 +112,7 @@ class Local(object): self.log.debug("Local run script env: %s", env) try: - return subprocess.check_output(command, env=env) + return subprocess.check_output(command, env=env).decode() except subprocess.CalledProcessError as error: script_content = self.run(["cat", script]) self.log.error("Code that raised the error:\n%s", script_content) diff --git a/lib/cdist/exec/remote.py b/lib/cdist/exec/remote.py index 6ec9c46f..7821e993 100644 --- a/lib/cdist/exec/remote.py +++ b/lib/cdist/exec/remote.py @@ -104,7 +104,7 @@ class Remote(object): assert isinstance(command, (list, tuple)), "list or tuple argument expected, got: %s" % command self.log.debug("Remote run: %s", command) try: - return subprocess.check_output(command, env=env) + return subprocess.check_output(command, env=env).decode() except subprocess.CalledProcessError: raise cdist.Error("Command failed: " + " ".join(command)) except OSError as error: @@ -125,7 +125,7 @@ class Remote(object): self.log.debug("Remote run script env: %s", env) try: - return subprocess.check_output(command, env=env) + return subprocess.check_output(command, env=env).decode() except subprocess.CalledProcessError as error: script_content = self.run(["cat", script]) self.log.error("Code that raised the error:\n%s", script_content) diff --git a/lib/cdist/test/exec/test_local.py b/lib/cdist/test/exec/test_local.py index 40611a0b..b74f412e 100644 --- a/lib/cdist/test/exec/test_local.py +++ b/lib/cdist/test/exec/test_local.py @@ -81,7 +81,7 @@ class LocalTestCase(unittest.TestCase): fd = open(script, "w") fd.writelines(["#!/bin/sh\n", "echo foobar"]) fd.close() - self.assertEqual(self.local.run_script(script), b"foobar\n") + self.assertEqual(self.local.run_script(script), "foobar\n") def test_mkdir(self): temp_dir = self.mkdtemp(dir=self.temp_dir) diff --git a/lib/cdist/test/exec/test_remote.py b/lib/cdist/test/exec/test_remote.py index ae5e7aed..1fdb5833 100644 --- a/lib/cdist/test/exec/test_remote.py +++ b/lib/cdist/test/exec/test_remote.py @@ -76,7 +76,7 @@ class RemoteTestCase(unittest.TestCase): fd = open(script, "w") fd.writelines(["#!/bin/sh\n", "echo foobar"]) fd.close() - self.assertEqual(self.remote.run_script(script), b"foobar\n") + self.assertEqual(self.remote.run_script(script), "foobar\n") def test_mkdir(self): temp_dir = self.mkdtemp(dir=self.temp_dir) From 5df8479c5a6e69a85c59a923a435003c060cba4b Mon Sep 17 00:00:00 2001 From: Steven Armstrong Date: Wed, 12 Oct 2011 17:20:47 +0200 Subject: [PATCH 11/14] implement Manifest and tests Signed-off-by: Steven Armstrong --- lib/cdist/core/manifest.py | 94 +++++++++++++++++++ lib/cdist/test/manifest/__init__.py | 87 +++++++++++++++++ .../fixtures/conf/manifest/dump_environment | 7 ++ .../test/manifest/fixtures/conf/manifest/init | 4 + .../manifest/fixtures/conf/type/__moon/.keep | 0 .../fixtures/conf/type/__moon/manifest | 8 ++ .../conf/type/__moon/parameter/optional | 1 + .../conf/type/__moon/parameter/required | 1 + .../fixtures/conf/type/__planet/.keep | 0 .../fixtures/conf/type/__planet/manifest | 8 ++ .../conf/type/__planet/parameter/optional | 1 + 11 files changed, 211 insertions(+) create mode 100644 lib/cdist/core/manifest.py create mode 100644 lib/cdist/test/manifest/__init__.py create mode 100755 lib/cdist/test/manifest/fixtures/conf/manifest/dump_environment create mode 100755 lib/cdist/test/manifest/fixtures/conf/manifest/init create mode 100644 lib/cdist/test/manifest/fixtures/conf/type/__moon/.keep create mode 100755 lib/cdist/test/manifest/fixtures/conf/type/__moon/manifest create mode 100644 lib/cdist/test/manifest/fixtures/conf/type/__moon/parameter/optional create mode 100644 lib/cdist/test/manifest/fixtures/conf/type/__moon/parameter/required create mode 100644 lib/cdist/test/manifest/fixtures/conf/type/__planet/.keep create mode 100755 lib/cdist/test/manifest/fixtures/conf/type/__planet/manifest create mode 100644 lib/cdist/test/manifest/fixtures/conf/type/__planet/parameter/optional diff --git a/lib/cdist/core/manifest.py b/lib/cdist/core/manifest.py new file mode 100644 index 00000000..8f9fc7ae --- /dev/null +++ b/lib/cdist/core/manifest.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- +# +# 2011 Steven Armstrong (steven-cdist at armstrong.cc) +# 2011 Nico Schottelius (nico-cdist at schottelius.org) +# +# 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 . +# +# + +import logging +import os + +import cdist + +log = logging.getLogger(__name__) + + +''' +common: + runs only locally, does not need remote + + env: + PATH: prepend directory with type emulator symlinks == local.bin_path + __target_host: the target host we are working on + __global: full qualified path to the global output dir == local.out_path + __cdist_manifest: full qualified path of the manifest == script + __cdist_type_base_path: full qualified path to the directory where types are defined for use in type emulator + == local.type_base_path + +initial manifest is: + script: full qualified path to the initial manifest + + env: + __manifest: path to .../conf/manifest/ == local.manifest_path + + creates: new objects through type emulator + +type manifeste is: + script: full qualified path to the type manifest + + env: + __object: full qualified path to the object's dir + __object_id: the objects id + __object_fq: full qualified object id, iow: $type.name + / + object_id + __type: full qualified path to the type's dir + + creates: new objects through type emulator +''' + + +class Manifest(object): + """Represents a cdist manifest. + + """ + def __init__(self, target_host, local): + self.target_host = target_host + self.local = local + self.env = { + 'PATH': "%s:%s" % (self.local.bin_path, os.environ['PATH']), + '__target_host': self.target_host, + '__global': self.local.out_path, + '__cdist_type_base_path': self.local.type_base_path, # for use in type emulator + } + + def run_initial_manifest(self, script): + env = os.environ.copy() + env.update(self.env) + env['__manifest'] = self.local.manifest_path + return self.local.run_script(script, env=env) + + def run_type_manifest(self, cdist_object): + env = os.environ.copy() + env.update(self.env) + env.update({ + '__object': cdist_object.absolute_path, + '__object_id': cdist_object.object_id, + '__object_fq': cdist_object.path, + '__type': cdist_object.type.absolute_path, + }) + script = os.path.join(self.local.type_base_path, cdist_object.type.manifest_path) + return self.local.run_script(script, env=env) diff --git a/lib/cdist/test/manifest/__init__.py b/lib/cdist/test/manifest/__init__.py new file mode 100644 index 00000000..8fcb5d79 --- /dev/null +++ b/lib/cdist/test/manifest/__init__.py @@ -0,0 +1,87 @@ +# -*- coding: utf-8 -*- +# +# 2010-2011 Steven Armstrong (steven-cdist at armstrong.cc) +# +# 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 . +# +# + +import unittest +import os +import tempfile +import getpass +import shutil +import string +import random + +#import logging +#logging.basicConfig(level=logging.DEBUG, format='%(levelname)s: %(message)s') + +import cdist +from cdist.exec import local +from cdist import core +from cdist.core import manifest + +import os.path as op +my_dir = op.abspath(op.dirname(__file__)) +fixtures = op.join(my_dir, 'fixtures') +local_base_path = fixtures + + +class ManifestTestCase(unittest.TestCase): + + def mkdtemp(self, **kwargs): + return tempfile.mkdtemp(prefix='tmp.cdist.test.', **kwargs) + + def mkstemp(self, **kwargs): + return tempfile.mkstemp(prefix='tmp.cdist.test.', **kwargs) + + def setUp(self): + self.temp_dir = self.mkdtemp() + target_host = 'localhost' + out_path = self.temp_dir + self.local = local.Local(target_host, local_base_path, out_path) + self.local.create_directories() + self.local.link_emulator(cdist.test.cdist_exec_path) + self.manifest = manifest.Manifest(target_host, self.local) + + def tearDown(self): + #shutil.rmtree(self.temp_dir) + pass + + def test_initial_manifest_environment(self): + #initial_manifest = os.path.join(self.local.manifest_path, "init") + initial_manifest = os.path.join(self.local.manifest_path, "dump_environment") + output_string = self.manifest.run_initial_manifest(initial_manifest) + output_dict = {} + for line in output_string.split('\n'): + if line: + key,value = line.split(': ') + output_dict[key] = value + self.assertTrue(output_dict['PATH'].startswith(self.local.bin_path)) + self.assertEqual(output_dict['__target_host'], self.local.target_host) + self.assertEqual(output_dict['__global'], self.local.out_path) + self.assertEqual(output_dict['__cdist_type_base_path'], self.local.type_base_path) + self.assertEqual(output_dict['__manifest'], self.local.manifest_path) + + +# for line in output.split('\n'): +# print(line) + +# def test_type_manifest(self): +# cdist_type = core.Type(self.local.type_base_path, '__moon') +# cdist_object = core.Object(cdist_type, self.local.object_base_path, 'Saturn') +# self.manifest.run_type_manifest(cdist_object) diff --git a/lib/cdist/test/manifest/fixtures/conf/manifest/dump_environment b/lib/cdist/test/manifest/fixtures/conf/manifest/dump_environment new file mode 100755 index 00000000..1abe7755 --- /dev/null +++ b/lib/cdist/test/manifest/fixtures/conf/manifest/dump_environment @@ -0,0 +1,7 @@ +#!/bin/sh + +echo "PATH: $PATH" +echo "__target_host: $__target_host" +echo "__global: $__global" +echo "__cdist_type_base_path: $__cdist_type_base_path" +echo "__manifest: $__manifest" diff --git a/lib/cdist/test/manifest/fixtures/conf/manifest/init b/lib/cdist/test/manifest/fixtures/conf/manifest/init new file mode 100755 index 00000000..0bdb391a --- /dev/null +++ b/lib/cdist/test/manifest/fixtures/conf/manifest/init @@ -0,0 +1,4 @@ +#!/bin/sh + +__planet Saturn +__moon Prometheus --planet Saturn diff --git a/lib/cdist/test/manifest/fixtures/conf/type/__moon/.keep b/lib/cdist/test/manifest/fixtures/conf/type/__moon/.keep new file mode 100644 index 00000000..e69de29b diff --git a/lib/cdist/test/manifest/fixtures/conf/type/__moon/manifest b/lib/cdist/test/manifest/fixtures/conf/type/__moon/manifest new file mode 100755 index 00000000..362be5a1 --- /dev/null +++ b/lib/cdist/test/manifest/fixtures/conf/type/__moon/manifest @@ -0,0 +1,8 @@ +#!/bin/sh + +if [ -f "$__object/parameter/name" ]; then + name="(cat "$__object/parameter/name")" +else + name="$__object_id" + echo "$name" > "$__object/parameter/name" +fi diff --git a/lib/cdist/test/manifest/fixtures/conf/type/__moon/parameter/optional b/lib/cdist/test/manifest/fixtures/conf/type/__moon/parameter/optional new file mode 100644 index 00000000..f121bdbf --- /dev/null +++ b/lib/cdist/test/manifest/fixtures/conf/type/__moon/parameter/optional @@ -0,0 +1 @@ +name diff --git a/lib/cdist/test/manifest/fixtures/conf/type/__moon/parameter/required b/lib/cdist/test/manifest/fixtures/conf/type/__moon/parameter/required new file mode 100644 index 00000000..729a5167 --- /dev/null +++ b/lib/cdist/test/manifest/fixtures/conf/type/__moon/parameter/required @@ -0,0 +1 @@ +planet diff --git a/lib/cdist/test/manifest/fixtures/conf/type/__planet/.keep b/lib/cdist/test/manifest/fixtures/conf/type/__planet/.keep new file mode 100644 index 00000000..e69de29b diff --git a/lib/cdist/test/manifest/fixtures/conf/type/__planet/manifest b/lib/cdist/test/manifest/fixtures/conf/type/__planet/manifest new file mode 100755 index 00000000..362be5a1 --- /dev/null +++ b/lib/cdist/test/manifest/fixtures/conf/type/__planet/manifest @@ -0,0 +1,8 @@ +#!/bin/sh + +if [ -f "$__object/parameter/name" ]; then + name="(cat "$__object/parameter/name")" +else + name="$__object_id" + echo "$name" > "$__object/parameter/name" +fi diff --git a/lib/cdist/test/manifest/fixtures/conf/type/__planet/parameter/optional b/lib/cdist/test/manifest/fixtures/conf/type/__planet/parameter/optional new file mode 100644 index 00000000..f121bdbf --- /dev/null +++ b/lib/cdist/test/manifest/fixtures/conf/type/__planet/parameter/optional @@ -0,0 +1 @@ +name From 9a33bd3b909a73736abef11c3f3846f390319c9c Mon Sep 17 00:00:00 2001 From: Steven Armstrong Date: Wed, 12 Oct 2011 17:56:45 +0200 Subject: [PATCH 12/14] test type manifest environment Signed-off-by: Steven Armstrong --- lib/cdist/test/manifest/__init__.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/lib/cdist/test/manifest/__init__.py b/lib/cdist/test/manifest/__init__.py index 8fcb5d79..01d3696a 100644 --- a/lib/cdist/test/manifest/__init__.py +++ b/lib/cdist/test/manifest/__init__.py @@ -81,7 +81,21 @@ class ManifestTestCase(unittest.TestCase): # for line in output.split('\n'): # print(line) -# def test_type_manifest(self): -# cdist_type = core.Type(self.local.type_base_path, '__moon') -# cdist_object = core.Object(cdist_type, self.local.object_base_path, 'Saturn') -# self.manifest.run_type_manifest(cdist_object) + def test_type_manifest_environment(self): + cdist_type = core.Type(self.local.type_base_path, '__dump_environment') + cdist_object = core.Object(cdist_type, self.local.object_base_path, 'whatever') + + output_string = self.manifest.run_type_manifest(cdist_object) + output_dict = {} + for line in output_string.split('\n'): + if line: + key,value = line.split(': ') + output_dict[key] = value + self.assertTrue(output_dict['PATH'].startswith(self.local.bin_path)) + self.assertEqual(output_dict['__target_host'], self.local.target_host) + self.assertEqual(output_dict['__global'], self.local.out_path) + self.assertEqual(output_dict['__cdist_type_base_path'], self.local.type_base_path) + self.assertEqual(output_dict['__type'], cdist_type.absolute_path) + self.assertEqual(output_dict['__object'], cdist_object.absolute_path) + self.assertEqual(output_dict['__object_id'], cdist_object.object_id) + self.assertEqual(output_dict['__object_fq'], cdist_object.path) From 1c38fb492e0989ba307b583fe5a5f35b3e6d5f8a Mon Sep 17 00:00:00 2001 From: Steven Armstrong Date: Wed, 12 Oct 2011 17:57:06 +0200 Subject: [PATCH 13/14] fixtures for type manifest environment test Signed-off-by: Steven Armstrong --- .../fixtures/conf/type/__dump_environment/manifest | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100755 lib/cdist/test/manifest/fixtures/conf/type/__dump_environment/manifest diff --git a/lib/cdist/test/manifest/fixtures/conf/type/__dump_environment/manifest b/lib/cdist/test/manifest/fixtures/conf/type/__dump_environment/manifest new file mode 100755 index 00000000..92f533a8 --- /dev/null +++ b/lib/cdist/test/manifest/fixtures/conf/type/__dump_environment/manifest @@ -0,0 +1,10 @@ +#!/bin/sh + +echo "PATH: $PATH" +echo "__target_host: $__target_host" +echo "__global: $__global" +echo "__cdist_type_base_path: $__cdist_type_base_path" +echo "__type: $__type" +echo "__object: $__object" +echo "__object_id: $__object_id" +echo "__object_fq: $__object_fq" From fb80a9555565360e107ddb58e04f6274cd38a2cb Mon Sep 17 00:00:00 2001 From: Steven Armstrong Date: Wed, 12 Oct 2011 17:58:09 +0200 Subject: [PATCH 14/14] --debug Signed-off-by: Steven Armstrong --- lib/cdist/test/manifest/__init__.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/lib/cdist/test/manifest/__init__.py b/lib/cdist/test/manifest/__init__.py index 01d3696a..ffbbff29 100644 --- a/lib/cdist/test/manifest/__init__.py +++ b/lib/cdist/test/manifest/__init__.py @@ -59,11 +59,9 @@ class ManifestTestCase(unittest.TestCase): self.manifest = manifest.Manifest(target_host, self.local) def tearDown(self): - #shutil.rmtree(self.temp_dir) - pass + shutil.rmtree(self.temp_dir) def test_initial_manifest_environment(self): - #initial_manifest = os.path.join(self.local.manifest_path, "init") initial_manifest = os.path.join(self.local.manifest_path, "dump_environment") output_string = self.manifest.run_initial_manifest(initial_manifest) output_dict = {} @@ -76,10 +74,6 @@ class ManifestTestCase(unittest.TestCase): self.assertEqual(output_dict['__global'], self.local.out_path) self.assertEqual(output_dict['__cdist_type_base_path'], self.local.type_base_path) self.assertEqual(output_dict['__manifest'], self.local.manifest_path) - - -# for line in output.split('\n'): -# print(line) def test_type_manifest_environment(self): cdist_type = core.Type(self.local.type_base_path, '__dump_environment')