diff --git a/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/components/packages_list_app.vue b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/components/packages_list_app.vue index 73a897ad7d566166f2a4dfd602ac8c25aff0a5b5..95e648bda3c694839ea0e79833e2cb1a308ba496 100644 --- a/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/components/packages_list_app.vue +++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/components/packages_list_app.vue @@ -24,10 +24,6 @@ export default { InfrastructureSearch, }, inject: { - emptyPageTitle: { - from: 'emptyPageTitle', - default: s__('PackageRegistry|There are no packages yet'), - }, noResultsText: { from: 'noResultsText', default: s__( @@ -40,6 +36,7 @@ export default { emptyListIllustration: (state) => state.config.emptyListIllustration, emptyListHelpUrl: (state) => state.config.emptyListHelpUrl, filter: (state) => state.filter, + isGroupPage: (state) => state.config.isGroupPage, selectedType: (state) => state.selectedType, packageHelpUrl: (state) => state.config.packageHelpUrl, packagesCount: (state) => state.pagination?.total, @@ -49,7 +46,11 @@ export default { this.filter.filter((f) => f.type !== FILTERED_SEARCH_TERM || f.value?.data).length === 0 ); }, - + emptyPageTitle() { + return this.isGroupPage + ? s__('InfrastructureRegistry|You have no Terraform modules in your group') + : s__('InfrastructureRegistry|You have no Terraform modules in your project'); + }, emptyStateTitle() { return this.emptySearch ? this.emptyPageTitle diff --git a/app/assets/javascripts/packages_and_registries/infrastructure_registry/list_app_bundle.js b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list_app_bundle.js index 1467218dd4170aeb2e15be16af527f7058029f31..f897d588a7a256733f7ea84f5b7213613c2c71a5 100644 --- a/app/assets/javascripts/packages_and_registries/infrastructure_registry/list_app_bundle.js +++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list_app_bundle.js @@ -18,7 +18,6 @@ export default () => { PackagesListApp, }, provide: { - emptyPageTitle: s__('InfrastructureRegistry|You have no Terraform modules in your project'), noResultsText: s__( 'InfrastructureRegistry|Terraform modules are the main way to package and reuse resource configurations with Terraform. Learn more about how to %{noPackagesLinkStart}create Terraform modules%{noPackagesLinkEnd} in GitLab.', ), diff --git a/app/assets/javascripts/pages/groups/infrastructure_registry/index/index.js b/app/assets/javascripts/pages/groups/infrastructure_registry/index/index.js new file mode 100644 index 0000000000000000000000000000000000000000..dfb750eca41ea039e98a896055c8ac90f8c61b88 --- /dev/null +++ b/app/assets/javascripts/pages/groups/infrastructure_registry/index/index.js @@ -0,0 +1,3 @@ +import initList from '~/packages_and_registries/infrastructure_registry/list_app_bundle'; + +initList(); diff --git a/app/controllers/groups/infrastructure_registry_controller.rb b/app/controllers/groups/infrastructure_registry_controller.rb new file mode 100644 index 0000000000000000000000000000000000000000..edd05900170416cfa8b6b3100fe0861b289a84e7 --- /dev/null +++ b/app/controllers/groups/infrastructure_registry_controller.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Groups + class InfrastructureRegistryController < Groups::ApplicationController + before_action :verify_packages_enabled! + + feature_category :package_registry + urgency :low + + private + + def verify_packages_enabled! + unless group.packages_feature_enabled? && + Feature.enabled?(:group_level_infrastructure_registry, group.root_ancestor, type: :gitlab_com_derisk) + render_404 + end + end + end +end diff --git a/app/views/groups/infrastructure_registry/index.html.haml b/app/views/groups/infrastructure_registry/index.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..8801dc249dce28e5675eb37bb692bb6bffc19737 --- /dev/null +++ b/app/views/groups/infrastructure_registry/index.html.haml @@ -0,0 +1,9 @@ +- page_title _("Infrastructure Registry") + +.row + .col-12 + #js-vue-packages-list{ data: { resource_id: @group.id, + page_type: 'groups', + empty_list_help_url: help_page_path('user/infrastructure/index'), + empty_list_illustration: image_path('illustrations/empty-state/empty-terraform-register-lg.svg'), + package_help_url: help_page_path('user/infrastructure/index') } } diff --git a/config/feature_flags/gitlab_com_derisk/group_level_infrastructure_registry.yml b/config/feature_flags/gitlab_com_derisk/group_level_infrastructure_registry.yml new file mode 100644 index 0000000000000000000000000000000000000000..0a0a7dfda520a7165d21a715fc4ba71955224091 --- /dev/null +++ b/config/feature_flags/gitlab_com_derisk/group_level_infrastructure_registry.yml @@ -0,0 +1,9 @@ +--- +name: group_level_infrastructure_registry +feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/352041 +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/140215 +rollout_issue_url: https://gitlab.com/gitlab-com/gl-infra/production/-/issues/17430 +milestone: '16.9' +group: group::package registry +type: gitlab_com_derisk +default_enabled: false diff --git a/config/routes/group.rb b/config/routes/group.rb index e1185b15d12c3b104879fd6a8075c9723927721b..115c6abeb5f5c1cc885ad6e57be7dac70fbd4ea2 100644 --- a/config/routes/group.rb +++ b/config/routes/group.rb @@ -82,6 +82,8 @@ resources :packages, only: [:index, :show] + resources :infrastructure_registry, only: [:index] + resources :milestones, constraints: { id: %r{[^/]+} } do member do get :issues diff --git a/ee/spec/features/groups/navbar_spec.rb b/ee/spec/features/groups/navbar_spec.rb index 2dcfe53c66af45d68a3b75226980b226e5cd9f70..d2c5b9035de20d2d6f3e0f878a1d72b6393aa8a5 100644 --- a/ee/spec/features/groups/navbar_spec.rb +++ b/ee/spec/features/groups/navbar_spec.rb @@ -16,10 +16,12 @@ before do group.add_maintainer(user) stub_group_wikis(false) + stub_config(registry: { enabled: false }) sign_in(user) create_package_nav(_('Operate')) insert_after_nav_item(_('Analyze'), new_nav_item: settings_for_maintainer_nav_item) + insert_infrastructure_registry_nav(_('Kubernetes')) end context 'when devops adoption analytics is available' do @@ -98,7 +100,7 @@ context 'when packages are available' do before do - stub_config(packages: { enabled: true }, registry: { enabled: false }) + stub_config(packages: { enabled: true }) visit group_path(group) end @@ -173,7 +175,7 @@ before do group.update!(harbor_integration: harbor_integration) - insert_harbor_registry_nav(_('Kubernetes')) + insert_harbor_registry_nav(_('Terraform modules')) visit group_path(group) end @@ -185,10 +187,12 @@ context 'for owners', :saas do before do group.add_owner(user) + stub_config(registry: { enabled: false }) stub_group_wikis(false) stub_licensed_features(domain_verification: true) sign_in(user) create_package_nav(_('Operate')) + insert_infrastructure_registry_nav(_('Kubernetes')) end describe 'structure' do diff --git a/ee/spec/features/projects/navbar_spec.rb b/ee/spec/features/projects/navbar_spec.rb index 0a02f43713cc07f798cdf7e8453d9ae0e2ddc27e..7bfcab7e7f3ea6ef27fb4e2aa8e4117551457ebf 100644 --- a/ee/spec/features/projects/navbar_spec.rb +++ b/ee/spec/features/projects/navbar_spec.rb @@ -20,7 +20,7 @@ stub_feature_flags(model_registry: false) stub_feature_flags(remove_monitor_metrics: false) insert_package_nav - insert_infrastructure_registry_nav + insert_infrastructure_registry_nav(s_('Terraform|Terraform states')) insert_infrastructure_google_cloud_nav insert_infrastructure_aws_nav end diff --git a/lib/sidebars/groups/menus/packages_registries_menu.rb b/lib/sidebars/groups/menus/packages_registries_menu.rb index 19a6e210231249ac16c7619d3671ee2c5dc32ff8..0e762559fe2a527e88f15297f93a91841ff6e65e 100644 --- a/lib/sidebars/groups/menus/packages_registries_menu.rb +++ b/lib/sidebars/groups/menus/packages_registries_menu.rb @@ -8,6 +8,7 @@ class PackagesRegistriesMenu < ::Sidebars::Menu def configure_menu_items add_item(packages_registry_menu_item) add_item(container_registry_menu_item) + add_item(infrastructure_registry_menu_item) add_item(harbor_registry_menu_item) add_item(dependency_proxy_menu_item) true @@ -56,6 +57,21 @@ def container_registry_menu_item ) end + def infrastructure_registry_menu_item + unless context.group.packages_feature_enabled? && + Feature.enabled?(:group_level_infrastructure_registry, context.group.root_ancestor, type: :gitlab_com_derisk) + return nil_menu_item(:infrastructure_registry) + end + + ::Sidebars::MenuItem.new( + title: _('Terraform modules'), + link: group_infrastructure_registry_index_path(context.group), + super_sidebar_parent: Sidebars::Groups::SuperSidebarMenus::OperationsMenu, + active_routes: { controller: :infrastructure_registry }, + item_id: :infrastructure_registry + ) + end + def harbor_registry_menu_item if context.group.harbor_integration.nil? || !context.group.harbor_integration.activated? diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 876c38f712654ec05863bbf7bc1438ae151de018..0f416c3e355d04e0857048efb77f58e09dd76fc7 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -25932,6 +25932,9 @@ msgstr "" msgid "InfrastructureRegistry|To authorize access to the Terraform registry:" msgstr "" +msgid "InfrastructureRegistry|You have no Terraform modules in your group" +msgstr "" + msgid "InfrastructureRegistry|You have no Terraform modules in your project" msgstr "" diff --git a/spec/features/groups/infrastructure_registry_spec.rb b/spec/features/groups/infrastructure_registry_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..f9ed2bca68ff579557272758d8d2cd60704c9128 --- /dev/null +++ b/spec/features/groups/infrastructure_registry_spec.rb @@ -0,0 +1,130 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Infrastructure Registry', feature_category: :package_registry do + let_it_be(:user) { create(:user) } + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, :public, group: group) } + + context 'when user is not signed in' do + before do + visit_group_infrastructure_registry + end + + it 'returns 200' do + expect(status_code).to eq(200) + end + + context 'when there are modules' do + let_it_be(:terraform_module) do + create(:terraform_module_package, project: project, created_at: 1.day.ago, version: '1.0.0') + end + + it 'does not allow you to delete package', :js do + expect(page).to have_button('Remove package', disabled: true) + end + end + end + + context 'when user is signed in' do + before do + sign_in(user) + end + + context 'when user is not a group member' do + before do + visit_group_infrastructure_registry + end + + it 'returns 200' do + expect(status_code).to eq(200) + end + + context 'when there are modules' do + let_it_be(:terraform_module) do + create(:terraform_module_package, project: project, created_at: 1.day.ago, version: '1.0.0') + end + + it 'does not allow you to delete package', :js do + expect(page).to have_button('Remove package', disabled: true) + end + end + end + + context 'when user is a group member' do + before_all do + group.add_maintainer(user) + end + + context 'when packages registry is not enabled' do + before do + stub_config(packages: { enabled: false }) + end + + it 'returns 404' do + visit_group_infrastructure_registry + + expect(status_code).to eq(404) + end + end + + context 'when packages registry is enabled', :js do + before do + visit_group_infrastructure_registry + end + + context 'when there are modules' do + let_it_be(:terraform_module) do + create(:terraform_module_package, project: project, created_at: 1.day.ago, version: '1.0.0') + end + + context 'and there is more than one' do + let_it_be(:terraform_module2) do + create(:terraform_module_package, project: project, created_at: 2.days.ago, version: '2.0.0') + end + + let_it_be(:packages) { [terraform_module, terraform_module2] } + + it_behaves_like 'packages list' + end + + describe 'details link' do + it 'navigates to the correct url' do + page.within(packages_table_selector) do + click_link terraform_module.name + end + + expect(page).to have_current_path(project_infrastructure_registry_path(terraform_module.project, + terraform_module)) + + expect(page).to have_css('.packages-app h2[data-testid="title"]', text: terraform_module.name) + + expect(page).to have_content('Provision instructions') + expect(page).to have_content('Registry setup') + end + end + + context 'when deleting a package' do + it 'allows you to delete a module', :aggregate_failures do + # this is still using the package copy in the UI too + click_button('Remove package') + click_button('Permanently delete') + + expect(page).to have_content 'Package deleted successfully' + expect(page).not_to have_content(terraform_module.name) + end + end + end + + it 'displays the empty message' do + expect(page).to have_content('You have no Terraform modules in your group') + end + end + end + end + + def visit_group_infrastructure_registry + visit group_infrastructure_registry_index_path(group) + end +end diff --git a/spec/features/groups/navbar_spec.rb b/spec/features/groups/navbar_spec.rb index 7d5cc704f9c8a413a6c581a9197e2b593968160e..e9cdacf82557d48b43dd6dfc63e2f0dccc737649 100644 --- a/spec/features/groups/navbar_spec.rb +++ b/spec/features/groups/navbar_spec.rb @@ -15,6 +15,7 @@ before do create_package_nav(_('Operate')) insert_after_nav_item(_('Analyze'), new_nav_item: settings_for_maintainer_nav_item) if Gitlab.ee? + insert_infrastructure_registry_nav(_('Kubernetes')) stub_config(dependency_proxy: { enabled: false }) stub_config(registry: { enabled: false }) @@ -85,7 +86,7 @@ before do group.update!(harbor_integration: harbor_integration) - insert_harbor_registry_nav(_('Kubernetes')) + insert_harbor_registry_nav(_('Terraform modules')) visit group_path(group) end diff --git a/spec/features/projects/navbar_spec.rb b/spec/features/projects/navbar_spec.rb index 9babe3c3b71e48525bfa224994b40c5ddd940186..b63c8e72e850458e2ea7be3b446539a8a328f352 100644 --- a/spec/features/projects/navbar_spec.rb +++ b/spec/features/projects/navbar_spec.rb @@ -18,7 +18,7 @@ stub_feature_flags(ml_experiment_tracking: false) stub_feature_flags(model_registry: false) insert_package_nav - insert_infrastructure_registry_nav + insert_infrastructure_registry_nav(s_('Terraform|Terraform states')) insert_infrastructure_google_cloud_nav insert_infrastructure_aws_nav end diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/__snapshots__/packages_list_app_spec.js.snap b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/__snapshots__/packages_list_app_spec.js.snap index 009d9c959cb5d419fafcf2d9a9c4c0b4056c2c1c..6af5dd49f63f7130f627bb5dab47a4f1fb63780b 100644 --- a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/__snapshots__/packages_list_app_spec.js.snap +++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/__snapshots__/packages_list_app_spec.js.snap @@ -28,7 +28,7 @@ exports[`packages_list_app renders 1`] = ` <h1 class="gl-font-size-h-display gl-line-height-36 gl-mb-0 gl-mt-0 h4" > - There are no packages yet + You have no Terraform modules in your project </h1> <p class="gl-mb-0 gl-mt-4" diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_app_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_app_spec.js index eb905fbec400084f0084c5bf78dac80c9c341a57..c477c070ad0e90e59b670c2daad0b02ffe46813f 100644 --- a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_app_spec.js +++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_app_spec.js @@ -34,7 +34,7 @@ describe('packages_list_app', () => { const findListComponent = () => wrapper.findComponent(PackageList); const findInfrastructureSearch = () => wrapper.findComponent(InfrastructureSearch); - const createStore = ({ filter = [], packageCount = 0 } = {}) => { + const createStore = ({ isGroupPage = false, filter = [], packageCount = 0 } = {}) => { store = new Vuex.Store({ state: { isLoading: false, @@ -43,6 +43,7 @@ describe('packages_list_app', () => { emptyListIllustration: 'helpSvg', emptyListHelpUrl, packageHelpUrl: 'foo', + isGroupPage, }, filter, pagination: { @@ -53,7 +54,7 @@ describe('packages_list_app', () => { store.dispatch = jest.fn(); }; - const mountComponent = (provide) => { + const mountComponent = () => { wrapper = shallowMount(PackageListApp, { store, stubs: { @@ -63,7 +64,6 @@ describe('packages_list_app', () => { GlSprintf, GlLink, }, - provide, }); }; @@ -145,6 +145,8 @@ describe('packages_list_app', () => { }); describe('empty state', () => { + const heading = () => findEmptyState().find('h1'); + it('generate the correct empty list link', () => { const link = findListComponent().findComponent(GlLink); @@ -153,9 +155,18 @@ describe('packages_list_app', () => { }); it('includes the right content on the default tab', () => { - const heading = findEmptyState().find('h1'); + expect(heading().text()).toBe('You have no Terraform modules in your project'); + }); - expect(heading.text()).toBe('There are no packages yet'); + describe('when group page', () => { + beforeEach(() => { + createStore({ isGroupPage: true }); + mountComponent(); + }); + + it('includes the right content', () => { + expect(heading().text()).toBe('You have no Terraform modules in your group'); + }); }); }); diff --git a/spec/lib/sidebars/groups/menus/packages_registries_menu_spec.rb b/spec/lib/sidebars/groups/menus/packages_registries_menu_spec.rb index 713e22e2e76805ec357c625bf72bb370158ad007..1e680f08e16242af7653ce25a0d6271fd7299d04 100644 --- a/spec/lib/sidebars/groups/menus/packages_registries_menu_spec.rb +++ b/spec/lib/sidebars/groups/menus/packages_registries_menu_spec.rb @@ -183,6 +183,42 @@ it_behaves_like 'the menu entry is not available' end end + + describe 'Infrastructure Registry' do + let(:item_id) { :infrastructure_registry } + + context 'when user can read packages' do + before do + stub_config(packages: { enabled: packages_enabled }) + end + + context 'when config package setting is disabled' do + let(:packages_enabled) { false } + + it_behaves_like 'the menu entry is not available' + end + + context 'when config package setting is enabled' do + let(:packages_enabled) { true } + + it_behaves_like 'the menu entry is available' + + context 'when feature flag is disabled' do + before do + stub_feature_flags(group_level_infrastructure_registry: false) + end + + it_behaves_like 'the menu entry is not available' + end + end + end + + context 'when user cannot read packages' do + let(:user) { nil } + + it_behaves_like 'the menu entry is available' + end + end end private diff --git a/spec/requests/groups/infrastructure_registry_controller_spec.rb b/spec/requests/groups/infrastructure_registry_controller_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..f3935ebb7087e073cc4c67d3b83e2c441544db0f --- /dev/null +++ b/spec/requests/groups/infrastructure_registry_controller_spec.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Groups::InfrastructureRegistryController, feature_category: :package_registry do + let_it_be(:user) { create(:user) } + let_it_be(:group) { create(:group, :private) } + + describe 'GET #index' do + subject { get group_infrastructure_registry_index_path(group) } + + context 'when user is not signed in' do + it { is_expected.to redirect_to(new_user_session_path) } + end + + context 'when user is signed in' do + before do + sign_in(user) + end + + context 'when user is not a group member' do + it_behaves_like 'returning response status', :not_found + end + + context 'when user is group maintainer' do + before_all do + group.add_maintainer(user) + end + + it_behaves_like 'returning response status', :ok + + context 'when the packages registry is not available' do + before do + stub_config(packages: { enabled: false }) + end + + it_behaves_like 'returning response status', :not_found + end + + context 'when feature flag is disabled' do + before do + stub_feature_flags(group_level_infrastructure_registry: false) + end + + it_behaves_like 'returning response status', :not_found + end + end + end + end +end diff --git a/spec/support/helpers/navbar_structure_helper.rb b/spec/support/helpers/navbar_structure_helper.rb index 2d3f7a1b8a9a0095c433f8c01f542ec2310c7e9f..e4b30df063fa43d63e8a387cb336f84b672d586b 100644 --- a/spec/support/helpers/navbar_structure_helper.rb +++ b/spec/support/helpers/navbar_structure_helper.rb @@ -92,9 +92,9 @@ def insert_dependency_proxy_nav ) end - def insert_infrastructure_registry_nav + def insert_infrastructure_registry_nav(within) insert_after_sub_nav_item( - s_('Terraform|Terraform states'), + within, within: _('Operate'), new_sub_nav_item_name: _('Terraform modules') )