diff options
author | Grzegorz Bizon <grzegorz@gitlab.com> | 2019-04-05 10:05:32 +0300 |
---|---|---|
committer | Grzegorz Bizon <grzegorz@gitlab.com> | 2019-04-05 10:05:32 +0300 |
commit | ff648879642f81ccb5eda7c70b9738916a185630 (patch) | |
tree | 281ed755c60e4dbfa4f698540210aa39fb917af4 | |
parent | 07388b30b11f92e45da31d9a96a9a64dfd1a97c2 (diff) | |
parent | bd750af785037a105dc3347d3bd38cb49a003dc2 (diff) |
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
19 files changed, 255 insertions, 4 deletions
diff --git a/app/assets/javascripts/jobs/components/empty_state.vue b/app/assets/javascripts/jobs/components/empty_state.vue index 668fcf3d673..04f910b6b80 100644 --- a/app/assets/javascripts/jobs/components/empty_state.vue +++ b/app/assets/javascripts/jobs/components/empty_state.vue @@ -49,7 +49,7 @@ export default { <div class="text-content"> <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"> <gl-link diff --git a/app/assets/javascripts/jobs/components/job_app.vue b/app/assets/javascripts/jobs/components/job_app.vue index dbadd224251..0670e2b06b9 100644 --- a/app/assets/javascripts/jobs/components/job_app.vue +++ b/app/assets/javascripts/jobs/components/job_app.vue @@ -15,6 +15,7 @@ import ErasedBlock from './erased_block.vue'; import Log from './job_log.vue'; import LogTopBar from './job_log_controllers.vue'; import StuckBlock from './stuck_block.vue'; +import UnmetPrerequisitesBlock from './unmet_prerequisites_block.vue'; import Sidebar from './sidebar.vue'; import { sprintf } from '~/locale'; import delayedJobMixin from '../mixins/delayed_job_mixin'; @@ -32,6 +33,7 @@ export default { Log, LogTopBar, StuckBlock, + UnmetPrerequisitesBlock, Sidebar, GlLoadingIcon, SharedRunner: () => import('ee_component/jobs/components/shared_runner_limit_block.vue'), @@ -48,6 +50,11 @@ export default { required: false, default: null, }, + deploymentHelpUrl: { + type: String, + required: false, + default: null, + }, endpoint: { type: String, required: true, @@ -82,6 +89,7 @@ export default { ]), ...mapGetters([ 'headerTime', + 'hasUnmetPrerequisitesFailure', 'shouldRenderCalloutMessage', 'shouldRenderTriggeredLabel', 'hasEnvironment', @@ -210,7 +218,10 @@ export default { /> </div> - <callout v-if="shouldRenderCalloutMessage" :message="job.callout_message" /> + <callout + v-if="shouldRenderCalloutMessage && !hasUnmetPrerequisitesFailure" + :message="job.callout_message" + /> </header> <!-- EO Header Section --> @@ -223,6 +234,12 @@ export default { :runners-path="runnerSettingsUrl" /> + <unmet-prerequisites-block + v-if="hasUnmetPrerequisitesFailure" + class="js-job-failed" + :help-path="deploymentHelpUrl" + /> + <shared-runner v-if="shouldRenderSharedRunnerLimitWarning" class="js-shared-runner-limit" diff --git a/app/assets/javascripts/jobs/components/unmet_prerequisites_block.vue b/app/assets/javascripts/jobs/components/unmet_prerequisites_block.vue new file mode 100644 index 00000000000..25a8da84873 --- /dev/null +++ b/app/assets/javascripts/jobs/components/unmet_prerequisites_block.vue @@ -0,0 +1,30 @@ +<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> diff --git a/app/assets/javascripts/jobs/index.js b/app/assets/javascripts/jobs/index.js index a32e945627c..25132449458 100644 --- a/app/assets/javascripts/jobs/index.js +++ b/app/assets/javascripts/jobs/index.js @@ -12,6 +12,7 @@ export default () => { render(createElement) { return createElement('job-app', { props: { + deploymentHelpUrl: element.dataset.deploymentHelpUrl, runnerHelpUrl: element.dataset.runnerHelpUrl, runnerSettingsUrl: element.dataset.runnerSettingsUrl, endpoint: element.dataset.endpoint, diff --git a/app/assets/javascripts/jobs/store/getters.js b/app/assets/javascripts/jobs/store/getters.js index 73c1cbc3a99..406b1a2e375 100644 --- a/app/assets/javascripts/jobs/store/getters.js +++ b/app/assets/javascripts/jobs/store/getters.js @@ -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 hasUnmetPrerequisitesFailure = state => + state.job && state.job.failure_reason && state.job.failure_reason === 'unmet_prerequisites'; + export const shouldRenderCalloutMessage = state => !_.isEmpty(state.job.status) && !_.isEmpty(state.job.callout_message); diff --git a/app/assets/stylesheets/framework/callout.scss b/app/assets/stylesheets/framework/callout.scss index 0d8e4afa76f..643b20c56bc 100644 --- a/app/assets/stylesheets/framework/callout.scss +++ b/app/assets/stylesheets/framework/callout.scss @@ -28,6 +28,10 @@ background-color: $red-100; border-color: $red-200; color: $red-700; + + a { + color: $red-700; + } } .bs-callout-warning { diff --git a/app/serializers/build_details_entity.rb b/app/serializers/build_details_entity.rb index 9ddce0d2c80..62c26809eeb 100644 --- a/app/serializers/build_details_entity.rb +++ b/app/serializers/build_details_entity.rb @@ -45,6 +45,8 @@ class BuildDetailsEntity < JobEntity erase_project_job_path(project, build) end + expose :failure_reason, if: -> (*) { build.failed? } + expose :terminal_path, if: -> (*) { can_create_build_terminal? } do |build| terminal_project_job_path(project, build) end diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml index 475bae887ec..81a53f22f67 100644 --- a/app/views/projects/jobs/show.html.haml +++ b/app/views/projects/jobs/show.html.haml @@ -8,6 +8,7 @@ %div{ class: container_class } #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_settings_url: project_runners_path(@build.project, anchor: 'js-runners-settings'), build_options: javascript_build_options } } diff --git a/changelogs/unreleased/54506-show-error-when-namespace-svc-missing.yml b/changelogs/unreleased/54506-show-error-when-namespace-svc-missing.yml new file mode 100644 index 00000000000..3e3784d5413 --- /dev/null +++ b/changelogs/unreleased/54506-show-error-when-namespace-svc-missing.yml @@ -0,0 +1,5 @@ +--- +title: Show error when namespace/svc account missing +merge_request: 26362 +author: +type: added diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md index c94a3f4d3b5..878d30dddaa 100644 --- a/doc/user/project/clusters/index.md +++ b/doc/user/project/clusters/index.md @@ -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: ```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). diff --git a/lib/gitlab/ci/status/build/factory.rb b/lib/gitlab/ci/status/build/factory.rb index f7d0715e617..96d05842838 100644 --- a/lib/gitlab/ci/status/build/factory.rb +++ b/lib/gitlab/ci/status/build/factory.rb @@ -16,7 +16,8 @@ module Gitlab Status::Build::Skipped], [Status::Build::Cancelable, Status::Build::Retryable], - [Status::Build::Failed], + [Status::Build::FailedUnmetPrerequisites, + Status::Build::Failed], [Status::Build::FailedAllowed, Status::Build::Unschedule, Status::Build::Play, diff --git a/lib/gitlab/ci/status/build/failed_unmet_prerequisites.rb b/lib/gitlab/ci/status/build/failed_unmet_prerequisites.rb new file mode 100644 index 00000000000..eaad3969a4c --- /dev/null +++ b/lib/gitlab/ci/status/build/failed_unmet_prerequisites.rb @@ -0,0 +1,24 @@ +# 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 diff --git a/locale/gitlab.pot b/locale/gitlab.pot index c9c7cbce95a..63a4fe9c52b 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -3583,6 +3583,9 @@ msgstr "" msgid "Failed to check related branches." msgstr "" +msgid "Failed to create resources" +msgstr "" + msgid "Failed to deploy to" msgstr "" @@ -4610,6 +4613,9 @@ msgstr "" msgid "Job|The artifacts will be removed" 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." msgstr "" @@ -6879,6 +6885,9 @@ msgstr "" msgid "Retry this job" msgstr "" +msgid "Retry this job in order to create the necessary resources." +msgstr "" + msgid "Retry verification" msgstr "" diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb index 067391c1179..f8c494c159e 100644 --- a/spec/factories/ci/builds.rb +++ b/spec/factories/ci/builds.rb @@ -336,6 +336,11 @@ FactoryBot.define do failure_reason 2 end + trait :prerequisite_failure do + failed + failure_reason 10 + end + trait :with_runner_session do after(:build) do |build| build.build_runner_session(url: 'https://localhost') diff --git a/spec/javascripts/jobs/components/job_app_spec.js b/spec/javascripts/jobs/components/job_app_spec.js index ba5d672f189..cef40117304 100644 --- a/spec/javascripts/jobs/components/job_app_spec.js +++ b/spec/javascripts/jobs/components/job_app_spec.js @@ -17,6 +17,7 @@ describe('Job App ', () => { const props = { endpoint: `${gl.TEST_HOST}jobs/123.json`, runnerHelpUrl: 'help/runner', + deploymentHelpUrl: 'help/deployment', runnerSettingsUrl: 'settings/ci-cd/runners', terminalPath: 'jobs/123/terminal', pagePath: `${gl.TEST_HOST}jobs/123`, @@ -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', () => { it('renders environment block when job has environment', done => { mock.onGet(props.endpoint).replyOnce( diff --git a/spec/javascripts/jobs/components/unmet_prerequisites_block_spec.js b/spec/javascripts/jobs/components/unmet_prerequisites_block_spec.js new file mode 100644 index 00000000000..68fcb321214 --- /dev/null +++ b/spec/javascripts/jobs/components/unmet_prerequisites_block_spec.js @@ -0,0 +1,37 @@ +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); + }); +}); diff --git a/spec/lib/gitlab/ci/status/build/factory_spec.rb b/spec/lib/gitlab/ci/status/build/factory_spec.rb index b379b08ad62..b6231510b91 100644 --- a/spec/lib/gitlab/ci/status/build/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/build/factory_spec.rb @@ -123,6 +123,35 @@ describe Gitlab::Ci::Status::Build::Factory do expect(status.action_path).to include 'retry' 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 context 'when build is a canceled' do diff --git a/spec/lib/gitlab/ci/status/build/failed_unmet_prerequisites_spec.rb b/spec/lib/gitlab/ci/status/build/failed_unmet_prerequisites_spec.rb new file mode 100644 index 00000000000..a4854bdc6b9 --- /dev/null +++ b/spec/lib/gitlab/ci/status/build/failed_unmet_prerequisites_spec.rb @@ -0,0 +1,37 @@ +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 diff --git a/spec/serializers/build_details_entity_spec.rb b/spec/serializers/build_details_entity_spec.rb index f6bd6e9ede4..1edf69dc290 100644 --- a/spec/serializers/build_details_entity_spec.rb +++ b/spec/serializers/build_details_entity_spec.rb @@ -112,5 +112,15 @@ describe BuildDetailsEntity do expect(subject['merge_request_path']).to be_nil 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 |