lexicon-cli 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/Gemfile +10 -0
- data/LICENSE.md +609 -0
- data/README.md +5 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/lexicon +14 -0
- data/bin/rubocop +29 -0
- data/bin/setup +8 -0
- data/lexicon-cli.gemspec +35 -0
- data/lib/lexicon-cli.rb +14 -0
- data/lib/lexicon/cli/application.rb +40 -0
- data/lib/lexicon/cli/cli_base.rb +29 -0
- data/lib/lexicon/cli/command/console_command.rb +41 -0
- data/lib/lexicon/cli/command/container_aware_command.rb +17 -0
- data/lib/lexicon/cli/command/production_command.rb +227 -0
- data/lib/lexicon/cli/command/remote_command.rb +76 -0
- data/lib/lexicon/cli/extension/common_extension.rb +72 -0
- data/lib/lexicon/cli/extension/extension_base.rb +36 -0
- data/lib/lexicon/cli/extension/production_extension.rb +62 -0
- data/lib/lexicon/cli/extension/remote_extension.rb +49 -0
- data/lib/lexicon/cli/version.rb +7 -0
- metadata +191 -0
data/README.md
ADDED
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "lexicon-cli"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/lexicon
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'lexicon-cli'
|
5
|
+
|
6
|
+
require 'dotenv/load'
|
7
|
+
|
8
|
+
application = Lexicon::Cli::Application.new(ARGV, extensions: [
|
9
|
+
Lexicon::Cli::Extension::CommonExtension.new(data_root: Pathname.new(__dir__).join('..')),
|
10
|
+
Lexicon::Cli::Extension::RemoteExtension.new,
|
11
|
+
Lexicon::Cli::Extension::ProductionExtension.new,
|
12
|
+
])
|
13
|
+
|
14
|
+
application.start
|
data/bin/rubocop
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'rubocop' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load Gem.bin_path("rubocop", "rubocop")
|
data/bin/setup
ADDED
data/lexicon-cli.gemspec
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lib/lexicon/cli/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'lexicon-cli'
|
7
|
+
spec.version = Lexicon::Cli::VERSION
|
8
|
+
spec.authors = ['Ekylibre developers']
|
9
|
+
spec.email = ['dev@ekylibre.com']
|
10
|
+
|
11
|
+
spec.summary = 'Basic Cli for the 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
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
18
|
+
spec.files = Dir.glob(%w[lib/**/*.rb bin/**/* *.gemspec Gemfile Rakefile *.md])
|
19
|
+
|
20
|
+
spec.bindir = 'bin'
|
21
|
+
spec.executables << 'lexicon'
|
22
|
+
|
23
|
+
spec.require_paths = ['lib']
|
24
|
+
|
25
|
+
spec.add_dependency 'corindon', '~> 0.7.0'
|
26
|
+
spec.add_dependency 'dotenv', '~> 2.7'
|
27
|
+
spec.add_dependency 'lexicon-common', '~> 0.1.0'
|
28
|
+
spec.add_dependency 'thor', '~> 1.0'
|
29
|
+
spec.add_dependency 'zeitwerk', '~> 2.4'
|
30
|
+
|
31
|
+
spec.add_development_dependency 'bundler', '~> 2.0'
|
32
|
+
spec.add_development_dependency 'minitest', '~> 5.14'
|
33
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
34
|
+
spec.add_development_dependency 'rubocop', '~> 1.3.1'
|
35
|
+
end
|
data/lib/lexicon-cli.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'corindon'
|
4
|
+
require 'lexicon-common'
|
5
|
+
require 'thor'
|
6
|
+
require 'zeitwerk'
|
7
|
+
|
8
|
+
# Make sure the Lexicon module already exists so that Zeitwerk does not manage it
|
9
|
+
module Lexicon
|
10
|
+
end
|
11
|
+
|
12
|
+
loader = Zeitwerk::Loader.for_gem
|
13
|
+
loader.ignore(__FILE__)
|
14
|
+
loader.setup
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lexicon
|
4
|
+
module Cli
|
5
|
+
class Application
|
6
|
+
attr_reader :args
|
7
|
+
|
8
|
+
def initialize(args, extensions: [])
|
9
|
+
@args = args
|
10
|
+
@extensions = extensions
|
11
|
+
end
|
12
|
+
|
13
|
+
def start
|
14
|
+
container = Corindon::DependencyInjection::Container.new
|
15
|
+
extensions.each do |extension|
|
16
|
+
extension.boot(container)
|
17
|
+
end
|
18
|
+
|
19
|
+
make_app(extensions).start(args, container: container)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
# @return [Array<Lexicon::Cli::ExtensionBase>]
|
25
|
+
attr_reader :extensions
|
26
|
+
|
27
|
+
# @param [Array<Lexicon::Cli::ExtensionBase>]
|
28
|
+
# @return [Class]
|
29
|
+
def make_app(extensions)
|
30
|
+
Class.new(CliBase) do
|
31
|
+
extensions.each do |extension|
|
32
|
+
if (commands = extension.commands).is_a?(Proc)
|
33
|
+
instance_eval(&commands)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lexicon
|
4
|
+
module Cli
|
5
|
+
class CliBase < Command::ContainerAwareCommand
|
6
|
+
def initialize(args = [], local_options = {}, config = {})
|
7
|
+
super(args, local_options, config)
|
8
|
+
|
9
|
+
register_config(container, options)
|
10
|
+
end
|
11
|
+
|
12
|
+
default_command :help
|
13
|
+
class_option :verbose, type: :boolean, default: false, aliases: ['v']
|
14
|
+
class_option :parallel, type: :boolean, default: false, aliases: ['P']
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def register_config(container, options)
|
19
|
+
verbose = options['verbose']
|
20
|
+
parallel = options['parallel']
|
21
|
+
jobs = options.fetch('jobs', parallel ? 4 : 1)
|
22
|
+
|
23
|
+
container.set_parameter('config.verbose', verbose)
|
24
|
+
container.set_parameter('config.parallel', parallel)
|
25
|
+
container.set_parameter('config.jobs', jobs)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lexicon
|
4
|
+
module Cli
|
5
|
+
module Command
|
6
|
+
class ConsoleCommand < ContainerAwareCommand
|
7
|
+
default_command :exec_command
|
8
|
+
|
9
|
+
desc 'run console', ''
|
10
|
+
|
11
|
+
def exec_command
|
12
|
+
# rubocop:disable Lint/Debugger
|
13
|
+
binding.pry
|
14
|
+
# rubocop:enable Lint/Debugger
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
%i[collect load normalize].each do |meth|
|
20
|
+
define_method meth do |*names|
|
21
|
+
get('datasource.name_runner').run(names, action: meth)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def version_bump(part)
|
26
|
+
get('version.bumper').bump(part)
|
27
|
+
end
|
28
|
+
|
29
|
+
def release(*names)
|
30
|
+
get('database.dumper').dump(get('version'), datasource_names: names)
|
31
|
+
end
|
32
|
+
|
33
|
+
def load_package(version = nil)
|
34
|
+
version ||= get('version')
|
35
|
+
|
36
|
+
get('production.package.loader').load_package(version)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lexicon
|
4
|
+
module Cli
|
5
|
+
module Command
|
6
|
+
class ContainerAwareCommand < Thor
|
7
|
+
include Lexicon::Common::Mixin::ContainerAware
|
8
|
+
|
9
|
+
def initialize(args = [], local_options = {}, config = {})
|
10
|
+
super(args, local_options, config)
|
11
|
+
|
12
|
+
self.container = config[:container]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,227 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
module Lexicon
|
6
|
+
module Cli
|
7
|
+
module Command
|
8
|
+
class ProductionCommand < ContainerAwareCommand
|
9
|
+
include Lexicon::Common::Mixin::SchemaNamer
|
10
|
+
|
11
|
+
desc 'loadable', 'List all available loadable versions'
|
12
|
+
|
13
|
+
def loadable
|
14
|
+
available_packages.each do |package|
|
15
|
+
puts package.version.to_s.green
|
16
|
+
package.file_sets
|
17
|
+
.sort_by(&:name)
|
18
|
+
.each do |fs|
|
19
|
+
puts ' -> ' + fs.name.send(fs.data_path.nil? ? :yellow : :green)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
desc 'config', 'Display production config information'
|
25
|
+
|
26
|
+
def config
|
27
|
+
puts <<~TEXT
|
28
|
+
Version dir: #{container.parameter('lexicon.common.package_dir')}
|
29
|
+
Prod database URL: #{container.parameter('lexicon.common.production.database.url')}
|
30
|
+
TEXT
|
31
|
+
end
|
32
|
+
|
33
|
+
desc 'load <VERSION>', 'Load a package into the production database'
|
34
|
+
option :validate, type: :boolean, default: true
|
35
|
+
option :datasources, type: :array, default: []
|
36
|
+
option :without, type: :array, default: []
|
37
|
+
|
38
|
+
def load(pkg_name)
|
39
|
+
# @type [Production::DatasourceLoader] datasource_loader
|
40
|
+
datasource_loader = get(Lexicon::Common::Production::DatasourceLoader)
|
41
|
+
# @type [Package::PackageIntegrityValidator] integrity_validator
|
42
|
+
integrity_validator = get(Lexicon::Common::Package::PackageIntegrityValidator)
|
43
|
+
# @type [Package::DirectoryPackageLoader] package_loader
|
44
|
+
package_loader = get(Lexicon::Common::Package::DirectoryPackageLoader)
|
45
|
+
|
46
|
+
validate = options.fetch(:validate)
|
47
|
+
names = options.fetch(:datasources, [])
|
48
|
+
without = options.fetch(:without, [])
|
49
|
+
package = package_loader.load_package(pkg_name)
|
50
|
+
|
51
|
+
if package.nil?
|
52
|
+
puts '[ NOK ] Did not find any package to load'.red
|
53
|
+
elsif package.nil?
|
54
|
+
puts "[ NOK ] No Package found for version #{version}".red
|
55
|
+
elsif !validate || integrity_validator.valid?(package)
|
56
|
+
datasource_loader.load_package(package, only: (names.empty? ? nil : names), without: without)
|
57
|
+
else
|
58
|
+
puts "[ NOK ] Lexicon package #{package.version} is corrupted".red
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
desc 'versions', 'List versions available on the server'
|
63
|
+
|
64
|
+
def versions
|
65
|
+
puts "Lexicon is #{enabled? ? 'ENABLED'.green + " (#{enabled_version.to_s.yellow})" : 'DISABLED'.red}"
|
66
|
+
|
67
|
+
available = loaded_versions
|
68
|
+
if available.empty?
|
69
|
+
puts 'No other versions are loaded'
|
70
|
+
else
|
71
|
+
puts 'Available loaded versions are:'
|
72
|
+
loaded_versions
|
73
|
+
.each { |e| puts " - #{e}" }
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
desc 'disable', 'Disable the lexicon'
|
78
|
+
|
79
|
+
def disable
|
80
|
+
if enabled? && !(version = enabled_version).nil?
|
81
|
+
puts "Disabling version #{version.to_s.yellow}"
|
82
|
+
do_disable
|
83
|
+
puts '[ OK ] Done'.green
|
84
|
+
else
|
85
|
+
puts 'Lexicon is not enabled'.red
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
desc 'enable [version]', 'Enable the lexicon'
|
90
|
+
|
91
|
+
def enable(version = nil)
|
92
|
+
if enabled?
|
93
|
+
puts 'Disabling current version'
|
94
|
+
do_disable
|
95
|
+
end
|
96
|
+
|
97
|
+
semver = if version.nil?
|
98
|
+
loaded_versions.max
|
99
|
+
else
|
100
|
+
Semantic::Version.new(version)
|
101
|
+
end
|
102
|
+
|
103
|
+
puts "Enabling version #{semver.to_s.yellow}"
|
104
|
+
|
105
|
+
do_enable(semver)
|
106
|
+
|
107
|
+
puts '[ OK ] Done'.green
|
108
|
+
end
|
109
|
+
|
110
|
+
desc 'delete', 'Deletes a loaded version of the lexicon'
|
111
|
+
|
112
|
+
def delete(version)
|
113
|
+
semver = Semantic::Version.new(version)
|
114
|
+
|
115
|
+
if loaded_versions.include?(semver)
|
116
|
+
production_database
|
117
|
+
.query("DROP SCHEMA \"#{version_to_schema(semver)}\" CASCADE")
|
118
|
+
|
119
|
+
puts '[ OK ] '.green + "The version #{semver} has been deleted."
|
120
|
+
else
|
121
|
+
puts '[ NOK ] '.red + "The version #{semver.to_s.yellow} is not loaded or is enabled."
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
|
127
|
+
def production_database
|
128
|
+
get(Lexicon::Cli::Extension::ProductionExtension::DATABASE)
|
129
|
+
end
|
130
|
+
|
131
|
+
# @return [Array<Package::Package>]
|
132
|
+
def available_packages
|
133
|
+
# @type [Package::DirectoryPackageLoader] package_loader
|
134
|
+
package_loader = get(Lexicon::Common::Package::DirectoryPackageLoader)
|
135
|
+
|
136
|
+
if package_loader.root_dir.exist?
|
137
|
+
package_loader.root_dir
|
138
|
+
.children
|
139
|
+
.select(&:directory?)
|
140
|
+
.map { |dir| package_loader.load_package(dir.basename.to_s) }
|
141
|
+
.compact
|
142
|
+
.sort { |a, b| a.version <=> b.version }
|
143
|
+
else
|
144
|
+
[]
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# @param [Semantic::Version] version
|
149
|
+
def do_enable(version)
|
150
|
+
production_database.query <<~SQL
|
151
|
+
BEGIN;
|
152
|
+
ALTER SCHEMA "lexicon__#{version.to_s.gsub('.', '_')}" RENAME TO "lexicon";
|
153
|
+
CREATE TABLE "lexicon"."version" ("version" VARCHAR);
|
154
|
+
INSERT INTO "lexicon"."version" VALUES ('#{version}');
|
155
|
+
COMMIT;
|
156
|
+
SQL
|
157
|
+
end
|
158
|
+
|
159
|
+
def do_disable
|
160
|
+
version = enabled_version
|
161
|
+
raise StandardError.new('No version table present, cannot continue automatically') if version.nil?
|
162
|
+
|
163
|
+
production_database.query <<~SQL
|
164
|
+
BEGIN;
|
165
|
+
DROP TABLE "lexicon"."version";
|
166
|
+
ALTER SCHEMA "lexicon" RENAME TO "#{version_to_schema(version)}";
|
167
|
+
COMMIT;
|
168
|
+
SQL
|
169
|
+
end
|
170
|
+
|
171
|
+
# @return [Array<Semantic::Version>]
|
172
|
+
def loaded_versions
|
173
|
+
disabled = production_database
|
174
|
+
.query("SELECT nspname FROM pg_catalog.pg_namespace WHERE nspname LIKE 'lexicon__%';")
|
175
|
+
.to_a
|
176
|
+
.map { |name| schema_to_version(name.fetch('nspname')) }
|
177
|
+
|
178
|
+
enabled = enabled_version
|
179
|
+
|
180
|
+
if enabled.nil?
|
181
|
+
disabled
|
182
|
+
else
|
183
|
+
[*disabled, enabled].sort
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
# @return [Semantic::Version, nil]
|
188
|
+
def enabled_version
|
189
|
+
if version_table_present?
|
190
|
+
Semantic::Version.new(
|
191
|
+
production_database
|
192
|
+
.query('SELECT version FROM lexicon.version LIMIT 1')
|
193
|
+
.to_a.first
|
194
|
+
.fetch('version')
|
195
|
+
)
|
196
|
+
else
|
197
|
+
nil
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
# @param [String, nil] version
|
202
|
+
# @return [Semantic::Version]
|
203
|
+
def as_version(version, &block)
|
204
|
+
if version.nil?
|
205
|
+
block.call
|
206
|
+
else
|
207
|
+
Semantic::Version.new(version)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def version_table_present?
|
212
|
+
production_database
|
213
|
+
.query("SELECT count(*) AS presence FROM information_schema.tables WHERE table_schema = 'lexicon' AND table_name = 'version'")
|
214
|
+
.to_a.first
|
215
|
+
.fetch('presence').to_i.positive?
|
216
|
+
end
|
217
|
+
|
218
|
+
def enabled?
|
219
|
+
production_database
|
220
|
+
.query("SELECT count(nspname) AS presence FROM pg_catalog.pg_namespace WHERE nspname = 'lexicon'")
|
221
|
+
.to_a.first
|
222
|
+
.fetch('presence').to_i.positive?
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|