Skip to content

Commit aad20ab

Browse files
jaimem88balasankarc
authored andcommitted
Add registry-database migrate command to gitlab-ctl
The command will allow gitlab-ctl to interact with the configured registry metadata database. It adds the subcommand `migrate` to manage schema migrations for the database. The command interacts with a given database but does not do anything about provisioning the database. For more details see https://gitlab.com/gitlab-org/container-registry/-/issues/1108. Changelog: added
1 parent 60c841b commit aad20ab

File tree

5 files changed

+570
-0
lines changed

5 files changed

+570
-0
lines changed
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
require 'optparse'
2+
3+
module Migrate
4+
CMD_NAME = 'migrate'.freeze
5+
SUMMARY_WIDTH = 40
6+
DESC_INDENT = 45
7+
8+
def self.indent(str, len)
9+
str.gsub(/%%/, ' ' * len)
10+
end
11+
12+
USAGE ||= <<~EOS.freeze
13+
Manage migrations
14+
15+
Usage:
16+
gitlab-ctl registry-database migrate SUBCOMMAND [options]
17+
18+
Subcommands:
19+
down Apply down migrations
20+
status Show migration status
21+
up Apply up migrations
22+
version Show current migration version
23+
24+
Options:
25+
-h, --help help for migrate
26+
EOS
27+
28+
UP_USAGE ||= <<~EOS.freeze
29+
Apply up migrations
30+
31+
Usage:
32+
gitlab-ctl registry-database migrate up [options]
33+
34+
Options:
35+
-d, --dry-run do not commit changes to the database
36+
-h, --help help for up
37+
-l, --limit int limit the number of migrations (all by default)
38+
-s, --skip-post-deployment do not apply post deployment migrations
39+
EOS
40+
41+
DOWN_USAGE ||= <<~EOS.freeze
42+
Apply down migrations
43+
44+
Usage:
45+
gitlab-ctl registry-database migrate down [options]
46+
47+
Options:
48+
-d, --dry-run do not commit changes to the database
49+
-f, --force no confirmation message
50+
-h, --help help for down
51+
-l, --limit int limit the number of migrations (all by default)
52+
EOS
53+
54+
STATUS_USAGE ||= <<~EOS.freeze
55+
Show migration status
56+
57+
Usage:
58+
gitlab-ctl registry-database migrate status [options]
59+
60+
Options:
61+
-h, --help help for status
62+
-s, --skip-post-deployment ignore post deployment migrations
63+
-u, --up-to-date check if all known migrations are applied
64+
65+
EOS
66+
67+
VERSION_USAGE ||= <<~EOS.freeze
68+
Show current migration version
69+
70+
Usage:
71+
gitlab-ctl registry-database migrate version [options]
72+
73+
Flags:
74+
-h, --help help for version
75+
EOS
76+
77+
def self.parse_options!(args, options)
78+
return unless args.include? CMD_NAME
79+
80+
loop do
81+
break if args.shift == CMD_NAME
82+
end
83+
84+
OptionParser.new do |opts|
85+
opts.on('-h', '--help', 'Usage help') do
86+
Kernel.puts USAGE
87+
Kernel.exit 0
88+
end
89+
end.order! args
90+
91+
subcommands = populate_subcommands(options)
92+
subcommand = args.shift
93+
94+
raise OptionParser::ParseError, "migrate subcommand is not specified." \
95+
if subcommand.nil? || subcommand.empty?
96+
97+
raise OptionParser::ParseError, "Unknown migrate subcommand: #{subcommand}" \
98+
unless subcommands.key?(subcommand)
99+
100+
subcommands[subcommand].parse!(args)
101+
options[:subcommand] = subcommand
102+
needs_stop!(options)
103+
104+
options
105+
end
106+
107+
def self.populate_subcommands(options)
108+
database_docs_url = 'https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs-gitlab/database-migrations.md?ref_type=heads#administration'
109+
110+
{
111+
'up' => OptionParser.new do |opts|
112+
opts.banner = "Usage gitlab-ctl registry-database migrate up [options]. See documentation at #{database_docs_url}"
113+
parse_common_options!(opts)
114+
parse_up_down_common_options!(options, opts)
115+
parse_up_options!(options, opts)
116+
end,
117+
'down' => OptionParser.new do |opts|
118+
opts.banner = "Usage gitlab-ctl registry-database migrate down [options]. See documentation at #{database_docs_url}"
119+
parse_common_options!(opts)
120+
parse_up_down_common_options!(options, opts)
121+
parse_down_options!(options, opts)
122+
end,
123+
'status' => OptionParser.new do |opts|
124+
opts.banner = "Usage gitlab-ctl registry-database migrate status [options]. See documentation at #{database_docs_url}"
125+
parse_common_options!(opts)
126+
parse_status_options!(options, opts)
127+
end,
128+
'version' => OptionParser.new do |opts|
129+
opts.banner = "Usage gitlab-ctl registry-database migrate version [options]. See documentation at #{database_docs_url}"
130+
opts.on('-h', '--help', 'Usage help') do
131+
Kernel.puts VERSION_USAGE
132+
Kernel.exit 0
133+
end
134+
135+
parse_common_options!(opts)
136+
end,
137+
}
138+
end
139+
140+
def self.parse_common_options!(option_parser)
141+
option_parser.on("-h", "--help", "Prints this help") do
142+
option_parser.set_summary_width(SUMMARY_WIDTH)
143+
Kernel.puts USAGE
144+
Kernel.exit 0
145+
end
146+
end
147+
148+
def self.parse_up_down_common_options!(options, option_parser)
149+
option_parser.on('-d', '--dry-run', indent('do not commit changes to the database', DESC_INDENT)) do
150+
options[:dry_run] = '-d'
151+
end
152+
153+
option_parser.on('-l limit', '--limit LIMIT', indent('limit the number of migrations (all by default)', DESC_INDENT)) do |limit|
154+
raise OptionParser::ParseError, "--limit option must be a positive number" \
155+
if limit.nil? || limit.to_i <= 0
156+
157+
options[:limit] = limit
158+
end
159+
end
160+
161+
def self.parse_up_options!(options, option_parser)
162+
option_parser.on('-h', '--help', 'Usage help') do
163+
Kernel.puts UP_USAGE
164+
Kernel.exit 0
165+
end
166+
167+
option_parser.on('-s', '--skip-post-deployment', indent('do not apply post deployment migration', DESC_INDENT)) do
168+
options[:skip_post_deploy] = '-s'
169+
end
170+
end
171+
172+
def self.parse_down_options!(options, option_parser)
173+
option_parser.on('-h', '--help', 'Usage help') do
174+
Kernel.puts DOWN_USAGE
175+
Kernel.exit 0
176+
end
177+
178+
option_parser.on('-f', '--force', indent('no confirmation message', DESC_INDENT)) do
179+
options[:force] = '-f'
180+
end
181+
end
182+
183+
def self.parse_status_options!(options, option_parser)
184+
option_parser.on('-h', '--help', 'Usage help') do
185+
Kernel.puts STATUS_USAGE
186+
Kernel.exit 0
187+
end
188+
189+
option_parser.on('-u', '--up-to-date', indent('do not commit changes to the database', DESC_INDENT)) do
190+
options[:up_to_date] = '-u'
191+
end
192+
193+
option_parser.on('-s', '--skip-post-deployment', indent('do not apply post deployment migration', DESC_INDENT)) do
194+
options[:skip_post_deploy] = '-s'
195+
end
196+
end
197+
198+
def self.needs_stop!(options)
199+
case options[:subcommand]
200+
when 'up', 'down'
201+
options[:needs_stop] = true unless options.has_key? :dry_run
202+
else
203+
options[:needs_stop] = false
204+
end
205+
end
206+
end
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
require 'fileutils'
2+
require 'optparse'
3+
4+
require_relative './migrate'
5+
6+
module RegistryDatabase
7+
EXEC_PATH = '/opt/gitlab/embedded/bin/registry'.freeze
8+
CONFIG_PATH = '/var/opt/gitlab/registry/config.yml'.freeze
9+
10+
USAGE ||= <<~EOS.freeze
11+
Usage:
12+
gitlab-ctl registry-database command subcommand [options]
13+
14+
GLOBAL OPTIONS:
15+
-h, --help Usage help
16+
17+
COMMANDS:
18+
migrate Manage schema migrations
19+
EOS
20+
21+
def self.parse_options!(ctl, args)
22+
@ctl = ctl
23+
24+
loop do
25+
break if args.shift == 'registry-database'
26+
end
27+
28+
global = OptionParser.new do |opts|
29+
opts.on('-h', '--help', 'Usage help') do
30+
Kernel.puts USAGE
31+
Kernel.exit 0
32+
end
33+
end
34+
35+
global.order!(args)
36+
37+
# the command is needed by the dependencies in populate_commands
38+
command = args[0]
39+
raise OptionParser::ParseError, "registry-database command is not specified." \
40+
if command.nil? || command.empty?
41+
42+
options = {}
43+
commands = populate_commands(options)
44+
45+
raise OptionParser::ParseError, "Unknown registry-database command: #{command}" \
46+
unless commands.key?(command)
47+
48+
commands[command].parse!(args)
49+
options[:command] = command
50+
51+
options
52+
end
53+
54+
def self.populate_commands(options)
55+
database_docs_url = 'https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs-gitlab/database-migrations.md?ref_type=heads#administration'
56+
57+
{
58+
'migrate' => OptionParser.new do |opts|
59+
opts.banner = "Usage: gitlab-ctl registry-database migrate SUBCOMMAND [options]. See documentation at #{database_docs_url}"
60+
begin
61+
Migrate.parse_options!(ARGV, options)
62+
rescue OptionParser::ParseError => e
63+
warn "#{e}\n\n#{Migrate::USAGE}"
64+
exit 128
65+
end
66+
end,
67+
}
68+
end
69+
70+
def self.usage
71+
USAGE
72+
end
73+
74+
def self.execute(options)
75+
unless enabled?
76+
log "Container registry is not enabled, exiting..."
77+
return
78+
end
79+
80+
[EXEC_PATH, CONFIG_PATH].each do |path|
81+
next if File.exist?(path)
82+
83+
Kernel.abort "Could not find '#{path}' file. Is this command being run on a Container Registry node?"
84+
end
85+
86+
command = set_command(options)
87+
88+
begin
89+
status = Kernel.system(*command)
90+
Kernel.exit!(1) unless status
91+
ensure
92+
start!
93+
end
94+
end
95+
96+
def self.set_command(options)
97+
command = [EXEC_PATH, "database", options[:command], options[:subcommand]]
98+
99+
options.delete(:command)
100+
options.delete(:subcommand)
101+
needs_stop = options[:needs_stop]
102+
options.delete(:needs_stop)
103+
104+
continue?(needs_stop)
105+
106+
command += ["-n", options[:limit]] unless options[:limit].nil?
107+
options.delete(:limit)
108+
109+
options.each do |_, opt|
110+
command.append(opt)
111+
end
112+
113+
# always set the config file at the end
114+
command += [CONFIG_PATH]
115+
116+
command
117+
end
118+
119+
def self.log(msg)
120+
@ctl.log(msg)
121+
end
122+
123+
def self.running?
124+
!GitlabCtl::Util.run_command("gitlab-ctl status #{service_name}").error?
125+
end
126+
127+
def self.start!
128+
puts "Starting service #{service_name}"
129+
130+
@ctl.run_sv_command_for_service('start', service_name)
131+
end
132+
133+
def self.stop!
134+
puts "Stopping service #{service_name}"
135+
136+
@ctl.run_sv_command_for_service('stop', service_name)
137+
end
138+
139+
def self.enabled?
140+
@ctl.service_enabled?(service_name)
141+
end
142+
143+
def self.config?
144+
File.exist?(@path)
145+
end
146+
147+
def self.service_name
148+
"registry"
149+
end
150+
151+
def self.continue?(needs_stop)
152+
return unless needs_stop && running?
153+
154+
puts 'WARNING: Migrations cannot be applied while the container registry is running. '\
155+
'Stop the registry before proceeding? (y/n)'.color(:yellow)
156+
157+
if $stdin.gets.chomp.casecmp('y').zero?
158+
stop!
159+
else
160+
puts "Exiting..."
161+
exit 1
162+
end
163+
end
164+
end

0 commit comments

Comments
 (0)