mirror of
https://github.com/camptocamp/odoo-cloud-platform.git
synced 2026-06-24 02:08:36 +00:00
Change CI to GitHub actions
Use copier template from oca/oca-addons-repo-template Target Python3.8 Apply linting Fix a missing call to super
This commit is contained in:
@@ -2,20 +2,18 @@
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
|
||||
|
||||
|
||||
{'name': 'Attachments on Swift storage',
|
||||
'summary': 'Store assets and attachments on a Swift compatible object store',
|
||||
'version': "14.0.1.0.0",
|
||||
'author': 'Camptocamp,Odoo Community Association (OCA)',
|
||||
'license': 'AGPL-3',
|
||||
'category': 'Knowledge Management',
|
||||
'depends': ['base_attachment_object_storage'],
|
||||
'external_dependencies': {
|
||||
'python': ['swiftclient',
|
||||
'keystoneclient',
|
||||
'keystoneauth1',
|
||||
],
|
||||
},
|
||||
'website': 'https://www.camptocamp.com',
|
||||
'data': [],
|
||||
'installable': True,
|
||||
}
|
||||
{
|
||||
"name": "Attachments on Swift storage",
|
||||
"summary": "Store assets and attachments on a Swift compatible object store",
|
||||
"version": "14.0.1.0.0",
|
||||
"author": "Camptocamp,Odoo Community Association (OCA)",
|
||||
"license": "AGPL-3",
|
||||
"category": "Knowledge Management",
|
||||
"depends": ["base_attachment_object_storage"],
|
||||
"external_dependencies": {
|
||||
"python": ["swiftclient", "keystoneclient", "keystoneauth1"],
|
||||
},
|
||||
"website": "https://github.com/camptocamp/odoo-cloud-platform",
|
||||
"data": [],
|
||||
"installable": True,
|
||||
}
|
||||
|
||||
@@ -4,17 +4,18 @@
|
||||
|
||||
import logging
|
||||
import os
|
||||
from ..swift_uri import SwiftUri
|
||||
|
||||
from odoo import api, exceptions, models, _
|
||||
from odoo import _, api, exceptions, models
|
||||
|
||||
from ..swift_uri import SwiftUri
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
import swiftclient
|
||||
import keystoneauth1
|
||||
import keystoneauth1.identity
|
||||
import keystoneauth1.session
|
||||
import swiftclient
|
||||
from swiftclient.exceptions import ClientException
|
||||
except ImportError:
|
||||
swiftclient = None
|
||||
@@ -48,8 +49,9 @@ class SwiftSessionStore(object):
|
||||
def _get_key(self, auth_url, username, password, project_name):
|
||||
return (auth_url, username, password, project_name)
|
||||
|
||||
def get_session(self, auth_url=None, username=None, password=None,
|
||||
project_name=None):
|
||||
def get_session(
|
||||
self, auth_url=None, username=None, password=None, project_name=None
|
||||
):
|
||||
key = self._get_key(auth_url, username, password, project_name)
|
||||
session = self._sessions.get(key)
|
||||
if not session:
|
||||
@@ -58,8 +60,8 @@ class SwiftSessionStore(object):
|
||||
password=password,
|
||||
project_name=project_name,
|
||||
auth_url=auth_url,
|
||||
project_domain_id='default',
|
||||
user_domain_id='default',
|
||||
project_domain_id="default",
|
||||
user_domain_id="default",
|
||||
)
|
||||
session = keystoneauth1.session.Session(
|
||||
auth=auth,
|
||||
@@ -73,36 +75,38 @@ swift_session_store = SwiftSessionStore()
|
||||
|
||||
|
||||
class IrAttachment(models.Model):
|
||||
_inherit = 'ir.attachment'
|
||||
_inherit = "ir.attachment"
|
||||
|
||||
def _get_stores(self):
|
||||
l = ['swift']
|
||||
l += super()._get_stores()
|
||||
return l
|
||||
stores = ["swift"]
|
||||
stores += super()._get_stores()
|
||||
return stores
|
||||
|
||||
@api.model
|
||||
def _get_swift_connection(self):
|
||||
""" Returns a connection object for the Swift object store """
|
||||
host = os.environ.get('SWIFT_AUTH_URL')
|
||||
account = os.environ.get('SWIFT_ACCOUNT')
|
||||
password = os.environ.get('SWIFT_PASSWORD')
|
||||
project_name = os.environ.get('SWIFT_PROJECT_NAME')
|
||||
if not project_name and os.environ.get('SWIFT_TENANT_NAME'):
|
||||
project_name = os.environ['SWIFT_TENANT_NAME']
|
||||
"""Returns a connection object for the Swift object store"""
|
||||
host = os.environ.get("SWIFT_AUTH_URL")
|
||||
account = os.environ.get("SWIFT_ACCOUNT")
|
||||
password = os.environ.get("SWIFT_PASSWORD")
|
||||
project_name = os.environ.get("SWIFT_PROJECT_NAME")
|
||||
if not project_name and os.environ.get("SWIFT_TENANT_NAME"):
|
||||
project_name = os.environ["SWIFT_TENANT_NAME"]
|
||||
_logger.warning(
|
||||
"SWIFT_TENANT_NAME is deprecated and "
|
||||
"must be replaced by SWIFT_PROJECT_NAME"
|
||||
)
|
||||
region = os.environ.get('SWIFT_REGION_NAME')
|
||||
region = os.environ.get("SWIFT_REGION_NAME")
|
||||
os_options = {}
|
||||
if region:
|
||||
os_options['region_name'] = region
|
||||
os_options["region_name"] = region
|
||||
if not (host and account and password and project_name):
|
||||
raise exceptions.UserError(_(
|
||||
"Problem connecting to Swift store, are the env variables "
|
||||
"(SWIFT_AUTH_URL, SWIFT_ACCOUNT, SWIFT_PASSWORD, "
|
||||
"SWIFT_TENANT_NAME) properly set?"
|
||||
))
|
||||
raise exceptions.UserError(
|
||||
_(
|
||||
"Problem connecting to Swift store, are the env variables "
|
||||
"(SWIFT_AUTH_URL, SWIFT_ACCOUNT, SWIFT_PASSWORD, "
|
||||
"SWIFT_TENANT_NAME) properly set?"
|
||||
)
|
||||
)
|
||||
try:
|
||||
session = swift_session_store.get_session(
|
||||
username=account,
|
||||
@@ -115,13 +119,13 @@ class IrAttachment(models.Model):
|
||||
os_options=os_options,
|
||||
)
|
||||
except ClientException:
|
||||
_logger.exception('Error connecting to Swift object store')
|
||||
raise exceptions.UserError(_('Error on Swift connection'))
|
||||
_logger.exception("Error connecting to Swift object store")
|
||||
raise exceptions.UserError(_("Error on Swift connection"))
|
||||
return conn
|
||||
|
||||
@api.model
|
||||
def _store_file_read(self, fname):
|
||||
if fname.startswith('swift://'):
|
||||
if fname.startswith("swift://"):
|
||||
swifturi = SwiftUri(fname)
|
||||
try:
|
||||
conn = self._get_swift_connection()
|
||||
@@ -129,31 +133,27 @@ class IrAttachment(models.Model):
|
||||
_logger.exception(
|
||||
"error reading attachment '%s' from object storage", fname
|
||||
)
|
||||
return ''
|
||||
return ""
|
||||
try:
|
||||
resp, read = conn.get_object(
|
||||
swifturi.container(),
|
||||
swifturi.item()
|
||||
)
|
||||
resp, read = conn.get_object(swifturi.container(), swifturi.item())
|
||||
except ClientException:
|
||||
read = ''
|
||||
_logger.exception(
|
||||
'Error reading object from Swift object store')
|
||||
read = ""
|
||||
_logger.exception("Error reading object from Swift object store")
|
||||
return read
|
||||
else:
|
||||
return super()._store_file_read(fname)
|
||||
|
||||
def _store_file_write(self, key, bin_data):
|
||||
if self._storage() == 'swift':
|
||||
container = os.environ.get('SWIFT_WRITE_CONTAINER')
|
||||
if self._storage() == "swift":
|
||||
container = os.environ.get("SWIFT_WRITE_CONTAINER")
|
||||
conn = self._get_swift_connection()
|
||||
conn.put_container(container)
|
||||
filename = 'swift://{}/{}'.format(container, key)
|
||||
filename = "swift://{}/{}".format(container, key)
|
||||
try:
|
||||
conn.put_object(container, key, bin_data)
|
||||
except ClientException:
|
||||
_logger.exception('Error writing to Swift object store')
|
||||
raise exceptions.UserError(_('Error writing to Swift'))
|
||||
_logger.exception("Error writing to Swift object store")
|
||||
raise exceptions.UserError(_("Error writing to Swift"))
|
||||
else:
|
||||
_super = super()
|
||||
filename = _super._store_file_write(key, bin_data)
|
||||
@@ -161,18 +161,17 @@ class IrAttachment(models.Model):
|
||||
|
||||
@api.model
|
||||
def _store_file_delete(self, fname):
|
||||
if fname.startswith('swift://'):
|
||||
if fname.startswith("swift://"):
|
||||
swifturi = SwiftUri(fname)
|
||||
container = swifturi.container()
|
||||
# delete the file only if it is on the current configured bucket
|
||||
# otherwise, we might delete files used on a different environment
|
||||
if container == os.environ.get('SWIFT_WRITE_CONTAINER'):
|
||||
if container == os.environ.get("SWIFT_WRITE_CONTAINER"):
|
||||
conn = self._get_swift_connection()
|
||||
try:
|
||||
conn.delete_object(container, swifturi.item())
|
||||
except ClientException:
|
||||
_logger.exception(
|
||||
_('Error deleting an object on the Swift store'))
|
||||
_logger.exception(_("Error deleting an object on the Swift store"))
|
||||
# we ignore the error, file will stay on the object
|
||||
# storage but won't disrupt the process
|
||||
else:
|
||||
|
||||
@@ -6,8 +6,7 @@ import re
|
||||
|
||||
class SwiftUri(object):
|
||||
|
||||
_url_re = re.compile("^swift:///*([^/]*)/?(.*)",
|
||||
re.IGNORECASE | re.UNICODE)
|
||||
_url_re = re.compile("^swift:///*([^/]*)/?(.*)", re.IGNORECASE | re.UNICODE)
|
||||
|
||||
def __init__(self, uri):
|
||||
match = self._url_re.match(uri)
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
|
||||
from . import test_mock_swift_api
|
||||
|
||||
@@ -2,30 +2,27 @@
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
|
||||
|
||||
import base64
|
||||
import mock
|
||||
import os
|
||||
|
||||
import keystoneauth1
|
||||
import mock
|
||||
from mock import patch
|
||||
|
||||
import keystoneauth1
|
||||
|
||||
from odoo.addons.base.tests.test_ir_attachment import TestIrAttachment
|
||||
from odoo.addons.attachment_swift.models.ir_attachment import SwiftSessionStore
|
||||
from odoo.addons.attachment_swift.swift_uri import SwiftUri
|
||||
from odoo.addons.base.tests.test_ir_attachment import TestIrAttachment
|
||||
|
||||
|
||||
class TestAttachmentSwift(TestIrAttachment):
|
||||
|
||||
def setup(self):
|
||||
super().setUp()
|
||||
self.env['ir.config_parameter'].set_param('ir_attachment.location',
|
||||
'swift')
|
||||
self.env["ir.config_parameter"].set_param("ir_attachment.location", "swift")
|
||||
|
||||
def test_session_store_get_session(self):
|
||||
auth_url = 'auth_url'
|
||||
username = 'username'
|
||||
password = 'password'
|
||||
project_name = 'project_name'
|
||||
auth_url = "auth_url"
|
||||
username = "username"
|
||||
password = "password"
|
||||
project_name = "project_name"
|
||||
store = SwiftSessionStore()
|
||||
session = store.get_session(
|
||||
auth_url=auth_url,
|
||||
@@ -34,10 +31,12 @@ class TestAttachmentSwift(TestIrAttachment):
|
||||
project_name=project_name,
|
||||
)
|
||||
self.assertEqual(session.auth.auth_url, auth_url)
|
||||
self.assertEqual(session.auth.get_cache_id_elements().get(
|
||||
'password_username'), username)
|
||||
self.assertEqual(session.auth.get_cache_id_elements().get(
|
||||
'password_password'), password)
|
||||
self.assertEqual(
|
||||
session.auth.get_cache_id_elements().get("password_username"), username
|
||||
)
|
||||
self.assertEqual(
|
||||
session.auth.get_cache_id_elements().get("password_password"), password
|
||||
)
|
||||
self.assertEqual(session.auth.project_name, project_name)
|
||||
|
||||
# get the same session on a second call
|
||||
@@ -48,73 +47,73 @@ class TestAttachmentSwift(TestIrAttachment):
|
||||
password=password,
|
||||
project_name=project_name,
|
||||
),
|
||||
session
|
||||
session,
|
||||
)
|
||||
|
||||
@patch('swiftclient.client')
|
||||
@patch("swiftclient.client")
|
||||
def test_connection(self, mock_swift_client):
|
||||
""" Test the connection to the store"""
|
||||
os.environ['SWIFT_AUTH_URL'] = 'auth_url'
|
||||
os.environ['SWIFT_ACCOUNT'] = 'account'
|
||||
os.environ['SWIFT_PASSWORD'] = 'password'
|
||||
os.environ['SWIFT_PROJECT_NAME'] = 'project_name'
|
||||
os.environ['SWIFT_REGION_NAME'] = 'NOWHERE'
|
||||
"""Test the connection to the store"""
|
||||
os.environ["SWIFT_AUTH_URL"] = "auth_url"
|
||||
os.environ["SWIFT_ACCOUNT"] = "account"
|
||||
os.environ["SWIFT_PASSWORD"] = "password"
|
||||
os.environ["SWIFT_PROJECT_NAME"] = "project_name"
|
||||
os.environ["SWIFT_REGION_NAME"] = "NOWHERE"
|
||||
attachment = self.Attachment
|
||||
attachment._get_swift_connection()
|
||||
mock_swift_client.Connection.assert_called_once_with(
|
||||
session=mock.ANY,
|
||||
os_options={'region_name': os.environ.get('SWIFT_REGION_NAME')},
|
||||
os_options={"region_name": os.environ.get("SWIFT_REGION_NAME")},
|
||||
)
|
||||
__, kwargs = mock_swift_client.Connection.call_args
|
||||
session = kwargs['session']
|
||||
session = kwargs["session"]
|
||||
self.assertTrue(isinstance(session, keystoneauth1.session.Session))
|
||||
self.assertEqual(session.auth.auth_url, os.environ['SWIFT_AUTH_URL'])
|
||||
self.assertEqual(session.auth.get_cache_id_elements().get(
|
||||
'password_username'), os.environ['SWIFT_ACCOUNT'])
|
||||
self.assertEqual(session.auth.get_cache_id_elements().get(
|
||||
'password_password'), os.environ['SWIFT_PASSWORD'])
|
||||
self.assertEqual(session.auth.project_name,
|
||||
os.environ['SWIFT_PROJECT_NAME'])
|
||||
self.assertEqual(session.auth.auth_url, os.environ["SWIFT_AUTH_URL"])
|
||||
self.assertEqual(
|
||||
session.auth.get_cache_id_elements().get("password_username"),
|
||||
os.environ["SWIFT_ACCOUNT"],
|
||||
)
|
||||
self.assertEqual(
|
||||
session.auth.get_cache_id_elements().get("password_password"),
|
||||
os.environ["SWIFT_PASSWORD"],
|
||||
)
|
||||
self.assertEqual(session.auth.project_name, os.environ["SWIFT_PROJECT_NAME"])
|
||||
|
||||
def test_store_file_on_swift(self):
|
||||
"""
|
||||
Test writing a file
|
||||
Test writing a file
|
||||
"""
|
||||
(self.env['ir.config_parameter'].
|
||||
set_param('ir_attachment.location', 'swift'))
|
||||
os.environ['SWIFT_AUTH_URL'] = 'auth_url'
|
||||
os.environ['SWIFT_ACCOUNT'] = 'account'
|
||||
os.environ['SWIFT_PASSWORD'] = 'password'
|
||||
os.environ['SWIFT_PROJECT_NAME'] = 'project_name'
|
||||
os.environ['SWIFT_WRITE_CONTAINER'] = 'my_container'
|
||||
container = os.environ.get('SWIFT_WRITE_CONTAINER')
|
||||
(self.env["ir.config_parameter"].set_param("ir_attachment.location", "swift"))
|
||||
os.environ["SWIFT_AUTH_URL"] = "auth_url"
|
||||
os.environ["SWIFT_ACCOUNT"] = "account"
|
||||
os.environ["SWIFT_PASSWORD"] = "password"
|
||||
os.environ["SWIFT_PROJECT_NAME"] = "project_name"
|
||||
os.environ["SWIFT_WRITE_CONTAINER"] = "my_container"
|
||||
container = os.environ.get("SWIFT_WRITE_CONTAINER")
|
||||
attachment = self.Attachment
|
||||
bin_data = base64.b64decode(self.blob1_b64)
|
||||
with patch('swiftclient.client.Connection') as MockConnection:
|
||||
with patch("swiftclient.client.Connection") as MockConnection:
|
||||
conn = MockConnection.return_value
|
||||
attachment.create({'name': 'a5', 'datas': self.blob1_b64})
|
||||
attachment.create({"name": "a5", "datas": self.blob1_b64})
|
||||
conn.put_object.assert_called_with(
|
||||
container,
|
||||
attachment._compute_checksum(bin_data),
|
||||
bin_data)
|
||||
container, attachment._compute_checksum(bin_data), bin_data
|
||||
)
|
||||
|
||||
def test_delete_file_on_swift(self):
|
||||
"""
|
||||
Test deleting a file
|
||||
Test deleting a file
|
||||
"""
|
||||
(self.env['ir.config_parameter'].
|
||||
set_param('ir_attachment.location', 'swift'))
|
||||
os.environ['SWIFT_AUTH_URL'] = 'auth_url'
|
||||
os.environ['SWIFT_ACCOUNT'] = 'account'
|
||||
os.environ['SWIFT_PASSWORD'] = 'password'
|
||||
os.environ['SWIFT_PROJECT_NAME'] = 'project_name'
|
||||
os.environ['SWIFT_WRITE_CONTAINER'] = 'my_container'
|
||||
(self.env["ir.config_parameter"].set_param("ir_attachment.location", "swift"))
|
||||
os.environ["SWIFT_AUTH_URL"] = "auth_url"
|
||||
os.environ["SWIFT_ACCOUNT"] = "account"
|
||||
os.environ["SWIFT_PASSWORD"] = "password"
|
||||
os.environ["SWIFT_PROJECT_NAME"] = "project_name"
|
||||
os.environ["SWIFT_WRITE_CONTAINER"] = "my_container"
|
||||
|
||||
attachment = self.Attachment
|
||||
container = os.environ.get('SWIFT_WRITE_CONTAINER')
|
||||
with patch('swiftclient.client.Connection') as MockConnection:
|
||||
container = os.environ.get("SWIFT_WRITE_CONTAINER")
|
||||
with patch("swiftclient.client.Connection") as MockConnection:
|
||||
conn = MockConnection.return_value
|
||||
a5 = attachment.create({'name': 'a5', 'datas': self.blob1_b64})
|
||||
a5 = attachment.create({"name": "a5", "datas": self.blob1_b64})
|
||||
uri = SwiftUri(a5.store_fname)
|
||||
a5.unlink()
|
||||
conn.delete_object.assert_called_with(container, uri.item())
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
# Copyright 2017-2019 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
|
||||
|
||||
from odoo.addons.base.tests.test_ir_attachment import TestIrAttachment
|
||||
from ..swift_uri import SwiftUri
|
||||
from swiftclient.exceptions import ClientException
|
||||
|
||||
from odoo.addons.base.tests.test_ir_attachment import TestIrAttachment
|
||||
|
||||
from ..swift_uri import SwiftUri
|
||||
|
||||
|
||||
class TestAttachmentSwift(TestIrAttachment):
|
||||
"""
|
||||
@@ -13,27 +15,24 @@ class TestAttachmentSwift(TestIrAttachment):
|
||||
|
||||
def setup(self):
|
||||
super().setUp()
|
||||
self.env['ir.config_parameter'].set_param('ir_attachment.location',
|
||||
'swift')
|
||||
self.env["ir.config_parameter"].set_param("ir_attachment.location", "swift")
|
||||
|
||||
def test_connection(self):
|
||||
""" Test the connection to the Swift object store """
|
||||
"""Test the connection to the Swift object store"""
|
||||
conn = self.Attachment._get_swift_connection()
|
||||
self.assertNotEqual(conn, False)
|
||||
|
||||
def test_store_file_on_swift(self):
|
||||
""" Test writing a file and then reading it """
|
||||
(self.env['ir.config_parameter'].
|
||||
set_param('ir_attachment.location', 'swift'))
|
||||
a5 = self.Attachment.create({'name': 'a5', 'datas': self.blob1_b64})
|
||||
"""Test writing a file and then reading it"""
|
||||
(self.env["ir.config_parameter"].set_param("ir_attachment.location", "swift"))
|
||||
a5 = self.Attachment.create({"name": "a5", "datas": self.blob1_b64})
|
||||
a5bis = self.Attachment.browse(a5.id)[0]
|
||||
self.assertEqual(a5.datas, a5bis.datas)
|
||||
|
||||
def test_delete_file_on_swift(self):
|
||||
""" Create a file and then test the deletion """
|
||||
(self.env['ir.config_parameter'].
|
||||
set_param('ir_attachment.location', 'swift'))
|
||||
a5 = self.Attachment.create({'name': 'a5', 'datas': self.blob1_b64})
|
||||
"""Create a file and then test the deletion"""
|
||||
(self.env["ir.config_parameter"].set_param("ir_attachment.location", "swift"))
|
||||
a5 = self.Attachment.create({"name": "a5", "datas": self.blob1_b64})
|
||||
uri = SwiftUri(a5.store_fname)
|
||||
con = self.Attachment._get_swift_connection()
|
||||
con.get_object(uri.container(), uri.item())
|
||||
|
||||
Reference in New Issue
Block a user