Skip to content
Closed
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
55 changes: 24 additions & 31 deletions .github/workflows/linux.yml
Original file line number Diff line number Diff line change
@@ -1,46 +1,39 @@
name: Linux tests

on:
"on":
push: {branches: [master]}
pull_request: {branches: [master]}

jobs:
test:
runs-on: ubuntu-20.04
runs-on: ubuntu-24.04
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
imgtag:
- 2.7-buster
- 3.4-stretch
- 3.5-buster
- 3.6-buster
- 3.7-buster
- 3.8-bullseye
- 3.9-bullseye
- 3.10-bullseye
- 3.6-bullseye
- 3.7-bullseye
- 3.8-bookworm
- 3.9-bookworm
- 3.10-bookworm
- 3.11-bookworm
- 3.12-bookworm
- 3.13-bookworm
paramiko_ver:
- 2.6.4
- 2.7.10
- 2.8.8
- 2.6.6
- 2.7.12
- 2.8.10
container: "python:${{matrix.imgtag}}"
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: System dependencies
run: |
apt-get -q -y update
apt-get -q -y install openssh-client openssh-server rsync sudo
if [ 3.4-stretch = "${{matrix.imgtag}}" ]; then
sed -i -e '/^mesg n/d' /root/.profile
fi
- name: Python dependencies
run: |
# nose and Fudge need use_2to3 which setuptools-58 removed
pip install 'setuptools<58'
# nose binary wheel is not compatible with python-3.10
# see https://github.com/nose-devs/nose/issues/1099#issuecomment-577412313
pip install --no-binary nose -r dev-requirements.txt
pip install -r dev-requirements.txt
pip install paramiko-ng==${{matrix.paramiko_ver}}
pip install -e .
- name: Lint
Expand All @@ -50,10 +43,10 @@ jobs:
- name: Setup SSH
run: |
export USER=root HOME=/root
mkdir -v /run/sshd
mkdir -v -p /run/sshd
/usr/sbin/sshd -D & sleep 1
mkdir -v ~/.ssh
ssh-keygen -N "" -f ~/.ssh/testkey
mkdir -v -p ~/.ssh
ssh-keygen -t ecdsa -N "" -f ~/.ssh/testkey
cp -v ~/.ssh/testkey.pub ~/.ssh/authorized_keys
ssh-keyscan -t ecdsa localhost > ~/.ssh/known_hosts
- name: Test
Expand All @@ -65,19 +58,19 @@ jobs:
script -e -q -c "fab -H localhost test:integration" /dev/null
docs:
runs-on: ubuntu-20.04
runs-on: ubuntu-24.04
timeout-minutes: 30
container: "python:3.9-bookworm"

steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v1
with:
python-version: 3.8
- uses: actions/checkout@v4

- name: Install dependencies
run: |
pip install -r doc-requirements.txt
pip install paramiko-ng==2.8.4
pip install paramiko-ng==2.8.10
pip install -e .
- name: Build docs
run: |
sphinx-build -W sites/docs tmpbuild
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
*fab-classic* is a Python (2.7 or 3.4+) library and command-line tool
*fab-classic* is a Python (3.6+) library and command-line tool
for streamlining the use of SSH for application deployment or systems
administration tasks.

Expand Down
16 changes: 5 additions & 11 deletions dev-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
# You should already have Paramiko and your local Fabric checkout installed
# Test runner/testing utils
nose<2.0
# Rudolf adds color to the output of 'fab test'. This is a custom fork
# addressing Python 2.7 and Nose's 'skip' plugin compatibility issues.
https://github.com/iknite/rudolf/archive/4a33a26a3aff.tar.gz#egg=rudolf==0.4
# Mocking library
Fudge<1.0
nose-py3
# Fudge mocking library, small fork with use_2to3 removed from setup
https://github.com/ploxiln/fudge/archive/a252954d6e34.tar.gz#egg=fudge
# used in some tests
jinja2<4.0
jinja2 <4.0
# flake8
flake8==3.7.9
mccabe==0.6.1
pycodestyle==2.5.0
pyflakes==2.1.1
flake8 <6
2 changes: 1 addition & 1 deletion fabfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ def test(args=None):
# Default to explicitly targeting the 'tests' folder, but only if nothing
# is being overridden.
tests = "" if args else " tests"
default_args = "-sv --with-doctest --nologcapture --with-color %s" % tests
default_args = "-sv --with-doctest --nologcapture %s" % tests
default_args += (" " + args) if args else ""
nose.core.run_exit(argv=[''] + default_args.split())
21 changes: 8 additions & 13 deletions fabric/context_managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@
"""

from contextlib import contextmanager
import six
from contextlib import contextmanager, ExitStack
import socket
import select

Expand All @@ -28,16 +27,12 @@
from fabric import state
from fabric.utils import isatty

if six.PY2 is True:
from contextlib import nested
else:
from contextlib import ExitStack

class nested(ExitStack):
def __init__(self, *managers):
super(nested, self).__init__()
for manager in managers:
self.enter_context(manager)
class nested(ExitStack):
def __init__(self, *managers):
super(nested, self).__init__()
for manager in managers:
self.enter_context(manager)


if not win32:
Expand Down Expand Up @@ -130,7 +125,7 @@ def _setenv(variables):
clean_revert = variables.pop('clean_revert', False)
previous = {}
new = []
for key, value in six.iteritems(variables):
for key, value in variables.items():
if key in state.env:
previous[key] = state.env[key]
else:
Expand All @@ -140,7 +135,7 @@ def _setenv(variables):
yield
finally:
if clean_revert:
for key, value in six.iteritems(variables):
for key, value in variables.items():
# If the current env value for this key still matches the
# value we set it to beforehand, we are OK to revert it to the
# pre-block value.
Expand Down
9 changes: 4 additions & 5 deletions fabric/contrib/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@

import hashlib
import os
import six

from io import StringIO
from functools import partial

from fabric.api import run, sudo, hide, settings, env, put, abort
Expand Down Expand Up @@ -163,12 +162,12 @@ def upload_template(filename, destination, context=None, use_jinja=False,
target = destination.replace(' ', r'\ ')
func("cp %s %s.bak" % (target, target))

if six.PY3 is True and isinstance(text, bytes):
if isinstance(text, bytes):
text = text.decode('utf-8')

# Upload the file.
return put(
local_path=six.StringIO(text),
local_path=StringIO(text),
remote_path=destination,
use_sudo=use_sudo,
mirror_local_mode=mirror_local_mode,
Expand Down Expand Up @@ -413,7 +412,7 @@ def append(filename, text, use_sudo=False, partial=False, escape=True,
"""
func = use_sudo and sudo or run
# Normalize non-list input to be a list
if isinstance(text, six.string_types):
if isinstance(text, str):
text = [text]
for line in text:
regex = '^' + _escape_for_regex(line) + ('' if partial else '$')
Expand Down
4 changes: 1 addition & 3 deletions fabric/contrib/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
import os.path
from tempfile import mkdtemp

import six

from fabric.network import needs_host, key_filenames, normalize
from fabric.operations import local, run, sudo, put
from fabric.state import env, output
Expand Down Expand Up @@ -106,7 +104,7 @@ def rsync_project(
The ``default_opts`` keyword argument.
"""
# Turn single-string exclude into a one-item list for consistency
if isinstance(exclude, six.string_types):
if isinstance(exclude, str):
exclude = (exclude,)
# Create --exclude options from exclude list
exclude_opts = ' --exclude "%s"' * len(exclude)
Expand Down
3 changes: 1 addition & 2 deletions fabric/decorators.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""
Convenience decorators for use in fabfiles.
"""
import six
import types
from functools import wraps

Expand Down Expand Up @@ -55,7 +54,7 @@ def inner_decorator(*args, **kwargs):
return func(*args, **kwargs)
_values = values
# Allow for single iterable argument as well as *args
if len(_values) == 1 and not isinstance(_values[0], six.string_types):
if len(_values) == 1 and not isinstance(_values[0], str):
_values = _values[0]
setattr(inner_decorator, attribute, list(_values))
# Don't replace @task new-style task objects with inner_decorator by
Expand Down
6 changes: 2 additions & 4 deletions fabric/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
from select import select
from collections import deque

import six

from fabric.state import env, output, win32
from fabric.auth import get_password, set_password
import fabric.network
Expand Down Expand Up @@ -92,7 +90,7 @@ def loop(self):
raise CommandTimeout(timeout=self.timeout)
continue

if six.PY3 is True and isinstance(bytelist, six.binary_type):
if isinstance(bytelist, bytes):
# Note that we have to decode this right away, even if an error
# is thrown only later in the code, because e.g. '' != b'' (see
# first if below).
Expand Down Expand Up @@ -243,7 +241,7 @@ def _get_prompt_response(self):
Iterate through the request prompts dict and return the response and
original request if we find a match
"""
for tup in six.iteritems(env.prompts):
for tup in env.prompts.items():
if _endswith(self.capture, tup[0]):
return tup
return None, None
Expand Down
8 changes: 3 additions & 5 deletions fabric/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@
except ImportError:
from collections import Mapping

import six

# For checking callables against the API, & easy mocking
from fabric import api, state, colors
from fabric.contrib import console, files, project
Expand Down Expand Up @@ -379,7 +377,7 @@ def _is_task(name, value):

def _sift_tasks(mapping):
tasks, collections = [], []
for name, value in six.iteritems(mapping):
for name, value in mapping.items():
if _is_task(name, value):
tasks.append(name)
elif isinstance(value, Mapping):
Expand Down Expand Up @@ -410,7 +408,7 @@ def _print_docstring(docstrings, name):
if not docstrings:
return False
docstring = crawl(name, state.commands).__doc__
if isinstance(docstring, six.string_types):
if isinstance(docstring, str):
return docstring


Expand Down Expand Up @@ -644,7 +642,7 @@ def main(fabfile_locations=None):
# Handle --hosts, --roles, --exclude-hosts (comma separated string =>
# list)
for key in ['hosts', 'roles', 'exclude_hosts']:
if key in state.env and isinstance(state.env[key], six.string_types):
if key in state.env and isinstance(state.env[key], str):
state.env[key] = state.env[key].split(',')

# Feed the env.tasks : tasks that are asked to be executed.
Expand Down
17 changes: 4 additions & 13 deletions fabric/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
import os
import re
import time
import six
import socket
import sys
from io import StringIO

import paramiko as ssh

Expand Down Expand Up @@ -204,7 +204,7 @@ def key_filenames():
from fabric.state import env
keys = env.key_filename
# For ease of use, coerce stringish key filename into list
if isinstance(env.key_filename, six.string_types) or env.key_filename is None:
if isinstance(env.key_filename, str) or env.key_filename is None:
keys = [keys]
# Strip out any empty strings (such as the default value...meh)
keys = list(filter(bool, keys))
Expand Down Expand Up @@ -232,7 +232,7 @@ def key_from_env(passphrase=None):
if output.debug:
sys.stderr.write("Trying to load it as %s\n" % pkey_class)
try:
return pkey_class.from_private_key(six.StringIO(env.key), passphrase)
return pkey_class.from_private_key(StringIO(env.key), passphrase)
except Exception as e:
# File is valid key, but is encrypted: raise it, this will
# cause cxn loop to prompt for passphrase & retry
Expand Down Expand Up @@ -612,10 +612,6 @@ def connect(user, host, port, cache, seek_gateway=True):


def _password_prompt(prompt, stream):
# NOTE: Using encode-to-ascii to prevent (Windows, at least) getpass from
# choking if given Unicode.
if six.PY3 is False:
prompt = prompt.encode('ascii', 'ignore')
return getpass.getpass(prompt, stream)

def prompt_for_password(prompt=None, no_colon=False, stream=None):
Expand Down Expand Up @@ -678,12 +674,7 @@ def host_prompting_wrapper(*args, **kwargs):
handle_prompt_abort("the target host connection string")
prompt = "No hosts found. Please specify (single) " \
"host string for connection: "
# WARNING: do not use six.moves.input, because test cases to not
# overwrite that method with a faked method from Fudge
if six.PY3 is True:
host_string = input(prompt)
else:
host_string = raw_input(prompt) # noqa: F821
host_string = input(prompt)
env.update(to_dict(host_string))
return func(*args, **kwargs)
host_prompting_wrapper.undecorated = func
Expand Down
Loading