lexicon-common 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
  }