lexicon-common 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lexicon-common.gemspec +32 -0
- data/lib/lexicon-common.rb +17 -0
- data/lib/lexicon/common.rb +7 -0
- data/lib/lexicon/common/database/database.rb +140 -0
- data/lib/lexicon/common/database/factory.rb +19 -0
- data/lib/lexicon/common/mixin/container_aware.rb +16 -0
- data/lib/lexicon/common/mixin/finalizable.rb +57 -0
- data/lib/lexicon/common/mixin/logger_aware.rb +26 -0
- data/lib/lexicon/common/mixin/schema_namer.rb +23 -0
- data/lib/lexicon/common/package/directory_package_loader.rb +67 -0
- data/lib/lexicon/common/package/package.rb +76 -0
- data/lib/lexicon/common/package/package_builder.rb +68 -0
- data/lib/lexicon/common/package/package_integrity_validator.rb +37 -0
- data/lib/lexicon/common/package/source_file_set.rb +24 -0
- data/lib/lexicon/common/production/datasource_loader.rb +112 -0
- data/lib/lexicon/common/production/file_loader.rb +55 -0
- data/lib/lexicon/common/remote/package_downloader.rb +84 -0
- data/lib/lexicon/common/remote/package_uploader.rb +58 -0
- data/lib/lexicon/common/remote/remote_base.rb +19 -0
- data/lib/lexicon/common/remote/s3_client.rb +37 -0
- data/lib/lexicon/common/schema/validator_factory.rb +24 -0
- data/lib/lexicon/common/shell_executor.rb +40 -0
- data/lib/lexicon/common/version.rb +5 -0
- data/resources/lexicon.schema.json +51 -0
- metadata +207 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: bb97bec1598709b8fefb46c28055ac8291edd138177995e08616de3b06dfbdb8
|
4
|
+
data.tar.gz: 20db194da73907714cb53ae861bd650ded613f71ef99ae1c93db370c8547c830
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: aa5b4f83c9647382dabdaa4dd09250fd4e165476ca3984740763bd4b2cd8f54ac41e73379a5988391a3336d31e9928ca9332598216b102829dff1efe28ab14dd
|
7
|
+
data.tar.gz: 296628e759a78533b06160d653ab6e1299d0d1378964ac20d5350c7b83892d0b385b4d78f1569c1844591c014db2a0130f9249e423946e7d3e38095f28029f5a
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lib/lexicon/common/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'lexicon-common'
|
7
|
+
spec.version = Lexicon::Common::VERSION
|
8
|
+
spec.authors = ['Ekylibre developers']
|
9
|
+
spec.email = ['dev@ekylibre.com']
|
10
|
+
|
11
|
+
spec.summary = "Common classes and services for Ekylibre's Lexicon"
|
12
|
+
spec.required_ruby_version = '>= 2.6.0'
|
13
|
+
spec.homepage = 'https://www.ekylibre.com'
|
14
|
+
spec.license = 'AGPL-3.0-only'
|
15
|
+
|
16
|
+
# Specify which files should be added to the gem when it is released.
|
17
|
+
spec.files = Dir.glob(%w[lib/**/*.rb resources/**/* *.gemspec])
|
18
|
+
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_dependency 'aws-sdk-s3', '~> 1.84'
|
22
|
+
spec.add_dependency 'colored', '~> 1.2'
|
23
|
+
spec.add_dependency 'json_schemer', '~> 0.2.16'
|
24
|
+
spec.add_dependency 'pg', '~> 1.2'
|
25
|
+
spec.add_dependency 'semantic', '~> 1.6'
|
26
|
+
spec.add_dependency 'zeitwerk', '~> 2.4'
|
27
|
+
|
28
|
+
spec.add_development_dependency 'bundler', '~> 2.0'
|
29
|
+
spec.add_development_dependency 'minitest', '~> 5.14'
|
30
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
31
|
+
spec.add_development_dependency 'rubocop', '1.11.0'
|
32
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# require 'lexicon/common'
|
4
|
+
require 'aws-sdk-s3'
|
5
|
+
require 'colored'
|
6
|
+
require 'logger'
|
7
|
+
require 'json_schemer'
|
8
|
+
require 'pg'
|
9
|
+
require 'semantic'
|
10
|
+
require 'zeitwerk'
|
11
|
+
|
12
|
+
# Require the common file as loading the version first through the gemspec prevents Zeitwerk to load it.
|
13
|
+
require_relative 'lexicon/common'
|
14
|
+
|
15
|
+
loader = Zeitwerk::Loader.for_gem
|
16
|
+
loader.ignore(__FILE__)
|
17
|
+
loader.setup
|
@@ -0,0 +1,140 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lexicon
|
4
|
+
module Common
|
5
|
+
module Database
|
6
|
+
class Database
|
7
|
+
class << self
|
8
|
+
def connect(url)
|
9
|
+
new(PG.connect(url))
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_writer :verbose
|
14
|
+
# @return [Array<String>]
|
15
|
+
attr_reader :search_path
|
16
|
+
|
17
|
+
def initialize(connection, verbose: false)
|
18
|
+
@connection = connection
|
19
|
+
@search_path = []
|
20
|
+
@verbose = verbose
|
21
|
+
|
22
|
+
disable_notices unless verbose
|
23
|
+
end
|
24
|
+
|
25
|
+
def verbose?
|
26
|
+
@verbose
|
27
|
+
end
|
28
|
+
|
29
|
+
def transaction(&block)
|
30
|
+
connection.transaction(&block)
|
31
|
+
end
|
32
|
+
|
33
|
+
def prepend_search_path(*parts, &block)
|
34
|
+
return if block.nil?
|
35
|
+
|
36
|
+
parts.each { |part| ensure_schema(part) }
|
37
|
+
|
38
|
+
with_search_path(*parts, *search_path) do
|
39
|
+
block.call
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def on_empty_schema(base_path: [], &block)
|
44
|
+
schema = make_random_schema_name
|
45
|
+
|
46
|
+
prepend_search_path(schema, *base_path) do
|
47
|
+
block.call(schema)
|
48
|
+
end
|
49
|
+
ensure
|
50
|
+
drop_schema(schema, cascade: true)
|
51
|
+
end
|
52
|
+
|
53
|
+
def drop_schema(name, cascade: false)
|
54
|
+
cascade = if cascade
|
55
|
+
' CASCADE'
|
56
|
+
else
|
57
|
+
''
|
58
|
+
end
|
59
|
+
|
60
|
+
query <<~SQL
|
61
|
+
DROP SCHEMA "#{name}"#{cascade};
|
62
|
+
SQL
|
63
|
+
end
|
64
|
+
|
65
|
+
def make_random_schema_name(prefix = 'lex')
|
66
|
+
"#{prefix}_#{rand(0x100000000).to_s(36)}"
|
67
|
+
end
|
68
|
+
|
69
|
+
def query(sql, *params, **_options)
|
70
|
+
pp sql if verbose?
|
71
|
+
if params.any?
|
72
|
+
@connection.exec_params(sql, params)
|
73
|
+
else
|
74
|
+
@connection.exec(sql)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# @param [#to_s] name
|
79
|
+
def ensure_schema(name)
|
80
|
+
query(<<~SQL)
|
81
|
+
CREATE SCHEMA IF NOT EXISTS "#{name}";
|
82
|
+
SQL
|
83
|
+
end
|
84
|
+
|
85
|
+
def ensure_schema_empty(name)
|
86
|
+
query(<<~SQL)
|
87
|
+
DROP SCHEMA IF EXISTS #{name} CASCADE;
|
88
|
+
SQL
|
89
|
+
|
90
|
+
ensure_schema(name)
|
91
|
+
end
|
92
|
+
|
93
|
+
def copy_data(sql, &block)
|
94
|
+
put_data = ->(d) { @connection.put_copy_data(d) }
|
95
|
+
@connection.copy_data(sql) { block.call(put_data) }
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
# @return [PG::Connection]
|
101
|
+
attr_reader :connection
|
102
|
+
|
103
|
+
def disable_notices
|
104
|
+
query <<~SQL
|
105
|
+
SET client_min_messages TO WARNING;
|
106
|
+
SQL
|
107
|
+
end
|
108
|
+
|
109
|
+
def with_search_path(*path, &block)
|
110
|
+
return if block.nil?
|
111
|
+
|
112
|
+
begin
|
113
|
+
saved_path = @search_path
|
114
|
+
@search_path = path
|
115
|
+
|
116
|
+
query <<~SQL
|
117
|
+
SET search_path TO #{path.map { |part| "\"#{part}\"" }.join(', ')};
|
118
|
+
SQL
|
119
|
+
|
120
|
+
result = block.call
|
121
|
+
|
122
|
+
result
|
123
|
+
ensure
|
124
|
+
path = if saved_path.any?
|
125
|
+
saved_path.map { |part| "\"#{part}\"" }.join(', ')
|
126
|
+
else
|
127
|
+
'" "'
|
128
|
+
end
|
129
|
+
|
130
|
+
query <<~SQL
|
131
|
+
SET search_path TO #{path};
|
132
|
+
SQL
|
133
|
+
|
134
|
+
@search_path = saved_path
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lexicon
|
4
|
+
module Common
|
5
|
+
module Database
|
6
|
+
class Factory
|
7
|
+
attr_reader :verbose
|
8
|
+
|
9
|
+
def initialize(verbose: false)
|
10
|
+
@verbose = verbose
|
11
|
+
end
|
12
|
+
|
13
|
+
def new_instance(url:)
|
14
|
+
Database.new(PG.connect(url), verbose: @verbose)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Lexicon
|
2
|
+
module Common
|
3
|
+
module Mixin
|
4
|
+
module Finalizable
|
5
|
+
def self.included(base)
|
6
|
+
class << base
|
7
|
+
alias_method :_new, :new
|
8
|
+
|
9
|
+
def new(*args, **options)
|
10
|
+
e = do_call(self, '_new', args, options)
|
11
|
+
|
12
|
+
ObjectSpace.define_finalizer(e, e.method(:_finalize))
|
13
|
+
|
14
|
+
e
|
15
|
+
end
|
16
|
+
|
17
|
+
# Empty Array and Hash splats are handled correctly starting ruby 2.7:
|
18
|
+
# if both args and kwargs are empty, no parameters are sent.
|
19
|
+
if ::Semantic::Version.new(RUBY_VERSION).satisfies?('>= 2.7.0')
|
20
|
+
private def do_call(obj, method, args, kwargs)
|
21
|
+
obj.send(method, *args, **kwargs)
|
22
|
+
end
|
23
|
+
else
|
24
|
+
private def do_call(obj, method, args, kwargs)
|
25
|
+
if args.empty? && kwargs.empty?
|
26
|
+
obj.send(method)
|
27
|
+
elsif args.empty?
|
28
|
+
obj.send(method, **kwargs)
|
29
|
+
elsif kwargs.empty?
|
30
|
+
obj.send(method, *args)
|
31
|
+
else
|
32
|
+
obj.send(method, *args, **kwargs)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def finalize
|
42
|
+
raise StandardError.new("Finalizer is not implemented in #{self.class.name}")
|
43
|
+
end
|
44
|
+
|
45
|
+
def _finalize(_id)
|
46
|
+
m = method(:finalize)
|
47
|
+
|
48
|
+
if !m.nil?
|
49
|
+
finalize
|
50
|
+
end
|
51
|
+
rescue StandardError => e
|
52
|
+
puts "Exception in finalizer: #{e.message}\n" + e.backtrace.join("\n")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lexicon
|
4
|
+
module Common
|
5
|
+
module Mixin
|
6
|
+
module LoggerAware
|
7
|
+
# @return [Logger]
|
8
|
+
attr_accessor :logger
|
9
|
+
|
10
|
+
def log(*args, **options)
|
11
|
+
if !logger.nil?
|
12
|
+
logger.debug(*args, **options)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def log_error(error)
|
17
|
+
if error.nil?
|
18
|
+
log('Error (nil)')
|
19
|
+
else
|
20
|
+
log([error.message, *error.backtrace].join("\n"))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lexicon
|
4
|
+
module Common
|
5
|
+
module Mixin
|
6
|
+
module SchemaNamer
|
7
|
+
protected
|
8
|
+
|
9
|
+
# @param [Semantic::Version] version
|
10
|
+
# @return [String]
|
11
|
+
def version_to_schema(version)
|
12
|
+
"lexicon__#{version.to_s.gsub('.', '_')}"
|
13
|
+
end
|
14
|
+
|
15
|
+
# @param [String] schema
|
16
|
+
# @return [Semantic::Version, nil]
|
17
|
+
def schema_to_version(schema)
|
18
|
+
Semantic::Version.new(schema.sub(/\Alexicon__/, '').gsub('_', '.'))
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lexicon
|
4
|
+
module Common
|
5
|
+
module Package
|
6
|
+
class DirectoryPackageLoader
|
7
|
+
include Mixin::LoggerAware
|
8
|
+
|
9
|
+
# @return [Pathname]
|
10
|
+
attr_reader :root_dir
|
11
|
+
|
12
|
+
# @param [Pathname] root_dir
|
13
|
+
# @param [JSONSchemer::Schema::Base] schema_validator
|
14
|
+
def initialize(root_dir, schema_validator:)
|
15
|
+
@root_dir = root_dir
|
16
|
+
@schema_validator = schema_validator
|
17
|
+
end
|
18
|
+
|
19
|
+
# @param [String] name
|
20
|
+
# @return [Package, nil]
|
21
|
+
def load_package(name)
|
22
|
+
package_dir = root_dir.join(name.to_s)
|
23
|
+
|
24
|
+
if package_dir.directory?
|
25
|
+
load_from_dir(package_dir)
|
26
|
+
else
|
27
|
+
nil
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
protected
|
32
|
+
|
33
|
+
def load_from_dir(dir)
|
34
|
+
# @type [Pathname]
|
35
|
+
spec_file = dir.join(Package::SPEC_FILE_NAME)
|
36
|
+
# @type [Pathname]
|
37
|
+
checksum_file = dir.join(Package::CHECKSUM_FILE_NAME)
|
38
|
+
|
39
|
+
if spec_file.exist? && checksum_file.exist?
|
40
|
+
json = JSON.parse(spec_file.read)
|
41
|
+
|
42
|
+
if @schema_validator.valid?(json)
|
43
|
+
version = Semantic::Version.new(json.fetch('version'))
|
44
|
+
file_sets = json.fetch('content').map do |id, values|
|
45
|
+
SourceFileSet.new(
|
46
|
+
id: id,
|
47
|
+
name: values.fetch('name'),
|
48
|
+
structure: values.fetch('structure'),
|
49
|
+
data: values.fetch('data', nil),
|
50
|
+
tables: values.fetch('tables', [])
|
51
|
+
)
|
52
|
+
end
|
53
|
+
|
54
|
+
Package.new(file_sets: file_sets, version: version, dir: dir, checksum_file: checksum_file, spec_file: spec_file)
|
55
|
+
else
|
56
|
+
log("Package at path #{dir} has invalid manifest")
|
57
|
+
|
58
|
+
nil
|
59
|
+
end
|
60
|
+
else
|
61
|
+
nil
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lexicon
|
4
|
+
module Common
|
5
|
+
module Package
|
6
|
+
class Package
|
7
|
+
SPEC_FILE_NAME = 'lexicon.json'
|
8
|
+
CHECKSUM_FILE_NAME = 'lexicon.sum'
|
9
|
+
|
10
|
+
# @return [Pathname]
|
11
|
+
attr_reader :checksum_file, :spec_file
|
12
|
+
# @return [Pathname]
|
13
|
+
attr_reader :dir
|
14
|
+
# @return [Array<SourceFileSet>]
|
15
|
+
attr_reader :file_sets
|
16
|
+
# @return [Semantic::Version]
|
17
|
+
attr_reader :version
|
18
|
+
|
19
|
+
# @param [Array<SourceFileSet>] file_sets
|
20
|
+
# @param [Pathname] dir
|
21
|
+
# @param [Pathname] checksum_file
|
22
|
+
# @param [Semantic::Version] version
|
23
|
+
def initialize(file_sets:, version:, dir:, checksum_file:, spec_file:)
|
24
|
+
@checksum_file = checksum_file
|
25
|
+
@dir = dir
|
26
|
+
@file_sets = file_sets
|
27
|
+
@spec_file = spec_file
|
28
|
+
@version = version
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [Boolean]
|
32
|
+
def valid?
|
33
|
+
checksum_file.exist? && dir.directory? && data_dir.directory? && all_sets_valid?
|
34
|
+
end
|
35
|
+
|
36
|
+
# @return [Array<Pathname>]
|
37
|
+
def structure_files
|
38
|
+
file_sets.map { |fs| dir.join(relative_structure_path(fs)) }
|
39
|
+
end
|
40
|
+
|
41
|
+
# @param [SourceFileSet] file_set
|
42
|
+
# @return [Pathname]
|
43
|
+
def data_path(file_set)
|
44
|
+
dir.join(relative_data_path(file_set))
|
45
|
+
end
|
46
|
+
|
47
|
+
# @return [Pathname]
|
48
|
+
def data_dir
|
49
|
+
dir.join('data')
|
50
|
+
end
|
51
|
+
|
52
|
+
# @return [Pathname, nil]
|
53
|
+
def relative_data_path(file_set)
|
54
|
+
if file_set.data_path.nil?
|
55
|
+
nil
|
56
|
+
else
|
57
|
+
data_dir.basename.join(file_set.data_path)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# @return [Pathname]
|
62
|
+
def relative_structure_path(file_set)
|
63
|
+
data_dir.basename.join(file_set.structure_path)
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def all_sets_valid?
|
69
|
+
file_sets.all? do |set|
|
70
|
+
data_dir.join(set.structure_path).exist? && !set.data_path.nil? && data_dir.join(set.data_path).exist?
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lexicon
|
4
|
+
module Common
|
5
|
+
module Package
|
6
|
+
class PackageBuilder < Package
|
7
|
+
def initialize(version:, dir:)
|
8
|
+
super(
|
9
|
+
file_sets: [],
|
10
|
+
version: version,
|
11
|
+
dir: dir,
|
12
|
+
checksum_file: dir.join(CHECKSUM_FILE_NAME),
|
13
|
+
spec_file: dir.join(SPEC_FILE_NAME)
|
14
|
+
)
|
15
|
+
|
16
|
+
FileUtils.mkdir_p(data_dir)
|
17
|
+
end
|
18
|
+
|
19
|
+
# @param [String] id
|
20
|
+
# @param [String] name
|
21
|
+
# @param [Pathname] structure
|
22
|
+
# Takes ownership of the file (moves it to the correct folder)
|
23
|
+
# @param [Array<String>] tables
|
24
|
+
# @param [Pathname] data
|
25
|
+
# Takes ownership of the file (moves it to the correct folder)
|
26
|
+
# @param [String] data_ext
|
27
|
+
def add_file_set(id, name:, structure:, tables:, data: nil, data_ext: '.sql')
|
28
|
+
# @type [Pathname] structure_file_path
|
29
|
+
structure_file_path = data_dir.join(structure_file_name(id))
|
30
|
+
FileUtils.mv(structure.to_s, structure_file_path.to_s)
|
31
|
+
|
32
|
+
# @type [Pathname] data_file_path
|
33
|
+
data_name = if data.nil?
|
34
|
+
nil
|
35
|
+
else
|
36
|
+
dname = data_file_name(id, data_ext)
|
37
|
+
path = data_dir.join(dname)
|
38
|
+
FileUtils.mv(data, path)
|
39
|
+
|
40
|
+
dname
|
41
|
+
end
|
42
|
+
|
43
|
+
file_sets << SourceFileSet.new(
|
44
|
+
id: id,
|
45
|
+
name: name,
|
46
|
+
structure: structure_file_name(id),
|
47
|
+
data: data.nil? ? nil : data_name,
|
48
|
+
tables: tables
|
49
|
+
)
|
50
|
+
end
|
51
|
+
|
52
|
+
def as_package
|
53
|
+
Package.new(version: version, dir: dir, file_sets: file_sets, checksum_file: checksum_file, spec_file: spec_file)
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def data_file_name(id, ext)
|
59
|
+
"#{id}#{ext}"
|
60
|
+
end
|
61
|
+
|
62
|
+
def structure_file_name(id)
|
63
|
+
"#{id}__structure.sql"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lexicon
|
4
|
+
module Common
|
5
|
+
module Package
|
6
|
+
class PackageIntegrityValidator
|
7
|
+
# @param [ShellExecutor] shell
|
8
|
+
def initialize(shell:)
|
9
|
+
@shell = shell
|
10
|
+
end
|
11
|
+
|
12
|
+
# @param [Package] package
|
13
|
+
# @return [Boolean]
|
14
|
+
def valid?(package)
|
15
|
+
integrity_states(package).values.all? { |v| v == true }
|
16
|
+
end
|
17
|
+
|
18
|
+
# @param [Package] package
|
19
|
+
# @return [Hash{String => Boolean}]
|
20
|
+
def integrity_states(package)
|
21
|
+
sumstr = shell.execute <<~BASH
|
22
|
+
(cd "#{package.dir}" && sha256sum -c #{package.checksum_file.basename} 2>/dev/null)
|
23
|
+
BASH
|
24
|
+
|
25
|
+
sumstr.scan(/(.*?): (.*?)\n/)
|
26
|
+
.to_h
|
27
|
+
.transform_values { |value| value == 'OK' }
|
28
|
+
end
|
29
|
+
|
30
|
+
protected
|
31
|
+
|
32
|
+
# @return [ShellExecutor]
|
33
|
+
attr_reader :shell
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lexicon
|
4
|
+
module Common
|
5
|
+
module Package
|
6
|
+
class SourceFileSet
|
7
|
+
attr_reader :id, :name, :structure_path, :data_path, :tables
|
8
|
+
|
9
|
+
# @param [String] id
|
10
|
+
# @param [String] name
|
11
|
+
# @param [String] structure
|
12
|
+
# @param [String] data
|
13
|
+
# @param [Array<String>] tables
|
14
|
+
def initialize(id:, name:, structure:, data:, tables:)
|
15
|
+
@id = id
|
16
|
+
@name = name
|
17
|
+
@structure_path = structure
|
18
|
+
@data_path = data
|
19
|
+
@tables = tables
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lexicon
|
4
|
+
module Common
|
5
|
+
module Production
|
6
|
+
class DatasourceLoader
|
7
|
+
include Mixin::SchemaNamer
|
8
|
+
# @param [ShellExecutor] shell
|
9
|
+
# @param [Database::Factory] database_factory
|
10
|
+
# @param [FileLoader] file_loader
|
11
|
+
# @param [String] database_url
|
12
|
+
def initialize(shell:, database_factory:, file_loader:, database_url:)
|
13
|
+
@shell = shell
|
14
|
+
@database_factory = database_factory
|
15
|
+
@file_loader = file_loader
|
16
|
+
@database_url = database_url
|
17
|
+
end
|
18
|
+
|
19
|
+
# @param [Package::Package] package
|
20
|
+
# @param [Array<String>, nil] only
|
21
|
+
# @param [Array<String>] without
|
22
|
+
# If nil, all datasets are loaded.
|
23
|
+
# If present, only listed datasets are loaded.
|
24
|
+
# Structures are ALWAYS loaded
|
25
|
+
def load_package(package, only: nil, without: [])
|
26
|
+
file_sets = if only.nil?
|
27
|
+
package.file_sets.select(&:data_path)
|
28
|
+
else
|
29
|
+
sets_by_name = package.file_sets.map { |fs| [fs.name, fs] }.to_h
|
30
|
+
|
31
|
+
missing, present = only.map { |name| [name, sets_by_name.fetch(name, nil)] }
|
32
|
+
.partition { |(_name, value)| value.nil? }
|
33
|
+
|
34
|
+
if missing.any?
|
35
|
+
puts "[ NOK ] Datasources #{missing.map(&:first).join(', ')} don't exist!"
|
36
|
+
return
|
37
|
+
end
|
38
|
+
|
39
|
+
present.map(&:second)
|
40
|
+
.select(&:data_path)
|
41
|
+
end
|
42
|
+
|
43
|
+
file_sets = file_sets.reject { |fs| without.include?(fs.name) }
|
44
|
+
|
45
|
+
load_structure_files(package.structure_files, schema: version_to_schema(package.version))
|
46
|
+
|
47
|
+
file_sets.map do |fs|
|
48
|
+
Thread.new do
|
49
|
+
puts "Loading #{fs.name}"
|
50
|
+
file_loader.load_file(package.data_path(fs))
|
51
|
+
puts '[ OK ] '.green + fs.name.yellow
|
52
|
+
end
|
53
|
+
end.each(&:join)
|
54
|
+
|
55
|
+
lock_tables(package)
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
# @return [Database::Factory]
|
61
|
+
attr_reader :database_factory
|
62
|
+
# @return [ShellExecutor]
|
63
|
+
attr_reader :shell
|
64
|
+
# @return [FileLoader]
|
65
|
+
attr_reader :file_loader
|
66
|
+
# @return [String]
|
67
|
+
attr_reader :database_url
|
68
|
+
|
69
|
+
def load_structure_files(files, schema:)
|
70
|
+
database = database_factory.new_instance(url: database_url)
|
71
|
+
database.prepend_search_path(schema) do
|
72
|
+
files.each do |file|
|
73
|
+
database.query(file.read)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# @param [Package::Package] package
|
79
|
+
def lock_tables(package)
|
80
|
+
database = database_factory.new_instance(url: database_url)
|
81
|
+
|
82
|
+
schema = version_to_schema(package.version)
|
83
|
+
|
84
|
+
database.prepend_search_path schema do
|
85
|
+
database.query <<~SQL
|
86
|
+
CREATE OR REPLACE FUNCTION #{schema}.deny_changes()
|
87
|
+
RETURNS TRIGGER
|
88
|
+
AS $$
|
89
|
+
BEGIN
|
90
|
+
RAISE EXCEPTION '% denied on % (master data)', TG_OP, TG_RELNAME;
|
91
|
+
END;
|
92
|
+
$$
|
93
|
+
LANGUAGE plpgsql;
|
94
|
+
SQL
|
95
|
+
package.file_sets.flat_map(&:tables).each do |table_name|
|
96
|
+
database.query <<~SQL
|
97
|
+
CREATE TRIGGER deny_changes
|
98
|
+
BEFORE INSERT
|
99
|
+
OR UPDATE
|
100
|
+
OR DELETE
|
101
|
+
OR TRUNCATE
|
102
|
+
ON #{schema}.#{table_name}
|
103
|
+
FOR EACH STATEMENT
|
104
|
+
EXECUTE PROCEDURE #{schema}.deny_changes()
|
105
|
+
SQL
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lexicon
|
4
|
+
module Common
|
5
|
+
module Production
|
6
|
+
class FileLoader
|
7
|
+
# @param [ShellExecutor] shell
|
8
|
+
# @param [String] database_url
|
9
|
+
def initialize(shell:, database_url:)
|
10
|
+
@shell = shell
|
11
|
+
@database_url = database_url
|
12
|
+
end
|
13
|
+
|
14
|
+
# @param [Pathname] data_file
|
15
|
+
# @return [Boolean]
|
16
|
+
def load_file(data_file)
|
17
|
+
if data_file.basename.to_s =~ /\.sql\z/
|
18
|
+
load_sql(data_file)
|
19
|
+
elsif data_file.basename.to_s =~ /\.sql\.gz\z/
|
20
|
+
load_archive(data_file)
|
21
|
+
else
|
22
|
+
raise StandardError.new("Unknown file type: #{data_file.basename}")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# @param [Pathname] archive
|
27
|
+
# @return [Boolean]
|
28
|
+
def load_archive(archive)
|
29
|
+
shell.execute <<~BASH
|
30
|
+
cat '#{archive}' | gzip -d | psql '#{database_url}'
|
31
|
+
BASH
|
32
|
+
|
33
|
+
true
|
34
|
+
end
|
35
|
+
|
36
|
+
# @param [Pathname] file
|
37
|
+
# @return [Boolean]
|
38
|
+
def load_sql(file)
|
39
|
+
shell.execute <<~BASH
|
40
|
+
echo psql '#{database_url}' < '#{file}'
|
41
|
+
BASH
|
42
|
+
|
43
|
+
true
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
# @return [String]
|
49
|
+
attr_reader :database_url
|
50
|
+
# @return [ShellExecutor]
|
51
|
+
attr_reader :shell
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lexicon
|
4
|
+
module Common
|
5
|
+
module Remote
|
6
|
+
class PackageDownloader < RemoteBase
|
7
|
+
# @param [S3Client] s3
|
8
|
+
# @param [Pathname] out_dir
|
9
|
+
# @param [DirectoryPackageLoader] package_loader
|
10
|
+
def initialize(s3:, out_dir:, package_loader:)
|
11
|
+
super(s3: s3)
|
12
|
+
@out_dir = out_dir
|
13
|
+
@package_loader = package_loader
|
14
|
+
end
|
15
|
+
|
16
|
+
# @param [Semantic::Version] version
|
17
|
+
# @return [Boolean]
|
18
|
+
def download(version)
|
19
|
+
bucket = version.to_s
|
20
|
+
|
21
|
+
if s3.bucket_exist?(bucket)
|
22
|
+
Dir.mktmpdir(nil, out_dir) do |tmp_dir|
|
23
|
+
tmp_dir = Pathname.new(tmp_dir)
|
24
|
+
|
25
|
+
s3.raw.get_object(
|
26
|
+
bucket: bucket,
|
27
|
+
key: Package::Package::SPEC_FILE_NAME,
|
28
|
+
response_target: tmp_dir.join(Package::Package::SPEC_FILE_NAME).to_s
|
29
|
+
)
|
30
|
+
s3.raw.get_object(
|
31
|
+
bucket: bucket,
|
32
|
+
key: Package::Package::CHECKSUM_FILE_NAME,
|
33
|
+
response_target: tmp_dir.join(Package::Package::CHECKSUM_FILE_NAME).to_s
|
34
|
+
)
|
35
|
+
|
36
|
+
package = package_loader.load_package(tmp_dir.basename.to_s)
|
37
|
+
if !package.nil?
|
38
|
+
puts "[ OK ] Found package with key #{version}, version is #{package.version}".green
|
39
|
+
|
40
|
+
FileUtils.mkdir_p package.data_dir
|
41
|
+
|
42
|
+
package.structure_files.map do |file|
|
43
|
+
Thread.new do
|
44
|
+
s3.raw.get_object(bucket: bucket, key: "data/#{file.basename.to_s}", response_target: file.to_s)
|
45
|
+
puts "[ OK ] Downloaded #{file.basename}".green
|
46
|
+
end
|
47
|
+
end.each(&:join)
|
48
|
+
|
49
|
+
package.file_sets.map do |fs|
|
50
|
+
Thread.new do
|
51
|
+
path = package.data_path(fs)
|
52
|
+
s3.raw.get_object(bucket: bucket, key: "data/#{path.basename.to_s}", response_target: path.to_s)
|
53
|
+
puts "[ OK ] Downloaded #{path.basename}".green
|
54
|
+
end
|
55
|
+
end.each(&:join)
|
56
|
+
|
57
|
+
dest_dir = out_dir.join(version.to_s)
|
58
|
+
FileUtils.mkdir_p(dest_dir)
|
59
|
+
tmp_dir.children.each do |child|
|
60
|
+
FileUtils.mv(child.to_s, dest_dir.join(child.basename).to_s)
|
61
|
+
end
|
62
|
+
|
63
|
+
true
|
64
|
+
else
|
65
|
+
puts "[ NOK ] The remote contains a bucket '#{version}' but it does not contains a valid package.".red
|
66
|
+
|
67
|
+
false
|
68
|
+
end
|
69
|
+
end
|
70
|
+
else
|
71
|
+
false
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
# @return [DirectoryPackageLoader]
|
78
|
+
attr_reader :package_loader
|
79
|
+
# @return [Pathname]
|
80
|
+
attr_reader :out_dir
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lexicon
|
4
|
+
module Common
|
5
|
+
module Remote
|
6
|
+
class PackageUploader < RemoteBase
|
7
|
+
include Mixin::LoggerAware
|
8
|
+
|
9
|
+
# @param [Package] package
|
10
|
+
# @return [Boolean]
|
11
|
+
def upload(package)
|
12
|
+
bucket_name = package.version.to_s
|
13
|
+
if !s3.bucket_exist?(bucket_name)
|
14
|
+
s3.create_bucket(bucket: bucket_name)
|
15
|
+
puts 'Uploading structures...'
|
16
|
+
|
17
|
+
upload_files(*package.structure_files, bucket: bucket_name, prefix: 'data')
|
18
|
+
puts '[ OK ] Structure uploaded.'.green
|
19
|
+
|
20
|
+
data_files = package.file_sets
|
21
|
+
.select(&:data_path)
|
22
|
+
.map { |fs| package.data_path(fs) }
|
23
|
+
|
24
|
+
upload_files(*data_files, bucket: bucket_name, prefix: 'data') do |path|
|
25
|
+
puts "[ OK ] #{path.basename}".green
|
26
|
+
end
|
27
|
+
|
28
|
+
upload_files(package.checksum_file, package.spec_file, bucket: bucket_name) do |path|
|
29
|
+
puts "[ OK ] #{path.basename}".green
|
30
|
+
end
|
31
|
+
|
32
|
+
true
|
33
|
+
else
|
34
|
+
false
|
35
|
+
end
|
36
|
+
rescue StandardError => e
|
37
|
+
log_error(e)
|
38
|
+
|
39
|
+
false
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
# @param [Array<Pathname>] files
|
45
|
+
#
|
46
|
+
# @yieldparam [Pathname] path
|
47
|
+
def upload_files(*files, bucket:, prefix: nil)
|
48
|
+
files.each do |path|
|
49
|
+
path.open do |f|
|
50
|
+
s3.put_object(bucket: bucket, key: [prefix, path.basename.to_s].compact.join('/'), body: f)
|
51
|
+
end
|
52
|
+
yield path if block_given?
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lexicon
|
4
|
+
module Common
|
5
|
+
module Remote
|
6
|
+
class RemoteBase
|
7
|
+
# @param [S3Client] s3
|
8
|
+
def initialize(s3:)
|
9
|
+
@s3 = s3
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
# @return [S3Client]
|
15
|
+
attr_reader :s3
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lexicon
|
4
|
+
module Common
|
5
|
+
module Remote
|
6
|
+
class S3Client
|
7
|
+
# @return [Aws::S3::Client]
|
8
|
+
attr_reader :raw
|
9
|
+
|
10
|
+
# @param [Aws::S3::Client] raw
|
11
|
+
def initialize(raw:)
|
12
|
+
@raw = raw
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return [Array<Object>]
|
16
|
+
def ls(bucket)
|
17
|
+
raw.list_objects_v2(bucket: bucket)
|
18
|
+
.to_h
|
19
|
+
.fetch(:contents, [])
|
20
|
+
end
|
21
|
+
|
22
|
+
# @param [String] name
|
23
|
+
# @return [Boolean]
|
24
|
+
def bucket_exist?(name)
|
25
|
+
if raw.head_bucket(bucket: name)
|
26
|
+
true
|
27
|
+
else
|
28
|
+
false
|
29
|
+
end
|
30
|
+
rescue StandardError
|
31
|
+
false
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lexicon
|
4
|
+
module Common
|
5
|
+
module Schema
|
6
|
+
class ValidatorFactory
|
7
|
+
# @param [Pathname] schema_path
|
8
|
+
def initialize(schema_path)
|
9
|
+
@schema_path = schema_path
|
10
|
+
end
|
11
|
+
|
12
|
+
# @return [JSONSchemer::Schema]
|
13
|
+
def build
|
14
|
+
JSONSchemer.schema(schema_path)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
# @return [Pathname]
|
20
|
+
attr_reader :schema_path
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lexicon
|
4
|
+
module Common
|
5
|
+
class ShellExecutor
|
6
|
+
include Mixin::Finalizable
|
7
|
+
include Mixin::LoggerAware
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@command_dir = Dir.mktmpdir
|
11
|
+
end
|
12
|
+
|
13
|
+
# @param [String] command
|
14
|
+
# @return [String]
|
15
|
+
def execute(command)
|
16
|
+
log(command.cyan)
|
17
|
+
|
18
|
+
cmd = Tempfile.new('command-', @command_dir)
|
19
|
+
cmd.write <<~BASH
|
20
|
+
#!/usr/bin/env bash
|
21
|
+
set -e
|
22
|
+
|
23
|
+
#{command}
|
24
|
+
BASH
|
25
|
+
cmd.close
|
26
|
+
|
27
|
+
`bash #{cmd.path}`
|
28
|
+
ensure
|
29
|
+
cmd.close
|
30
|
+
cmd.unlink
|
31
|
+
end
|
32
|
+
|
33
|
+
def finalize
|
34
|
+
if !@command_dir.nil?
|
35
|
+
FileUtils.rm_rf(@command_dir)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
{
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
3
|
+
"$id": "https://lexicon.ekylibre.dev/lexicon.schema.json",
|
4
|
+
"title": "Lexicon Package JSON Schema",
|
5
|
+
"description": "",
|
6
|
+
"type": "object",
|
7
|
+
"properties": {
|
8
|
+
"version": {
|
9
|
+
"description": "The version of the packaged version",
|
10
|
+
"type": "string",
|
11
|
+
"$comment": "Regex is from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string",
|
12
|
+
"pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$"
|
13
|
+
},
|
14
|
+
"content": {
|
15
|
+
"type": "object",
|
16
|
+
"patternProperties": {
|
17
|
+
"^[a-z][a-z_]*$": {
|
18
|
+
"type": "object",
|
19
|
+
"properties": {
|
20
|
+
"name": {
|
21
|
+
"type": "string"
|
22
|
+
},
|
23
|
+
"structure": {
|
24
|
+
"type": "string"
|
25
|
+
},
|
26
|
+
"data": {
|
27
|
+
"type": "string"
|
28
|
+
},
|
29
|
+
"tables": {
|
30
|
+
"type": "array",
|
31
|
+
"items": {
|
32
|
+
"type": "string"
|
33
|
+
},
|
34
|
+
"additionalItems": false
|
35
|
+
}
|
36
|
+
},
|
37
|
+
"required": [
|
38
|
+
"name",
|
39
|
+
"structure",
|
40
|
+
"tables"
|
41
|
+
]
|
42
|
+
}
|
43
|
+
},
|
44
|
+
"additionalProperties": false
|
45
|
+
}
|
46
|
+
},
|
47
|
+
"required": [
|
48
|
+
"version",
|
49
|
+
"content"
|
50
|
+
]
|
51
|
+
}
|
metadata
ADDED
@@ -0,0 +1,207 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: lexicon-common
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ekylibre developers
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-03-31 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: aws-sdk-s3
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.84'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.84'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: colored
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.2'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.2'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: json_schemer
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.2.16
|
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.16
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pg
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.2'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.2'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: semantic
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '1.6'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '1.6'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: zeitwerk
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '2.4'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '2.4'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: bundler
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '2.0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '2.0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: minitest
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '5.14'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '5.14'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: rake
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '13.0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '13.0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: rubocop
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - '='
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: 1.11.0
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - '='
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: 1.11.0
|
153
|
+
description:
|
154
|
+
email:
|
155
|
+
- dev@ekylibre.com
|
156
|
+
executables: []
|
157
|
+
extensions: []
|
158
|
+
extra_rdoc_files: []
|
159
|
+
files:
|
160
|
+
- lexicon-common.gemspec
|
161
|
+
- lib/lexicon-common.rb
|
162
|
+
- lib/lexicon/common.rb
|
163
|
+
- lib/lexicon/common/database/database.rb
|
164
|
+
- lib/lexicon/common/database/factory.rb
|
165
|
+
- lib/lexicon/common/mixin/container_aware.rb
|
166
|
+
- lib/lexicon/common/mixin/finalizable.rb
|
167
|
+
- lib/lexicon/common/mixin/logger_aware.rb
|
168
|
+
- lib/lexicon/common/mixin/schema_namer.rb
|
169
|
+
- lib/lexicon/common/package/directory_package_loader.rb
|
170
|
+
- lib/lexicon/common/package/package.rb
|
171
|
+
- lib/lexicon/common/package/package_builder.rb
|
172
|
+
- lib/lexicon/common/package/package_integrity_validator.rb
|
173
|
+
- lib/lexicon/common/package/source_file_set.rb
|
174
|
+
- lib/lexicon/common/production/datasource_loader.rb
|
175
|
+
- lib/lexicon/common/production/file_loader.rb
|
176
|
+
- lib/lexicon/common/remote/package_downloader.rb
|
177
|
+
- lib/lexicon/common/remote/package_uploader.rb
|
178
|
+
- lib/lexicon/common/remote/remote_base.rb
|
179
|
+
- lib/lexicon/common/remote/s3_client.rb
|
180
|
+
- lib/lexicon/common/schema/validator_factory.rb
|
181
|
+
- lib/lexicon/common/shell_executor.rb
|
182
|
+
- lib/lexicon/common/version.rb
|
183
|
+
- resources/lexicon.schema.json
|
184
|
+
homepage: https://www.ekylibre.com
|
185
|
+
licenses:
|
186
|
+
- AGPL-3.0-only
|
187
|
+
metadata: {}
|
188
|
+
post_install_message:
|
189
|
+
rdoc_options: []
|
190
|
+
require_paths:
|
191
|
+
- lib
|
192
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
193
|
+
requirements:
|
194
|
+
- - ">="
|
195
|
+
- !ruby/object:Gem::Version
|
196
|
+
version: 2.6.0
|
197
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
198
|
+
requirements:
|
199
|
+
- - ">="
|
200
|
+
- !ruby/object:Gem::Version
|
201
|
+
version: '0'
|
202
|
+
requirements: []
|
203
|
+
rubygems_version: 3.0.3
|
204
|
+
signing_key:
|
205
|
+
specification_version: 4
|
206
|
+
summary: Common classes and services for Ekylibre's Lexicon
|
207
|
+
test_files: []
|