ckan_cli 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 0d5686c7f8bd8d3fe87ac9fc4de600667ecd455fa773e383f8af10410d237dab
4
+ data.tar.gz: 0203fd33391a22b27eefcabc8f663b75080df194673800383fe2e563f448096f
5
+ SHA512:
6
+ metadata.gz: 9f2b823b83f3a9c9570fe45db2b797012e9b32d77f2db22b52d63db1bc1f7dfc1d2ffd3aa89ca6d019995b07f25f8dfaf7f43f3ffdf0b814cc409cfb018b584c
7
+ data.tar.gz: 9597fcf8f12824676a93c23b92496e8e282ff847ac3b41657915873e024b969f8b276d3aa17cfa2d5a4a91682e5a8eeb27f4e20064beb64b7a29db8500072005
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 datagovlvlv
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,57 @@
1
+ # [CKAN CLI](https://github.com/datagovlv/ckan_cli)
2
+
3
+ > CKAN on your command line. Use your terminal to validate and publish resources to CKAN. Works with CKAN API.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'ckan_cli'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install ckan_cli
20
+
21
+ ## Usage
22
+
23
+ Run it:
24
+
25
+ ```shell
26
+ $ ckancli
27
+ ```
28
+
29
+ For instance, to publish to CKAN all CSV files in folder do
30
+
31
+ ```shell
32
+ $ ckancli upload -d C:\my_csv_collection\ -c config.json -r resource.json
33
+ ```
34
+
35
+ To see help do
36
+
37
+ ```shell
38
+ $ ckancli help
39
+ ```
40
+
41
+ ## Development
42
+
43
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests.
44
+
45
+ To install this gem onto your local machine, run `bundle exec rake install`.
46
+
47
+ ## Contributing
48
+
49
+ Bug reports and pull requests are welcome on GitHub at https://github.com/datagovlv/ckan_cli. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
50
+
51
+ ## Code of Conduct
52
+
53
+ Everyone interacting in the CKAN CLI project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/datagovlv/ckan_cli/blob/master/CODE_OF_CONDUCT.md).
54
+
55
+ ## Copyright
56
+
57
+ Copyright (c) 2019 datagovlv. See [MIT License](LICENSE.txt) for further details.
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/ckan_cli.gemspec ADDED
@@ -0,0 +1,41 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "ckancli/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "ckan_cli"
8
+ spec.license = "MIT"
9
+ spec.version = Ckancli::VERSION
10
+ spec.authors = ["datagovlv"]
11
+ spec.email = ["dati@varam.gov.lv"]
12
+ spec.summary = "CKAN command line interface."
13
+ spec.description = "CKAN CLI with built-in CSV file validation and CKAN API integration."
14
+ spec.homepage = "https://github.com/datagovlv/ckan_cli"
15
+
16
+ spec.files = Dir['lib/**/*']
17
+ spec.files += Dir['bin/*']
18
+ spec.files += Dir['exe/*']
19
+ spec.files += Dir['ckan_cli.gemspec', 'README.md', 'LICENSE.txt', 'Rakefile']
20
+ spec.bindir = "exe"
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ["lib"]
23
+
24
+ spec.required_ruby_version = '>= 2.0.0'
25
+
26
+ spec.add_dependency "tty-color", "~> 0.4"
27
+ spec.add_dependency "tty-logger", "~> 0.1.0"
28
+ spec.add_dependency "tty-platform", "~> 0.2.0"
29
+ spec.add_dependency "tty-progressbar", "~> 0.16.0"
30
+ spec.add_dependency "tty-spinner", "~> 0.9.0"
31
+ spec.add_dependency "tty-which", "~> 0.4"
32
+ spec.add_dependency "pastel", "~> 0.7.2"
33
+ spec.add_dependency "thor", "~> 0.20.0"
34
+ spec.add_dependency "csvlint", "~> 0.4.0"
35
+ spec.add_dependency "rest-client", "~> 2.0.2"
36
+ spec.add_dependency "mail", "~> 2.7.1"
37
+
38
+ spec.add_development_dependency "bundler", "~> 1.17"
39
+ spec.add_development_dependency "rake", "~> 10.0"
40
+ spec.add_development_dependency "rspec", "~> 3.0"
41
+ end
data/exe/ckancli.rb ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ lib_path = File.expand_path('../lib', __dir__)
5
+ $:.unshift(lib_path) if !$:.include?(lib_path)
6
+ require 'ckancli/cli'
7
+
8
+ Signal.trap('INT') do
9
+ warn("\n#{caller.join("\n")}: interrupted")
10
+ exit(1)
11
+ end
12
+
13
+ begin
14
+ Ckancli::CLI.start
15
+ rescue Ckancli::CLI::Error => err
16
+ puts "ERROR: #{err.message}"
17
+ exit 1
18
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+
5
+ module Ckancli
6
+ # Handle the application command line parsing
7
+ # and the dispatch to various command objects
8
+ #
9
+ # @api public
10
+ class CLI < Thor
11
+ class_option :dir, type: :string, required: true, desc: 'Path to CSV files. Full path to directory or URL.', aliases: '-d'
12
+ class_option :resourceconfig, type: :string, required: true, desc: 'CKAN resource metadata file (JSON). Absolute or relative path.', aliases: '-r'
13
+ class_option :configuration, type: :string, required: true, desc: 'Global configuration file (JSON). Absolute or relative path.', aliases: '-c'
14
+ class_option :validationschema, type: :string, required: false, desc: 'Validation schema (JSON). Absolute or relative path.', aliases: '-v'
15
+ class_option :datasetconfig, type: :string, required: false, desc: 'CKAN dataset metadata file (JSON). Absolute or relative path.', aliases: '-p'
16
+ class_option :updatemodified, type: :boolean, required: false, desc: 'Update modified date of resource.', aliases: '-m'
17
+ class_option :ignoreextension, type: :boolean, require: false, desc: 'Ignore file extensions (process all files not only CSV).', aliases: '-i'
18
+ class_option :overwrite, type: :boolean, require: false, desc: 'Overwrite resource if existing is found by identifier.', aliases: '-w'
19
+
20
+ # Error raised by this runner
21
+ Error = Class.new(StandardError)
22
+
23
+ # desc 'version', 'CKAN CLI version'
24
+ # def version
25
+ # require_relative 'version'
26
+ # puts "v#{Ckancli::VERSION}"
27
+ # end
28
+ # map %w(--version -v) => :version
29
+
30
+ desc 'upload', 'Processes CSV files and uploads to CKAN API. '
31
+ method_option :help, aliases: '-h', type: :boolean,
32
+ desc: 'Display usage information'
33
+ def upload(*)
34
+ if options[:help]
35
+ invoke :help, ['upload']
36
+ else
37
+ require_relative 'commands/upload'
38
+ Ckancli::Commands::Upload.new(options).execute
39
+ end
40
+ end
41
+ default_task :help
42
+ end
43
+ end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ module Ckancli
6
+ class Command
7
+ extend Forwardable
8
+
9
+ def_delegators :command, :run
10
+
11
+ # Execute this command
12
+ #
13
+ # @api public
14
+ def execute(*)
15
+ raise(
16
+ NotImplementedError,
17
+ "#{self.class}##{__method__} must be implemented"
18
+ )
19
+ end
20
+
21
+ # The external commands runner
22
+ #
23
+ # @see http://www.rubydoc.info/gems/tty-command
24
+ #
25
+ # @api public
26
+ def command(**options)
27
+ require 'tty-command'
28
+ TTY::Command.new(options)
29
+ end
30
+
31
+ # The cursor movement
32
+ #
33
+ # @see http://www.rubydoc.info/gems/tty-cursor
34
+ #
35
+ # @api public
36
+ def cursor
37
+ require 'tty-cursor'
38
+ TTY::Cursor
39
+ end
40
+
41
+ # Open a file or text in the user's preferred editor
42
+ #
43
+ # @see http://www.rubydoc.info/gems/tty-editor
44
+ #
45
+ # @api public
46
+ def editor
47
+ require 'tty-editor'
48
+ TTY::Editor
49
+ end
50
+
51
+ # File manipulation utility methods
52
+ #
53
+ # @see http://www.rubydoc.info/gems/tty-file
54
+ #
55
+ # @api public
56
+ def generator
57
+ require 'tty-file'
58
+ TTY::File
59
+ end
60
+
61
+ # Terminal output paging
62
+ #
63
+ # @see http://www.rubydoc.info/gems/tty-pager
64
+ #
65
+ # @api public
66
+ def pager(**options)
67
+ require 'tty-pager'
68
+ TTY::Pager.new(options)
69
+ end
70
+
71
+ # Terminal platform and OS properties
72
+ #
73
+ # @see http://www.rubydoc.info/gems/tty-pager
74
+ #
75
+ # @api public
76
+ def platform
77
+ require 'tty-platform'
78
+ TTY::Platform.new
79
+ end
80
+
81
+ # The interactive prompt
82
+ #
83
+ # @see http://www.rubydoc.info/gems/tty-prompt
84
+ #
85
+ # @api public
86
+ def prompt(**options)
87
+ require 'tty-prompt'
88
+ TTY::Prompt.new(options)
89
+ end
90
+
91
+ # Get terminal screen properties
92
+ #
93
+ # @see http://www.rubydoc.info/gems/tty-screen
94
+ #
95
+ # @api public
96
+ def screen
97
+ require 'tty-screen'
98
+ TTY::Screen
99
+ end
100
+
101
+ # The unix which utility
102
+ #
103
+ # @see http://www.rubydoc.info/gems/tty-which
104
+ #
105
+ # @api public
106
+ def which(*args)
107
+ require 'tty-which'
108
+ TTY::Which.which(*args)
109
+ end
110
+
111
+ # Check if executable exists
112
+ #
113
+ # @see http://www.rubydoc.info/gems/tty-which
114
+ #
115
+ # @api public
116
+ def exec_exist?(*args)
117
+ require 'tty-which'
118
+ TTY::Which.exist?(*args)
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,445 @@
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 StandartError => 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[:to] = @mail_receivers["error"]
242
+ mail[:subject] = @mail_server["subject"]
243
+ mail.delivery_method :smtp, {
244
+ :address => @mail_server["address"],
245
+ :port => @mail_server["port"],
246
+ :user_name => @mail_server["smtp_user"],
247
+ :password => @mail_server["smtp_password"],
248
+ :authentication => @mail_server["smtp_user"].nil? ? "none" : "plain",
249
+ :ssl => @mail_server["ssl"],
250
+ :openssl_verify_mode => OpenSSL::SSL::VERIFY_NONE
251
+ }
252
+
253
+ # message
254
+ msg = "CKAN CLI task completed.\r\n\r\n#{@resources.length} files processed"
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
+ end
262
+ mail.add_file :filename => File.basename(res[:path] + ".log"), :content => File.read(res[:path] + ".log")
263
+ end
264
+ msg = "#{msg}\r\n\r\nLog files attached."
265
+ mail[:body] = msg
266
+
267
+ mail.deliver!
268
+ rescue StandardError => e
269
+ show_error "Could not send e-mail", e
270
+ end
271
+
272
+ show_info "End of sending"
273
+ end
274
+
275
+ def upload()
276
+ puts ""
277
+ show_info "Starting upload..."
278
+ @resources.each do |res|
279
+ next unless res[:valid]
280
+
281
+ task("Uploading resource: #{File.basename(res[:path])}") do
282
+ res[:log].info "Uploading resource..."
283
+
284
+ ok = true
285
+ msg = nil
286
+ exception = nil
287
+
288
+ params = @resource_config["result"].clone
289
+
290
+ if @update_modified
291
+ params["last_modified"] = JSON.parse(Time.new.to_json)
292
+ end
293
+
294
+ # check if resource exists and if overwriting
295
+ if !params["id"].nil? && !@overwrite
296
+ resource = @ckan_client.get_resource(params["id"])
297
+
298
+ if !resource.nil?
299
+ ok = false
300
+ msg = "Resource already exists - skipping. Use parameter 'overwrite' to overwrite changes."
301
+ end
302
+ end
303
+
304
+ if ok
305
+ @ckan_client.create_or_update_resource(params, File.new(res[:path], 'rb'), true){ |response, status_ok|
306
+ ok = status_ok
307
+
308
+ if !ok
309
+ msg = "HTTP error #{response.code}"
310
+
311
+ log_error response.body
312
+ end
313
+ }
314
+ end
315
+
316
+ if !ok
317
+ res[:log].error msg
318
+ else
319
+ res[:log].info "OK - upload successfull"
320
+ end
321
+ res[:log].info "End of upload"
322
+
323
+ [ok, msg, exception]
324
+ end
325
+ end
326
+
327
+ show_info "End of upload"
328
+ end
329
+
330
+ def update_dataset()
331
+ puts ""
332
+ show_info "Starting dataset update..."
333
+
334
+ task("Updating dataset") do
335
+ ok = false
336
+ msg = nil
337
+ exception = nil
338
+
339
+ @ckan_client.update_package(@dataset_config["result"]["id"], @dataset_config["result"].clone, true){ |response, status_ok|
340
+ ok = status_ok
341
+
342
+ if !ok
343
+ msg = "HTTP error #{response.code}"
344
+
345
+ log_error response.body
346
+ end
347
+ }
348
+
349
+ [ok, msg, exception]
350
+ end
351
+ show_info "End of dataset update"
352
+ end
353
+
354
+ def return_error(message, exception = nil)
355
+ show_error(message, exception)
356
+
357
+ exit 1
358
+ end
359
+
360
+ def show_error(message, exception = nil)
361
+ if !exception.nil?
362
+ message = "#{message} - #{exception.message}"
363
+ end
364
+
365
+ puts " #{message} ".white.on_red
366
+
367
+ log_fatal message, exception
368
+ end
369
+
370
+ def show_info(message)
371
+ puts " #{message} "
372
+
373
+ log_info message
374
+ end
375
+
376
+ def task(message)
377
+ log_info message
378
+
379
+ spinner = TTY::Spinner.new("[:spinner] #{message}...")
380
+ spinner.auto_spin
381
+ ok, message, exception = yield
382
+ spinner.stop(ok ? "OK".green : "ERROR".red)
383
+
384
+ if !message.nil?
385
+ message = " #{message}"
386
+
387
+ if !exception.nil?
388
+ show_error message, exception
389
+ else
390
+ show_info message
391
+ end
392
+ end
393
+ end
394
+
395
+ def read_resource(source)
396
+ # check if URL or file
397
+ unless source =~ /^http(s)?/
398
+ source = File.new( source )
399
+ end
400
+
401
+ source
402
+ end
403
+
404
+ def read_schema(path)
405
+ schema = Csvlint::Schema.load_from_uri(path, false)
406
+
407
+ schema
408
+ end
409
+
410
+ def format_validation(error, schema = nil)
411
+ h = {
412
+ type: error.type,
413
+ category: error.category,
414
+ row: error.row,
415
+ col: error.column
416
+ }
417
+
418
+ if error.column && !schema.nil? && schema.class == Csvlint::Schema && schema.fields[error.column - 1] != nil
419
+ field = schema.fields[error.column - 1]
420
+ h[:header] = field.name
421
+ end
422
+
423
+ h
424
+ end
425
+
426
+ def log_fatal(message, exception = nil)
427
+ if !@logger.nil?
428
+ @logger.fatal message, exception
429
+ end
430
+ end
431
+
432
+ def log_info(message)
433
+ if !@logger.nil?
434
+ @logger.info message
435
+ end
436
+ end
437
+
438
+ def log_error(message)
439
+ if !@logger.nil?
440
+ @logger.error message
441
+ end
442
+ end
443
+ end
444
+ end
445
+ end
@@ -0,0 +1,3 @@
1
+ module Ckancli
2
+ VERSION = "1.0.0"
3
+ end
data/lib/ckancli.rb ADDED
@@ -0,0 +1,6 @@
1
+ require "ckancli/version"
2
+
3
+ module Ckancli
4
+ class Error < StandardError; end
5
+ # Your code goes here...
6
+ end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'rest-client'
5
+
6
+ module CkanClient
7
+ class Client
8
+ def initialize(url, key)
9
+ raise ArgumentError, "No URL provided" unless !url.nil?
10
+ raise ArgumentError, "No API KEY provided" unless !key.nil?
11
+
12
+ # remove trailig slashes
13
+ @url = url.sub(/(\/)+$/,"")
14
+ @key = key
15
+ @headers = {}
16
+ @headers[:Authorization] = @key
17
+ end
18
+
19
+ def get_resource(id, params = {}, &block)
20
+ raise ArgumentError, "No ID provided for resource" unless !id.nil?
21
+
22
+ result = nil
23
+ params["id"] = id
24
+
25
+ post("resource_show", params, nil){ |response, status_ok|
26
+ if status_ok
27
+ result = JSON.parse(response.body)["result"]
28
+ end
29
+ }
30
+
31
+ return result
32
+ end
33
+
34
+ def create_resource(params = {}, file = nil, &block)
35
+ if params["name"].nil? && !file.nil?
36
+ params["name"] = File.basename(file.path)
37
+ end
38
+
39
+ post("resource_create", params, file, &block)
40
+ end
41
+
42
+ def update_resource(id, params = {}, file = nil, resolve_delta = false, &block)
43
+ raise ArgumentError, "No ID provided for resource update" unless !id.nil?
44
+
45
+ params["id"] = id
46
+
47
+ if resolve_delta
48
+ metadata = get_resource(id)
49
+ if !metadata.nil?
50
+ params = metadata.merge(params)
51
+ end
52
+ end
53
+
54
+ post("resource_update", params, file, &block)
55
+ end
56
+
57
+ def create_or_update_resource(params = {}, file = nil, resolve_delta = false, &block)
58
+ if params["id"].nil?
59
+ create_resource(params, file, &block)
60
+ else
61
+ update_resource(params["id"], params, file, resolve_delta, &block)
62
+ end
63
+ end
64
+
65
+ def get_package(id, params = {}, &block)
66
+ raise ArgumentError, "No ID provided for package" unless !id.nil?
67
+
68
+ result = nil
69
+ params["id"] = id
70
+
71
+ post("package_show", params, nil){ |response, status_ok|
72
+ if status_ok
73
+ result = JSON.parse(response.body)["result"]
74
+ end
75
+ }
76
+
77
+ return result
78
+ end
79
+
80
+ def create_package(params = {}, &block)
81
+ post("package_create", params, nil, &block)
82
+ end
83
+
84
+ def update_package(id, params = {}, resolve_delta = false, &block)
85
+ raise ArgumentError, "No ID provided for package update" unless !id.nil?
86
+
87
+ params["id"] = id
88
+
89
+ if resolve_delta
90
+ metadata = get_package(id)
91
+ if !metadata.nil?
92
+ params = metadata.merge(params)
93
+ end
94
+ end
95
+
96
+ post("package_update", params, nil, &block)
97
+ end
98
+
99
+ def create_or_update_package(params = {}, resolve_delta = false, &block)
100
+ if params["id"].nil?
101
+ create_package(params, &block)
102
+ else
103
+ update_package(params["id"], params, resolve_delta, &block)
104
+ end
105
+ end
106
+
107
+ private
108
+ def post(action, params = {}, file = nil, &block)
109
+ raise ArgumentError, "No action provided" unless !action.nil?
110
+
111
+ payload = {}
112
+ if !file.nil?
113
+ payload[:upload] = file
114
+ end
115
+ payload = payload.merge(params)
116
+
117
+ if file.nil?
118
+ payload = payload.to_json
119
+ @headers[:content_type] = :json
120
+ else
121
+ @headers.delete(:content_type)
122
+ end
123
+
124
+ RestClient.post("#{@url}/action/#{action}", payload, @headers){ |response, request, result|
125
+ block.call(response, response.code == 200)
126
+ }
127
+ end
128
+ end
129
+ end
metadata ADDED
@@ -0,0 +1,252 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ckan_cli
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - datagovlv
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-08-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: tty-color
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.4'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.4'
27
+ - !ruby/object:Gem::Dependency
28
+ name: tty-logger
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.1.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.1.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: tty-platform
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.2.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.2.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: tty-progressbar
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.16.0
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.16.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: tty-spinner
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.9.0
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.9.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: tty-which
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.4'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.4'
97
+ - !ruby/object:Gem::Dependency
98
+ name: pastel
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 0.7.2
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 0.7.2
111
+ - !ruby/object:Gem::Dependency
112
+ name: thor
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: 0.20.0
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: 0.20.0
125
+ - !ruby/object:Gem::Dependency
126
+ name: csvlint
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: 0.4.0
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: 0.4.0
139
+ - !ruby/object:Gem::Dependency
140
+ name: rest-client
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: 2.0.2
146
+ type: :runtime
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: 2.0.2
153
+ - !ruby/object:Gem::Dependency
154
+ name: mail
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: 2.7.1
160
+ type: :runtime
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: 2.7.1
167
+ - !ruby/object:Gem::Dependency
168
+ name: bundler
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - "~>"
172
+ - !ruby/object:Gem::Version
173
+ version: '1.17'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - "~>"
179
+ - !ruby/object:Gem::Version
180
+ version: '1.17'
181
+ - !ruby/object:Gem::Dependency
182
+ name: rake
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - "~>"
186
+ - !ruby/object:Gem::Version
187
+ version: '10.0'
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - "~>"
193
+ - !ruby/object:Gem::Version
194
+ version: '10.0'
195
+ - !ruby/object:Gem::Dependency
196
+ name: rspec
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - "~>"
200
+ - !ruby/object:Gem::Version
201
+ version: '3.0'
202
+ type: :development
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - "~>"
207
+ - !ruby/object:Gem::Version
208
+ version: '3.0'
209
+ description: CKAN CLI with built-in CSV file validation and CKAN API integration.
210
+ email:
211
+ - dati@varam.gov.lv
212
+ executables:
213
+ - ckancli.rb
214
+ extensions: []
215
+ extra_rdoc_files: []
216
+ files:
217
+ - LICENSE.txt
218
+ - README.md
219
+ - Rakefile
220
+ - bin/setup
221
+ - ckan_cli.gemspec
222
+ - exe/ckancli.rb
223
+ - lib/ckancli.rb
224
+ - lib/ckancli/cli.rb
225
+ - lib/ckancli/command.rb
226
+ - lib/ckancli/commands/upload.rb
227
+ - lib/ckancli/version.rb
228
+ - lib/ckanclient/ckanclient.rb
229
+ homepage: https://github.com/datagovlv/ckan_cli
230
+ licenses:
231
+ - MIT
232
+ metadata: {}
233
+ post_install_message:
234
+ rdoc_options: []
235
+ require_paths:
236
+ - lib
237
+ required_ruby_version: !ruby/object:Gem::Requirement
238
+ requirements:
239
+ - - ">="
240
+ - !ruby/object:Gem::Version
241
+ version: 2.0.0
242
+ required_rubygems_version: !ruby/object:Gem::Requirement
243
+ requirements:
244
+ - - ">="
245
+ - !ruby/object:Gem::Version
246
+ version: '0'
247
+ requirements: []
248
+ rubygems_version: 3.0.3
249
+ signing_key:
250
+ specification_version: 4
251
+ summary: CKAN command line interface.
252
+ test_files: []