Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
		
							parent
							
								
									b8d3aa799c
								
							
						
					
					
						commit
						aaa0fba820
					
				|  | @ -2,7 +2,7 @@ | |||
| import { GlModal, GlAlert } from '@gitlab/ui'; | ||||
| import { mapGetters, mapActions, mapState } from 'vuex'; | ||||
| import { TYPE_USER, TYPE_ITERATION, TYPE_MILESTONE } from '~/graphql_shared/constants'; | ||||
| import { convertToGraphQLId, getZeroBasedIdFromGraphQLId } from '~/graphql_shared/utils'; | ||||
| import { convertToGraphQLId, getIdFromGraphQLId } from '~/graphql_shared/utils'; | ||||
| import { getParameterByName, visitUrl } from '~/lib/utils/url_utility'; | ||||
| import { __, s__ } from '~/locale'; | ||||
| import { fullLabelId } from '../boards_util'; | ||||
|  | @ -169,11 +169,11 @@ export default { | |||
|           : null, | ||||
|         // Temporarily converting to milestone ID due to https://gitlab.com/gitlab-org/gitlab/-/issues/344779 | ||||
|         milestoneId: this.board.milestone?.id | ||||
|           ? convertToGraphQLId(TYPE_MILESTONE, getZeroBasedIdFromGraphQLId(this.board.milestone.id)) | ||||
|           ? convertToGraphQLId(TYPE_MILESTONE, getIdFromGraphQLId(this.board.milestone.id)) | ||||
|           : null, | ||||
|         // Temporarily converting to iteration ID due to https://gitlab.com/gitlab-org/gitlab/-/issues/344779 | ||||
|         iterationId: this.board.iteration?.id | ||||
|           ? convertToGraphQLId(TYPE_ITERATION, getZeroBasedIdFromGraphQLId(this.board.iteration.id)) | ||||
|           ? convertToGraphQLId(TYPE_ITERATION, getIdFromGraphQLId(this.board.iteration.id)) | ||||
|           : null, | ||||
|       }; | ||||
|     }, | ||||
|  |  | |||
|  | @ -268,12 +268,11 @@ export default { | |||
| <template> | ||||
|   <div class="boards-switcher js-boards-selector gl-mr-3"> | ||||
|     <span class="boards-selector-wrapper js-boards-selector-wrapper"> | ||||
|       <gl-loading-icon v-if="isBoardLoading" size="md" class="gl-mt-2" /> | ||||
|       <gl-dropdown | ||||
|         v-else | ||||
|         data-qa-selector="boards_dropdown" | ||||
|         toggle-class="dropdown-menu-toggle js-dropdown-toggle" | ||||
|         menu-class="flex-column dropdown-extended-height" | ||||
|         :loading="isBoardLoading" | ||||
|         :text="board.name" | ||||
|         @show="loadBoards" | ||||
|       > | ||||
|  |  | |||
|  | @ -1,14 +1,27 @@ | |||
| import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory'; | ||||
| import Vue from 'vue'; | ||||
| import VueApollo from 'vue-apollo'; | ||||
| import BoardsSelector from 'ee_else_ce/boards/components/boards_selector.vue'; | ||||
| import store from '~/boards/stores'; | ||||
| import createDefaultClient from '~/lib/graphql'; | ||||
| import { parseBoolean } from '~/lib/utils/common_utils'; | ||||
| import introspectionQueryResultData from '~/sidebar/fragmentTypes.json'; | ||||
| 
 | ||||
| Vue.use(VueApollo); | ||||
| 
 | ||||
| const fragmentMatcher = new IntrospectionFragmentMatcher({ | ||||
|   introspectionQueryResultData, | ||||
| }); | ||||
| 
 | ||||
| const apolloProvider = new VueApollo({ | ||||
|   defaultClient: createDefaultClient(), | ||||
|   defaultClient: createDefaultClient( | ||||
|     {}, | ||||
|     { | ||||
|       cacheConfig: { | ||||
|         fragmentMatcher, | ||||
|       }, | ||||
|     }, | ||||
|   ), | ||||
| }); | ||||
| 
 | ||||
| export default (params = {}) => { | ||||
|  |  | |||
|  | @ -6,14 +6,6 @@ const elementRenderer = (element, props = {}) => (createElement) => | |||
| 
 | ||||
| export default () => { | ||||
|   const root = document.querySelector('#js-google-cloud'); | ||||
| 
 | ||||
|   // uncomment this once backend is ready
 | ||||
|   // const dataset = JSON.parse(root.getAttribute('data'));
 | ||||
|   const mockDataset = { | ||||
|     createServiceAccountUrl: '#create-url', | ||||
|     serviceAccounts: [], | ||||
|     emptyIllustrationUrl: | ||||
|       'https://gitlab.com/gitlab-org/gitlab-svgs/-/raw/main/illustrations/pipelines_empty.svg', | ||||
|   }; | ||||
|   return new Vue({ el: root, render: elementRenderer(App, mockDataset) }); | ||||
|   const props = JSON.parse(root.getAttribute('data')); | ||||
|   return new Vue({ el: root, render: elementRenderer(App, props) }); | ||||
| }; | ||||
|  |  | |||
|  | @ -25,9 +25,7 @@ const parseGid = (gid) => parseInt(`${gid}`.replace(/gid:\/\/gitlab\/.*\//g, '') | |||
|  * @param {String} gid GraphQL global ID | ||||
|  * @returns {Number} | ||||
|  */ | ||||
| export const getIdFromGraphQLId = (gid = '') => parseGid(gid) || null; | ||||
| 
 | ||||
| export const getZeroBasedIdFromGraphQLId = (gid = '') => { | ||||
| export const getIdFromGraphQLId = (gid = '') => { | ||||
|   const parsedGid = parseGid(gid); | ||||
|   return Number.isInteger(parsedGid) ? parsedGid : null; | ||||
| }; | ||||
|  |  | |||
|  | @ -8,6 +8,11 @@ class Projects::GoogleCloudController < Projects::ApplicationController | |||
|   before_action :feature_flag_enabled? | ||||
| 
 | ||||
|   def index | ||||
|     @js_data = { | ||||
|       serviceAccounts: GoogleCloud::ServiceAccountsService.new(project).find_for_project, | ||||
|       createServiceAccountUrl: '#mocked-url-create-service', | ||||
|       emptyIllustrationUrl: ActionController::Base.helpers.image_path('illustrations/pipelines_empty.svg') | ||||
|     }.to_json | ||||
|   end | ||||
| 
 | ||||
|   private | ||||
|  |  | |||
|  | @ -0,0 +1,40 @@ | |||
| # frozen_string_literal: true | ||||
| 
 | ||||
| module GoogleCloud | ||||
|   ## | ||||
|   # GCP keys used to store Google Cloud Service Accounts | ||||
|   GCP_KEYS = %w[GCP_PROJECT_ID GCP_SERVICE_ACCOUNT GCP_SERVICE_ACCOUNT_KEY].freeze | ||||
| 
 | ||||
|   ## | ||||
|   # This service deals with GCP Service Accounts in GitLab | ||||
| 
 | ||||
|   class ServiceAccountsService < ::BaseService | ||||
|     ## | ||||
|     # Find GCP Service Accounts in a GitLab project | ||||
|     # | ||||
|     # This method looks up GitLab project's CI vars | ||||
|     # and returns Google Cloud Service Accounts combinations | ||||
|     # aligning GitLab project and environment to GCP projects | ||||
| 
 | ||||
|     def find_for_project | ||||
|       group_vars_by_environment.map do |environment_scope, value| | ||||
|         { | ||||
|           environment: environment_scope, | ||||
|           gcp_project: value['GCP_PROJECT_ID'], | ||||
|           service_account_exists: value['GCP_SERVICE_ACCOUNT'].present?, | ||||
|           service_account_key_exists: value['GCP_SERVICE_ACCOUNT_KEY'].present? | ||||
|         } | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     private | ||||
| 
 | ||||
|     def group_vars_by_environment | ||||
|       filtered_vars = @project.variables.filter { |variable| GCP_KEYS.include? variable.key } | ||||
|       filtered_vars.each_with_object({}) do |variable, grouped| | ||||
|         grouped[variable.environment_scope] ||= {} | ||||
|         grouped[variable.environment_scope][variable.key] = variable.value | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  | @ -3,4 +3,4 @@ | |||
| 
 | ||||
| - @content_class = "limit-container-width" unless fluid_layout | ||||
| 
 | ||||
| #js-google-cloud | ||||
| #js-google-cloud{ data: @js_data } | ||||
|  |  | |||
|  | @ -1,4 +1,6 @@ | |||
| # frozen_string_literal: true | ||||
| 
 | ||||
| # Currently we register validator only for `dev` or `test` environment | ||||
| Gitlab::Database::QueryAnalyzer.new.hook! if Gitlab.dev_or_test_env? | ||||
| if Gitlab.dev_or_test_env? || Gitlab::Utils.to_boolean(ENV['GITLAB_ENABLE_QUERY_ANALYZERS'], default: false) | ||||
|   Gitlab::Database::QueryAnalyzer.instance.hook! | ||||
| end | ||||
|  |  | |||
|  | @ -2599,33 +2599,12 @@ faster-test-job: | |||
|     - echo "Running tests..." | ||||
| ``` | ||||
| 
 | ||||
| ### `artifacts` | ||||
| ### `dependencies` | ||||
| 
 | ||||
| Use `artifacts` to specify a list of files and directories that are | ||||
| attached to the job when it [succeeds, fails, or always](#artifactswhen). | ||||
| 
 | ||||
| The artifacts are sent to GitLab after the job finishes. They are | ||||
| available for download in the GitLab UI if the size is not | ||||
| larger than the [maximum artifact size](../../user/gitlab_com/index.md#gitlab-cicd). | ||||
| 
 | ||||
| By default, jobs in later stages automatically download all the artifacts created | ||||
| by jobs in earlier stages. You can control artifact download behavior in jobs with | ||||
| [`dependencies`](#dependencies). | ||||
| 
 | ||||
| When using the [`needs`](#artifact-downloads-with-needs) keyword, jobs can only download | ||||
| artifacts from the jobs defined in the `needs` configuration. | ||||
| 
 | ||||
| Job artifacts are only collected for successful jobs by default, and | ||||
| artifacts are restored after [caches](#cache). | ||||
| 
 | ||||
| [Read more about artifacts](../pipelines/job_artifacts.md). | ||||
| 
 | ||||
| #### `dependencies` | ||||
| 
 | ||||
| Use the `dependencies` keyword to define a list of jobs to fetch artifacts from. | ||||
| Use the `dependencies` keyword to define a list of jobs to fetch [artifacts](#artifacts) from. | ||||
| You can also set a job to download no artifacts at all. | ||||
| 
 | ||||
| If you do not use `dependencies`, all `artifacts` from previous stages are passed to each job. | ||||
| If you do not use `dependencies`, all artifacts from previous stages are passed to each job. | ||||
| 
 | ||||
| **Keyword type**: Job keyword. You can use it only as part of a job. | ||||
| 
 | ||||
|  | @ -2681,6 +2660,27 @@ the [stage](#stages) precedence. | |||
| - If the artifacts of a dependent job are [expired](#artifactsexpire_in) or | ||||
|   [deleted](../pipelines/job_artifacts.md#delete-job-artifacts), then the job fails. | ||||
| 
 | ||||
| ### `artifacts` | ||||
| 
 | ||||
| Use `artifacts` to specify a list of files and directories that are | ||||
| attached to the job when it [succeeds, fails, or always](#artifactswhen). | ||||
| 
 | ||||
| The artifacts are sent to GitLab after the job finishes. They are | ||||
| available for download in the GitLab UI if the size is not | ||||
| larger than the [maximum artifact size](../../user/gitlab_com/index.md#gitlab-cicd). | ||||
| 
 | ||||
| By default, jobs in later stages automatically download all the artifacts created | ||||
| by jobs in earlier stages. You can control artifact download behavior in jobs with | ||||
| [`dependencies`](#dependencies). | ||||
| 
 | ||||
| When using the [`needs`](#artifact-downloads-with-needs) keyword, jobs can only download | ||||
| artifacts from the jobs defined in the `needs` configuration. | ||||
| 
 | ||||
| Job artifacts are only collected for successful jobs by default, and | ||||
| artifacts are restored after [caches](#cache). | ||||
| 
 | ||||
| [Read more about artifacts](../pipelines/job_artifacts.md). | ||||
| 
 | ||||
| #### `artifacts:exclude` | ||||
| 
 | ||||
| > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/15122) in GitLab 13.1 | ||||
|  |  | |||
|  | @ -118,8 +118,9 @@ SSO has the following effects when enabled: | |||
| 
 | ||||
| - For groups, users can't share a project in the group outside the top-level group, | ||||
|   even if the project is forked. | ||||
| - For a Git activity, users must be signed-in through SSO before they can push to or | ||||
| - For Git activity over SSH and HTTPS, users must have at least one active session signed-in through SSO before they can push to or | ||||
|   pull from a GitLab repository.  | ||||
| - Credentials that are not tied to regular users (for example, access tokens and deploy keys) do not have the SSO check enforced. | ||||
| - Users must be signed-in through SSO before they can pull images using the [Dependency Proxy](../../packages/dependency_proxy/index.md). | ||||
| <!-- Add bullet for API activity when https://gitlab.com/gitlab-org/gitlab/-/issues/9152 is complete --> | ||||
| 
 | ||||
|  |  | |||
|  | @ -66,7 +66,7 @@ module Gitlab | |||
|                 # times before using the primary instead. | ||||
|                 will_retry = conflict_retried < @host_list.length * 3 | ||||
| 
 | ||||
|                 LoadBalancing::Logger.warn( | ||||
|                 ::Gitlab::Database::LoadBalancing::Logger.warn( | ||||
|                   event: :host_query_conflict, | ||||
|                   message: 'Query conflict on host', | ||||
|                   conflict_retried: conflict_retried, | ||||
|  | @ -91,7 +91,7 @@ module Gitlab | |||
|             end | ||||
|           end | ||||
| 
 | ||||
|           LoadBalancing::Logger.warn( | ||||
|           ::Gitlab::Database::LoadBalancing::Logger.warn( | ||||
|             event: :no_secondaries_available, | ||||
|             message: 'No secondaries were available, using primary instead', | ||||
|             conflict_retried: conflict_retried, | ||||
|  |  | |||
|  | @ -13,7 +13,7 @@ module Gitlab | |||
|           job['load_balancing_strategy'] = strategy.to_s | ||||
| 
 | ||||
|           if use_primary?(strategy) | ||||
|             Session.current.use_primary! | ||||
|             ::Gitlab::Database::LoadBalancing::Session.current.use_primary! | ||||
|           elsif strategy == :retry | ||||
|             raise JobReplicaNotUpToDate, "Sidekiq job #{worker_class} JID-#{job['jid']} couldn't use the replica."\ | ||||
|               "  Replica was not up to date." | ||||
|  | @ -29,8 +29,8 @@ module Gitlab | |||
|         private | ||||
| 
 | ||||
|         def clear | ||||
|           LoadBalancing.release_hosts | ||||
|           Session.clear_session | ||||
|           ::Gitlab::Database::LoadBalancing.release_hosts | ||||
|           ::Gitlab::Database::LoadBalancing::Session.clear_session | ||||
|         end | ||||
| 
 | ||||
|         def use_primary?(strategy) | ||||
|  | @ -66,7 +66,7 @@ module Gitlab | |||
|         def legacy_wal_location(job) | ||||
|           wal_location = job['database_write_location'] || job['database_replica_location'] | ||||
| 
 | ||||
|           { Gitlab::Database::MAIN_DATABASE_NAME.to_sym => wal_location } if wal_location | ||||
|           { ::Gitlab::Database::MAIN_DATABASE_NAME.to_sym => wal_location } if wal_location | ||||
|         end | ||||
| 
 | ||||
|         def load_balancing_available?(worker_class) | ||||
|  | @ -90,7 +90,7 @@ module Gitlab | |||
|         end | ||||
| 
 | ||||
|         def databases_in_sync?(wal_locations) | ||||
|           LoadBalancing.each_load_balancer.all? do |lb| | ||||
|           ::Gitlab::Database::LoadBalancing.each_load_balancer.all? do |lb| | ||||
|             if (location = wal_locations[lb.name]) | ||||
|               lb.select_up_to_date_host(location) | ||||
|             else | ||||
|  |  | |||
|  | @ -5,6 +5,8 @@ module Gitlab | |||
|     # The purpose of this class is to implement a various query analyzers based on `pg_query` | ||||
|     # And process them all via `Gitlab::Database::QueryAnalyzers::*` | ||||
|     class QueryAnalyzer | ||||
|       include ::Singleton | ||||
| 
 | ||||
|       ANALYZERS = [].freeze | ||||
| 
 | ||||
|       Parsed = Struct.new( | ||||
|  |  | |||
|  | @ -1,7 +1,6 @@ | |||
| import { | ||||
|   isGid, | ||||
|   getIdFromGraphQLId, | ||||
|   getZeroBasedIdFromGraphQLId, | ||||
|   convertToGraphQLId, | ||||
|   convertToGraphQLIds, | ||||
|   convertFromGraphQLIds, | ||||
|  | @ -54,7 +53,7 @@ describe('getIdFromGraphQLId', () => { | |||
|     }, | ||||
|     { | ||||
|       input: 'gid://gitlab/Environments/0', | ||||
|       output: null, | ||||
|       output: 0, | ||||
|     }, | ||||
|     { | ||||
|       input: 'gid://gitlab/Environments/123', | ||||
|  | @ -71,55 +70,6 @@ describe('getIdFromGraphQLId', () => { | |||
|   }); | ||||
| }); | ||||
| 
 | ||||
| describe('getZeroBasedIdFromGraphQLId', () => { | ||||
|   [ | ||||
|     { | ||||
|       input: '', | ||||
|       output: null, | ||||
|     }, | ||||
|     { | ||||
|       input: null, | ||||
|       output: null, | ||||
|     }, | ||||
|     { | ||||
|       input: 2, | ||||
|       output: 2, | ||||
|     }, | ||||
|     { | ||||
|       input: 'gid://', | ||||
|       output: null, | ||||
|     }, | ||||
|     { | ||||
|       input: 'gid://gitlab/', | ||||
|       output: null, | ||||
|     }, | ||||
|     { | ||||
|       input: 'gid://gitlab/Environments', | ||||
|       output: null, | ||||
|     }, | ||||
|     { | ||||
|       input: 'gid://gitlab/Environments/', | ||||
|       output: null, | ||||
|     }, | ||||
|     { | ||||
|       input: 'gid://gitlab/Environments/0', | ||||
|       output: 0, | ||||
|     }, | ||||
|     { | ||||
|       input: 'gid://gitlab/Environments/123', | ||||
|       output: 123, | ||||
|     }, | ||||
|     { | ||||
|       input: 'gid://gitlab/DesignManagement::Version/2', | ||||
|       output: 2, | ||||
|     }, | ||||
|   ].forEach(({ input, output }) => { | ||||
|     it(`getZeroBasedIdFromGraphQLId returns ${output} when passed ${input}`, () => { | ||||
|       expect(getZeroBasedIdFromGraphQLId(input)).toBe(output); | ||||
|     }); | ||||
|   }); | ||||
| }); | ||||
| 
 | ||||
| describe('convertToGraphQLId', () => { | ||||
|   it('combines $type and $id into $result', () => { | ||||
|     expect(convertToGraphQLId(mockType, mockId)).toBe(mockGid); | ||||
|  |  | |||
|  | @ -65,7 +65,7 @@ RSpec.describe Gitlab::Database::QueryAnalyzer do | |||
| 
 | ||||
|     def process_sql(sql) | ||||
|       ApplicationRecord.load_balancer.read_write do |connection| | ||||
|         described_class.new.send(:process_sql, sql, connection) | ||||
|         described_class.instance.send(:process_sql, sql, connection) | ||||
|       end | ||||
|     end | ||||
|   end | ||||
|  |  | |||
|  | @ -0,0 +1,58 @@ | |||
| # frozen_string_literal: true | ||||
| 
 | ||||
| require 'spec_helper' | ||||
| 
 | ||||
| RSpec.describe GoogleCloud::ServiceAccountsService do | ||||
|   let_it_be(:project) { create(:project) } | ||||
| 
 | ||||
|   let(:service) { described_class.new(project) } | ||||
| 
 | ||||
|   describe 'find_for_project' do | ||||
|     context 'when a project does not have GCP service account vars' do | ||||
|       before do | ||||
|         project.variables.build(key: 'blah', value: 'foo', environment_scope: 'world') | ||||
|         project.save! | ||||
|       end | ||||
| 
 | ||||
|       it 'returns an empty list' do | ||||
|         expect(service.find_for_project.length).to eq(0) | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     context 'when a project has GCP service account ci vars' do | ||||
|       before do | ||||
|         project.variables.build(environment_scope: '*', key: 'GCP_PROJECT_ID', value: 'prj1') | ||||
|         project.variables.build(environment_scope: '*', key: 'GCP_SERVICE_ACCOUNT_KEY', value: 'mock') | ||||
|         project.variables.build(environment_scope: 'staging', key: 'GCP_PROJECT_ID', value: 'prj2') | ||||
|         project.variables.build(environment_scope: 'staging', key: 'GCP_SERVICE_ACCOUNT', value: 'mock') | ||||
|         project.variables.build(environment_scope: 'production', key: 'GCP_PROJECT_ID', value: 'prj3') | ||||
|         project.variables.build(environment_scope: 'production', key: 'GCP_SERVICE_ACCOUNT', value: 'mock') | ||||
|         project.variables.build(environment_scope: 'production', key: 'GCP_SERVICE_ACCOUNT_KEY', value: 'mock') | ||||
|         project.save! | ||||
|       end | ||||
| 
 | ||||
|       it 'returns a list of service accounts' do | ||||
|         list = service.find_for_project | ||||
| 
 | ||||
|         aggregate_failures 'testing list of service accounts' do | ||||
|           expect(list.length).to eq(3) | ||||
| 
 | ||||
|           expect(list.first[:environment]).to eq('*') | ||||
|           expect(list.first[:gcp_project]).to eq('prj1') | ||||
|           expect(list.first[:service_account_exists]).to eq(false) | ||||
|           expect(list.first[:service_account_key_exists]).to eq(true) | ||||
| 
 | ||||
|           expect(list.second[:environment]).to eq('staging') | ||||
|           expect(list.second[:gcp_project]).to eq('prj2') | ||||
|           expect(list.second[:service_account_exists]).to eq(true) | ||||
|           expect(list.second[:service_account_key_exists]).to eq(false) | ||||
| 
 | ||||
|           expect(list.third[:environment]).to eq('production') | ||||
|           expect(list.third[:gcp_project]).to eq('prj3') | ||||
|           expect(list.third[:service_account_exists]).to eq(true) | ||||
|           expect(list.third[:service_account_key_exists]).to eq(true) | ||||
|         end | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
		Loading…
	
		Reference in New Issue