mirror of
https://github.com/bringout/oca-storage.git
synced 2026-04-20 19:12:03 +02:00
Initial commit: OCA Storage packages (17 packages)
This commit is contained in:
commit
7a380f05d3
659 changed files with 41828 additions and 0 deletions
46
odoo-bringout-oca-storage-storage_backend_sftp/README.md
Normal file
46
odoo-bringout-oca-storage-storage_backend_sftp/README.md
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# Storage Backend SFTP
|
||||
|
||||
Odoo addon: storage_backend_sftp
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install odoo-bringout-oca-storage-storage_backend_sftp
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
This addon depends on:
|
||||
- storage_backend
|
||||
|
||||
## Manifest Information
|
||||
|
||||
- **Name**: Storage Backend SFTP
|
||||
- **Version**: 16.0.1.0.1
|
||||
- **Category**: Storage
|
||||
- **License**: LGPL-3
|
||||
- **Installable**: True
|
||||
|
||||
## Source
|
||||
|
||||
Based on [OCA/storage](https://github.com/OCA/storage) branch 16.0, addon `storage_backend_sftp`.
|
||||
|
||||
## License
|
||||
|
||||
This package maintains the original LGPL-3 license from the upstream Odoo project.
|
||||
|
||||
## Documentation
|
||||
|
||||
- Overview: doc/OVERVIEW.md
|
||||
- Architecture: doc/ARCHITECTURE.md
|
||||
- Models: doc/MODELS.md
|
||||
- Controllers: doc/CONTROLLERS.md
|
||||
- Wizards: doc/WIZARDS.md
|
||||
- Reports: doc/REPORTS.md
|
||||
- Security: doc/SECURITY.md
|
||||
- Install: doc/INSTALL.md
|
||||
- Usage: doc/USAGE.md
|
||||
- Configuration: doc/CONFIGURATION.md
|
||||
- Dependencies: doc/DEPENDENCIES.md
|
||||
- Troubleshooting: doc/TROUBLESHOOTING.md
|
||||
- FAQ: doc/FAQ.md
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
# Architecture
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
U[Users] -->|HTTP| V[Views and QWeb Templates]
|
||||
V --> C[Controllers]
|
||||
V --> W[Wizards – Transient Models]
|
||||
C --> M[Models and ORM]
|
||||
W --> M
|
||||
M --> R[Reports]
|
||||
DX[Data XML] --> M
|
||||
S[Security – ACLs and Groups] -. enforces .-> M
|
||||
|
||||
subgraph Storage_backend_sftp Module - storage_backend_sftp
|
||||
direction LR
|
||||
M:::layer
|
||||
W:::layer
|
||||
C:::layer
|
||||
V:::layer
|
||||
R:::layer
|
||||
S:::layer
|
||||
DX:::layer
|
||||
end
|
||||
|
||||
classDef layer fill:#eef8ff,stroke:#6ea8fe,stroke-width:1px
|
||||
```
|
||||
|
||||
Notes
|
||||
- Views include tree/form/kanban templates and report templates.
|
||||
- Controllers provide website/portal routes when present.
|
||||
- Wizards are UI flows implemented with `models.TransientModel`.
|
||||
- Data XML loads data/demo records; Security defines groups and access.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Configuration
|
||||
|
||||
Refer to Odoo settings for storage_backend_sftp. Configure related models, access rights, and options as needed.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Controllers
|
||||
|
||||
This module does not define custom HTTP controllers.
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
# Dependencies
|
||||
|
||||
This addon depends on:
|
||||
|
||||
- [storage_backend](../../odoo-bringout-oca-storage-storage_backend)
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
# FAQ
|
||||
|
||||
- Q: Which Odoo version? A: 16.0 (OCA/OCB packaged).
|
||||
- Q: How to enable? A: Start server with --addon storage_backend_sftp or install in UI.
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# Install
|
||||
|
||||
```bash
|
||||
pip install odoo-bringout-oca-storage-storage_backend_sftp"
|
||||
# or
|
||||
uv pip install odoo-bringout-oca-storage-storage_backend_sftp"
|
||||
```
|
||||
12
odoo-bringout-oca-storage-storage_backend_sftp/doc/MODELS.md
Normal file
12
odoo-bringout-oca-storage-storage_backend_sftp/doc/MODELS.md
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# Models
|
||||
|
||||
Detected core models and extensions in storage_backend_sftp.
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class storage_backend
|
||||
```
|
||||
|
||||
Notes
|
||||
- Classes show model technical names; fields omitted for brevity.
|
||||
- Items listed under _inherit are extensions of existing models.
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
# Overview
|
||||
|
||||
Packaged Odoo addon: storage_backend_sftp. Provides features documented in upstream Odoo 16 under this addon.
|
||||
|
||||
- Source: OCA/OCB 16.0, addon storage_backend_sftp
|
||||
- License: LGPL-3
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Reports
|
||||
|
||||
This module does not define custom reports.
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
# Security
|
||||
|
||||
This module does not define custom security rules or access controls beyond Odoo defaults.
|
||||
|
||||
Default Odoo security applies:
|
||||
- Base user access through standard groups
|
||||
- Model access inherited from dependencies
|
||||
- No custom row-level security rules
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
# Troubleshooting
|
||||
|
||||
- Ensure Python and Odoo environment matches repo guidance.
|
||||
- Check database connectivity and logs if startup fails.
|
||||
- Validate that dependent addons listed in DEPENDENCIES.md are installed.
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# Usage
|
||||
|
||||
Start Odoo including this addon (from repo root):
|
||||
|
||||
```bash
|
||||
python3 scripts/nix_odoo_web_server.py --db-name mydb --addon storage_backend_sftp
|
||||
```
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Wizards
|
||||
|
||||
This module does not include UI wizards.
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
[project]
|
||||
name = "odoo-bringout-oca-storage-storage_backend_sftp"
|
||||
version = "16.0.0"
|
||||
description = "Storage Backend SFTP - Implement SFTP Storage"
|
||||
authors = [
|
||||
{ name = "Ernad Husremovic", email = "hernad@bring.out.ba" }
|
||||
]
|
||||
dependencies = [
|
||||
"odoo-bringout-oca-storage-storage_backend>=16.0.0",
|
||||
"requests>=2.25.1"
|
||||
]
|
||||
readme = "README.md"
|
||||
requires-python = ">= 3.11"
|
||||
classifiers = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Topic :: Office/Business",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
homepage = "https://github.com/bringout/0"
|
||||
repository = "https://github.com/bringout/0"
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.hatch.metadata]
|
||||
allow-direct-references = true
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["storage_backend_sftp"]
|
||||
|
||||
[tool.rye]
|
||||
managed = true
|
||||
dev-dependencies = [
|
||||
"pytest>=8.4.1",
|
||||
]
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
|
||||
.. image:: https://img.shields.io/badge/licence-LGPL--3-blue.svg
|
||||
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
|
||||
:alt: License: LGPL-3
|
||||
|
||||
=====================
|
||||
Storage backend SFTP
|
||||
=====================
|
||||
|
||||
Add the possibility to store and get data from an SFTP for your storage backend
|
||||
|
||||
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
To install this module, you need to:
|
||||
|
||||
#. (root) pip install paramiko
|
||||
|
||||
|
||||
Known issues / Roadmap
|
||||
======================
|
||||
|
||||
Update README with the last model of README when migration to v11 in OCA
|
||||
|
||||
|
||||
Credits
|
||||
=======
|
||||
|
||||
|
||||
Contributors
|
||||
------------
|
||||
|
||||
* Sebastien Beau <sebastien.beau@akretion.com>
|
||||
* Raphaël Reverdy <raphael.reverdy@akretion.com>
|
||||
* Cédric Pigeon <cedric.pigeon@acsone.eu>
|
||||
* Simone Orsi <simone.orsi@camptocamp.com>
|
||||
|
||||
|
||||
Maintainer
|
||||
----------
|
||||
|
||||
* Akretion
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
from . import models
|
||||
from . import components
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
# Copyright 2017 Akretion (http://www.akretion.com).
|
||||
# @author Sébastien BEAU <sebastien.beau@akretion.com>
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
|
||||
|
||||
{
|
||||
"name": "Storage Backend SFTP",
|
||||
"summary": "Implement SFTP Storage",
|
||||
"version": "16.0.1.0.1",
|
||||
"category": "Storage",
|
||||
"website": "https://github.com/OCA/storage",
|
||||
"author": " Akretion,Odoo Community Association (OCA)",
|
||||
"license": "LGPL-3",
|
||||
"installable": True,
|
||||
"external_dependencies": {"python": ["paramiko"]},
|
||||
"depends": ["storage_backend"],
|
||||
"data": ["views/backend_storage_view.xml"],
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
from . import sftp_adapter
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
# Copyright 2017 Akretion (http://www.akretion.com).
|
||||
# @author Sébastien BEAU <sebastien.beau@akretion.com>
|
||||
# Copyright 2019 Camptocamp SA (http://www.camptocamp.com).
|
||||
# Copyright 2020 ACSONE SA/NV (<http://acsone.eu>)
|
||||
# @author Simone Orsi <simahawk@gmail.com>
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
|
||||
import errno
|
||||
import logging
|
||||
import os
|
||||
from contextlib import contextmanager
|
||||
from io import StringIO
|
||||
|
||||
from odoo.addons.component.core import Component
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
import paramiko
|
||||
except ImportError as err: # pragma: no cover
|
||||
_logger.debug(err)
|
||||
|
||||
|
||||
def sftp_mkdirs(client, path, mode=511):
|
||||
try:
|
||||
client.mkdir(path, mode)
|
||||
except IOError as e:
|
||||
if e.errno == errno.ENOENT and path:
|
||||
sftp_mkdirs(client, os.path.dirname(path), mode=mode)
|
||||
client.mkdir(path, mode)
|
||||
else:
|
||||
raise # pragma: no cover
|
||||
|
||||
|
||||
def load_ssh_key(ssh_key_buffer):
|
||||
for pkey_class in (
|
||||
paramiko.RSAKey,
|
||||
paramiko.DSSKey,
|
||||
paramiko.ECDSAKey,
|
||||
paramiko.Ed25519Key,
|
||||
):
|
||||
try:
|
||||
return pkey_class.from_private_key(ssh_key_buffer)
|
||||
except paramiko.SSHException:
|
||||
ssh_key_buffer.seek(0) # reset the buffer "file"
|
||||
raise Exception("Invalid ssh private key")
|
||||
|
||||
|
||||
@contextmanager
|
||||
def sftp(backend):
|
||||
transport = paramiko.Transport((backend.sftp_server, backend.sftp_port))
|
||||
if backend.sftp_auth_method == "pwd":
|
||||
transport.connect(username=backend.sftp_login, password=backend.sftp_password)
|
||||
elif backend.sftp_auth_method == "ssh_key":
|
||||
ssh_key_buffer = StringIO(backend.sftp_ssh_private_key)
|
||||
private_key = load_ssh_key(ssh_key_buffer)
|
||||
transport.connect(username=backend.sftp_login, pkey=private_key)
|
||||
client = paramiko.SFTPClient.from_transport(transport)
|
||||
yield client
|
||||
transport.close()
|
||||
|
||||
|
||||
class SFTPStorageBackendAdapter(Component):
|
||||
_name = "sftp.adapter"
|
||||
_inherit = "base.storage.adapter"
|
||||
_usage = "sftp"
|
||||
|
||||
def add(self, relative_path, data, **kwargs):
|
||||
with sftp(self.collection) as client:
|
||||
full_path = self._fullpath(relative_path)
|
||||
dirname = os.path.dirname(full_path)
|
||||
if dirname:
|
||||
try:
|
||||
client.stat(dirname)
|
||||
except IOError as e:
|
||||
if e.errno == errno.ENOENT:
|
||||
sftp_mkdirs(client, dirname)
|
||||
else:
|
||||
raise # pragma: no cover
|
||||
remote_file = client.open(full_path, "w")
|
||||
remote_file.write(data)
|
||||
remote_file.close()
|
||||
|
||||
def get(self, relative_path, **kwargs):
|
||||
full_path = self._fullpath(relative_path)
|
||||
with sftp(self.collection) as client:
|
||||
file_data = client.open(full_path, "r")
|
||||
data = file_data.read()
|
||||
# TODO: shouldn't we close the file?
|
||||
return data
|
||||
|
||||
def list(self, relative_path):
|
||||
full_path = self._fullpath(relative_path)
|
||||
with sftp(self.collection) as client:
|
||||
try:
|
||||
return client.listdir(full_path)
|
||||
except IOError as e:
|
||||
if e.errno == errno.ENOENT:
|
||||
# The path do not exist return an empty list
|
||||
return []
|
||||
else:
|
||||
raise # pragma: no cover
|
||||
|
||||
def move_files(self, files, destination_path):
|
||||
_logger.debug("mv %s %s", files, destination_path)
|
||||
fp = self._fullpath
|
||||
with sftp(self.collection) as client:
|
||||
for sftp_file in files:
|
||||
dest_file_path = os.path.join(
|
||||
destination_path, os.path.basename(sftp_file)
|
||||
)
|
||||
# Remove existing file at the destination path (an error is raised
|
||||
# otherwise)
|
||||
try:
|
||||
client.lstat(dest_file_path)
|
||||
except FileNotFoundError:
|
||||
_logger.debug("destination %s is free", dest_file_path)
|
||||
else:
|
||||
client.unlink(dest_file_path)
|
||||
# Move the file using absolute filepaths
|
||||
client.rename(fp(sftp_file), fp(dest_file_path))
|
||||
|
||||
def delete(self, relative_path):
|
||||
full_path = self._fullpath(relative_path)
|
||||
with sftp(self.collection) as client:
|
||||
return client.remove(full_path)
|
||||
|
||||
def validate_config(self):
|
||||
with sftp(self.collection) as client:
|
||||
client.listdir()
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * storage_backend_sftp
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 16.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#. module: storage_backend_sftp
|
||||
#: model:ir.model.fields,field_description:storage_backend_sftp.field_storage_backend__backend_type
|
||||
msgid "Backend Type"
|
||||
msgstr "Tip pozadine"
|
||||
|
||||
#. module: storage_backend_sftp
|
||||
#: model:ir.model.fields,field_description:storage_backend_sftp.field_storage_backend__backend_type_env_default
|
||||
msgid "Backend Type Env Default"
|
||||
msgstr "Zadano okruženje tipa pozadine"
|
||||
|
||||
#. module: storage_backend_sftp
|
||||
#: model:ir.model.fields,help:storage_backend_sftp.field_storage_backend__sftp_ssh_private_key
|
||||
msgid ""
|
||||
"It's recommended to not store the key here but to provide it via secret env "
|
||||
"variable. See `server_environment` docs."
|
||||
msgstr ""
|
||||
|
||||
#. module: storage_backend_sftp
|
||||
#: model:ir.model.fields,help:storage_backend_sftp.field_storage_backend__sftp_login
|
||||
msgid "Login to connect to sftp server"
|
||||
msgstr "Prijava za povezivanje na SFTP server"
|
||||
|
||||
#. module: storage_backend_sftp
|
||||
#: model:ir.model.fields.selection,name:storage_backend_sftp.selection__storage_backend__sftp_auth_method__pwd
|
||||
msgid "Password"
|
||||
msgstr "Zaporka"
|
||||
|
||||
#. module: storage_backend_sftp
|
||||
#: model:ir.model.fields.selection,name:storage_backend_sftp.selection__storage_backend__sftp_auth_method__ssh_key
|
||||
msgid "Private key"
|
||||
msgstr "Privatni ključ"
|
||||
|
||||
#. module: storage_backend_sftp
|
||||
#: model:ir.model.fields.selection,name:storage_backend_sftp.selection__storage_backend__backend_type__sftp
|
||||
#: model_terms:ir.ui.view,arch_db:storage_backend_sftp.storage_backend_view_form
|
||||
msgid "SFTP"
|
||||
msgstr "SFTP"
|
||||
|
||||
#. module: storage_backend_sftp
|
||||
#: model:ir.model.fields,field_description:storage_backend_sftp.field_storage_backend__sftp_auth_method
|
||||
msgid "SFTP Authentification Method"
|
||||
msgstr "SFTP metoda autentifikacije"
|
||||
|
||||
#. module: storage_backend_sftp
|
||||
#: model:ir.model.fields,field_description:storage_backend_sftp.field_storage_backend__sftp_server
|
||||
msgid "SFTP Host"
|
||||
msgstr "SFTP domaćin"
|
||||
|
||||
#. module: storage_backend_sftp
|
||||
#: model:ir.model.fields,field_description:storage_backend_sftp.field_storage_backend__sftp_login
|
||||
msgid "SFTP Login"
|
||||
msgstr "SFTP prijava"
|
||||
|
||||
#. module: storage_backend_sftp
|
||||
#: model:ir.model.fields,field_description:storage_backend_sftp.field_storage_backend__sftp_password
|
||||
msgid "SFTP Password"
|
||||
msgstr "SFTP Password"
|
||||
|
||||
#. module: storage_backend_sftp
|
||||
#: model:ir.model.fields,field_description:storage_backend_sftp.field_storage_backend__sftp_port
|
||||
msgid "SFTP Port"
|
||||
msgstr "SFTP Port"
|
||||
|
||||
#. module: storage_backend_sftp
|
||||
#: model:ir.model.fields,field_description:storage_backend_sftp.field_storage_backend__sftp_ssh_private_key
|
||||
msgid "SSH private key"
|
||||
msgstr "SSH privatni ključ"
|
||||
|
||||
#. module: storage_backend_sftp
|
||||
#: model:ir.model,name:storage_backend_sftp.model_storage_backend
|
||||
msgid "Storage Backend"
|
||||
msgstr "Pozadina skladištenja"
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * storage_backend_sftp
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 16.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2025-06-04 09:26+0000\n"
|
||||
"Last-Translator: mymage <stefano.consolaro@mymage.it>\n"
|
||||
"Language-Team: none\n"
|
||||
"Language: it\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 5.10.4\n"
|
||||
|
||||
#. module: storage_backend_sftp
|
||||
#: model:ir.model.fields,field_description:storage_backend_sftp.field_storage_backend__backend_type
|
||||
msgid "Backend Type"
|
||||
msgstr "Tipo backend"
|
||||
|
||||
#. module: storage_backend_sftp
|
||||
#: model:ir.model.fields,field_description:storage_backend_sftp.field_storage_backend__backend_type_env_default
|
||||
msgid "Backend Type Env Default"
|
||||
msgstr "Tipo backend ambiente predefinito"
|
||||
|
||||
#. module: storage_backend_sftp
|
||||
#: model:ir.model.fields,help:storage_backend_sftp.field_storage_backend__sftp_ssh_private_key
|
||||
msgid ""
|
||||
"It's recommended to not store the key here but to provide it via secret env "
|
||||
"variable. See `server_environment` docs."
|
||||
msgstr ""
|
||||
"Si raccomanda si non salvare qui la chiave ma di fornirla attraverso una "
|
||||
"variabile di ambiente segreta. Vedere documentazione 'server_enviroment'."
|
||||
|
||||
#. module: storage_backend_sftp
|
||||
#: model:ir.model.fields,help:storage_backend_sftp.field_storage_backend__sftp_login
|
||||
msgid "Login to connect to sftp server"
|
||||
msgstr "Accedere per collegarsi al server SFTP"
|
||||
|
||||
#. module: storage_backend_sftp
|
||||
#: model:ir.model.fields.selection,name:storage_backend_sftp.selection__storage_backend__sftp_auth_method__pwd
|
||||
msgid "Password"
|
||||
msgstr "Password"
|
||||
|
||||
#. module: storage_backend_sftp
|
||||
#: model:ir.model.fields.selection,name:storage_backend_sftp.selection__storage_backend__sftp_auth_method__ssh_key
|
||||
msgid "Private key"
|
||||
msgstr "Chiave privata"
|
||||
|
||||
#. module: storage_backend_sftp
|
||||
#: model:ir.model.fields.selection,name:storage_backend_sftp.selection__storage_backend__backend_type__sftp
|
||||
#: model_terms:ir.ui.view,arch_db:storage_backend_sftp.storage_backend_view_form
|
||||
msgid "SFTP"
|
||||
msgstr "SFTP"
|
||||
|
||||
#. module: storage_backend_sftp
|
||||
#: model:ir.model.fields,field_description:storage_backend_sftp.field_storage_backend__sftp_auth_method
|
||||
msgid "SFTP Authentification Method"
|
||||
msgstr "Metodo autenticazione SFTP"
|
||||
|
||||
#. module: storage_backend_sftp
|
||||
#: model:ir.model.fields,field_description:storage_backend_sftp.field_storage_backend__sftp_server
|
||||
msgid "SFTP Host"
|
||||
msgstr "Host SFTP"
|
||||
|
||||
#. module: storage_backend_sftp
|
||||
#: model:ir.model.fields,field_description:storage_backend_sftp.field_storage_backend__sftp_login
|
||||
msgid "SFTP Login"
|
||||
msgstr "Accesso SFTP"
|
||||
|
||||
#. module: storage_backend_sftp
|
||||
#: model:ir.model.fields,field_description:storage_backend_sftp.field_storage_backend__sftp_password
|
||||
msgid "SFTP Password"
|
||||
msgstr "Password SFTP"
|
||||
|
||||
#. module: storage_backend_sftp
|
||||
#: model:ir.model.fields,field_description:storage_backend_sftp.field_storage_backend__sftp_port
|
||||
msgid "SFTP Port"
|
||||
msgstr "Porta SFTP"
|
||||
|
||||
#. module: storage_backend_sftp
|
||||
#: model:ir.model.fields,field_description:storage_backend_sftp.field_storage_backend__sftp_ssh_private_key
|
||||
msgid "SSH private key"
|
||||
msgstr "Chiave privata SSH"
|
||||
|
||||
#. module: storage_backend_sftp
|
||||
#: model:ir.model,name:storage_backend_sftp.model_storage_backend
|
||||
msgid "Storage Backend"
|
||||
msgstr "Backend deposito"
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * storage_backend_sftp
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 16.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#. module: storage_backend_sftp
|
||||
#: model:ir.model.fields,field_description:storage_backend_sftp.field_storage_backend__backend_type
|
||||
msgid "Backend Type"
|
||||
msgstr ""
|
||||
|
||||
#. module: storage_backend_sftp
|
||||
#: model:ir.model.fields,field_description:storage_backend_sftp.field_storage_backend__backend_type_env_default
|
||||
msgid "Backend Type Env Default"
|
||||
msgstr ""
|
||||
|
||||
#. module: storage_backend_sftp
|
||||
#: model:ir.model.fields,help:storage_backend_sftp.field_storage_backend__sftp_ssh_private_key
|
||||
msgid ""
|
||||
"It's recommended to not store the key here but to provide it via secret env "
|
||||
"variable. See `server_environment` docs."
|
||||
msgstr ""
|
||||
|
||||
#. module: storage_backend_sftp
|
||||
#: model:ir.model.fields,help:storage_backend_sftp.field_storage_backend__sftp_login
|
||||
msgid "Login to connect to sftp server"
|
||||
msgstr ""
|
||||
|
||||
#. module: storage_backend_sftp
|
||||
#: model:ir.model.fields.selection,name:storage_backend_sftp.selection__storage_backend__sftp_auth_method__pwd
|
||||
msgid "Password"
|
||||
msgstr ""
|
||||
|
||||
#. module: storage_backend_sftp
|
||||
#: model:ir.model.fields.selection,name:storage_backend_sftp.selection__storage_backend__sftp_auth_method__ssh_key
|
||||
msgid "Private key"
|
||||
msgstr ""
|
||||
|
||||
#. module: storage_backend_sftp
|
||||
#: model:ir.model.fields.selection,name:storage_backend_sftp.selection__storage_backend__backend_type__sftp
|
||||
#: model_terms:ir.ui.view,arch_db:storage_backend_sftp.storage_backend_view_form
|
||||
msgid "SFTP"
|
||||
msgstr ""
|
||||
|
||||
#. module: storage_backend_sftp
|
||||
#: model:ir.model.fields,field_description:storage_backend_sftp.field_storage_backend__sftp_auth_method
|
||||
msgid "SFTP Authentification Method"
|
||||
msgstr ""
|
||||
|
||||
#. module: storage_backend_sftp
|
||||
#: model:ir.model.fields,field_description:storage_backend_sftp.field_storage_backend__sftp_server
|
||||
msgid "SFTP Host"
|
||||
msgstr ""
|
||||
|
||||
#. module: storage_backend_sftp
|
||||
#: model:ir.model.fields,field_description:storage_backend_sftp.field_storage_backend__sftp_login
|
||||
msgid "SFTP Login"
|
||||
msgstr ""
|
||||
|
||||
#. module: storage_backend_sftp
|
||||
#: model:ir.model.fields,field_description:storage_backend_sftp.field_storage_backend__sftp_password
|
||||
msgid "SFTP Password"
|
||||
msgstr ""
|
||||
|
||||
#. module: storage_backend_sftp
|
||||
#: model:ir.model.fields,field_description:storage_backend_sftp.field_storage_backend__sftp_port
|
||||
msgid "SFTP Port"
|
||||
msgstr ""
|
||||
|
||||
#. module: storage_backend_sftp
|
||||
#: model:ir.model.fields,field_description:storage_backend_sftp.field_storage_backend__sftp_ssh_private_key
|
||||
msgid "SSH private key"
|
||||
msgstr ""
|
||||
|
||||
#. module: storage_backend_sftp
|
||||
#: model:ir.model,name:storage_backend_sftp.model_storage_backend
|
||||
msgid "Storage Backend"
|
||||
msgstr ""
|
||||
|
|
@ -0,0 +1 @@
|
|||
from . import storage_backend
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
# Copyright 2017 Akretion (http://www.akretion.com).
|
||||
# @author Sébastien BEAU <sebastien.beau@akretion.com>
|
||||
# Copyright 2019 Camptocamp SA (http://www.camptocamp.com).
|
||||
# @author Simone Orsi <simone.orsi@camptocamp.com>
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class StorageBackend(models.Model):
|
||||
_inherit = "storage.backend"
|
||||
|
||||
backend_type = fields.Selection(
|
||||
selection_add=[("sftp", "SFTP")], ondelete={"sftp": "set default"}
|
||||
)
|
||||
sftp_server = fields.Char(string="SFTP Host")
|
||||
sftp_port = fields.Integer(string="SFTP Port", default=22)
|
||||
sftp_auth_method = fields.Selection(
|
||||
string="SFTP Authentification Method",
|
||||
selection=[("pwd", "Password"), ("ssh_key", "Private key")],
|
||||
default="pwd",
|
||||
required=True,
|
||||
)
|
||||
sftp_login = fields.Char(
|
||||
string="SFTP Login", help="Login to connect to sftp server"
|
||||
)
|
||||
sftp_password = fields.Char(string="SFTP Password")
|
||||
sftp_ssh_private_key = fields.Text(
|
||||
string="SSH private key",
|
||||
help="It's recommended to not store the key here "
|
||||
"but to provide it via secret env variable. "
|
||||
"See `server_environment` docs.",
|
||||
)
|
||||
|
||||
@property
|
||||
def _server_env_fields(self):
|
||||
env_fields = super()._server_env_fields
|
||||
env_fields.update(
|
||||
{
|
||||
"sftp_password": {},
|
||||
"sftp_login": {},
|
||||
"sftp_server": {},
|
||||
"sftp_port": {},
|
||||
"sftp_auth_method": {},
|
||||
"sftp_ssh_private_key": {},
|
||||
}
|
||||
)
|
||||
return env_fields
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 9.2 KiB |
|
|
@ -0,0 +1 @@
|
|||
from . import test_sftp
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
# Copyright 2017 Akretion (http://www.akretion.com).
|
||||
# @author Sébastien BEAU <sebastien.beau@akretion.com>
|
||||
# Copyright 2019 Camptocamp (http://www.camptocamp.com).
|
||||
# @author Simone Orsi <simone.orsi@camptocamp.com>
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
|
||||
|
||||
# pylint: disable=missing-manifest-dependency
|
||||
# disable warning on 'vcr' missing in manifest: this is only a dependency for
|
||||
# dev/tests
|
||||
|
||||
import errno
|
||||
import logging
|
||||
import os
|
||||
from unittest import mock
|
||||
|
||||
from odoo.addons.storage_backend.tests.common import BackendStorageTestMixin, CommonCase
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
MOD_PATH = "odoo.addons.storage_backend_sftp.components.sftp_adapter"
|
||||
ADAPTER_PATH = MOD_PATH + ".SFTPStorageBackendAdapter"
|
||||
PARAMIKO_PATH = MOD_PATH + ".paramiko"
|
||||
|
||||
|
||||
class SftpCase(CommonCase, BackendStorageTestMixin):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.backend.write(
|
||||
{
|
||||
"backend_type": "sftp",
|
||||
"sftp_login": "foo",
|
||||
"sftp_password": "pass",
|
||||
"sftp_server": os.environ.get("SFTP_HOST", "localhost"),
|
||||
"sftp_port": os.environ.get("SFTP_PORT", "2222"),
|
||||
"directory_path": "upload",
|
||||
}
|
||||
)
|
||||
cls.case_with_subdirectory = "upload/subdirectory/here"
|
||||
|
||||
@mock.patch(MOD_PATH + ".sftp_mkdirs")
|
||||
@mock.patch(PARAMIKO_PATH)
|
||||
def test_add(self, mocked_paramiko, mocked_mkdirs):
|
||||
client = mocked_paramiko.SFTPClient.from_transport()
|
||||
# simulate errors
|
||||
exc = IOError()
|
||||
# general
|
||||
client.stat.side_effect = exc
|
||||
with self.assertRaises(IOError):
|
||||
self.backend.add("fake/path", b"fake data")
|
||||
# not found
|
||||
exc.errno = errno.ENOENT
|
||||
client.stat.side_effect = exc
|
||||
fakefile = open("/tmp/fakefile.txt", "w+b")
|
||||
client.open.return_value = fakefile
|
||||
self.backend.add("fake/path", b"fake data")
|
||||
# mkdirs has been called
|
||||
mocked_mkdirs.assert_called()
|
||||
# file has been written and closed
|
||||
self.assertTrue(fakefile.closed)
|
||||
with open("/tmp/fakefile.txt", "r") as thefile:
|
||||
self.assertEqual(thefile.read(), "fake data")
|
||||
|
||||
@mock.patch(PARAMIKO_PATH)
|
||||
def test_get(self, mocked_paramiko):
|
||||
client = mocked_paramiko.SFTPClient.from_transport()
|
||||
with open("/tmp/fakefile2.txt", "w+b") as fakefile:
|
||||
fakefile.write(b"filecontent")
|
||||
client.open.return_value = open("/tmp/fakefile2.txt", "r")
|
||||
self.assertEqual(self.backend.get("fake/path"), "filecontent")
|
||||
|
||||
@mock.patch(PARAMIKO_PATH)
|
||||
def test_list(self, mocked_paramiko):
|
||||
client = mocked_paramiko.SFTPClient.from_transport()
|
||||
# simulate errors
|
||||
exc = IOError()
|
||||
# general
|
||||
client.listdir.side_effect = exc
|
||||
with self.assertRaises(IOError):
|
||||
self.backend.list_files()
|
||||
# not found
|
||||
exc.errno = errno.ENOENT
|
||||
client.listdir.side_effect = exc
|
||||
self.assertEqual(self.backend.list_files(), [])
|
||||
|
||||
def test_find_files(self):
|
||||
good_filepaths = ["somepath/file%d.good" % x for x in range(1, 10)]
|
||||
bad_filepaths = ["somepath/file%d.bad" % x for x in range(1, 10)]
|
||||
mocked_filepaths = bad_filepaths + good_filepaths
|
||||
backend = self.backend.sudo()
|
||||
expected = good_filepaths[:]
|
||||
expected = [backend.directory_path + "/" + path for path in good_filepaths]
|
||||
self._test_find_files(
|
||||
backend, ADAPTER_PATH, mocked_filepaths, r".*\.good$", expected
|
||||
)
|
||||
|
||||
@mock.patch(PARAMIKO_PATH)
|
||||
def test_move_files(self, mocked_paramiko):
|
||||
client = mocked_paramiko.SFTPClient.from_transport()
|
||||
# simulate file is not already there
|
||||
client.lstat.side_effect = FileNotFoundError()
|
||||
to_move = "move/from/path/myfile.txt"
|
||||
to_path = "move/to/path"
|
||||
self.backend.move_files([to_move], to_path)
|
||||
# no need to delete it
|
||||
client.unlink.assert_not_called()
|
||||
# rename gets called
|
||||
client.rename.assert_called_with(
|
||||
"upload/" + to_move, "upload/" + to_move.replace("from", "to")
|
||||
)
|
||||
# now try to override destination
|
||||
client.lstat.side_effect = None
|
||||
client.lstat.return_value = True
|
||||
self.backend.move_files([to_move], to_path)
|
||||
# client will delete it first
|
||||
client.unlink.assert_called_with(to_move.replace("from", "to"))
|
||||
# then move it
|
||||
client.rename.assert_called_with(
|
||||
"upload/" + to_move, "upload/" + to_move.replace("from", "to")
|
||||
)
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<record id="storage_backend_view_form" model="ir.ui.view">
|
||||
<field name="model">storage.backend</field>
|
||||
<field name="inherit_id" ref="storage_backend.storage_backend_view_form" />
|
||||
<field name="arch" type="xml">
|
||||
<group name="config" position="after">
|
||||
<group
|
||||
name="sftp"
|
||||
string="SFTP"
|
||||
attrs="{'invisible': [('backend_type', '!=', 'sftp')]}"
|
||||
>
|
||||
<field name="sftp_server" />
|
||||
<field name="sftp_port" />
|
||||
<field name="sftp_auth_method" />
|
||||
<field name="sftp_login" />
|
||||
<field
|
||||
name="sftp_password"
|
||||
password="True"
|
||||
attrs="{'invisible': [('sftp_auth_method', '!=', 'pwd')]}"
|
||||
/>
|
||||
<field
|
||||
name="sftp_ssh_private_key"
|
||||
password="True"
|
||||
attrs="{'invisible': [('sftp_auth_method', '!=', 'ssh_key')]}"
|
||||
/>
|
||||
</group>
|
||||
</group>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
Loading…
Add table
Add a link
Reference in a new issue