Add gitlab-pages admin ping rake task
This commit is contained in:
parent
4fe3383133
commit
7da1b4cbf1
|
@ -72,3 +72,4 @@ eslint-report.html
|
|||
/locale/**/*.time_stamp
|
||||
/.rspec
|
||||
/plugins/*
|
||||
/.gitlab_pages_secret
|
||||
|
|
|
@ -1 +1 @@
|
|||
0.8.0
|
||||
0.9.0
|
||||
|
|
|
@ -212,6 +212,8 @@ production: &base
|
|||
artifacts_server: true
|
||||
# external_http: ["1.1.1.1:80", "[2001::1]:80"] # If defined, enables custom domain support in GitLab Pages
|
||||
# external_https: ["1.1.1.1:443", "[2001::1]:443"] # If defined, enables custom domain and certificate support in GitLab Pages
|
||||
admin:
|
||||
address: unix:/home/git/gitlab/tmp/sockets/private/pages-admin.socket # TCP connections are supported too (e.g. tcp://host:port)
|
||||
|
||||
## Mattermost
|
||||
## For enabling Add to Mattermost button
|
||||
|
|
|
@ -215,6 +215,9 @@ Settings.pages['external_http'] ||= false unless Settings.pages['external_ht
|
|||
Settings.pages['external_https'] ||= false unless Settings.pages['external_https'].present?
|
||||
Settings.pages['artifacts_server'] ||= Settings.pages['enabled'] if Settings.pages['artifacts_server'].nil?
|
||||
|
||||
Settings.pages['admin'] ||= Settingslogic.new({})
|
||||
Settings.pages.admin['certificate'] ||= ''
|
||||
|
||||
#
|
||||
# Git LFS
|
||||
#
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
Gitlab::PagesClient.read_or_create_token
|
||||
Gitlab::PagesClient.load_certificate
|
|
@ -0,0 +1,117 @@
|
|||
module Gitlab
|
||||
class PagesClient
|
||||
class << self
|
||||
attr_reader :certificate, :token
|
||||
|
||||
def call(service, rpc, request, timeout: nil)
|
||||
kwargs = request_kwargs(timeout)
|
||||
stub(service).__send__(rpc, request, kwargs) # rubocop:disable GitlabSecurity/PublicSend
|
||||
end
|
||||
|
||||
# This function is not thread-safe. Call it from an initializer only.
|
||||
def read_or_create_token
|
||||
@token = read_token
|
||||
rescue Errno::ENOENT
|
||||
# TODO: uncomment this when omnibus knows how to write the token file for us
|
||||
# https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests/2466
|
||||
#
|
||||
# write_token(SecureRandom.random_bytes(64))
|
||||
#
|
||||
# # Read from disk in case someone else won the race and wrote the file
|
||||
# # before us. If this fails again let the exception bubble up.
|
||||
# @token = read_token
|
||||
end
|
||||
|
||||
# This function is not thread-safe. Call it from an initializer only.
|
||||
def load_certificate
|
||||
cert_path = config.certificate
|
||||
return unless cert_path.present?
|
||||
|
||||
@certificate = File.read(cert_path)
|
||||
end
|
||||
|
||||
def ping
|
||||
request = Grpc::Health::V1::HealthCheckRequest.new
|
||||
call(:health_check, :check, request, timeout: 5.seconds)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def request_kwargs(timeout)
|
||||
encoded_token = Base64.strict_encode64(token.to_s)
|
||||
metadata = {
|
||||
'authorization' => "Bearer #{encoded_token}"
|
||||
}
|
||||
|
||||
result = { metadata: metadata }
|
||||
|
||||
return result unless timeout
|
||||
|
||||
# Do not use `Time.now` for deadline calculation, since it
|
||||
# will be affected by Timecop in some tests, but grpc's c-core
|
||||
# uses system time instead of timecop's time, so tests will fail
|
||||
# `Time.at(Process.clock_gettime(Process::CLOCK_REALTIME))` will
|
||||
# circumvent timecop
|
||||
deadline = Time.at(Process.clock_gettime(Process::CLOCK_REALTIME)) + timeout
|
||||
result[:deadline] = deadline
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def stub(name)
|
||||
stub_class(name).new(address, grpc_creds)
|
||||
end
|
||||
|
||||
def stub_class(name)
|
||||
if name == :health_check
|
||||
Grpc::Health::V1::Health::Stub
|
||||
else
|
||||
# TODO use pages namespace
|
||||
Gitaly.const_get(name.to_s.camelcase.to_sym).const_get(:Stub)
|
||||
end
|
||||
end
|
||||
|
||||
def address
|
||||
addr = config.address
|
||||
addr = addr.sub(%r{^tcp://}, '') if URI(addr).scheme == 'tcp'
|
||||
addr
|
||||
end
|
||||
|
||||
def grpc_creds
|
||||
if address.start_with?('unix:')
|
||||
:this_channel_is_insecure
|
||||
elsif @certificate
|
||||
GRPC::Core::ChannelCredentials.new(@certificate)
|
||||
else
|
||||
# Use system certificate pool
|
||||
GRPC::Core::ChannelCredentials.new
|
||||
end
|
||||
end
|
||||
|
||||
def config
|
||||
Gitlab.config.pages.admin
|
||||
end
|
||||
|
||||
def read_token
|
||||
File.read(token_path)
|
||||
end
|
||||
|
||||
def token_path
|
||||
Rails.root.join('.gitlab_pages_secret').to_s
|
||||
end
|
||||
|
||||
def write_token(new_token)
|
||||
Tempfile.open(File.basename(token_path), File.dirname(token_path), encoding: 'ascii-8bit') do |f|
|
||||
f.write(new_token)
|
||||
f.close
|
||||
File.link(f.path, token_path)
|
||||
end
|
||||
rescue Errno::EACCES => ex
|
||||
# TODO stop rescuing this exception in GitLab 11.0 https://gitlab.com/gitlab-org/gitlab-ce/issues/45672
|
||||
Rails.logger.error("Could not write pages admin token file: #{ex}")
|
||||
rescue Errno::EEXIST
|
||||
# Another process wrote the token file concurrently with us. Use their token, not ours.
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
namespace :gitlab do
|
||||
namespace :pages do
|
||||
desc 'Ping the pages admin API'
|
||||
task admin_ping: :gitlab_environment do
|
||||
Gitlab::PagesClient.ping
|
||||
puts "OK: gitlab-pages admin API is reachable"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,172 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::PagesClient do
|
||||
subject { described_class }
|
||||
|
||||
describe '.token' do
|
||||
it 'returns the token as it is on disk' do
|
||||
pending 'add omnibus support for generating the secret file https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests/2466'
|
||||
expect(subject.token).to eq(File.read('.gitlab_pages_secret'))
|
||||
end
|
||||
end
|
||||
|
||||
describe '.read_or_create_token' do
|
||||
subject { described_class.read_or_create_token }
|
||||
let(:token_path) { 'tmp/tests/gitlab-pages-secret' }
|
||||
before do
|
||||
allow(described_class).to receive(:token_path).and_return(token_path)
|
||||
FileUtils.rm_f(token_path)
|
||||
end
|
||||
|
||||
it 'uses the existing token file if it exists' do
|
||||
secret = 'existing secret'
|
||||
File.write(token_path, secret)
|
||||
|
||||
subject
|
||||
expect(described_class.token).to eq(secret)
|
||||
end
|
||||
|
||||
it 'creates one if none exists' do
|
||||
pending 'add omnibus support for generating the secret file https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests/2466'
|
||||
|
||||
old_token = described_class.token
|
||||
# sanity check
|
||||
expect(File.exist?(token_path)).to eq(false)
|
||||
|
||||
subject
|
||||
expect(described_class.token.bytesize).to eq(64)
|
||||
expect(described_class.token).not_to eq(old_token)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.write_token' do
|
||||
let(:token_path) { 'tmp/tests/gitlab-pages-secret' }
|
||||
before do
|
||||
allow(described_class).to receive(:token_path).and_return(token_path)
|
||||
FileUtils.rm_f(token_path)
|
||||
end
|
||||
|
||||
it 'writes the secret' do
|
||||
new_secret = 'hello new secret'
|
||||
expect(File.exist?(token_path)).to eq(false)
|
||||
|
||||
described_class.send(:write_token, new_secret)
|
||||
|
||||
expect(File.read(token_path)).to eq(new_secret)
|
||||
end
|
||||
|
||||
it 'does nothing if the file already exists' do
|
||||
existing_secret = 'hello secret'
|
||||
File.write(token_path, existing_secret)
|
||||
|
||||
described_class.send(:write_token, 'new secret')
|
||||
|
||||
expect(File.read(token_path)).to eq(existing_secret)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.load_certificate' do
|
||||
subject { described_class.load_certificate }
|
||||
before do
|
||||
allow(described_class).to receive(:config).and_return(config)
|
||||
end
|
||||
|
||||
context 'with no certificate in the config' do
|
||||
let(:config) { double(:config, certificate: '') }
|
||||
|
||||
it 'does not set @certificate' do
|
||||
subject
|
||||
|
||||
expect(described_class.certificate).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a certificate path in the config' do
|
||||
let(:certificate_path) { 'tmp/tests/fake-certificate' }
|
||||
let(:config) { double(:config, certificate: certificate_path) }
|
||||
|
||||
it 'sets @certificate' do
|
||||
certificate_data = "--- BEGIN CERTIFICATE ---\nbla\n--- END CERTIFICATE ---\n"
|
||||
File.write(certificate_path, certificate_data)
|
||||
subject
|
||||
|
||||
expect(described_class.certificate).to eq(certificate_data)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.request_kwargs' do
|
||||
let(:token) { 'secret token' }
|
||||
let(:auth_header) { 'Bearer c2VjcmV0IHRva2Vu' }
|
||||
before do
|
||||
allow(described_class).to receive(:token).and_return(token)
|
||||
end
|
||||
|
||||
context 'without timeout' do
|
||||
it { expect(subject.send(:request_kwargs, nil)[:metadata]['authorization']).to eq(auth_header) }
|
||||
end
|
||||
|
||||
context 'with timeout' do
|
||||
let(:timeout) { 1.second }
|
||||
|
||||
it 'still sets the authorization header' do
|
||||
expect(subject.send(:request_kwargs, timeout)[:metadata]['authorization']).to eq(auth_header)
|
||||
end
|
||||
|
||||
it 'sets a deadline value' do
|
||||
now = Time.now
|
||||
deadline = subject.send(:request_kwargs, timeout)[:deadline]
|
||||
|
||||
expect(deadline).to be_between(now, now + 2 * timeout)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.stub' do
|
||||
before do
|
||||
allow(described_class).to receive(:address).and_return('unix:/foo/bar')
|
||||
end
|
||||
|
||||
it { expect(subject.send(:stub, :health_check)).to be_a(Grpc::Health::V1::Health::Stub) }
|
||||
end
|
||||
|
||||
describe '.address' do
|
||||
subject { described_class.send(:address) }
|
||||
|
||||
before do
|
||||
allow(described_class).to receive(:config).and_return(config)
|
||||
end
|
||||
|
||||
context 'with a unix: address' do
|
||||
let(:config) { double(:config, address: 'unix:/foo/bar') }
|
||||
|
||||
it { expect(subject).to eq('unix:/foo/bar') }
|
||||
end
|
||||
|
||||
context 'with a tcp:// address' do
|
||||
let(:config) { double(:config, address: 'tcp://localhost:1234') }
|
||||
|
||||
it { expect(subject).to eq('localhost:1234') }
|
||||
end
|
||||
end
|
||||
|
||||
describe '.grpc_creds' do
|
||||
subject { described_class.send(:grpc_creds) }
|
||||
|
||||
before do
|
||||
allow(described_class).to receive(:config).and_return(config)
|
||||
end
|
||||
|
||||
context 'with a unix: address' do
|
||||
let(:config) { double(:config, address: 'unix:/foo/bar') }
|
||||
|
||||
it { expect(subject).to eq(:this_channel_is_insecure) }
|
||||
end
|
||||
|
||||
context 'with a tcp:// address' do
|
||||
let(:config) { double(:config, address: 'tcp://localhost:1234') }
|
||||
|
||||
it { expect(subject).to be_a(GRPC::Core::ChannelCredentials) }
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue