Commit 9e6098a6 authored by Kamil Trzciński's avatar Kamil Trzciński

Merge branch 'refactor_gitlab_kube_client' into 'master'

Refactor Gitlab::KubeClient

Closes #52131

See merge request gitlab-org/gitlab-ce!22073
parents a5412de5 a5419138
...@@ -107,7 +107,7 @@ module Clusters ...@@ -107,7 +107,7 @@ module Clusters
end end
def kubeclient def kubeclient
@kubeclient ||= build_kube_client!(api_groups: ['api', 'apis/rbac.authorization.k8s.io']) @kubeclient ||= build_kube_client!
end end
private private
...@@ -136,7 +136,7 @@ module Clusters ...@@ -136,7 +136,7 @@ module Clusters
Gitlab::NamespaceSanitizer.sanitize(slug) Gitlab::NamespaceSanitizer.sanitize(slug)
end end
def build_kube_client!(api_groups: ['api'], api_version: 'v1') def build_kube_client!
raise "Incomplete settings" unless api_url && actual_namespace raise "Incomplete settings" unless api_url && actual_namespace
unless (username && password) || token unless (username && password) || token
...@@ -145,8 +145,6 @@ module Clusters ...@@ -145,8 +145,6 @@ module Clusters
Gitlab::Kubernetes::KubeClient.new( Gitlab::Kubernetes::KubeClient.new(
api_url, api_url,
api_groups,
api_version,
auth_options: kubeclient_auth_options, auth_options: kubeclient_auth_options,
ssl_options: kubeclient_ssl_options, ssl_options: kubeclient_ssl_options,
http_proxy_uri: ENV['http_proxy'] http_proxy_uri: ENV['http_proxy']
......
...@@ -144,7 +144,7 @@ class KubernetesService < DeploymentService ...@@ -144,7 +144,7 @@ class KubernetesService < DeploymentService
end end
def kubeclient def kubeclient
@kubeclient ||= build_kube_client!(api_groups: ['api', 'apis/rbac.authorization.k8s.io']) @kubeclient ||= build_kube_client!
end end
def deprecated? def deprecated?
...@@ -182,13 +182,11 @@ class KubernetesService < DeploymentService ...@@ -182,13 +182,11 @@ class KubernetesService < DeploymentService
slug.gsub(/[^-a-z0-9]/, '-').gsub(/^-+/, '') slug.gsub(/[^-a-z0-9]/, '-').gsub(/^-+/, '')
end end
def build_kube_client!(api_groups: ['api'], api_version: 'v1') def build_kube_client!
raise "Incomplete settings" unless api_url && actual_namespace && token raise "Incomplete settings" unless api_url && actual_namespace && token
Gitlab::Kubernetes::KubeClient.new( Gitlab::Kubernetes::KubeClient.new(
api_url, api_url,
api_groups,
api_version,
auth_options: kubeclient_auth_options, auth_options: kubeclient_auth_options,
ssl_options: kubeclient_ssl_options, ssl_options: kubeclient_ssl_options,
http_proxy_uri: ENV['http_proxy'] http_proxy_uri: ENV['http_proxy']
......
...@@ -60,18 +60,15 @@ module Clusters ...@@ -60,18 +60,15 @@ module Clusters
'https://' + gke_cluster.endpoint, 'https://' + gke_cluster.endpoint,
Base64.decode64(gke_cluster.master_auth.cluster_ca_certificate), Base64.decode64(gke_cluster.master_auth.cluster_ca_certificate),
gke_cluster.master_auth.username, gke_cluster.master_auth.username,
gke_cluster.master_auth.password, gke_cluster.master_auth.password
api_groups: ['api', 'apis/rbac.authorization.k8s.io']
) )
end end
def build_kube_client!(api_url, ca_pem, username, password, api_groups: ['api'], api_version: 'v1') def build_kube_client!(api_url, ca_pem, username, password)
raise "Incomplete settings" unless api_url && username && password raise "Incomplete settings" unless api_url && username && password
Gitlab::Kubernetes::KubeClient.new( Gitlab::Kubernetes::KubeClient.new(
api_url, api_url,
api_groups,
api_version,
auth_options: { username: username, password: password }, auth_options: { username: username, password: password },
ssl_options: kubeclient_ssl_options(ca_pem), ssl_options: kubeclient_ssl_options(ca_pem),
http_proxy_uri: ENV['http_proxy'] http_proxy_uri: ENV['http_proxy']
......
...@@ -13,11 +13,21 @@ module Gitlab ...@@ -13,11 +13,21 @@ module Gitlab
class KubeClient class KubeClient
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
SUPPORTED_API_GROUPS = [ SUPPORTED_API_GROUPS = {
'api', core: { group: 'api', version: 'v1' },
'apis/rbac.authorization.k8s.io', rbac: { group: 'apis/rbac.authorization.k8s.io', version: 'v1' },
'apis/extensions' extensions: { group: 'apis/extensions', version: 'v1beta1' }
].freeze }.freeze
SUPPORTED_API_GROUPS.each do |name, params|
client_method_name = "#{name}_client".to_sym
define_method(client_method_name) do
strong_memoize(client_method_name) do
build_kubeclient(params[:group], params[:version])
end
end
end
# Core API methods delegates to the core api group client # Core API methods delegates to the core api group client
delegate :get_pods, delegate :get_pods,
...@@ -62,48 +72,21 @@ module Gitlab ...@@ -62,48 +72,21 @@ module Gitlab
:watch_pod_log, :watch_pod_log,
to: :core_client to: :core_client
def initialize(api_prefix, api_groups = ['api'], api_version = 'v1', **kubeclient_options) attr_reader :api_prefix, :kubeclient_options
raise ArgumentError unless check_api_groups_supported?(api_groups)
def initialize(api_prefix, **kubeclient_options)
@api_prefix = api_prefix @api_prefix = api_prefix
@api_groups = api_groups
@api_version = api_version
@kubeclient_options = kubeclient_options @kubeclient_options = kubeclient_options
end end
def discover!
clients.each(&:discover)
end
def clients
hashed_clients.values
end
def core_client
hashed_clients['api']
end
def rbac_client
hashed_clients['apis/rbac.authorization.k8s.io']
end
def extensions_client
hashed_clients['apis/extensions']
end
def hashed_clients
strong_memoize(:hashed_clients) do
@api_groups.map do |api_group|
api_url = join_api_url(@api_prefix, api_group)
[api_group, ::Kubeclient::Client.new(api_url, @api_version, **@kubeclient_options)]
end.to_h
end
end
private private
def check_api_groups_supported?(api_groups) def build_kubeclient(api_group, api_version)
api_groups.all? {|api_group| SUPPORTED_API_GROUPS.include?(api_group) } ::Kubeclient::Client.new(
join_api_url(api_prefix, api_group),
api_version,
**kubeclient_options
)
end end
def join_api_url(api_prefix, api_path) def join_api_url(api_prefix, api_path)
......
...@@ -6,104 +6,63 @@ describe Gitlab::Kubernetes::KubeClient do ...@@ -6,104 +6,63 @@ describe Gitlab::Kubernetes::KubeClient do
include KubernetesHelpers include KubernetesHelpers
let(:api_url) { 'https://kubernetes.example.com/prefix' } let(:api_url) { 'https://kubernetes.example.com/prefix' }
let(:api_groups) { ['api', 'apis/rbac.authorization.k8s.io'] }
let(:api_version) { 'v1' }
let(:kubeclient_options) { { auth_options: { bearer_token: 'xyz' } } } let(:kubeclient_options) { { auth_options: { bearer_token: 'xyz' } } }
let(:client) { described_class.new(api_url, api_groups, api_version, kubeclient_options) } let(:client) { described_class.new(api_url, kubeclient_options) }
before do before do
stub_kubeclient_discover(api_url) stub_kubeclient_discover(api_url)
end end
describe '#hashed_clients' do shared_examples 'a Kubeclient' do
subject { client.hashed_clients } it 'is a Kubeclient::Client' do
is_expected.to be_an_instance_of Kubeclient::Client
it 'has keys from api groups' do
expect(subject.keys).to match_array api_groups
end
it 'has values of Kubeclient::Client' do
expect(subject.values).to all(be_an_instance_of Kubeclient::Client)
end
end
describe '#clients' do
subject { client.clients }
it 'is not empty' do
is_expected.to be_present
end
it 'is an array of Kubeclient::Client objects' do
is_expected.to all(be_an_instance_of Kubeclient::Client)
end
it 'has each API group url' do
expected_urls = api_groups.map { |group| "#{api_url}/#{group}" }
expect(subject.map(&:api_endpoint).map(&:to_s)).to match_array(expected_urls)
end end
it 'has the kubeclient options' do it 'has the kubeclient options' do
subject.each do |client| expect(subject.auth_options).to eq({ bearer_token: 'xyz' })
expect(client.auth_options).to eq({ bearer_token: 'xyz' })
end
end
it 'has the api_version' do
subject.each do |client|
expect(client.instance_variable_get(:@api_version)).to eq('v1')
end
end end
end end
describe '#core_client' do describe '#core_client' do
subject { client.core_client } subject { client.core_client }
it 'is a Kubeclient::Client' do it_behaves_like 'a Kubeclient'
is_expected.to be_an_instance_of Kubeclient::Client
end
it 'has the core API endpoint' do it 'has the core API endpoint' do
expect(subject.api_endpoint.to_s).to match(%r{\/api\Z}) expect(subject.api_endpoint.to_s).to match(%r{\/api\Z})
end end
it 'has the api_version' do
expect(subject.instance_variable_get(:@api_version)).to eq('v1')
end
end end
describe '#rbac_client' do describe '#rbac_client' do
subject { client.rbac_client } subject { client.rbac_client }
it 'is a Kubeclient::Client' do it_behaves_like 'a Kubeclient'
is_expected.to be_an_instance_of Kubeclient::Client
end
it 'has the RBAC API group endpoint' do it 'has the RBAC API group endpoint' do
expect(subject.api_endpoint.to_s).to match(%r{\/apis\/rbac.authorization.k8s.io\Z}) expect(subject.api_endpoint.to_s).to match(%r{\/apis\/rbac.authorization.k8s.io\Z})
end end
it 'has the api_version' do
expect(subject.instance_variable_get(:@api_version)).to eq('v1')
end
end end
describe '#extensions_client' do describe '#extensions_client' do
subject { client.extensions_client } subject { client.extensions_client }
let(:api_groups) { ['apis/extensions'] } it_behaves_like 'a Kubeclient'
it 'is a Kubeclient::Client' do
is_expected.to be_an_instance_of Kubeclient::Client
end
it 'has the extensions API group endpoint' do it 'has the extensions API group endpoint' do
expect(subject.api_endpoint.to_s).to match(%r{\/apis\/extensions\Z}) expect(subject.api_endpoint.to_s).to match(%r{\/apis\/extensions\Z})
end end
end
describe '#discover!' do
it 'makes a discovery request for each API group' do
client.discover!
api_groups.each do |api_group| it 'has the api_version' do
discovery_url = api_url + '/' + api_group + '/v1' expect(subject.instance_variable_get(:@api_version)).to eq('v1beta1')
expect(WebMock).to have_requested(:get, discovery_url).once
end
end end
end end
...@@ -156,21 +115,12 @@ describe Gitlab::Kubernetes::KubeClient do ...@@ -156,21 +115,12 @@ describe Gitlab::Kubernetes::KubeClient do
it 'responds to the method' do it 'responds to the method' do
expect(client).to respond_to method expect(client).to respond_to method
end end
context 'no rbac client' do
let(:api_groups) { ['api'] }
it 'throws an error' do
expect { client.public_send(method) }.to raise_error(Module::DelegationError)
end
end
end end
end end
end end
describe 'extensions API group' do describe 'extensions API group' do
let(:api_groups) { ['apis/extensions'] } let(:api_groups) { ['apis/extensions'] }
let(:api_version) { 'v1beta1' }
let(:extensions_client) { client.extensions_client } let(:extensions_client) { client.extensions_client }
describe '#get_deployments' do describe '#get_deployments' do
...@@ -181,22 +131,11 @@ describe Gitlab::Kubernetes::KubeClient do ...@@ -181,22 +131,11 @@ describe Gitlab::Kubernetes::KubeClient do
it 'responds to the method' do it 'responds to the method' do
expect(client).to respond_to :get_deployments expect(client).to respond_to :get_deployments
end end
context 'no extensions client' do
let(:api_groups) { ['api'] }
let(:api_version) { 'v1' }
it 'throws an error' do
expect { client.get_deployments }.to raise_error(Module::DelegationError)
end
end
end end
end end
describe 'non-entity methods' do describe 'non-entity methods' do
it 'does not proxy for non-entity methods' do it 'does not proxy for non-entity methods' do
expect(client.clients.first).to respond_to :proxy_url
expect(client).not_to respond_to :proxy_url expect(client).not_to respond_to :proxy_url
end end
...@@ -211,14 +150,6 @@ describe Gitlab::Kubernetes::KubeClient do ...@@ -211,14 +150,6 @@ describe Gitlab::Kubernetes::KubeClient do
it 'is delegated to the core client' do it 'is delegated to the core client' do
expect(client).to delegate_method(:get_pod_log).to(:core_client) expect(client).to delegate_method(:get_pod_log).to(:core_client)
end end
context 'when no core client' do
let(:api_groups) { ['apis/extensions'] }
it 'throws an error' do
expect { client.get_pod_log('pod-name') }.to raise_error(Module::DelegationError)
end
end
end end
describe '#watch_pod_log' do describe '#watch_pod_log' do
...@@ -227,14 +158,6 @@ describe Gitlab::Kubernetes::KubeClient do ...@@ -227,14 +158,6 @@ describe Gitlab::Kubernetes::KubeClient do
it 'is delegated to the core client' do it 'is delegated to the core client' do
expect(client).to delegate_method(:watch_pod_log).to(:core_client) expect(client).to delegate_method(:watch_pod_log).to(:core_client)
end end
context 'when no core client' do
let(:api_groups) { ['apis/extensions'] }
it 'throws an error' do
expect { client.watch_pod_log('pod-name') }.to raise_error(Module::DelegationError)
end
end
end end
describe 'methods that do not exist on any client' do describe 'methods that do not exist on any client' do
......
...@@ -16,7 +16,6 @@ describe Clusters::Gcp::Kubernetes::CreateServiceAccountService do ...@@ -16,7 +16,6 @@ describe Clusters::Gcp::Kubernetes::CreateServiceAccountService do
let(:kubeclient) do let(:kubeclient) do
Gitlab::Kubernetes::KubeClient.new( Gitlab::Kubernetes::KubeClient.new(
api_url, api_url,
['api', 'apis/rbac.authorization.k8s.io'],
auth_options: { username: username, password: password } auth_options: { username: username, password: password }
) )
end end
......
...@@ -11,7 +11,6 @@ describe Clusters::Gcp::Kubernetes::FetchKubernetesTokenService do ...@@ -11,7 +11,6 @@ describe Clusters::Gcp::Kubernetes::FetchKubernetesTokenService do
let(:kubeclient) do let(:kubeclient) do
Gitlab::Kubernetes::KubeClient.new( Gitlab::Kubernetes::KubeClient.new(
api_url, api_url,
['api', 'apis/rbac.authorization.k8s.io'],
auth_options: { username: username, password: password } auth_options: { username: username, password: password }
) )
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment