diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-10-23 03:08:30 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-10-23 03:08:30 +0300 |
commit | e6dd804c902b030e2178be0c9ec5c38b58bd2b14 (patch) | |
tree | 9cabaea51989befe4d8c2e65ebac11afdd5dc493 | |
parent | e5356e229f5a1181d1a49091d6dc611db68bfffb (diff) |
Add latest changes from gitlab-org/gitlab@master
35 files changed, 290 insertions, 254 deletions
diff --git a/app/assets/javascripts/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue b/app/assets/javascripts/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue index d403f370f9d..2858561e033 100644 --- a/app/assets/javascripts/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue +++ b/app/assets/javascripts/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue @@ -1,15 +1,12 @@ <script> import { createNamespacedHelpers, mapState, mapActions, mapGetters } from 'vuex'; -import { GlFormInput, GlFormCheckbox, GlIcon, GlLink, GlSprintf } from '@gitlab/ui'; +import { GlFormGroup, GlFormInput, GlFormCheckbox, GlIcon, GlLink, GlSprintf } from '@gitlab/ui'; import { s__ } from '~/locale'; import ClusterFormDropdown from '~/create_cluster/components/cluster_form_dropdown.vue'; import { KUBERNETES_VERSIONS } from '../constants'; import LoadingButton from '~/vue_shared/components/loading_button.vue'; const { mapState: mapRolesState, mapActions: mapRolesActions } = createNamespacedHelpers('roles'); -const { mapState: mapRegionsState, mapActions: mapRegionsActions } = createNamespacedHelpers( - 'regions', -); const { mapState: mapKeyPairsState, mapActions: mapKeyPairsActions } = createNamespacedHelpers( 'keyPairs', ); @@ -27,6 +24,7 @@ export default { components: { ClusterFormDropdown, GlFormCheckbox, + GlFormGroup, GlFormInput, GlIcon, GlLink, @@ -60,11 +58,10 @@ export default { ), roleDropdownHelpPath: 'https://docs.aws.amazon.com/eks/latest/userguide/service_IAM_role.html#create-service-role', - regionsDropdownHelpText: s__( - 'ClusterIntegration|Learn more about %{linkStart}Regions%{linkEnd}.', + regionInputLabel: s__('ClusterIntegration|Cluster Region'), + regionHelpText: s__( + 'ClusterIntegration|The region the new cluster will be created in. You must reauthenticate to change regions.', ), - regionsDropdownHelpPath: - 'https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services/', keyPairDropdownHelpText: s__( 'ClusterIntegration|Select the key pair name that will be used to create EC2 nodes. To use a new key pair name, first create one on %{linkStart}Amazon Web Services%{linkEnd}.', ), @@ -117,11 +114,6 @@ export default { isLoadingRoles: 'isLoadingItems', loadingRolesError: 'loadingItemsError', }), - ...mapRegionsState({ - regions: 'items', - isLoadingRegions: 'isLoadingItems', - loadingRegionsError: 'loadingItemsError', - }), ...mapKeyPairsState({ keyPairs: 'items', isLoadingKeyPairs: 'isLoadingItems', @@ -195,8 +187,8 @@ export default { }, }, mounted() { - this.fetchRegions(); this.fetchRoles(); + this.setRegionAndFetchVpcsAndKeyPairs(); }, methods: { ...mapActions([ @@ -215,20 +207,18 @@ export default { 'setGitlabManagedCluster', 'setNamespacePerEnvironment', ]), - ...mapRegionsActions({ fetchRegions: 'fetchItems' }), ...mapVpcActions({ fetchVpcs: 'fetchItems' }), ...mapSubnetActions({ fetchSubnets: 'fetchItems' }), ...mapRolesActions({ fetchRoles: 'fetchItems' }), ...mapKeyPairsActions({ fetchKeyPairs: 'fetchItems' }), ...mapSecurityGroupsActions({ fetchSecurityGroups: 'fetchItems' }), - setRegionAndFetchVpcsAndKeyPairs(region) { - this.setRegion({ region }); + setRegionAndFetchVpcsAndKeyPairs() { this.setVpc({ vpc: null }); this.setKeyPair({ keyPair: null }); this.setSubnet({ subnet: [] }); this.setSecurityGroup({ securityGroup: null }); - this.fetchVpcs({ region }); - this.fetchKeyPairs({ region }); + this.fetchVpcs({ region: this.selectedRegion }); + this.fetchKeyPairs({ region: this.selectedRegion }); }, setVpcAndFetchSubnets(vpc) { this.setVpc({ vpc }); @@ -314,33 +304,12 @@ export default { </gl-sprintf> </p> </div> - <div class="form-group"> - <label class="label-bold" for="eks-role">{{ s__('ClusterIntegration|Region') }}</label> - <cluster-form-dropdown - field-id="eks-region" - field-name="eks-region" - :value="selectedRegion" - :items="regions" - :loading="isLoadingRegions" - :loading-text="s__('ClusterIntegration|Loading Regions')" - :placeholder="s__('ClusterIntergation|Select a region')" - :search-field-placeholder="s__('ClusterIntegration|Search regions')" - :empty-text="s__('ClusterIntegration|No region found')" - :has-errors="Boolean(loadingRegionsError)" - :error-message="s__('ClusterIntegration|Could not load regions from your AWS account')" - @input="setRegionAndFetchVpcsAndKeyPairs($event)" - /> - <p class="form-text text-muted"> - <gl-sprintf :message="$options.i18n.regionsDropdownHelpText"> - <template #link="{ content }"> - <gl-link :href="$options.i18n.regionsDropdownHelpPath" target="_blank"> - {{ content }} - <gl-icon name="external-link" class="gl-vertical-align-middle" /> - </gl-link> - </template> - </gl-sprintf> - </p> - </div> + <gl-form-group + :label="$options.i18n.regionInputLabel" + :description="$options.i18n.regionHelpText" + > + <gl-form-input id="eks-region" :value="selectedRegion" type="text" readonly /> + </gl-form-group> <div class="form-group"> <label class="label-bold" for="eks-key-pair">{{ s__('ClusterIntegration|Key pair name') diff --git a/app/assets/javascripts/create_cluster/eks_cluster/components/service_credentials_form.vue b/app/assets/javascripts/create_cluster/eks_cluster/components/service_credentials_form.vue index 5c13cbb2775..a3f76241bf2 100644 --- a/app/assets/javascripts/create_cluster/eks_cluster/components/service_credentials_form.vue +++ b/app/assets/javascripts/create_cluster/eks_cluster/components/service_credentials_form.vue @@ -1,15 +1,20 @@ <script> /* eslint-disable vue/no-v-html */ -import { GlFormInput, GlButton } from '@gitlab/ui'; +import { GlButton, GlFormGroup, GlFormInput, GlIcon, GlLink, GlSprintf } from '@gitlab/ui'; import { escape } from 'lodash'; import { mapState, mapActions } from 'vuex'; +import { DEFAULT_REGION } from '../constants'; import { sprintf, s__, __ } from '~/locale'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; export default { components: { - GlFormInput, GlButton, + GlFormGroup, + GlFormInput, + GlIcon, + GlLink, + GlSprintf, ClipboardButton, }, props: { @@ -26,9 +31,18 @@ export default { required: true, }, }, + i18n: { + regionInputLabel: s__('ClusterIntegration|Cluster Region'), + regionHelpPath: 'https://aws.amazon.com/about-aws/global-infrastructure/regions_az/', + regionHelpText: s__( + 'ClusterIntegration|Select the region you want to create the new cluster in. Make sure you have access to this region for your role to be able to authenticate. If no region is selected, we will use %{codeStart}DEFAULT_REGION%{codeEnd}. Learn more about %{linkStart}Regions%{linkEnd}.', + ), + regionHelpTextDefaultRegion: DEFAULT_REGION, + }, data() { return { roleArn: this.$store.state.roleArn, + selectedRegion: this.$store.state.selectedRegion, }; }, computed: { @@ -130,13 +144,33 @@ export default { <gl-form-input id="eks-provision-role-arn" v-model="roleArn" /> <p class="form-text text-muted" v-html="provisionRoleArnHelpText"></p> </div> + + <gl-form-group :label="$options.i18n.regionInputLabel"> + <gl-form-input id="eks-region" v-model="selectedRegion" type="text" /> + + <template #description> + <gl-sprintf :message="$options.i18n.regionHelpText"> + <template #code> + <code>{{ $options.i18n.regionHelpTextDefaultRegion }}</code> + </template> + + <template #link="{ content }"> + <gl-link :href="$options.i18n.regionHelpPath" target="_blank"> + {{ content }} + <gl-icon name="external-link" /> + </gl-link> + </template> + </gl-sprintf> + </template> + </gl-form-group> + <gl-button variant="success" category="primary" type="submit" :disabled="submitButtonDisabled" :loading="isCreatingRole" - @click.prevent="createRole({ roleArn, externalId })" + @click.prevent="createRole({ roleArn, selectedRegion, externalId })" > {{ submitButtonLabel }} </gl-button> diff --git a/app/assets/javascripts/create_cluster/eks_cluster/constants.js b/app/assets/javascripts/create_cluster/eks_cluster/constants.js index 471d6e1f0aa..0f0db2090c1 100644 --- a/app/assets/javascripts/create_cluster/eks_cluster/constants.js +++ b/app/assets/javascripts/create_cluster/eks_cluster/constants.js @@ -1,3 +1,5 @@ +export const DEFAULT_REGION = 'us-east-2'; + export const KUBERNETES_VERSIONS = [ { name: '1.14', value: '1.14' }, { name: '1.15', value: '1.15' }, diff --git a/app/assets/javascripts/create_cluster/eks_cluster/services/aws_services_facade.js b/app/assets/javascripts/create_cluster/eks_cluster/services/aws_services_facade.js index 601ff6f9adc..58568b5dedb 100644 --- a/app/assets/javascripts/create_cluster/eks_cluster/services/aws_services_facade.js +++ b/app/assets/javascripts/create_cluster/eks_cluster/services/aws_services_facade.js @@ -8,13 +8,8 @@ const lookupVpcName = ({ Tags: tags, VpcId: id }) => { return nameTag ? nameTag.Value : id; }; -export const DEFAULT_REGION = 'us-east-2'; - export const setAWSConfig = ({ awsCredentials }) => { - AWS.config = { - ...awsCredentials, - region: DEFAULT_REGION, - }; + AWS.config = awsCredentials; }; export const fetchRoles = () => { @@ -26,20 +21,6 @@ export const fetchRoles = () => { .then(({ Roles: roles }) => roles.map(({ RoleName: name, Arn: value }) => ({ name, value }))); }; -export const fetchRegions = () => { - const ec2 = new EC2(); - - return ec2 - .describeRegions() - .promise() - .then(({ Regions: regions }) => - regions.map(({ RegionName: name }) => ({ - name, - value: name, - })), - ); -}; - export const fetchKeyPairs = ({ region }) => { const ec2 = new EC2({ region }); diff --git a/app/assets/javascripts/create_cluster/eks_cluster/store/actions.js b/app/assets/javascripts/create_cluster/eks_cluster/store/actions.js index 48c85ff627f..f3950a3343a 100644 --- a/app/assets/javascripts/create_cluster/eks_cluster/store/actions.js +++ b/app/assets/javascripts/create_cluster/eks_cluster/store/actions.js @@ -1,4 +1,5 @@ import * as types from './mutation_types'; +import { DEFAULT_REGION } from '../constants'; import { setAWSConfig } from '../services/aws_services_facade'; import axios from '~/lib/utils/axios_utils'; import { deprecatedCreateFlash as createFlash } from '~/flash'; @@ -25,12 +26,22 @@ export const setKubernetesVersion = ({ commit }, payload) => { export const createRole = ({ dispatch, state: { createRolePath } }, payload) => { dispatch('requestCreateRole'); + const region = payload.selectedRegion || DEFAULT_REGION; + return axios .post(createRolePath, { role_arn: payload.roleArn, role_external_id: payload.externalId, + region, + }) + .then(({ data }) => { + const awsData = { + ...convertObjectPropsToCamelCase(data), + region, + }; + + dispatch('createRoleSuccess', awsData); }) - .then(({ data }) => dispatch('createRoleSuccess', convertObjectPropsToCamelCase(data))) .catch(error => dispatch('createRoleError', { error })); }; @@ -38,7 +49,8 @@ export const requestCreateRole = ({ commit }) => { commit(types.REQUEST_CREATE_ROLE); }; -export const createRoleSuccess = ({ commit }, awsCredentials) => { +export const createRoleSuccess = ({ dispatch, commit }, awsCredentials) => { + dispatch('setRegion', { region: awsCredentials.region }); setAWSConfig({ awsCredentials }); commit(types.CREATE_ROLE_SUCCESS); }; diff --git a/app/assets/javascripts/create_cluster/eks_cluster/store/index.js b/app/assets/javascripts/create_cluster/eks_cluster/store/index.js index 8dc55506dc2..262bbb3167a 100644 --- a/app/assets/javascripts/create_cluster/eks_cluster/store/index.js +++ b/app/assets/javascripts/create_cluster/eks_cluster/store/index.js @@ -8,7 +8,6 @@ import clusterDropdownStore from '~/create_cluster/store/cluster_dropdown'; import { fetchRoles, - fetchRegions, fetchKeyPairs, fetchVpcs, fetchSubnets, @@ -26,10 +25,6 @@ const createStore = ({ initialState }) => namespaced: true, ...clusterDropdownStore({ fetchFn: fetchRoles }), }, - regions: { - namespaced: true, - ...clusterDropdownStore({ fetchFn: fetchRegions }), - }, keyPairs: { namespaced: true, ...clusterDropdownStore({ fetchFn: fetchKeyPairs }), diff --git a/app/controllers/clusters/clusters_controller.rb b/app/controllers/clusters/clusters_controller.rb index 52719e90e04..9800d94964d 100644 --- a/app/controllers/clusters/clusters_controller.rb +++ b/app/controllers/clusters/clusters_controller.rb @@ -272,7 +272,7 @@ class Clusters::ClustersController < Clusters::BaseController end def aws_role_params - params.require(:cluster).permit(:role_arn) + params.require(:cluster).permit(:role_arn, :region) end def generate_gcp_authorize_url diff --git a/app/services/bulk_create_integration_service.rb b/app/services/bulk_create_integration_service.rb index 23b89b0d8a9..2cec7e71989 100644 --- a/app/services/bulk_create_integration_service.rb +++ b/app/services/bulk_create_integration_service.rb @@ -11,6 +11,8 @@ class BulkCreateIntegrationService service_list = ServiceList.new(batch, service_hash, association).to_array Service.transaction do + run_callbacks(batch) if association == 'project' + results = bulk_insert(*service_list) if integration.data_fields_present? @@ -18,8 +20,6 @@ class BulkCreateIntegrationService bulk_insert(*data_list) end - - run_callbacks(batch) if association == 'project' end end @@ -33,17 +33,15 @@ class BulkCreateIntegrationService klass.insert_all(items_to_insert, returning: [:id]) end - # rubocop: disable CodeReuse/ActiveRecord def run_callbacks(batch) - if integration.issue_tracker? - Project.where(id: batch.select(:id)).update_all(has_external_issue_tracker: true) + if integration.issue_tracker? && integration.active? + batch.update_all(has_external_issue_tracker: true) end - if integration.type == 'ExternalWikiService' - Project.where(id: batch.select(:id)).update_all(has_external_wiki: true) + if integration.type == 'ExternalWikiService' && integration.active? + batch.update_all(has_external_wiki: true) end end - # rubocop: enable CodeReuse/ActiveRecord def service_hash if integration.template? diff --git a/app/services/clusters/aws/authorize_role_service.rb b/app/services/clusters/aws/authorize_role_service.rb index 2712a4b05bb..1f6f350bcc8 100644 --- a/app/services/clusters/aws/authorize_role_service.rb +++ b/app/services/clusters/aws/authorize_role_service.rb @@ -17,7 +17,8 @@ module Clusters def initialize(user, params:) @user = user - @params = params + @role_arn = params[:role_arn] + @region = params[:region] end def execute @@ -33,18 +34,18 @@ module Clusters private - attr_reader :role, :params + attr_reader :role, :role_arn, :region def ensure_role_exists! @role = ::Aws::Role.find_by_user_id!(user.id) end def update_role_arn! - role.update!(params) + role.update!(role_arn: role_arn) end def credentials - Clusters::Aws::FetchCredentialsService.new(role).execute + Clusters::Aws::FetchCredentialsService.new(role, region: region).execute end end end diff --git a/app/services/clusters/aws/fetch_credentials_service.rb b/app/services/clusters/aws/fetch_credentials_service.rb index 33efc4cc120..f94d8d8c66d 100644 --- a/app/services/clusters/aws/fetch_credentials_service.rb +++ b/app/services/clusters/aws/fetch_credentials_service.rb @@ -7,9 +7,10 @@ module Clusters MissingRoleError = Class.new(StandardError) - def initialize(provision_role, provider: nil) + def initialize(provision_role, provider: nil, region: nil) @provision_role = provision_role @provider = provider + @region = provider&.region || region end def execute @@ -26,7 +27,7 @@ module Clusters private - attr_reader :provider + attr_reader :provider, :region def client ::Aws::STS::Client.new(credentials: gitlab_credentials, region: region) @@ -44,10 +45,6 @@ module Clusters Gitlab::CurrentSettings.eks_secret_access_key end - def region - provider&.region || Clusters::Providers::Aws::DEFAULT_REGION - end - ## # If we haven't created a provider record yet, # we restrict ourselves to read only access so diff --git a/changelogs/unreleased/216974-optional-input-field.yml b/changelogs/unreleased/216974-optional-input-field.yml new file mode 100644 index 00000000000..1816b6202bf --- /dev/null +++ b/changelogs/unreleased/216974-optional-input-field.yml @@ -0,0 +1,5 @@ +--- +title: Remove default EKS Region dropdown in cluster create form +merge_request: 43017 +author: +type: fixed diff --git a/changelogs/unreleased/268133-fix-propagate-integrations-for-project-callback.yml b/changelogs/unreleased/268133-fix-propagate-integrations-for-project-callback.yml new file mode 100644 index 00000000000..6af3e240d16 --- /dev/null +++ b/changelogs/unreleased/268133-fix-propagate-integrations-for-project-callback.yml @@ -0,0 +1,5 @@ +--- +title: Fix project callbacks when propagating integrations +merge_request: 45781 +author: +type: fixed diff --git a/changelogs/unreleased/jh-migration_mvc_gmau.yml b/changelogs/unreleased/jh-migration_mvc_gmau.yml new file mode 100644 index 00000000000..5d3e2b95749 --- /dev/null +++ b/changelogs/unreleased/jh-migration_mvc_gmau.yml @@ -0,0 +1,6 @@ +--- +title: Add usage ping for unique users importing groups and projects via the group + migration tool +merge_request: 45536 +author: +type: changed diff --git a/doc/administration/object_storage.md b/doc/administration/object_storage.md index 8b788e6d91d..5bfd38312df 100644 --- a/doc/administration/object_storage.md +++ b/doc/administration/object_storage.md @@ -288,6 +288,11 @@ Although Azure uses the word `container` to denote a collection of blobs, GitLab standardizes on the term `bucket`. Be sure to configure Azure container names in the `bucket` settings. +Azure Blob storage can only be used with the [consolidated form](#consolidated-object-storage-configuration) +because a single set of credentials are used to access multiple +containers. The [storage-specific form](#storage-specific-configuration) +is not supported. For more details, see [how to transition to consolidated form](#transition-to-consolidated-form). + The following are the valid connection parameters for Azure. Read the [Azure Blob storage documentation](https://docs.microsoft.com/en-us/azure/storage/blobs/storage-blobs-introduction) to learn more. diff --git a/doc/development/changelog.md b/doc/development/changelog.md index 922c4814d91..a633ab6919e 100644 --- a/doc/development/changelog.md +++ b/doc/development/changelog.md @@ -38,7 +38,8 @@ the `author` field. GitLab team members **should not**. - [Security fixes](https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md) **must** have a changelog entry, without `merge_request` value and with `type` set to `security`. -- Any user-facing change **should** have a changelog entry. This includes both visual changes (regardless of how minor), and changes to the rendered DOM which impact how a screen reader may announce the content. +- Any user-facing change **must** have a changelog entry. This includes both visual changes (regardless of how minor), and changes to the rendered DOM which impact how a screen reader may announce the content. +- Any client-facing change to our REST and GraphQL APIs **must** have a changelog entry. - Performance improvements **should** have a changelog entry. - Changes that need to be documented in the Product Analytics [Event Dictionary](product_analytics/event_dictionary.md) also require a changelog entry. diff --git a/doc/operations/metrics/dashboards/img/metrics_settings_button_v13_3.png b/doc/operations/metrics/dashboards/img/metrics_settings_button_v13_3.png Binary files differdeleted file mode 100644 index 9c0eac12a3f..00000000000 --- a/doc/operations/metrics/dashboards/img/metrics_settings_button_v13_3.png +++ /dev/null diff --git a/doc/operations/metrics/dashboards/index.md b/doc/operations/metrics/dashboards/index.md index 970bca70aff..ea7b987e7bb 100644 --- a/doc/operations/metrics/dashboards/index.md +++ b/doc/operations/metrics/dashboards/index.md @@ -116,14 +116,9 @@ Your custom dashboard is available at `https://example.com/project/-/metrics/cus > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/223204) in GitLab 13.2. -To manage the settings for your metrics dashboard: - -1. Sign in as a user with project Maintainer or Administrator - [permissions](../../../user/permissions.md#project-members-permissions). -1. Navigate to your dashboard at **Operations > Metrics**. -1. In the top-right corner of your dashboard, click **Metrics Settings**: - - ![Monitoring Dashboard actions menu with create new item](img/metrics_settings_button_v13_3.png) +Users with project Maintainer or Administrator +[permissions](../../../user/permissions.md#project-members-permissions) +can manage [the settings](settings.md) for your metrics dashboard. ## Chart Context Menu diff --git a/doc/operations/metrics/dashboards/settings.md b/doc/operations/metrics/dashboards/settings.md index aa0b9a81771..6052b0778da 100644 --- a/doc/operations/metrics/dashboards/settings.md +++ b/doc/operations/metrics/dashboards/settings.md @@ -21,8 +21,8 @@ time zone, but you can display dates and times in UTC format. To change the time zone: 1. Sign in as a user with Manage Project Operations [permissions](../../../user/permissions.md). -1. Navigate to **Settings > Operations**, and scroll to - **Metrics Dashboard**. +1. Navigate to **Settings > Operations**. +1. Scroll to **Metrics Dashboard** and click **Expand**. 1. In the **Dashboard timezone** select box, select *User's local timezone* or *UTC*: @@ -37,8 +37,8 @@ You can add a button on your monitoring dashboard that links directly to your existing external dashboards: 1. Sign in as a user with Manage Project Operations [permissions](../../../user/permissions.md). -1. Navigate to **Settings > Operations**, and scroll to - **Metrics Dashboard**. +1. Navigate to **Settings > Operations**. +1. Scroll to **Metrics Dashboard** and click **Expand**. 1. In **External dashboard URL**, provide the URL to your external dashboard: ![External Dashboard Setting](img/dashboard_external_link_v13_1.png) diff --git a/doc/user/permissions.md b/doc/user/permissions.md index 2e9f36360c6..565bae99d50 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -391,7 +391,7 @@ with the permissions described on the documentation on [auditor users permission [Read more about Auditor users.](../administration/auditor_users.md) -## Users with minimal access **(PREMIUM ONLY)** +## Users with minimal access **(PREMIUM)** >[Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/40942) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.4. diff --git a/lib/gitlab/experimentation.rb b/lib/gitlab/experimentation.rb index 1ce3ffe4c86..d5da260406f 100644 --- a/lib/gitlab/experimentation.rb +++ b/lib/gitlab/experimentation.rb @@ -201,7 +201,7 @@ module Gitlab return false unless EXPERIMENTS.key?(experiment_key) experiment = experiment(experiment_key) - experiment.enabled? && experiment.enabled_for_environment? + experiment.enabled_for_environment? && experiment.enabled? end def enabled_for_attribute?(experiment_key, attribute) diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb index e7e1ee949e6..030d50815a6 100644 --- a/lib/gitlab/usage_data.rb +++ b/lib/gitlab/usage_data.rb @@ -585,6 +585,9 @@ module Gitlab users_created: count(::User.where(time_period), start: user_minimum_id, finish: user_maximum_id), omniauth_providers: filtered_omniauth_provider_names.reject { |name| name == 'group_saml' }, user_auth_by_provider: distinct_count_user_auth_by_provider(time_period), + bulk_imports: { + gitlab: distinct_count(::BulkImport.where(time_period, source_type: :gitlab), :user_id) + }, projects_imported: { gitlab_project: projects_imported_count('gitlab_project', time_period), gitlab: projects_imported_count('gitlab', time_period), diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 0e61fbeb119..4f41df0e22b 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -5687,6 +5687,9 @@ msgstr "" msgid "ClusterIntegration|Clear the local cache of namespace and service accounts." msgstr "" +msgid "ClusterIntegration|Cluster Region" +msgstr "" + msgid "ClusterIntegration|Cluster management project (alpha)" msgstr "" @@ -5744,9 +5747,6 @@ msgstr "" msgid "ClusterIntegration|Could not load networks" msgstr "" -msgid "ClusterIntegration|Could not load regions from your AWS account" -msgstr "" - msgid "ClusterIntegration|Could not load security groups for the selected VPC" msgstr "" @@ -6002,9 +6002,6 @@ msgstr "" msgid "ClusterIntegration|Learn more about %{help_link_start}zones%{help_link_end}." msgstr "" -msgid "ClusterIntegration|Learn more about %{linkStart}Regions%{linkEnd}." -msgstr "" - msgid "ClusterIntegration|Learn more about Kubernetes" msgstr "" @@ -6020,9 +6017,6 @@ msgstr "" msgid "ClusterIntegration|Loading Key Pairs" msgstr "" -msgid "ClusterIntegration|Loading Regions" -msgstr "" - msgid "ClusterIntegration|Loading VPCs" msgstr "" @@ -6089,9 +6083,6 @@ msgstr "" msgid "ClusterIntegration|No projects matched your search" msgstr "" -msgid "ClusterIntegration|No region found" -msgstr "" - msgid "ClusterIntegration|No security group found" msgstr "" @@ -6158,9 +6149,6 @@ msgstr "" msgid "ClusterIntegration|Real-time web application monitoring, logging and access control. %{linkStart}More information%{linkEnd}" msgstr "" -msgid "ClusterIntegration|Region" -msgstr "" - msgid "ClusterIntegration|Remove Kubernetes cluster integration" msgstr "" @@ -6227,9 +6215,6 @@ msgstr "" msgid "ClusterIntegration|Search projects" msgstr "" -msgid "ClusterIntegration|Search regions" -msgstr "" - msgid "ClusterIntegration|Search security groups" msgstr "" @@ -6290,6 +6275,9 @@ msgstr "" msgid "ClusterIntegration|Select the key pair name that will be used to create EC2 nodes. To use a new key pair name, first create one on %{linkStart}Amazon Web Services%{linkEnd}." msgstr "" +msgid "ClusterIntegration|Select the region you want to create the new cluster in. Make sure you have access to this region for your role to be able to authenticate. If no region is selected, we will use %{codeStart}DEFAULT_REGION%{codeEnd}. Learn more about %{linkStart}Regions%{linkEnd}." +msgstr "" + msgid "ClusterIntegration|Select zone" msgstr "" @@ -6374,6 +6362,9 @@ msgstr "" msgid "ClusterIntegration|The namespace associated with your project. This will be used for deploy boards, logs, and Web terminals." msgstr "" +msgid "ClusterIntegration|The region the new cluster will be created in. You must reauthenticate to change regions." +msgstr "" + msgid "ClusterIntegration|There was a problem authenticating with your cluster. Please ensure your CA Certificate and Token are valid." msgstr "" @@ -6515,9 +6506,6 @@ msgstr "" msgid "ClusterIntergation|Select a network" msgstr "" -msgid "ClusterIntergation|Select a region" -msgstr "" - msgid "ClusterIntergation|Select a security group" msgstr "" diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index 87e4a8e355d..639fff06cec 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -10,8 +10,10 @@ FactoryBot.define do factory :project, class: 'Project' do sequence(:name) { |n| "project#{n}" } path { name.downcase.gsub(/\s/, '_') } - # Behaves differently to nil due to cache_has_external_issue_tracker + + # Behaves differently to nil due to cache_has_external_* methods. has_external_issue_tracker { false } + has_external_wiki { false } # Associations namespace diff --git a/spec/factories/services.rb b/spec/factories/services.rb index 13997080817..f4b73b6ed70 100644 --- a/spec/factories/services.rb +++ b/spec/factories/services.rb @@ -139,6 +139,13 @@ FactoryBot.define do end end + factory :external_wiki_service do + project + type { ExternalWikiService } + active { true } + external_wiki_url { 'http://external-wiki-url.com' } + end + factory :open_project_service do project active { true } diff --git a/spec/frontend/create_cluster/eks_cluster/components/eks_cluster_configuration_form_spec.js b/spec/frontend/create_cluster/eks_cluster/components/eks_cluster_configuration_form_spec.js index 2600415fc9f..f9984091df0 100644 --- a/spec/frontend/create_cluster/eks_cluster/components/eks_cluster_configuration_form_spec.js +++ b/spec/frontend/create_cluster/eks_cluster/components/eks_cluster_configuration_form_spec.js @@ -16,7 +16,6 @@ describe('EksClusterConfigurationForm', () => { let getters; let state; let rolesState; - let regionsState; let vpcsState; let subnetsState; let keyPairsState; @@ -24,7 +23,6 @@ describe('EksClusterConfigurationForm', () => { let instanceTypesState; let vpcsActions; let rolesActions; - let regionsActions; let subnetsActions; let keyPairsActions; let securityGroupsActions; @@ -46,9 +44,6 @@ describe('EksClusterConfigurationForm', () => { setNodeCount: jest.fn(), setGitlabManagedCluster: jest.fn(), }; - regionsActions = { - fetchItems: jest.fn(), - }; keyPairsActions = { fetchItems: jest.fn(), }; @@ -72,10 +67,6 @@ describe('EksClusterConfigurationForm', () => { ...clusterDropdownStoreState(), ...config.rolesState, }; - regionsState = { - ...clusterDropdownStoreState(), - ...config.regionsState, - }; vpcsState = { ...clusterDropdownStoreState(), ...config.vpcsState, @@ -109,11 +100,6 @@ describe('EksClusterConfigurationForm', () => { state: vpcsState, actions: vpcsActions, }, - regions: { - namespaced: true, - state: regionsState, - actions: regionsActions, - }, subnets: { namespaced: true, state: subnetsState, @@ -189,7 +175,6 @@ describe('EksClusterConfigurationForm', () => { const findClusterNameInput = () => vm.find('[id=eks-cluster-name]'); const findEnvironmentScopeInput = () => vm.find('[id=eks-environment-scope]'); const findKubernetesVersionDropdown = () => vm.find('[field-id="eks-kubernetes-version"]'); - const findRegionDropdown = () => vm.find('[field-id="eks-region"]'); const findKeyPairDropdown = () => vm.find('[field-id="eks-key-pair"]'); const findVpcDropdown = () => vm.find('[field-id="eks-vpc"]'); const findSubnetDropdown = () => vm.find('[field-id="eks-subnet"]'); @@ -200,13 +185,44 @@ describe('EksClusterConfigurationForm', () => { const findGitlabManagedClusterCheckbox = () => vm.find(GlFormCheckbox); describe('when mounted', () => { - it('fetches available regions', () => { - expect(regionsActions.fetchItems).toHaveBeenCalled(); - }); - it('fetches available roles', () => { expect(rolesActions.fetchItems).toHaveBeenCalled(); }); + + describe('when fetching vpcs and key pairs', () => { + const region = 'us-west-2'; + + beforeEach(() => { + createValidStateStore({ selectedRegion: region }); + buildWrapper(); + }); + + it('fetches available vpcs', () => { + expect(vpcsActions.fetchItems).toHaveBeenCalledWith(expect.anything(), { region }); + }); + + it('fetches available key pairs', () => { + expect(keyPairsActions.fetchItems).toHaveBeenCalledWith(expect.anything(), { region }); + }); + + it('cleans selected vpc', () => { + expect(actions.setVpc).toHaveBeenCalledWith(expect.anything(), { vpc: null }); + }); + + it('cleans selected key pair', () => { + expect(actions.setKeyPair).toHaveBeenCalledWith(expect.anything(), { keyPair: null }); + }); + + it('cleans selected subnet', () => { + expect(actions.setSubnet).toHaveBeenCalledWith(expect.anything(), { subnet: [] }); + }); + + it('cleans selected security group', () => { + expect(actions.setSecurityGroup).toHaveBeenCalledWith(expect.anything(), { + securityGroup: null, + }); + }); + }); }); it('sets isLoadingRoles to RoleDropdown loading property', () => { @@ -229,26 +245,6 @@ describe('EksClusterConfigurationForm', () => { }); }); - it('sets isLoadingRegions to RegionDropdown loading property', () => { - regionsState.isLoadingItems = true; - - return Vue.nextTick().then(() => { - expect(findRegionDropdown().props('loading')).toBe(regionsState.isLoadingItems); - }); - }); - - it('sets regions to RegionDropdown regions property', () => { - expect(findRegionDropdown().props('items')).toBe(regionsState.items); - }); - - it('sets loadingRegionsError to RegionDropdown error property', () => { - regionsState.loadingItemsError = new Error(); - - return Vue.nextTick().then(() => { - expect(findRegionDropdown().props('hasErrors')).toEqual(true); - }); - }); - it('disables KeyPairDropdown when no region is selected', () => { expect(findKeyPairDropdown().props('disabled')).toBe(true); }); @@ -394,44 +390,6 @@ describe('EksClusterConfigurationForm', () => { }); }); - describe('when region is selected', () => { - const region = { name: 'us-west-2' }; - - beforeEach(() => { - findRegionDropdown().vm.$emit('input', region); - }); - - it('dispatches setRegion action', () => { - expect(actions.setRegion).toHaveBeenCalledWith(expect.anything(), { region }); - }); - - it('fetches available vpcs', () => { - expect(vpcsActions.fetchItems).toHaveBeenCalledWith(expect.anything(), { region }); - }); - - it('fetches available key pairs', () => { - expect(keyPairsActions.fetchItems).toHaveBeenCalledWith(expect.anything(), { region }); - }); - - it('cleans selected vpc', () => { - expect(actions.setVpc).toHaveBeenCalledWith(expect.anything(), { vpc: null }); - }); - - it('cleans selected key pair', () => { - expect(actions.setKeyPair).toHaveBeenCalledWith(expect.anything(), { keyPair: null }); - }); - - it('cleans selected subnet', () => { - expect(actions.setSubnet).toHaveBeenCalledWith(expect.anything(), { subnet: [] }); - }); - - it('cleans selected security group', () => { - expect(actions.setSecurityGroup).toHaveBeenCalledWith(expect.anything(), { - securityGroup: null, - }); - }); - }); - it('dispatches setClusterName when cluster name input changes', () => { const clusterName = 'name'; diff --git a/spec/frontend/create_cluster/eks_cluster/services/aws_services_facade_spec.js b/spec/frontend/create_cluster/eks_cluster/services/aws_services_facade_spec.js index 0ef09b4b87e..03c22c570a8 100644 --- a/spec/frontend/create_cluster/eks_cluster/services/aws_services_facade_spec.js +++ b/spec/frontend/create_cluster/eks_cluster/services/aws_services_facade_spec.js @@ -3,12 +3,10 @@ import EC2 from 'aws-sdk/clients/ec2'; import { setAWSConfig, fetchRoles, - fetchRegions, fetchKeyPairs, fetchVpcs, fetchSubnets, fetchSecurityGroups, - DEFAULT_REGION, } from '~/create_cluster/eks_cluster/services/aws_services_facade'; const mockListRolesPromise = jest.fn(); @@ -45,19 +43,17 @@ describe('awsServicesFacade', () => { vpc = 'vpc-2'; }); - it('setAWSConfig configures AWS SDK with provided credentials and default region', () => { + it('setAWSConfig configures AWS SDK with provided credentials', () => { const awsCredentials = { accessKeyId: 'access-key', secretAccessKey: 'secret-key', sessionToken: 'session-token', + region, }; setAWSConfig({ awsCredentials }); - expect(AWS.config).toEqual({ - ...awsCredentials, - region: DEFAULT_REGION, - }); + expect(AWS.config).toEqual(awsCredentials); }); describe('when fetchRoles succeeds', () => { @@ -79,22 +75,6 @@ describe('awsServicesFacade', () => { }); }); - describe('when fetchRegions succeeds', () => { - let regions; - let regionsOutput; - - beforeEach(() => { - regions = [{ RegionName: 'east-1' }, { RegionName: 'west-2' }]; - regionsOutput = regions.map(({ RegionName: name }) => ({ name, value: name })); - - mockDescribeRegionsPromise.mockResolvedValueOnce({ Regions: regions }); - }); - - it('return list of roles where each item has a name and value', () => { - return expect(fetchRegions()).resolves.toEqual(regionsOutput); - }); - }); - describe('when fetchKeyPairs succeeds', () => { let keyPairs; let keyPairsOutput; diff --git a/spec/frontend/create_cluster/eks_cluster/store/actions_spec.js b/spec/frontend/create_cluster/eks_cluster/store/actions_spec.js index f929216689a..f12f300872a 100644 --- a/spec/frontend/create_cluster/eks_cluster/store/actions_spec.js +++ b/spec/frontend/create_cluster/eks_cluster/store/actions_spec.js @@ -23,6 +23,7 @@ import { REQUEST_CREATE_CLUSTER, CREATE_CLUSTER_ERROR, } from '~/create_cluster/eks_cluster/store/mutation_types'; +import { DEFAULT_REGION } from '~/create_cluster/eks_cluster/constants'; import axios from '~/lib/utils/axios_utils'; import { deprecatedCreateFlash as createFlash } from '~/flash'; @@ -109,12 +110,13 @@ describe('EKS Cluster Store Actions', () => { secretAccessKey: 'secret-key-id', }; - describe('when request succeeds', () => { + describe('when request succeeds with default region', () => { beforeEach(() => { mock .onPost(state.createRolePath, { role_arn: payload.roleArn, role_external_id: payload.externalId, + region: DEFAULT_REGION, }) .reply(201, response); }); @@ -125,7 +127,51 @@ describe('EKS Cluster Store Actions', () => { payload, state, [], - [{ type: 'requestCreateRole' }, { type: 'createRoleSuccess', payload: response }], + [ + { type: 'requestCreateRole' }, + { + type: 'createRoleSuccess', + payload: { + region: DEFAULT_REGION, + ...response, + }, + }, + ], + )); + }); + + describe('when request succeeds with custom region', () => { + const customRegion = 'custom-region'; + + beforeEach(() => { + mock + .onPost(state.createRolePath, { + role_arn: payload.roleArn, + role_external_id: payload.externalId, + region: customRegion, + }) + .reply(201, response); + }); + + it('dispatches createRoleSuccess action', () => + testAction( + actions.createRole, + { + selectedRegion: customRegion, + ...payload, + }, + state, + [], + [ + { type: 'requestCreateRole' }, + { + type: 'createRoleSuccess', + payload: { + region: customRegion, + ...response, + }, + }, + ], )); }); @@ -138,6 +184,7 @@ describe('EKS Cluster Store Actions', () => { .onPost(state.createRolePath, { role_arn: payload.roleArn, role_external_id: payload.externalId, + region: DEFAULT_REGION, }) .reply(400, error); }); @@ -160,8 +207,14 @@ describe('EKS Cluster Store Actions', () => { }); describe('createRoleSuccess', () => { - it('commits createRoleSuccess mutation', () => { - testAction(actions.createRoleSuccess, null, state, [{ type: CREATE_ROLE_SUCCESS }]); + it('sets region and commits createRoleSuccess mutation', () => { + testAction( + actions.createRoleSuccess, + { region }, + state, + [{ type: CREATE_ROLE_SUCCESS }], + [{ type: 'setRegion', payload: { region } }], + ); }); }); diff --git a/spec/lib/gitlab/experimentation_spec.rb b/spec/lib/gitlab/experimentation_spec.rb index e93593d348f..24f8db3a1c8 100644 --- a/spec/lib/gitlab/experimentation_spec.rb +++ b/spec/lib/gitlab/experimentation_spec.rb @@ -442,6 +442,14 @@ RSpec.describe Gitlab::Experimentation, :snowplow do let(:environment) { ::Gitlab.com? } it { is_expected.to be_falsey } + + it 'ensures the typically less expensive environment is checked before the more expensive call to database for Feature' do + expect_next_instance_of(described_class::Experiment) do |experiment| + expect(experiment).not_to receive(:enabled?) + end + + subject + end end end diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index 7f5d41e71a2..ada1feb2632 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -203,6 +203,8 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do for_defined_days_back do user = create(:user) + create(:bulk_import, user: user) + %w(gitlab_project gitlab github bitbucket bitbucket_server gitea git manifest fogbugz phabricator).each do |type| create(:project, import_type: type, creator_id: user.id) end @@ -215,6 +217,9 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do expect(described_class.usage_activity_by_stage_manage({})).to include( { + bulk_imports: { + gitlab: 2 + }, projects_imported: { gitlab_project: 2, gitlab: 2, @@ -235,6 +240,9 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do ) expect(described_class.usage_activity_by_stage_manage(described_class.last_28_days_time_period)).to include( { + bulk_imports: { + gitlab: 1 + }, projects_imported: { gitlab_project: 1, gitlab: 1, diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 2abcb39a1c8..306478774c1 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -890,7 +890,7 @@ RSpec.describe API::Projects do expect(response).to have_gitlab_http_status(:created) project.each_pair do |k, v| - next if %i[has_external_issue_tracker issues_enabled merge_requests_enabled wiki_enabled storage_version].include?(k) + next if %i[has_external_issue_tracker has_external_wiki issues_enabled merge_requests_enabled wiki_enabled storage_version].include?(k) expect(json_response[k.to_s]).to eq(v) end @@ -1309,7 +1309,7 @@ RSpec.describe API::Projects do expect(response).to have_gitlab_http_status(:created) project.each_pair do |k, v| - next if %i[has_external_issue_tracker path storage_version].include?(k) + next if %i[has_external_issue_tracker has_external_wiki path storage_version].include?(k) expect(json_response[k.to_s]).to eq(v) end diff --git a/spec/services/bulk_create_integration_service_spec.rb b/spec/services/bulk_create_integration_service_spec.rb index 5d896f78b35..140b7a84d40 100644 --- a/spec/services/bulk_create_integration_service_spec.rb +++ b/spec/services/bulk_create_integration_service_spec.rb @@ -41,7 +41,7 @@ RSpec.describe BulkCreateIntegrationService do end end - shared_examples 'runs project callbacks' do + shared_examples 'updates project callbacks' do it 'updates projects#has_external_issue_tracker for issue tracker services' do described_class.new(integration, batch, association).execute @@ -49,14 +49,7 @@ RSpec.describe BulkCreateIntegrationService do end context 'with an external wiki integration' do - let(:integration) do - ExternalWikiService.create!( - instance: true, - active: true, - push_events: false, - external_wiki_url: 'http://external-wiki-url.com' - ) - end + let(:integration) { create(:external_wiki_service, :instance) } it 'updates projects#has_external_wiki for external wiki services' do described_class.new(integration, batch, association).execute @@ -66,18 +59,44 @@ RSpec.describe BulkCreateIntegrationService do end end + shared_examples 'does not update project callbacks' do + it 'does not update projects#has_external_issue_tracker for issue tracker services' do + described_class.new(integration, batch, association).execute + + expect(project.reload.has_external_issue_tracker).to eq(false) + end + + context 'with an inactive external wiki integration' do + let(:integration) { create(:external_wiki_service, :instance, active: false) } + + it 'does not update projects#has_external_wiki for external wiki services' do + described_class.new(integration, batch, association).execute + + expect(project.reload.has_external_wiki).to eq(false) + end + end + end + context 'with an instance-level integration' do let(:integration) { instance_integration } context 'with a project association' do let!(:project) { create(:project) } let(:created_integration) { project.jira_service } - let(:batch) { Project.all } + let(:batch) { Project.without_integration(integration) } let(:association) { 'project' } it_behaves_like 'creates integration from batch ids' it_behaves_like 'updates inherit_from_id' - it_behaves_like 'runs project callbacks' + it_behaves_like 'updates project callbacks' + + context 'when integration is not active' do + before do + integration.update!(active: false) + end + + it_behaves_like 'does not update project callbacks' + end end context 'with a group association' do @@ -101,7 +120,7 @@ RSpec.describe BulkCreateIntegrationService do let(:association) { 'project' } it_behaves_like 'creates integration from batch ids' - it_behaves_like 'runs project callbacks' + it_behaves_like 'updates project callbacks' end end end diff --git a/spec/services/clusters/aws/authorize_role_service_spec.rb b/spec/services/clusters/aws/authorize_role_service_spec.rb index 5b47cf0ecde..0659f92c927 100644 --- a/spec/services/clusters/aws/authorize_role_service_spec.rb +++ b/spec/services/clusters/aws/authorize_role_service_spec.rb @@ -11,19 +11,21 @@ RSpec.describe Clusters::Aws::AuthorizeRoleService do let(:credentials_service) { instance_double(Clusters::Aws::FetchCredentialsService, execute: credentials) } let(:role_arn) { 'arn:my-role' } + let(:region) { 'region' } let(:params) do params = ActionController::Parameters.new({ cluster: { - role_arn: role_arn + role_arn: role_arn, + region: region } }) - params.require(:cluster).permit(:role_arn) + params.require(:cluster).permit(:role_arn, :region) end before do allow(Clusters::Aws::FetchCredentialsService).to receive(:new) - .with(instance_of(Aws::Role)).and_return(credentials_service) + .with(instance_of(Aws::Role), region: region).and_return(credentials_service) end context 'role exists' do diff --git a/spec/services/clusters/aws/fetch_credentials_service_spec.rb b/spec/services/clusters/aws/fetch_credentials_service_spec.rb index a0e63d96a5c..1e39f63a282 100644 --- a/spec/services/clusters/aws/fetch_credentials_service_spec.rb +++ b/spec/services/clusters/aws/fetch_credentials_service_spec.rb @@ -53,10 +53,12 @@ RSpec.describe Clusters::Aws::FetchCredentialsService do context 'provider is not specifed' do let(:provider) { nil } - let(:region) { Clusters::Providers::Aws::DEFAULT_REGION } + let(:region) { 'custom-region' } let(:session_name) { "gitlab-eks-autofill-user-#{user.id}" } let(:session_policy) { 'policy-document' } + subject { described_class.new(provision_role, provider: provider, region: region).execute } + before do allow(File).to receive(:read) .with(Rails.root.join('vendor', 'aws', 'iam', 'eks_cluster_read_only_policy.json')) diff --git a/vendor/gitignore/C++.gitignore b/vendor/gitignore/C++.gitignore index 259148fa18f..259148fa18f 100644..100755 --- a/vendor/gitignore/C++.gitignore +++ b/vendor/gitignore/C++.gitignore diff --git a/vendor/gitignore/Java.gitignore b/vendor/gitignore/Java.gitignore index a1c2a238a96..a1c2a238a96 100644..100755 --- a/vendor/gitignore/Java.gitignore +++ b/vendor/gitignore/Java.gitignore |