lexicon-common 0.1.0 → 0.2.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.
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lexicon
4
+ module Common
5
+ class Psql
6
+ # @param [String] url
7
+ # @param [ShellExecutor] executor
8
+ def initialize(url:, executor:)
9
+ @url = url
10
+ @executor = executor
11
+ end
12
+
13
+ # @param [String] command
14
+ # @param [String, Array<String>] search_path
15
+ def execute(command, search_path:)
16
+ command = <<~SQL
17
+ SET search_path TO #{Array(search_path).join(', ')};
18
+ #{command}
19
+ SQL
20
+
21
+ execute_raw(command)
22
+ end
23
+
24
+ # @param [String] command
25
+ def execute_raw(command)
26
+ @executor.execute <<~BASH
27
+ psql '#{url}' --quiet -c #{Shellwords.escape(command)}
28
+ BASH
29
+ end
30
+
31
+ # @param [Pathname] file
32
+ # @param [String, Array<String>] search_path
33
+ def load_sql(file, search_path:)
34
+ @executor.execute <<~BASH
35
+ echo 'SET SEARCH_PATH TO #{Array(search_path).join(', ')};' | cat - #{file} | psql '#{url}'
36
+ BASH
37
+ end
38
+
39
+ private
40
+
41
+ # @return [ShellExecutor]
42
+ attr_reader :executor
43
+ # @return [String]
44
+ attr_reader :url
45
+ end
46
+ end
47
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ using Corindon::Result::Ext
4
+
3
5
  module Lexicon
4
6
  module Common
5
7
  module Remote
@@ -9,71 +11,87 @@ module Lexicon
9
11
  # @param [DirectoryPackageLoader] package_loader
10
12
  def initialize(s3:, out_dir:, package_loader:)
11
13
  super(s3: s3)
14
+
12
15
  @out_dir = out_dir
13
16
  @package_loader = package_loader
14
17
  end
15
18
 
16
19
  # @param [Semantic::Version] version
17
- # @return [Boolean]
20
+ # @return [Corindon::Result::Result]
18
21
  def download(version)
19
- bucket = version.to_s
22
+ rescue_failure do
23
+ bucket = version.to_s
20
24
 
21
- if s3.bucket_exist?(bucket)
22
- Dir.mktmpdir(nil, out_dir) do |tmp_dir|
23
- tmp_dir = Pathname.new(tmp_dir)
25
+ if s3.bucket_exist?(bucket)
26
+ Dir.mktmpdir(nil, out_dir) do |tmp_dir|
27
+ tmp_dir = Pathname.new(tmp_dir)
24
28
 
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
- )
29
+ download_spec_files(bucket, tmp_dir).unwrap!
35
30
 
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
31
+ package = package_loader.load_package(tmp_dir.basename.to_s)
32
+ if !package.nil?
33
+ puts "[ OK ] Found package with key #{version}, version is #{package.version}".green
39
34
 
40
- FileUtils.mkdir_p package.data_dir
35
+ download_data_files(package, bucket).unwrap!
41
36
 
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)
37
+ dest_dir = out_dir.join(version.to_s)
38
+ FileUtils.mkdir_p(dest_dir)
48
39
 
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
40
+ tmp_dir.children.each do |child|
41
+ FileUtils.mv(child.to_s, dest_dir.join(child.basename).to_s)
54
42
  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
43
 
63
- true
64
- else
65
- puts "[ NOK ] The remote contains a bucket '#{version}' but it does not contains a valid package.".red
44
+ Success(package)
45
+ else
46
+ puts "[ NOK ] The remote contains a bucket '#{version}' but it does not contains a valid package.".red
66
47
 
67
- false
48
+ Failure(StandardError.new("The folder #{bucket} on the server does not contain a valid package"))
49
+ end
68
50
  end
51
+ else
52
+ Failure(StandardError.new("The server does not have a directory named #{bucket}"))
69
53
  end
70
- else
71
- false
72
54
  end
73
55
  end
74
56
 
75
57
  private
76
58
 
59
+ def download_data_files(package, bucket)
60
+ rescue_failure do
61
+ threads = package.files.map do |file|
62
+ Thread.new do
63
+ destination = package.dir.join(file.path)
64
+ FileUtils.mkdir_p(destination.dirname)
65
+
66
+ s3.raw.get_object(bucket: bucket, key: file.to_s, response_target: destination)
67
+
68
+ puts "[ OK ] Downloaded #{file}".green
69
+ end
70
+ end
71
+
72
+ threads.each(&:join)
73
+
74
+ Success(nil)
75
+ end
76
+ end
77
+
78
+ def download_spec_files(bucket, tmp_dir)
79
+ rescue_failure do
80
+ s3.raw.get_object(
81
+ bucket: bucket,
82
+ key: Package::Package::SPEC_FILE_NAME,
83
+ response_target: tmp_dir.join(Package::Package::SPEC_FILE_NAME).to_s
84
+ )
85
+ s3.raw.get_object(
86
+ bucket: bucket,
87
+ key: Package::Package::CHECKSUM_FILE_NAME,
88
+ response_target: tmp_dir.join(Package::Package::CHECKSUM_FILE_NAME).to_s
89
+ )
90
+
91
+ Success(nil)
92
+ end
93
+ end
94
+
77
95
  # @return [DirectoryPackageLoader]
78
96
  attr_reader :package_loader
79
97
  # @return [Pathname]
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ using Corindon::Result::Ext
4
+
3
5
  module Lexicon
4
6
  module Common
5
7
  module Remote
@@ -7,51 +9,58 @@ module Lexicon
7
9
  include Mixin::LoggerAware
8
10
 
9
11
  # @param [Package] package
10
- # @return [Boolean]
12
+ # @return [Corindon::Result::Result]
11
13
  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...'
14
+ rescue_failure do
15
+ bucket_name = package.version.to_s
16
+
17
+ if s3.bucket_exist?(bucket_name)
18
+ Failure(StandardError.new("The server already has a folder named #{bucket_name}"))
19
+ else
20
+ upload_package(package, bucket_name)
21
+ end
22
+ end
23
+ end
16
24
 
17
- upload_files(*package.structure_files, bucket: bucket_name, prefix: 'data')
18
- puts '[ OK ] Structure uploaded.'.green
25
+ private
19
26
 
20
- data_files = package.file_sets
21
- .select(&:data_path)
22
- .map { |fs| package.data_path(fs) }
27
+ # @return [Corindon::Result::Result]
28
+ def upload_package(package, bucket_name)
29
+ s3.raw.create_bucket(bucket: bucket_name)
23
30
 
24
- upload_files(*data_files, bucket: bucket_name, prefix: 'data') do |path|
25
- puts "[ OK ] #{path.basename}".green
26
- end
31
+ relative_paths = [*base_files, *package.files.map(&:path)]
27
32
 
28
- upload_files(package.checksum_file, package.spec_file, bucket: bucket_name) do |path|
33
+ upload_files(*relative_paths, from: package.dir, bucket: bucket_name) do |path|
29
34
  puts "[ OK ] #{path.basename}".green
30
35
  end
31
36
 
32
- true
33
- else
34
- false
35
- end
36
- rescue StandardError => e
37
- log_error(e)
38
-
39
- false
40
- end
37
+ Success(package)
38
+ rescue StandardError => e
39
+ s3.ensure_bucket_absent(bucket_name)
41
40
 
42
- private
41
+ Failure(e)
42
+ end
43
43
 
44
44
  # @param [Array<Pathname>] files
45
- #
45
+ # @param [Pathname] from
46
46
  # @yieldparam [Pathname] path
47
- def upload_files(*files, bucket:, prefix: nil)
47
+ def upload_files(*files, bucket:, from:)
48
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)
49
+ from.join(path).open do |f|
50
+ s3.raw.put_object(bucket: bucket, key: path.to_s, body: f)
51
51
  end
52
+
52
53
  yield path if block_given?
53
54
  end
54
55
  end
56
+
57
+ # @return [Array<Pathname>]
58
+ def base_files
59
+ [
60
+ Pathname.new(Package::Package::CHECKSUM_FILE_NAME),
61
+ Pathname.new(Package::Package::SPEC_FILE_NAME),
62
+ ]
63
+ end
55
64
  end
56
65
  end
57
66
  end
@@ -31,6 +31,12 @@ module Lexicon
31
31
  false
32
32
  end
33
33
 
34
+ # @param [String] name
35
+ def ensure_bucket_absent(name)
36
+ if bucket_exist?(name)
37
+ raw.delete_bucket(bucket: name)
38
+ end
39
+ end
34
40
  end
35
41
  end
36
42
  end
@@ -9,7 +9,7 @@ module Lexicon
9
9
  @schema_path = schema_path
10
10
  end
11
11
 
12
- # @return [JSONSchemer::Schema]
12
+ # @return [JSONSchemer::Schema::Base]
13
13
  def build
14
14
  JSONSchemer.schema(schema_path)
15
15
  end
@@ -4,7 +4,6 @@ module Lexicon
4
4
  module Common
5
5
  class ShellExecutor
6
6
  include Mixin::Finalizable
7
- include Mixin::LoggerAware
8
7
 
9
8
  def initialize
10
9
  @command_dir = Dir.mktmpdir
@@ -13,8 +12,6 @@ module Lexicon
13
12
  # @param [String] command
14
13
  # @return [String]
15
14
  def execute(command)
16
- log(command.cyan)
17
-
18
15
  cmd = Tempfile.new('command-', @command_dir)
19
16
  cmd.write <<~BASH
20
17
  #!/usr/bin/env bash
@@ -1,5 +1,5 @@
1
1
  module Lexicon
2
2
  module Common
3
- VERSION = '0.1.0'.freeze
3
+ VERSION = '0.2.0'.freeze
4
4
  end
5
5
  end
@@ -5,47 +5,132 @@
5
5
  "description": "",
6
6
  "type": "object",
7
7
  "properties": {
8
+ "schema_version": {
9
+ "$ref": "#/definitions/schema_versions"
10
+ },
8
11
  "version": {
12
+ "$ref": "#/definitions/semver"
13
+ }
14
+ },
15
+ "oneOf": [
16
+ {
17
+ "properties": {
18
+ "schema_version": {
19
+ "type": "integer",
20
+ "const": 1
21
+ },
22
+ "content": {
23
+ "$ref": "#/definitions/content/1"
24
+ }
25
+ }
26
+ },
27
+ {
28
+ "properties": {
29
+ "schema_version": {
30
+ "type": "integer",
31
+ "const": 2
32
+ },
33
+ "content": {
34
+ "$ref": "#/definitions/content/2"
35
+ }
36
+ }
37
+ }
38
+ ],
39
+ "required": [
40
+ "version",
41
+ "content"
42
+ ],
43
+ "definitions": {
44
+ "schema_versions": {
45
+ "type": "integer",
46
+ "default": 2,
47
+ "enum": [
48
+ 2
49
+ ]
50
+ },
51
+ "semver": {
9
52
  "description": "The version of the packaged version",
10
53
  "type": "string",
11
54
  "$comment": "Regex is from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string",
12
55
  "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
56
  },
14
57
  "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"
58
+ "1": {
59
+ "type": "object",
60
+ "patternProperties": {
61
+ "^[a-z][a-z_]*$": {
62
+ "type": "object",
63
+ "properties": {
64
+ "name": {
65
+ "type": "string"
66
+ },
67
+ "structure": {
68
+ "type": "string"
69
+ },
70
+ "data": {
71
+ "type": "string"
72
+ },
73
+ "tables": {
74
+ "type": "array",
75
+ "items": {
76
+ "type": "string"
77
+ },
78
+ "additionalItems": false
79
+ }
28
80
  },
29
- "tables": {
30
- "type": "array",
31
- "items": {
81
+ "required": [
82
+ "name",
83
+ "structure",
84
+ "tables"
85
+ ]
86
+ }
87
+ },
88
+ "additionalProperties": false
89
+ },
90
+ "2": {
91
+ "type": "object",
92
+ "patternProperties": {
93
+ "^[a-z][a-z_]*$": {
94
+ "type": "object",
95
+ "properties": {
96
+ "name": {
97
+ "type": "string"
98
+ },
99
+ "structure": {
32
100
  "type": "string"
33
101
  },
34
- "additionalItems": false
35
- }
36
- },
37
- "required": [
38
- "name",
39
- "structure",
40
- "tables"
41
- ]
102
+ "tables": {
103
+ "type": "object",
104
+ "patternProperties": {
105
+ "^([a-z]+)(_[a-z]+)*$": {
106
+ "type": "array",
107
+ "items": {
108
+ "type": "string"
109
+ },
110
+ "additionalItems": false
111
+ }
112
+ },
113
+ "additionalProperties": false
114
+ }
115
+ },
116
+ "required": [
117
+ "name",
118
+ "structure",
119
+ "tables"
120
+ ]
121
+ }
122
+ },
123
+ "additionalProperties": false
124
+ }
125
+ },
126
+ "versions": {
127
+ "default": {
128
+ "properties": {
129
+ "content": {
130
+ "$ref": "#/definitions/content/1"
131
+ }
42
132
  }
43
- },
44
- "additionalProperties": false
133
+ }
45
134
  }
46
- },
47
- "required": [
48
- "version",
49
- "content"
50
- ]
135
+ }
51
136
  }