Commit aa862ae8 authored by Zamir Martins Filho's avatar Zamir Martins Filho Committed by Stan Hu

Add support for multiple environments

Update code to support multiple environments
scoped within a project for NetworkPolicy k8s
resource
parent 433a839a
...@@ -34,7 +34,7 @@ module Projects ...@@ -34,7 +34,7 @@ module Projects
end end
def index def index
response = NetworkPolicies::ResourcesService.new(environment: environment).execute response = NetworkPolicies::ResourcesService.new(project: project, environment_id: params[:environment_id]).execute
respond_with_service_response(response) respond_with_service_response(response)
end end
...@@ -89,7 +89,7 @@ module Projects ...@@ -89,7 +89,7 @@ module Projects
end end
def respond_with_service_response(response) def respond_with_service_response(response)
payload = response.success? ? response.payload : { error: response.message } payload = response.success? ? response.payload : { payload: response.payload, error: response.message }
respond_to do |format| respond_to do |format|
format.json do format.json do
render status: response.http_status, json: payload render status: response.http_status, json: payload
......
...@@ -4,23 +4,59 @@ module NetworkPolicies ...@@ -4,23 +4,59 @@ module NetworkPolicies
class ResourcesService class ResourcesService
include NetworkPolicies::Responses include NetworkPolicies::Responses
def initialize(environment:) LIMIT = 100
@platform = environment.deployment_platform
@kubernetes_namespace = environment.deployment_namespace def initialize(project:, environment_id: nil)
@kubeclient_info = extract_info_for_kubeclient(project, environment_id)
end end
def execute def execute
return no_platform_response unless @platform return no_platform_response unless has_deployment_platform? @kubeclient_info
policies = []
errors = []
@kubeclient_info.each do |platform, namespace|
policies_per_environment, error_per_environment = execute_per_environment(platform, namespace)
policies += policies_per_environment
errors << error_per_environment if error_per_environment
end
errors.empty? ? ServiceResponse.success(payload: policies) : kubernetes_error_response(errors.join, policies)
end
private
policies = @platform.kubeclient def execute_per_environment(platform, namespace)
.get_network_policies(namespace: @kubernetes_namespace) policies = platform.kubeclient
.get_network_policies(namespace: namespace)
.map { |resource| Gitlab::Kubernetes::NetworkPolicy.from_resource(resource) } .map { |resource| Gitlab::Kubernetes::NetworkPolicy.from_resource(resource) }
policies += @platform.kubeclient policies += platform.kubeclient
.get_cilium_network_policies(namespace: @kubernetes_namespace) .get_cilium_network_policies(namespace: namespace)
.map { |resource| Gitlab::Kubernetes::CiliumNetworkPolicy.from_resource(resource) } .map { |resource| Gitlab::Kubernetes::CiliumNetworkPolicy.from_resource(resource) }
ServiceResponse.success(payload: policies) [policies, nil]
rescue Kubeclient::HttpError => e rescue Kubeclient::HttpError => e
kubernetes_error_response(e) [[], e]
end
def has_deployment_platform?(kubeclient_info)
kubeclient_info.any? { |platform, namespace| platform.present? }
end
# rubocop: disable CodeReuse/ActiveRecord
def extract_info_for_kubeclient(project, environment_id)
kubernetes_namespaces =
if environment_id
Clusters::KubernetesNamespace.where(environment: project.environments.id_in(environment_id))
else
Clusters::KubernetesNamespace.where(environment: project.environments.available.at_most(LIMIT))
end
kubernetes_namespaces
.order(updated_at: :desc)
.preload(:platform_kubernetes)
.group_by(&:namespace)
.map { |namespace, kubernetes_namespaces| [kubernetes_namespaces.first.platform_kubernetes, namespace] }
end end
# rubocop: enable CodeReuse/ActiveRecord
end end
end end
...@@ -2,10 +2,11 @@ ...@@ -2,10 +2,11 @@
module NetworkPolicies module NetworkPolicies
module Responses module Responses
def kubernetes_error_response(error) def kubernetes_error_response(error, payload = {})
ServiceResponse.error( ServiceResponse.error(
http_status: :bad_request, http_status: :bad_request,
message: s_('NetworkPolicies|Kubernetes error: %{error}') % { error: error } message: s_('NetworkPolicies|Kubernetes error: %{error}') % { error: error },
payload: payload
) )
end end
......
---
title: Add support for multiple environments on the controller and service level
merge_request: 40529
author:
type: changed
...@@ -42,7 +42,7 @@ RSpec.describe Projects::Security::NetworkPoliciesController do ...@@ -42,7 +42,7 @@ RSpec.describe Projects::Security::NetworkPoliciesController do
subject subject
expect(response).to have_gitlab_http_status(:bad_request) expect(response).to have_gitlab_http_status(:bad_request)
expect(response.body).to eq('{"error":"error"}') expect(response.body).to eq('{"payload":{},"error":"error"}')
end end
end end
end end
...@@ -175,7 +175,7 @@ RSpec.describe Projects::Security::NetworkPoliciesController do ...@@ -175,7 +175,7 @@ RSpec.describe Projects::Security::NetworkPoliciesController do
before do before do
group.add_developer(user) group.add_developer(user)
allow(NetworkPolicies::ResourcesService).to receive(:new).with(environment: environment) { service } allow(NetworkPolicies::ResourcesService).to receive(:new).with(environment_id: environment.id.to_s, project: project) { service }
end end
it 'responds with policies' do it 'responds with policies' do
......
...@@ -3,9 +3,13 @@ ...@@ -3,9 +3,13 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe NetworkPolicies::ResourcesService do RSpec.describe NetworkPolicies::ResourcesService do
let(:service) { NetworkPolicies::ResourcesService.new(environment: environment) } let(:service) { NetworkPolicies::ResourcesService.new(environment_id: environment_id, project: project) }
let(:environment) { instance_double('Environment', deployment_platform: platform, deployment_namespace: 'namespace') } let(:environment) { create(:environment, project: project) }
let(:platform) { instance_double('Clusters::Platforms::Kubernetes', kubeclient: kubeclient) } let(:environment_id) { environment.id }
let(:project) { create(:project) }
let(:cluster) { create(:cluster, :instance) }
let!(:cluster_kubernetes_namespace) { create(:cluster_kubernetes_namespace, project: project, cluster: cluster, environment: environment, namespace: 'namespace') }
let(:platform) { double('Clusters::Platforms::Kubernetes', kubeclient: kubeclient) }
let(:kubeclient) { double('Kubeclient::Client') } let(:kubeclient) { double('Kubeclient::Client') }
let(:policy) do let(:policy) do
Gitlab::Kubernetes::NetworkPolicy.new( Gitlab::Kubernetes::NetworkPolicy.new(
...@@ -26,12 +30,16 @@ RSpec.describe NetworkPolicies::ResourcesService do ...@@ -26,12 +30,16 @@ RSpec.describe NetworkPolicies::ResourcesService do
) )
end end
before do
allow_any_instance_of(Clusters::KubernetesNamespace).to receive(:platform_kubernetes).and_return(platform)
end
describe '#execute' do describe '#execute' do
subject { service.execute } subject { service.execute }
it 'returns success response with policies from the deployment namespace' do it 'returns success response with policies from the deployment namespace' do
expect(kubeclient).to receive(:get_network_policies).with(namespace: environment.deployment_namespace) { [policy.generate] } expect(kubeclient).to receive(:get_network_policies).with(namespace: cluster_kubernetes_namespace.namespace) { [policy.generate] }
expect(kubeclient).to receive(:get_cilium_network_policies).with(namespace: environment.deployment_namespace) { [cilium_policy.generate] } expect(kubeclient).to receive(:get_cilium_network_policies).with(namespace: cluster_kubernetes_namespace.namespace) { [cilium_policy.generate] }
expect(subject).to be_success expect(subject).to be_success
expect(subject.payload.count).to eq(2) expect(subject.payload.count).to eq(2)
expect(subject.payload.first.as_json).to eq(policy.as_json) expect(subject.payload.first.as_json).to eq(policy.as_json)
...@@ -57,6 +65,52 @@ RSpec.describe NetworkPolicies::ResourcesService do ...@@ -57,6 +65,52 @@ RSpec.describe NetworkPolicies::ResourcesService do
expect(subject).to be_error expect(subject).to be_error
expect(subject.http_status).to eq(:bad_request) expect(subject.http_status).to eq(:bad_request)
expect(subject.message).not_to be_nil expect(subject.message).not_to be_nil
expect(subject.payload).to be_empty
end
end
context 'without environment_id' do
let(:environment_id) { nil }
let(:cluster_2) { create(:cluster, :project) }
let!(:cluster_kubernetes_namespace_2) { create(:cluster_kubernetes_namespace, project: project, cluster: cluster_2, environment: environment, namespace: 'namespace_2') }
let(:policy_2) do
Gitlab::Kubernetes::NetworkPolicy.new(
name: 'policy_2',
namespace: 'another_2',
selector: { matchLabels: { role: 'db' } },
ingress: [{ from: [{ namespaceSelector: { matchLabels: { project: 'myproject' } } }] }]
)
end
it 'returns success response with policies from two deployment namespaces', :aggregate_failures do
expect(kubeclient).to receive(:get_network_policies).with(namespace: cluster_kubernetes_namespace.namespace) { [policy.generate] }
expect(kubeclient).to receive(:get_cilium_network_policies).with(namespace: cluster_kubernetes_namespace.namespace) { [cilium_policy.generate] }
expect(kubeclient).to receive(:get_network_policies).with(namespace: cluster_kubernetes_namespace_2.namespace) { [policy_2.generate] }
expect(kubeclient).to receive(:get_cilium_network_policies).with(namespace: cluster_kubernetes_namespace_2.namespace) { [] }
expect(subject).to be_success
expect(subject.payload.count).to eq(3)
expect(subject.payload.map(&:as_json)).to include(policy.as_json, policy_2.as_json)
end
context 'with a partial successful response' do
let(:error_message) { 'system failure' }
before do
allow(kubeclient).to receive(:get_network_policies).with(namespace: cluster_kubernetes_namespace.namespace).and_return([policy.generate])
allow(kubeclient).to receive(:get_cilium_network_policies).with(namespace: cluster_kubernetes_namespace.namespace) { [] }
allow(kubeclient).to receive(:get_network_policies).with(namespace: cluster_kubernetes_namespace_2.namespace).and_raise(Kubeclient::HttpError.new(500, error_message, nil))
end
it 'returns error response for the platforms with failures' do
expect(subject).to be_error
expect(subject.message).to match(error_message)
end
it 'returns error response with the policies for all successful platforms' do
expect(subject).to be_error
expect(subject.payload.count).to eq(1)
expect(subject.payload.first.as_json).to eq(policy.as_json)
end
end end
end end
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