7

I am trying to std in an xml file. I read the xml file from subversion, updated a line in the file and now I am trying to create a jenkins job by using subporcess.Popen and stdin

test = subprocess.Popen('svn cat http://localhost/svn/WernerTest/JenkinsJobTemplates/trunk/smartTemplate.xml --username admin --password admin', stdout=subprocess.PIPE, universal_newlines=True)
job = test.stdout.read().replace("@url@", "http://localhost/svn/WernerTest/TMS/branches/test1")
output = io.StringIO()
output.write(job)
subprocess.Popen('java -jar D:\\applications\\Jenkins\\war\\WEB-INF\\jenkins-cli.jar\\jenkins-cli.jar -s http://localhost:8080/ create-job test7', stdin=output)

and I am getting the following error:

Traceback (most recent call last):  File "D:\scripts\jenkinsGetJobs.py", line 20, in <module>
subprocess.Popen('java -jar D:\\applications\\Jenkins\\war\\WEB-INF\\jenkins-cli.jar\\jenkins-cli.jar -s http://localhost:8080/ create-job test7', stdin=output)
File "D:\applications\Python 3.5\lib\subprocess.py", line 914, 
in __init__errread, errwrite) = self._get_handles(stdin, stdout, stderr)
File "D:\applications\Python 3.5\lib\subprocess.py", line 1127, in _get_handles
p2cread = msvcrt.get_osfhandle(stdin.fileno())
io.UnsupportedOperation: fileno

So how do I pass in the updated file to the next subprocess?

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Dan
  • 85
  • 1
  • 3

2 Answers2

6

Use a pipe and write the data directly to that pipe:

test = subprocess.Popen(
    'svn cat http://localhost/svn/WernerTest/JenkinsJobTemplates/trunk/smartTemplate.xml --username admin --password admin',
    stdout=subprocess.PIPE, universal_newlines=True)
job = test.stdout.read().replace("@url@", "http://localhost/svn/WernerTest/TMS/branches/test1")

jenkins = subprocess.Popen(
    'java -jar D:\\applications\\Jenkins\\war\\WEB-INF\\jenkins-cli.jar\\jenkins-cli.jar -s http://localhost:8080/ create-job test7',
    stdin=subprocess.PIPE, universal_newlines=True)
jenkins.communicate(job)

The Popen.communicate() method takes the first argument and sends that as stdin to the subprocess.

Note that I set the universal_newlines argument to True for Jenkins as well; the alternative would be for you to explicitly encode the job string to a suitable codec that Jenkins will accept.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • 1
    I am getting the following for an error now: TypeError: Can't convert 'bytes' object to str implicitly – Dan May 11 '15 at 15:34
  • @Dan: oops, I missed your `universal_newlines` argument there; you probably need to use the same on the Jenkins process for it to accept `str` as input, or you'll need to explicitly encode your data manually. – Martijn Pieters May 11 '15 at 15:42
  • By using stdin=subprocess.PIPE, is this call actually getting the correct data from the line test.stdout.read().replace.....? – Dan May 11 '15 at 15:54
  • @Dan: the process will get whatever you pass in as a first argument to the `Popen.communicate()` call. – Martijn Pieters May 11 '15 at 15:55
  • Sorry, thanks. I am getting an unable to access jar error but that should hopefully be something else now. Thanks for the help Martijn! – Dan May 11 '15 at 15:58
3

Popen() accepts only real files (valid .fileno() at least). @Martijn Pieters♦' answer shows how pass the data if you can load it all at once in memory (also, jenkins process is not started until svn produces all output).

Here's how to read one line at a time (svn and jenkins processes run in parallel):

#!/usr/bine/env python3
from subprocess import Popen, PIPE

with Popen(svn_cmd, stdout=PIPE, bufsize=1, universal_newlines=True) as svn, \
     Popen(java_cmd, stdin=PIPE, bufsize=1, universal_newlines=True) as java:
    for line in svn.stdout:
        line = line.replace('@url@', 'http://localhost/svn/WernerTest/TMS/branches/test1')
        java.stdin.write(line)
if java.returncode != 0:
   "handle error"

see svn_cmd, java_cmd definitions below (you don't need shlex.split(cmd) on Windows -- note: no shell=True).


If you didn't need to replace @url@ then It would look like you are trying to emulate: svn_cmd | java_cmd pipeline, where:

svn_cmd = 'svn cat http://localhost/svn/WernerTest/JenkinsJobTemplates/trunk/smartTemplate.xml --username admin --password admin'
java_cmd = 'java -jar D:\\applications\\Jenkins\\war\\WEB-INF\\jenkins-cli.jar\\jenkins-cli.jar -s http://localhost:8080/ create-job test7'

The simplest way is to envoke the shell:

#!/usr/bin/env python
import subprocess

subprocess.check_call(svn_cmd + ' | ' + java_cmd, shell=True)

You could emulate it in Python:

#!/usr/bin/env python3
from subprocess import Popen, PIPE

#NOTE: use a list for compatibility with POSIX systems
with Popen(java_cmd.split(), stdin=PIPE) as java, \
     Popen(svn_cmd.split(), stdout=java.stdin):
    java.stdin.close() # close unused pipe in the parent
    # no more code here (the for-loop is inside an OS code that implements pipes)
if java.returncode != 0:
   "handle error here"

See How do I use subprocess.Popen to connect multiple processes by pipes?

jfs
  • 399,953
  • 195
  • 994
  • 1,670
  • The *If you wouldn't need to replace `@url@`* bit is *crucial* though. It is why I didn't just dupe the post to the one you linked to. – Martijn Pieters May 11 '15 at 17:17
  • @MartijnPieters: yes. I've missed it at first. I've updated the answer to include the 'replace' step. – jfs May 11 '15 at 17:23