diff --git a/app/assets/javascripts/integrations/edit/index.js b/app/assets/javascripts/integrations/edit/index.js index 4df120820d84afce92eae0365c959d39867b69c2..d24b961a989547778efe3c472094d945f38427b2 100644 --- a/app/assets/javascripts/integrations/edit/index.js +++ b/app/assets/javascripts/integrations/edit/index.js @@ -40,6 +40,7 @@ function parseDatasetToProps(data) { artifactRegistryPath, personalAccessTokensPath, workloadIdentityFederationPath, + wlifIssuer, redirectTo, upgradeSlackUrl, ...booleanAttributes @@ -104,6 +105,7 @@ function parseDatasetToProps(data) { integrationLevel, id: parseInt(id, 10), projectId: parseInt(projectId, 10), + wlifIssuer, redirectTo, shouldUpgradeSlack, upgradeSlackUrl, diff --git a/ee/app/assets/javascripts/integrations/edit/components/google_cloud_iam/manual_setup.vue b/ee/app/assets/javascripts/integrations/edit/components/google_cloud_iam/manual_setup.vue index 48f99bc5149a0cf92044131ab4a7c3c75d391026..938de9a22c2463a51f4a8fb0c1b367fe2aa76ec0 100644 --- a/ee/app/assets/javascripts/integrations/edit/components/google_cloud_iam/manual_setup.vue +++ b/ee/app/assets/javascripts/integrations/edit/components/google_cloud_iam/manual_setup.vue @@ -1,6 +1,10 @@ <script> import { GlIcon, GlLink, GlSprintf } from '@gitlab/ui'; +import { helpPagePath } from '~/helpers/help_page_helper'; +import { getBaseURL, joinPaths } from '~/lib/utils/url_utility'; + import InviteMembersTrigger from '~/invite_members/components/invite_members_trigger.vue'; +import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import { STATE_GUIDED } from './constants'; export default { @@ -9,8 +13,21 @@ export default { GlLink, GlSprintf, InviteMembersTrigger, + ClipboardButton, + }, + props: { + wlifIssuer: { + type: String, + required: true, + }, }, STATE_GUIDED, + helpURL: joinPaths( + getBaseURL(), + helpPagePath('integration/google_cloud_iam', { + anchor: 'with-the-google-cloud-cli', + }), + ), }; </script> @@ -63,13 +80,27 @@ export default { </li> <ol type="a"> <li> - {{ s__('GoogleCloud|The setup instructions page') }} - </li> - <li> - {{ s__('GoogleCloud|Your GitLab project ID') }} + <gl-link :href="$options.helpURL">{{ s__('GoogleCloud|Setup instructions') }}</gl-link> + <clipboard-button + :text="$options.helpURL" + :title="s__('GoogleCloud|Copy instructions URL')" + category="tertiary" + size="small" + /> </li> <li> - {{ s__('GoogleCloud|Your workload identify federation (WLIF) issuer URL.') }} + <gl-sprintf :message="s__('GoogleCloud|Your identity provider issuer: %{issuer}')"> + <template #issuer> + <code>{{ wlifIssuer }}</code> + </template> + > + </gl-sprintf> + <clipboard-button + :text="wlifIssuer" + :title="s__('GoogleCloud|Copy issuer')" + category="tertiary" + size="small" + /> </li> </ol> <li> diff --git a/ee/app/assets/javascripts/integrations/edit/components/sections/google_cloud_iam.vue b/ee/app/assets/javascripts/integrations/edit/components/sections/google_cloud_iam.vue index de067fc6ae2e501bf2801e9a606ec607df7bd259..b2fdf09fa86ccc655eff88e0f02380c9d70afed9 100644 --- a/ee/app/assets/javascripts/integrations/edit/components/sections/google_cloud_iam.vue +++ b/ee/app/assets/javascripts/integrations/edit/components/sections/google_cloud_iam.vue @@ -28,6 +28,9 @@ export default { }, computed: { ...mapGetters(['propsSource']), + wlifIssuer() { + return this.propsSource.wlifIssuer; + }, dynamicFields() { return this.propsSource.fields; }, @@ -64,7 +67,11 @@ export default { <div aria-live="polite"> <empty-state v-if="show === $options.STATE_EMPTY" @show="onShow" /> <guided-setup v-else-if="show === $options.STATE_GUIDED" @show="onShow" /> - <manual-setup v-else-if="show === $options.STATE_MANUAL" @show="onShow" /> + <manual-setup + v-else-if="show === $options.STATE_MANUAL" + :wlif-issuer="wlifIssuer" + @show="onShow" + /> <gc-iam-form v-if="isEditable" :fields="dynamicFields" /> </div> diff --git a/ee/app/helpers/ee/integrations_helper.rb b/ee/app/helpers/ee/integrations_helper.rb index 9cd08c49207146971000ef87337236ceef01ec44..40e1aed55b069831537b7115febd8a07026a9105 100644 --- a/ee/app/helpers/ee/integrations_helper.rb +++ b/ee/app/helpers/ee/integrations_helper.rb @@ -42,6 +42,10 @@ def integration_form_data(integration, project: nil, group: nil) end end + if integration.is_a?(::Integrations::GoogleCloudPlatform::WorkloadIdentityFederation) + form_data[:wlif_issuer] = ::Integrations::GoogleCloudPlatform::WorkloadIdentityFederation.wlif_issuer_url(group || project) + end + form_data end diff --git a/ee/spec/frontend/integrations/edit/components/google_cloud_iam/manual_setup_spec.js b/ee/spec/frontend/integrations/edit/components/google_cloud_iam/manual_setup_spec.js index 7aace69f03d84ad33a3f9b1598a87f79a0420eda..bf835a79c75b2ef7c151d76b2ad4646953094133 100644 --- a/ee/spec/frontend/integrations/edit/components/google_cloud_iam/manual_setup_spec.js +++ b/ee/spec/frontend/integrations/edit/components/google_cloud_iam/manual_setup_spec.js @@ -2,14 +2,23 @@ import { GlLink, GlSprintf } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import { STATE_GUIDED } from 'ee/integrations/edit/components/google_cloud_iam/constants'; import ManualSetup from 'ee/integrations/edit/components/google_cloud_iam/manual_setup.vue'; +import { getBaseURL } from '~/lib/utils/url_utility'; +import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; describe('ManualSetup', () => { + const wlifIssuer = 'https://test.com'; let wrapper; const createComponent = () => { - wrapper = shallowMount(ManualSetup, { stubs: { GlSprintf } }); + wrapper = shallowMount(ManualSetup, { + propsData: { + wlifIssuer, + }, + stubs: { GlSprintf }, + }); }; - const findFirstLink = () => wrapper.findAllComponents(GlLink).at(0); + const findLinks = () => wrapper.findAllComponents(GlLink); + const findClipboardButtons = () => wrapper.findAllComponents(ClipboardButton); beforeEach(() => { createComponent(); @@ -19,7 +28,7 @@ describe('ManualSetup', () => { let switchLink; beforeEach(() => { - switchLink = findFirstLink(); + switchLink = findLinks().at(0); }); it('renders link', () => { @@ -35,4 +44,15 @@ describe('ManualSetup', () => { expect(wrapper.emitted().show[0]).toContain(STATE_GUIDED); }); }); + + it('renders links to help doc page and corresponding clipboard button', () => { + const helpPath = `${getBaseURL()}/help/integration/google_cloud_iam#with-the-google-cloud-cli`; + expect(findLinks().at(2).attributes('href')).toBe(helpPath); + expect(findClipboardButtons().at(0).props('text')).toBe(helpPath); + }); + + it('show the workload identity federation provider issuer and corresponding clipboard button', () => { + expect(wrapper.text()).toContain(wlifIssuer); + expect(findClipboardButtons().at(1).props('text')).toBe(wlifIssuer); + }); }); diff --git a/ee/spec/frontend/integrations/edit/components/sections/google_cloud_iam_spec.js b/ee/spec/frontend/integrations/edit/components/sections/google_cloud_iam_spec.js index 648a3ee7d8577a5ee3f10ad4c0eaffbbfd5ae774..14d4a91d8702c636d7bf6eef316fc09b8ed97760 100644 --- a/ee/spec/frontend/integrations/edit/components/sections/google_cloud_iam_spec.js +++ b/ee/spec/frontend/integrations/edit/components/sections/google_cloud_iam_spec.js @@ -13,12 +13,14 @@ import ManualSetup from 'ee/integrations/edit/components/google_cloud_iam/manual import { createStore } from '~/integrations/edit/store'; describe('IntegrationSectionGoogleCloudIAM', () => { + const wlifIssuer = 'https://test.com'; let wrapper; const createComponent = ({ fields = [] } = {}) => { const store = createStore({ customState: { fields, + wlifIssuer, }, }); @@ -84,4 +86,12 @@ describe('IntegrationSectionGoogleCloudIAM', () => { }, ); }); + + it('pass `wlifIssuer` prop to ManualSetup component', async () => { + createComponent(); + findEmptyState().vm.$emit('show', STATE_MANUAL); + await nextTick(); + + expect(findManualSetup().props('wlifIssuer')).toBe(wlifIssuer); + }); }); diff --git a/ee/spec/helpers/ee/integrations_helper_spec.rb b/ee/spec/helpers/ee/integrations_helper_spec.rb index d438c3f2e48f7f95b513cf1fe38f6862a7216955..35c022dd4f7719b9fc9d236e8ec9eee6ab9c58b0 100644 --- a/ee/spec/helpers/ee/integrations_helper_spec.rb +++ b/ee/spec/helpers/ee/integrations_helper_spec.rb @@ -7,100 +7,126 @@ let_it_be_with_refind(:project) { create(:project, group: group) } describe '#integration_form_data' do - let(:integration) { build(:jenkins_integration) } - - let(:jira_fields) do - { - show_jira_issues_integration: 'false', - show_jira_vulnerabilities_integration: 'false', - enable_jira_issues: 'true', - enable_jira_vulnerabilities: 'false', - project_key: 'FE', - project_keys: 'FE,BE', - vulnerabilities_issuetype: '10001' - } - end + context 'when integration is at the project level' do + let(:integration) { build(:jenkins_integration) } + + let(:jira_fields) do + { + show_jira_issues_integration: 'false', + show_jira_vulnerabilities_integration: 'false', + enable_jira_issues: 'true', + enable_jira_vulnerabilities: 'false', + project_key: 'FE', + project_keys: 'FE,BE', + vulnerabilities_issuetype: '10001' + } + end - subject(:form_data) { helper.integration_form_data(integration, project: project) } + subject(:form_data) { helper.integration_form_data(integration, project: project) } - it 'does not include Jira-specific fields' do - is_expected.not_to include(*jira_fields.keys) - end + it 'does not include Jira-specific fields' do + is_expected.not_to include(*jira_fields.keys) + end + + context 'with a Jira integration' do + let_it_be_with_refind(:integration) { create(:jira_integration, project: project, issues_enabled: true, project_key: 'FE', project_keys: %w[FE BE], vulnerabilities_enabled: true, vulnerabilities_issuetype: '10001') } - context 'with a Jira integration' do - let_it_be_with_refind(:integration) { create(:jira_integration, project: project, issues_enabled: true, project_key: 'FE', project_keys: %w[FE BE], vulnerabilities_enabled: true, vulnerabilities_issuetype: '10001') } + context 'when there is no license for jira_vulnerabilities_integration' do + before do + allow(integration).to receive(:jira_vulnerabilities_integration_available?).and_return(false) + end - context 'when there is no license for jira_vulnerabilities_integration' do - before do - allow(integration).to receive(:jira_vulnerabilities_integration_available?).and_return(false) + it 'includes default Jira fields' do + is_expected.to include(jira_fields) + end end - it 'includes default Jira fields' do - is_expected.to include(jira_fields) + context 'when all flags are enabled' do + before do + stub_licensed_features(jira_issues_integration: true, jira_vulnerabilities_integration: true) + end + + it 'includes all Jira fields' do + is_expected.to include( + jira_fields.merge( + show_jira_issues_integration: 'true', + show_jira_vulnerabilities_integration: 'true', + enable_jira_vulnerabilities: 'true' + ) + ) + end end end - context 'when all flags are enabled' do - before do - stub_licensed_features(jira_issues_integration: true, jira_vulnerabilities_integration: true) + context 'with Google Artifact Registry integration' do + let_it_be_with_refind(:integration) { create(:google_cloud_platform_artifact_registry_integration, project: project) } + + shared_examples 'excludes wlif related fields' do + it 'is editable' do + is_expected.to include(editable: 'true') + end + + it 'does not include wlif related fields' do + is_expected.not_to include( + workload_identity_federation_path: edit_project_settings_integration_path(project, :google_cloud_platform_workload_identity_federation) + ) + end end - it 'includes all Jira fields' do - is_expected.to include( - jira_fields.merge( - show_jira_issues_integration: 'true', - show_jira_vulnerabilities_integration: 'true', - enable_jira_vulnerabilities: 'true' + context 'when Google Cloud IAM integration does not exist' do + it 'includes Google Artifact Registry fields' do + is_expected.to include( + artifact_registry_path: project_google_cloud_artifact_registry_index_path(project), + personal_access_tokens_path: user_settings_personal_access_tokens_path, + operating: 'true', + editable: 'false', + workload_identity_federation_path: edit_project_settings_integration_path(project, :google_cloud_platform_workload_identity_federation) ) - ) + end end - end - end - context 'with Google Artifact Registry integration' do - let_it_be_with_refind(:integration) { create(:google_cloud_platform_artifact_registry_integration, project: project) } + context 'with active Google Cloud IAM integration' do + before do + create(:google_cloud_platform_workload_identity_federation_integration, project: project) + end - shared_examples 'excludes wlif related fields' do - it 'is editable' do - is_expected.to include(editable: 'true') + it_behaves_like 'excludes wlif related fields' end - it 'does not include wlif related fields' do - is_expected.not_to include( - workload_identity_federation_path: edit_project_settings_integration_path(project, :google_cloud_platform_workload_identity_federation) - ) + context 'with inactive Google Cloud IAM integration' do + before do + create(:google_cloud_platform_workload_identity_federation_integration, project: project, active: false) + end + + it 'includes Google Artifact Registry fields' do + is_expected.to include( + editable: 'false', + workload_identity_federation_path: edit_project_settings_integration_path(project, :google_cloud_platform_workload_identity_federation) + ) + end end end - context 'when Google Cloud IAM integration does not exist' do - it 'includes Google Artifact Registry fields' do + context 'with Google Cloud IAM integration' do + let_it_be_with_refind(:integration) { create(:google_cloud_platform_workload_identity_federation_integration, project: project) } + + it 'include wlif_issuer field' do is_expected.to include( - artifact_registry_path: project_google_cloud_artifact_registry_index_path(project), - personal_access_tokens_path: user_settings_personal_access_tokens_path, - operating: 'true', - editable: 'false', - workload_identity_federation_path: edit_project_settings_integration_path(project, :google_cloud_platform_workload_identity_federation) + wlif_issuer: ::Integrations::GoogleCloudPlatform::WorkloadIdentityFederation.wlif_issuer_url(project) ) end end + end - context 'with active Google Cloud IAM integration' do - before do - create(:google_cloud_platform_workload_identity_federation_integration, project: project) - end + context 'when integration is at the group level' do + let(:integration) { build(:google_cloud_platform_workload_identity_federation_integration, group: group) } - it_behaves_like 'excludes wlif related fields' - end - - context 'with inactive Google Cloud IAM integration at project level' do - before do - create(:google_cloud_platform_workload_identity_federation_integration, project: project, active: false) - end + subject(:form_data) { helper.integration_form_data(integration, group: group) } - it 'includes Google Artifact Registry fields' do + context 'with Google Cloud IAM integration' do + it 'include wlif_issuer field' do is_expected.to include( - editable: 'false', - workload_identity_federation_path: edit_project_settings_integration_path(project, :google_cloud_platform_workload_identity_federation) + wlif_issuer: ::Integrations::GoogleCloudPlatform::WorkloadIdentityFederation.wlif_issuer_url(group) ) end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 4ceece01805de6b61dd1f344dff21193bbe4811f..16e24808b5182917e9557d00ec813241a2c17fca 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -24227,6 +24227,12 @@ msgstr "" msgid "GoogleCloud|Connect to Google Cloud with workload identity federation. Select %{strongStart}Guided setup%{strongEnd} if you can manage workload identity federation in Google Cloud. %{linkStart}What are the required permissions?%{linkEnd}" msgstr "" +msgid "GoogleCloud|Copy instructions URL" +msgstr "" + +msgid "GoogleCloud|Copy issuer" +msgstr "" + msgid "GoogleCloud|Create service account" msgstr "" @@ -24341,6 +24347,9 @@ msgstr "" msgid "GoogleCloud|Run the following command to set up and connect to your Google Cloud project with workload identity federation." msgstr "" +msgid "GoogleCloud|Setup instructions" +msgstr "" + msgid "GoogleCloud|Share the following information with someone that can manage Google Cloud workload identity federation. Or %{linkStart}invite them%{linkEnd} to set up." msgstr "" @@ -24353,9 +24362,6 @@ msgstr "" msgid "GoogleCloud|The google_cloud_support feature is not available" msgstr "" -msgid "GoogleCloud|The setup instructions page" -msgstr "" - msgid "GoogleCloud|To improve security, use a dedicated project for resources, separate from CI/CD and identity management projects. %{link_start}Where’s my project ID? %{icon}%{link_end}" msgstr "" @@ -24365,10 +24371,7 @@ msgstr "" msgid "GoogleCloud|You might be prompted to sign in to Google." msgstr "" -msgid "GoogleCloud|Your GitLab project ID" -msgstr "" - -msgid "GoogleCloud|Your workload identify federation (WLIF) issuer URL." +msgid "GoogleCloud|Your identity provider issuer: %{issuer}" msgstr "" msgid "GooglePlayStore|Protected branches and tags only"