diff options
65 files changed, 1142 insertions, 231 deletions
diff --git a/.browserslistrc b/.browserslistrc index 7c2e6fb4b75..a608ac7c734 100644 --- a/.browserslistrc +++ b/.browserslistrc @@ -6,11 +6,11 @@ # - We should support the latest ESR of Firefox: 78, because it used quite a lot. # - We use Edge/Chrome >= 84 because 83 had an annoying bug which would mean we # need to polyfill Array.reduce: https://bugs.chromium.org/p/chromium/issues/detail?id=1049982 -# - Safari 13 because it is the second latest major version of Safari +# - Safari 13.1 because it is the current minor version of the previous major version # # See also this epic: https://gitlab.com/groups/gitlab-org/-/epics/3957 # chrome >= 84 edge >= 84 firefox >= 78 -safari >= 13.0.4 +safari >= 13.1 diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml index 8d030b33cbc..c97ef4fc868 100644 --- a/.gitlab/ci/rules.gitlab-ci.yml +++ b/.gitlab/ci/rules.gitlab-ci.yml @@ -224,6 +224,7 @@ .code-patterns: &code-patterns - "{package.json,yarn.lock}" + - ".browserslistrc" - "babel.config.js" - "jest.config.{base,integration,unit}.js" - ".csscomb.json" @@ -249,6 +250,7 @@ .code-backstage-patterns: &code-backstage-patterns - "{package.json,yarn.lock}" + - ".browserslistrc" - "babel.config.js" - "jest.config.{base,integration,unit}.js" - ".csscomb.json" @@ -277,6 +279,7 @@ .code-qa-patterns: &code-qa-patterns - "{package.json,yarn.lock}" + - ".browserslistrc" - "babel.config.js" - "jest.config.{base,integration,unit}.js" - ".csscomb.json" @@ -301,6 +304,7 @@ .code-backstage-qa-patterns: &code-backstage-qa-patterns - "{package.json,yarn.lock}" + - ".browserslistrc" - "babel.config.js" - "jest.config.{base,integration,unit}.js" - ".csscomb.json" diff --git a/app/assets/javascripts/diffs/components/diff_row.vue b/app/assets/javascripts/diffs/components/diff_row.vue index 734f9258c58..c310bd9f31a 100644 --- a/app/assets/javascripts/diffs/components/diff_row.vue +++ b/app/assets/javascripts/diffs/components/diff_row.vue @@ -140,6 +140,16 @@ export default { }, (line) => line.type, ), + lineContent: memoize( + (line) => { + if (line.isConflictMarker) { + return line.type === CONFLICT_MARKER_THEIR ? 'HEAD//our changes' : 'origin//their changes'; + } + + return line.rich_text; + }, + (line) => line.line_code, + ), CONFLICT_MARKER, CONFLICT_MARKER_THEIR, CONFLICT_OUR, @@ -261,15 +271,14 @@ export default { </div> <div :key="props.line.left.line_code" - :class="[$options.parallelViewLeftLineType(props), { parallel: !props.inline }]" + :class="[ + $options.parallelViewLeftLineType(props), + { parallel: !props.inline, 'gl-font-weight-bold': props.line.left.isConflictMarker }, + ]" class="diff-td line_content with-coverage left-side" data-testid="left-content" - > - <strong v-if="props.line.left.isConflictMarker">{{ - $options.conflictText(props.line.left) - }}</strong> - <span v-else v-html="props.line.left.rich_text"></span> - </div> + v-html="$options.lineContent(props.line.left)" + ></div> </template> <template v-else-if=" @@ -386,15 +395,12 @@ export default { { hll: props.isHighlighted, hll: props.isCommented, + 'gl-font-weight-bold': props.line.right.type === $options.CONFLICT_MARKER_THEIR, }, ]" class="diff-td line_content with-coverage right-side parallel" - > - <strong v-if="props.line.right.type === $options.CONFLICT_MARKER_THEIR">{{ - $options.conflictText(props.line.right) - }}</strong> - <span v-else v-html="props.line.right.rich_text"></span> - </div> + v-html="$options.lineContent(props.line.right)" + ></div> </template> <template v-else> <div data-testid="right-empty-cell" class="diff-td diff-line-num old_line empty-cell"></div> diff --git a/app/assets/javascripts/diffs/components/pre_renderer.vue b/app/assets/javascripts/diffs/components/pre_renderer.vue index 4e69a2c064e..c357aa2d924 100644 --- a/app/assets/javascripts/diffs/components/pre_renderer.vue +++ b/app/assets/javascripts/diffs/components/pre_renderer.vue @@ -17,6 +17,7 @@ export default { }, mounted() { this.width = this.$el.parentNode.offsetWidth; + window.test = this; this.$_itemsWithSizeWatcher = this.$watch('vscrollParent.itemsWithSize', async () => { await this.$nextTick(); @@ -27,6 +28,14 @@ export default { this.startedRender = true; requestIdleCallback(() => { this.nextItem = nextItem; + + if (this.nextIndex === this.maxLength - 1) { + this.$nextTick(() => { + if (this.vscrollParent.itemsWithSize[this.maxLength - 1].size !== 0) { + this.clearRendering(); + } + }); + } }); } else if (this.startedRender) { this.clearRendering(); diff --git a/app/assets/javascripts/pages/projects/index.js b/app/assets/javascripts/pages/projects/index.js index 1eab3becbc3..8ec6e5e66b3 100644 --- a/app/assets/javascripts/pages/projects/index.js +++ b/app/assets/javascripts/pages/projects/index.js @@ -1,7 +1,9 @@ import ShortcutsNavigation from '../../behaviors/shortcuts/shortcuts_navigation'; +import initTerraformNotification from '../../projects/terraform_notification'; import { initSidebarTracking } from '../shared/nav/sidebar_tracking'; import Project from './project'; new Project(); // eslint-disable-line no-new new ShortcutsNavigation(); // eslint-disable-line no-new initSidebarTracking(); +initTerraformNotification(); diff --git a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue index 11e6b4577e0..6fcaa3ab04b 100644 --- a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue +++ b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue @@ -104,6 +104,11 @@ export default { required: false, default: '', }, + issuesHelpPath: { + type: String, + required: false, + default: '', + }, lfsHelpPath: { type: String, required: false, @@ -438,8 +443,13 @@ export default { > <project-setting-row ref="issues-settings" + :help-path="issuesHelpPath" :label="$options.i18n.issuesLabel" - :help-text="s__('ProjectSettings|Lightweight issue tracking system.')" + :help-text=" + s__( + 'ProjectSettings|Flexible tool to collaboratively develop ideas and plan work in this project.', + ) + " > <project-feature-setting v-model="issuesAccessLevel" diff --git a/app/assets/javascripts/pipeline_editor/components/editor/ci_config_merged_preview.vue b/app/assets/javascripts/pipeline_editor/components/editor/ci_config_merged_preview.vue index 73c91839f04..853e839a7ab 100644 --- a/app/assets/javascripts/pipeline_editor/components/editor/ci_config_merged_preview.vue +++ b/app/assets/javascripts/pipeline_editor/components/editor/ci_config_merged_preview.vue @@ -1,27 +1,18 @@ <script> -import { GlAlert, GlLink, GlSprintf, GlIcon } from '@gitlab/ui'; +import { GlIcon } from '@gitlab/ui'; import { uniqueId } from 'lodash'; -import { helpPagePath } from '~/helpers/help_page_helper'; import { s__ } from '~/locale'; import SourceEditor from '~/vue_shared/components/source_editor.vue'; -import getCurrentBranch from '../../graphql/queries/client/current_branch.graphql'; export default { i18n: { viewOnlyMessage: s__('Pipelines|Merged YAML is view only'), - unavailableDefaultTitle: s__('Pipelines|Merged YAML unavailable'), - unavailableDefaultText: s__( - 'Pipelines|The merged YAML view is only available for the default branch. %{linkStart}Learn more.%{linkEnd}', - ), }, components: { SourceEditor, - GlAlert, GlIcon, - GlLink, - GlSprintf, }, - inject: ['ciConfigPath', 'defaultBranch'], + inject: ['ciConfigPath'], props: { ciConfigData: { type: Object, @@ -33,15 +24,6 @@ export default { failureType: null, }; }, - // This is not the best practice, don't copy me (@samdbeckham) - // This is a temporary workaround to unblock a release. - // See this comment for more information on this approach - // https://gitlab.com/gitlab-org/gitlab/-/merge_requests/65972#note_626095648 - apollo: { - currentBranch: { - query: getCurrentBranch, - }, - }, computed: { fileGlobalId() { return `${this.ciConfigPath}-${uniqueId()}`; @@ -49,44 +31,24 @@ export default { mergedYaml() { return this.ciConfigData.mergedYaml; }, - isOnDefaultBranch() { - return this.currentBranch === this.defaultBranch; - }, - expandedConfigHelpPath() { - return helpPagePath('ci/pipeline_editor/index', { anchor: 'view-expanded-configuration' }); - }, }, }; </script> <template> <div> - <div v-if="isOnDefaultBranch"> - <div class="gl-display-flex gl-align-items-center"> - <gl-icon :size="16" name="lock" class="gl-text-gray-500 gl-mr-3" /> - {{ $options.i18n.viewOnlyMessage }} - </div> - <div class="gl-mt-3 gl-border-solid gl-border-gray-100 gl-border-1"> - <source-editor - ref="editor" - :value="mergedYaml" - :file-name="ciConfigPath" - :file-global-id="fileGlobalId" - :editor-options="{ readOnly: true }" - v-on="$listeners" - /> - </div> + <div class="gl-display-flex gl-align-items-center"> + <gl-icon :size="16" name="lock" class="gl-text-gray-500 gl-mr-3" /> + {{ $options.i18n.viewOnlyMessage }} + </div> + <div class="gl-mt-3 gl-border-solid gl-border-gray-100 gl-border-1"> + <source-editor + ref="editor" + :value="mergedYaml" + :file-name="ciConfigPath" + :file-global-id="fileGlobalId" + :editor-options="{ readOnly: true }" + v-on="$listeners" + /> </div> - <gl-alert - v-else - variant="info" - :dismissible="false" - :title="$options.i18n.unavailableDefaultTitle" - > - <gl-sprintf :message="$options.i18n.unavailableDefaultText"> - <template #link="{ content }"> - <gl-link :href="expandedConfigHelpPath" target="_blank">{{ content }}</gl-link> - </template> - </gl-sprintf> - </gl-alert> </div> </template> diff --git a/app/assets/javascripts/pipeline_editor/graphql/queries/ci_config.graphql b/app/assets/javascripts/pipeline_editor/graphql/queries/ci_config.graphql index 30c18a96536..df7de6a1f54 100644 --- a/app/assets/javascripts/pipeline_editor/graphql/queries/ci_config.graphql +++ b/app/assets/javascripts/pipeline_editor/graphql/queries/ci_config.graphql @@ -1,7 +1,7 @@ #import "~/pipelines/graphql/fragments/pipeline_stages_connection.fragment.graphql" -query getCiConfigData($projectPath: ID!, $content: String!) { - ciConfig(projectPath: $projectPath, content: $content) { +query getCiConfigData($projectPath: ID!, $sha: String, $content: String!) { + ciConfig(projectPath: $projectPath, sha: $sha, content: $content) { errors mergedYaml status diff --git a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue index 758c8c51a5b..0e8a6805a59 100644 --- a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue +++ b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue @@ -20,6 +20,7 @@ import updateCommitShaMutation from './graphql/mutations/update_commit_sha.mutat import getBlobContent from './graphql/queries/blob_content.graphql'; import getCiConfigData from './graphql/queries/ci_config.graphql'; import getAppStatus from './graphql/queries/client/app_status.graphql'; +import getCommitSha from './graphql/queries/client/commit_sha.graphql'; import getCurrentBranch from './graphql/queries/client/current_branch.graphql'; import getIsNewCiConfigFile from './graphql/queries/client/is_new_ci_config_file.graphql'; import getTemplate from './graphql/queries/get_starter_template.query.graphql'; @@ -128,6 +129,7 @@ export default { variables() { return { projectPath: this.projectFullPath, + sha: this.commitSha, content: this.currentCiFileContent, }; }, @@ -153,6 +155,9 @@ export default { appStatus: { query: getAppStatus, }, + commitSha: { + query: getCommitSha, + }, currentBranch: { query: getCurrentBranch, }, diff --git a/app/assets/javascripts/projects/terraform_notification/components/terraform_notification.vue b/app/assets/javascripts/projects/terraform_notification/components/terraform_notification.vue new file mode 100644 index 00000000000..0b398eddc9c --- /dev/null +++ b/app/assets/javascripts/projects/terraform_notification/components/terraform_notification.vue @@ -0,0 +1,65 @@ +<script> +import { GlBanner } from '@gitlab/ui'; +import { helpPagePath } from '~/helpers/help_page_helper'; +import { parseBoolean, setCookie, getCookie } from '~/lib/utils/common_utils'; +import { s__ } from '~/locale'; + +export default { + name: 'TerraformNotification', + i18n: { + title: s__('TerraformBanner|Using Terraform? Try the GitLab Managed Terraform State'), + description: s__( + 'TerraformBanner|The GitLab managed Terraform state backend can store your Terraform state easily and securely, and spares you from setting up additional remote resources. Its features include: versioning, encryption of the state file both in transit and at rest, locking, and remote Terraform plan/apply execution.', + ), + buttonText: s__("TerraformBanner|Learn more about GitLab's Backend State"), + }, + components: { + GlBanner, + }, + props: { + projectId: { + type: Number, + required: true, + }, + }, + data() { + return { + isVisible: true, + }; + }, + computed: { + bannerDissmisedKey() { + return `terraform_notification_dismissed_for_project_${this.projectId}`; + }, + docsUrl() { + return helpPagePath('user/infrastructure/terraform_state'); + }, + }, + created() { + if (parseBoolean(getCookie(this.bannerDissmisedKey))) { + this.isVisible = false; + } + }, + methods: { + handleClose() { + setCookie(this.bannerDissmisedKey, true); + this.isVisible = false; + }, + }, +}; +</script> +<template> + <div v-if="isVisible"> + <div class="gl-py-5"> + <gl-banner + :title="$options.i18n.title" + :button-text="$options.i18n.buttonText" + :button-link="docsUrl" + variant="introduction" + @close="handleClose" + > + <p>{{ $options.i18n.description }}</p> + </gl-banner> + </div> + </div> +</template> diff --git a/app/assets/javascripts/projects/terraform_notification/index.js b/app/assets/javascripts/projects/terraform_notification/index.js new file mode 100644 index 00000000000..eb04f109a8e --- /dev/null +++ b/app/assets/javascripts/projects/terraform_notification/index.js @@ -0,0 +1,18 @@ +import Vue from 'vue'; +import TerraformNotification from './components/terraform_notification.vue'; + +export default () => { + const el = document.querySelector('.js-terraform-notification'); + + if (!el) { + return false; + } + + const { projectId } = el.dataset; + + return new Vue({ + el, + render: (createElement) => + createElement(TerraformNotification, { props: { projectId: Number(projectId) } }), + }); +}; diff --git a/app/assets/javascripts/security_configuration/components/auto_dev_ops_alert.vue b/app/assets/javascripts/security_configuration/components/auto_dev_ops_alert.vue new file mode 100644 index 00000000000..ce6a1b4888b --- /dev/null +++ b/app/assets/javascripts/security_configuration/components/auto_dev_ops_alert.vue @@ -0,0 +1,41 @@ +<script> +import { GlAlert, GlLink, GlSprintf } from '@gitlab/ui'; +import { s__ } from '~/locale'; + +export default { + components: { + GlSprintf, + GlAlert, + GlLink, + }, + inject: ['autoDevopsHelpPagePath', 'autoDevopsPath'], + i18n: { + primaryButtonText: s__('SecurityConfiguration|Enable Auto DevOps'), + body: s__( + 'SecurityConfiguration|Quickly enable all continuous testing and compliance tools by enabling %{linkStart}Auto DevOps%{linkEnd}', + ), + }, + methods: { + dismissMethod() { + this.$emit('dismiss'); + }, + }, +}; +</script> + +<template> + <gl-alert + variant="info" + :primary-button-link="autoDevopsPath" + :primary-button-text="$options.i18n.primaryButtonText" + @dismiss="dismissMethod" + > + <gl-sprintf :message="$options.i18n.body"> + <template #link="{ content }"> + <gl-link :href="autoDevopsHelpPagePath"> + {{ content }} + </gl-link> + </template> + </gl-sprintf> + </gl-alert> +</template> diff --git a/app/assets/javascripts/security_configuration/components/redesigned_app.vue b/app/assets/javascripts/security_configuration/components/redesigned_app.vue index 717a568e7d8..915da378a4f 100644 --- a/app/assets/javascripts/security_configuration/components/redesigned_app.vue +++ b/app/assets/javascripts/security_configuration/components/redesigned_app.vue @@ -2,6 +2,7 @@ import { GlTab, GlTabs, GlSprintf, GlLink } from '@gitlab/ui'; import { __, s__ } from '~/locale'; import UserCalloutDismisser from '~/vue_shared/components/user_callout_dismisser.vue'; +import AutoDevOpsAlert from './auto_dev_ops_alert.vue'; import FeatureCard from './feature_card.vue'; import SectionLayout from './section_layout.vue'; import UpgradeBanner from './upgrade_banner.vue'; @@ -31,6 +32,7 @@ export default { FeatureCard, SectionLayout, UpgradeBanner, + AutoDevOpsAlert, UserCalloutDismisser, }, props: { @@ -47,6 +49,16 @@ export default { required: false, default: false, }, + autoDevopsEnabled: { + type: Boolean, + required: false, + default: false, + }, + canEnableAutoDevops: { + type: Boolean, + required: false, + default: false, + }, gitlabCiHistoryPath: { type: String, required: false, @@ -67,16 +79,26 @@ export default { canViewCiHistory() { return Boolean(this.gitlabCiPresent && this.gitlabCiHistoryPath); }, + shouldShowDevopsAlert() { + return !this.autoDevopsEnabled && !this.gitlabCiPresent && this.canEnableAutoDevops; + }, }, }; </script> <template> <article> + <user-callout-dismisser + v-if="shouldShowDevopsAlert" + feature-name="security_configuration_devops_alert" + > + <template #default="{ dismiss, shouldShowCallout }"> + <auto-dev-ops-alert v-if="shouldShowCallout" class="gl-mt-3" @dismiss="dismiss" /> + </template> + </user-callout-dismisser> <header> <h1 class="gl-font-size-h1">{{ $options.i18n.securityConfiguration }}</h1> </header> - <user-callout-dismisser v-if="canUpgrade" feature-name="security_configuration_upgrade_banner"> <template #default="{ dismiss, shouldShowCallout }"> <upgrade-banner v-if="shouldShowCallout" @close="dismiss" /> diff --git a/app/assets/javascripts/security_configuration/index.js b/app/assets/javascripts/security_configuration/index.js index 0aafb2653d8..f05bd79258e 100644 --- a/app/assets/javascripts/security_configuration/index.js +++ b/app/assets/javascripts/security_configuration/index.js @@ -20,6 +20,8 @@ export const initRedesignedSecurityConfiguration = (el) => { features, latestPipelinePath, gitlabCiHistoryPath, + autoDevopsHelpPagePath, + autoDevopsPath, } = el.dataset; const { augmentedSecurityFeatures, augmentedComplianceFeatures } = augmentFeatures( @@ -34,6 +36,8 @@ export const initRedesignedSecurityConfiguration = (el) => { provide: { projectPath, upgradePath, + autoDevopsHelpPagePath, + autoDevopsPath, }, render(createElement) { return createElement(RedesignedSecurityConfigurationApp, { @@ -42,7 +46,11 @@ export const initRedesignedSecurityConfiguration = (el) => { augmentedSecurityFeatures, latestPipelinePath, gitlabCiHistoryPath, - ...parseBooleanDataAttributes(el, ['gitlabCiPresent']), + ...parseBooleanDataAttributes(el, [ + 'gitlabCiPresent', + 'autoDevopsEnabled', + 'canEnableAutoDevops', + ]), }, }); }, diff --git a/app/assets/stylesheets/utilities.scss b/app/assets/stylesheets/utilities.scss index 5e2ec774655..10334d771b8 100644 --- a/app/assets/stylesheets/utilities.scss +++ b/app/assets/stylesheets/utilities.scss @@ -125,17 +125,22 @@ } } -// Will be moved to @gitlab/ui in https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1462 -.gl-md-mt-11 { +// Will be moved to @gitlab/ui (without the !important) in https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1462 +// We only need the bang (!) version until the non-bang version is added to +// @gitlab/ui utitlities.scss. Once there, it will get loaded in the correct +// order to properly override `.gl-mt-6` which is used for narrower screen +// widths (currently that style gets added to the application.css stylesheet +// after this one, so it takes precedence). +.gl-md-mt-11\! { @media (min-width: $breakpoint-md) { - margin-top: $gl-spacing-scale-11; + margin-top: $gl-spacing-scale-11 !important; } } -// Same as above -.gl-md-pt-11 { +// Same as above (also without the !important) but for overriding `.gl-pt-6` +.gl-md-pt-11\! { @media (min-width: $breakpoint-md) { - padding-top: $gl-spacing-scale-11; + padding-top: $gl-spacing-scale-11 !important; } } @@ -160,13 +165,6 @@ } } -// Will be moved to @gitlab/ui in https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1463 -.gl-xs-mt-6 { - @media (max-width: $breakpoint-sm) { - margin-top: $gl-spacing-scale-6; - } -} - // Will be moved to @gitlab/ui in https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1165 .gl-xs-mb-4 { @media (max-width: $breakpoint-sm) { @@ -181,13 +179,6 @@ } } -// Will be moved to @gitlab/ui in https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1463 -.gl-xs-pt-6 { - @media (max-width: $breakpoint-sm) { - padding-top: $gl-spacing-scale-6; - } -} - // Will be moved to @gitlab/ui in https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1168 .gl-sm-pr-3 { @media (min-width: $breakpoint-sm) { diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 8df03c39690..752e91df9c4 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -350,6 +350,10 @@ module ProjectsHelper nil end + def show_terraform_banner?(project) + project.repository_languages.with_programming_language('HCL').exists? && project.terraform_states.empty? + end + private def tab_ability_map @@ -530,7 +534,8 @@ module ProjectsHelper pagesAvailable: Gitlab.config.pages.enabled, pagesAccessControlEnabled: Gitlab.config.pages.access_control, pagesAccessControlForced: ::Gitlab::Pages.access_control_is_forced?, - pagesHelpPath: help_page_path('user/project/pages/introduction', anchor: 'gitlab-pages-access-control') + pagesHelpPath: help_page_path('user/project/pages/introduction', anchor: 'gitlab-pages-access-control'), + issuesHelpPath: help_page_path('user/project/issues/index') } end diff --git a/app/models/repository_language.rb b/app/models/repository_language.rb index b7a96211fb1..2816aa4cc5b 100644 --- a/app/models/repository_language.rb +++ b/app/models/repository_language.rb @@ -8,6 +8,10 @@ class RepositoryLanguage < ApplicationRecord default_scope { includes(:programming_language) } # rubocop:disable Cop/DefaultScope + scope :with_programming_language, ->(name) do + joins(:programming_language).merge(ProgrammingLanguage.with_name_case_insensitive(name)) + end + validates :project, presence: true validates :share, inclusion: { in: 0..100, message: "The share of a language is between 0 and 100" } validates :programming_language, uniqueness: { scope: :project_id } diff --git a/app/models/user_callout.rb b/app/models/user_callout.rb index e14ba035cc8..854992dcd1e 100644 --- a/app/models/user_callout.rb +++ b/app/models/user_callout.rb @@ -34,7 +34,8 @@ class UserCallout < ApplicationRecord security_configuration_upgrade_banner: 32, cloud_licensing_subscription_activation_banner: 33, # EE-only trial_status_reminder_d14: 34, # EE-only - trial_status_reminder_d3: 35 # EE-only + trial_status_reminder_d3: 35, # EE-only + security_configuration_devops_alert: 36 # EE-only } validates :user, presence: true diff --git a/app/views/admin/application_settings/_help_page.html.haml b/app/views/admin/application_settings/_help_page.html.haml index e7816f5a1c0..b71e8ca831e 100644 --- a/app/views/admin/application_settings/_help_page.html.haml +++ b/app/views/admin/application_settings/_help_page.html.haml @@ -5,18 +5,18 @@ = render_if_exists 'admin/application_settings/help_text_setting', form: f .form-group - = f.label :help_page_text, class: 'label-bold' + = f.label :help_page_text, _('Additional text to show on the Help page'), class: 'label-bold' = f.text_area :help_page_text, class: 'form-control gl-form-input', rows: 4 .form-text.text-muted= _('Markdown enabled') .form-group .form-check = f.check_box :help_page_hide_commercial_content, class: 'form-check-input' = f.label :help_page_hide_commercial_content, class: 'form-check-label' do - = _('Hide marketing-related entries from help') + = _('Hide marketing-related entries from the Help page.') .form-group = f.label :help_page_support_url, _('Support page URL'), class: 'label-bold' = f.text_field :help_page_support_url, class: 'form-control gl-form-input', placeholder: 'http://company.example.com/getting-help', :'aria-describedby' => 'support_help_block' - %span.form-text.text-muted#support_help_block= _('Alternate support URL for help page and help dropdown') + %span.form-text.text-muted#support_help_block= _('Alternate support URL for Help page and Help dropdown') - if show_documentation_base_url_field? .form-group diff --git a/app/views/admin/application_settings/preferences.html.haml b/app/views/admin/application_settings/preferences.html.haml index 52aed0b36e7..0dfc3d7a60d 100644 --- a/app/views/admin/application_settings/preferences.html.haml +++ b/app/views/admin/application_settings/preferences.html.haml @@ -27,11 +27,12 @@ %section.settings.as-help-page.no-animate#js-help-settings{ class: ('expanded' if expanded_by_default?) } .settings-header %h4 - = _('Help page') + = _('Sign-in and Help page') %button.btn.gl-button.btn-default.js-settings-toggle{ type: 'button' } = expanded_by_default? ? _('Collapse') : _('Expand') %p - = _('Help page text and support page url.') + = _('Additional text for the sign-in and Help page.') + = link_to s_('Learn more.'), help_page_path('user/admin_area/settings/help_page.md'), target: '_blank', rel: 'noopener noreferrer' .settings-content = render 'help_page' diff --git a/app/views/help/index.html.haml b/app/views/help/index.html.haml index a56eaaf685f..95888963947 100644 --- a/app/views/help/index.html.haml +++ b/app/views/help/index.html.haml @@ -38,7 +38,7 @@ .card-header = _('Quick help') %ul.content-list - %li= link_to _('See our website for getting help'), support_url + %li= link_to _('See our website for help'), support_url %li %button.btn-blank.btn-link.js-trigger-search-bar{ type: 'button' } = _('Use the search bar on the top of this page') diff --git a/app/views/layouts/minimal.html.haml b/app/views/layouts/minimal.html.haml index ec909fcc279..b5cb8f2af37 100644 --- a/app/views/layouts/minimal.html.haml +++ b/app/views/layouts/minimal.html.haml @@ -8,7 +8,7 @@ = render 'peek/bar' = render "layouts/header/empty" .layout-page - .content-wrapper.content-wrapper-margin.gl-md-pt-11.gl-xs-pt-6 + .content-wrapper.content-wrapper-margin.gl-pt-6{ class: 'gl-md-pt-11!' } .alert-wrapper.gl-force-block-formatting-context = render "layouts/broadcast" .limit-container-width{ class: container_class } diff --git a/app/views/projects/_flash_messages.html.haml b/app/views/projects/_flash_messages.html.haml index f9222387e97..7395495b537 100644 --- a/app/views/projects/_flash_messages.html.haml +++ b/app/views/projects/_flash_messages.html.haml @@ -9,3 +9,4 @@ = render 'shared/auto_devops_implicitly_enabled_banner', project: project = render_if_exists 'projects/above_size_limit_warning', project: project = render_if_exists 'shared/shared_runners_minutes_limit', project: project, classes: [container_class, ("limit-container-width" unless fluid_layout)] + = render_if_exists 'projects/terraform_banner', project: project diff --git a/app/views/projects/_terraform_banner.html.haml b/app/views/projects/_terraform_banner.html.haml new file mode 100644 index 00000000000..a30c4a2d624 --- /dev/null +++ b/app/views/projects/_terraform_banner.html.haml @@ -0,0 +1,5 @@ +- @content_class = "container-limited limit-container-width" unless fluid_layout + +- if show_terraform_banner?(project) + .container-fluid{ class: @content_class } + .js-terraform-notification{ data: { project_id: project.id } } diff --git a/babel.config.js b/babel.config.js index 4dfca8f6144..d10de05258b 100644 --- a/babel.config.js +++ b/babel.config.js @@ -19,6 +19,10 @@ const plugins = [ '@babel/plugin-proposal-private-methods', // See: https://gitlab.com/gitlab-org/gitlab/-/issues/229146 '@babel/plugin-transform-arrow-functions', + // See: https://gitlab.com/gitlab-org/gitlab/-/issues/336216 + '@babel/plugin-proposal-optional-chaining', + // See: https://gitlab.com/gitlab-org/gitlab/-/issues/336216 + '@babel/plugin-proposal-nullish-coalescing-operator', 'lodash', ]; diff --git a/config/feature_flags/development/ci_scoped_job_token.yml b/config/feature_flags/development/ci_scoped_job_token.yml index a7fa0244839..a885a1e6391 100644 --- a/config/feature_flags/development/ci_scoped_job_token.yml +++ b/config/feature_flags/development/ci_scoped_job_token.yml @@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/332272 milestone: '14.0' type: development group: group::pipeline execution -default_enabled: false +default_enabled: true diff --git a/db/migrate/20210709132707_change_default_job_token_scope_enabled.rb b/db/migrate/20210709132707_change_default_job_token_scope_enabled.rb new file mode 100644 index 00000000000..fbd9f7baa61 --- /dev/null +++ b/db/migrate/20210709132707_change_default_job_token_scope_enabled.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class ChangeDefaultJobTokenScopeEnabled < ActiveRecord::Migration[6.1] + include Gitlab::Database::MigrationHelpers + + def up + with_lock_retries do + change_column_default :project_ci_cd_settings, :job_token_scope_enabled, from: false, to: true + end + end + + def down + with_lock_retries do + change_column_default :project_ci_cd_settings, :job_token_scope_enabled, from: true, to: false + end + end +end diff --git a/db/schema_migrations/20210709132707 b/db/schema_migrations/20210709132707 new file mode 100644 index 00000000000..04ba096a692 --- /dev/null +++ b/db/schema_migrations/20210709132707 @@ -0,0 +1 @@ +e0a2de69a3c9d616b87207b764e33fa3326627e065f28fc200c1414f08ee9fff
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 07cbad0f69d..0a1b53400d8 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -16753,7 +16753,7 @@ CREATE TABLE project_ci_cd_settings ( auto_rollback_enabled boolean DEFAULT false NOT NULL, keep_latest_artifact boolean DEFAULT true NOT NULL, restrict_user_defined_variables boolean DEFAULT false NOT NULL, - job_token_scope_enabled boolean DEFAULT false NOT NULL + job_token_scope_enabled boolean DEFAULT true NOT NULL ); CREATE SEQUENCE project_ci_cd_settings_id_seq diff --git a/doc/administration/geo/setup/database.md b/doc/administration/geo/setup/database.md index a3b48476941..03908e6fc45 100644 --- a/doc/administration/geo/setup/database.md +++ b/doc/administration/geo/setup/database.md @@ -487,7 +487,7 @@ The replication process is now complete. PostgreSQL connections, which can improve performance even when using in a single instance installation. -We recommend using PgBouncer if you use GitLab in a highly available +We recommend using PgBouncer if you use GitLab in a highly available configuration with a cluster of nodes supporting a Geo **primary** site and two other clusters of nodes supporting a Geo **secondary** site. One for the main database and the other for the tracking database. For more information, @@ -550,12 +550,12 @@ Leader instance**: ```ruby roles(['patroni_role']) - + consul['services'] = %w(postgresql) consul['configuration'] = { retry_join: %w[CONSUL_PRIMARY1_IP CONSUL_PRIMARY2_IP CONSUL_PRIMARY3_IP] } - + # You need one entry for each secondary, with a unique name following PostgreSQL slot_name constraints: # # Configuration syntax is: 'unique_slotname' => { 'type' => 'physical' }, @@ -567,6 +567,8 @@ Leader instance**: patroni['use_pg_rewind'] = true patroni['postgresql']['max_wal_senders'] = 8 # Use double of the amount of patroni/reserved slots (3 patronis + 1 reserved slot for a Geo secondary). patroni['postgresql']['max_replication_slots'] = 8 # Use double of the amount of patroni/reserved slots (3 patronis + 1 reserved slot for a Geo secondary). + patroni['username'] = 'PATRONI_API_USERNAME' + patroni['password'] = 'PATRONI_API_PASSWORD' patroni['replication_password'] = 'PLAIN_TEXT_POSTGRESQL_REPLICATION_PASSWORD' # We list all secondary instances as they can all become a Standby Leader @@ -727,16 +729,18 @@ For each Patroni instance on the secondary site: patroni['standby_cluster']['host'] = 'INTERNAL_LOAD_BALANCER_PRIMARY_IP' patroni['standby_cluster']['port'] = INTERNAL_LOAD_BALANCER_PRIMARY_PORT patroni['standby_cluster']['primary_slot_name'] = 'geo_secondary' # Or the unique replication slot name you setup before + patroni['username'] = 'PATRONI_API_USERNAME' + patroni['password'] = 'PATRONI_API_PASSWORD' patroni['replication_password'] = 'PLAIN_TEXT_POSTGRESQL_REPLICATION_PASSWORD' patroni['use_pg_rewind'] = true patroni['postgresql']['max_wal_senders'] = 5 # A minimum of three for one replica, plus two for each additional replica patroni['postgresql']['max_replication_slots'] = 5 # A minimum of three for one replica, plus two for each additional replica - + postgresql['pgbouncer_user_password'] = 'PGBOUNCER_PASSWORD_HASH' postgresql['sql_replication_password'] = 'POSTGRESQL_REPLICATION_PASSWORD_HASH' postgresql['sql_user_password'] = 'POSTGRESQL_PASSWORD_HASH' postgresql['listen_address'] = '0.0.0.0' # You can use a public or VPC address here instead - + gitlab_rails['dbpassword'] = 'POSTGRESQL_PASSWORD' gitlab_rails['enable'] = true gitlab_rails['auto_migrate'] = false @@ -754,7 +758,7 @@ For each Patroni instance on the secondary site: - If you are configuring a Patroni standby cluster on a site that previously had a working Patroni cluster: ```shell - gitlab-ctl stop patroni + gitlab-ctl stop patroni rm -rf /var/opt/gitlab/postgresql/data /opt/gitlab/embedded/bin/patronictl -c /var/opt/gitlab/patroni/patroni.yaml remove postgresql-ha gitlab-ctl reconfigure @@ -900,6 +904,8 @@ For each Patroni instance on the secondary site for the tracking database: ] # Patroni configuration + patroni['username'] = 'PATRONI_API_USERNAME' + patroni['password'] = 'PATRONI_API_PASSWORD' patroni['replication_password'] = 'PLAIN_TEXT_POSTGRESQL_REPLICATION_PASSWORD' patroni['postgresql']['max_wal_senders'] = 5 # A minimum of three for one replica, plus two for each additional replica diff --git a/doc/administration/monitoring/prometheus/gitlab_metrics.md b/doc/administration/monitoring/prometheus/gitlab_metrics.md index 4a9e525ead5..8b9ba55bd3c 100644 --- a/doc/administration/monitoring/prometheus/gitlab_metrics.md +++ b/doc/administration/monitoring/prometheus/gitlab_metrics.md @@ -121,6 +121,7 @@ The following metrics are available: | `action_cable_single_client_transmissions_total` | Counter | 13.10 | The number of ActionCable messages transmitted to any client in any channel | `server_mode` | | `action_cable_subscription_confirmations_total` | Counter | 13.10 | The number of ActionCable subscriptions from clients confirmed | `server_mode` | | `action_cable_subscription_rejections_total` | Counter | 13.10 | The number of ActionCable subscriptions from clients rejected | `server_mode` | +| `action_cable_transmitted_bytes` | Histogram | 14.1 | Message size, in bytes, transmitted over action cable | `operation`, `channel` | | `gitlab_issuable_fast_count_by_state_total` | Counter | 13.5 | Total number of row count operations on issue/merge request list pages | | | `gitlab_issuable_fast_count_by_state_failures_total` | Counter | 13.5 | Number of soft-failed row count operations on issue/merge request list pages | | | `gitlab_external_http_total` | Counter | 13.8 | Total number of HTTP calls to external systems | `controller`, `action` | diff --git a/doc/administration/postgresql/replication_and_failover.md b/doc/administration/postgresql/replication_and_failover.md index 3c10a245c3f..870b1f6aea2 100644 --- a/doc/administration/postgresql/replication_and_failover.md +++ b/doc/administration/postgresql/replication_and_failover.md @@ -157,6 +157,13 @@ We will need the following password information for the application's database u sudo gitlab-ctl pg-password-md5 POSTGRESQL_USERNAME ``` +#### Patroni information + +We will need the following password information for the Patroni API: + +- `PATRONI_API_USERNAME`. A username for basic auth to the API +- `PATRONI_API_PASSWORD`. A password for basic auth to the API + #### PgBouncer information When using default setup, minimum configuration requires: @@ -236,6 +243,11 @@ postgresql['sql_replication_password'] = 'POSTGRESQL_REPLICATION_PASSWORD_HASH' # Replace POSTGRESQL_PASSWORD_HASH with a generated md5 value postgresql['sql_user_password'] = 'POSTGRESQL_PASSWORD_HASH' +# Replace PATRONI_API_USERNAME with a username for Patroni Rest API calls (use the same username in all nodes) +patroni['username'] = 'PATRONI_API_USERNAME' +# Replace PATRONI_API_PASSWORD with a password for Patroni Rest API calls (use the same password in all nodes) +patroni['password'] = 'PATRONI_API_PASSWORD' + # Sets `max_replication_slots` to double the number of database nodes. # Patroni uses one extra slot per node when initiating the replication. patroni['postgresql']['max_replication_slots'] = X @@ -246,7 +258,7 @@ patroni['postgresql']['max_replication_slots'] = X patroni['postgresql']['max_wal_senders'] = X+1 # Replace XXX.XXX.XXX.XXX/YY with Network Address -postgresql['trust_auth_cidr_addresses'] = %w(XXX.XXX.XXX.XXX/YY) +postgresql['trust_auth_cidr_addresses'] = %w(XXX.XXX.XXX.XXX/YY 127.0.0.1/32) # Replace placeholders: # @@ -259,8 +271,8 @@ consul['configuration'] = { # END user configuration ``` -You do not need an additional or different configuration for replica nodes. As a matter of fact, you don't have to have -a predetermined primary node. Therefore all database nodes use the same configuration. +All database nodes use the same configuration. The leader node is not determined in configuration, +and there is no additional or different configuration for either leader or replica nodes. Once the configuration of a node is done, you must [reconfigure Omnibus GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) on each node for the changes to take effect. @@ -555,10 +567,12 @@ gitlab_rails['auto_migrate'] = false postgresql['pgbouncer_user_password'] = '771a8625958a529132abe6f1a4acb19c' postgresql['sql_user_password'] = '450409b85a0223a214b5fb1484f34d0f' +patroni['username'] = 'PATRONI_API_USERNAME' +patroni['password'] = 'PATRONI_API_PASSWORD' patroni['postgresql']['max_replication_slots'] = 6 patroni['postgresql']['max_wal_senders'] = 7 -postgresql['trust_auth_cidr_addresses'] = %w(10.6.0.0/16) +postgresql['trust_auth_cidr_addresses'] = %w(10.6.0.0/16 127.0.0.1/32) # Configure the Consul agent consul['services'] = %w(postgresql) @@ -642,12 +656,15 @@ postgresql['sql_user_password'] = '450409b85a0223a214b5fb1484f34d0f' # Patroni uses one extra slot per node when initiating the replication. patroni['postgresql']['max_replication_slots'] = 6 +patroni['username'] = 'PATRONI_API_USERNAME' +patroni['password'] = 'PATRONI_API_PASSWORD' + # Set `max_wal_senders` to one more than the number of replication slots in the cluster. # This is used to prevent replication from using up all of the # available database connections. patroni['postgresql']['max_wal_senders'] = 7 -postgresql['trust_auth_cidr_addresses'] = %w(10.6.0.0/16) +postgresql['trust_auth_cidr_addresses'] = %w(10.6.0.0/16 127.0.0.1/32) consul['configuration'] = { server: true, @@ -721,6 +738,97 @@ functional or does not have a leader, Patroni and by extension PostgreSQL will n API which can be accessed via its [default port](https://docs.gitlab.com/omnibus/package-information/defaults.html#patroni) on each node. +### Check replication status + +Run `gitlab-ctl patroni members` to query Patroni for a summary of the cluster status: + +```plaintext ++ Cluster: postgresql-ha (6970678148837286213) ------+---------+---------+----+-----------+ +| Member | Host | Role | State | TL | Lag in MB | ++-------------------------------------+--------------+---------+---------+----+-----------+ +| gitlab-database-1.example.com | 172.18.0.111 | Replica | running | 5 | 0 | +| gitlab-database-2.example.com | 172.18.0.112 | Replica | running | 5 | 100 | +| gitlab-database-3.example.com | 172.18.0.113 | Leader | running | 5 | | ++-------------------------------------+--------------+---------+---------+----+-----------+ +``` + +To verify the status of replication: + +```shell +echo 'select * from pg_stat_wal_receiver\x\g\x \n select * from pg_stat_replication\x\g\x' | gitlab-psql +``` + +The same command can be run on all three database servers, and will return any information +about replication available depending on the role the server is performing. + +The leader should return one record per replica: + +```sql +-[ RECORD 1 ]----+------------------------------ +pid | 371 +usesysid | 16384 +usename | gitlab_replicator +application_name | gitlab-database-1.example.com +client_addr | 172.18.0.111 +client_hostname | +client_port | 42900 +backend_start | 2021-06-14 08:01:59.580341+00 +backend_xmin | +state | streaming +sent_lsn | 0/EA13220 +write_lsn | 0/EA13220 +flush_lsn | 0/EA13220 +replay_lsn | 0/EA13220 +write_lag | +flush_lag | +replay_lag | +sync_priority | 0 +sync_state | async +reply_time | 2021-06-18 19:17:14.915419+00 +``` + +Investigate further if: + +- There are missing or extra records. +- `reply_time` is not current. + +The `lsn` fields relate to which write-ahead-log segments have been replicated. +Run the following on the leader to find out the current LSN: + +```shell +echo 'SELECT pg_current_wal_lsn();' | gitlab-psql +``` + +If a replica is not in sync, `gitlab-ctl patroni members` indicates the volume +of missing data, and the `lag` fields indicate the elapsed time. + +Read more about the data returned by the leader +[in the PostgreSQL documentation](https://www.postgresql.org/docs/12/monitoring-stats.html#PG-STAT-REPLICATION-VIEW), +including other values for the `state` field. + +The replicas should return: + +```sql +-[ RECORD 1 ]---------+------------------------------------------------------------------------------------------------- +pid | 391 +status | streaming +receive_start_lsn | 0/D000000 +receive_start_tli | 5 +received_lsn | 0/EA13220 +received_tli | 5 +last_msg_send_time | 2021-06-18 19:16:54.807375+00 +last_msg_receipt_time | 2021-06-18 19:16:54.807512+00 +latest_end_lsn | 0/EA13220 +latest_end_time | 2021-06-18 19:07:23.844879+00 +slot_name | gitlab-database-1.example.com +sender_host | 172.18.0.113 +sender_port | 5432 +conninfo | user=gitlab_replicator host=172.18.0.113 port=5432 application_name=gitlab-database-1.example.com +``` + +Read more about the data returned by the replica +[in the PostgreSQL documentation](https://www.postgresql.org/docs/12/monitoring-stats.html#PG-STAT-WAL-RECEIVER-VIEW). + ### Selecting the appropriate Patroni replication method [Review the Patroni documentation carefully](https://patroni.readthedocs.io/en/latest/SETTINGS.html#postgresql) @@ -1017,6 +1125,29 @@ postgresql['trust_auth_cidr_addresses'] = %w(123.123.123.123/32 <other_cidrs>) [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect. +### Reinitialize a replica + +If replication is not occurring, it may be necessary to reinitialize a replica. + +1. On any server in the cluster, determine the Cluster and Member names, + and check the replication lag by running `gitlab-ctl patroni members`. Here is an example: + + ```plaintext + + Cluster: postgresql-ha (6970678148837286213) ------+---------+---------+----+-----------+ + | Member | Host | Role | State | TL | Lag in MB | + +-------------------------------------+--------------+---------+---------+----+-----------+ + | gitlab-database-1.example.com | 172.18.0.111 | Replica | running | 5 | 0 | + | gitlab-database-2.example.com | 172.18.0.112 | Replica | running | 5 | 100 | + | gitlab-database-3.example.com | 172.18.0.113 | Leader | running | 5 | | + +-------------------------------------+--------------+---------+---------+----+-----------+ + ``` + +1. Reinitialize the affected replica server: + + ```plaintext + gitlab-ctl patroni reinitialize-replica postgresql-ha gitlab-database-2.example.com + ``` + ### Reset the Patroni state in Consul WARNING: @@ -1058,6 +1189,70 @@ To reset the Patroni state in Consul: If you are still seeing issues, the next step is restoring the last healthy backup. +### Errors in the Patroni log about a `pg_hba.conf` entry for `127.0.0.1` + +The following log entry in the Patroni log indicates the replication is not working +and a configuration change is needed: + +```plaintext +FATAL: no pg_hba.conf entry for replication connection from host "127.0.0.1", user "gitlab_replicator" +``` + +To fix the problem, ensure the loopback interface is included in the CIDR addresses list: + +1. Edit `/etc/gitlab/gitlab.rb`: + + ```ruby + postgresql['trust_auth_cidr_addresses'] = %w(<other_cidrs> 127.0.0.1/32) + ``` + +1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect. +1. Check that [all the replicas are synchronized](#check-replication-status) + +### Errors in Patroni logs: the requested start point is ahead of the WAL flush position + +This error indicates that the database is not replicating: + +```plaintext +FATAL: could not receive data from WAL stream: ERROR: requested starting point 0/5000000 is ahead of the WAL flush position of this server 0/4000388 +``` + +This example error is from a replica that was initially misconfigured, and had never replicated. + +Fix it [by reinitializing the replica](#reinitialize-a-replica). + +### Patroni fails to start with `MemoryError` + +Patroni may fail to start, logging an error and stack trace: + +```plaintext +MemoryError +Traceback (most recent call last): + File "/opt/gitlab/embedded/bin/patroni", line 8, in <module> + sys.exit(main()) +[..] + File "/opt/gitlab/embedded/lib/python3.7/ctypes/__init__.py", line 273, in _reset_cache + CFUNCTYPE(c_int)(lambda: None) +``` + +If the stack trace ends with `CFUNCTYPE(c_int)(lambda: None)`, this code triggers `MemoryError` +if the Linux server has been hardened for security. + +The code causes Python to write temporary executable files, and if it cannot find a filesystem +in which to do this, for example if `noexec` is set on the `/tmp` filesystem, it fails with +`MemoryError` ([read more in the issue](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/6184)). + +Workarounds: + +- Remove `noexec` from the mount options for filesystems like `/tmp` and `/var/tmp`. +- If set to enforcing, SELinux may also prevent these operations. Verify the issue is fixed by setting + SELinux to permissive. + +Omnibus GitLab has shipped with Patroni since 13.1 along with a build of Python 3.7. +Workarounds should stop being required when GitLab 14.x starts shipping with +[a later version of Python](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/6164) as +the code which causes this was removed from Python 3.8. + ### Issues with other components If you're running into an issue with a component not outlined here, be sure to check the troubleshooting section of their specific documentation page: diff --git a/doc/administration/reference_architectures/10k_users.md b/doc/administration/reference_architectures/10k_users.md index e554f5c3693..862f35f4b5c 100644 --- a/doc/administration/reference_architectures/10k_users.md +++ b/doc/administration/reference_architectures/10k_users.md @@ -598,8 +598,12 @@ in the second step, do not supply the `EXTERNAL_URL` value. # Replace POSTGRESQL_PASSWORD_HASH with a generated md5 value postgresql['sql_user_password'] = '<postgresql_password_hash>' + # Set up basic authentication for the Patroni API (use the same username/password in all nodes). + patroni['username'] = '<patroni_api_username>' + patroni['password'] = '<patroni_api_password>' + # Replace XXX.XXX.XXX.XXX/YY with Network Address - postgresql['trust_auth_cidr_addresses'] = %w(10.6.0.0/24) + postgresql['trust_auth_cidr_addresses'] = %w(10.6.0.0/24 127.0.0.1/32) # Set the network addresses that the exporters will listen on for monitoring node_exporter['listen_address'] = '0.0.0.0:9100' @@ -1403,7 +1407,7 @@ in the second step, do not supply the `EXTERNAL_URL` value. postgresql['sql_user_password'] = "<praefect_postgresql_password_hash>" # Replace XXX.XXX.XXX.XXX/YY with Network Address - postgresql['trust_auth_cidr_addresses'] = %w(10.6.0.0/24) + postgresql['trust_auth_cidr_addresses'] = %w(10.6.0.0/24 127.0.0.1/32) # Set the network addresses that the exporters will listen on for monitoring node_exporter['listen_address'] = '0.0.0.0:9100' @@ -1681,7 +1685,7 @@ On each node: # balancer. gitlab_rails['internal_api_url'] = 'https://gitlab.example.com' - # Gitaly + # Gitaly gitaly['enable'] = true # Make Gitaly accept connections on all network interfaces. You must use diff --git a/doc/administration/reference_architectures/25k_users.md b/doc/administration/reference_architectures/25k_users.md index 920ed5a7652..76477a6ffb2 100644 --- a/doc/administration/reference_architectures/25k_users.md +++ b/doc/administration/reference_architectures/25k_users.md @@ -600,8 +600,12 @@ in the second step, do not supply the `EXTERNAL_URL` value. # Replace POSTGRESQL_PASSWORD_HASH with a generated md5 value postgresql['sql_user_password'] = '<postgresql_password_hash>' + # Set up basic authentication for the Patroni API (use the same username/password in all nodes). + patroni['username'] = '<patroni_api_username>' + patroni['password'] = '<patroni_api_password>' + # Replace XXX.XXX.XXX.XXX/YY with Network Address - postgresql['trust_auth_cidr_addresses'] = %w(10.6.0.0/24) + postgresql['trust_auth_cidr_addresses'] = %w(10.6.0.0/24 127.0.0.1/32) # Set the network addresses that the exporters will listen on for monitoring node_exporter['listen_address'] = '0.0.0.0:9100' @@ -863,7 +867,7 @@ a node and change its status from primary to replica (and vice versa). redis_exporter['flags'] = { 'redis.addr' => 'redis://10.6.0.51:6379', 'redis.password' => 'redis-password-goes-here', - } + } # Prevent database migrations from running on upgrade automatically gitlab_rails['auto_migrate'] = false @@ -1421,7 +1425,7 @@ in the second step, do not supply the `EXTERNAL_URL` value. postgresql['sql_user_password'] = "<praefect_postgresql_password_hash>" # Replace XXX.XXX.XXX.XXX/YY with Network Address - postgresql['trust_auth_cidr_addresses'] = %w(10.6.0.0/24) + postgresql['trust_auth_cidr_addresses'] = %w(10.6.0.0/24 127.0.0.1/32) # Set the network addresses that the exporters will listen on for monitoring node_exporter['listen_address'] = '0.0.0.0:9100' @@ -1699,7 +1703,7 @@ On each node: # balancer. gitlab_rails['internal_api_url'] = 'https://gitlab.example.com' - # Gitaly + # Gitaly gitaly['enable'] = true # Make Gitaly accept connections on all network interfaces. You must use diff --git a/doc/administration/reference_architectures/3k_users.md b/doc/administration/reference_architectures/3k_users.md index 71ca67075d3..655afc58343 100644 --- a/doc/administration/reference_architectures/3k_users.md +++ b/doc/administration/reference_architectures/3k_users.md @@ -848,7 +848,7 @@ in the second step, do not supply the `EXTERNAL_URL` value. ```ruby # Disable all components except Patroni and Consul roles(['patroni_role']) - + # PostgreSQL configuration postgresql['listen_address'] = '0.0.0.0' @@ -866,7 +866,7 @@ in the second step, do not supply the `EXTERNAL_URL` value. # Prevent database migrations from running on upgrade automatically gitlab_rails['auto_migrate'] = false - + # Configure the Consul agent consul['services'] = %w(postgresql) ## Enable service discovery for Prometheus @@ -882,8 +882,12 @@ in the second step, do not supply the `EXTERNAL_URL` value. # Replace POSTGRESQL_PASSWORD_HASH with a generated md5 value postgresql['sql_user_password'] = '<postgresql_password_hash>' + # Set up basic authentication for the Patroni API (use the same username/password in all nodes). + patroni['username'] = '<patroni_api_username>' + patroni['password'] = '<patroni_api_password>' + # Replace XXX.XXX.XXX.XXX/YY with Network Address - postgresql['trust_auth_cidr_addresses'] = %w(10.6.0.0/24) + postgresql['trust_auth_cidr_addresses'] = %w(10.6.0.0/24 127.0.0.1/32) # Set the network addresses that the exporters will listen on for monitoring node_exporter['listen_address'] = '0.0.0.0:9100' @@ -1127,7 +1131,7 @@ in the second step, do not supply the `EXTERNAL_URL` value. postgresql['sql_user_password'] = "<praefect_postgresql_password_hash>" # Replace XXX.XXX.XXX.XXX/YY with Network Address - postgresql['trust_auth_cidr_addresses'] = %w(10.6.0.0/24) + postgresql['trust_auth_cidr_addresses'] = %w(10.6.0.0/24 127.0.0.1/32) # Set the network addresses that the exporters will listen on for monitoring node_exporter['listen_address'] = '0.0.0.0:9100' diff --git a/doc/administration/reference_architectures/50k_users.md b/doc/administration/reference_architectures/50k_users.md index 3b3b1cf8ced..3b362f91a61 100644 --- a/doc/administration/reference_architectures/50k_users.md +++ b/doc/administration/reference_architectures/50k_users.md @@ -608,8 +608,12 @@ in the second step, do not supply the `EXTERNAL_URL` value. # Replace POSTGRESQL_PASSWORD_HASH with a generated md5 value postgresql['sql_user_password'] = '<postgresql_password_hash>' + # Set up basic authentication for the Patroni API (use the same username/password in all nodes). + patroni['username'] = '<patroni_api_username>' + patroni['password'] = '<patroni_api_password>' + # Replace XXX.XXX.XXX.XXX/YY with Network Address - postgresql['trust_auth_cidr_addresses'] = %w(10.6.0.0/24) + postgresql['trust_auth_cidr_addresses'] = %w(10.6.0.0/24 127.0.0.1/32) # Set the network addresses that the exporters will listen on for monitoring node_exporter['listen_address'] = '0.0.0.0:9100' @@ -872,7 +876,7 @@ a node and change its status from primary to replica (and vice versa). 'redis.addr' => 'redis://10.6.0.51:6379', 'redis.password' => 'redis-password-goes-here', } - + # Prevent database migrations from running on upgrade automatically gitlab_rails['auto_migrate'] = false ``` @@ -1425,7 +1429,7 @@ in the second step, do not supply the `EXTERNAL_URL` value. postgresql['sql_user_password'] = "<praefect_postgresql_password_hash>" # Replace XXX.XXX.XXX.XXX/YY with Network Address - postgresql['trust_auth_cidr_addresses'] = %w(10.6.0.0/24) + postgresql['trust_auth_cidr_addresses'] = %w(10.6.0.0/24 127.0.0.1/32) # Set the network addresses that the exporters will listen on for monitoring node_exporter['listen_address'] = '0.0.0.0:9100' @@ -1703,7 +1707,7 @@ On each node: # balancer. gitlab_rails['internal_api_url'] = 'https://gitlab.example.com' - # Gitaly + # Gitaly gitaly['enable'] = true # Make Gitaly accept connections on all network interfaces. You must use @@ -1929,7 +1933,7 @@ To configure the Sidekiq nodes, on each one: ## Set number of Sidekiq threads per queue process to the recommend number of 10 sidekiq['max_concurrency'] = 10 - # Monitoring + # Monitoring consul['enable'] = true consul['monitoring_service_discovery'] = true diff --git a/doc/administration/reference_architectures/5k_users.md b/doc/administration/reference_architectures/5k_users.md index b5001068945..960f7148eda 100644 --- a/doc/administration/reference_architectures/5k_users.md +++ b/doc/administration/reference_architectures/5k_users.md @@ -844,7 +844,7 @@ in the second step, do not supply the `EXTERNAL_URL` value. # Sets `max_replication_slots` to double the number of database nodes. # Patroni uses one extra slot per node when initiating the replication. patroni['postgresql']['max_replication_slots'] = 8 - + # Set `max_wal_senders` to one more than the number of replication slots in the cluster. # This is used to prevent replication from using up all of the # available database connections. @@ -871,8 +871,12 @@ in the second step, do not supply the `EXTERNAL_URL` value. # Replace POSTGRESQL_PASSWORD_HASH with a generated md5 value postgresql['sql_user_password'] = '<postgresql_password_hash>' + # Set up basic authentication for the Patroni API (use the same username/password in all nodes). + patroni['username'] = '<patroni_api_username>' + patroni['password'] = '<patroni_api_password>' + # Replace XXX.XXX.XXX.XXX/YY with Network Address - postgresql['trust_auth_cidr_addresses'] = %w(10.6.0.0/24) + postgresql['trust_auth_cidr_addresses'] = %w(10.6.0.0/24 127.0.0.1/32) # Set the network addresses that the exporters will listen on for monitoring node_exporter['listen_address'] = '0.0.0.0:9100' @@ -1116,7 +1120,7 @@ in the second step, do not supply the `EXTERNAL_URL` value. postgresql['sql_user_password'] = "<praefect_postgresql_password_hash>" # Replace XXX.XXX.XXX.XXX/YY with Network Address - postgresql['trust_auth_cidr_addresses'] = %w(10.6.0.0/24) + postgresql['trust_auth_cidr_addresses'] = %w(10.6.0.0/24 127.0.0.1/32) # Set the network addresses that the exporters will listen on for monitoring node_exporter['listen_address'] = '0.0.0.0:9100' diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 4a0572569a3..de07d601c4f 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -15290,6 +15290,7 @@ Name of the feature that the callout is for. | <a id="usercalloutfeaturenameenumpipeline_needs_banner"></a>`PIPELINE_NEEDS_BANNER` | Callout feature name for pipeline_needs_banner. | | <a id="usercalloutfeaturenameenumpipeline_needs_hover_tip"></a>`PIPELINE_NEEDS_HOVER_TIP` | Callout feature name for pipeline_needs_hover_tip. | | <a id="usercalloutfeaturenameenumregistration_enabled_callout"></a>`REGISTRATION_ENABLED_CALLOUT` | Callout feature name for registration_enabled_callout. | +| <a id="usercalloutfeaturenameenumsecurity_configuration_devops_alert"></a>`SECURITY_CONFIGURATION_DEVOPS_ALERT` | Callout feature name for security_configuration_devops_alert. | | <a id="usercalloutfeaturenameenumsecurity_configuration_upgrade_banner"></a>`SECURITY_CONFIGURATION_UPGRADE_BANNER` | Callout feature name for security_configuration_upgrade_banner. | | <a id="usercalloutfeaturenameenumservice_templates_deprecated_callout"></a>`SERVICE_TEMPLATES_DEPRECATED_CALLOUT` | Callout feature name for service_templates_deprecated_callout. | | <a id="usercalloutfeaturenameenumsuggest_pipeline"></a>`SUGGEST_PIPELINE` | Callout feature name for suggest_pipeline. | diff --git a/doc/api/index.md b/doc/api/index.md index f1059904ac3..d80f4ecf19b 100644 --- a/doc/api/index.md +++ b/doc/api/index.md @@ -248,13 +248,13 @@ tries to steal tokens from other jobs. #### Limit GitLab CI/CD job token access > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/328553) in GitLab 14.1. -> - [Deployed behind a feature flag](../user/feature_flags.md), disabled by default. -> - Disabled on GitLab.com. -> - Not recommended for production use. -> - To use in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-ci-job-token-scope-limit). **(FREE SELF)** +> - [Deployed behind a feature flag](../user/feature_flags.md), enabled by default. +> - Enabled on GitLab.com. +> - Recommended for production use. +> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#enable-or-disable-ci-job-token-scope-limit). **(FREE SELF)** -This in-development feature might not be available for your use. There can be -[risks when enabling features still in development](../user/feature_flags.md#risks-when-enabling-features-still-in-development). +There can be +[risks when disabling released features](../user/feature_flags.md#risks-when-disabling-released-features). Refer to this feature's version history for more details. You can limit the access scope of a project's CI/CD job token to increase the @@ -292,21 +292,21 @@ the feature with more strategic control of the access permissions. ##### Enable or disable CI job token scope limit **(FREE SELF)** -The GitLab CI/CD job token access scope limit is under development and not ready for production -use. It is deployed behind a feature flag that is **disabled by default**. +The GitLab CI/CD job token access scope limit is under development but ready for production +use. It is deployed behind a feature flag that is **enabled by default**. [GitLab administrators with access to the GitLab Rails console](../administration/feature_flags.md) -can enable it. +can disable the feature. -To enable it: +To disable it: ```ruby -Feature.enable(:ci_scoped_job_token) +Feature.disable(:ci_scoped_job_token) ``` -To disable it: +To enable it: ```ruby -Feature.disable(:ci_scoped_job_token) +Feature.enable(:ci_scoped_job_token) ``` ### Impersonation tokens diff --git a/doc/ci/pipeline_editor/index.md b/doc/ci/pipeline_editor/index.md index 96df93848dc..7132d47d324 100644 --- a/doc/ci/pipeline_editor/index.md +++ b/doc/ci/pipeline_editor/index.md @@ -85,9 +85,6 @@ where: [extended configuration merged into the job](../yaml/index.md#merge-details). - YAML anchors are [replaced with the linked configuration](../yaml/index.md#anchors). -NOTE: -You can only see the expanded view when editing the [default branch](../../user/project/repository/branches/default.md). - ## Commit changes to CI configuration The commit form appears at the bottom of each tab in the editor so you can commit diff --git a/doc/subscriptions/bronze_starter.md b/doc/subscriptions/bronze_starter.md index 7d3af5979fb..410759aa506 100644 --- a/doc/subscriptions/bronze_starter.md +++ b/doc/subscriptions/bronze_starter.md @@ -16,7 +16,7 @@ The following features remain available to Bronze and Starter customers, even th the tiers are no longer mentioned in GitLab documentation: - [Activate GitLab EE with a license](../user/admin_area/license.md) -- [Adding a help message to the login page](../user/admin_area/settings/help_page.md#adding-a-help-message-to-the-login-page) +- [Add a help message to the sign-in page](../user/admin_area/settings/help_page.md#add-a-help-message-to-the-sign-in-page) - [Burndown and burnup charts](../user/project/milestones/burndown_and_burnup_charts.md), including [per-project charts](../user/project/milestones/index.md#project-burndown-charts) and [per-group charts](../user/project/milestones/index.md#group-burndown-charts) diff --git a/doc/user/admin_area/settings/help_page.md b/doc/user/admin_area/settings/help_page.md index e11527db49a..d2f99a51ec3 100644 --- a/doc/user/admin_area/settings/help_page.md +++ b/doc/user/admin_area/settings/help_page.md @@ -5,41 +5,58 @@ info: To determine the technical writer assigned to the Stage/Group associated w type: howto --- -# Customizing the 'Help' and login page messages +# Customize the Help and sign-in page messages In large organizations, it is useful to have information about who to contact or where -to go for help. You can customize and display this information on the GitLab server's -`/help` page and on the GitLab login page. +to go for help. You can customize and display this information on the GitLab `/help` page and on +the GitLab sign-in page. -## Adding a help message to the help page +## Add a help message to the Help page -You can add a help message, which is shown on the GitLab `/help` page (for example, -<https://gitlab.com/help>) in a new section at the top of the `/help` page: +You can add a help message, which is shown at the top of the GitLab `/help` page (for example, +<https://gitlab.com/help>): 1. On the top bar, select **Menu >** **{admin}** **Admin**. 1. In the left sidebar, select **Settings > Preferences**, then expand **Help page**. -1. Under **Help page text**, fill in the information you wish to display on `/help`. -1. Save your changes. You can now see the message on `/help`. +1. Under **Additional text to show on the Help page**, fill in the information you wish to display on `/help`. +1. Select **Save changes**. You can now see the message on `/help`. NOTE: By default, `/help` is visible to unauthenticated users. However, if the [**Public** visibility level](visibility_and_access_controls.md#restricted-visibility-levels) is restricted, `/help` is visible only to signed-in users. -## Adding a help message to the login page **(STARTER)** +## Add a help message to the sign-in page **(STARTER)** -You can add a help message, which is shown on the GitLab login page in a new section -titled `Need Help?`, located below the login page message: +You can add a help message, which is shown on the GitLab sign-in page. The message appears in a new +section titled **Need Help?**, located below the sign-in page message: 1. On the top bar, select **Menu >** **{admin}** **Admin**. 1. In the left sidebar, select **Settings > Preferences**, then expand **Help page**. -1. Under **Help text**, fill in the information you wish to display on the login page. +1. Under **Additional text to show on the sign-in page**, fill in the information you wish to + display on the sign-in page. +1. Select **Save changes**. You can now see the message on the sign-in page. - ![help message on login page](img/help_page_help_text_v12_3.png) +## Hide marketing-related entries from the Help page -1. Save your changes. +GitLab marketing-related entries are occasionally shown on the Help page. To hide these entries: -![help message on login page example](img/help_page_help_text_ex_v12_3.png) +1. On the top bar, select **Menu >** **{admin}** **Admin**. +1. In the left sidebar, select **Settings > Preferences**, then expand **Help page**. +1. Select the **Hide marketing-related entries from the Help page** checkbox. +1. Select **Save changes**. + +## Set a custom Support page URL + +You can specify a custom URL to which users are directed when they: + +- Select **Support** from the Help dropdown. +- Select **See our website for help** on the Help page. + +1. On the top bar, select **Menu >** **{admin}** **Admin**. +1. In the left sidebar, select **Settings > Preferences**, then expand **Help page**. +1. Enter the URL in the **Support page URL** field. +1. Select **Save changes**. <!-- ## Troubleshooting diff --git a/doc/user/admin_area/settings/img/help_page_help_text_ex_v12_3.png b/doc/user/admin_area/settings/img/help_page_help_text_ex_v12_3.png Binary files differdeleted file mode 100644 index 973be2e8b6e..00000000000 --- a/doc/user/admin_area/settings/img/help_page_help_text_ex_v12_3.png +++ /dev/null diff --git a/doc/user/admin_area/settings/img/help_page_help_text_v12_3.png b/doc/user/admin_area/settings/img/help_page_help_text_v12_3.png Binary files differdeleted file mode 100644 index 8848ea55cf3..00000000000 --- a/doc/user/admin_area/settings/img/help_page_help_text_v12_3.png +++ /dev/null diff --git a/doc/user/search/advanced_search.md b/doc/user/search/advanced_search.md index 20a2a7263c3..bb74a035121 100644 --- a/doc/user/search/advanced_search.md +++ b/doc/user/search/advanced_search.md @@ -12,6 +12,7 @@ type: reference NOTE: This is the user documentation. To configure the Advanced Search, visit the [administrator documentation](../../integration/elasticsearch.md). +Advanced Search is enabled in GitLab.com. GitLab Advanced Search expands on the Basic Search with an additional set of features for faster, more advanced searches across the entire GitLab instance @@ -34,6 +35,11 @@ The Advanced Search can be useful in various scenarios: Advanced Search is based on Elasticsearch, which is a purpose-built full text search engine that can be horizontally scaled so that it can provide search results in 1-2 seconds in most cases. +- **Code Maintenance:** + Finding all the code that needs to be updated at once across an entire + instance can save time spent maintaining code. + This is especially helpful for organizations with more than 10 active projects. + This can also help build confidence is code refactoring to identify unknown impacts. - **Promote innersourcing:** Your company may consist of many different developer teams each of which has their own group where the various projects are hosted. Some of your applications diff --git a/lib/gitlab/ci/pipeline/seed/build.rb b/lib/gitlab/ci/pipeline/seed/build.rb index fd05e542430..54d92745992 100644 --- a/lib/gitlab/ci/pipeline/seed/build.rb +++ b/lib/gitlab/ci/pipeline/seed/build.rb @@ -72,6 +72,7 @@ module Gitlab .deep_merge(rules_attributes) .deep_merge(allow_failure_criteria_attributes) .deep_merge(@cache.cache_attributes) + .deep_merge(runner_tags) end def bridge? @@ -211,6 +212,16 @@ module Gitlab end end + def runner_tags + { tag_list: evaluate_runner_tags }.compact + end + + def evaluate_runner_tags + @seed_attributes[:tag_list]&.map do |tag| + ExpandVariables.expand_existing(tag, evaluate_context.variables) + end + end + # If a job uses `allow_failure:exit_codes` and `rules:allow_failure` # we need to prevent the exit codes from being persisted because they # would break the behavior defined by `rules:allow_failure`. diff --git a/lib/gitlab/kas/client.rb b/lib/gitlab/kas/client.rb index 6675903e692..842ee98e4da 100644 --- a/lib/gitlab/kas/client.rb +++ b/lib/gitlab/kas/client.rb @@ -49,14 +49,14 @@ module Gitlab end def kas_endpoint_url - Gitlab::Kas.internal_url.delete_prefix('grpc://') + Gitlab::Kas.internal_url.sub(%r{^grpc://|^grpcs://}, '') end def credentials - if Rails.env.test? || Rails.env.development? - :this_channel_is_insecure - else + if URI(Gitlab::Kas.internal_url).scheme == 'grpcs' GRPC::Core::ChannelCredentials.new + else + :this_channel_is_insecure end end diff --git a/lib/gitlab/metrics/subscribers/action_cable.rb b/lib/gitlab/metrics/subscribers/action_cable.rb index a9355eeae40..631b9209f14 100644 --- a/lib/gitlab/metrics/subscribers/action_cable.rb +++ b/lib/gitlab/metrics/subscribers/action_cable.rb @@ -12,6 +12,7 @@ module Gitlab TRANSMIT_SUBSCRIPTION_CONFIRMATION = :action_cable_subscription_confirmations_total TRANSMIT_SUBSCRIPTION_REJECTION = :action_cable_subscription_rejections_total BROADCAST = :action_cable_broadcasts_total + DATA_TRANSMITTED_BYTES = :action_cable_transmitted_bytes def transmit_subscription_confirmation(event) confirm_subscription_counter.increment @@ -23,6 +24,14 @@ module Gitlab def transmit(event) transmit_counter.increment + + if event.payload.present? + channel = event.payload[:channel_class] + operation = operation_name_from(event.payload) + data_size = ::ActiveSupport::JSON.encode(event.payload[:data]).bytesize + + transmitted_bytes_histogram.observe({ channel: channel, operation: operation }, data_size) + end end def broadcast(event) @@ -31,6 +40,13 @@ module Gitlab private + # When possible tries to query operation name + def operation_name_from(payload) + data = payload.dig(:data, 'result', 'data') || {} + + data.each_key.first + end + def transmit_counter strong_memoize("transmission_counter") do ::Gitlab::Metrics.counter( @@ -66,6 +82,12 @@ module Gitlab ) end end + + def transmitted_bytes_histogram + strong_memoize("transmitted_bytes_histogram") do + ::Gitlab::Metrics.histogram(DATA_TRANSMITTED_BYTES, 'Message size, in bytes, transmitted over action cable') + end + end end end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index df97469d47d..565d8fc76c2 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -2188,6 +2188,15 @@ msgstr "" msgid "Additional text" msgstr "" +msgid "Additional text for the sign-in and Help page." +msgstr "" + +msgid "Additional text to show on the Help page" +msgstr "" + +msgid "Additional text to show on the sign-in page" +msgstr "" + msgid "Address" msgstr "" @@ -3459,7 +3468,7 @@ msgstr "" msgid "Also unassign this user from related issues and merge requests" msgstr "" -msgid "Alternate support URL for help page and help dropdown" +msgid "Alternate support URL for Help page and Help dropdown" msgstr "" msgid "Alternatively, you can convert your account to a managed account by the %{group_name} group." @@ -6409,12 +6418,18 @@ msgstr "" msgid "Checkout" msgstr "" +msgid "Checkout|$%{selectedPlanPrice} per pack per year" +msgstr "" + msgid "Checkout|$%{selectedPlanPrice} per user per year" msgstr "" msgid "Checkout|%{cardType} ending in %{lastFourDigits}" msgstr "" +msgid "Checkout|%{name}'s CI minutes" +msgstr "" + msgid "Checkout|%{name}'s GitLab subscription" msgstr "" @@ -6433,6 +6448,9 @@ msgstr "" msgid "Checkout|(x%{numberOfUsers})" msgstr "" +msgid "Checkout|(x%{quantity})" +msgstr "" + msgid "Checkout|Billing address" msgstr "" @@ -16183,12 +16201,6 @@ msgstr "" msgid "Help" msgstr "" -msgid "Help page" -msgstr "" - -msgid "Help page text and support page url." -msgstr "" - msgid "Helps prevent bots from brute-force attacks." msgstr "" @@ -16248,7 +16260,7 @@ msgstr "" msgid "Hide list" msgstr "" -msgid "Hide marketing-related entries from help" +msgid "Hide marketing-related entries from the Help page." msgstr "" msgid "Hide payload" @@ -24227,9 +24239,6 @@ msgstr "" msgid "Pipelines|Merged YAML is view only" msgstr "" -msgid "Pipelines|Merged YAML unavailable" -msgstr "" - msgid "Pipelines|More Information" msgstr "" @@ -24266,9 +24275,6 @@ msgstr "" msgid "Pipelines|The GitLab CI configuration could not be updated." msgstr "" -msgid "Pipelines|The merged YAML view is only available for the default branch. %{linkStart}Learn more.%{linkEnd}" -msgstr "" - msgid "Pipelines|There are currently no finished pipelines." msgstr "" @@ -25838,6 +25844,9 @@ msgstr "" msgid "ProjectSettings|Fast-forward merges only." msgstr "" +msgid "ProjectSettings|Flexible tool to collaboratively develop ideas and plan work in this project." +msgstr "" + msgid "ProjectSettings|Forks" msgstr "" @@ -25859,9 +25868,6 @@ msgstr "" msgid "ProjectSettings|LFS objects from this repository are available to forks. %{linkStart}How do I remove them?%{linkEnd}" msgstr "" -msgid "ProjectSettings|Lightweight issue tracking system." -msgstr "" - msgid "ProjectSettings|Manages large files such as audio, video, and graphics files." msgstr "" @@ -29033,6 +29039,9 @@ msgstr "" msgid "SecurityConfiguration|Enable %{feature}" msgstr "" +msgid "SecurityConfiguration|Enable Auto DevOps" +msgstr "" + msgid "SecurityConfiguration|Enabled" msgstr "" @@ -29066,6 +29075,9 @@ msgstr "" msgid "SecurityConfiguration|Once you've enabled a scan for the default branch, any subsequent feature branch you create will include the scan." msgstr "" +msgid "SecurityConfiguration|Quickly enable all continuous testing and compliance tools by enabling %{linkStart}Auto DevOps%{linkEnd}" +msgstr "" + msgid "SecurityConfiguration|Runtime security metrics for application environments." msgstr "" @@ -29408,7 +29420,7 @@ msgstr "" msgid "See metrics" msgstr "" -msgid "See our website for getting help" +msgid "See our website for help" msgstr "" msgid "See the affected projects in the GitLab admin panel" @@ -30330,6 +30342,9 @@ msgstr "" msgid "Sign up was successful! Please confirm your email to sign in." msgstr "" +msgid "Sign-in and Help page" +msgstr "" + msgid "Sign-in count:" msgstr "" @@ -32187,6 +32202,15 @@ msgstr "" msgid "Terraform" msgstr "" +msgid "TerraformBanner|Learn more about GitLab's Backend State" +msgstr "" + +msgid "TerraformBanner|The GitLab managed Terraform state backend can store your Terraform state easily and securely, and spares you from setting up additional remote resources. Its features include: versioning, encryption of the state file both in transit and at rest, locking, and remote Terraform plan/apply execution." +msgstr "" + +msgid "TerraformBanner|Using Terraform? Try the GitLab Managed Terraform State" +msgstr "" + msgid "Terraform|%{name} successfully removed" msgstr "" diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb index 59bf397adf2..20238d0a027 100644 --- a/spec/features/admin/admin_settings_spec.rb +++ b/spec/features/admin/admin_settings_spec.rb @@ -571,8 +571,8 @@ RSpec.describe 'Admin updates settings' do new_documentation_url = 'https://docs.gitlab.com' page.within('.as-help-page') do - fill_in 'Help page text', with: 'Example text' - check 'Hide marketing-related entries from help' + fill_in 'Additional text to show on the Help page', with: 'Example text' + check 'Hide marketing-related entries from the Help page.' fill_in 'Support page URL', with: new_support_url fill_in 'Documentation pages URL', with: new_documentation_url click_button 'Save changes' diff --git a/spec/features/help_pages_spec.rb b/spec/features/help_pages_spec.rb index 90647305281..66ba4dc987c 100644 --- a/spec/features/help_pages_spec.rb +++ b/spec/features/help_pages_spec.rb @@ -65,7 +65,7 @@ RSpec.describe 'Help Pages' do end it 'uses a custom support url' do - expect(page).to have_link "See our website for getting help", href: "http://example.com/help" + expect(page).to have_link "See our website for help", href: "http://example.com/help" end end end diff --git a/spec/frontend/pipeline_editor/components/editor/ci_config_merged_preview_spec.js b/spec/frontend/pipeline_editor/components/editor/ci_config_merged_preview_spec.js index b797a664b75..7dd8a77d055 100644 --- a/spec/frontend/pipeline_editor/components/editor/ci_config_merged_preview_spec.js +++ b/spec/frontend/pipeline_editor/components/editor/ci_config_merged_preview_spec.js @@ -1,12 +1,10 @@ -import { GlIcon, GlAlert } from '@gitlab/ui'; +import { GlIcon } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import { EDITOR_READY_EVENT } from '~/editor/constants'; import CiConfigMergedPreview from '~/pipeline_editor/components/editor/ci_config_merged_preview.vue'; import { mockLintResponse, mockCiConfigPath } from '../../mock_data'; -const DEFAULT_BRANCH = 'main'; - describe('Text editor component', () => { let wrapper; @@ -18,7 +16,7 @@ describe('Text editor component', () => { }, }; - const createComponent = ({ props = {}, currentBranch = DEFAULT_BRANCH } = {}) => { + const createComponent = ({ props = {} } = {}) => { wrapper = shallowMount(CiConfigMergedPreview, { propsData: { ciConfigData: mockLintResponse, @@ -26,45 +24,20 @@ describe('Text editor component', () => { }, provide: { ciConfigPath: mockCiConfigPath, - defaultBranch: DEFAULT_BRANCH, }, stubs: { SourceEditor: MockSourceEditor, }, - data() { - return { - currentBranch, - }; - }, }); }; const findIcon = () => wrapper.findComponent(GlIcon); - const findAlert = () => wrapper.findComponent(GlAlert); const findEditor = () => wrapper.findComponent(MockSourceEditor); afterEach(() => { wrapper.destroy(); }); - // This is testing a temporary feature. - // It may be slightly hacky code that doesn't follow best practice. - // See the related MR for more information. - // https://gitlab.com/gitlab-org/gitlab/-/merge_requests/65972#note_626095644 - describe('on a non-default branch', () => { - beforeEach(() => { - createComponent({ currentBranch: 'feature' }); - }); - - it('does not load the editor', () => { - expect(findEditor().exists()).toBe(false); - }); - - it('renders an informational alert', () => { - expect(findAlert().exists()).toBe(true); - }); - }); - describe('when status is valid', () => { beforeEach(() => { createComponent(); diff --git a/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js b/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js index 3dd09c583d6..b0d1a69ee56 100644 --- a/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js +++ b/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js @@ -23,9 +23,10 @@ import { mockBlobContentQueryResponse, mockBlobContentQueryResponseEmptyCiFile, mockBlobContentQueryResponseNoCiFile, + mockCiYml, + mockCommitSha, mockDefaultBranch, mockProjectFullPath, - mockCiYml, mockNewCommitShaResults, } from './mock_data'; @@ -95,10 +96,16 @@ describe('Pipeline editor app component', () => { ]; const resolvers = { + Query: { + commitSha() { + return mockCommitSha; + }, + }, Mutation: { updateCommitSha: mockUpdateCommitSha, }, }; + mockApollo = createMockApollo(handlers, resolvers); const options = { @@ -170,6 +177,7 @@ describe('Pipeline editor app component', () => { expect(mockCiConfigData).toHaveBeenCalledWith({ content: mockCiYml, projectPath: mockProjectFullPath, + sha: mockCommitSha, }); }); }); diff --git a/spec/frontend/projects/terraform_notification/terraform_notification_spec.js b/spec/frontend/projects/terraform_notification/terraform_notification_spec.js new file mode 100644 index 00000000000..be34b207c4b --- /dev/null +++ b/spec/frontend/projects/terraform_notification/terraform_notification_spec.js @@ -0,0 +1,62 @@ +import { GlBanner } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import { setCookie, parseBoolean } from '~/lib/utils/common_utils'; +import TerraformNotification from '~/projects/terraform_notification/components/terraform_notification.vue'; + +jest.mock('~/lib/utils/common_utils'); + +const bannerDissmisedKey = 'terraform_notification_dismissed_for_project_1'; + +describe('TerraformNotificationBanner', () => { + let wrapper; + + const propsData = { + projectId: 1, + }; + const findBanner = () => wrapper.findComponent(GlBanner); + + beforeEach(() => { + wrapper = shallowMount(TerraformNotification, { + propsData, + stubs: { GlBanner }, + }); + }); + + afterEach(() => { + wrapper.destroy(); + parseBoolean.mockReturnValue(false); + }); + + describe('when the dismiss cookie is set', () => { + beforeEach(() => { + parseBoolean.mockReturnValue(true); + wrapper = shallowMount(TerraformNotification, { + propsData, + }); + }); + + it('should not render the banner', () => { + expect(findBanner().exists()).toBe(false); + }); + }); + + describe('when the dismiss cookie is not set', () => { + it('should render the banner', () => { + expect(findBanner().exists()).toBe(true); + }); + }); + + describe('when close button is clicked', () => { + beforeEach(async () => { + await findBanner().vm.$emit('close'); + }); + + it('should set the cookie with the bannerDissmisedKey', () => { + expect(setCookie).toHaveBeenCalledWith(bannerDissmisedKey, true); + }); + + it('should remove the banner', () => { + expect(findBanner().exists()).toBe(false); + }); + }); +}); diff --git a/spec/frontend/security_configuration/components/auto_dev_ops_alert_spec.js b/spec/frontend/security_configuration/components/auto_dev_ops_alert_spec.js new file mode 100644 index 00000000000..467ae35408c --- /dev/null +++ b/spec/frontend/security_configuration/components/auto_dev_ops_alert_spec.js @@ -0,0 +1,55 @@ +import { GlAlert } from '@gitlab/ui'; +import { mount } from '@vue/test-utils'; +import AutoDevopsAlert from '~/security_configuration/components/auto_dev_ops_alert.vue'; + +const autoDevopsHelpPagePath = '/autoDevopsHelpPagePath'; +const autoDevopsPath = '/enableAutoDevopsPath'; + +describe('AutoDevopsAlert component', () => { + let wrapper; + + const createComponent = () => { + wrapper = mount(AutoDevopsAlert, { + provide: { + autoDevopsHelpPagePath, + autoDevopsPath, + }, + }); + }; + + const findAlert = () => wrapper.findComponent(GlAlert); + + beforeEach(() => { + createComponent(); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('contains correct body text', () => { + expect(wrapper.text()).toContain('Quickly enable all'); + }); + + it('renders the link correctly', () => { + const link = wrapper.find('a'); + + expect(link.attributes('href')).toBe(autoDevopsHelpPagePath); + expect(link.text()).toBe('Auto DevOps'); + }); + + it('bubbles up dismiss events from the GlAlert', () => { + expect(wrapper.emitted('dismiss')).toBe(undefined); + + findAlert().vm.$emit('dismiss'); + + expect(wrapper.emitted('dismiss')).toEqual([[]]); + }); + + it('has a button pointing to autoDevopsPath', () => { + expect(findAlert().props()).toMatchObject({ + primaryButtonText: 'Enable Auto DevOps', + primaryButtonLink: autoDevopsPath, + }); + }); +}); diff --git a/spec/frontend/security_configuration/components/redesigned_app_spec.js b/spec/frontend/security_configuration/components/redesigned_app_spec.js index 69c79082f5a..119a25a77c1 100644 --- a/spec/frontend/security_configuration/components/redesigned_app_spec.js +++ b/spec/frontend/security_configuration/components/redesigned_app_spec.js @@ -2,6 +2,7 @@ import { GlTab } from '@gitlab/ui'; import { mount } from '@vue/test-utils'; import { makeMockUserCalloutDismisser } from 'helpers/mock_user_callout_dismisser'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; +import AutoDevopsAlert from '~/security_configuration/components/auto_dev_ops_alert.vue'; import { SAST_NAME, SAST_SHORT_NAME, @@ -13,6 +14,7 @@ import { LICENSE_COMPLIANCE_HELP_PATH, } from '~/security_configuration/components/constants'; import FeatureCard from '~/security_configuration/components/feature_card.vue'; + import RedesignedSecurityConfigurationApp, { i18n, } from '~/security_configuration/components/redesigned_app.vue'; @@ -23,6 +25,8 @@ import { } from '~/vue_shared/security_reports/constants'; const upgradePath = '/upgrade'; +const autoDevopsHelpPagePath = '/autoDevopsHelpPagePath'; +const autoDevopsPath = '/autoDevopsPath'; const gitlabCiHistoryPath = 'test/historyPath'; describe('redesigned App component', () => { @@ -37,6 +41,8 @@ describe('redesigned App component', () => { propsData, provide: { upgradePath, + autoDevopsHelpPagePath, + autoDevopsPath, }, stubs: { UserCalloutDismisser: makeMockUserCalloutDismisser({ @@ -76,6 +82,7 @@ describe('redesigned App component', () => { container: findByTestId('compliance-testing-tab'), }); const findUpgradeBanner = () => wrapper.findComponent(UpgradeBanner); + const findAutoDevopsAlert = () => wrapper.findComponent(AutoDevopsAlert); const securityFeaturesMock = [ { @@ -154,6 +161,44 @@ describe('redesigned App component', () => { }); }); + describe('autoDevOpsAlert', () => { + describe('given the right props', () => { + beforeEach(() => { + createComponent({ + augmentedSecurityFeatures: securityFeaturesMock, + augmentedComplianceFeatures: complianceFeaturesMock, + autoDevopsEnabled: false, + gitlabCiPresent: false, + canEnableAutoDevops: true, + }); + }); + + it('should show AutoDevopsAlert', () => { + expect(findAutoDevopsAlert().exists()).toBe(true); + }); + + it('calls the dismiss callback when closing the AutoDevopsAlert', () => { + expect(userCalloutDismissSpy).not.toHaveBeenCalled(); + + findAutoDevopsAlert().vm.$emit('dismiss'); + + expect(userCalloutDismissSpy).toHaveBeenCalledTimes(1); + }); + }); + + describe('given the wrong props', () => { + beforeEach(() => { + createComponent({ + augmentedSecurityFeatures: securityFeaturesMock, + augmentedComplianceFeatures: complianceFeaturesMock, + }); + }); + it('should not show AutoDevopsAlert', () => { + expect(findAutoDevopsAlert().exists()).toBe(false); + }); + }); + }); + describe('upgrade banner', () => { const makeAvailable = (available) => (feature) => ({ ...feature, available }); diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index 1804a9a99cf..10be0975225 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -876,6 +876,37 @@ RSpec.describe ProjectsHelper do end end + describe '#show_terraform_banner?' do + let_it_be(:ruby) { create(:programming_language, name: 'Ruby') } + let_it_be(:hcl) { create(:programming_language, name: 'HCL') } + + subject { helper.show_terraform_banner?(project) } + + before do + create(:repository_language, project: project, programming_language: language, share: 1) + end + + context 'the project does not contain terraform files' do + let(:language) { ruby } + + it { is_expected.to be_falsey } + end + + context 'the project contains terraform files' do + let(:language) { hcl } + + it { is_expected.to be_truthy } + + context 'the project already has a terraform state' do + before do + create(:terraform_state, project: project) + end + + it { is_expected.to be_falsey } + end + end + end + describe '#project_title' do subject { helper.project_title(project) } diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb index 42878b508de..58938251ca1 100644 --- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb @@ -91,6 +91,20 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do end end + context 'with job:tags' do + let(:attributes) do + { + name: 'rspec', + ref: 'master', + job_variables: [{ key: 'VARIABLE', value: 'value', public: true }], + tag_list: ['static-tag', '$VARIABLE', '$NO_VARIABLE'] + } + end + + it { is_expected.to include(tag_list: ['static-tag', 'value', '$NO_VARIABLE']) } + it { is_expected.to include(yaml_variables: [{ key: 'VARIABLE', value: 'value', public: true }]) } + end + context 'with cache:key' do let(:attributes) do { diff --git a/spec/lib/gitlab/kas/client_spec.rb b/spec/lib/gitlab/kas/client_spec.rb index 7bf2d30ca48..40e18f58ee4 100644 --- a/spec/lib/gitlab/kas/client_spec.rb +++ b/spec/lib/gitlab/kas/client_spec.rb @@ -30,10 +30,11 @@ RSpec.describe Gitlab::Kas::Client do describe 'gRPC calls' do let(:token) { instance_double(JSONWebToken::HMACToken, encoded: 'test-token') } + let(:kas_url) { 'grpc://example.kas.internal' } before do allow(Gitlab::Kas).to receive(:enabled?).and_return(true) - allow(Gitlab::Kas).to receive(:internal_url).and_return('grpc://example.kas.internal') + allow(Gitlab::Kas).to receive(:internal_url).and_return(kas_url) expect(JSONWebToken::HMACToken).to receive(:new) .with(Gitlab::Kas.secret) @@ -80,5 +81,21 @@ RSpec.describe Gitlab::Kas::Client do it { expect(subject).to eq(agent_configurations) } end + + describe 'with grpcs' do + let(:stub) { instance_double(Gitlab::Agent::ConfigurationProject::Rpc::ConfigurationProject::Stub) } + let(:kas_url) { 'grpcs://example.kas.internal' } + + it 'uses a ChannelCredentials object' do + expect(Gitlab::Agent::ConfigurationProject::Rpc::ConfigurationProject::Stub).to receive(:new) + .with('example.kas.internal', instance_of(GRPC::Core::ChannelCredentials), timeout: described_class::TIMEOUT) + .and_return(stub) + + allow(stub).to receive(:list_agent_config_files) + .and_return(double(config_files: [])) + + described_class.new.list_agent_config_files(project: project) + end + end end end diff --git a/spec/lib/gitlab/metrics/subscribers/action_cable_spec.rb b/spec/lib/gitlab/metrics/subscribers/action_cable_spec.rb index 153cf43be0a..0516091a8ec 100644 --- a/spec/lib/gitlab/metrics/subscribers/action_cable_spec.rb +++ b/spec/lib/gitlab/metrics/subscribers/action_cable_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe Gitlab::Metrics::Subscribers::ActionCable, :request_store do let(:subscriber) { described_class.new } let(:counter) { double(:counter) } - let(:data) { { data: { event: 'updated' } } } + let(:data) { { 'result' => { 'data' => { 'event' => 'updated' } } } } let(:channel_class) { 'IssuesChannel' } let(:event) do double( @@ -35,6 +35,17 @@ RSpec.describe Gitlab::Metrics::Subscribers::ActionCable, :request_store do subscriber.transmit(event) end + + it 'tracks size of payload as JSON' do + allow(::Gitlab::Metrics).to receive(:histogram).with( + :action_cable_transmitted_bytes, /transmit/ + ).and_return(counter) + message_size = ::ActiveSupport::JSON.encode(data).bytesize + + expect(counter).to receive(:observe).with({ channel: channel_class, operation: 'event' }, message_size) + + subscriber.transmit(event) + end end describe '#broadcast' do diff --git a/spec/models/project_ci_cd_setting_spec.rb b/spec/models/project_ci_cd_setting_spec.rb index caab182cda8..c206ba27ec1 100644 --- a/spec/models/project_ci_cd_setting_spec.rb +++ b/spec/models/project_ci_cd_setting_spec.rb @@ -22,8 +22,8 @@ RSpec.describe ProjectCiCdSetting do end describe '#job_token_scope_enabled' do - it 'is false by default' do - expect(described_class.new.job_token_scope_enabled).to be_falsey + it 'is true by default' do + expect(described_class.new.job_token_scope_enabled).to be_truthy end end diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index 3fb683ea0fa..b80b3f4e159 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -889,10 +889,10 @@ RSpec.describe 'Git HTTP requests' do context 'when admin mode is enabled', :enable_admin_mode do it_behaves_like 'can download code only' - it 'downloads from other project get status 403' do + it 'downloads from other project get status 404' do clone_get "#{other_project.full_path}.git", user: 'gitlab-ci-token', password: build.token - expect(response).to have_gitlab_http_status(:forbidden) + expect(response).to have_gitlab_http_status(:not_found) end end @@ -1490,10 +1490,10 @@ RSpec.describe 'Git HTTP requests' do context 'when admin mode is enabled', :enable_admin_mode do it_behaves_like 'can download code only' - it 'downloads from other project get status 403' do + it 'downloads from other project get status 404' do clone_get "#{other_project.full_path}.git", user: 'gitlab-ci-token', password: build.token - expect(response).to have_gitlab_http_status(:forbidden) + expect(response).to have_gitlab_http_status(:not_found) end end diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb index 02eb4262690..833889431e9 100644 --- a/spec/requests/lfs_http_spec.rb +++ b/spec/requests/lfs_http_spec.rb @@ -574,7 +574,7 @@ RSpec.describe 'Git LFS API and storage' do let(:pipeline) { create(:ci_empty_pipeline, project: other_project) } # I'm not sure what this tests that is different from the previous test - it_behaves_like 'LFS http 403 response' + it_behaves_like 'LFS http 404 response' end end @@ -1049,7 +1049,7 @@ RSpec.describe 'Git LFS API and storage' do let(:pipeline) { create(:ci_empty_pipeline, project: other_project) } # I'm not sure what this tests that is different from the previous test - it_behaves_like 'LFS http 403 response' + it_behaves_like 'LFS http 404 response' end end diff --git a/spec/services/ci/create_pipeline_service/evaluate_runner_tags_spec.rb b/spec/services/ci/create_pipeline_service/evaluate_runner_tags_spec.rb new file mode 100644 index 00000000000..df881c1ac8f --- /dev/null +++ b/spec/services/ci/create_pipeline_service/evaluate_runner_tags_spec.rb @@ -0,0 +1,144 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Ci::CreatePipelineService do + let_it_be(:group) { create(:group, :private) } + let_it_be(:group_variable) { create(:ci_group_variable, group: group, key: 'RUNNER_TAG', value: 'group')} + let_it_be(:project) { create(:project, :repository, group: group) } + let_it_be(:user) { create(:user) } + + let(:service) { described_class.new(project, user, ref: 'master') } + let(:pipeline) { service.execute(:push) } + let(:job) { pipeline.builds.find_by(name: 'job') } + + before do + project.add_developer(user) + stub_ci_pipeline_yaml_file config + end + + context 'when the variable is set' do + let(:config) do + <<~EOS + variables: + KUBERNETES_RUNNER: kubernetes + + job: + tags: + - docker + - $KUBERNETES_RUNNER + script: + - echo "Hello runner selector feature" + EOS + end + + it 'uses the evaluated variable' do + expect(pipeline).to be_created_successfully + expect(job.tags.pluck(:name)).to match_array(%w[docker kubernetes]) + end + end + + context 'when the tag is composed by two variables' do + let(:config) do + <<~EOS + variables: + CLOUD_PROVIDER: aws + KUBERNETES_RUNNER: kubernetes + ENVIRONMENT_NAME: prod + + job: + tags: + - docker + - $CLOUD_PROVIDER-$KUBERNETES_RUNNER-$ENVIRONMENT_NAME + script: + - echo "Hello runner selector feature" + EOS + end + + it 'uses the evaluated variables' do + expect(pipeline).to be_created_successfully + expect(job.tags.pluck(:name)).to match_array(%w[docker aws-kubernetes-prod]) + end + end + + context 'when the variable is not set' do + let(:config) do + <<~EOS + job: + tags: + - docker + - $KUBERNETES_RUNNER + script: + - echo "Hello runner selector feature" + EOS + end + + it 'uses the variable as a regular string' do + expect(pipeline).to be_created_successfully + expect(job.tags.pluck(:name)).to match_array(%w[docker $KUBERNETES_RUNNER]) + end + end + + context 'when the tag uses group variables' do + let(:config) do + <<~EOS + job: + tags: + - docker + - $RUNNER_TAG + script: + - echo "Hello runner selector feature" + EOS + end + + it 'uses the evaluated variables' do + expect(pipeline).to be_created_successfully + expect(job.tags.pluck(:name)).to match_array(%w[docker group]) + end + end + + context 'when the tag has the same variable name defined for both group and project' do + let_it_be(:project_variable) { create(:ci_variable, project: project, key: 'RUNNER_TAG', value: 'project') } + + let(:config) do + <<~EOS + variables: + RUNNER_TAG: pipeline + job: + tags: + - docker + - $RUNNER_TAG + script: + - echo "Hello runner selector feature" + EOS + end + + it 'uses the project variable instead of group due to variable precedence' do + expect(pipeline).to be_created_successfully + expect(job.tags.pluck(:name)).to match_array(%w[docker project]) + end + end + + context 'with parallel:matrix config' do + let(:tags) { pipeline.builds.map(&:tags).flatten.pluck(:name) } + + let(:config) do + <<~EOS + job: + parallel: + matrix: + - PROVIDER: [aws, gcp] + STACK: [monitoring, backup, app] + tags: + - ${PROVIDER}-${STACK} + script: + - echo "Hello runner selector feature" + EOS + end + + it 'uses the evaluated variables' do + expect(pipeline).to be_created_successfully + expect(tags).to match_array(%w[aws-monitoring aws-backup aws-app gcp-monitoring gcp-backup gcp-app]) + end + end +end diff --git a/spec/views/projects/_flash_messages.html.haml_spec.rb b/spec/views/projects/_flash_messages.html.haml_spec.rb new file mode 100644 index 00000000000..e1858229208 --- /dev/null +++ b/spec/views/projects/_flash_messages.html.haml_spec.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'projects/_flash_messages' do + let_it_be(:template) { 'projects/flash_messages' } + let_it_be(:user) { create(:user) } + + let_it_be(:ruby) { create(:programming_language, name: 'Ruby') } + let_it_be(:html) { create(:programming_language, name: 'HTML') } + let_it_be(:hcl) { create(:programming_language, name: 'HCL') } + + before do + allow(view).to receive(:current_user).and_return(user) + allow(view).to receive(:can?).with(user, :download_code, project).and_return(true) + end + + context 'when current_user has download_code permission' do + context 'when user has a terraform state' do + let_it_be(:project) { create(:project) } + let_it_be(:terraform_state) { create(:terraform_state, :locked, :with_version, project: project) } + + it "doesn't show the terraform notification banner" do + render(template, project: project) + expect(view.content_for(:flash_message)).not_to have_selector('.js-terraform-notification') + end + end + + context 'when there are no .tf files in the repository' do + let_it_be(:project) { create(:project) } + let_it_be(:mock_repo_languages) do + { project => { ruby => 0.5, html => 0.5 } } + end + + before do + mock_repo_languages.each do |project, lang_shares| + lang_shares.each do |lang, share| + create(:repository_language, project: project, programming_language: lang, share: share) + end + end + end + + it "doesn't show the terraform notification banner" do + render(template, project: project) + expect(view.content_for(:flash_message)).not_to have_selector('.js-terraform-notification') + end + end + + context 'when .tf files are present in the repository and user does not have any terraform states' do + let_it_be(:project) { create(:project) } + let_it_be(:mock_repo_languages) do + { project => { ruby => 0.5, hcl => 0.5 } } + end + + before do + mock_repo_languages.each do |project, lang_shares| + lang_shares.each do |lang, share| + create(:repository_language, project: project, programming_language: lang, share: share) + end + end + end + + it 'shows the terraform notification banner' do + render(template, project: project) + expect(view.content_for(:flash_message)).to have_selector('.js-terraform-notification') + end + end + end +end |