diff --git a/.github/workflows/manual-republishing.yml b/.github/workflows/manual-republishing.yml new file mode 100644 index 0000000..f82298c --- /dev/null +++ b/.github/workflows/manual-republishing.yml @@ -0,0 +1,53 @@ +name: Build & Upload Python Package to Asset + +# Controls when the action will run. Workflow runs when manually triggered using the UI +# or API. +on: + workflow_dispatch: + +permissions: + contents: write + +jobs: + build-n-publish: + name: Build and publish + runs-on: ubuntu-latest + + steps: + - name: Checkout source + uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.10" + cache: 'pip' + + - name: GitHub Tag Name example + run: | + echo "Tag name from GITHUB_REF_NAME: $GITHUB_REF_NAME" + echo "Tag name from github.ref_name: ${{ github.ref_name }}" + echo "Tag name from github.tag_name: ${{ github.tag_name }}" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + pip install -U poetry + pip install build + + - name: Build package + run: python -m build + + #- name: Publish package to PyPI + # uses: pypa/gh-action-pypi-publish@master + # with: + # user: __token__ + # password: ${{ secrets.PYPI_API_TOKEN }} + + - name: Create and upload release with asset to GitHub + id: upload-release-asset + uses: softprops/action-gh-release@v1 + with: + draft: true + generate_release_notes: true + files: dist/* \ No newline at end of file diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 459c0c7..81616ce 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -6,8 +6,12 @@ name: Python package build on: push: branches: [ "main" ] + tags: + - "v*" pull_request: branches: [ "dev" ] + tags: + - "v*" jobs: build: diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index a448fcd..007a91c 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -9,18 +9,22 @@ name: Upload Python Package on: - release: - types: [published] + #release: + # types: [published] + workflow_dispatch: permissions: - contents: read + contents: write + pull-requests: write + repository-projects: write jobs: deploy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - name: Checkout source + uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v4 with: @@ -32,10 +36,39 @@ jobs: if [ -f requirements.txt ]; then pip install -r requirements.txt; fi pip install -U poetry pip install build + - name: Build package run: python -m build - - name: Publish package - uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 + + - name: Publish package to PyPI + uses: pypa/gh-action-pypi-publish@master with: user: __token__ password: ${{ secrets.PYPI_API_TOKEN }} + + #- name: Create GitHub Release + # id: create_release + # uses: actions/create-release@v1 + # env: + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token + # with: + # tag_name: ${{ github.ref }} + # release_name: ${{ github.ref }} + # draft: false + # prerelease: false + + - name: Get Asset Name + run: | + export PKG=$(ls dist/ | grep whl) + set -- $PKG + echo "name=$1" >> $GITHUB_ENV + - name: Upload Release Asset (whl) to GitHub + id: upload-release-asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: dist/${{ env.name }} + asset_name: ${{ env.name }} + asset_content_type: application/zip diff --git a/PySSHHelper/connection.py b/PySSHHelper/connection.py index 2da9b28..5e45ff6 100644 --- a/PySSHHelper/connection.py +++ b/PySSHHelper/connection.py @@ -3,6 +3,7 @@ import logging import paramiko import time +from re import Pattern class SSHConnection: @@ -11,7 +12,23 @@ class SSHConnection: def __init__(self, host: str, port: int, username_ssh: str, password_ssh: str, username_sudo: str, password_sudo: str, exec_sleep_time: int = 1, ssh_timeout: int = 15, prompt_shell: str = '$', prompt_sudo: str = '#', - verbose_ssh: bool = False, verbose: bool = False): + verbose_ssh: bool = False, verbose: bool = False) -> object: + """ + Represents interactive SSH shell and SFTP. + + :param host: remote server address + :param port: ssh port + :param username_ssh: ssh login + :param password_ssh: shh password for interactive authentication, can be empty if key authentication used + :param username_sudo: login for sudo su - login, can be empty. + :param password_sudo: password for sudo + :param exec_sleep_time: loop sleep time when command executing + :param ssh_timeout: common ssh client timeout. + :param prompt_shell: unprivileged shell prompt symbol. + :param prompt_sudo: privileged shell prompt symbol. + :param verbose_ssh: verbose logging only for ssh connection. + :param verbose: verbose logging. + """ self.host = host self.port = port self.username_ssh = username_ssh @@ -75,6 +92,7 @@ def connect(self): def elevate_privileges(self, prompt_start_timeout: int = 2): """ Elevate privileges by sudo su - username command + :param prompt_start_timeout: wait timeout before sudo prompt. """ if self.ssh_client: @@ -100,14 +118,15 @@ def revoke_privileges(self): else: self.logger.warning("Not connected to SSH server") - def execute_command(self, command, buffer_size: int = 65535, + def execute_command(self, command: str, buffer_size: int = 65535, wait_complete: bool = True, timeout: int = 60) -> str: """ + :return: all output from console + :rtype: str :param command: shell command :param buffer_size: socket buffer size :param wait_complete: wait shell prompt after command execution :param timeout: output wait timeout - :return: all output from console """ if self.ssh_client: if not self.ssh_shell: @@ -150,13 +169,17 @@ def execute_command(self, command, buffer_size: int = 65535, @staticmethod def cleanup_escape_codes(text: str) -> str: - """Remove escape codes from the text using the pre-compiled pattern""" + """ + Remove escape codes from the text using the pre-compiled pattern + :param text: text string + :return: clean string + """ clean_text = SSHConnection.ESCAPE_PATTERN.sub('', text) return clean_text def sftp_get(self, remote_path, local_path): """ - Get file from remote server + Get file from remote server """ if self.ssh_client: sftp_client = self.ssh_client.open_sftp() @@ -176,9 +199,12 @@ def sftp_put(self, local_path, remote_path): else: self.logger.warning("Not connected to SSH server") - def list_directory(self, path: str = '.'): + def list_directory(self, path: str = '.') -> list[str]: """ - List files in directory path on remote server + List files in a directory path on a remote server + :rtype: list + :param path: path + :return: file list """ if self.ssh_client: sftp_client = self.ssh_client.open_sftp() @@ -189,6 +215,9 @@ def list_directory(self, path: str = '.'): self.logger.warning("Not connected to SSH server") def close(self): + """ + Close all connections + """ if self.ssh_client: self.ssh_client.close() self.logger.info("Disconnected from SSH server")