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
87 changes: 87 additions & 0 deletions connector_sftp/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
.. image:: https://img.shields.io/badge/license-LGPL--3-blue.svg
:target: http://www.gnu.org/licenses/lgpl.html
:alt: License: LGPL-3

==============
SFTP Connector
==============

This module allows you to connect & interact with remote SFTP hosts.

This module does not provide functionality on its own, it is meant to provide
an abstract SFTP core to be utilized by other business logic.

Installation
============

To install this module, you need to:

* Install paramiko
``pip install paramiko``

Configuration
=============

SFTP Connectors are configured at the company level, and are available in the
``res.company`` form inside of the ``SFTP Connectors`` page.

Usage
=====

Read Remote File
----------------

.. code-block:: python

# sftp is a ``connector.sftp`` singleton.
with sftp.open('path/to/remote/file') as file_handler:
data = file_handler.read()

Write Remote File
-----------------

.. code-block:: python

# sftp is a ``connector.sftp`` singleton.
with sftp.open('path/to/remote/file', 'w') as file_handler:
file_handler.write('Some data')

.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot
:target: https://runbot.odoo-community.org/runbot/149/10.0

Bug Tracker
===========

Bugs are tracked on `GitHub Issues
<https://github.com/OCA/server-tools/issues>`_. In case of trouble, please
check there if your issue has already been reported. If you spotted it first,
help us smash it by providing detailed and welcomed feedback.

Credits
=======

Images
------

* Odoo Community Association: `Icon <https://github.com/OCA/maintainer-tools/blob/master/template/module/static/description/icon.svg>`_.

Contributors
------------

* Dave Lasley <dave@laslabs.com>

Maintainer
----------

.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org

This module is maintained by the OCA.

OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.

To contribute to this module, please visit https://odoo-community.org.
5 changes: 5 additions & 0 deletions connector_sftp/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2016 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).

from . import models
29 changes: 29 additions & 0 deletions connector_sftp/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
# Copyright 2016-2017 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).

{
"name": "SFTP Connector",
"summary": "Framework for interacting with SFTP hosts",
"version": "10.0.1.0.0",
"category": "Base",
"website": "https://laslabs.com/",
"author": "LasLabs, Odoo Community Association (OCA)",
"license": "LGPL-3",
"application": False,
"installable": True,
"depends": [
"base_external_system",
],
"external_dependencies": {
"python": [
'paramiko',
],
},
'data': [
'security/ir.model.access.csv',
],
'demo': [
'demo/connector_sftp_demo.xml',
],
}
71 changes: 71 additions & 0 deletions connector_sftp/demo/connector_sftp_demo.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="utf-8"?>

<!--
Copyright 2016-201 LasLabs Inc.
License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl).
-->

<odoo>

<record id="connector_sftp_example" model="external.system">
<field name="name">Example SFTP Connection</field>
<field name="system_type">connector.sftp</field>
<field name="host">sftp.example.com</field>
<field name="port">22</field>
<field name="username">Test</field>
<field name="password">Password</field>
<field name="private_key"><![CDATA[-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----]]></field>
<field name="company_ids" eval="[(5, 0), (4, ref('base.main_company'))]" />
</record>

</odoo>
2 changes: 2 additions & 0 deletions connector_sftp/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
from . import connector_sftp
151 changes: 151 additions & 0 deletions connector_sftp/models/connector_sftp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
# -*- coding: utf-8 -*-
# Copyright 2016-2017 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).

import logging

from contextlib import contextmanager
from io import StringIO

from odoo import api, fields, models, _
from odoo.exceptions import ValidationError

_logger = logging.getLogger(__name__)

try:
import paramiko
except ImportError:
_logger.info('`paramiko` Python library is not installed')


class ConnectorSftp(models.Model):
_name = 'connector.sftp'
_inherit = 'external.system.adapter'
_description = 'SFTP Connector'

@api.multi
def external_get_client(self):
"""Return a usable SFTP client."""
super(ConnectorSftp, self).external_get_client()
transport = paramiko.Transport((
self.host, self.port,
))
fingerprint = self.fingerprint or None
if self.private_key:
with StringIO(self.private_key) as io:
private_key = paramiko.RSAKey.from_private_key(
io, self.private_key_password or None,
)
else:
private_key = None
transport.connect(
hostkey=fingerprint,
username=self.username,
password=self.password or None,
pkey=private_key,
)
client = paramiko.SFTPClient.from_transport(transport)
if self.remote_path:
client.chdir(self.remote_path)
return client

@api.multi
def external_destroy_client(self, client):
"""Close the connection."""
super(ConnectorSftp, self).external_destroy_client(client)
if client:
client.close()

@api.multi
def external_test_connection(self):
with self.client() as client:
if not client:
raise ValidationError(_(
'The SFTP connection was not able to be established.',
))
super(ConnectorSftp, self).external_test_connection()

@api.multi
def list_dir(self, path):
"""Return a list containing the names of the entries in ``path``

The list is in arbitrary order. It does not include the special
entries ``'.'`` and ``'..'`` even if they are present in the folder.
This method is meant to mirror the ``os.listdir`` as closely as
possible.

Params:
path (str): Path to list

Returns:
list: of names in path
"""
with self.client() as client:
return client.listdir(path)

@api.multi
def stat(self, path):
"""Retrieve information about a file on remote system. Return value is
an obj whose attributes correspond to the structure of the stdlib
``os.stat``, except that it may be lacking fields due to SFTP server
configuration.

Unlike a Python stat object, the result may not be accessed as a tuple.
This is mostly due to the author’s slack factor.

The fields supported are: ``st_mode``, ``st_size``, ``st_uid``,
`st_gid``, ``st_atime``, and ``st_mtime``.

Params:
path (str): Filename to stat

Returns:
paramiko.SFTPAttributes: object containing attributes about file.
"""
with self.client() as client:
return client.stat(path)

@api.multi
@contextmanager
def open(self, file_name, mode='r', buff_size=-1):
"""Open file on remote server. Mimicks python open function, and result
can be used as a context manager.

Params:
file_name (str): File to open
mode (str); How to open file, reference Python open
buff_size (int): Desired buffering

Returns:
paramiko.SFTPFile object: representing the open file

Raises:
IOError: if the file cannot be opened
"""
with self.client() as client:
with client.open(file_name, mode, buff_size) as fh:
yield fh

@api.multi
def delete(self, path):
"""Remove the file at the given path. Does not work on directories.

Params:
path (str): Path of file to delete.

Raises:
IOError: if path refers to a directory.
"""
with self.client() as client:
return client.unlink(path)

@api.multi
def symlink(self, source, dest):
"""Create a symbolic link of the source path at destination on remote.

Params:
source (str): Path of original file
dest (str): Path of new symlink
"""
with self.client() as client:
return client.symlink(source, dest)
2 changes: 2 additions & 0 deletions connector_sftp/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_connector_sftp_manager,access.connector.sftp.user,model_connector_sftp,base.group_system,1,1,1,1
Binary file added connector_sftp/static/description/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions connector_sftp/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-

from . import test_connector_sftp
Loading