ckan_cli 1.0.0 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +121 -16
- data/lib/ckancli/commands/upload.rb +448 -444
- data/lib/ckancli/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e6e9e4fe9afdc7e11b96c179e7eefe16b7c19247d4d1f08b029965688528bdf6
|
4
|
+
data.tar.gz: bd4ed9291ff176a1303f7ba30283070dd732ba30342347e60e25b856fdb33f16
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5cb9bbe079a905983d59158537b87ccc49901efa3e63b8ef9ca66a910224eba9b9cc7d253b864af9bd14bf1e152f828cd78ab0351a8a63767c8a93c2216b2c0f
|
7
|
+
data.tar.gz: 29f6dd5cf5a07bb95284dffe88529815544bb7fb95b0dc32b9ccf27542913bec7bd32307492060ead203b084fe4e49c0990be68f4591d26b72a106636dd2f4f3
|
data/README.md
CHANGED
@@ -1,20 +1,16 @@
|
|
1
1
|
# [CKAN CLI](https://github.com/datagovlv/ckan_cli)
|
2
2
|
|
3
|
-
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/ckan_cli.svg)][gem]
|
4
4
|
|
5
|
-
|
5
|
+
[gem]: http://badge.fury.io/rb/ckan_cli
|
6
6
|
|
7
|
-
|
7
|
+
> CKAN on your command line. Use your terminal to validate and publish resources to CKAN. Works with CKAN API. Developed and adjusted for Latvian open data portal (Comprehensive Knowledge Archive Network).
|
8
8
|
|
9
|
-
|
10
|
-
gem 'ckan_cli'
|
11
|
-
```
|
12
|
-
|
13
|
-
And then execute:
|
9
|
+
![Interface](https://github.com/datagovlv/ckan_cli/raw/master/assets/interface.png)
|
14
10
|
|
15
|
-
|
11
|
+
## Installation
|
16
12
|
|
17
|
-
|
13
|
+
Install Ruby Gem yourself:
|
18
14
|
|
19
15
|
$ gem install ckan_cli
|
20
16
|
|
@@ -23,26 +19,135 @@ Or install it yourself as:
|
|
23
19
|
Run it:
|
24
20
|
|
25
21
|
```shell
|
26
|
-
$ ckancli
|
22
|
+
$ ckancli.rb
|
27
23
|
```
|
28
24
|
|
29
25
|
For instance, to publish to CKAN all CSV files in folder do
|
30
26
|
|
31
27
|
```shell
|
32
|
-
$ ckancli upload -d C:\my_csv_collection\ -c config.json -r resource.json
|
28
|
+
$ ckancli.rb upload -d C:\my_csv_collection\ -c config.json -r resource.json
|
33
29
|
```
|
34
30
|
|
35
31
|
To see help do
|
36
32
|
|
37
33
|
```shell
|
38
|
-
$ ckancli help
|
34
|
+
$ ckancli.rb help
|
35
|
+
```
|
36
|
+
|
37
|
+
## Configuration
|
38
|
+
|
39
|
+
> Example files including schemas and configurations are located in 'example_files' directory.
|
40
|
+
|
41
|
+
### Global configuration
|
42
|
+
|
43
|
+
Contains configuration for CKAN API and e-mail notifications (sections "email_server" and "notification_receiver" are optional).
|
44
|
+
|
45
|
+
```javascript
|
46
|
+
{
|
47
|
+
"ckan_api":{
|
48
|
+
"api_key":"YOUR_CKAN_API_KEY",
|
49
|
+
"url":"https://data.gov.lv/api/3/"
|
50
|
+
},
|
51
|
+
"email_server": {
|
52
|
+
"address": "smtp.yourdomain.com",
|
53
|
+
"port": "25",
|
54
|
+
"ssl": false,
|
55
|
+
"smtp_user": null,
|
56
|
+
"smtp_password": null,
|
57
|
+
"sender": "ckancli@data.gov.lv",
|
58
|
+
"subject": "CKAN CLI task summary"
|
59
|
+
},
|
60
|
+
"notification_receiver": {
|
61
|
+
"error": "mail_one@yourdomain.com",
|
62
|
+
"success": "mail_one@yourdomain.com, mail_two@yourdomain.com"
|
63
|
+
}
|
64
|
+
}
|
65
|
+
```
|
66
|
+
|
67
|
+
### Resource configuration
|
68
|
+
|
69
|
+
Contains configuration for resource metadata (parameters as specified in CKAN API guidelines). If resource name is not specified, file name will be used. If resource identifier is not specified, new resource will be created.
|
70
|
+
|
71
|
+
```javascript
|
72
|
+
{
|
73
|
+
"result": {
|
74
|
+
"name": "CKAN CLI file",
|
75
|
+
"package_name": "ta",
|
76
|
+
"package_id": "d1819200-121a-4452-8868-34f2c2a898c1",
|
77
|
+
"last_modified": "2019-05-14T05:12:21.257451",
|
78
|
+
"package_title": "TA",
|
79
|
+
"id": "15f950f0-1d50-467f-8db3-87366d30f7db"
|
80
|
+
},
|
81
|
+
"success": true
|
82
|
+
}
|
83
|
+
```
|
84
|
+
|
85
|
+
### Package configuration
|
86
|
+
|
87
|
+
Contains configuration for package metadata (parameters as specified in CKAN API guidelines). If package configuration is not specified, package metadata won't be affected.
|
88
|
+
|
89
|
+
```javascript
|
90
|
+
{
|
91
|
+
"result": {
|
92
|
+
"frequency": "http://publications.europa.eu/mdr/authority/frequency/DAILY",
|
93
|
+
"id": "d1819200-121a-4452-8868-34f2c2a898c1",
|
94
|
+
"metadata_modified": "2019-08-06T06:39:18.422714",
|
95
|
+
"name": "cli",
|
96
|
+
"title": "CKAN CLI test"
|
97
|
+
},
|
98
|
+
"success": true
|
99
|
+
}
|
100
|
+
```
|
101
|
+
|
102
|
+
### CSV schemas
|
103
|
+
|
104
|
+
CSV files can be validated against a schema. The structure currently follows [JSON Table Schema](http://www.w3.org/TR/tabular-metadata/). Detailed information for validations can be found at [CSV Lint project](https://github.com/theodi/csvlint.rb).
|
105
|
+
|
106
|
+
```javascript
|
107
|
+
{
|
108
|
+
"fields": [
|
109
|
+
{
|
110
|
+
"name": "id",
|
111
|
+
"constraints": {
|
112
|
+
"required": true,
|
113
|
+
"type": "http://www.w3.org/2001/XMLSchema#int"
|
114
|
+
}
|
115
|
+
},
|
116
|
+
{
|
117
|
+
"name": "price",
|
118
|
+
"constraints": {
|
119
|
+
"required": true,
|
120
|
+
"minLength": 1
|
121
|
+
}
|
122
|
+
},
|
123
|
+
{
|
124
|
+
"name": "postcode",
|
125
|
+
"constraints": {
|
126
|
+
"required": true,
|
127
|
+
"pattern": "[A-Z]{1,2}[0-9]{4}"
|
128
|
+
}
|
129
|
+
}
|
130
|
+
]
|
131
|
+
}
|
39
132
|
```
|
40
133
|
|
41
|
-
##
|
134
|
+
## Known issues for windows users
|
135
|
+
|
136
|
+
### Error "Could not open library 'libcurl.dll': The specified module could not be found.".
|
137
|
+
|
138
|
+
If this error is ocurring, copy file '\ext\libcurl.dll' to Ruby executables directory (e.g. 'C:\Ruby26-x64\bin\').
|
139
|
+
|
140
|
+
## For developers
|
141
|
+
|
142
|
+
Add this line to your application's Gemfile:
|
42
143
|
|
43
|
-
|
144
|
+
```ruby
|
145
|
+
gem 'ckan_cli'
|
146
|
+
```
|
44
147
|
|
45
|
-
|
148
|
+
And then execute:
|
149
|
+
|
150
|
+
$ bundle
|
46
151
|
|
47
152
|
## Contributing
|
48
153
|
|
@@ -1,445 +1,449 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'colorize'
|
4
|
-
require 'pathname'
|
5
|
-
require 'csvlint'
|
6
|
-
require 'json'
|
7
|
-
require 'tty-logger'
|
8
|
-
require 'tty-spinner'
|
9
|
-
require 'net/smtp'
|
10
|
-
require 'mail'
|
11
|
-
require 'open-uri'
|
12
|
-
require 'uri'
|
13
|
-
|
14
|
-
require_relative '../command'
|
15
|
-
require_relative '../../ckanclient/ckanclient'
|
16
|
-
|
17
|
-
module Ckancli
|
18
|
-
module Commands
|
19
|
-
class Upload < Ckancli::Command
|
20
|
-
def initialize(options)
|
21
|
-
@options = options
|
22
|
-
|
23
|
-
@dir = nil
|
24
|
-
@resources = []
|
25
|
-
@http_resource = false
|
26
|
-
@schema = nil
|
27
|
-
@resource_config = nil
|
28
|
-
@dataset_config = nil
|
29
|
-
@api_config = nil
|
30
|
-
@logger = nil
|
31
|
-
@ckan_client = nil
|
32
|
-
@update_modified = false
|
33
|
-
@mail_server = nil
|
34
|
-
end
|
35
|
-
|
36
|
-
def execute(input: $stdin, output: $stdout)
|
37
|
-
begin
|
38
|
-
prepare_options(@options)
|
39
|
-
|
40
|
-
if !@schema.nil?
|
41
|
-
validate()
|
42
|
-
else
|
43
|
-
puts ""
|
44
|
-
show_info "No schema - skipping validation"
|
45
|
-
end
|
46
|
-
|
47
|
-
upload()
|
48
|
-
|
49
|
-
if !@dataset_config.nil?
|
50
|
-
update_dataset()
|
51
|
-
else
|
52
|
-
puts ""
|
53
|
-
show_info "No dataset configuration - skipping update"
|
54
|
-
end
|
55
|
-
|
56
|
-
@resources.each do |res|
|
57
|
-
if !res[:log_file].nil?
|
58
|
-
res[:log_file].close
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
if !@mail_server.nil? && !@mail_receivers.nil?
|
63
|
-
mail_notify()
|
64
|
-
end
|
65
|
-
rescue StandardError => e
|
66
|
-
return_error "Unexpected error", e
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
private
|
71
|
-
def prepare_options(options)
|
72
|
-
@http_resource = true if options[:dir] =~ /^http(s)?/
|
73
|
-
|
74
|
-
if @http_resource
|
75
|
-
@dir = Dir.pwd
|
76
|
-
else
|
77
|
-
@dir = File.directory?(options[:dir]) ? options[:dir] : (File.file?(options[:dir]) ? File.dirname(options[:dir]) : nil)
|
78
|
-
end
|
79
|
-
|
80
|
-
if @dir.nil?
|
81
|
-
return_error "Invalid directory - #{options[:dir]}"
|
82
|
-
end
|
83
|
-
|
84
|
-
begin
|
85
|
-
@logger = TTY::Logger.new do |config|
|
86
|
-
config.metadata = [:date, :time]
|
87
|
-
config.handlers = [
|
88
|
-
[:stream, output: File.open(File.join(@dir, "output.log"), "a")]
|
89
|
-
]
|
90
|
-
end
|
91
|
-
rescue
|
92
|
-
return_error "Could not initialize logger", e
|
93
|
-
end
|
94
|
-
|
95
|
-
begin
|
96
|
-
if @http_resource
|
97
|
-
file_path = File.join(@dir, File.basename(URI.parse(options[:dir]).path))
|
98
|
-
|
99
|
-
File.open(file_path, "wb") do |file|
|
100
|
-
file.write open(options[:dir]).read
|
101
|
-
end
|
102
|
-
|
103
|
-
@resources.push(file_path)
|
104
|
-
else
|
105
|
-
if File.directory?(options[:dir])
|
106
|
-
@resources = Dir.entries(options[:dir]).select{ |f| File.file?(File.join(options[:dir], f)) && (options[:ignoreextension] || File.extname(f).downcase == ".csv") }.map { |f| File.join(options[:dir], f) }
|
107
|
-
else
|
108
|
-
@resources.push(options[:dir])
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
@resources = @resources.map{ |r|
|
113
|
-
{
|
114
|
-
:path => r,
|
115
|
-
:valid => true,
|
116
|
-
:summary => nil,
|
117
|
-
:log_file => File.open(File.join(@dir, "#{File.basename(r)}.log"), "w"),
|
118
|
-
:log => nil
|
119
|
-
}
|
120
|
-
}
|
121
|
-
|
122
|
-
@resources.each do |res|
|
123
|
-
res[:log] = TTY::Logger.new do |config|
|
124
|
-
config.metadata = [:date, :time]
|
125
|
-
config.handlers = [
|
126
|
-
[:stream, output: res[:log_file]]
|
127
|
-
]
|
128
|
-
end
|
129
|
-
end
|
130
|
-
rescue Errno::ENOENT => e
|
131
|
-
return_error "Could not load resources", e
|
132
|
-
rescue StandardError => e
|
133
|
-
return_error "Could not load resources", e
|
134
|
-
end
|
135
|
-
|
136
|
-
if options[:validationschema]
|
137
|
-
@schema = (Pathname.new(options[:validationschema])).absolute? ? options[:validationschema] : File.join(@dir, options[:validationschema])
|
138
|
-
end
|
139
|
-
|
140
|
-
if options[:resourceconfig]
|
141
|
-
begin
|
142
|
-
@resource_config = JSON.parse(File.read((Pathname.new(options[:resourceconfig])).absolute? ? options[:resourceconfig] : File.join(@dir, options[:resourceconfig])))
|
143
|
-
rescue Errno::ENOENT => e
|
144
|
-
return_error "Could not load resource configuration", e
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
if options[:datasetconfig]
|
149
|
-
begin
|
150
|
-
@dataset_config = JSON.parse(File.read((Pathname.new(options[:datasetconfig])).absolute? ? options[:datasetconfig] : File.join(@dir, options[:datasetconfig])))
|
151
|
-
rescue Errno::ENOENT => e
|
152
|
-
return_error "Could not load dataset configuration", e
|
153
|
-
end
|
154
|
-
end
|
155
|
-
|
156
|
-
if options[:configuration]
|
157
|
-
begin
|
158
|
-
@api_config = JSON.parse(File.read((Pathname.new(options[:configuration])).absolute? ? options[:configuration] : File.join(@dir, options[:configuration])))
|
159
|
-
rescue Errno::ENOENT => e
|
160
|
-
return_error "Could not load configuration", e
|
161
|
-
end
|
162
|
-
end
|
163
|
-
|
164
|
-
if !@api_config.nil?
|
165
|
-
@ckan_client = CkanClient::Client.new(@api_config["ckan_api"]["url"], @api_config["ckan_api"]["api_key"])
|
166
|
-
@mail_server = @api_config["email_server"]
|
167
|
-
@mail_receivers = @api_config["notification_receiver"]
|
168
|
-
end
|
169
|
-
|
170
|
-
@update_modified = options[:updatemodified]
|
171
|
-
@overwrite = options[:overwrite]
|
172
|
-
end
|
173
|
-
|
174
|
-
def validate()
|
175
|
-
puts ""
|
176
|
-
show_info "Starting validations..."
|
177
|
-
@resources.each do |res|
|
178
|
-
task("Validating resource: #{File.basename(res[:path])}") do
|
179
|
-
ok = false
|
180
|
-
msg = nil
|
181
|
-
exception = nil
|
182
|
-
|
183
|
-
begin
|
184
|
-
res[:log].info "Validating resource..."
|
185
|
-
|
186
|
-
res[:valid] = false
|
187
|
-
|
188
|
-
source = read_resource(res[:path])
|
189
|
-
schema = read_schema(@schema)
|
190
|
-
|
191
|
-
if schema.class == Csvlint::Schema && schema.description == "malformed"
|
192
|
-
msg = "Invalid schema - malformed JSON"
|
193
|
-
else
|
194
|
-
validator = Csvlint::Validator.new(source, {}, schema)
|
195
|
-
|
196
|
-
if validator.errors.length > 0 || validator.warnings.length > 0
|
197
|
-
msg = "#{validator.errors.length} errors, #{validator.warnings.length} warnings"
|
198
|
-
res[:summary] = {
|
199
|
-
:errors => validator.errors.map { |v| format_validation(v, schema) },
|
200
|
-
:warnings => validator.warnings.map { |v| format_validation(v, schema) }
|
201
|
-
}
|
202
|
-
|
203
|
-
res[:log].error res[:summary].to_json
|
204
|
-
end
|
205
|
-
|
206
|
-
ok = validator.valid?
|
207
|
-
res[:valid] = ok
|
208
|
-
end
|
209
|
-
rescue Errno::ENOENT, OpenURI::HTTPError => e
|
210
|
-
msg = "Could not load file - #{e.message}"
|
211
|
-
rescue Csvlint::Csvw::MetadataError => e
|
212
|
-
msg = "Invalid schema metadata - #{e.message}"
|
213
|
-
rescue StandardError => e
|
214
|
-
msg = "Could not validate resource"
|
215
|
-
exception = e
|
216
|
-
end
|
217
|
-
|
218
|
-
if !exception.nil?
|
219
|
-
res[:log].error msg, exception
|
220
|
-
elsif !msg.nil?
|
221
|
-
res[:log].info msg
|
222
|
-
else
|
223
|
-
res[:log].info "OK - Validation successfull"
|
224
|
-
end
|
225
|
-
res[:log].info "End of validation"
|
226
|
-
|
227
|
-
[ok, msg, exception]
|
228
|
-
end
|
229
|
-
end
|
230
|
-
|
231
|
-
show_info "End of validations"
|
232
|
-
end
|
233
|
-
|
234
|
-
def mail_notify
|
235
|
-
puts ""
|
236
|
-
show_info "Sending e-mail notifications..."
|
237
|
-
|
238
|
-
begin
|
239
|
-
mail = Mail.new
|
240
|
-
mail[:from] = @mail_server["sender"]
|
241
|
-
mail[:
|
242
|
-
mail
|
243
|
-
|
244
|
-
:
|
245
|
-
:
|
246
|
-
:
|
247
|
-
:
|
248
|
-
:
|
249
|
-
:
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
#
|
254
|
-
|
255
|
-
|
256
|
-
# attachments
|
257
|
-
@resources.each do |res|
|
258
|
-
msg = "#{msg}\r\n- #{File.basename(res[:path])}"
|
259
|
-
if !res[:summary].nil?
|
260
|
-
msg = "#{msg} (#{res[:summary][:errors].length} errors, #{res[:summary][:warnings].length} warnings)"
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
log_info message
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
source
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'colorize'
|
4
|
+
require 'pathname'
|
5
|
+
require 'csvlint'
|
6
|
+
require 'json'
|
7
|
+
require 'tty-logger'
|
8
|
+
require 'tty-spinner'
|
9
|
+
require 'net/smtp'
|
10
|
+
require 'mail'
|
11
|
+
require 'open-uri'
|
12
|
+
require 'uri'
|
13
|
+
|
14
|
+
require_relative '../command'
|
15
|
+
require_relative '../../ckanclient/ckanclient'
|
16
|
+
|
17
|
+
module Ckancli
|
18
|
+
module Commands
|
19
|
+
class Upload < Ckancli::Command
|
20
|
+
def initialize(options)
|
21
|
+
@options = options
|
22
|
+
|
23
|
+
@dir = nil
|
24
|
+
@resources = []
|
25
|
+
@http_resource = false
|
26
|
+
@schema = nil
|
27
|
+
@resource_config = nil
|
28
|
+
@dataset_config = nil
|
29
|
+
@api_config = nil
|
30
|
+
@logger = nil
|
31
|
+
@ckan_client = nil
|
32
|
+
@update_modified = false
|
33
|
+
@mail_server = nil
|
34
|
+
end
|
35
|
+
|
36
|
+
def execute(input: $stdin, output: $stdout)
|
37
|
+
begin
|
38
|
+
prepare_options(@options)
|
39
|
+
|
40
|
+
if !@schema.nil?
|
41
|
+
validate()
|
42
|
+
else
|
43
|
+
puts ""
|
44
|
+
show_info "No schema - skipping validation"
|
45
|
+
end
|
46
|
+
|
47
|
+
upload()
|
48
|
+
|
49
|
+
if !@dataset_config.nil?
|
50
|
+
update_dataset()
|
51
|
+
else
|
52
|
+
puts ""
|
53
|
+
show_info "No dataset configuration - skipping update"
|
54
|
+
end
|
55
|
+
|
56
|
+
@resources.each do |res|
|
57
|
+
if !res[:log_file].nil?
|
58
|
+
res[:log_file].close
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
if !@mail_server.nil? && !@mail_receivers.nil?
|
63
|
+
mail_notify()
|
64
|
+
end
|
65
|
+
rescue StandardError => e
|
66
|
+
return_error "Unexpected error", e
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
def prepare_options(options)
|
72
|
+
@http_resource = true if options[:dir] =~ /^http(s)?/
|
73
|
+
|
74
|
+
if @http_resource
|
75
|
+
@dir = Dir.pwd
|
76
|
+
else
|
77
|
+
@dir = File.directory?(options[:dir]) ? options[:dir] : (File.file?(options[:dir]) ? File.dirname(options[:dir]) : nil)
|
78
|
+
end
|
79
|
+
|
80
|
+
if @dir.nil?
|
81
|
+
return_error "Invalid directory - #{options[:dir]}"
|
82
|
+
end
|
83
|
+
|
84
|
+
begin
|
85
|
+
@logger = TTY::Logger.new do |config|
|
86
|
+
config.metadata = [:date, :time]
|
87
|
+
config.handlers = [
|
88
|
+
[:stream, output: File.open(File.join(@dir, "output.log"), "a")]
|
89
|
+
]
|
90
|
+
end
|
91
|
+
rescue StandardError => e
|
92
|
+
return_error "Could not initialize logger", e
|
93
|
+
end
|
94
|
+
|
95
|
+
begin
|
96
|
+
if @http_resource
|
97
|
+
file_path = File.join(@dir, File.basename(URI.parse(options[:dir]).path))
|
98
|
+
|
99
|
+
File.open(file_path, "wb") do |file|
|
100
|
+
file.write open(options[:dir]).read
|
101
|
+
end
|
102
|
+
|
103
|
+
@resources.push(file_path)
|
104
|
+
else
|
105
|
+
if File.directory?(options[:dir])
|
106
|
+
@resources = Dir.entries(options[:dir]).select{ |f| File.file?(File.join(options[:dir], f)) && (options[:ignoreextension] || File.extname(f).downcase == ".csv") }.map { |f| File.join(options[:dir], f) }
|
107
|
+
else
|
108
|
+
@resources.push(options[:dir])
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
@resources = @resources.map{ |r|
|
113
|
+
{
|
114
|
+
:path => r,
|
115
|
+
:valid => true,
|
116
|
+
:summary => nil,
|
117
|
+
:log_file => File.open(File.join(@dir, "#{File.basename(r)}.log"), "w"),
|
118
|
+
:log => nil
|
119
|
+
}
|
120
|
+
}
|
121
|
+
|
122
|
+
@resources.each do |res|
|
123
|
+
res[:log] = TTY::Logger.new do |config|
|
124
|
+
config.metadata = [:date, :time]
|
125
|
+
config.handlers = [
|
126
|
+
[:stream, output: res[:log_file]]
|
127
|
+
]
|
128
|
+
end
|
129
|
+
end
|
130
|
+
rescue Errno::ENOENT => e
|
131
|
+
return_error "Could not load resources", e
|
132
|
+
rescue StandardError => e
|
133
|
+
return_error "Could not load resources", e
|
134
|
+
end
|
135
|
+
|
136
|
+
if options[:validationschema]
|
137
|
+
@schema = (Pathname.new(options[:validationschema])).absolute? ? options[:validationschema] : File.join(@dir, options[:validationschema])
|
138
|
+
end
|
139
|
+
|
140
|
+
if options[:resourceconfig]
|
141
|
+
begin
|
142
|
+
@resource_config = JSON.parse(File.read((Pathname.new(options[:resourceconfig])).absolute? ? options[:resourceconfig] : File.join(@dir, options[:resourceconfig])))
|
143
|
+
rescue Errno::ENOENT => e
|
144
|
+
return_error "Could not load resource configuration", e
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
if options[:datasetconfig]
|
149
|
+
begin
|
150
|
+
@dataset_config = JSON.parse(File.read((Pathname.new(options[:datasetconfig])).absolute? ? options[:datasetconfig] : File.join(@dir, options[:datasetconfig])))
|
151
|
+
rescue Errno::ENOENT => e
|
152
|
+
return_error "Could not load dataset configuration", e
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
if options[:configuration]
|
157
|
+
begin
|
158
|
+
@api_config = JSON.parse(File.read((Pathname.new(options[:configuration])).absolute? ? options[:configuration] : File.join(@dir, options[:configuration])))
|
159
|
+
rescue Errno::ENOENT => e
|
160
|
+
return_error "Could not load configuration", e
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
if !@api_config.nil?
|
165
|
+
@ckan_client = CkanClient::Client.new(@api_config["ckan_api"]["url"], @api_config["ckan_api"]["api_key"])
|
166
|
+
@mail_server = @api_config["email_server"]
|
167
|
+
@mail_receivers = @api_config["notification_receiver"]
|
168
|
+
end
|
169
|
+
|
170
|
+
@update_modified = options[:updatemodified]
|
171
|
+
@overwrite = options[:overwrite]
|
172
|
+
end
|
173
|
+
|
174
|
+
def validate()
|
175
|
+
puts ""
|
176
|
+
show_info "Starting validations..."
|
177
|
+
@resources.each do |res|
|
178
|
+
task("Validating resource: #{File.basename(res[:path])}") do
|
179
|
+
ok = false
|
180
|
+
msg = nil
|
181
|
+
exception = nil
|
182
|
+
|
183
|
+
begin
|
184
|
+
res[:log].info "Validating resource..."
|
185
|
+
|
186
|
+
res[:valid] = false
|
187
|
+
|
188
|
+
source = read_resource(res[:path])
|
189
|
+
schema = read_schema(@schema)
|
190
|
+
|
191
|
+
if schema.class == Csvlint::Schema && schema.description == "malformed"
|
192
|
+
msg = "Invalid schema - malformed JSON"
|
193
|
+
else
|
194
|
+
validator = Csvlint::Validator.new(source, {}, schema)
|
195
|
+
|
196
|
+
if validator.errors.length > 0 || validator.warnings.length > 0
|
197
|
+
msg = "#{validator.errors.length} errors, #{validator.warnings.length} warnings"
|
198
|
+
res[:summary] = {
|
199
|
+
:errors => validator.errors.map { |v| format_validation(v, schema) },
|
200
|
+
:warnings => validator.warnings.map { |v| format_validation(v, schema) }
|
201
|
+
}
|
202
|
+
|
203
|
+
res[:log].error res[:summary].to_json
|
204
|
+
end
|
205
|
+
|
206
|
+
ok = validator.valid?
|
207
|
+
res[:valid] = ok
|
208
|
+
end
|
209
|
+
rescue Errno::ENOENT, OpenURI::HTTPError => e
|
210
|
+
msg = "Could not load file - #{e.message}"
|
211
|
+
rescue Csvlint::Csvw::MetadataError => e
|
212
|
+
msg = "Invalid schema metadata - #{e.message}"
|
213
|
+
rescue StandardError => e
|
214
|
+
msg = "Could not validate resource"
|
215
|
+
exception = e
|
216
|
+
end
|
217
|
+
|
218
|
+
if !exception.nil?
|
219
|
+
res[:log].error msg, exception
|
220
|
+
elsif !msg.nil?
|
221
|
+
res[:log].info msg
|
222
|
+
else
|
223
|
+
res[:log].info "OK - Validation successfull"
|
224
|
+
end
|
225
|
+
res[:log].info "End of validation"
|
226
|
+
|
227
|
+
[ok, msg, exception]
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
show_info "End of validations"
|
232
|
+
end
|
233
|
+
|
234
|
+
def mail_notify
|
235
|
+
puts ""
|
236
|
+
show_info "Sending e-mail notifications..."
|
237
|
+
|
238
|
+
begin
|
239
|
+
mail = Mail.new
|
240
|
+
mail[:from] = @mail_server["sender"]
|
241
|
+
mail[:subject] = @mail_server["subject"]
|
242
|
+
mail.delivery_method :smtp, {
|
243
|
+
:address => @mail_server["address"],
|
244
|
+
:port => @mail_server["port"],
|
245
|
+
:user_name => @mail_server["smtp_user"],
|
246
|
+
:password => @mail_server["smtp_password"],
|
247
|
+
:authentication => @mail_server["smtp_user"].nil? ? "none" : "plain",
|
248
|
+
:ssl => @mail_server["ssl"],
|
249
|
+
:openssl_verify_mode => OpenSSL::SSL::VERIFY_NONE
|
250
|
+
}
|
251
|
+
|
252
|
+
# message
|
253
|
+
msg = "CKAN CLI task completed.\r\n\r\n#{@resources.length} files processed"
|
254
|
+
has_errors = false
|
255
|
+
|
256
|
+
# attachments
|
257
|
+
@resources.each do |res|
|
258
|
+
msg = "#{msg}\r\n- #{File.basename(res[:path])}"
|
259
|
+
if !res[:summary].nil?
|
260
|
+
msg = "#{msg} (#{res[:summary][:errors].length} errors, #{res[:summary][:warnings].length} warnings)"
|
261
|
+
if res[:summary][:errors].length > 0
|
262
|
+
has_errors = true
|
263
|
+
end
|
264
|
+
end
|
265
|
+
mail.add_file :filename => File.basename(res[:path] + ".log"), :content => File.read(res[:path] + ".log")
|
266
|
+
end
|
267
|
+
msg = "#{msg}\r\n\r\nLog files attached."
|
268
|
+
mail[:body] = msg
|
269
|
+
mail[:to] = has_errors ? @mail_receivers["error"] : @mail_receivers["success"]
|
270
|
+
|
271
|
+
mail.deliver!
|
272
|
+
rescue StandardError => e
|
273
|
+
show_error "Could not send e-mail", e
|
274
|
+
end
|
275
|
+
|
276
|
+
show_info "End of sending"
|
277
|
+
end
|
278
|
+
|
279
|
+
def upload()
|
280
|
+
puts ""
|
281
|
+
show_info "Starting upload..."
|
282
|
+
@resources.each do |res|
|
283
|
+
next unless res[:valid]
|
284
|
+
|
285
|
+
task("Uploading resource: #{File.basename(res[:path])}") do
|
286
|
+
res[:log].info "Uploading resource..."
|
287
|
+
|
288
|
+
ok = true
|
289
|
+
msg = nil
|
290
|
+
exception = nil
|
291
|
+
|
292
|
+
params = @resource_config["result"].clone
|
293
|
+
|
294
|
+
if @update_modified
|
295
|
+
params["last_modified"] = JSON.parse(Time.new.to_json)
|
296
|
+
end
|
297
|
+
|
298
|
+
# check if resource exists and if overwriting
|
299
|
+
if !params["id"].nil? && !@overwrite
|
300
|
+
resource = @ckan_client.get_resource(params["id"])
|
301
|
+
|
302
|
+
if !resource.nil?
|
303
|
+
ok = false
|
304
|
+
msg = "Resource already exists - skipping. Use parameter 'overwrite' to overwrite changes."
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
if ok
|
309
|
+
@ckan_client.create_or_update_resource(params, File.new(res[:path], 'rb'), true){ |response, status_ok|
|
310
|
+
ok = status_ok
|
311
|
+
|
312
|
+
if !ok
|
313
|
+
msg = "HTTP error #{response.code}"
|
314
|
+
|
315
|
+
log_error response.body
|
316
|
+
end
|
317
|
+
}
|
318
|
+
end
|
319
|
+
|
320
|
+
if !ok
|
321
|
+
res[:log].error msg
|
322
|
+
else
|
323
|
+
res[:log].info "OK - upload successfull"
|
324
|
+
end
|
325
|
+
res[:log].info "End of upload"
|
326
|
+
|
327
|
+
[ok, msg, exception]
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
show_info "End of upload"
|
332
|
+
end
|
333
|
+
|
334
|
+
def update_dataset()
|
335
|
+
puts ""
|
336
|
+
show_info "Starting dataset update..."
|
337
|
+
|
338
|
+
task("Updating dataset") do
|
339
|
+
ok = false
|
340
|
+
msg = nil
|
341
|
+
exception = nil
|
342
|
+
|
343
|
+
@ckan_client.update_package(@dataset_config["result"]["id"], @dataset_config["result"].clone, true){ |response, status_ok|
|
344
|
+
ok = status_ok
|
345
|
+
|
346
|
+
if !ok
|
347
|
+
msg = "HTTP error #{response.code}"
|
348
|
+
|
349
|
+
log_error response.body
|
350
|
+
end
|
351
|
+
}
|
352
|
+
|
353
|
+
[ok, msg, exception]
|
354
|
+
end
|
355
|
+
show_info "End of dataset update"
|
356
|
+
end
|
357
|
+
|
358
|
+
def return_error(message, exception = nil)
|
359
|
+
show_error(message, exception)
|
360
|
+
|
361
|
+
exit 1
|
362
|
+
end
|
363
|
+
|
364
|
+
def show_error(message, exception = nil)
|
365
|
+
if !exception.nil?
|
366
|
+
message = "#{message} - #{exception.message}"
|
367
|
+
end
|
368
|
+
|
369
|
+
puts " #{message} ".white.on_red
|
370
|
+
|
371
|
+
log_fatal message, exception
|
372
|
+
end
|
373
|
+
|
374
|
+
def show_info(message)
|
375
|
+
puts " #{message} "
|
376
|
+
|
377
|
+
log_info message
|
378
|
+
end
|
379
|
+
|
380
|
+
def task(message)
|
381
|
+
log_info message
|
382
|
+
|
383
|
+
spinner = TTY::Spinner.new("[:spinner] #{message}...")
|
384
|
+
spinner.auto_spin
|
385
|
+
ok, message, exception = yield
|
386
|
+
spinner.stop(ok ? "OK".green : "ERROR".red)
|
387
|
+
|
388
|
+
if !message.nil?
|
389
|
+
message = " #{message}"
|
390
|
+
|
391
|
+
if !exception.nil?
|
392
|
+
show_error message, exception
|
393
|
+
else
|
394
|
+
show_info message
|
395
|
+
end
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
def read_resource(source)
|
400
|
+
# check if URL or file
|
401
|
+
unless source =~ /^http(s)?/
|
402
|
+
source = File.new( source )
|
403
|
+
end
|
404
|
+
|
405
|
+
source
|
406
|
+
end
|
407
|
+
|
408
|
+
def read_schema(path)
|
409
|
+
schema = Csvlint::Schema.load_from_uri(path, false)
|
410
|
+
|
411
|
+
schema
|
412
|
+
end
|
413
|
+
|
414
|
+
def format_validation(error, schema = nil)
|
415
|
+
h = {
|
416
|
+
type: error.type,
|
417
|
+
category: error.category,
|
418
|
+
row: error.row,
|
419
|
+
col: error.column
|
420
|
+
}
|
421
|
+
|
422
|
+
if error.column && !schema.nil? && schema.class == Csvlint::Schema && schema.fields[error.column - 1] != nil
|
423
|
+
field = schema.fields[error.column - 1]
|
424
|
+
h[:header] = field.name
|
425
|
+
end
|
426
|
+
|
427
|
+
h
|
428
|
+
end
|
429
|
+
|
430
|
+
def log_fatal(message, exception = nil)
|
431
|
+
if !@logger.nil?
|
432
|
+
@logger.fatal message, exception
|
433
|
+
end
|
434
|
+
end
|
435
|
+
|
436
|
+
def log_info(message)
|
437
|
+
if !@logger.nil?
|
438
|
+
@logger.info message
|
439
|
+
end
|
440
|
+
end
|
441
|
+
|
442
|
+
def log_error(message)
|
443
|
+
if !@logger.nil?
|
444
|
+
@logger.error message
|
445
|
+
end
|
446
|
+
end
|
447
|
+
end
|
448
|
+
end
|
445
449
|
end
|
data/lib/ckancli/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ckan_cli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- datagovlv
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-08-
|
11
|
+
date: 2019-08-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: tty-color
|