Skip to content

Commit

Permalink
- Adding "live" output to node-executor: #8
Browse files Browse the repository at this point in the history
- Password can be passed from the job level using a input option
- Removing weird output on "inline script"
  • Loading branch information
ltamaster committed Aug 31, 2018
1 parent 1510956 commit 5790b1f
Show file tree
Hide file tree
Showing 10 changed files with 393 additions and 53 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ For further information see:

* **Authentication Type**: The authentication type used for the connection: basic, ntlm, credssp. It can be overwriting at node level using `winrm-authtype`
* **Username**: (Optional) Username that will connect to the remote node. This value can be set also at node level or as a job input option (with the name `username)
* **Password Storage Path**: Key storage path of the window's user password. It can be overwriting at node level using `winrm-password-storage-path`
* **Password Storage Path**: Key storage path of the window's user password. It can be overwriting at node level using `winrm-password-storage-path`.
Also the password can be overwritten on the job level using an input secure option called `winrmpassword`
* **No SSL Verification**: When set to true SSL certificate validation is not performed. It can be overwriting at node level using `winrm-nossl`
* **WinRM Transport Protocol**: WinRM transport protocol (http or https). It can be overwriting at node level using `winrm-transport`
* **WinRM Port**: WinRM port (Default: 5985/5986 for http/https). It can be overwriting at node level using `winrm-port`
Expand Down
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ plugins {
}

ext.pluginName = 'Python Winrm Node Executor/File Copier Plugin'
ext.pluginDescription = "Sincronize Azure Storage with a folder on remote nodes"
ext.pluginDescription = "Connect to remote windows nodes using WINRM"
ext.sopsCopyright = "© 2017, Rundeck, Inc."
ext.sopsUrl = "http://rundeck.com"
ext.buildDateString=new Date().format("yyyy-MM-dd'T'HH:mm:ssX")
ext.archivesBaseName = "py-winrm-plugin"
ext.pluginBaseFolder = "."

scmVersion {
ignoreUncommittedChanges = true
ignoreUncommittedChanges = false
tag {
prefix = ''
versionSeparator = ''
Expand Down
Empty file added contents/__init__.py
Empty file.
27 changes: 27 additions & 0 deletions contents/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import re


def check_is_file(destination):
# check if destination file is a file
regex = r"((?:(?:[cC]:))[^\.]+\.[A-Za-z]{3})"

matches = re.finditer(regex, destination, re.MULTILINE)
isfile = False

for matchNum, match in enumerate(matches):
isfile = True

return isfile


def get_file(destination):
filename = ""
split = "/"
if("\\" in destination):
split = "\\"

for file in destination.split(split):
filename = file

return filename

83 changes: 83 additions & 0 deletions contents/protocol.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import xml.etree.ElementTree as ET
import xmltodict
import base64

from winrm.exceptions import WinRMError, WinRMTransportError, WinRMOperationTimeoutError

# TODO: this PR https://github.com/diyan/pywinrm/pull/55 will add this fix.
# when this PR is merged, this won't be needed anymore


def get_command_output(protocol, shell_id, command_id, out_stream=None, err_stream=None):
"""
Get the Output of the given shell and command
@param string shell_id: The shell id on the remote machine.
See #open_shell
@param string command_id: The command id on the remote machine.
See #run_command
@param stream out_stream: The stream of which the std_out would be directed to. (optional)
@param stream err_stream: The stream of which the std_err would be directed to. (optional)
#@return [Hash] Returns a Hash with a key :exitcode and :data.
Data is an Array of Hashes where the cooresponding key
# is either :stdout or :stderr. The reason it is in an Array so so
we can get the output in the order it ocurrs on
# the console.
"""
stdout_buffer, stderr_buffer = [], []
command_done = False
while not command_done:
try:
stdout, stderr, return_code, command_done = \
_raw_get_command_output(protocol, shell_id, command_id, out_stream, err_stream)

stdout_buffer.append(stdout)
stderr_buffer.append(stderr)
except WinRMOperationTimeoutError as e:
# this is an expected error when waiting for a long-running process, just silently retry
pass
return b''.join(stdout_buffer), b''.join(stderr_buffer), return_code


def _raw_get_command_output(protocol,shell_id, command_id, out_stream=None, err_stream=None):
req = {'env:Envelope': protocol._get_soap_header(
resource_uri='http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd', # NOQA
action='http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Receive', # NOQA
shell_id=shell_id)}

stream = req['env:Envelope'].setdefault('env:Body', {}).setdefault(
'rsp:Receive', {}).setdefault('rsp:DesiredStream', {})
stream['@CommandId'] = command_id
stream['#text'] = 'stdout stderr'

res = protocol.send_message(xmltodict.unparse(req))
root = ET.fromstring(res)
stream_nodes = [
node for node in root.findall('.//*')
if node.tag.endswith('Stream')]
stdout = stderr = b''
return_code = -1
for stream_node in stream_nodes:
if not stream_node.text:
continue

content = str(base64.b64decode(stream_node.text.encode('ascii')))

if stream_node.attrib['Name'] == 'stdout':
if out_stream:
out_stream.write(content)
stdout += content
elif stream_node.attrib['Name'] == 'stderr':
if err_stream:
err_stream.write(content)
stderr += content

command_done = len([
node for node in root.findall('.//*')
if node.get('State', '').endswith('CommandState/Done')]) == 1
if command_done:
return_code = int(
next(node for node in root.findall('.//*')
if node.tag.endswith('ExitCode')).text)

return stdout, stderr, return_code, command_done

82 changes: 56 additions & 26 deletions contents/winrm-exec.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import winrm
import argparse
import os
import sys
import requests.packages.urllib3
import winrm_session
import threading

requests.packages.urllib3.disable_warnings()
from winrm.protocol import Protocol

parser = argparse.ArgumentParser(description='Run Bolt command.')
parser.add_argument('hostname', help='the hostname')
Expand All @@ -19,9 +20,6 @@
shell = "cmd"
certpath = None

if "RD_CONFIG_PASSWORD_STORAGE_PATH" in os.environ:
password = os.getenv("RD_CONFIG_PASSWORD_STORAGE_PATH")

if "RD_CONFIG_AUTHTYPE" in os.environ:
authentication = os.getenv("RD_CONFIG_AUTHTYPE")

Expand Down Expand Up @@ -62,37 +60,69 @@
if "RD_CONFIG_USERNAME" in os.environ and os.getenv("RD_CONFIG_USERNAME"):
username = os.getenv("RD_CONFIG_USERNAME").strip('\'')

if(debug):
print "------------------------------------------"
print "endpoint:" +endpoint
print "authentication:" +authentication
print "username:" +username
print "nossl:" + str(nossl)
print "------------------------------------------"
if "RD_OPTION_WINRMPASSWORD" in os.environ and os.getenv("RD_OPTION_WINRMPASSWORD"):
#take password from job
password = os.getenv("RD_OPTION_WINRMPASSWORD").strip('\'')
else:
if "RD_CONFIG_PASSWORD_STORAGE_PATH" in os.environ:
password = os.getenv("RD_CONFIG_PASSWORD_STORAGE_PATH")

if debug:
print("------------------------------------------")
print("endpoint:" + endpoint)
print("authentication:" + authentication)
print("username:" + username)
print("nossl:" + str(nossl))
print("------------------------------------------")

arguments = {}
arguments["transport"] = authentication

if(nossl == True):
arguments["server_cert_validation"] = "ignore"
else:
if(transport=="https"):
if(transport == "https"):
arguments["server_cert_validation"] = "validate"
arguments["ca_trust_path"] = certpath

session = winrm.Session(target=endpoint,
session = winrm_session.Session(target=endpoint,
auth=(username, password),
**arguments)

if shell == "cmd":
result = session.run_cmd(exec_command)

if shell == "powershell":
result = session.run_ps(exec_command)

print result.std_out

if(result.std_err):
print result.std_err

sys.exit(result.status_code)
tsk = winrm_session.RunCommand(session, shell, exec_command)
t = threading.Thread(target=tsk.get_response)
t.start()
realstdout = sys.stdout
realstderr = sys.stderr
sys.stdout = tsk.o_stream
sys.stderr = tsk.e_stream

lastpos = 0
lasterrorpos = 0

while True:
t.join(.1)

if sys.stdout.tell() != lastpos:
sys.stdout.seek(lastpos)
realstdout.write(sys.stdout.read())
lastpos = sys.stdout.tell()

if sys.stderr.tell() != lasterrorpos:
sys.stderr.seek(lasterrorpos)
realstderr.write(session._clean_error_msg(sys.stderr.read()))
lasterrorpos = sys.stderr.tell()

if not t.is_alive():
break

sys.stdout.seek(0)
sys.stderr.seek(0)
sys.stdout = realstdout
sys.stderr = realstderr

if tsk.e_std:
sys.stderr.write(tsk.e_std)
sys.exit(1)
else:
sys.exit(tsk.stat)
Loading

0 comments on commit 5790b1f

Please sign in to comment.