Commit ff648879 authored by Grzegorz Bizon's avatar Grzegorz Bizon

Merge branch '54506-show-error-when-namespace-svc-missing' into 'master'

Show error when namespace/svc account missing

Closes #54506

See merge request gitlab-org/gitlab-ce!26362
parents 07388b30 bd750af7
...@@ -49,7 +49,7 @@ export default { ...@@ -49,7 +49,7 @@ export default {
<div class="text-content"> <div class="text-content">
<h4 class="js-job-empty-state-title text-center">{{ title }}</h4> <h4 class="js-job-empty-state-title text-center">{{ title }}</h4>
<p v-if="content" class="js-job-empty-state-content">{{ content }}</p> <p v-if="content" class="js-job-empty-state-content text-center">{{ content }}</p>
<div v-if="action" class="text-center"> <div v-if="action" class="text-center">
<gl-link <gl-link
......
...@@ -15,6 +15,7 @@ import ErasedBlock from './erased_block.vue'; ...@@ -15,6 +15,7 @@ import ErasedBlock from './erased_block.vue';
import Log from './job_log.vue'; import Log from './job_log.vue';
import LogTopBar from './job_log_controllers.vue'; import LogTopBar from './job_log_controllers.vue';
import StuckBlock from './stuck_block.vue'; import StuckBlock from './stuck_block.vue';
import UnmetPrerequisitesBlock from './unmet_prerequisites_block.vue';
import Sidebar from './sidebar.vue'; import Sidebar from './sidebar.vue';
import { sprintf } from '~/locale'; import { sprintf } from '~/locale';
import delayedJobMixin from '../mixins/delayed_job_mixin'; import delayedJobMixin from '../mixins/delayed_job_mixin';
...@@ -32,6 +33,7 @@ export default { ...@@ -32,6 +33,7 @@ export default {
Log, Log,
LogTopBar, LogTopBar,
StuckBlock, StuckBlock,
UnmetPrerequisitesBlock,
Sidebar, Sidebar,
GlLoadingIcon, GlLoadingIcon,
SharedRunner: () => import('ee_component/jobs/components/shared_runner_limit_block.vue'), SharedRunner: () => import('ee_component/jobs/components/shared_runner_limit_block.vue'),
...@@ -48,6 +50,11 @@ export default { ...@@ -48,6 +50,11 @@ export default {
required: false, required: false,
default: null, default: null,
}, },
deploymentHelpUrl: {
type: String,
required: false,
default: null,
},
endpoint: { endpoint: {
type: String, type: String,
required: true, required: true,
...@@ -82,6 +89,7 @@ export default { ...@@ -82,6 +89,7 @@ export default {
]), ]),
...mapGetters([ ...mapGetters([
'headerTime', 'headerTime',
'hasUnmetPrerequisitesFailure',
'shouldRenderCalloutMessage', 'shouldRenderCalloutMessage',
'shouldRenderTriggeredLabel', 'shouldRenderTriggeredLabel',
'hasEnvironment', 'hasEnvironment',
...@@ -210,7 +218,10 @@ export default { ...@@ -210,7 +218,10 @@ export default {
/> />
</div> </div>
<callout v-if="shouldRenderCalloutMessage" :message="job.callout_message" /> <callout
v-if="shouldRenderCalloutMessage && !hasUnmetPrerequisitesFailure"
:message="job.callout_message"
/>
</header> </header>
<!-- EO Header Section --> <!-- EO Header Section -->
...@@ -223,6 +234,12 @@ export default { ...@@ -223,6 +234,12 @@ export default {
:runners-path="runnerSettingsUrl" :runners-path="runnerSettingsUrl"
/> />
<unmet-prerequisites-block
v-if="hasUnmetPrerequisitesFailure"
class="js-job-failed"
:help-path="deploymentHelpUrl"
/>
<shared-runner <shared-runner
v-if="shouldRenderSharedRunnerLimitWarning" v-if="shouldRenderSharedRunnerLimitWarning"
class="js-shared-runner-limit" class="js-shared-runner-limit"
......
<script>
import { GlLink } from '@gitlab/ui';
/**
* Renders Unmet Prerequisites block for job's view.
*/
export default {
components: {
GlLink,
},
props: {
helpPath: {
type: String,
required: true,
},
},
};
</script>
<template>
<div class="bs-callout bs-callout-danger">
<p class="js-failed-unmet-prerequisites append-bottom-0">
{{
s__(`Job|This job failed because the necessary resources were not successfully created.`)
}}
<gl-link :href="helpPath" class="js-help-path">
<strong> {{ __('More information') }} </strong>
</gl-link>
</p>
</div>
</template>
...@@ -12,6 +12,7 @@ export default () => { ...@@ -12,6 +12,7 @@ export default () => {
render(createElement) { render(createElement) {
return createElement('job-app', { return createElement('job-app', {
props: { props: {
deploymentHelpUrl: element.dataset.deploymentHelpUrl,
runnerHelpUrl: element.dataset.runnerHelpUrl, runnerHelpUrl: element.dataset.runnerHelpUrl,
runnerSettingsUrl: element.dataset.runnerSettingsUrl, runnerSettingsUrl: element.dataset.runnerSettingsUrl,
endpoint: element.dataset.endpoint, endpoint: element.dataset.endpoint,
......
...@@ -3,6 +3,9 @@ import { isScrolledToBottom } from '~/lib/utils/scroll_utils'; ...@@ -3,6 +3,9 @@ import { isScrolledToBottom } from '~/lib/utils/scroll_utils';
export const headerTime = state => (state.job.started ? state.job.started : state.job.created_at); export const headerTime = state => (state.job.started ? state.job.started : state.job.created_at);
export const hasUnmetPrerequisitesFailure = state =>
state.job && state.job.failure_reason && state.job.failure_reason === 'unmet_prerequisites';
export const shouldRenderCalloutMessage = state => export const shouldRenderCalloutMessage = state =>
!_.isEmpty(state.job.status) && !_.isEmpty(state.job.callout_message); !_.isEmpty(state.job.status) && !_.isEmpty(state.job.callout_message);
......
...@@ -28,6 +28,10 @@ ...@@ -28,6 +28,10 @@
background-color: $red-100; background-color: $red-100;
border-color: $red-200; border-color: $red-200;
color: $red-700; color: $red-700;
a {
color: $red-700;
}
} }
.bs-callout-warning { .bs-callout-warning {
......
...@@ -45,6 +45,8 @@ class BuildDetailsEntity < JobEntity ...@@ -45,6 +45,8 @@ class BuildDetailsEntity < JobEntity
erase_project_job_path(project, build) erase_project_job_path(project, build)
end end
expose :failure_reason, if: -> (*) { build.failed? }
expose :terminal_path, if: -> (*) { can_create_build_terminal? } do |build| expose :terminal_path, if: -> (*) { can_create_build_terminal? } do |build|
terminal_project_job_path(project, build) terminal_project_job_path(project, build)
end end
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
%div{ class: container_class } %div{ class: container_class }
#js-job-vue-app{ data: { endpoint: project_job_path(@project, @build, format: :json), #js-job-vue-app{ data: { endpoint: project_job_path(@project, @build, format: :json),
deployment_help_url: help_page_path('user/project/clusters/index.html', anchor: 'troubleshooting-failed-deployment-jobs'),
runner_help_url: help_page_path('ci/runners/README.html', anchor: 'setting-maximum-job-timeout-for-a-runner'), runner_help_url: help_page_path('ci/runners/README.html', anchor: 'setting-maximum-job-timeout-for-a-runner'),
runner_settings_url: project_runners_path(@build.project, anchor: 'js-runners-settings'), runner_settings_url: project_runners_path(@build.project, anchor: 'js-runners-settings'),
build_options: javascript_build_options } } build_options: javascript_build_options } }
---
title: Show error when namespace/svc account missing
merge_request: 26362
author:
type: added
...@@ -570,7 +570,7 @@ deployment jobs, immediately before the jobs starts. ...@@ -570,7 +570,7 @@ deployment jobs, immediately before the jobs starts.
However, sometimes GitLab can not create them. In such instances, your job will fail with the message: However, sometimes GitLab can not create them. In such instances, your job will fail with the message:
```text ```text
The job failed to complete prerequisite tasks This job failed because the necessary resources were not successfully created.
``` ```
To find the cause of this error when creating a namespace and service account, check the [logs](../../../administration/logs.md#sidekiqlog). To find the cause of this error when creating a namespace and service account, check the [logs](../../../administration/logs.md#sidekiqlog).
......
...@@ -16,7 +16,8 @@ module Gitlab ...@@ -16,7 +16,8 @@ module Gitlab
Status::Build::Skipped], Status::Build::Skipped],
[Status::Build::Cancelable, [Status::Build::Cancelable,
Status::Build::Retryable], Status::Build::Retryable],
[Status::Build::Failed], [Status::Build::FailedUnmetPrerequisites,
Status::Build::Failed],
[Status::Build::FailedAllowed, [Status::Build::FailedAllowed,
Status::Build::Unschedule, Status::Build::Unschedule,
Status::Build::Play, Status::Build::Play,
......
# frozen_string_literal: true
module Gitlab
module Ci
module Status
module Build
class FailedUnmetPrerequisites < Status::Extended
def illustration
{
image: 'illustrations/pipelines_failed.svg',
size: 'svg-430',
title: _('Failed to create resources'),
content: _('Retry this job in order to create the necessary resources.')
}
end
def self.matches?(build, _)
build.unmet_prerequisites?
end
end
end
end
end
end
...@@ -3583,6 +3583,9 @@ msgstr "" ...@@ -3583,6 +3583,9 @@ msgstr ""
msgid "Failed to check related branches." msgid "Failed to check related branches."
msgstr "" msgstr ""
msgid "Failed to create resources"
msgstr ""
msgid "Failed to deploy to" msgid "Failed to deploy to"
msgstr "" msgstr ""
...@@ -4610,6 +4613,9 @@ msgstr "" ...@@ -4610,6 +4613,9 @@ msgstr ""
msgid "Job|The artifacts will be removed" msgid "Job|The artifacts will be removed"
msgstr "" msgstr ""
msgid "Job|This job failed because the necessary resources were not successfully created."
msgstr ""
msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it." msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
msgstr "" msgstr ""
...@@ -6879,6 +6885,9 @@ msgstr "" ...@@ -6879,6 +6885,9 @@ msgstr ""
msgid "Retry this job" msgid "Retry this job"
msgstr "" msgstr ""
msgid "Retry this job in order to create the necessary resources."
msgstr ""
msgid "Retry verification" msgid "Retry verification"
msgstr "" msgstr ""
......
...@@ -336,6 +336,11 @@ FactoryBot.define do ...@@ -336,6 +336,11 @@ FactoryBot.define do
failure_reason 2 failure_reason 2
end end
trait :prerequisite_failure do
failed
failure_reason 10
end
trait :with_runner_session do trait :with_runner_session do
after(:build) do |build| after(:build) do |build|
build.build_runner_session(url: 'https://localhost') build.build_runner_session(url: 'https://localhost')
......
...@@ -17,6 +17,7 @@ describe('Job App ', () => { ...@@ -17,6 +17,7 @@ describe('Job App ', () => {
const props = { const props = {
endpoint: `${gl.TEST_HOST}jobs/123.json`, endpoint: `${gl.TEST_HOST}jobs/123.json`,
runnerHelpUrl: 'help/runner', runnerHelpUrl: 'help/runner',
deploymentHelpUrl: 'help/deployment',
runnerSettingsUrl: 'settings/ci-cd/runners', runnerSettingsUrl: 'settings/ci-cd/runners',
terminalPath: 'jobs/123/terminal', terminalPath: 'jobs/123/terminal',
pagePath: `${gl.TEST_HOST}jobs/123`, pagePath: `${gl.TEST_HOST}jobs/123`,
...@@ -253,6 +254,41 @@ describe('Job App ', () => { ...@@ -253,6 +254,41 @@ describe('Job App ', () => {
}); });
}); });
describe('unmet prerequisites block', () => {
it('renders unmet prerequisites block when there is an unmet prerequisites failure', done => {
mock.onGet(props.endpoint).replyOnce(
200,
Object.assign({}, job, {
status: {
group: 'failed',
icon: 'status_failed',
label: 'failed',
text: 'failed',
details_path: 'path',
illustration: {
content: 'Retry this job in order to create the necessary resources.',
image: 'path',
size: 'svg-430',
title: 'Failed to create resources',
},
},
failure_reason: 'unmet_prerequisites',
has_trace: false,
runners: {
available: true,
},
tags: [],
}),
);
vm = mountComponentWithStore(Component, { props, store });
setTimeout(() => {
expect(vm.$el.querySelector('.js-job-failed')).not.toBeNull();
done();
}, 0);
});
});
describe('environments block', () => { describe('environments block', () => {
it('renders environment block when job has environment', done => { it('renders environment block when job has environment', done => {
mock.onGet(props.endpoint).replyOnce( mock.onGet(props.endpoint).replyOnce(
......
import Vue from 'vue';
import component from '~/jobs/components/unmet_prerequisites_block.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
describe('Unmet Prerequisites Block Job component', () => {
const Component = Vue.extend(component);
let vm;
const helpPath = '/user/project/clusters/index.html#troubleshooting-failed-deployment-jobs';
beforeEach(() => {
vm = mountComponent(Component, {
hasNoRunnersForProject: true,
helpPath,
});
});
afterEach(() => {
vm.$destroy();
});
it('renders an alert with the correct message', () => {
const container = vm.$el.querySelector('.js-failed-unmet-prerequisites');
const alertMessage =
'This job failed because the necessary resources were not successfully created.';
expect(container).not.toBeNull();
expect(container.innerHTML).toContain(alertMessage);
});
it('renders link to help page', () => {
const helpLink = vm.$el.querySelector('.js-help-path');
expect(helpLink).not.toBeNull();
expect(helpLink.innerHTML).toContain('More information');
expect(helpLink.getAttribute('href')).toEqual(helpPath);
});
});
...@@ -123,6 +123,35 @@ describe Gitlab::Ci::Status::Build::Factory do ...@@ -123,6 +123,35 @@ describe Gitlab::Ci::Status::Build::Factory do
expect(status.action_path).to include 'retry' expect(status.action_path).to include 'retry'
end end
end end
context 'when build has unmet prerequisites' do
let(:build) { create(:ci_build, :prerequisite_failure) }
it 'matches correct core status' do
expect(factory.core_status).to be_a Gitlab::Ci::Status::Failed
end
it 'matches correct extended statuses' do
expect(factory.extended_statuses)
.to eq [Gitlab::Ci::Status::Build::Retryable,
Gitlab::Ci::Status::Build::FailedUnmetPrerequisites]
end
it 'fabricates a failed with unmet prerequisites build status' do
expect(status).to be_a Gitlab::Ci::Status::Build::FailedUnmetPrerequisites
end
it 'fabricates status with correct details' do
expect(status.text).to eq 'failed'
expect(status.icon).to eq 'status_failed'
expect(status.favicon).to eq 'favicon_status_failed'
expect(status.label).to eq 'failed'
expect(status).to have_details
expect(status).to have_action
expect(status.action_title).to include 'Retry'
expect(status.action_path).to include 'retry'
end
end
end end
context 'when build is a canceled' do context 'when build is a canceled' do
......
require 'spec_helper'
RSpec.describe Gitlab::Ci::Status::Build::FailedUnmetPrerequisites do
describe '#illustration' do
subject { described_class.new(double).illustration }
it { is_expected.to include(:image, :size, :title, :content) }
end
describe '.matches?' do
let(:build) { create(:ci_build, :created) }
subject { described_class.matches?(build, double) }
context 'when build has not failed' do
it { is_expected.to be_falsey }
end
context 'when build has failed' do
before do
build.drop!(failure_reason)
end
context 'with unmet prerequisites' do
let(:failure_reason) { :unmet_prerequisites }
it { is_expected.to be_truthy }
end
context 'with a different error' do
let(:failure_reason) { :runner_system_failure }
it { is_expected.to be_falsey }
end
end
end
end
...@@ -112,5 +112,15 @@ describe BuildDetailsEntity do ...@@ -112,5 +112,15 @@ describe BuildDetailsEntity do
expect(subject['merge_request_path']).to be_nil expect(subject['merge_request_path']).to be_nil
end end
end end
context 'when the build has failed' do
let(:build) { create(:ci_build, :created) }
before do
build.drop!(:unmet_prerequisites)
end
it { is_expected.to include(failure_reason: 'unmet_prerequisites') }
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