files/gitlab-ctl-commands/lib/gitlab_ctl/util.rb (142 lines of code) (raw):
require 'mixlib/shellout'
require 'io/console'
require 'chef/mash'
require 'chef/mixins'
require 'json'
require 'socket'
module GitlabCtl
module Util
PUBLIC_ATTRIBUTES_FILE = '/var/opt/gitlab/public_attributes.json'.freeze
class <<self
def get_command_output(command, user = nil, timeout = nil)
begin
shell_out = run_command(command, live: false, user: user, timeout: timeout)
shell_out.error!
rescue Mixlib::ShellOut::ShellCommandFailed
raise GitlabCtl::Errors::ExecutionError.new(
command, shell_out.stdout, shell_out.stderr
)
end
shell_out.stdout
end
def run_command(command, live: false, user: nil, timeout: nil, env: {}, input: nil)
timeout = Mixlib::ShellOut::DEFAULT_READ_TIMEOUT if timeout.nil?
shell_out = Mixlib::ShellOut.new(command, timeout: timeout, environment: env)
shell_out.user = user unless user.nil?
shell_out.input = input if input
shell_out.live_stdout = $stdout if live
shell_out.live_stderr = $stderr if live
shell_out.run_command
shell_out
end
def get_fqdn
results = run_command('hostname -f')
results.stdout.chomp
end
def parse_json_file(file)
begin
data = ::JSON.load_file(file)
rescue JSON::ParserError
raise GitlabCtl::Errors::NodeError,
"Error reading #{file}, has reconfigure been run yet?"
end
if file.start_with?('/opt/gitlab/embedded/nodes') && !data.key?('normal')
raise GitlabCtl::Errors::NodeError,
"Attributes not found in #{file}, has reconfigure been run yet?"
end
data
end
def get_node_attributes(base_path = '/opt/gitlab')
# reconfigure creates a json file containing all of the attributes of
# the node after a chef run, indexed by priority. Merge an return those
# as a single level Hash
fqdn = get_fqdn
attribute_file = File.exist?("#{base_path}/embedded/nodes/#{fqdn}.json") ? "#{base_path}/embedded/nodes/#{fqdn}.json" : Dir.glob("#{base_path}/embedded/nodes/*.json").max_by { |f| File.mtime(f) }
raise GitlabCtl::Errors::NodeError, "Node attributes JSON file not found in #{base_path}/embedded/nodes, has reconfigure been run yet?" unless attribute_file
data = parse_json_file(attribute_file)
Chef::Mixin::DeepMerge.merge(data['default'], data['normal'])
end
def get_public_node_attributes
return {} if public_attributes_missing?
parse_json_file(PUBLIC_ATTRIBUTES_FILE)
end
def get_password(input_text: 'Enter password: ', do_confirm: true)
return STDIN.gets.chomp unless STDIN.tty?
password = STDIN.getpass(input_text)
if do_confirm
password_confirm = STDIN.getpass('Confirm password: ')
raise GitlabCtl::Errors::PasswordMismatch unless password.eql?(password_confirm)
end
password
end
def userinfo(username)
Etc.getpwnam(username)
end
def groupinfo(groupname)
Etc.getgrnam(groupname)
end
def chef_run(config, attribs, alternate_log = nil, timeout: nil)
cookbook_path = "/opt/gitlab/embedded/cookbooks"
alternate_log = " -L #{alternate_log}" if alternate_log
run_command("/opt/gitlab/embedded/bin/cinc-client#{alternate_log} -z -c #{cookbook_path}/#{config} -j #{cookbook_path}/#{attribs}", timeout: timeout)
end
# Parse enabled roles out of the attributes json file and return an Array of Strings
def roles(base_path)
roles = get_node_attributes(base_path)['roles']
return [] unless roles.is_a?(Hash)
roles.select { |k, v| v.key?('enable') && v['enable'] }.keys
end
def delay_for(seconds)
$stdout.print "\nPlease hit Ctrl-C now if you want to cancel the operation.\n"
seconds.times do
$stdout.print '.'
sleep 1
end
true
rescue Interrupt
$stdout.print "\nInterrupt received, cancelling operation.\n"
false
end
def progress_message(message, &block)
$stdout.print "\r#{message}:"
results = yield
if results
$stdout.print "\r#{message}: \e[32mOK\e[0m\n"
else
$stdout.print "\r#{message}: \e[31mNOT OK\e[0m\n"
end
results
end
def warn(message)
$stderr.print "\r\e[33m#{message}\e[0m\n"
end
DURATION_UNITS = {
'ms' => 1,
's' => 1000,
'm' => 1000 * 60,
'h' => 1000 * 60 * 60,
'd' => 1000 * 60 * 60 * 24
}.freeze
def parse_duration(duration)
millis = 0
duration&.scan(/(?<quantity>\d+(\.\d+)?)(?<unit>[a-zA-Z]+)/)&.each do |quantity, unit|
multiplier = DURATION_UNITS[unit]
break if multiplier.nil?
millis += multiplier * quantity.to_f
end
begin
millis = Float(duration || '') if millis.zero?
rescue ArgumentError
# Translating exception
raise ArgumentError, "invalid value for duration: `#{duration}`"
end
millis.to_i
end
def public_attributes_missing?
!File.exist?(PUBLIC_ATTRIBUTES_FILE)
end
def public_attributes_broken?(attribute_key = "gitlab")
return true if public_attributes_missing?
!get_public_node_attributes.key?(attribute_key)
end
def master_cookbook
File.directory?('/opt/gitlab/embedded/cookbooks/gitlab-ee') ? 'gitlab-ee' : 'gitlab'
end
end
end
end