Skip to content

Commit 0b3aa86

Browse files
authored
Merge pull request #243 from qld-gov-au/feature/custom-xloader-site-url_rebased
Feature/custom xloader site url rebased: ``` - key: ckanext.xloader.site_url example: http://ckan-dev:5000 default: description: | Provide an alternate site URL for the xloader_submit action. This is useful, for example, when the site is running within a docker network. Note: This setting will not alter path. i.e ckan.root_path required: false - key: ckanext.xloader.site_url_ignore_path_regex example: "(/PathToS3HostOriginIWantToGoDirectTo|/anotherPath)" default: description: | Provide the ability to ignore paths which can't be mapped to alternative site URL for resource access. This is useful, for example, when the site is running within a docker network and the cdn front door has Blob storage mapped to another path on the same domain. required: false ```
2 parents 6cca0b6 + ad5ea42 commit 0b3aa86

File tree

13 files changed

+263
-146
lines changed

13 files changed

+263
-146
lines changed

.github/workflows/publish.yml

Lines changed: 4 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ jobs:
3434

3535
validateVersion:
3636
runs-on: ubuntu-latest
37+
if: github.repository == 'ckan/ckanext-xloader'
3738
steps:
3839
- uses: actions/checkout@v4
3940

@@ -52,79 +53,10 @@ jobs:
5253
exit 1
5354
fi
5455
55-
lint:
56-
needs: validateVersion
57-
if: github.repository == 'ckan/ckanext-xloader'
58-
runs-on: ubuntu-latest
59-
steps:
60-
- uses: actions/checkout@v4
61-
62-
- uses: actions/setup-python@v5
63-
with:
64-
python-version: '3.10'
65-
66-
- name: Install requirements
67-
run: pip install flake8 pycodestyle
68-
69-
- name: Check syntax
70-
run: flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics --extend-exclude ckan
71-
7256
test:
73-
needs: lint
74-
strategy:
75-
matrix:
76-
include: #ckan-image see https://github.com/ckan/ckan-docker-base, ckan-version controls other image tags
77-
- ckan-version: "2.11"
78-
ckan-image: "2.11-py3.10"
79-
- ckan-version: "2.10"
80-
ckan-image: "2.10-py3.10"
81-
#- ckan-version: "master" Publish does not care about master
82-
# ckan-image: "master"
83-
fail-fast: false
84-
85-
name: CKAN ${{ matrix.ckan-version }}
86-
runs-on: ubuntu-latest
87-
container:
88-
image: ckan/ckan-dev:${{ matrix.ckan-image }}
89-
options: --user root
90-
services:
91-
solr:
92-
image: ckan/ckan-solr:${{ matrix.ckan-version }}-solr9
93-
postgres:
94-
image: ckan/ckan-postgres-dev:${{ matrix.ckan-version }}
95-
env:
96-
POSTGRES_USER: postgres
97-
POSTGRES_PASSWORD: postgres
98-
POSTGRES_DB: postgres
99-
ports:
100-
- 5432:5432
101-
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
102-
redis:
103-
image: redis:3
104-
env:
105-
CKAN_SQLALCHEMY_URL: postgresql://ckan_default:pass@postgres/ckan_test
106-
CKAN_DATASTORE_WRITE_URL: postgresql://datastore_write:pass@postgres/datastore_test
107-
CKAN_DATASTORE_READ_URL: postgresql://datastore_read:pass@postgres/datastore_test
108-
CKAN_SOLR_URL: http://solr:8983/solr/ckan
109-
CKAN_REDIS_URL: redis://redis:6379/1
110-
111-
steps:
112-
- uses: actions/checkout@v4
113-
- if: ${{ matrix.ckan-version == 2.9 }}
114-
run: pip install "setuptools>=44.1.0,<71"
115-
- name: Install requirements
116-
run: |
117-
pip install -r requirements.txt
118-
pip install -r dev-requirements.txt
119-
pip install -e .
120-
pip install -U requests[security]
121-
# Replace default path to CKAN core config file with the one on the container
122-
sed -i -e 's/use = config:.*/use = config:\/srv\/app\/src\/ckan\/test-core.ini/' test.ini
123-
- name: Setup extension (CKAN >= 2.9)
124-
run: |
125-
ckan -c test.ini db init
126-
- name: Run tests
127-
run: pytest --ckan-ini=test.ini --cov=ckanext.xloader --disable-warnings ckanext/xloader/tests
57+
needs: validateVersion
58+
name: Test
59+
uses: ./.github/workflows/test.yml # Call the reusable workflow
12860

12961
publishSkipped:
13062
if: github.repository != 'ckan/ckanext-xloader'

.github/workflows/test.yml

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ jobs:
3535
experimental: true # master is unstable, good to know if we are compatible or not
3636
fail-fast: false
3737

38-
name: CKAN ${{ matrix.ckan-version }}
38+
name: ${{ matrix.experimental && '**Fail_Ignored** ' || '' }} CKAN ${{ matrix.ckan-version }}
3939
runs-on: ubuntu-latest
4040
container:
4141
image: ckan/ckan-dev:${{ matrix.ckan-image }}
@@ -65,12 +65,7 @@ jobs:
6565
- uses: actions/checkout@v4
6666
continue-on-error: ${{ matrix.experimental }}
6767

68-
- name: Pin setuptools for ckan 2.9 only
69-
if: ${{ matrix.ckan-version == 2.9 }}
70-
run: pip install "setuptools>=44.1.0,<71"
71-
continue-on-error: ${{ matrix.experimental }}
72-
73-
- name: Install requirements
68+
- name: ${{ matrix.experimental && '**Fail_Ignored** ' || '' }} Install requirements
7469
continue-on-error: ${{ matrix.experimental }}
7570
run: |
7671
pip install -r requirements.txt
@@ -80,16 +75,20 @@ jobs:
8075
# Replace default path to CKAN core config file with the one on the container
8176
sed -i -e 's/use = config:.*/use = config:\/srv\/app\/src\/ckan\/test-core.ini/' test.ini
8277
83-
- name: Setup extension
78+
- name: ${{ matrix.experimental && '**Fail_Ignored** ' || '' }} Setup extension
8479
continue-on-error: ${{ matrix.experimental }}
8580
run: |
8681
ckan -c test.ini db init
82+
ckan -c test.ini user add ckan_admin email=ckan_admin@localhost password="AbCdEf12345!@#%"
83+
ckan -c test.ini sysadmin add ckan_admin
84+
ckan config-tool test.ini "ckanext.xloader.api_token=$(ckan -c test.ini user token add ckan_admin xloader | tail -n 1 | tr -d '\t')"
85+
ckan -c test.ini user list
8786
88-
- name: Run tests
87+
- name: ${{ matrix.experimental && '**Fail_Ignored** ' || '' }} Run tests
8988
continue-on-error: ${{ matrix.experimental }}
9089
run: pytest --ckan-ini=test.ini --cov=ckanext.xloader --disable-warnings ckanext/xloader/tests --junit-xml=/tmp/artifacts/junit/results.xml
9190

92-
- name: Test Summary
91+
- name: ${{ matrix.experimental && '**Fail_Ignored** ' || '' }} Test Summary
9392
uses: test-summary/action@v2
9493
continue-on-error: ${{ matrix.experimental }}
9594
with:

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ To install XLoader:
151151
execute jobs against the server:
152152

153153
ckanext.xloader.api_token = <your-CKAN-generated-API-Token>
154+
ckan config-tool test.ini "ckanext.xloader.api_token=$(ckan -c test.ini user token add ckan_admin xloader | tail -n 1 | tr -d '\t')"
154155

155156
6. If it is a production server, you'll want to store jobs info in a
156157
more robust database than the default sqlite file. It can happily
@@ -220,8 +221,7 @@ ckanext.xloader.api_token = eyJ0eXAiOiJKV1QiLCJh.eyJqdGkiOiJ0M2VNUFlQWFg0VU.8QgV
220221

221222
Default value: none
222223

223-
Uses a specific API token for the xloader_submit action instead of the
224-
apikey of the site_user. It's mandatory starting from CKAN 2.10. You can get one
224+
It's mandatory starting from CKAN 2.10. You can get one
225225
running the command `ckan user token add {USER_NAME} xloader -q`
226226

227227

ckanext/xloader/action.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ def xloader_submit(context, data_dict):
4747
:rtype: bool
4848
'''
4949
p.toolkit.check_access('xloader_submit', context, data_dict)
50+
api_key = utils.get_xloader_user_apitoken()
5051
custom_queue = data_dict.pop('queue', rq_jobs.DEFAULT_QUEUE_NAME)
5152
schema = context.get('schema', ckanext.xloader.schema.xloader_submit_schema())
5253
data_dict, errors = _validate(data_dict, schema, context)
@@ -147,7 +148,7 @@ def xloader_submit(context, data_dict):
147148
qualified=True
148149
)
149150
data = {
150-
'api_key': utils.get_xloader_user_apitoken(),
151+
'api_key': api_key,
151152
'job_type': 'xloader_to_datastore',
152153
'result_url': callback_url,
153154
'metadata': {

ckanext/xloader/command.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import ckan.plugins.toolkit as tk
66

77
from ckanext.xloader.jobs import xloader_data_into_datastore_
8-
from ckanext.xloader.utils import XLoaderFormats
8+
from ckanext.xloader.utils import XLoaderFormats, get_xloader_user_apitoken
99

1010

1111
class XloaderCmd:
@@ -117,7 +117,7 @@ def _submit_resource(self, resource, user, indent=0, sync=False, queue=None):
117117
data_dict['ckan_url'] = tk.config.get('ckan.site_url')
118118
input_dict = {
119119
'metadata': data_dict,
120-
'api_key': 'TODO'
120+
'api_key': get_xloader_user_apitoken()
121121
}
122122
logger = logging.getLogger('ckanext.xloader.cli')
123123
xloader_data_into_datastore_(input_dict, None, logger)

ckanext/xloader/config_declaration.yaml

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,22 @@ version: 1
22
groups:
33
- annotation: ckanext-xloader settings
44
options:
5+
- key: ckanext.xloader.site_url
6+
example: http://ckan-dev:5000
7+
default:
8+
description: |
9+
Provide an alternate site URL for the xloader_submit action.
10+
This is useful, for example, when the site is running within a docker network.
11+
Note: This setting will not alter path. i.e ckan.root_path
12+
required: false
13+
- key: ckanext.xloader.site_url_ignore_path_regex
14+
example: "(/PathToS3HostOriginIWantToGoDirectTo|/anotherPath)"
15+
default:
16+
description: |
17+
Provide the ability to ignore paths which can't be mapped to alternative site URL for resource access.
18+
This is useful, for example, when the site is running within a docker network and the cdn front door has
19+
Blob storage mapped to another path on the same domain.
20+
required: false
521
- key: ckanext.xloader.jobs_db.uri
622
default: sqlite:////tmp/xloader_jobs.db
723
description: |
@@ -14,9 +30,9 @@ groups:
1430
example: eyJ0eXAiOiJKV1QiLCJh.eyJqdGkiOiJ0M2VNUFlQWFg0VU.8QgV8em4RA
1531
description: |
1632
Uses a specific API token for the xloader_submit action instead of the
17-
apikey of the site_user. Will be mandatory when dropping support for
18-
CKAN 2.9.
19-
required: false
33+
apikey of the site_user.
34+
default: 'NOT_SET'
35+
required: true
2036
- key: ckanext.xloader.formats
2137
example: csv application/csv xls application/vnd.ms-excel
2238
description: |
@@ -171,5 +187,3 @@ groups:
171187
they will also display "complete", "active", "inactive", and "unknown".
172188
type: bool
173189
required: false
174-
175-

ckanext/xloader/jobs.py

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,10 @@
2323

2424
from . import db, loader
2525
from .job_exceptions import JobError, HTTPError, DataTooBigError, FileCouldNotBeLoadedError
26-
from .utils import datastore_resource_exists, set_resource_metadata
26+
from .utils import datastore_resource_exists, set_resource_metadata, modify_input_url
2727

28-
try:
29-
from ckan.lib.api_token import get_user_from_token
30-
except ImportError:
31-
get_user_from_token = None
28+
29+
from ckan.lib.api_token import get_user_from_token
3230

3331
log = logging.getLogger(__name__)
3432

@@ -297,8 +295,9 @@ def _download_resource_data(resource, data, api_key, logger):
297295
data['datastore_contains_all_records_of_source_file'] = False
298296
which will be saved to the resource later on.
299297
'''
298+
# update base url (for possible local loopback)
299+
url = modify_input_url(resource.get('url'))
300300
# check scheme
301-
url = resource.get('url')
302301
url_parts = urlsplit(url)
303302
scheme = url_parts.scheme
304303
if scheme not in ('http', 'https', 'ftp'):
@@ -468,7 +467,7 @@ def callback_xloader_hook(result_url, api_key, job_dict):
468467

469468
try:
470469
result = requests.post(
471-
result_url,
470+
modify_input_url(result_url), # modify with local config
472471
data=json.dumps(job_dict, cls=DatetimeJsonEncoder),
473472
verify=SSL_VERIFY,
474473
headers=headers)
@@ -511,19 +510,9 @@ def update_resource(resource, patch_only=False):
511510

512511
def _get_user_from_key(api_key_or_token):
513512
""" Gets the user using the API Token or API Key.
514-
515-
This method provides backwards compatibility for CKAN 2.9 that
516-
supported both methods and previous CKAN versions supporting
517-
only API Keys.
518513
"""
519-
user = None
520-
if get_user_from_token:
521-
user = get_user_from_token(api_key_or_token)
522-
if not user:
523-
user = model.Session.query(model.User).filter_by(
524-
apikey=api_key_or_token
525-
).first()
526-
return user
514+
return get_user_from_token(api_key_or_token)
515+
527516

528517

529518
def get_resource_and_dataset(resource_id, api_key):

ckanext/xloader/plugin.py

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,7 @@
1717
except ImportError:
1818
HAS_IPIPE_VALIDATION = False
1919

20-
try:
21-
config_declarations = toolkit.blanket.config_declarations
22-
except AttributeError:
23-
# CKAN 2.9 does not have config_declarations.
24-
# Remove when dropping support.
25-
def config_declarations(cls):
26-
return cls
20+
config_declarations = toolkit.blanket.config_declarations
2721

2822
if toolkit.check_ckan_version(min_version='2.11'):
2923
from ckanext.datastore.interfaces import IDataDictionaryForm
@@ -76,14 +70,6 @@ def configure(self, config_):
7670
else:
7771
self.ignore_hash = False
7872

79-
for config_option in ("ckan.site_url",):
80-
if not config_.get(config_option):
81-
raise Exception(
82-
"Config option `{0}` must be set to use ckanext-xloader.".format(
83-
config_option
84-
)
85-
)
86-
8773
# IPipeValidation
8874

8975
def receive_validation_report(self, validation_report):

ckanext/xloader/schema.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,7 @@
1717
int_validator = get_validator('int_validator')
1818
OneOf = get_validator('OneOf')
1919
ignore_not_sysadmin = get_validator('ignore_not_sysadmin')
20-
21-
if p.toolkit.check_ckan_version('2.9'):
22-
unicode_safe = get_validator('unicode_safe')
23-
else:
24-
unicode_safe = str
20+
unicode_safe = get_validator('unicode_safe')
2521

2622

2723
def xloader_submit_schema():

ckanext/xloader/tests/test_action.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import pytest
2+
from ckan.plugins import toolkit
23
try:
34
from unittest import mock
45
except ImportError:
@@ -117,10 +118,24 @@ def test_status(self):
117118

118119
assert status["status"] == "pending"
119120

120-
def test_xloader_user_api_token_defaults_to_site_user_apikey(self):
121-
api_token = get_xloader_user_apitoken()
122-
site_user = helpers.call_action("get_site_user")
123-
assert api_token == site_user["apikey"]
121+
122+
def test_xloader_user_api_token_from_config(self):
123+
sysadmin = factories.SysadminWithToken()
124+
apikey = sysadmin["token"]
125+
with mock.patch.dict(toolkit.config, {'ckanext.xloader.api_token': apikey}):
126+
api_token = get_xloader_user_apitoken()
127+
assert api_token == apikey
128+
129+
@pytest.mark.ckan_config("ckanext.xloader.api_token", "NOT_SET")
130+
def test_xloader_user_api_token_from_config_should_throw_exceptio_when_not_set(self):
131+
132+
hasNotThrownException = True
133+
try:
134+
get_xloader_user_apitoken()
135+
except Exception:
136+
hasNotThrownException = False
137+
138+
assert not hasNotThrownException
124139

125140
@pytest.mark.ckan_config("ckanext.xloader.api_token", "random-api-token")
126141
def test_xloader_user_api_token(self):

0 commit comments

Comments
 (0)