cpl 0.4.1 → 0.5.1
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 +4 -4
- data/.overcommit.yml +10 -0
- data/Gemfile.lock +10 -3
- data/README.md +6 -0
- data/cpl.gemspec +1 -0
- data/docs/commands.md +51 -3
- data/googlee2da545df05d92f9.html +1 -0
- data/lib/command/base.rb +65 -7
- data/lib/command/build_image.rb +6 -5
- data/lib/command/cleanup_old_images.rb +8 -7
- data/lib/command/cleanup_stale_apps.rb +11 -9
- data/lib/command/config.rb +30 -15
- data/lib/command/copy_image_from_upstream.rb +110 -0
- data/lib/command/delete.rb +10 -12
- data/lib/command/deploy_image.rb +18 -7
- data/lib/command/env.rb +2 -2
- data/lib/command/exists.rb +4 -4
- data/lib/command/info.rb +233 -0
- data/lib/command/latest_image.rb +2 -2
- data/lib/command/logs.rb +4 -4
- data/lib/command/no_command.rb +3 -3
- data/lib/command/open.rb +4 -4
- data/lib/command/promote_app_from_upstream.rb +58 -0
- data/lib/command/ps.rb +10 -13
- data/lib/command/ps_restart.rb +9 -6
- data/lib/command/ps_start.rb +7 -6
- data/lib/command/ps_stop.rb +7 -6
- data/lib/command/run.rb +5 -5
- data/lib/command/run_detached.rb +7 -5
- data/lib/command/setup.rb +71 -13
- data/lib/command/test.rb +2 -2
- data/lib/command/version.rb +2 -2
- data/lib/core/config.rb +26 -19
- data/lib/core/controlplane.rb +77 -11
- data/lib/core/controlplane_api.rb +12 -0
- data/lib/core/controlplane_api_cli.rb +1 -1
- data/lib/core/controlplane_api_direct.rb +2 -2
- data/lib/core/shell.rb +25 -3
- data/lib/cpl/version.rb +1 -1
- data/lib/cpl.rb +19 -10
- data/lib/deprecated_commands.json +6 -0
- data/script/add_command +37 -0
- data/script/generate_commands_docs +5 -5
- data/script/rename_command +43 -0
- metadata +24 -2
data/lib/command/setup.rb
CHANGED
@@ -9,7 +9,7 @@ module Command
|
|
9
9
|
app_option(required: true)
|
10
10
|
].freeze
|
11
11
|
DESCRIPTION = "Applies application-specific configs from templates"
|
12
|
-
LONG_DESCRIPTION = <<~
|
12
|
+
LONG_DESCRIPTION = <<~DESC
|
13
13
|
- Applies application-specific configs from templates (e.g., for every review-app)
|
14
14
|
- Publishes (creates or updates) those at Control Plane infrastructure
|
15
15
|
- Picks templates from the `.controlplane/templates` directory
|
@@ -23,8 +23,8 @@ module Command
|
|
23
23
|
APP_ORG - organization
|
24
24
|
APP_IMAGE - will use latest app image
|
25
25
|
```
|
26
|
-
|
27
|
-
EXAMPLES = <<~
|
26
|
+
DESC
|
27
|
+
EXAMPLES = <<~EX
|
28
28
|
```sh
|
29
29
|
# Applies single template.
|
30
30
|
cpl setup redis -a $APP_NAME
|
@@ -32,31 +32,89 @@ module Command
|
|
32
32
|
# Applies several templates (practically creating full app).
|
33
33
|
cpl setup gvc postgres redis rails -a $APP_NAME
|
34
34
|
```
|
35
|
-
|
35
|
+
EX
|
36
|
+
|
37
|
+
def call # rubocop:disable Metrics/MethodLength
|
38
|
+
@app_status = :existing
|
39
|
+
@created_workloads = []
|
40
|
+
@failed_workloads = []
|
36
41
|
|
37
|
-
def call
|
38
42
|
config.args.each do |template|
|
39
43
|
filename = "#{config.app_cpln_dir}/templates/#{template}.yml"
|
40
|
-
|
41
|
-
|
42
|
-
|
44
|
+
|
45
|
+
step("Applying template '#{template}'", abort_on_error: false) do
|
46
|
+
unless File.exist?(filename)
|
47
|
+
report_failure(template)
|
48
|
+
|
49
|
+
raise "Can't find template '#{template}' at '#{filename}', please create it."
|
50
|
+
end
|
51
|
+
|
52
|
+
apply_template(filename)
|
53
|
+
if $CHILD_STATUS.success?
|
54
|
+
report_success(template)
|
55
|
+
else
|
56
|
+
report_failure(template)
|
57
|
+
end
|
58
|
+
|
59
|
+
$CHILD_STATUS.success?
|
60
|
+
end
|
43
61
|
end
|
62
|
+
|
63
|
+
print_app_status
|
64
|
+
print_created_workloads
|
65
|
+
print_failed_workloads
|
44
66
|
end
|
45
67
|
|
46
68
|
private
|
47
69
|
|
48
|
-
def ensure_template!(template, filename)
|
49
|
-
Shell.abort("Can't find template '#{template}' at '#{filename}', please create it.") unless File.exist?(filename)
|
50
|
-
end
|
51
|
-
|
52
70
|
def apply_template(filename)
|
53
71
|
data = File.read(filename)
|
54
72
|
.gsub("APP_GVC", config.app)
|
55
73
|
.gsub("APP_LOCATION", config[:default_location])
|
56
|
-
.gsub("APP_ORG", config
|
74
|
+
.gsub("APP_ORG", config.org)
|
57
75
|
.gsub("APP_IMAGE", latest_image)
|
58
76
|
|
59
77
|
cp.apply(YAML.safe_load(data))
|
60
78
|
end
|
79
|
+
|
80
|
+
def report_success(template)
|
81
|
+
if template == "gvc"
|
82
|
+
@app_status = :success
|
83
|
+
else
|
84
|
+
@created_workloads.push(template)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def report_failure(template)
|
89
|
+
if template == "gvc"
|
90
|
+
@app_status = :failure
|
91
|
+
else
|
92
|
+
@failed_workloads.push(template)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def print_app_status
|
97
|
+
return if @app_status == :existing
|
98
|
+
|
99
|
+
if @app_status == :success
|
100
|
+
progress.puts("\n#{Shell.color("Created app '#{config.app}'.", :green)}")
|
101
|
+
else
|
102
|
+
progress.puts("\n#{Shell.color("Failed to create app '#{config.app}'.", :red)}")
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def print_created_workloads
|
107
|
+
return unless @created_workloads.any?
|
108
|
+
|
109
|
+
workloads = @created_workloads.map { |template| " - #{template}" }.join("\n")
|
110
|
+
progress.puts("\n#{Shell.color('Created workloads:', :green)}\n#{workloads}")
|
111
|
+
end
|
112
|
+
|
113
|
+
def print_failed_workloads
|
114
|
+
return unless @failed_workloads.any?
|
115
|
+
|
116
|
+
workloads = @failed_workloads.map { |template| " - #{template}" }.join("\n")
|
117
|
+
progress.puts("\n#{Shell.color('Failed to create workloads:', :red)}\n#{workloads}")
|
118
|
+
end
|
61
119
|
end
|
62
120
|
end
|
data/lib/command/test.rb
CHANGED
data/lib/command/version.rb
CHANGED
@@ -4,10 +4,10 @@ module Command
|
|
4
4
|
class Version < Base
|
5
5
|
NAME = "version"
|
6
6
|
DESCRIPTION = "Displays the current version of the CLI"
|
7
|
-
LONG_DESCRIPTION = <<~
|
7
|
+
LONG_DESCRIPTION = <<~DESC
|
8
8
|
- Displays the current version of the CLI
|
9
9
|
- Can also be done with `cpl --version` or `cpl -v`
|
10
|
-
|
10
|
+
DESC
|
11
11
|
|
12
12
|
def call
|
13
13
|
puts Cpl::VERSION
|
data/lib/core/config.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
class Config
|
4
4
|
attr_reader :config, :current,
|
5
|
-
:app, :app_dir,
|
5
|
+
:org, :app, :apps, :app_dir,
|
6
6
|
# command line options
|
7
7
|
:args, :options
|
8
8
|
|
@@ -11,9 +11,13 @@ class Config
|
|
11
11
|
def initialize(args, options)
|
12
12
|
@args = args
|
13
13
|
@options = options
|
14
|
+
@org = options[:org]
|
14
15
|
@app = options[:app]
|
15
16
|
|
16
17
|
load_app_config
|
18
|
+
|
19
|
+
@apps = config[:apps]
|
20
|
+
|
17
21
|
pick_current_config if app
|
18
22
|
warn_deprecated_options if current
|
19
23
|
end
|
@@ -27,7 +31,7 @@ class Config
|
|
27
31
|
elsif old_key && current.key?(old_key)
|
28
32
|
current.fetch(old_key)
|
29
33
|
else
|
30
|
-
|
34
|
+
raise "Can't find option '#{key}' for app '#{app}' in 'controlplane.yml'."
|
31
35
|
end
|
32
36
|
end
|
33
37
|
|
@@ -42,34 +46,33 @@ class Config
|
|
42
46
|
private
|
43
47
|
|
44
48
|
def ensure_current_config!
|
45
|
-
|
49
|
+
raise "Can't find current config, please specify an app." unless current
|
46
50
|
end
|
47
51
|
|
48
52
|
def ensure_current_config_app!(app)
|
49
|
-
|
53
|
+
raise "Can't find app '#{app}' in 'controlplane.yml'." unless current
|
50
54
|
end
|
51
55
|
|
52
56
|
def ensure_config!
|
53
|
-
|
57
|
+
raise "'controlplane.yml' is empty." unless config
|
54
58
|
end
|
55
59
|
|
56
60
|
def ensure_config_apps!
|
57
|
-
|
61
|
+
raise "Can't find key 'apps' in 'controlplane.yml'." unless config[:apps]
|
58
62
|
end
|
59
63
|
|
60
64
|
def ensure_config_app!(app, options)
|
61
|
-
|
65
|
+
raise "App '#{app}' is empty in 'controlplane.yml'." unless options
|
62
66
|
end
|
63
67
|
|
64
68
|
def pick_current_config
|
65
|
-
ensure_config!
|
66
|
-
ensure_config_apps!
|
67
69
|
config[:apps].each do |c_app, c_data|
|
68
70
|
ensure_config_app!(c_app, c_data)
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
71
|
+
next unless c_app.to_s == app || (c_data[:match_if_app_name_starts_with] && app.start_with?(c_app.to_s))
|
72
|
+
|
73
|
+
@current = c_data
|
74
|
+
@org = self[:cpln_org]
|
75
|
+
break
|
73
76
|
end
|
74
77
|
ensure_current_config_app!(app)
|
75
78
|
end
|
@@ -78,6 +81,8 @@ class Config
|
|
78
81
|
config_file = find_app_config_file
|
79
82
|
@config = YAML.safe_load_file(config_file, symbolize_names: true, aliases: true)
|
80
83
|
@app_dir = Pathname.new(config_file).parent.parent.to_s
|
84
|
+
ensure_config!
|
85
|
+
ensure_config_apps!
|
81
86
|
end
|
82
87
|
|
83
88
|
def find_app_config_file
|
@@ -90,7 +95,7 @@ class Config
|
|
90
95
|
path = path.parent
|
91
96
|
|
92
97
|
if path.root?
|
93
|
-
|
98
|
+
raise "Can't find project config file at 'project_folder/#{CONFIG_FILE_LOCATIION}', please create it."
|
94
99
|
end
|
95
100
|
end
|
96
101
|
end
|
@@ -104,11 +109,13 @@ class Config
|
|
104
109
|
end
|
105
110
|
|
106
111
|
def warn_deprecated_options
|
107
|
-
old_option_keys.
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
+
deprecated_option_keys = old_option_keys.filter { |_new_key, old_key| current.key?(old_key) }
|
113
|
+
return if deprecated_option_keys.empty?
|
114
|
+
|
115
|
+
deprecated_option_keys.each do |new_key, old_key|
|
116
|
+
Shell.warn_deprecated("Option '#{old_key}' is deprecated, " \
|
117
|
+
"please use '#{new_key}' instead (in 'controlplane.yml').")
|
112
118
|
end
|
119
|
+
$stderr.puts
|
113
120
|
end
|
114
121
|
end
|
data/lib/core/controlplane.rb
CHANGED
@@ -7,7 +7,30 @@ class Controlplane # rubocop:disable Metrics/ClassLength
|
|
7
7
|
@config = config
|
8
8
|
@api = ControlplaneApi.new
|
9
9
|
@gvc = config.app
|
10
|
-
@org = config
|
10
|
+
@org = config.org
|
11
|
+
end
|
12
|
+
|
13
|
+
# profile
|
14
|
+
|
15
|
+
def profile_switch(profile)
|
16
|
+
ENV["CPLN_PROFILE"] = profile
|
17
|
+
end
|
18
|
+
|
19
|
+
def profile_exists?(profile)
|
20
|
+
cmd = "cpln profile get #{profile} -o yaml"
|
21
|
+
perform_yaml(cmd).length.positive?
|
22
|
+
end
|
23
|
+
|
24
|
+
def profile_create(profile, token)
|
25
|
+
cmd = "cpln profile create #{profile} --token #{token}"
|
26
|
+
cmd += " > /dev/null" if Shell.tmp_stderr
|
27
|
+
perform!(cmd)
|
28
|
+
end
|
29
|
+
|
30
|
+
def profile_delete(profile)
|
31
|
+
cmd = "cpln profile delete #{profile}"
|
32
|
+
cmd += " > /dev/null" if Shell.tmp_stderr
|
33
|
+
perform!(cmd)
|
11
34
|
end
|
12
35
|
|
13
36
|
# image
|
@@ -18,8 +41,8 @@ class Controlplane # rubocop:disable Metrics/ClassLength
|
|
18
41
|
perform!(cmd)
|
19
42
|
end
|
20
43
|
|
21
|
-
def image_query(app_name = config.app)
|
22
|
-
cmd = "cpln image query --org #{
|
44
|
+
def image_query(app_name = config.app, org_name = config.org)
|
45
|
+
cmd = "cpln image query --org #{org_name} -o yaml --max -1 --prop repository=#{app_name}"
|
23
46
|
perform_yaml(cmd)
|
24
47
|
end
|
25
48
|
|
@@ -27,8 +50,36 @@ class Controlplane # rubocop:disable Metrics/ClassLength
|
|
27
50
|
api.image_delete(org: org, image: image)
|
28
51
|
end
|
29
52
|
|
53
|
+
def image_login(org_name = config.org)
|
54
|
+
cmd = "cpln image docker-login --org #{org_name}"
|
55
|
+
cmd += " > /dev/null 2>&1" if Shell.tmp_stderr
|
56
|
+
perform!(cmd)
|
57
|
+
end
|
58
|
+
|
59
|
+
def image_pull(image)
|
60
|
+
cmd = "docker pull #{image}"
|
61
|
+
cmd += " > /dev/null" if Shell.tmp_stderr
|
62
|
+
perform!(cmd)
|
63
|
+
end
|
64
|
+
|
65
|
+
def image_tag(old_tag, new_tag)
|
66
|
+
cmd = "docker tag #{old_tag} #{new_tag}"
|
67
|
+
cmd += " > /dev/null" if Shell.tmp_stderr
|
68
|
+
perform!(cmd)
|
69
|
+
end
|
70
|
+
|
71
|
+
def image_push(image)
|
72
|
+
cmd = "docker push #{image}"
|
73
|
+
cmd += " > /dev/null" if Shell.tmp_stderr
|
74
|
+
perform!(cmd)
|
75
|
+
end
|
76
|
+
|
30
77
|
# gvc
|
31
78
|
|
79
|
+
def fetch_gvcs
|
80
|
+
api.gvc_list(org: org)
|
81
|
+
end
|
82
|
+
|
32
83
|
def gvc_query(app_name = config.app)
|
33
84
|
# When `match_if_app_name_starts_with` is `true`, we query for any gvc containing the name,
|
34
85
|
# otherwise we query for a gvc with the exact name.
|
@@ -38,15 +89,15 @@ class Controlplane # rubocop:disable Metrics/ClassLength
|
|
38
89
|
perform_yaml(cmd)
|
39
90
|
end
|
40
91
|
|
41
|
-
def fetch_gvc(a_gvc = gvc)
|
42
|
-
api.gvc_get(gvc: a_gvc, org:
|
92
|
+
def fetch_gvc(a_gvc = gvc, a_org = org)
|
93
|
+
api.gvc_get(gvc: a_gvc, org: a_org)
|
43
94
|
end
|
44
95
|
|
45
96
|
def fetch_gvc!(a_gvc = gvc)
|
46
97
|
gvc_data = fetch_gvc(a_gvc)
|
47
98
|
return gvc_data if gvc_data
|
48
99
|
|
49
|
-
|
100
|
+
raise "Can't find GVC '#{gvc}', please create it with 'cpl setup gvc -a #{config.app}'."
|
50
101
|
end
|
51
102
|
|
52
103
|
def gvc_delete(a_gvc = gvc)
|
@@ -55,6 +106,14 @@ class Controlplane # rubocop:disable Metrics/ClassLength
|
|
55
106
|
|
56
107
|
# workload
|
57
108
|
|
109
|
+
def fetch_workloads(a_gvc = gvc)
|
110
|
+
api.workload_list(gvc: a_gvc, org: org)
|
111
|
+
end
|
112
|
+
|
113
|
+
def fetch_workloads_by_org(a_org = org)
|
114
|
+
api.workload_list_by_org(org: a_org)
|
115
|
+
end
|
116
|
+
|
58
117
|
def fetch_workload(workload)
|
59
118
|
api.workload_get(workload: workload, gvc: gvc, org: org)
|
60
119
|
end
|
@@ -63,7 +122,7 @@ class Controlplane # rubocop:disable Metrics/ClassLength
|
|
63
122
|
workload_data = fetch_workload(workload)
|
64
123
|
return workload_data if workload_data
|
65
124
|
|
66
|
-
|
125
|
+
raise "Can't find workload '#{workload}', please create it with 'cpl setup #{workload} -a #{config.app}'."
|
67
126
|
end
|
68
127
|
|
69
128
|
def workload_get_replicas(workload, location:)
|
@@ -73,7 +132,8 @@ class Controlplane # rubocop:disable Metrics/ClassLength
|
|
73
132
|
|
74
133
|
def workload_set_image_ref(workload, container:, image:)
|
75
134
|
cmd = "cpln workload update #{workload} #{gvc_org}"
|
76
|
-
cmd += " --set spec.containers.#{container}.image=/org/#{config
|
135
|
+
cmd += " --set spec.containers.#{container}.image=/org/#{config.org}/image/#{image}"
|
136
|
+
cmd += " > /dev/null" if Shell.tmp_stderr
|
77
137
|
perform!(cmd)
|
78
138
|
end
|
79
139
|
|
@@ -85,6 +145,7 @@ class Controlplane # rubocop:disable Metrics/ClassLength
|
|
85
145
|
|
86
146
|
def workload_force_redeployment(workload)
|
87
147
|
cmd = "cpln workload force-redeployment #{workload} #{gvc_org}"
|
148
|
+
cmd += " > /dev/null" if Shell.tmp_stderr
|
88
149
|
perform!(cmd)
|
89
150
|
end
|
90
151
|
|
@@ -126,12 +187,17 @@ class Controlplane # rubocop:disable Metrics/ClassLength
|
|
126
187
|
|
127
188
|
# apply
|
128
189
|
|
129
|
-
def apply(data)
|
190
|
+
def apply(data) # rubocop:disable Metrics/MethodLength
|
130
191
|
Tempfile.create do |f|
|
131
192
|
f.write(data.to_yaml)
|
132
193
|
f.rewind
|
133
194
|
cmd = "cpln apply #{gvc_org} --file #{f.path} > /dev/null"
|
134
|
-
|
195
|
+
if Shell.tmp_stderr
|
196
|
+
cmd += " 2> #{Shell.tmp_stderr.path}"
|
197
|
+
perform(cmd)
|
198
|
+
else
|
199
|
+
perform!(cmd)
|
200
|
+
end
|
135
201
|
end
|
136
202
|
end
|
137
203
|
|
@@ -147,7 +213,7 @@ class Controlplane # rubocop:disable Metrics/ClassLength
|
|
147
213
|
|
148
214
|
def perform_yaml(cmd)
|
149
215
|
result = `#{cmd}`
|
150
|
-
|
216
|
+
$CHILD_STATUS.success? ? YAML.safe_load(result) : exit(false)
|
151
217
|
end
|
152
218
|
|
153
219
|
def gvc_org
|
@@ -1,6 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class ControlplaneApi
|
4
|
+
def gvc_list(org:)
|
5
|
+
api_json("/org/#{org}/gvc", method: :get)
|
6
|
+
end
|
7
|
+
|
4
8
|
def gvc_get(org:, gvc:)
|
5
9
|
api_json("/org/#{org}/gvc/#{gvc}", method: :get)
|
6
10
|
end
|
@@ -29,6 +33,14 @@ class ControlplaneApi
|
|
29
33
|
api_json_direct("/logs/org/#{org}/loki/api/v1/query_range?#{params}", method: :get, host: :logs)
|
30
34
|
end
|
31
35
|
|
36
|
+
def workload_list(org:, gvc:)
|
37
|
+
api_json("/org/#{org}/gvc/#{gvc}/workload", method: :get)
|
38
|
+
end
|
39
|
+
|
40
|
+
def workload_list_by_org(org:)
|
41
|
+
api_json("/org/#{org}/workload", method: :get)
|
42
|
+
end
|
43
|
+
|
32
44
|
def workload_get(org:, gvc:, workload:)
|
33
45
|
api_json("/org/#{org}/gvc/#{gvc}/workload/#{workload}", method: :get)
|
34
46
|
end
|
@@ -37,7 +37,7 @@ class ControlplaneApiDirect
|
|
37
37
|
@@api_token = ENV.fetch("CPLN_TOKEN", `cpln profile token`.chomp) # rubocop:disable Style/ClassVars
|
38
38
|
return @@api_token if @@api_token.match?(API_TOKEN_REGEX)
|
39
39
|
|
40
|
-
|
41
|
-
|
40
|
+
raise "Unknown API token format. " \
|
41
|
+
"Please re-run 'cpln profile login' or set the correct CPLN_TOKEN env variable."
|
42
42
|
end
|
43
43
|
end
|
data/lib/core/shell.rb
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class Shell
|
4
|
+
class << self
|
5
|
+
attr_reader :tmp_stderr
|
6
|
+
end
|
7
|
+
|
4
8
|
def self.shell
|
5
9
|
@shell ||= Thor::Shell::Color.new
|
6
10
|
end
|
@@ -9,6 +13,24 @@ class Shell
|
|
9
13
|
@stderr ||= $stderr
|
10
14
|
end
|
11
15
|
|
16
|
+
def self.use_tmp_stderr
|
17
|
+
@tmp_stderr = Tempfile.create
|
18
|
+
|
19
|
+
yield
|
20
|
+
|
21
|
+
@tmp_stderr.close
|
22
|
+
@tmp_stderr = nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.write_to_tmp_stderr(message)
|
26
|
+
tmp_stderr.write(message)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.read_from_tmp_stderr
|
30
|
+
tmp_stderr.rewind
|
31
|
+
tmp_stderr.read.strip
|
32
|
+
end
|
33
|
+
|
12
34
|
def self.color(message, color_key)
|
13
35
|
shell.set_color(message, color_key)
|
14
36
|
end
|
@@ -18,14 +40,14 @@ class Shell
|
|
18
40
|
end
|
19
41
|
|
20
42
|
def self.warn(message)
|
21
|
-
stderr.puts(color("WARNING: #{message}
|
43
|
+
stderr.puts(color("WARNING: #{message}", :yellow))
|
22
44
|
end
|
23
45
|
|
24
46
|
def self.warn_deprecated(message)
|
25
|
-
stderr.puts(color("DEPRECATED: #{message}
|
47
|
+
stderr.puts(color("DEPRECATED: #{message}", :yellow))
|
26
48
|
end
|
27
49
|
|
28
50
|
def self.abort(message)
|
29
|
-
Kernel.abort(color("ERROR: #{message}
|
51
|
+
Kernel.abort(color("ERROR: #{message}", :red))
|
30
52
|
end
|
31
53
|
end
|
data/lib/cpl/version.rb
CHANGED
data/lib/cpl.rb
CHANGED
@@ -70,12 +70,17 @@ module Cpl
|
|
70
70
|
end
|
71
71
|
|
72
72
|
def self.deprecated_commands
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
73
|
+
@deprecated_commands ||= begin
|
74
|
+
deprecated_commands_file_path = "#{__dir__}/deprecated_commands.json"
|
75
|
+
deprecated_commands_data = File.binread(deprecated_commands_file_path)
|
76
|
+
deprecated_commands = JSON.parse(deprecated_commands_data)
|
77
|
+
deprecated_commands.to_h do |old_command_name, new_command_name|
|
78
|
+
file_name = new_command_name.gsub(/[^A-Za-z]/, "_")
|
79
|
+
class_name = file_name.split("_").map(&:capitalize).join
|
80
|
+
|
81
|
+
[old_command_name, Object.const_get("::Command::#{class_name}")]
|
82
|
+
end
|
83
|
+
end
|
79
84
|
end
|
80
85
|
|
81
86
|
def self.all_base_commands
|
@@ -113,6 +118,7 @@ module Cpl
|
|
113
118
|
if deprecated
|
114
119
|
::Shell.warn_deprecated("Command '#{command_key}' is deprecated, " \
|
115
120
|
"please use '#{name}' instead.")
|
121
|
+
$stderr.puts
|
116
122
|
end
|
117
123
|
|
118
124
|
args = if provided_args.length.positive?
|
@@ -123,13 +129,16 @@ module Cpl
|
|
123
129
|
|
124
130
|
raise_args_error.call(args, nil) if (args.empty? && requires_args) || (!args.empty? && !requires_args)
|
125
131
|
|
126
|
-
|
132
|
+
begin
|
133
|
+
config = Config.new(args, options)
|
127
134
|
|
128
|
-
|
135
|
+
command_class.new(config).call
|
136
|
+
rescue RuntimeError => e
|
137
|
+
::Shell.abort(e.message)
|
138
|
+
end
|
129
139
|
end
|
130
140
|
rescue StandardError => e
|
131
|
-
|
132
|
-
logger.puts("Unable to load command: #{e.message}")
|
141
|
+
::Shell.abort("Unable to load command: #{e.message}")
|
133
142
|
end
|
134
143
|
end
|
135
144
|
end
|
data/script/add_command
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
command_name = ARGV[0]&.downcase
|
5
|
+
|
6
|
+
abort("ERROR: Must provide command name.") unless command_name
|
7
|
+
|
8
|
+
file_name = command_name.gsub(/[^A-Za-z]/, "_")
|
9
|
+
file_path = "#{__dir__}/../lib/command/#{file_name}.rb"
|
10
|
+
|
11
|
+
abort("ERROR: Command '#{command_name}' already exists.") if File.exist?(file_path)
|
12
|
+
|
13
|
+
class_name = file_name.split("_").map(&:capitalize).join
|
14
|
+
|
15
|
+
file_data =
|
16
|
+
<<~DATA
|
17
|
+
# frozen_string_literal: true
|
18
|
+
|
19
|
+
module Command
|
20
|
+
class #{class_name} < Base
|
21
|
+
# See `base.rb` for other constants to add here
|
22
|
+
NAME = "#{command_name}"
|
23
|
+
OPTIONS = [
|
24
|
+
# Add options here
|
25
|
+
].freeze
|
26
|
+
DESCRIPTION = "Add description here"
|
27
|
+
LONG_DESCRIPTION = <<~DESC
|
28
|
+
- Add long description here
|
29
|
+
DESC
|
30
|
+
|
31
|
+
def call
|
32
|
+
# Add command logic here
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
DATA
|
37
|
+
File.binwrite(file_path, file_data)
|
@@ -41,9 +41,9 @@ end
|
|
41
41
|
|
42
42
|
commands_str = commands_str_arr.join("\n\n")
|
43
43
|
|
44
|
-
|
45
|
-
|
46
|
-
<<~
|
44
|
+
file_path = "#{__dir__}/../docs/commands.md"
|
45
|
+
file_data =
|
46
|
+
<<~DATA
|
47
47
|
<!-- NOTE: This file is automatically generated by running `script/generate_commands_docs`. Do NOT edit it manually. -->
|
48
48
|
|
49
49
|
### Common Options
|
@@ -58,5 +58,5 @@ commands_data =
|
|
58
58
|
### Commands
|
59
59
|
|
60
60
|
#{commands_str}
|
61
|
-
|
62
|
-
File.binwrite(
|
61
|
+
DATA
|
62
|
+
File.binwrite(file_path, file_data)
|