From 06649d3478fa67220bbc3b24dde478e6e79dc028 Mon Sep 17 00:00:00 2001
From: Steven Armstrong <steven@icarus.ethz.ch>
Date: Mon, 4 Jun 2012 14:11:34 +0200
Subject: [PATCH] new feature: capture and forward stdin to types

Signed-off-by: Steven Armstrong <steven@icarus.ethz.ch>
---
 lib/cdist/emulator.py                         | 24 +++++++++
 lib/cdist/test/emulator/__init__.py           | 52 ++++++++++++++++++-
 .../emulator/fixtures/conf/explorer/.keep     |  0
 .../test/emulator/fixtures/conf/type/__file   |  1 +
 .../conf/type/__file_from_stdin/manifest      |  4 ++
 .../type/__file_from_stdin/parameter/required |  1 +
 6 files changed, 80 insertions(+), 2 deletions(-)
 create mode 100644 lib/cdist/test/emulator/fixtures/conf/explorer/.keep
 create mode 120000 lib/cdist/test/emulator/fixtures/conf/type/__file
 create mode 100755 lib/cdist/test/emulator/fixtures/conf/type/__file_from_stdin/manifest
 create mode 100644 lib/cdist/test/emulator/fixtures/conf/type/__file_from_stdin/parameter/required

diff --git a/lib/cdist/emulator.py b/lib/cdist/emulator.py
index 39d8ca40..5bde16b9 100644
--- a/lib/cdist/emulator.py
+++ b/lib/cdist/emulator.py
@@ -1,6 +1,7 @@
 # -*- coding: utf-8 -*-
 #
 # 2011-2012 Nico Schottelius (nico-cdist at schottelius.org)
+# 2012 Steven Armstrong (steven-cdist at armstrong.cc)
 #
 # This file is part of cdist.
 #
@@ -22,6 +23,7 @@
 import argparse
 import logging
 import os
+import sys
 
 import cdist
 from cdist import core
@@ -67,6 +69,7 @@ class Emulator(object):
 
         self.commandline()
         self.setup_object()
+        self.save_stdin()
         self.record_requirements()
         self.record_auto_requirements()
         self.log.debug("Finished %s %s" % (self.cdist_object.path, self.parameters))
@@ -137,6 +140,27 @@ class Emulator(object):
         # Record / Append source
         self.cdist_object.source.append(self.object_source)
 
+    chunk_size = 8192
+    def _read_stdin(self):
+        return sys.stdin.buffer.read(self.chunk_size)
+    def save_stdin(self):
+        """If something is written to stdin, save it in the object as
+        $__object/stdin so it can be accessed in manifest and gencode-*
+        scripts.
+        """
+        if not sys.stdin.isatty():
+            try:
+                # go directly to file instead of using CdistObject's api
+                # as that does not support streaming
+                path = os.path.join(self.cdist_object.absolute_path, 'stdin')
+                with open(path, 'wb') as fd:
+                    chunk = self._read_stdin()
+                    while chunk:
+                        fd.write(chunk)
+                        chunk = self._read_stdin()
+            except EnvironmentError as e:
+                raise cdist.Error('Failed to read from stdin: %s' % e)
+
     def record_requirements(self):
         """record requirements"""
 
diff --git a/lib/cdist/test/emulator/__init__.py b/lib/cdist/test/emulator/__init__.py
index 077ea111..ff18fe87 100644
--- a/lib/cdist/test/emulator/__init__.py
+++ b/lib/cdist/test/emulator/__init__.py
@@ -21,12 +21,17 @@
 
 import os
 import shutil
+import string
+import filecmp
+import random
 
 import cdist
 from cdist import test
 from cdist.exec import local
 from cdist import emulator
 from cdist import core
+from cdist import config
+import cdist.context
 
 local_base_path = test.cdist_base_path
 
@@ -114,8 +119,7 @@ class AutoRequireEmulatorTestCase(test.CdistTestCase):
         self.manifest = core.Manifest(self.target_host, self.local)
 
     def tearDown(self):
-        pass
-        #shutil.rmtree(self.temp_dir)
+        shutil.rmtree(self.temp_dir)
 
     def test_autorequire(self):
         initial_manifest = os.path.join(self.local.manifest_path, "init")
@@ -216,3 +220,47 @@ class ArgumentsTestCase(test.CdistTestCase):
         self.assertTrue('optional1' in cdist_object.parameters)
         self.assertFalse('optional2' in cdist_object.parameters)
         self.assertEqual(cdist_object.parameters['optional1'], value)
+
+
+class StdinTestCase(test.CdistTestCase):
+
+    def setUp(self):
+        self.orig_environ = os.environ
+        os.environ = os.environ.copy()
+        self.target_host = 'localhost'
+        self.temp_dir = self.mkdtemp()
+        os.environ['__cdist_out_dir'] = self.temp_dir
+        local_base_path = fixtures
+
+        self.context = cdist.context.Context(
+            target_host=self.target_host,
+            remote_copy='scp -o User=root -q',
+            remote_exec='ssh -o User=root -q',
+            base_path=local_base_path,
+            exec_path=test.cdist_exec_path,
+            debug=False)
+        self.config = config.Config(self.context)
+
+    def tearDown(self):
+        os.environ = self.orig_environ
+        shutil.rmtree(self.temp_dir)
+
+    def test_file_from_stdin(self):
+        handle, destination = self.mkstemp(dir=self.temp_dir)
+        os.close(handle)
+        source_handle, source = self.mkstemp(dir=self.temp_dir)
+        candidates = string.ascii_letters+string.digits
+        with os.fdopen(source_handle, 'w') as fd:
+            for x in range(100):
+                fd.write(''.join(random.sample(candidates, len(candidates))))
+
+        handle, initial_manifest = self.mkstemp(dir=self.temp_dir)
+        with os.fdopen(handle, 'w') as fd:
+            fd.write('__file_from_stdin %s --source %s\n' % (destination, source))
+        self.context.initial_manifest = initial_manifest
+        self.config.stage_prepare()
+
+        cdist_type = core.CdistType(self.config.local.type_path, '__file')
+        cdist_object = core.CdistObject(cdist_type, self.config.local.object_path, destination)
+        # Test weither stdin has been stored correctly
+        self.assertTrue(filecmp.cmp(source, os.path.join(cdist_object.absolute_path, 'stdin')))
diff --git a/lib/cdist/test/emulator/fixtures/conf/explorer/.keep b/lib/cdist/test/emulator/fixtures/conf/explorer/.keep
new file mode 100644
index 00000000..e69de29b
diff --git a/lib/cdist/test/emulator/fixtures/conf/type/__file b/lib/cdist/test/emulator/fixtures/conf/type/__file
new file mode 120000
index 00000000..c57c4134
--- /dev/null
+++ b/lib/cdist/test/emulator/fixtures/conf/type/__file
@@ -0,0 +1 @@
+../../../../../../../conf/type/__file
\ No newline at end of file
diff --git a/lib/cdist/test/emulator/fixtures/conf/type/__file_from_stdin/manifest b/lib/cdist/test/emulator/fixtures/conf/type/__file_from_stdin/manifest
new file mode 100755
index 00000000..b4908cbf
--- /dev/null
+++ b/lib/cdist/test/emulator/fixtures/conf/type/__file_from_stdin/manifest
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+source="$(cat "$__object/parameter/source")"
+cat "$source" | __file "/$__object_id" --source /dev/null
diff --git a/lib/cdist/test/emulator/fixtures/conf/type/__file_from_stdin/parameter/required b/lib/cdist/test/emulator/fixtures/conf/type/__file_from_stdin/parameter/required
new file mode 100644
index 00000000..5a18cd2f
--- /dev/null
+++ b/lib/cdist/test/emulator/fixtures/conf/type/__file_from_stdin/parameter/required
@@ -0,0 +1 @@
+source