Commit ff0feee5 authored by rpereira2's avatar rpereira2

Use Stepable in PodLogsService

- This will simplify the service and make it easier to modify in the
future.
parent a7c66cac
...@@ -11,15 +11,15 @@ module Stepable ...@@ -11,15 +11,15 @@ module Stepable
initial_result = {} initial_result = {}
steps.inject(initial_result) do |previous_result, callback| steps.inject(initial_result) do |previous_result, callback|
result = method(callback).call result = method(callback).call(previous_result)
if result[:status] == :error if result[:status] != :success
result[:failed_step] = callback result[:last_step] = callback
break result break result
end end
previous_result.merge(result) result
end end
end end
......
...@@ -20,7 +20,7 @@ module EE ...@@ -20,7 +20,7 @@ module EE
result = PodLogsService.new(environment, params: params.permit!).execute result = PodLogsService.new(environment, params: params.permit!).execute
if result.nil? if result[:status] == :processing
head :accepted head :accepted
elsif result[:status] == :success elsif result[:status] == :success
render json: { render json: {
......
# frozen_string_literal: true # frozen_string_literal: true
class PodLogsService < ::BaseService class PodLogsService < ::BaseService
include Stepable
attr_reader :environment attr_reader :environment
K8S_NAME_MAX_LENGTH = 253 K8S_NAME_MAX_LENGTH = 253
PARAMS = %w(pod_name container_name).freeze PARAMS = %w(pod_name container_name).freeze
SUCCESS_RETURN_KEYS = [:status, :logs].freeze
steps :check_param_lengths,
:check_deployment_platform,
:check_pod_name,
:pod_logs,
:split_logs,
:filter_return_keys
def initialize(environment, params: {}) def initialize(environment, params: {})
@environment = environment @environment = environment
@params = filter_params(params.dup).to_hash @params = filter_params(params.dup).to_hash
end end
def execute def execute
execute_steps
end
private
def check_param_lengths(_result)
pod_name = params['pod_name'].presence pod_name = params['pod_name'].presence
container_name = params['container_name'].presence container_name = params['container_name'].presence
...@@ -24,34 +41,54 @@ class PodLogsService < ::BaseService ...@@ -24,34 +41,54 @@ class PodLogsService < ::BaseService
' %{max_length} chars' % { max_length: K8S_NAME_MAX_LENGTH })) ' %{max_length} chars' % { max_length: K8S_NAME_MAX_LENGTH }))
end end
success(pod_name: pod_name, container_name: container_name)
end
def check_deployment_platform(result)
unless environment.deployment_platform unless environment.deployment_platform
return error('No deployment platform') return error(_('No deployment platform available'))
end end
success(result)
end
def check_pod_name(result)
# If pod_name is not received as parameter, get the pod logs of the first # If pod_name is not received as parameter, get the pod logs of the first
# pod of this environment. # pod of this environment.
pod_name ||= environment.pod_names&.first result[:pod_name] ||= environment.pod_names&.first
pod_logs(pod_name, container_name) unless result[:pod_name]
return error(_('No pods available'))
end end
private success(result)
end
def pod_logs(pod_name, container_name) def pod_logs(result)
result = environment.deployment_platform.read_pod_logs( response = environment.deployment_platform.read_pod_logs(
pod_name, result[:pod_name],
namespace, namespace,
container: container_name container: result[:container_name]
) )
return unless result return { status: :processing } unless response
result[:logs] = response[:logs]
if result[:status] == :error if response[:status] == :error
error(result[:error]) error(response[:error])
else else
success(result)
end
end
def split_logs(result)
logs = split_by_newline(result[:logs]) logs = split_by_newline(result[:logs])
success(logs: logs) success(logs: logs)
end end
def filter_return_keys(result)
result.slice(*SUCCESS_RETURN_KEYS)
end end
def filter_params(params) def filter_params(params)
......
...@@ -170,8 +170,8 @@ describe Projects::EnvironmentsController do ...@@ -170,8 +170,8 @@ describe Projects::EnvironmentsController do
it_behaves_like 'resource not found', 'Pod not found' it_behaves_like 'resource not found', 'Pod not found'
end end
context 'when service returns nil' do context 'when service returns status processing' do
let(:service_result) { nil } let(:service_result) { { status: :processing } }
it 'renders accepted' do it 'renders accepted' do
get :logs, params: environment_params(pod_name: pod_name, format: :json) get :logs, params: environment_params(pod_name: pod_name, format: :json)
......
...@@ -79,7 +79,7 @@ describe PodLogsService do ...@@ -79,7 +79,7 @@ describe PodLogsService do
end end
context 'without deployment platform' do context 'without deployment platform' do
it_behaves_like 'error', 'No deployment platform' it_behaves_like 'error', 'No deployment platform available'
end end
context 'with deployment platform' do context 'with deployment platform' do
...@@ -137,6 +137,18 @@ describe PodLogsService do ...@@ -137,6 +137,18 @@ describe PodLogsService do
subject.execute subject.execute
end end
context 'when there are no pods' do
before do
allow_any_instance_of(Gitlab::Kubernetes::RolloutStatus).to receive(:instances)
.and_return([])
end
it 'returns error' do
expect(result[:status]).to eq(:error)
expect(result[:message]).to eq('No pods available')
end
end
end end
context 'when error is returned' do context 'when error is returned' do
...@@ -152,8 +164,8 @@ describe PodLogsService do ...@@ -152,8 +164,8 @@ describe PodLogsService do
.and_return(nil) .and_return(nil)
end end
it 'returns nil' do it 'returns processing' do
expect(result).to eq(nil) expect(result).to eq(status: :processing, last_step: :pod_logs)
end end
end end
end end
......
...@@ -33,7 +33,7 @@ module Gitlab ...@@ -33,7 +33,7 @@ module Gitlab
if result[:status] == :success if result[:status] == :success
result result
elsif STEPS_ALLOWED_TO_FAIL.include?(result[:failed_step]) elsif STEPS_ALLOWED_TO_FAIL.include?(result[:last_step])
success success
else else
raise StandardError, result[:message] raise StandardError, result[:message]
...@@ -42,121 +42,124 @@ module Gitlab ...@@ -42,121 +42,124 @@ module Gitlab
private private
def validate_application_settings def validate_application_settings(_result)
return success if application_settings return success if application_settings
log_error('No application_settings found') log_error('No application_settings found')
error(_('No application_settings found')) error(_('No application_settings found'))
end end
def validate_project_created def validate_project_created(result)
return success unless project_created? return success(result) unless project_created?
log_error('Project already created') log_error('Project already created')
error(_('Project already created')) error(_('Project already created'))
end end
def validate_admins def validate_admins(result)
unless instance_admins.any? unless instance_admins.any?
log_error('No active admin user found') log_error('No active admin user found')
return error(_('No active admin user found')) return error(_('No active admin user found'))
end end
success success(result)
end end
def create_group def create_group(result)
if project_created? if project_created?
log_info(_('Instance administrators group already exists')) log_info(_('Instance administrators group already exists'))
@group = application_settings.instance_administration_project.owner result[:group] = application_settings.instance_administration_project.owner
return success(group: @group) return success(result)
end end
@group = ::Groups::CreateService.new(group_owner, create_group_params).execute result[:group] = ::Groups::CreateService.new(group_owner, create_group_params).execute
if @group.persisted? if result[:group].persisted?
success(group: @group) success(result)
else else
error(_('Could not create group')) error(_('Could not create group'))
end end
end end
def create_project def create_project(result)
if project_created? if project_created?
log_info('Instance administration project already exists') log_info('Instance administration project already exists')
@project = application_settings.instance_administration_project result[:project] = application_settings.instance_administration_project
return success(project: project) return success(result)
end end
@project = ::Projects::CreateService.new(group_owner, create_project_params).execute result[:project] = ::Projects::CreateService.new(group_owner, create_project_params(result[:group])).execute
if project.persisted? if result[:project].persisted?
success(project: project) success(result)
else else
log_error("Could not create instance administration project. Errors: %{errors}" % { errors: project.errors.full_messages }) log_error("Could not create instance administration project. Errors: %{errors}" % { errors: result[:project].errors.full_messages })
error(_('Could not create project')) error(_('Could not create project'))
end end
end end
def save_project_id def save_project_id(result)
return success if project_created? return success if project_created?
result = application_settings.update(instance_administration_project_id: @project.id) response = application_settings.update(
instance_administration_project_id: result[:project].id
)
if result if response
success success(result)
else else
log_error("Could not save instance administration project ID, errors: %{errors}" % { errors: application_settings.errors.full_messages }) log_error("Could not save instance administration project ID, errors: %{errors}" % { errors: application_settings.errors.full_messages })
error(_('Could not save project ID')) error(_('Could not save project ID'))
end end
end end
def add_group_members def add_group_members(result)
members = @group.add_users(members_to_add, Gitlab::Access::MAINTAINER) group = result[:group]
members = group.add_users(members_to_add(group), Gitlab::Access::MAINTAINER)
errors = members.flat_map { |member| member.errors.full_messages } errors = members.flat_map { |member| member.errors.full_messages }
if errors.any? if errors.any?
log_error('Could not add admins as members to self-monitoring project. Errors: %{errors}' % { errors: errors }) log_error('Could not add admins as members to self-monitoring project. Errors: %{errors}' % { errors: errors })
error(_('Could not add admins as members')) error(_('Could not add admins as members'))
else else
success success(result)
end end
end end
def add_to_whitelist def add_to_whitelist(result)
return success unless prometheus_enabled? return success(result) unless prometheus_enabled?
return success unless prometheus_listen_address.present? return success(result) unless prometheus_listen_address.present?
uri = parse_url(internal_prometheus_listen_address_uri) uri = parse_url(internal_prometheus_listen_address_uri)
return error(_('Prometheus listen_address in config/gitlab.yml is not a valid URI')) unless uri return error(_('Prometheus listen_address in config/gitlab.yml is not a valid URI')) unless uri
application_settings.add_to_outbound_local_requests_whitelist([uri.normalized_host]) application_settings.add_to_outbound_local_requests_whitelist([uri.normalized_host])
result = application_settings.save response = application_settings.save
if result if response
# Expire the Gitlab::CurrentSettings cache after updating the whitelist. # Expire the Gitlab::CurrentSettings cache after updating the whitelist.
# This happens automatically in an after_commit hook, but in migrations, # This happens automatically in an after_commit hook, but in migrations,
# the after_commit hook only runs at the end of the migration. # the after_commit hook only runs at the end of the migration.
Gitlab::CurrentSettings.expire_current_application_settings Gitlab::CurrentSettings.expire_current_application_settings
success success(result)
else else
log_error("Could not add prometheus URL to whitelist, errors: %{errors}" % { errors: application_settings.errors.full_messages }) log_error("Could not add prometheus URL to whitelist, errors: %{errors}" % { errors: application_settings.errors.full_messages })
error(_('Could not add prometheus URL to whitelist')) error(_('Could not add prometheus URL to whitelist'))
end end
end end
def add_prometheus_manual_configuration def add_prometheus_manual_configuration(result)
return success unless prometheus_enabled? return success(result) unless prometheus_enabled?
return success unless prometheus_listen_address.present? return success(result) unless prometheus_listen_address.present?
service = project.find_or_initialize_service('prometheus') service = result[:project].find_or_initialize_service('prometheus')
unless service.update(prometheus_service_attributes) unless service.update(prometheus_service_attributes)
log_error('Could not save prometheus manual configuration for self-monitoring project. Errors: %{errors}' % { errors: service.errors.full_messages }) log_error('Could not save prometheus manual configuration for self-monitoring project. Errors: %{errors}' % { errors: service.errors.full_messages })
return error(_('Could not save prometheus manual configuration')) return error(_('Could not save prometheus manual configuration'))
end end
success success(result)
end end
def application_settings def application_settings
...@@ -196,11 +199,11 @@ module Gitlab ...@@ -196,11 +199,11 @@ module Gitlab
instance_admins.first instance_admins.first
end end
def members_to_add def members_to_add(group)
# Exclude admins who are already members of group because # Exclude admins who are already members of group because
# `@group.add_users(users)` returns an error if the users parameter contains # `group.add_users(users)` returns an error if the users parameter contains
# users who are already members of the group. # users who are already members of the group.
instance_admins - @group.members.collect(&:user) instance_admins - group.members.collect(&:user)
end end
def create_group_params def create_group_params
...@@ -217,13 +220,13 @@ module Gitlab ...@@ -217,13 +220,13 @@ module Gitlab
) )
end end
def create_project_params def create_project_params(group)
{ {
initialize_with_readme: true, initialize_with_readme: true,
visibility_level: VISIBILITY_LEVEL, visibility_level: VISIBILITY_LEVEL,
name: PROJECT_NAME, name: PROJECT_NAME,
description: "This project is automatically generated and will be used to help monitor this GitLab instance. [More information](#{docs_path})", description: "This project is automatically generated and will be used to help monitor this GitLab instance. [More information](#{docs_path})",
namespace_id: @group.id namespace_id: group.id
} }
end end
......
...@@ -10896,6 +10896,9 @@ msgstr "" ...@@ -10896,6 +10896,9 @@ msgstr ""
msgid "No data to display" msgid "No data to display"
msgstr "" msgstr ""
msgid "No deployment platform available"
msgstr ""
msgid "No deployments found" msgid "No deployments found"
msgstr "" msgstr ""
...@@ -10962,6 +10965,9 @@ msgstr "" ...@@ -10962,6 +10965,9 @@ msgstr ""
msgid "No parent group" msgid "No parent group"
msgstr "" msgstr ""
msgid "No pods available"
msgstr ""
msgid "No preview for this file type" msgid "No preview for this file type"
msgstr "" msgstr ""
......
...@@ -7,6 +7,8 @@ describe Stepable do ...@@ -7,6 +7,8 @@ describe Stepable do
Class.new do Class.new do
include Stepable include Stepable
attr_writer :return_non_success
steps :method1, :method2, :method3 steps :method1, :method2, :method3
def execute def execute
...@@ -15,18 +17,18 @@ describe Stepable do ...@@ -15,18 +17,18 @@ describe Stepable do
private private
def method1 def method1(_result)
{ status: :success } { status: :success }
end end
def method2 def method2(result)
return { status: :error } unless @pass return { status: :not_a_success } if @return_non_success
{ status: :success, variable1: 'var1' } result.merge({ status: :success, variable1: 'var1', excluded_variable: 'a' })
end end
def method3 def method3(result)
{ status: :success, variable2: 'var2' } result.except(:excluded_variable).merge({ status: :success, variable2: 'var2' })
end end
end end
end end
...@@ -41,8 +43,8 @@ describe Stepable do ...@@ -41,8 +43,8 @@ describe Stepable do
private private
def appended_method1 def appended_method1(previous_result)
{ status: :success } previous_result.merge({ status: :success })
end end
end end
end end
...@@ -51,21 +53,19 @@ describe Stepable do ...@@ -51,21 +53,19 @@ describe Stepable do
described_class.prepend(prepended_module) described_class.prepend(prepended_module)
end end
it 'stops after the first error' do it 'stops after the first non success status' do
subject.return_non_success = true
expect(subject).not_to receive(:method3) expect(subject).not_to receive(:method3)
expect(subject).not_to receive(:appended_method1) expect(subject).not_to receive(:appended_method1)
expect(subject.execute).to eq( expect(subject.execute).to eq(
status: :error, status: :not_a_success,
failed_step: :method2 last_step: :method2
) )
end end
context 'when all methods return success' do context 'when all methods return success' do
before do
subject.instance_variable_set(:@pass, true)
end
it 'calls all methods in order' do it 'calls all methods in order' do
expect(subject).to receive(:method1).and_call_original.ordered expect(subject).to receive(:method1).and_call_original.ordered
expect(subject).to receive(:method2).and_call_original.ordered expect(subject).to receive(:method2).and_call_original.ordered
...@@ -82,6 +82,10 @@ describe Stepable do ...@@ -82,6 +82,10 @@ describe Stepable do
variable2: 'var2' variable2: 'var2'
) )
end end
it 'can modify results of previous steps' do
expect(subject.execute).not_to include(excluded_variable: 'a')
end
end end
context 'with multiple stepable classes' do context 'with multiple stepable classes' do
......
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