Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added environment variable support for cmd-line arg defaults #1

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: sshpt Test

on:
push:
branches: '*'
pull_request:
branches: '*'

jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.6, 3.7, 3.8]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install depencies
run: |
python -m pip install --upgrade pip
pip install flake8 pytest
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The github editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Test with pytest
run: |
pytest
3 changes: 2 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
language: python
python:
- "2.7"
- "3.6"
- "3.7"
- "3.8"
# command to install dependencies
install:
- pip install -r requirements.txt
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@

# Import some basic built-in modules
import getpass
import Queue
from queue import Queue

# Import the module du jour
import sshpt

# Obtain the basic information necessary to use sshpt
hostlist = raw_input('Host(s) (use spaces for multiple): ').split(' ')
username = raw_input('Username: ')
hostlist = input('Host(s) (use spaces for multiple): ').split(' ')
username = input('Username: ')
password = getpass.getpass('Password: ')
command = raw_input('Command: ')
command = input('Command: ')
# 'commands' has to be a list
commands = [command, ]

Expand Down
12 changes: 9 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@
# -*- coding: utf-8 -*-

import sys

from os import path
from setuptools import setup, find_packages

from sshpt import version
_locals = {}
with open("sshpt/version.py") as f:
exec(f.read(), None, _locals)
version = _locals['__version__']


EXCLUDE_FROM_PACKAGES = ['test']
# Meta
Expand All @@ -31,8 +35,10 @@
"License :: OSI Approved :: GNU General Public License (GPL)",
"Operating System :: Unix",
"Environment :: Console",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Topic :: System :: Systems Administration",
], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
packages=find_packages(exclude=EXCLUDE_FROM_PACKAGES),
Expand Down
4 changes: 1 addition & 3 deletions sshpt/Generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@
from itertools import cycle
import base64
import threading
if sys.version_info[0] == 3:
xrange = range


### ---- Private Functions ----
Expand Down Expand Up @@ -61,7 +59,7 @@ def encode(s, key='sshpt256'):

@staticmethod
def decode(s, key='sshpt256'):
dec = [chr(abs(ord(s[i]) - ord(key[i % len(key)])) % 256) for i in xrange(len(s))]
dec = [chr(abs(ord(s[i]) - ord(key[i % len(key)])) % 256) for i in range(len(s))]
return "".join(dec)

class GenericThread(threading.Thread):
Expand Down
14 changes: 7 additions & 7 deletions sshpt/OutputThread.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,34 +66,34 @@ def printToStdout(self, output):

if self.outfile:
with open(self.outfile, 'a') as f:
f.write("%s\n" % output)
f.write(f"{output}\n")

def writeOut(self, queueObj):
"""Write relevant queueObj information to stdout and/or to the outfile (if one is set)"""
if queueObj['local_filepath']:
queueObj['commands'] = "sshpt: sftp.put %s %s:%s" % (queueObj['local_filepath'], queueObj['host'], queueObj['remote_filepath'])
queueObj['commands'] = f"sshpt: sftp.put {queueObj['local_filepath']} {queueObj['host']}:{queueObj['remote_filepath']}"
elif queueObj['sudo'] is False:
if len(queueObj['commands']) > 1:
# Only prepend 'index: ' if we were passed more than one command
queueObj['commands'] = "\n".join(["%s: %s" % (index, command) for index, command in enumerate(queueObj['commands'])])
queueObj['commands'] = "\n".join([f"{index}: {command}" for index, command in enumerate(queueObj['commands'])])
else:
queueObj['commands'] = "".join(queueObj['commands'])
else:
if len(queueObj['commands']) > 1:
# Only prepend 'index: ' if we were passed more than one command
queueObj['commands'] = "\n".join(["%s: sudo -u %s %s" % (index, queueObj['sudo'], command) for index, command in enumerate(queueObj['commands'])])
queueObj['commands'] = "\n".join([f"{index}: sudo -u {queueObj['sudo']} {command}" for index, command in enumerate(queueObj['commands'])])
else:
queueObj['commands'] = "sudo -u %s %s" % (queueObj['sudo'], "".join(queueObj['commands']))
queueObj['commands'] = f"sudo -u {queueObj['sudo']} {''.join(queueObj['commands'])}"
if isinstance(queueObj['command_output'], str):
# Since it is a string we'll assume it is already formatted properly
pass
elif len(queueObj['command_output']) > 1:
# Only prepend 'index: ' if we were passed more than one command
queueObj['command_output'] = "\n".join(["%s: %s" % (index, command) for index, command in enumerate(queueObj['command_output'])])
queueObj['command_output'] = "\n".join([f"{index}: {command}" for index, command in enumerate(queueObj['command_output'])])
else:
queueObj['command_output'] = "\n".join(queueObj['command_output'])
if self.output_format == 'csv':
output = "\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"" % (queueObj['host'], queueObj['connection_result'], datetime.datetime.now(), queueObj['commands'], queueObj['command_output'])
output = f"\"{queueObj['host']}\",\"{queueObj['connection_result']}\",\"{datetime.datetime.now()}\",\"{queueObj['commands']}\",\"{queueObj['command_output']}\""
elif self.output_format == 'json':
output = {'host': queueObj['host'], 'connection_result': queueObj['connection_result'], 'timestamp': str(datetime.datetime.now()), 'commands': queueObj['commands'], 'command_output': queueObj['command_output']}

Expand Down
15 changes: 8 additions & 7 deletions sshpt/SSHQueue.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ def paramikoConnect(self, host, username, password, timeout, port=22, key_file="
#paramiko.util.log_to_file('paramiko.log')
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
logger.debug("paramikoConnect:connect, %s:%s", username, host)
logger.debug(f"paramikoConnect:connect, {username}@{host}")

try:
if key_file:
Expand Down Expand Up @@ -136,7 +136,7 @@ def sudoExecute(self, ssh, command, password, sudo):
"""Executes the given command via sudo as the specified user (sudo) using the given Paramiko transport object.
Returns stdout, stderr (after command execution)"""
logger.debug("Run sudoExecute: %s, %s", sudo, command)
stdin, stdout, stderr = ssh.exec_command("sudo -S -u %s %s" % (sudo, command))
stdin, stdout, stderr = ssh.exec_command(f"sudo -S -u {sudo} {command}")
if stdout.channel.closed is False:
# If stdout is still open then sudo is asking us for a password
stdin.write('%s\n' % password)
Expand Down Expand Up @@ -186,22 +186,23 @@ def attemptConnection(self, host, username="", password="", keyfile="", keypass=
remote_fullpath = os.path.join(remote_filepath, local_short_filename)
try:
if sudo:
temp_path = os.path.join('/tmp/', local_short_filename)
temp_path = os.path.join('/tmp', local_short_filename)
logger.info("Put the file temp first %s to %s", local_filepath, temp_path)
self.sftpPut(ssh, local_filepath, temp_path)
command_output.append(self.executeCommand(ssh, command="mv %s %s" % (temp_path, remote_fullpath), sudo=sudo, password=password))
command = f"mv {temp_path} {remote_fullpath}"
command_output.append(self.executeCommand(ssh, command=command, sudo=sudo, password=password))
else:
self.sftpPut(ssh, local_filepath, remote_fullpath)

if execute:
# Make it executable (a+x in case we run as another user via sudo)
chmod_command = "chmod a+x %s" % remote_fullpath
chmod_command = f"chmod a+x {remote_filepath}"
self.executeCommand(ssh=ssh, command=chmod_command, sudo=sudo, password=password)
# The command to execute is now the uploaded file
commands = [remote_fullpath, ]
else:
# We're just copying a file (no execute) so let's return it's details
commands = ["ls -l %s" % remote_fullpath, ]
commands = [f"ls -l {remote_fullpath}", ]
except IOError as details:
# i.e. permission denied
# Make sure the error is included in the command output
Expand All @@ -222,7 +223,7 @@ def attemptConnection(self, host, username="", password="", keyfile="", keypass=
except Exception as detail:
# Connection failed
print (sys.exc_info())
print("Exception: %s" % detail)
print(f"Exception: {detail}")
connection_result = False
command_output = detail
finally:
Expand Down
19 changes: 4 additions & 15 deletions sshpt/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,7 @@
import sys
import select
import getpass
if sys.version_info[0] == 2:
from ConfigParser import SafeConfigParser
else:
from configparser import SafeConfigParser
from configparser import SafeConfigParser
from argparse import ArgumentParser
import logging

Expand Down Expand Up @@ -101,7 +98,7 @@ def create_argument():
parser.add_argument("-P", "--port", dest="port", type=int, default=22, metavar="<port>",
help="The port to be used when connecting. Defaults to 22.")
parser.add_argument("-u", "--username", dest="username", default=default_username, metavar="<username>",
help="The username to be used when connecting. Defaults to the currently logged-in user [{}].".format(default_username))
help=f"The username to be used when connecting. Defaults to the currently logged-in user [{default_username}].")
parser.add_argument("-p", "--password", dest="password", default=None, metavar="<password>",
help="The password to be used when connecting (not recommended--use an authfile unless the username and password are transient).")
parser.add_argument("-q", "--quiet", action="store_false", dest="verbose", default=True,
Expand Down Expand Up @@ -170,20 +167,12 @@ def create_argument():
credentials = open(options.authfile).readline()
options.username, options.password = credentials.split(":")
# Get rid of trailing newline
options.password = Password(password.rstrip('\n'))
options.password = Password(options.password.rstrip('\n'))
options.sudo = 'root' if options.sudo is None else options.sudo

# Get the username and password to use when checking hosts
if options.username is None:
options.username = raw_input('Username: ')

# if keyfile is None, use the default, if it's the default-delimiter, set it to None
if options.keyfile is None:
options.keyfile = f"/home/{options.username}/.ssh/id_rsa"
print(f"using default keyfile: {options.keyfile}")
elif options.keyfile == "-":
options.keyfile = None

options.username = input('Username: ')
if options.keyfile and options.keypass is None and not options.passwordless:
options.keypass = Password(getpass.getpass('Passphrase: '))
elif options.password is None and not options.passwordless:
Expand Down
Empty file removed test/__init__.py
Empty file.
19 changes: 15 additions & 4 deletions test/run_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,24 @@
import os
import sys
import logging
import unittest
from unittest import mock
import pytest
import argparse
from subprocess import Popen, PIPE

from os.path import dirname
from os.path import abspath
root_path = dirname(dirname(abspath(__file__)))
sys.path.append(root_path)

logging.basicConfig(level=logging.DEBUG)
from sshpt import version
from sshpt import main

if __name__ == '__main__':
from sshpt import main
main.main()
def test_version(capsys):
with mock.patch('sys.argv', ['sshpt', '-v']):
with pytest.raises(SystemExit) as exc:
ret = main.main()
out, err = capsys.readouterr()
assert(out.strip() == version.__version__)
assert(exc.value.code == 0)