From 7a27bec0ed76a2a6e9b51f0419e8ac663c6136d2 Mon Sep 17 00:00:00 2001 From: Miguel Rincon <mrincon@gitlab.com> Date: Wed, 26 Apr 2023 16:46:50 +0000 Subject: [PATCH] Support old registration token reset in projects This change adds the registration reset in a dropdown to the project CI/CD settings, to allow user to reset the runner registration token for their project. --- .../registration/registration_dropdown.vue | 30 +++++++++----- app/assets/javascripts/ci/runner/constants.js | 6 +++ .../runner/project_runners/register/index.js | 39 +++++++++++++++++++ .../projects/settings/ci_cd/show/index.js | 2 + .../projects/settings/ci_cd_controller.rb | 1 + .../runners/_project_runners.html.haml | 1 + spec/features/admin/admin_runners_spec.rb | 27 ++++++++++--- spec/features/runners_spec.rb | 17 ++++++-- .../registration_dropdown_spec.js | 37 +++++++++++++----- 9 files changed, 131 insertions(+), 29 deletions(-) create mode 100644 app/assets/javascripts/ci/runner/project_runners/register/index.js diff --git a/app/assets/javascripts/ci/runner/components/registration/registration_dropdown.vue b/app/assets/javascripts/ci/runner/components/registration/registration_dropdown.vue index c1e862f6fa88..2fdf8456615a 100644 --- a/app/assets/javascripts/ci/runner/components/registration/registration_dropdown.vue +++ b/app/assets/javascripts/ci/runner/components/registration/registration_dropdown.vue @@ -3,7 +3,15 @@ import { GlDropdown, GlDropdownForm, GlDropdownItem, GlDropdownDivider, GlIcon } import { s__ } from '~/locale'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import RunnerInstructionsModal from '~/vue_shared/components/runner_instructions/runner_instructions_modal.vue'; -import { INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE } from '../../constants'; +import { + INSTANCE_TYPE, + GROUP_TYPE, + PROJECT_TYPE, + I18N_REGISTER_INSTANCE_TYPE, + I18N_REGISTER_GROUP_TYPE, + I18N_REGISTER_PROJECT_TYPE, + I18N_REGISTER_RUNNER, +} from '../../constants'; import RegistrationToken from './registration_token.vue'; import RegistrationTokenResetDropdownItem from './registration_token_reset_dropdown_item.vue'; @@ -51,20 +59,23 @@ export default { this.glFeatures?.createRunnerWorkflowForNamespace ); }, - dropdownText() { - if (this.isDeprecated) { - return ''; - } + actionText() { switch (this.type) { case INSTANCE_TYPE: - return s__('Runners|Register an instance runner'); + return I18N_REGISTER_INSTANCE_TYPE; case GROUP_TYPE: - return s__('Runners|Register a group runner'); + return I18N_REGISTER_GROUP_TYPE; case PROJECT_TYPE: - return s__('Runners|Register a project runner'); + return I18N_REGISTER_PROJECT_TYPE; default: - return s__('Runners|Register a runner'); + return I18N_REGISTER_RUNNER; + } + }, + dropdownText() { + if (this.isDeprecated) { + return ''; } + return this.actionText; }, dropdownToggleClass() { if (this.isDeprecated) { @@ -109,6 +120,7 @@ export default { v-bind="$attrs" > <template v-if="isDeprecated" #button-content> + <span class="gl-sr-only">{{ actionText }}</span> <gl-icon name="ellipsis_v" /> </template> <gl-dropdown-form class="gl-p-4!"> diff --git a/app/assets/javascripts/ci/runner/constants.js b/app/assets/javascripts/ci/runner/constants.js index 84b2ed010e61..1225c0d75836 100644 --- a/app/assets/javascripts/ci/runner/constants.js +++ b/app/assets/javascripts/ci/runner/constants.js @@ -71,6 +71,12 @@ export const I18N_STALE_NEVER_CONTACTED_TOOLTIP = s__( 'Runners|Runner is stale; it has never contacted this instance', ); +// Registration dropdown +export const I18N_REGISTER_INSTANCE_TYPE = s__('Runners|Register an instance runner'); +export const I18N_REGISTER_GROUP_TYPE = s__('Runners|Register a group runner'); +export const I18N_REGISTER_PROJECT_TYPE = s__('Runners|Register a project runner'); +export const I18N_REGISTER_RUNNER = s__('Runners|Register a runner'); + // Actions export const I18N_EDIT = __('Edit'); diff --git a/app/assets/javascripts/ci/runner/project_runners/register/index.js b/app/assets/javascripts/ci/runner/project_runners/register/index.js new file mode 100644 index 000000000000..9986c93c9181 --- /dev/null +++ b/app/assets/javascripts/ci/runner/project_runners/register/index.js @@ -0,0 +1,39 @@ +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import createDefaultClient from '~/lib/graphql'; +import RegistrationDropdown from '~/ci/runner/components/registration/registration_dropdown.vue'; +import { PROJECT_TYPE } from '~/ci/runner/constants'; + +Vue.use(VueApollo); + +export const initProjectRunnersRegistrationDropdown = ( + selector = '#js-project-runner-registration-dropdown', +) => { + const el = document.querySelector(selector); + + if (!el) { + return null; + } + + const { registrationToken, projectId } = el.dataset; + + const apolloProvider = new VueApollo({ + defaultClient: createDefaultClient(), + }); + + return new Vue({ + el, + apolloProvider, + provide: { + projectId, + }, + render(h) { + return h(RegistrationDropdown, { + props: { + registrationToken, + type: PROJECT_TYPE, + }, + }); + }, + }); +}; diff --git a/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js b/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js index 9ec560154054..731b1373987b 100644 --- a/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js +++ b/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js @@ -12,6 +12,7 @@ import { initTokenAccess } from '~/token_access'; import { initCiSecureFiles } from '~/ci_secure_files'; import initDeployTokens from '~/deploy_tokens'; import { initProjectRunners } from '~/ci/runner/project_runners'; +import { initProjectRunnersRegistrationDropdown } from '~/ci/runner/project_runners/register'; // Initialize expandable settings panels initSettingsPanels(); @@ -42,6 +43,7 @@ initSettingsPipelinesTriggers(); initArtifactsSettings(); initProjectRunners(); +initProjectRunnersRegistrationDropdown(); initSharedRunnersToggle(); initRefSwitcherBadges(); initInstallRunner(); diff --git a/app/controllers/projects/settings/ci_cd_controller.rb b/app/controllers/projects/settings/ci_cd_controller.rb index 28ae730eda58..626587deb710 100644 --- a/app/controllers/projects/settings/ci_cd_controller.rb +++ b/app/controllers/projects/settings/ci_cd_controller.rb @@ -15,6 +15,7 @@ class CiCdController < Projects::ApplicationController before_action do push_frontend_feature_flag(:ci_variables_pages, current_user) push_frontend_feature_flag(:ci_limit_environment_scope, @project) + push_frontend_feature_flag(:create_runner_workflow_for_namespace, @project.namespace) end helper_method :highlight_badge diff --git a/app/views/projects/runners/_project_runners.html.haml b/app/views/projects/runners/_project_runners.html.haml index 9b401711ec5a..1d4e45c71b5b 100644 --- a/app/views/projects/runners/_project_runners.html.haml +++ b/app/views/projects/runners/_project_runners.html.haml @@ -7,6 +7,7 @@ - if can?(current_user, :create_runner, @project) = render Pajamas::ButtonComponent.new(href: new_project_runner_path(@project), variant: :confirm) do = s_('Runners|New project runner') + #js-project-runner-registration-dropdown{ data: { registration_token: @project.runners_token, project_id: @project.id } } - else = _('Please contact an admin to create runners.') = link_to _('Learn more.'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'restrict-runner-registration-by-all-users-in-an-instance'), target: '_blank', rel: 'noopener noreferrer' diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb index d82f9acdd074..582535790bdd 100644 --- a/spec/features/admin/admin_runners_spec.rb +++ b/spec/features/admin/admin_runners_spec.rb @@ -32,15 +32,30 @@ end describe "runners registration" do - before do - stub_feature_flags(create_runner_workflow_for_admin: false) + context 'when create_runner_workflow_for_namespace is enabled' do + before do + stub_feature_flags(create_runner_workflow_for_admin: true) - visit admin_runners_path + visit admin_runners_path + end + + it_behaves_like "shows and resets runner registration token" do + let(:dropdown_text) { s_('Runners|Register an instance runner') } + let(:registration_token) { Gitlab::CurrentSettings.runners_registration_token } + end end - it_behaves_like "shows and resets runner registration token" do - let(:dropdown_text) { s_('Runners|Register an instance runner') } - let(:registration_token) { Gitlab::CurrentSettings.runners_registration_token } + context 'when create_runner_workflow_for_namespace is disabled' do + before do + stub_feature_flags(create_runner_workflow_for_admin: false) + + visit admin_runners_path + end + + it_behaves_like "shows and resets runner registration token" do + let(:dropdown_text) { s_('Runners|Register an instance runner') } + let(:registration_token) { Gitlab::CurrentSettings.runners_registration_token } + end end end diff --git a/spec/features/runners_spec.rb b/spec/features/runners_spec.rb index 54482d141c71..2de95c21003c 100644 --- a/spec/features/runners_spec.rb +++ b/spec/features/runners_spec.rb @@ -21,19 +21,28 @@ project.add_maintainer(user) end - context 'when create_runner_workflow_for_namespace is enabled' do + context 'when create_runner_workflow_for_namespace is enabled', :js do before do stub_feature_flags(create_runner_workflow_for_namespace: [project.namespace]) - end - it 'user can see a link with instructions on how to install GitLab Runner' do visit project_runners_path(project) + end + it 'user can see a link with instructions on how to install GitLab Runner' do expect(page).to have_link(s_('Runners|New project runner'), href: new_project_runner_path(project)) end - describe 'runner registration', :js do + it_behaves_like "shows and resets runner registration token" do + let(:dropdown_text) { s_('Runners|Register a project runner') } + let(:registration_token) { project.runners_token } + end + end + + context 'when user views new runner page' do + context 'when create_runner_workflow_for_namespace is enabled', :js do before do + stub_feature_flags(create_runner_workflow_for_namespace: [project.namespace]) + visit new_project_runner_path(project) end diff --git a/spec/frontend/ci/runner/components/registration/registration_dropdown_spec.js b/spec/frontend/ci/runner/components/registration/registration_dropdown_spec.js index 9df7a974af38..e564cf49ca0d 100644 --- a/spec/frontend/ci/runner/components/registration/registration_dropdown_spec.js +++ b/spec/frontend/ci/runner/components/registration/registration_dropdown_spec.js @@ -12,7 +12,14 @@ import RegistrationDropdown from '~/ci/runner/components/registration/registrati import RegistrationToken from '~/ci/runner/components/registration/registration_token.vue'; import RegistrationTokenResetDropdownItem from '~/ci/runner/components/registration/registration_token_reset_dropdown_item.vue'; -import { INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE } from '~/ci/runner/constants'; +import { + INSTANCE_TYPE, + GROUP_TYPE, + PROJECT_TYPE, + I18N_REGISTER_INSTANCE_TYPE, + I18N_REGISTER_GROUP_TYPE, + I18N_REGISTER_PROJECT_TYPE, +} from '~/ci/runner/constants'; import getRunnerPlatformsQuery from '~/vue_shared/components/runner_instructions/graphql/get_runner_platforms.query.graphql'; import getRunnerSetupInstructionsQuery from '~/vue_shared/components/runner_instructions/graphql/get_runner_setup.query.graphql'; @@ -81,13 +88,13 @@ describe('RegistrationDropdown', () => { it.each` type | text - ${INSTANCE_TYPE} | ${s__('Runners|Register an instance runner')} - ${GROUP_TYPE} | ${s__('Runners|Register a group runner')} - ${PROJECT_TYPE} | ${s__('Runners|Register a project runner')} - `('Dropdown text for type $type is "$text"', () => { - createComponent({ props: { type: INSTANCE_TYPE } }, mountExtended); + ${INSTANCE_TYPE} | ${I18N_REGISTER_INSTANCE_TYPE} + ${GROUP_TYPE} | ${I18N_REGISTER_GROUP_TYPE} + ${PROJECT_TYPE} | ${I18N_REGISTER_PROJECT_TYPE} + `('Dropdown text for type $type is "$text"', ({ type, text }) => { + createComponent({ props: { type } }, mountExtended); - expect(wrapper.text()).toContain('Register an instance runner'); + expect(wrapper.text()).toContain(text); }); it('Passes attributes to dropdown', () => { @@ -214,7 +221,7 @@ describe('RegistrationDropdown', () => { { createRunnerWorkflowForAdmin: true }, { createRunnerWorkflowForNamespace: true }, ])('When showing a "deprecated" warning', (glFeatures) => { - it('Passes deprecated variant props and attributes to dropdown', () => { + it('passes deprecated variant props and attributes to dropdown', () => { createComponent({ provide: { glFeatures }, }); @@ -230,6 +237,17 @@ describe('RegistrationDropdown', () => { }); }); + it.each` + type | text + ${INSTANCE_TYPE} | ${I18N_REGISTER_INSTANCE_TYPE} + ${GROUP_TYPE} | ${I18N_REGISTER_GROUP_TYPE} + ${PROJECT_TYPE} | ${I18N_REGISTER_PROJECT_TYPE} + `('dropdown text for type $type is "$text"', ({ type, text }) => { + createComponent({ props: { type } }, mountExtended); + + expect(wrapper.text()).toContain(text); + }); + it('shows warning text', () => { createComponent( { @@ -243,7 +261,7 @@ describe('RegistrationDropdown', () => { expect(text.exists()).toBe(true); }); - it('button shows only ellipsis icon', () => { + it('button shows ellipsis icon', () => { createComponent( { provide: { glFeatures }, @@ -251,7 +269,6 @@ describe('RegistrationDropdown', () => { mountExtended, ); - expect(findDropdownBtn().text()).toBe(''); expect(findDropdownBtn().findComponent(GlIcon).props('name')).toBe('ellipsis_v'); expect(findDropdownBtn().findAllComponents(GlIcon)).toHaveLength(1); }); -- GitLab