diff options
17 files changed, 166 insertions, 106 deletions
diff --git a/app/assets/javascripts/boards/components/board_form.vue b/app/assets/javascripts/boards/components/board_form.vue index 45381ab1021..6ad57fd8985 100644 --- a/app/assets/javascripts/boards/components/board_form.vue +++ b/app/assets/javascripts/boards/components/board_form.vue @@ -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, }; }, diff --git a/app/assets/javascripts/boards/components/boards_selector.vue b/app/assets/javascripts/boards/components/boards_selector.vue index 2f2ac5409e0..71facba1378 100644 --- a/app/assets/javascripts/boards/components/boards_selector.vue +++ b/app/assets/javascripts/boards/components/boards_selector.vue @@ -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" > diff --git a/app/assets/javascripts/boards/mount_multiple_boards_switcher.js b/app/assets/javascripts/boards/mount_multiple_boards_switcher.js index 26dd8b99f98..ed32579a9c3 100644 --- a/app/assets/javascripts/boards/mount_multiple_boards_switcher.js +++ b/app/assets/javascripts/boards/mount_multiple_boards_switcher.js @@ -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 = {}) => { diff --git a/app/assets/javascripts/google_cloud/index.js b/app/assets/javascripts/google_cloud/index.js index a2838605219..a156a632e9a 100644 --- a/app/assets/javascripts/google_cloud/index.js +++ b/app/assets/javascripts/google_cloud/index.js @@ -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) }); }; diff --git a/app/assets/javascripts/graphql_shared/utils.js b/app/assets/javascripts/graphql_shared/utils.js index e459ebf629f..8fb70eb59bd 100644 --- a/app/assets/javascripts/graphql_shared/utils.js +++ b/app/assets/javascripts/graphql_shared/utils.js @@ -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; }; diff --git a/app/controllers/projects/google_cloud_controller.rb b/app/controllers/projects/google_cloud_controller.rb index c62b80fe7ac..7257ed1ef6f 100644 --- a/app/controllers/projects/google_cloud_controller.rb +++ b/app/controllers/projects/google_cloud_controller.rb @@ -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 diff --git a/app/services/google_cloud/service_accounts_service.rb b/app/services/google_cloud/service_accounts_service.rb new file mode 100644 index 00000000000..29ed69693b0 --- /dev/null +++ b/app/services/google_cloud/service_accounts_service.rb @@ -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 diff --git a/app/views/projects/google_cloud/index.html.haml b/app/views/projects/google_cloud/index.html.haml index 72736327988..69e481501d5 100644 --- a/app/views/projects/google_cloud/index.html.haml +++ b/app/views/projects/google_cloud/index.html.haml @@ -3,4 +3,4 @@ - @content_class = "limit-container-width" unless fluid_layout -#js-google-cloud +#js-google-cloud{ data: @js_data } diff --git a/config/initializers/database_query_analyzers.rb b/config/initializers/database_query_analyzers.rb index a29cd6552ff..7d4802b76c2 100644 --- a/config/initializers/database_query_analyzers.rb +++ b/config/initializers/database_query_analyzers.rb @@ -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 diff --git a/doc/ci/yaml/index.md b/doc/ci/yaml/index.md index ec135661a60..9458fe650bc 100644 --- a/doc/ci/yaml/index.md +++ b/doc/ci/yaml/index.md @@ -2599,33 +2599,12 @@ faster-test-job: - echo "Running tests..." ``` -### `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). - -#### `dependencies` +### `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 diff --git a/doc/user/group/saml_sso/index.md b/doc/user/group/saml_sso/index.md index 8c837f86065..402007b85b2 100644 --- a/doc/user/group/saml_sso/index.md +++ b/doc/user/group/saml_sso/index.md @@ -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 - pull from a GitLab repository. +- 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 --> diff --git a/lib/gitlab/database/load_balancing/load_balancer.rb b/lib/gitlab/database/load_balancing/load_balancer.rb index 78059ef00f8..1dc39abcf1e 100644 --- a/lib/gitlab/database/load_balancing/load_balancer.rb +++ b/lib/gitlab/database/load_balancing/load_balancer.rb @@ -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, diff --git a/lib/gitlab/database/load_balancing/sidekiq_server_middleware.rb b/lib/gitlab/database/load_balancing/sidekiq_server_middleware.rb index f0c7016032b..b9acc36b4cc 100644 --- a/lib/gitlab/database/load_balancing/sidekiq_server_middleware.rb +++ b/lib/gitlab/database/load_balancing/sidekiq_server_middleware.rb @@ -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 diff --git a/lib/gitlab/database/query_analyzer.rb b/lib/gitlab/database/query_analyzer.rb index 830ad1383c0..6cbeefa0aae 100644 --- a/lib/gitlab/database/query_analyzer.rb +++ b/lib/gitlab/database/query_analyzer.rb @@ -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( diff --git a/spec/frontend/graphql_shared/utils_spec.js b/spec/frontend/graphql_shared/utils_spec.js index 4dffb4dc73a..9f478eedbfb 100644 --- a/spec/frontend/graphql_shared/utils_spec.js +++ b/spec/frontend/graphql_shared/utils_spec.js @@ -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); diff --git a/spec/lib/gitlab/database/query_analyzer_spec.rb b/spec/lib/gitlab/database/query_analyzer_spec.rb index 3567bd6a058..f0a3021927b 100644 --- a/spec/lib/gitlab/database/query_analyzer_spec.rb +++ b/spec/lib/gitlab/database/query_analyzer_spec.rb @@ -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 diff --git a/spec/services/google_cloud/service_accounts_service_spec.rb b/spec/services/google_cloud/service_accounts_service_spec.rb new file mode 100644 index 00000000000..a0d09affa72 --- /dev/null +++ b/spec/services/google_cloud/service_accounts_service_spec.rb @@ -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 |