Search code examples
pythonunit-testingsubprocessrundeck

Executing python subprocess while extending the test class


I am trying to figure out the best way to run a python subprocess on a remote vm while the subprocess is wrapped in a test framework

#!/usr/bin/python3
import unittest, subprocess
from utils.framework.baseTest import BaseTest

class BriansTest(BaseTest):

 def test_briansTest(self):
   subprocess.call(['ls -la', '-1'], shell=True)

if __name__ == '__main__':
 unittest.main()

But when we run this it send the entire script over to the vm and executes, but obviously the VM does not know anything about BaseTest as this is on the testRunner (rundeck)

We want a solution that can just send commands to a VM without having to send an entire script and returns the desired output so we can assert it wrapped in a test class.


Solution

  • By design, inline-scripts and "external" scripts always imply the script copy on remote nodes, maybe a "dirty" workaround is to use a specific directory with your classes and use it on your node definition to copy the script that calls that class as copy directory (in that way the script which copies from Rundeck server to remote server contains only the call).

    The resources.xml definition (with file-copy-destination-dir attribute as a "library location" which uses to copy the file):

    <?xml version="1.0" encoding="UTF-8"?>
    <project>
      <node name="node00" description="Node 00" tags="user" hostname="192.168.33.20" osArch="amd64" osFamily="unix" osName="Linux" osVersion="3.10.0-1062.4.1.el7.x86_64" username="vagrant" ssh-key-storage-path="keys/rundeck" file-copy-destination-dir="/mylib/"/>
    </project>
    

    In that directory, I leave the main code (classes in your case), module.py, you can securitize that directory to avoid reads from other accounts.

    def greeting(name):
      print("Hello, " + name) 
    

    So, It means that the code that executes that module is the only copied from Rundeck instance:

    import module
    
    module.greeting("Brian")
    

    And here the full job definition (I've used inline-script):

    <joblist>
      <job>
        <defaultTab>nodes</defaultTab>
        <description></description>
        <dispatch>
          <excludePrecedence>true</excludePrecedence>
          <keepgoing>false</keepgoing>
          <rankOrder>ascending</rankOrder>
          <successOnEmptyNodeFilter>false</successOnEmptyNodeFilter>
          <threadcount>1</threadcount>
        </dispatch>
        <executionEnabled>true</executionEnabled>
        <id>4edefa30-8fb7-4b74-b8d6-a86f68625e12</id>
        <loglevel>INFO</loglevel>
        <name>HelloWorld</name>
        <nodeFilterEditable>false</nodeFilterEditable>
        <nodefilters>
          <filter>name: node00</filter>
        </nodefilters>
        <nodesSelectedByDefault>true</nodesSelectedByDefault>
        <plugins />
        <scheduleEnabled>true</scheduleEnabled>
        <sequence keepgoing='false' strategy='node-first'>
          <command>
            <fileExtension>.py</fileExtension>
            <script><![CDATA[import module
    
    module.greeting("Brian")]]></script>
            <scriptargs />
            <scriptinterpreter>/usr/bin/python2</scriptinterpreter>
          </command>
        </sequence>
        <uuid>4edefa30-8fb7-4b74-b8d6-a86f68625e12</uuid>
      </job>
    </joblist>
    

    And here the result.