KBEC-00047 - Proxy Customizations, ecproxy operations and examples

Summary

ecproxy is structured as a driver script with built-in support for ssh. Every major operation is overridable by defining a Perl function in the Proxy Customization (proxyCustomization) field of the resource and specifying which operation this function re-implements. These functions must have certain signatures in order for the driver to invoke them properly. These operations are enumerated below; for more detail, see the ssh implementation in ecproxy.pl.

General behaviors/environment

  • An empty hash named gProtocolConfig exists so that protocol implementations can store settings that operations will use. Helper functions can be declared to set values in the hash.
  • A hash named gDispatcher maps operations to implementation functions. Overriding an operation means resetting an entry in this hash to call a different function than the default.
  • When operation implementation functions fail, they should "die" with an appropriate error message. The message will show up in the step log file and will hopefully help users track down why the operation failed.
  • When in 'run' mode:
    • ecproxy is running in a step context, so all of the COMMANDER_* environment variables that are available to the agent are available to ecproxy.
    • The current working directory for ecproxy itself is the workspace. The command to be run on the proxy target will run in the working directory defined on the step.
  • When in 'ping' mode:
    • ecproxy is not running in a step context and does not have COMMANDER_* environment variables defined.
    • The current working directory for ecproxy is not a particular directory.

ecproxy Algorithm

ecproxy invokes the operations detailed below to perform the following actions:

  1. Uploads the command-file to 'workingDirectory' on the proxy target via the protocol specified in the proxy config. Currently, only ssh is supported.
  2. Creates a wrapper sh shell script that cd's to 'workingDirectory', sets COMMANDER_ environment variables that exist in the proxy agent's environment, and runs the command-file that was uploaded earlier.
  3. Uploads the wrapper script to 'workingDirectory' on the proxy target.
  4. Runs the wrapper script on the proxy target and streams its output to the proxy agent's stdout.
  5. Deletes the local wrapper shell script, deletes the remote wrapper and remote command-file.
  6. Exits with the exit code of running the wrapper script.

ecproxy Operations

getDefaultWorkingDirectory

Description: Computes the default working directory that a command should run in on the proxy target, if the step isn't defined with a working directory.
Arguments: None
Returns: The path to a directory as it would be accessed on the proxy target.
SSH implementation function: not ssh-specific, so the function is 'getDefaultWorkingDirectory'
Existing implementation: return $ENV{COMMANDER_WORKSPACE_UNIX};
Reason to override: If the "Working Directory" field is empty on a step that is going to run on a proxy target, the working directory for the step should be the workspace, just as it would be if the step were running on a proper Commander agent. ecproxy is guaranteed to run in the workspace directory on the proxy agent, but it's not guaranteed that the proxy target has the same path to the workspace. For example, the workspace on a Windows proxy agent is not going to be the path that a Unix proxy target uses to access the workspace. So the existing implementation of this operation simply returns the Unix path to the workspace. However, if the proxy target has a different path for accessing the workspace, the existing implementation will give the wrong answer. Thus, a user can provide a different implementation that gives the right answer.
Note: This operation is only applicable if "Working Directory" is empty, and is used as the working directory on the proxy target in that case for running the command.

getDefaultTargetPort

Description: Computes the default port that the proxy target is listening on for this protocol.
Arguments: None
Returns: The default port.
SSH implementation function: ssh_getDefaultTargetPort
Note: This operation is only applicable if the resource definition specifies no value for the port.

connect

Description: Opens a connection to the proxy target using the desired protocol.
Arguments: host, port (optional)
Returns: connection-context hash-ref upon successful connection. This context can contain anything that other functions can use to perform their tasks (e.g. a connection handle).
Example 1: my $context = connect('myhost', 22)
Example 2: my $context = connect('myhost')
SSH implementation function: ssh_connect
Note: Because the port is optional, the implementation of connect can default it to whatever's reasonable for the protocol. This means that the 'Proxy Target Port' need not be specified in the Commander webui for proxied agents that are reachable on the default port.

uploadFile

Description: Uploads the given srcFile to the proxy target as tgtFile.
Arguments: context, srcFile (typically simple file-name), tgtFile (typically workingDirectory/file-name)
Returns: Nothing; on failure it does a 'die' with an appropriate error message.
Example: uploadFile($context, 'agent123.tmp', '/opt/work/joe/agent123.tmp')
SSH implementation function: ssh_uploadFile

generateWrapperScript

Description: Generates the script body that will run the command-file on the proxy-target in workingDirectory.
Arguments: workingDirectory, cmd, cmdArg1, ..., cmdFileName (just the base-name, no directory)
Returns: A string containing the script to execute on the proxy target.
Example: generateWrapperScript('/opt/work/joe', 'perl', 'agent123.tmp'
SSH implementation function: not ssh-specific, so the function is 'generateWrapperScript'
Existing implementation: cd workingDirectory; set COMMANDER_ environment variables; run command-file, properly quoting the command and args.
Reason to override: If the proxy target doesn't have sh, the wrapper script needs to be written in a language that is available on the target.

uploadWrapperScript

Description: Uploads the wrapper-script code to the proxy target.
Arguments: context workingDirectory wrapperScriptBody
Returns: The path to the wrapper-script on the proxy target.
Example: my $wrapperFile = uploadWrapperScript($context, '/opt/work/joe', 'cd /opt/work/joe; perl agent123.tmp')
SSH implementation function: 3.1, 3.1.1: ssh_uploadWrapperScript
3.1.2 and later: not ssh-specific anymore, so the function is 'uploadWrapperScript'
Note: This function must generate a uniquely named file that won't conflict with other ecproxy invocations that may be occurring in parallel steps. The recommended approach is to generate a file name containing the job-step-id.
Note: Depending on the protocol and facilities available in the Perl implementation of the protocol, you may or may not have to create a local tmp file to upload to the proxy target. If you do, you should record that fact in the context and clean up the local file in the cleanup operation. Setting the local wrapper file path in $context->{wrapperFile} is recommended because the default cleanup operation implementation looks for that.

generateWrapperInvocationCommand

Description: Generates the command-line for running the wrapper script on the proxy target.
Arguments: remoteWrapperFile (path on proxy target)
Returns: A string containing the command-line for running the wrapper script file on the proxy target.
Example: my $wrapperCmdLine = generateWrapperInvocationCommand($wrapperFile)
SSH implementation function: not ssh-specific, so the function is 'generateWrapperInvocationCommand'
Existing implementation: return "sh $remoteWrapperFile";
Reason to override: The default implementation of this function returns something like 'sh $wrapperFile'. If the wrapper script is not an sh script, or if you want to pass different arguments to the shell, you must override this function.

runCommand

Description: Runs the given command-line on the proxy target.
Arguments: context, cmdLine
Returns: exit-code of running the command on the proxy target, undef if the command couldn't be run for some reason.
Example: runCommand($context, $wrapperCmdLine)
SSH implementation function: ssh_runCommand

cleanup

Description: Performs any cleanup tasks after the command has completed on the proxy target. Typically, it deletes any locally created temp files and uploaded files on the proxy target.
Arguments: context, cmdFile, wrapperFile (both are of the form workingDirectory/file-name)
Returns: 1 on success, undef on failure.
Example: cleanupTarget($context, '/opt/work/joe/agent123.tmp', '/opt/work/joe/cmdwrapper.123.tmp')
SSH implementation function: 3.1, 3.1.1: ssh_cleanup
3.1.2 and later: not ssh-specific anymore, so the function is 'cleanup'
Note: The default implementation deletes the locally created wrapper script file whose path is stored in $context->{wrapperFile}, if it exists. Thus, if the uploadWrapperScript operation is overridden, it's recommended that the overriding function set this attribute. That way, cleanup need not be overridden.

ping

Description: Tests if the proxy target is usable.
Arguments: host, port (optional)
Returns: 1 on success, undef on failure.
Example: ping('myhost', 22)
SSH implementation function: not ssh-specific, so the function is 'ping'
Existing implementation: Open a socket connection to the proxy target on the desired port.
Reason to override: The existing implementation may be deemed too simple for doing a ping; overriding ping to open a connection and do some protocol-specific handshaking may be more appropriate for some protocols / use cases.

Available helper functions

To make proxy customization easier, ecproxy has the following helper functions.

mesg

Description: Debug logging function. Writes to the file referenced in the ECPROXY_DEBUGFILE environment variable (if it exists). No-op otherwise.
Arguments: message
Example: mesg("myCleanup: about to delete $cmdFile on proxy target");
Note: This function automatically adds a newline to whatever it emits, so the caller doesn't have to incorporate a newline in message.

readFile

Description: Reads a file.
Arguments: fileName
Returns: Contents of the file; if there's an error, it returns empty string.
Example: my $data = readFile("foo.txt");

writeFile

Description: Creates a local file containing some data.
Arguments: fileName, data
Returns: 1 on success, undef on failure.
Example: writeFile("myWrapper.$ENV{COMMANDER_JOBSTEPID}.cmd", "perl foo.pl")

initDispatcher

Description: Initialize the operation dispatcher map to point to functions for the given protocol. For each operation, it checks if a function named protocol_operation exists, and if so, assigns that function as the implementation of that operation.
Arguments: protocol
Example: initDispatcher("ssh") sets the "connect" operation to run "ssh_connect", "uploadFile" => "ssh_uploadFile", etc.

setOperation

Description: Sets the implementation of an operation to be a particular function.
Arguments: operation, function. The 'function' argument may be the name of a function or a reference to a function.
Example: setOperation("ping", "my_ping"); sets the "ping" operation to run the "my_ping" function.
Example: setOperation("ping", \&my_ping); same as above, but using a function ref.
Note: This function manipulates the gDispatcher hash, but provides a safe interface to it.

loadFile

Description: Load proxy customizations from a file.
Arguments: fileName
Example: loadFile("custom.pl")

setSSHKeyFiles

Description: Set the paths to the public and private key files that ssh will use to authenticate with the proxy target.
Arguments: publicKeyFile, privateKeyFile
Example: setSSHKeyFiles('c:\foo\pub.key', 'c:\foo\priv.key')
Note: This is very useful on Windows proxies, where there is no reasonable default for ssh to use.

setSSHUser

Description: Set the name of the user to authenticate with the proxy target.
Arguments: userName
Example: setSSHUser('user1')
Note: By default, the username that the agent is running as is used to log into the proxy target. If key-based authentication is configured on the target system such that 'agentUser' can log into the 'user1' account on the proxy target, this function leverages that configuration.

useMultipleSSHSessions

Description: Normally, ecproxy uses one ssh session with a number of "channels" to perform tasks like uploading files, running the command, and running a cleanup command on the proxy target. Some SSH servers don't allow this. This method configures ecproxy to use a separate SSH session for each operation; this requires authenticating with the SSH daemon on the proxy target several times, and thus it may perform worse than the single-session-multi-channel mode.
Arguments: None
Example: useMultipleSSHSessions()

Examples

User wants to specify public/private key files for ssh

  1. Set the proxyCustomization property on the resource like this:
    setSSHKeyFiles('c:\foo\pub.key', 'c:\foo\priv.key');
    
  2. Set the ECPROXY_SSH_PRIVKEYFILE and ECPROXY_SSH_PUBKEYFILE environment variables on the proxy agent as system environment variables.

User wants to override one of the operations (say to enable ssh connection with username/password).

  1. Set the proxyCustomization property on the resource like this:
    sub myConnect($$) {...}setOperation("connect", \&myConnect);
    

User wants to load proxy customizations from a file rather than having all the logic in the proxyCustomization property on the resource

  1. Set the proxyCustomization property on the resource like this:
    loadFile('c:\foo\custom.pl');
    

User wants to implement a whole new protocol.

  1. Specify protocol as 'myproto' and have a proxy customization block like this:
    sub myproto_getDefaultTargetPort() {
    ...
    }
    sub myproto_connect($;$) {
    ...
    }
    sub myproto_uploadFile($$$) {
    ...
    }
    sub myproto_uploadWrapperScript($$$) {
        # Note: As of 3.1.2, the default implementation is likely good enough, so it may not be necessary to define this override....
    }
    sub myproto_runCommand($$) {
    ...
    }
    sub myproto_cleanup($$$) {
        # Note: As of 3.1.2, the default implementation is likely good enough, so it may not be necessary to define this override....
    }
    # Initialize the dispatcher to run these functions
    initDispatcher("myproto");
    

User wants to override ping to do a connect operation (which does a full protocol handshake, authentication, etc.)

  1. Write a specialized ping function for the proxy customization like this:
    sub heavy_ping($$) {
        my ($host, $port) = @_;
        return ssh_connect($host, $port);
    }
    setOperation("ping", \&heavy_ping);
    

Real World Examples

ClusterExec

A basic integration for using clusterupload and clusterexec to reach a proxy target is here. It has been tested on a Windows target with a Cygwin installation. It won't work "out of the box" because it makes the following assumptions:

  • The proxy target has sh and other Unix tools (e.g. rm).
  • The locations of the clusterexec and clusterupload binaries are hard-coded at the top of the proxy customization.

To make this proxy customization work on a Windows machine that doesn't have Cygwin, the generateWrapperScript operation would need to be overridden with a function that generates a cmd batch script, and the generateWrapperInvocationCommand operation would have to be overridden to generate a "cmd /c ..." command rather than "sh ...".

MySQL

A bare-bones integration with mysql is here. The idea here is that the proxy target need not be a host for running arbitrary commands. It could be a special entity like a db. This integration uses the mysql clt to run the step command (which should be SQL) on the db referenced by the proxy target host and port.

Android

A first attempt at proxying to android devices is here. It uses the adb tool to upload files to the device and run commands on it. It has only been tested against the android emulator, but it's implemented in such a way that it should work against a real android device that is attached via USB to the proxy agent, or a device on the network.

Have more questions? Submit a request

Comments

Powered by Zendesk