diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-07-11 15:07:02 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-07-11 15:07:02 +0300 |
commit | bcd11d993d80d46053a97ee3b0344ed4d2b4571b (patch) | |
tree | e3b4047cafd580d3a3d7d8cde094c183ee9aabfc | |
parent | 871b886a1794e5baefd6b2f96caf2ac4ce5da6ca (diff) |
Add latest changes from gitlab-org/gitlab@master
74 files changed, 1028 insertions, 356 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f1fbc0119bd..fc2f061bd81 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -29,13 +29,9 @@ default: .default-ruby-variables: &default-ruby-variables RUBY_VERSION: "3.0" - OMNIBUS_GITLAB_RUBY3_BUILD: "true" - OMNIBUS_GITLAB_CACHE_EDITION: "GITLAB_RUBY3" -.backcompat-ruby-variables: &backcompat-ruby-variables - RUBY_VERSION: "2.7" - OMNIBUS_GITLAB_RUBY2_BUILD: "true" - OMNIBUS_GITLAB_CACHE_EDITION: "GITLAB_RUBY2" +.next-ruby-variables: &next-ruby-variables + RUBY_VERSION: "3.1" .default-branch-pipeline-failure-variables: &default-branch-pipeline-failure-variables CREATE_RAILS_TEST_FAILURE_ISSUES: "true" @@ -59,10 +55,10 @@ workflow: # they serve no purpose and will run anyway when the changes are merged. - if: '$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME =~ /^release-tools\/\d+\.\d+\.\d+-rc\d+$/ && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME =~ /^[\d-]+-stable(-ee)?$/ && $CI_PROJECT_PATH == "gitlab-org/gitlab"' when: never - # For merge requests running exclusively in Ruby 2.7 - - if: '$CI_MERGE_REQUEST_LABELS =~ /pipeline:run-in-ruby2/' + # For merge requests running exclusively in Ruby 3.1 + - if: '$CI_MERGE_REQUEST_LABELS =~ /pipeline:run-in-ruby3_1/' variables: - <<: *backcompat-ruby-variables + <<: *next-ruby-variables PIPELINE_NAME: 'Ruby $RUBY_VERSION $CI_MERGE_REQUEST_EVENT_TYPE MR pipeline' NO_SOURCEMAPS: 'true' - if: '$CI_MERGE_REQUEST_LABELS =~ /Community contribution/' @@ -84,10 +80,10 @@ workflow: <<: *default-branch-pipeline-failure-variables CRYSTALBALL: "true" PIPELINE_NAME: 'Scheduled Ruby $RUBY_VERSION $CI_COMMIT_BRANCH branch pipeline' - # Run pipelines for ruby2 branch - - if: '$CI_COMMIT_BRANCH == "ruby2" && $CI_PIPELINE_SOURCE == "schedule"' + # Run pipelines for ruby3_1 branch + - if: '$CI_COMMIT_BRANCH == "ruby3_1" && $CI_PIPELINE_SOURCE == "schedule"' variables: - <<: *backcompat-ruby-variables + <<: *next-ruby-variables PIPELINE_NAME: 'Scheduled Ruby $RUBY_VERSION $CI_COMMIT_BRANCH branch pipeline' # This work around https://gitlab.com/gitlab-org/gitlab/-/issues/332411 whichs prevents usage of dependency proxy # when pipeline is triggered by a project access token. @@ -147,7 +143,6 @@ variables: UBI_VERSION: "8.6" CHROME_VERSION: "113" DOCKER_VERSION: "23.0.1" - RUBY_VERSION: "2.7" RUBYGEMS_VERSION: "3.4" GO_VERSION: "1.20" RUST_VERSION: "1.65" diff --git a/.gitlab/ci/qa-common/variables.gitlab-ci.yml b/.gitlab/ci/qa-common/variables.gitlab-ci.yml index 817f2e0020a..fe980293f3a 100644 --- a/.gitlab/ci/qa-common/variables.gitlab-ci.yml +++ b/.gitlab/ci/qa-common/variables.gitlab-ci.yml @@ -3,12 +3,6 @@ variables: REGISTRY_HOST: "registry.gitlab.com" REGISTRY_GROUP: "gitlab-org" - SKIP_OMNIBUS_TRIGGER: "true" - OMNIBUS_GITLAB_CACHE_UPDATE: "false" - OMNIBUS_GITLAB_RUBY3_BUILD: "false" - OMNIBUS_GITLAB_RUBY2_BUILD: "false" - OMNIBUS_GITLAB_CACHE_EDITION: "GITLAB" - OMNIBUS_GITLAB_BUILD_ON_ALL_OS: "false" ALLURE_JOB_NAME: $CI_PROJECT_NAME COLORIZED_LOGS: "true" QA_LOG_LEVEL: "info" @@ -17,3 +11,4 @@ variables: # run all tests by default when package-and-test is included natively in other projects # this will be overridden when selective test execution is used in gitlab canonical project QA_RUN_ALL_TESTS: "true" + USE_OLD_RUBY_VERSION: "true" diff --git a/.gitlab/ci/qa.gitlab-ci.yml b/.gitlab/ci/qa.gitlab-ci.yml index b10a02fd9fb..6d9f75a2c9a 100644 --- a/.gitlab/ci/qa.gitlab-ci.yml +++ b/.gitlab/ci/qa.gitlab-ci.yml @@ -35,9 +35,6 @@ - DEBIAN_VERSION - REGISTRY_GROUP - REGISTRY_HOST - - OMNIBUS_GITLAB_CACHE_EDITION - - OMNIBUS_GITLAB_RUBY3_BUILD - - OMNIBUS_GITLAB_RUBY2_BUILD trigger: strategy: depend forward: @@ -101,9 +98,9 @@ trigger-omnibus: TOP_UPSTREAM_SOURCE_PROJECT: $CI_PROJECT_PATH SECURITY_SOURCES: $SECURITY_SOURCES CACHE_UPDATE: $OMNIBUS_GITLAB_CACHE_UPDATE - RUBY3_BUILD: $OMNIBUS_GITLAB_RUBY3_BUILD - RUBY2_BUILD: $OMNIBUS_GITLAB_RUBY2_BUILD - CACHE_EDITION: $OMNIBUS_GITLAB_CACHE_EDITION + USE_OLD_RUBY_VERSION: "true" + CACHE_EDITION: "GITLAB_RUBY3_0" + NEXT_RUBY_VERSION: "3.1.4" BUILD_ON_ALL_OS: $OMNIBUS_GITLAB_BUILD_ON_ALL_OS SKIP_QA_TEST: "true" ee: $EE diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml index 129f82f9165..d0ce284fcef 100644 --- a/.gitlab/ci/rules.gitlab-ci.yml +++ b/.gitlab/ci/rules.gitlab-ci.yml @@ -21,7 +21,7 @@ if: '$FORCE_GITLAB_CI' .if-default-refs: &if-default-refs - if: '$CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH || $CI_COMMIT_REF_NAME =~ /^[\d-]+-stable(-ee)?$/ || $CI_COMMIT_REF_NAME =~ /^\d+-\d+-auto-deploy-\d+$/ || $CI_COMMIT_REF_NAME =~ /^security\// || $CI_COMMIT_REF_NAME == "ruby2" || ($CI_MERGE_REQUEST_EVENT_TYPE == "merged_result" || $CI_MERGE_REQUEST_EVENT_TYPE == "detached") || $CI_COMMIT_TAG || $FORCE_GITLAB_CI' + if: '$CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH || $CI_COMMIT_REF_NAME =~ /^[\d-]+-stable(-ee)?$/ || $CI_COMMIT_REF_NAME =~ /^\d+-\d+-auto-deploy-\d+$/ || $CI_COMMIT_REF_NAME =~ /^security\// || $CI_COMMIT_REF_NAME == "ruby3_1" || ($CI_MERGE_REQUEST_EVENT_TYPE == "merged_result" || $CI_MERGE_REQUEST_EVENT_TYPE == "detached") || $CI_COMMIT_TAG || $FORCE_GITLAB_CI' .if-default-branch-refs: &if-default-branch-refs if: '$CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH && $CI_MERGE_REQUEST_IID == null' @@ -53,8 +53,8 @@ .if-merge-request-targeting-stable-branch: &if-merge-request-targeting-stable-branch if: '($CI_MERGE_REQUEST_EVENT_TYPE == "merged_result" || $CI_MERGE_REQUEST_EVENT_TYPE == "detached") && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME =~ /^[\d-]+-stable(-ee|-jh)?$/' -.if-merge-request-labels-run-in-ruby2: &if-merge-request-labels-run-in-ruby2 - if: '$CI_MERGE_REQUEST_LABELS =~ /pipeline:run-in-ruby2/' +.if-merge-request-labels-run-in-ruby3_1: &if-merge-request-labels-run-in-ruby3_1 + if: '$CI_MERGE_REQUEST_LABELS =~ /pipeline:run-in-ruby3_1/' .if-merge-request-labels-as-if-foss: &if-merge-request-labels-as-if-foss if: '$CI_MERGE_REQUEST_LABELS =~ /pipeline:run-as-if-foss/' @@ -113,8 +113,8 @@ .if-default-branch-schedule-nightly: &if-default-branch-schedule-nightly if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_PIPELINE_SOURCE == "schedule" && $SCHEDULE_TYPE == "nightly"' -.if-ruby2-branch-schedule-nightly: &if-ruby2-branch-schedule-nightly - if: '$CI_COMMIT_BRANCH == "ruby2" && $CI_PIPELINE_SOURCE == "schedule" && $SCHEDULE_TYPE == "nightly"' +.if-ruby3_1-branch-schedule-nightly: &if-ruby3_1-branch-schedule-nightly + if: '$CI_COMMIT_BRANCH == "ruby3_1" && $CI_PIPELINE_SOURCE == "schedule" && $SCHEDULE_TYPE == "nightly"' .if-security-schedule: &if-security-schedule if: '$CI_PROJECT_NAMESPACE == "gitlab-org/security" && $CI_PIPELINE_SOURCE == "schedule"' @@ -149,8 +149,8 @@ .if-dot-com-gitlab-org-and-security-tag: &if-dot-com-gitlab-org-and-security-tag if: '$CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_NAMESPACE =~ /^gitlab-org($|\/security$)/ && $CI_COMMIT_TAG' -.if-ruby2-branch: &if-ruby2-branch - if: '$CI_COMMIT_BRANCH == "ruby2"' +.if-ruby3_1-branch: &if-ruby3_1-branch + if: '$CI_COMMIT_BRANCH == "ruby3_1" || $CI_MERGE_REQUEST_LABELS =~ /pipeline:run-in-ruby3_1/' #################### # Changes patterns # @@ -868,7 +868,7 @@ - <<: *if-dot-com-gitlab-org-schedule variables: ARCH: amd64,arm64 - - <<: *if-ruby2-branch + - <<: *if-ruby3_1-branch .build-images:rules:build-qa-image-as-if-foss: rules: @@ -913,7 +913,7 @@ - <<: *if-merge-request-targeting-stable-branch changes: *setup-test-env-patterns allow_failure: true - - <<: *if-ruby2-branch + - <<: *if-ruby3_1-branch allow_failure: true # We include the job under the matching conditions below, but unlike in .qa:rules:e2e:test-on-gdk we don't need to # set OMNIBUS_GITLAB_BUILD_ON_ALL_OS when testing against GDK @@ -948,7 +948,7 @@ when: never - <<: *if-merge-request-targeting-stable-branch changes: *setup-test-env-patterns - - <<: *if-ruby2-branch + - <<: *if-ruby3_1-branch - <<: *if-merge-request-labels-run-review-app - <<: *if-auto-deploy-branches - <<: *if-default-refs @@ -1151,7 +1151,7 @@ changes: *setup-test-env-patterns - <<: *if-merge-request-labels-run-review-app - <<: *if-auto-deploy-branches - - <<: *if-ruby2-branch + - <<: *if-ruby3_1-branch - <<: *if-default-refs changes: *ci-build-images-patterns - <<: *if-default-refs @@ -1422,7 +1422,7 @@ allow_failure: true - <<: *if-dot-com-gitlab-org-schedule allow_failure: true - - <<: *if-ruby2-branch + - <<: *if-ruby3_1-branch # Rules to support .qa:rules:package-and-test-ee - <<: *if-merge-request changes: *dependency-patterns @@ -1459,7 +1459,10 @@ - <<: *if-merge-request-targeting-stable-branch changes: *setup-test-env-patterns allow_failure: true - - <<: *if-ruby2-branch + - <<: *if-ruby3_1-branch + variables: + USE_OLD_RUBY_VERSION: 'false' + CACHE_EDITION: 'GITLAB_RUBY3_1' allow_failure: true - <<: *if-merge-request changes: *dependency-patterns @@ -1977,7 +1980,7 @@ - <<: *if-merge-request-labels-pipeline-expedite when: never - <<: *if-default-branch-schedule-nightly - - <<: *if-ruby2-branch-schedule-nightly + - <<: *if-ruby3_1-branch-schedule-nightly - <<: *if-merge-request-labels-run-all-rspec .rails:rules:rspec-coverage: @@ -2451,7 +2454,7 @@ .setup:rules:verify-ruby-3.0: rules: - - <<: *if-merge-request-labels-run-in-ruby2 + - <<: *if-merge-request-labels-run-in-ruby3_1 .setup:rules:verify-tests-yml: rules: diff --git a/.gitlab/ci/setup.gitlab-ci.yml b/.gitlab/ci/setup.gitlab-ci.yml index 85409d37fa4..aaa7f7255c7 100644 --- a/.gitlab/ci/setup.gitlab-ci.yml +++ b/.gitlab/ci/setup.gitlab-ci.yml @@ -57,7 +57,7 @@ verify-ruby-3.0: - .setup:rules:verify-ruby-3.0 stage: prepare script: - - echo 'Please remove label ~"pipeline:run-in-ruby2" so we do test against Ruby 3.0 (default version) before merging the merge request' + - echo 'Please remove label ~"pipeline:run-in-ruby3_1" so we do test against Ruby 3.0 (default version) before merging the merge request' - exit 1 verify-tests-yml: @@ -185,15 +185,15 @@ trigger-omnibus-env: before_script: - source scripts/utils.sh script: + # Note that OMNIBUS_GITLAB_CACHE_UPDATE is not used in the code, but it is actually used in the 2-hourly maintenance pipeline schedule. - | SECURITY_SOURCES=$([[ ! "$CI_PROJECT_NAMESPACE" =~ ^gitlab-org\/security ]] || echo "true") echo "SECURITY_SOURCES=${SECURITY_SOURCES:-false}" > $BUILD_ENV echo "OMNIBUS_GITLAB_CACHE_UPDATE=${OMNIBUS_GITLAB_CACHE_UPDATE:-false}" >> $BUILD_ENV for version_file in *_VERSION; do echo "$version_file=$(cat $version_file)" >> $BUILD_ENV; done - echo "OMNIBUS_GITLAB_RUBY3_BUILD=${OMNIBUS_GITLAB_RUBY3_BUILD:-false}" >> $BUILD_ENV - echo "OMNIBUS_GITLAB_RUBY2_BUILD=${OMNIBUS_GITLAB_RUBY2_BUILD:-false}" >> $BUILD_ENV - echo "OMNIBUS_GITLAB_CACHE_EDITION=${OMNIBUS_GITLAB_CACHE_EDITION:-GITLAB}" >> $BUILD_ENV echo "OMNIBUS_GITLAB_BUILD_ON_ALL_OS=${OMNIBUS_GITLAB_BUILD_ON_ALL_OS:-false}" >> $BUILD_ENV + echo "USE_OLD_RUBY_VERSION=${USE_OLD_RUBY_VERSION:-false}" >> $BUILD_ENV + echo "NEXT_RUBY_VERSION=${NEXT_RUBY_VERSION}" >> $BUILD_ENV echo "GITLAB_ASSETS_TAG=$(assets_image_tag)" >> $BUILD_ENV echo "EE=$([[ $FOSS_ONLY == '1' ]] && echo 'false' || echo 'true')" >> $BUILD_ENV target_branch_name="${CI_MERGE_REQUEST_TARGET_BRANCH_NAME:-${CI_COMMIT_REF_NAME}}" diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index d3076a818a3..4141eeca609 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -ab5d534057f73d43a8539406f5923e9314e9c982 +4689a41c399ee6d943a24ec14d946ad1596e3376 diff --git a/app/assets/javascripts/design_management/pages/index.vue b/app/assets/javascripts/design_management/pages/index.vue index b55610721a9..af7c5a25d94 100644 --- a/app/assets/javascripts/design_management/pages/index.vue +++ b/app/assets/javascripts/design_management/pages/index.vue @@ -424,7 +424,7 @@ export default { 'gl-new-card-body gl-mx-3!': hasDesigns, }" > - <gl-loading-icon v-if="isLoading" size="lg" /> + <gl-loading-icon v-if="isLoading" size="sm" class="gl-py-4" /> <gl-alert v-else-if="error" variant="danger" :dismissible="false"> {{ $options.i18n.designLoadingError }} </gl-alert> diff --git a/app/assets/javascripts/environments/components/edit_environment.vue b/app/assets/javascripts/environments/components/edit_environment.vue index 9e3f8d996e0..a2405d23924 100644 --- a/app/assets/javascripts/environments/components/edit_environment.vue +++ b/app/assets/javascripts/environments/components/edit_environment.vue @@ -2,7 +2,9 @@ import { GlLoadingIcon } from '@gitlab/ui'; import { createAlert } from '~/alert'; import { visitUrl } from '~/lib/utils/url_utility'; +import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import getEnvironment from '../graphql/queries/environment.query.graphql'; +import getEnvironmentWithNamespace from '../graphql/queries/environment_with_namespace.graphql'; import updateEnvironment from '../graphql/mutations/update_environment.mutation.graphql'; import EnvironmentForm from './environment_form.vue'; @@ -11,10 +13,15 @@ export default { GlLoadingIcon, EnvironmentForm, }, + mixins: [glFeatureFlagsMixin()], inject: ['projectEnvironmentsPath', 'projectPath', 'environmentName'], apollo: { environment: { - query: getEnvironment, + query() { + return this.glFeatures?.kubernetesNamespaceForEnvironment + ? getEnvironmentWithNamespace + : getEnvironment; + }, variables() { return { environmentName: this.environmentName, @@ -52,6 +59,7 @@ export default { id: this.formEnvironment.id, externalUrl: this.formEnvironment.externalUrl, clusterAgentId: this.formEnvironment.clusterAgentId, + kubernetesNamespace: this.formEnvironment.kubernetesNamespace, }, }, }); diff --git a/app/assets/javascripts/environments/components/environment_form.vue b/app/assets/javascripts/environments/components/environment_form.vue index 727d86fbb55..58e57f365eb 100644 --- a/app/assets/javascripts/environments/components/environment_form.vue +++ b/app/assets/javascripts/environments/components/environment_form.vue @@ -7,6 +7,7 @@ import { GlCollapsibleListbox, GlLink, GlSprintf, + GlAlert, } from '@gitlab/ui'; import { helpPagePath } from '~/helpers/help_page_helper'; import { isAbsolute } from '~/lib/utils/url_utility'; @@ -15,6 +16,10 @@ import { ENVIRONMENT_NEW_HELP_TEXT, ENVIRONMENT_EDIT_HELP_TEXT, } from 'ee_else_ce/environments/constants'; +import csrf from '~/lib/utils/csrf'; +import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; +import { getIdFromGraphQLId } from '~/graphql_shared/utils'; +import getNamespacesQuery from '../graphql/queries/k8s_namespaces.query.graphql'; import getUserAuthorizedAgents from '../graphql/queries/user_authorized_agents.query.graphql'; export default { @@ -26,10 +31,13 @@ export default { GlCollapsibleListbox, GlLink, GlSprintf, + GlAlert, }, + mixins: [glFeatureFlagsMixin()], inject: { protectedEnvironmentSettingsPath: { default: '' }, projectPath: { default: '' }, + kasTunnelUrl: { default: '' }, }, props: { environment: { @@ -62,6 +70,8 @@ export default { urlFeedback: __('The URL should start with http:// or https://'), agentLabel: s__('Environments|GitLab agent'), agentHelpText: s__('Environments|Select agent'), + namespaceLabel: s__('Environments|Kubernetes namespace (optional)'), + namespaceHelpText: s__('Environments|Select namespace'), save: __('Save'), cancel: __('Cancel'), reset: __('Reset'), @@ -79,10 +89,41 @@ export default { userAccessAuthorizedAgents: [], loadingAgentsList: false, selectedAgentId: this.environment.clusterAgentId, - searchTerm: '', + agentSearchTerm: '', + selectedNamespace: this.environment.kubernetesNamespace, + k8sNamespaces: [], + namespaceSearchTerm: '', + kubernetesError: '', }; }, + apollo: { + k8sNamespaces: { + query: getNamespacesQuery, + skip() { + return !this.showNamespaceSelector; + }, + variables() { + return { + configuration: this.k8sAccessConfiguration, + }; + }, + update(data) { + return data?.k8sNamespaces || []; + }, + error(error) { + this.kubernetesError = error.message; + }, + result(result) { + if (!result?.error && !result.errors?.length) { + this.kubernetesError = null; + } + }, + }, + }, computed: { + loadingNamespacesList() { + return this.$apollo.queries.k8sNamespaces.loading; + }, isNameDisabled() { return Boolean(this.environment.id); }, @@ -103,7 +144,7 @@ export default { }; }); }, - dropdownToggleText() { + agentDropdownToggleText() { if (!this.selectedAgentId) { return this.$options.i18n.agentHelpText; } @@ -113,11 +154,56 @@ export default { return selectedAgentById?.text || this.environment.clusterAgent?.name; }, filteredAgentsList() { - const lowerCasedSearchTerm = this.searchTerm.toLowerCase(); + const lowerCasedSearchTerm = this.agentSearchTerm.toLowerCase(); return this.agentsList.filter((item) => item.text.toLowerCase().includes(lowerCasedSearchTerm), ); }, + namespacesList() { + return this.k8sNamespaces.map((item) => { + return { + value: item.metadata.name, + text: item.metadata.name, + }; + }); + }, + filteredNamespacesList() { + const lowerCasedSearchTerm = this.namespaceSearchTerm.toLowerCase(); + return this.namespacesList.filter((item) => + item.text.toLowerCase().includes(lowerCasedSearchTerm), + ); + }, + isKasUserAccessAvailable() { + return this.glFeatures?.kasUserAccessProject; + }, + isKasKubernetesNamespaceAvailable() { + return this.glFeatures?.kubernetesNamespaceForEnvironment; + }, + showNamespaceSelector() { + return Boolean( + this.isKasUserAccessAvailable && + this.isKasKubernetesNamespaceAvailable && + this.selectedAgentId, + ); + }, + namespaceDropdownToggleText() { + return this.selectedNamespace || this.$options.i18n.namespaceHelpText; + }, + k8sAccessConfiguration() { + if (!this.showNamespaceSelector) { + return null; + } + return { + basePath: this.kasTunnelUrl, + baseOptions: { + headers: { + 'GitLab-Agent-Id': getIdFromGraphQLId(this.selectedAgentId), + ...csrf.headers, + }, + withCredentials: true, + }, + }; + }, }, watch: { environment(change) { @@ -146,7 +232,14 @@ export default { }); }, onAgentSearch(search) { - this.searchTerm = search; + this.agentSearchTerm = search; + }, + onAgentChange($event) { + this.selectedNamespace = null; + this.onChange({ ...this.environment, clusterAgentId: $event, kubernetesNamespace: null }); + }, + onNamespaceSearch(search) { + this.namespaceSearchTerm = search; }, }, }; @@ -225,20 +318,48 @@ export default { id="environment_agent" v-model="selectedAgentId" class="gl-w-full" + data-testid="agent-selector" block :items="filteredAgentsList" :loading="loadingAgentsList" - :toggle-text="dropdownToggleText" + :toggle-text="agentDropdownToggleText" :header-text="$options.i18n.agentHelpText" :reset-button-label="$options.i18n.reset" :searchable="true" @shown="getAgentsList" @search="onAgentSearch" - @select="onChange({ ...environment, clusterAgentId: $event })" + @select="onAgentChange" @reset="onChange({ ...environment, clusterAgentId: null })" /> </gl-form-group> + <gl-form-group + v-if="showNamespaceSelector" + :label="$options.i18n.namespaceLabel" + label-for="environment_namespace" + > + <gl-alert v-if="kubernetesError" variant="warning" :dismissible="false" class="gl-mb-5"> + {{ kubernetesError }} + </gl-alert> + <gl-collapsible-listbox + v-else + id="environment_namespace" + v-model="selectedNamespace" + class="gl-w-full" + data-testid="namespace-selector" + block + :items="filteredNamespacesList" + :loading="loadingNamespacesList" + :toggle-text="namespaceDropdownToggleText" + :header-text="$options.i18n.namespaceHelpText" + :reset-button-label="$options.i18n.reset" + :searchable="true" + @search="onNamespaceSearch" + @select="onChange({ ...environment, kubernetesNamespace: $event })" + @reset="onChange({ ...environment, kubernetesNamespace: null })" + /> + </gl-form-group> + <div class="gl-mr-6"> <gl-button :loading="loading" diff --git a/app/assets/javascripts/environments/components/new_environment.vue b/app/assets/javascripts/environments/components/new_environment.vue index 464b530b503..c6bc94b0b80 100644 --- a/app/assets/javascripts/environments/components/new_environment.vue +++ b/app/assets/javascripts/environments/components/new_environment.vue @@ -34,6 +34,7 @@ export default { externalUrl: this.environment.externalUrl, projectPath: this.projectPath, clusterAgentId: this.environment.clusterAgentId, + kubernetesNamespace: this.environment.kubernetesNamespace, }, }, }); diff --git a/app/assets/javascripts/environments/components/new_environment_item.vue b/app/assets/javascripts/environments/components/new_environment_item.vue index 1f3d429cc3e..aa2960c810a 100644 --- a/app/assets/javascripts/environments/components/new_environment_item.vue +++ b/app/assets/javascripts/environments/components/new_environment_item.vue @@ -14,6 +14,7 @@ import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import isLastDeployment from '../graphql/queries/is_last_deployment.query.graphql'; import getEnvironmentClusterAgent from '../graphql/queries/environment_cluster_agent.query.graphql'; +import getEnvironmentClusterAgentWithNamespace from '../graphql/queries/environment_cluster_agent_with_namespace.query.graphql'; import ExternalUrl from './environment_external_url.vue'; import Actions from './environment_actions.vue'; import StopComponent from './environment_stop.vue'; @@ -82,7 +83,7 @@ export default { tierTooltip: s__('Environment|Deployment tier'), }, data() { - return { visible: false, clusterAgent: null }; + return { visible: false, clusterAgent: null, kubernetesNamespace: '' }; }, computed: { icon() { @@ -167,6 +168,9 @@ export default { isKubernetesOverviewAvailable() { return this.glFeatures?.kasUserAccessProject; }, + isKubernetesNamespaceAvailable() { + return this.glFeatures?.kubernetesNamespaceForEnvironment; + }, showKubernetesOverview() { return Boolean(this.isKubernetesOverviewAvailable && this.clusterAgent); }, @@ -186,9 +190,14 @@ export default { variables() { return { environmentName: this.environment.name, projectFullPath: this.projectPath }; }, - query: getEnvironmentClusterAgent, + query() { + return this.isKubernetesNamespaceAvailable + ? getEnvironmentClusterAgentWithNamespace + : getEnvironmentClusterAgent; + }, update(data) { this.clusterAgent = data?.project?.environment?.clusterAgent; + this.kubernetesNamespace = data?.project?.environment?.kubernetesNamespace || ''; }, }); }, @@ -369,10 +378,7 @@ export default { </gl-sprintf> </div> <div v-if="showKubernetesOverview" :class="$options.kubernetesOverviewClasses"> - <kubernetes-overview - :cluster-agent="clusterAgent" - :namespace="environment.kubernetesNamespace" - /> + <kubernetes-overview :cluster-agent="clusterAgent" :namespace="kubernetesNamespace" /> </div> <div v-if="rolloutStatus" :class="$options.deployBoardClasses"> <deploy-board-wrapper diff --git a/app/assets/javascripts/environments/constants.js b/app/assets/javascripts/environments/constants.js index 2b178964c37..dc9481a5429 100644 --- a/app/assets/javascripts/environments/constants.js +++ b/app/assets/javascripts/environments/constants.js @@ -108,3 +108,19 @@ export const PHASE_RUNNING = 'Running'; export const PHASE_PENDING = 'Pending'; export const PHASE_SUCCEEDED = 'Succeeded'; export const PHASE_FAILED = 'Failed'; + +const ERROR_UNAUTHORIZED = 'unauthorized'; +const ERROR_FORBIDDEN = 'forbidden'; +const ERROR_NOT_FOUND = 'not found'; +const ERROR_OTHER = 'other'; + +export const CLUSTER_AGENT_ERROR_MESSAGES = { + [ERROR_UNAUTHORIZED]: s__( + 'Environment|Unauthorized to access the cluster agent from this environment. Check your authentication and try again.', + ), + [ERROR_FORBIDDEN]: s__( + 'Environment|Forbidden to access the cluster agent from this environment.', + ), + [ERROR_NOT_FOUND]: s__('Environment|Cluster agent not found.'), + [ERROR_OTHER]: s__('Environment|There was an error connecting to the cluster agent.'), +}; diff --git a/app/assets/javascripts/environments/edit.js b/app/assets/javascripts/environments/edit.js index f936085af15..3f22b83e618 100644 --- a/app/assets/javascripts/environments/edit.js +++ b/app/assets/javascripts/environments/edit.js @@ -1,5 +1,6 @@ import Vue from 'vue'; import VueApollo from 'vue-apollo'; +import { removeLastSlashInUrlPath } from '~/lib/utils/url_utility'; import EditEnvironment from './components/edit_environment.vue'; import { apolloProvider } from './graphql/client'; @@ -15,6 +16,7 @@ export default (el) => { protectedEnvironmentSettingsPath, projectPath, environmentName, + kasTunnelUrl, } = el.dataset; return new Vue({ @@ -25,6 +27,7 @@ export default (el) => { protectedEnvironmentSettingsPath, projectPath, environmentName, + kasTunnelUrl: removeLastSlashInUrlPath(kasTunnelUrl), }, render(h) { return h(EditEnvironment); diff --git a/app/assets/javascripts/environments/graphql/client.js b/app/assets/javascripts/environments/graphql/client.js index 6d06cff06b9..553b06e632f 100644 --- a/app/assets/javascripts/environments/graphql/client.js +++ b/app/assets/javascripts/environments/graphql/client.js @@ -8,6 +8,7 @@ import environmentToStopQuery from './queries/environment_to_stop.query.graphql' import k8sPodsQuery from './queries/k8s_pods.query.graphql'; import k8sServicesQuery from './queries/k8s_services.query.graphql'; import k8sWorkloadsQuery from './queries/k8s_workloads.query.graphql'; +import k8sNamespacesQuery from './queries/k8s_namespaces.query.graphql'; import { resolvers } from './resolvers'; import typeDefs from './typedefs.graphql'; @@ -161,6 +162,14 @@ export const apolloProvider = (endpoint) => { }, }, }); + cache.writeQuery({ + query: k8sNamespacesQuery, + data: { + metadata: { + name: null, + }, + }, + }); return new VueApollo({ defaultClient, }); diff --git a/app/assets/javascripts/environments/graphql/queries/environment_cluster_agent_with_namespace.query.graphql b/app/assets/javascripts/environments/graphql/queries/environment_cluster_agent_with_namespace.query.graphql new file mode 100644 index 00000000000..5e72c2dac20 --- /dev/null +++ b/app/assets/javascripts/environments/graphql/queries/environment_cluster_agent_with_namespace.query.graphql @@ -0,0 +1,20 @@ +query getEnvironmentClusterAgentWithNamespace($projectFullPath: ID!, $environmentName: String) { + project(fullPath: $projectFullPath) { + id + environment(name: $environmentName) { + id + kubernetesNamespace + clusterAgent { + id + name + webPath + tokens { + nodes { + id + lastUsedAt + } + } + } + } + } +} diff --git a/app/assets/javascripts/environments/graphql/queries/environment_with_namespace.graphql b/app/assets/javascripts/environments/graphql/queries/environment_with_namespace.graphql new file mode 100644 index 00000000000..42796f982b6 --- /dev/null +++ b/app/assets/javascripts/environments/graphql/queries/environment_with_namespace.graphql @@ -0,0 +1,15 @@ +query getEnvironmentWithNamespace($projectFullPath: ID!, $environmentName: String) { + project(fullPath: $projectFullPath) { + id + environment(name: $environmentName) { + id + name + externalUrl + kubernetesNamespace + clusterAgent { + id + name + } + } + } +} diff --git a/app/assets/javascripts/environments/graphql/queries/k8s_namespaces.query.graphql b/app/assets/javascripts/environments/graphql/queries/k8s_namespaces.query.graphql new file mode 100644 index 00000000000..c05d09b6ca2 --- /dev/null +++ b/app/assets/javascripts/environments/graphql/queries/k8s_namespaces.query.graphql @@ -0,0 +1,7 @@ +query getK8sNamespaces($configuration: LocalConfiguration) { + k8sNamespaces(configuration: $configuration) @client { + metadata { + name + } + } +} diff --git a/app/assets/javascripts/environments/graphql/resolvers.js b/app/assets/javascripts/environments/graphql/resolvers.js index 044e7927606..8cfe44c5a05 100644 --- a/app/assets/javascripts/environments/graphql/resolvers.js +++ b/app/assets/javascripts/environments/graphql/resolvers.js @@ -6,6 +6,7 @@ import { parseIntPagination, normalizeHeaders, } from '~/lib/utils/common_utils'; +import { humanizeClusterErrors } from '../helpers/k8s_integration_helper'; import pollIntervalQuery from './queries/poll_interval.query.graphql'; import environmentToRollbackQuery from './queries/environment_to_rollback.query.graphql'; @@ -72,6 +73,11 @@ const mapWorkloadItems = (items, kind) => { }); }; +const handleClusterError = (err) => { + const error = err?.response?.data?.message ? new Error(err.response.data.message) : err; + throw error; +}; + export const resolvers = (endpoint) => ({ Query: { environmentApp(_context, { page, scope, search }, { cache }) { @@ -124,8 +130,7 @@ export const resolvers = (endpoint) => ({ return podsApi .then((res) => res?.data?.items || []) .catch((err) => { - const error = err?.response?.data?.message ? new Error(err.response.data.message) : err; - throw error; + handleClusterError(err); }); }, k8sServices(_, { configuration }) { @@ -148,8 +153,7 @@ export const resolvers = (endpoint) => ({ }); }) .catch((err) => { - const error = err?.response?.data?.message ? new Error(err.response.data.message) : err; - throw error; + handleClusterError(err); }); }, k8sWorkloads(_, { configuration, namespace }) { @@ -206,6 +210,19 @@ export const resolvers = (endpoint) => ({ return summaryList; }); }, + k8sNamespaces(_, { configuration }) { + const coreV1Api = new CoreV1Api(new Configuration(configuration)); + const namespacesApi = coreV1Api.listCoreV1Namespace(); + + return namespacesApi + .then((res) => { + return res?.data?.items || []; + }) + .catch((err) => { + const error = err?.response?.data?.reason || err; + throw new Error(humanizeClusterErrors(error)); + }); + }, }, Mutation: { stopEnvironmentREST(_, { environment }, { client }) { diff --git a/app/assets/javascripts/environments/graphql/typedefs.graphql b/app/assets/javascripts/environments/graphql/typedefs.graphql index 7e46385946f..e2c22dda554 100644 --- a/app/assets/javascripts/environments/graphql/typedefs.graphql +++ b/app/assets/javascripts/environments/graphql/typedefs.graphql @@ -160,6 +160,12 @@ type LocalK8sWorkloads { JobList: [localK8sJob] CronJobList: [localK8sCronJob] } +type k8sNamespaceMetadata { + name: String +} +type LocalK8sNamespaces { + metadata: k8sNamespaceMetadata +} extend type Query { environmentApp(page: Int, scope: String): LocalEnvironmentApp diff --git a/app/assets/javascripts/environments/helpers/k8s_integration_helper.js b/app/assets/javascripts/environments/helpers/k8s_integration_helper.js index 45c65c93a91..e49f1451759 100644 --- a/app/assets/javascripts/environments/helpers/k8s_integration_helper.js +++ b/app/assets/javascripts/environments/helpers/k8s_integration_helper.js @@ -1,4 +1,5 @@ import { differenceInSeconds } from '~/lib/utils/datetime_utility'; +import { CLUSTER_AGENT_ERROR_MESSAGES } from '../constants'; export function generateServicePortsString(ports) { if (!ports?.length) return ''; @@ -139,3 +140,9 @@ export function getCronJobsStatuses(items) { ...(ready.length && { ready }), }; } + +export function humanizeClusterErrors(reason) { + const errorReason = reason.toLowerCase(); + const errorMessage = CLUSTER_AGENT_ERROR_MESSAGES[errorReason]; + return errorMessage || CLUSTER_AGENT_ERROR_MESSAGES.other; +} diff --git a/app/assets/javascripts/environments/new.js b/app/assets/javascripts/environments/new.js index 5dd112ac5e6..652085b1f28 100644 --- a/app/assets/javascripts/environments/new.js +++ b/app/assets/javascripts/environments/new.js @@ -1,5 +1,6 @@ import Vue from 'vue'; import VueApollo from 'vue-apollo'; +import { removeLastSlashInUrlPath } from '~/lib/utils/url_utility'; import NewEnvironment from './components/new_environment.vue'; import { apolloProvider } from './graphql/client'; @@ -10,12 +11,16 @@ export default (el) => { return null; } - const { projectEnvironmentsPath, projectPath } = el.dataset; + const { projectEnvironmentsPath, projectPath, kasTunnelUrl } = el.dataset; return new Vue({ el, apolloProvider: apolloProvider(), - provide: { projectEnvironmentsPath, projectPath }, + provide: { + projectEnvironmentsPath, + projectPath, + kasTunnelUrl: removeLastSlashInUrlPath(kasTunnelUrl), + }, render(h) { return h(NewEnvironment); }, diff --git a/app/assets/javascripts/issues/related_merge_requests/components/related_merge_requests.vue b/app/assets/javascripts/issues/related_merge_requests/components/related_merge_requests.vue index de59210ff19..cc3e2b145ed 100644 --- a/app/assets/javascripts/issues/related_merge_requests/components/related_merge_requests.vue +++ b/app/assets/javascripts/issues/related_merge_requests/components/related_merge_requests.vue @@ -66,7 +66,7 @@ export default { <template> <div v-if="isFetchingMergeRequests || (!isFetchingMergeRequests && totalCount)"> <div class="gl-new-card"> - <div class="gl-new-card-header"> + <div class="gl-new-card-header gl-flex-direction-column"> <div class="gl-new-card-title-wrapper"> <gl-link class="anchor gl-absolute gl-text-decoration-none" @@ -81,23 +81,23 @@ export default { <gl-icon name="merge-request" class="gl-ml-3 gl-mr-2 gl-text-gray-500" /> <span data-testid="count" class="gl-text-gray-500">{{ totalCount }}</span> </template> - <p - v-if="hasClosingMergeRequest && !isFetchingMergeRequests" - class="gl-font-sm gl-font-weight-normal gl-flex-basis-full gl-mb-0 gl-text-gray-500" - > - {{ closingMergeRequestsText }} - </p> </div> </div> + <p + v-if="hasClosingMergeRequest && !isFetchingMergeRequests" + class="gl-new-card-description" + > + {{ closingMergeRequestsText }} + </p> </div> - <gl-loading-icon - v-if="isFetchingMergeRequests" - size="sm" - label="Fetching related merge requests" - class="gl-py-4" - /> <div class="gl-new-card-body"> <div class="gl-new-card-content"> + <gl-loading-icon + v-if="isFetchingMergeRequests" + size="sm" + label="Fetching related merge requests" + class="gl-py-2" + /> <ul class="content-list related-items-list"> <li v-for="mr in mergeRequests" diff --git a/app/assets/javascripts/notes/components/discussion_notes_replies_wrapper.vue b/app/assets/javascripts/notes/components/discussion_notes_replies_wrapper.vue index 1dd07fe90d2..571928b972b 100644 --- a/app/assets/javascripts/notes/components/discussion_notes_replies_wrapper.vue +++ b/app/assets/javascripts/notes/components/discussion_notes_replies_wrapper.vue @@ -21,7 +21,7 @@ export default { 'li', { class: - 'discussion-collapsible gl-border-solid gl-border-gray-100 gl-border-1 gl-rounded-base clearfix', + 'discussion-collapsible gl-border-solid gl-border-gray-100 gl-border-1 gl-rounded-base gl-border-top-0', }, [h('ul', { class: 'notes' }, children)], ); diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue b/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue index 028f5370028..f7c0f960c0e 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue @@ -68,7 +68,7 @@ export default { }, isCollapsible() { if (!this.isLoadingSummary && this.loadingState !== LOADING_STATES.collapsedError) { - if (this.shouldCollapse) { + if ('shouldCollapse' in this) { return this.shouldCollapse(this.collapsedData); } diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/index.js b/app/assets/javascripts/vue_merge_request_widget/components/extensions/index.js index 7e329399957..0b8f5ffa397 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/extensions/index.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/index.js @@ -1,4 +1,5 @@ import Vue from 'vue'; +import { markRaw } from '~/lib/utils/vue3compat/mark_raw'; import ExtensionBase from './base.vue'; // Holds all the currently registered extensions @@ -7,45 +8,47 @@ export const registeredExtensions = Vue.observable({ extensions: [] }); export const registerExtension = (extension) => { // Pushes into the extenions array a dynamically created Vue component // that gets exteneded from `base.vue` - registeredExtensions.extensions.push({ - extends: ExtensionBase, - name: extension.name, - props: { - mr: { - type: Object, - required: true, + registeredExtensions.extensions.push( + markRaw({ + extends: ExtensionBase, + name: extension.name, + props: { + mr: { + type: Object, + required: true, + }, }, - }, - telemetry: extension.telemetry, - i18n: extension.i18n, - expandEvent: extension.expandEvent, - enablePolling: extension.enablePolling, - enableExpandedPolling: extension.enableExpandedPolling, - modalComponent: extension.modalComponent, - computed: { - ...extension.props.reduce( - (acc, propKey) => ({ - ...acc, - [propKey]() { - return this.mr[propKey]; - }, - }), - {}, - ), - ...Object.keys(extension.computed).reduce( - (acc, computedKey) => ({ - ...acc, - // Making the computed property a method allows us to pass in arguments - // this allows for each computed property to receive some data - [computedKey]() { - return extension.computed[computedKey]; - }, - }), - {}, - ), - }, - methods: { - ...extension.methods, - }, - }); + telemetry: extension.telemetry, + i18n: extension.i18n, + expandEvent: extension.expandEvent, + enablePolling: extension.enablePolling, + enableExpandedPolling: extension.enableExpandedPolling, + modalComponent: extension.modalComponent, + computed: { + ...extension.props.reduce( + (acc, propKey) => ({ + ...acc, + [propKey]() { + return this.mr[propKey]; + }, + }), + {}, + ), + ...Object.keys(extension.computed).reduce( + (acc, computedKey) => ({ + ...acc, + // Making the computed property a method allows us to pass in arguments + // this allows for each computed property to receive some data + [computedKey]() { + return extension.computed[computedKey]; + }, + }), + {}, + ), + }, + methods: { + ...extension.methods, + }, + }), + ); }; diff --git a/app/assets/stylesheets/framework/new_card.scss b/app/assets/stylesheets/framework/new_card.scss index e6a4165980c..3981b713564 100644 --- a/app/assets/stylesheets/framework/new_card.scss +++ b/app/assets/stylesheets/framework/new_card.scss @@ -37,10 +37,6 @@ @include gl-line-height-24; } - &-title-lg { - @include gl-font-lg; - } - &-count { @include gl-mx-3; @include gl-font-base; @@ -49,8 +45,9 @@ } &-description { + @include gl-font-sm; @include gl-text-gray-500; - @include gl-mt-1; + @include gl-m-0; } &-toggle { diff --git a/app/assets/stylesheets/page_bundles/merge_requests.scss b/app/assets/stylesheets/page_bundles/merge_requests.scss index d7d506cdcf3..f722b71772d 100644 --- a/app/assets/stylesheets/page_bundles/merge_requests.scss +++ b/app/assets/stylesheets/page_bundles/merge_requests.scss @@ -234,15 +234,15 @@ $tabs-holder-z-index: 250; margin-top: -2px; margin-right: $gl-padding-8; } +} - // tiny adjustment to vertical align with the note header text - .discussion-collapsible { - border: 0 !important; - margin: 0; +// tiny adjustment to vertical align with the note header text +.discussion-collapsible { + border: 0 !important; + margin: 0; - .timeline-icon { - padding-top: 2px; - } + .timeline-icon { + padding-top: 2px; } } diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index c47e6bd3375..364b30c9c57 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -12,8 +12,9 @@ class Projects::EnvironmentsController < Projects::ApplicationController push_frontend_feature_flag(:environment_details_vue, @project) end - before_action only: [:index] do + before_action only: [:index, :edit, :new] do push_frontend_feature_flag(:kas_user_access_project, @project) + push_frontend_feature_flag(:kubernetes_namespace_for_environment) end before_action :authorize_read_environment! @@ -24,7 +25,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController before_action :environment, only: [:show, :edit, :update, :stop, :terminal, :terminal_websocket_authorize, :cancel_auto_stop] before_action :verify_api_request!, only: :terminal_websocket_authorize before_action :expire_etag_cache, only: [:index], unless: -> { request.format.json? } - before_action :set_kas_cookie, only: [:index], if: -> { current_user && request.format.html? } + before_action :set_kas_cookie, only: [:index, :edit, :new], if: -> { current_user && request.format.html? } after_action :expire_etag_cache, only: [:cancel_auto_stop] track_event :index, :folder, :show, :new, :edit, :create, :update, :stop, :cancel_auto_stop, :terminal, diff --git a/app/graphql/types/root_storage_statistics_type.rb b/app/graphql/types/root_storage_statistics_type.rb index f7d07823667..45473fadff0 100644 --- a/app/graphql/types/root_storage_statistics_type.rb +++ b/app/graphql/types/root_storage_statistics_type.rb @@ -8,12 +8,17 @@ module Types field :build_artifacts_size, GraphQL::Types::Float, null: false, description: 'CI artifacts size in bytes.' field :container_registry_size, GraphQL::Types::Float, null: false, description: 'Container Registry size in bytes.' + field :container_registry_size_is_estimated, GraphQL::Types::Boolean, method: :registry_size_estimated, null: false, + description: 'Indicates whether the deduplicated Container Registry size for ' \ + 'the namespace is an estimated value or not.' field :dependency_proxy_size, GraphQL::Types::Float, null: false, description: 'Dependency Proxy sizes in bytes.' field :lfs_objects_size, GraphQL::Types::Float, null: false, description: 'LFS objects size in bytes.' field :packages_size, GraphQL::Types::Float, null: false, description: 'Packages size in bytes.' field :pipeline_artifacts_size, GraphQL::Types::Float, null: false, description: 'CI pipeline artifacts size in bytes.' - field :registry_size_estimated, GraphQL::Types::Boolean, null: false, + field :registry_size_estimated, GraphQL::Types::Boolean, + null: false, + deprecated: { reason: 'Use `container_registry_size_is_estimated`', milestone: '16.2' }, description: 'Indicates whether the deduplicated Container Registry size for ' \ 'the namespace is an estimated value or not.' field :repository_size, GraphQL::Types::Float, null: false, description: 'Git repository size in bytes.' diff --git a/app/models/ci/build_metadata.rb b/app/models/ci/build_metadata.rb index 382f861a802..4c723bb7c0c 100644 --- a/app/models/ci/build_metadata.rb +++ b/app/models/ci/build_metadata.rb @@ -10,11 +10,9 @@ module Ci include Presentable include ChronicDurationAttribute include Gitlab::Utils::StrongMemoize - include SafelyChangeColumnDefault self.table_name = 'p_ci_builds_metadata' self.primary_key = 'id' - columns_changing_default :partition_id partitionable scope: :build diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index f26831c1049..c95393d60fb 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -8,13 +8,11 @@ class CommitStatus < Ci::ApplicationRecord include Presentable include BulkInsertableAssociations include TaggableQueries - include SafelyChangeColumnDefault self.table_name = 'ci_builds' self.sequence_name = 'ci_builds_id_seq' self.primary_key = :id partitionable scope: :pipeline - columns_changing_default :partition_id belongs_to :user belongs_to :project diff --git a/app/views/projects/environments/edit.html.haml b/app/views/projects/environments/edit.html.haml index c637fee28f9..1c107784e08 100644 --- a/app/views/projects/environments/edit.html.haml +++ b/app/views/projects/environments/edit.html.haml @@ -4,4 +4,5 @@ #js-edit-environment{ data: { project_environments_path: project_environments_path(@project), protected_environment_settings_path: (project_settings_ci_cd_path(@project, anchor: 'js-protected-environments-settings') if @project.licensed_feature_available?(:protected_environments)), project_path: @project.full_path, - environment_name: @environment.name } } + environment_name: @environment.name, + kas_tunnel_url: ::Gitlab::Kas.tunnel_url } } diff --git a/app/views/projects/environments/new.html.haml b/app/views/projects/environments/new.html.haml index 9e8484b88b9..301c19ee6f0 100644 --- a/app/views/projects/environments/new.html.haml +++ b/app/views/projects/environments/new.html.haml @@ -3,4 +3,4 @@ - page_title s_("Environments|New Environment") - add_page_specific_style 'page_bundles/environments' -#js-new-environment{ data: { project_environments_path: project_environments_path(@project), project_path: @project.full_path, } } +#js-new-environment{ data: { project_environments_path: project_environments_path(@project), project_path: @project.full_path, kas_tunnel_url: ::Gitlab::Kas.tunnel_url } } diff --git a/app/views/projects/issues/_related_branches.html.haml b/app/views/projects/issues/_related_branches.html.haml index 3d6a266dc4d..21f1a4d19fa 100644 --- a/app/views/projects/issues/_related_branches.html.haml +++ b/app/views/projects/issues/_related_branches.html.haml @@ -1,24 +1,25 @@ - if @related_branches.any? - if @related_branches.any? - = render Pajamas::CardComponent.new(card_options: { class: 'gl-bg-gray-10 gl-mt-5 gl-mb-0' }, header_options: { class: 'gl-bg-white gl-pl-5 gl-pr-4 gl-py-4' } , body_options: { class: 'gl-py-3 gl-px-4' }) do |c| + = render Pajamas::CardComponent.new(card_options: { class: 'gl-new-card' }, header_options: { class: 'gl-new-card-header' } , body_options: { class: 'gl-new-card-body' }) do |c| - c.with_header do - %h3.card-title.h5.gl-my-0.gl-display-flex.gl-align-items-center.gl-flex-grow-1.gl-relative.gl-line-height-24 - = link_to "", "#related-branches", class: "gl-link anchor position-absolute gl-text-decoration-none", "aria-hidden": true - = _('Related branches') - .gl-display-inline-flex.gl-mx-3.gl-text-gray-500 - .gl-display-inline-flex.gl-align-items-center - = sprite_icon('branch', css_class: "gl-mr-2 gl-text-gray-500 gl-icon") - = @related_branches.size + .gl-new-card-title-wrapper + %h3.gl-new-card-title + = link_to "", "#related-branches", class: "gl-link anchor position-absolute gl-text-decoration-none", "aria-hidden": true + = _('Related branches') + .gl-new-card-count + = sprite_icon('branch', css_class: "gl-mr-2 gl-text-gray-500 gl-icon") + = @related_branches.size - c.with_body do - %ul.related-merge-requests.content-list.gl-p-3! - - @related_branches.each do |branch| - %li.list-item{ class: "gl-py-0! gl-border-0!" } - .item-body.gl-display-flex.align-items-center.gl-px-3.gl-pr-2.gl-mx-n2 - .item-contents.gl-display-flex.gl-align-items-center.gl-flex-wrap.gl-flex-grow-1.gl-min-h-7 - .item-title.gl-display-flex.mb-xl-0.gl-min-w-0 - - if branch[:pipeline_status].present? - %span.related-branch-ci-status - = render 'ci/status/icon', status: branch[:pipeline_status] - %span.related-branch-info - %strong - = link_to branch[:name], branch[:link], class: "ref-name" + .gl-new-card-content + %ul.related-merge-requests.content-list + - @related_branches.each do |branch| + %li.list-item{ class: "gl-py-0! gl-border-0!" } + .item-body.gl-display-flex.align-items-center.gl-px-3.gl-pr-2.gl-mx-n2 + .item-contents.gl-display-flex.gl-align-items-center.gl-flex-wrap.gl-flex-grow-1.gl-min-h-7 + .item-title.gl-display-flex.mb-xl-0.gl-min-w-0 + - if branch[:pipeline_status].present? + %span.related-branch-ci-status + = render 'ci/status/icon', status: branch[:pipeline_status] + %span.related-branch-info + %strong + = link_to branch[:name], branch[:link], class: "ref-name" diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml index 7a4ae409ee2..e1c904d000f 100644 --- a/app/views/projects/labels/index.html.haml +++ b/app/views/projects/labels/index.html.haml @@ -16,12 +16,13 @@ .labels-container -# Only show it in the first page - hide = @available_labels.empty? || (params[:page].present? && params[:page] != '1') - .prioritized-labels.gl-rounded-base.gl-border.gl-bg-gray-10.gl-mt-4{ class: [('hide' if hide), ('is-not-draggable' unless can_admin_label)] } - .gl-px-5.gl-py-4.gl-bg-white.gl-rounded-top-base.gl-border-b - %h3.card-title.h5.gl-m-0.gl-relative.gl-line-height-24 - = _('Prioritized labels') - .gl-font-sm.gl-font-weight-semibold.gl-text-gray-500 - = _('Drag to reorder prioritized labels and change their relative priority.') + .prioritized-labels.gl-new-card{ class: [('hide' if hide), ('is-not-draggable' unless can_admin_label)] } + .gl-new-card-header + .gl-new-card-title-wrapper.gl-flex-direction-column + %h3.gl-new-card-title + = _('Prioritized labels') + .gl-new-card-description + = _('Drag to reorder prioritized labels and change their relative priority.') .js-prioritized-labels.gl-px-3.gl-rounded-base.manage-labels-list{ data: { url: set_priorities_project_labels_path(@project), sortable: can_admin_label } } #js-priority-labels-empty-state.priority-labels-empty-state{ class: "#{'hidden' unless @prioritized_labels.empty? && search.blank?}" } = render 'shared/empty_states/priority_labels' @@ -32,12 +33,14 @@ = _('No prioritized labels with such name or description') - if @labels.any? - .other-labels.gl-rounded-base.gl-border.gl-bg-gray-10.gl-mt-4 - .gl-px-5.gl-py-4.gl-bg-white.gl-rounded-top-base.gl-border-b - %h3.card-title.h5.gl-m-0.gl-relative.gl-line-height-24{ class: ('hide' if hide) }= _('Other labels') - .js-other-labels.gl-px-3.gl-rounded-base.manage-labels-list - = render partial: 'shared/label', collection: @labels, as: :label, locals: { subject: @project } - = paginate @labels, theme: 'gitlab' + .other-labels.gl-new-card + .gl-new-card-header + .gl-new-card-title-wrapper + %h3.gl-new-card-title{ class: ('hide' if hide) }= _('Other labels') + .gl-new-card-body + .js-other-labels.manage-labels-list.gl-new-card-content + = render partial: 'shared/label', collection: @labels, as: :label, locals: { subject: @project } + = paginate @labels, theme: 'gitlab' - elsif search.present? .other-labels diff --git a/app/views/shared/empty_states/_priority_labels.html.haml b/app/views/shared/empty_states/_priority_labels.html.haml index b24fa0b3bdb..688df1705aa 100644 --- a/app/views/shared/empty_states/_priority_labels.html.haml +++ b/app/views/shared/empty_states/_priority_labels.html.haml @@ -1,8 +1,8 @@ -.text-center.gl-mt-1.gl-mb-6 +.text-center.gl-mt-1.gl-mb-5 .svg-content{ data: { qa_selector: 'label_svg_content' } } = image_tag 'illustrations/empty-state/empty-labels-starred-md.svg' - if can?(current_user, :admin_label, @project) - %div + %h5.gl-my-0 = _("No prioritized labels yet!") - %div + %p.gl-text-secondary = _("Star labels to start sorting by priority.") diff --git a/config/feature_flags/development/kubernetes_namespace_for_environment.yml b/config/feature_flags/development/kubernetes_namespace_for_environment.yml new file mode 100644 index 00000000000..f5da9ea7c9a --- /dev/null +++ b/config/feature_flags/development/kubernetes_namespace_for_environment.yml @@ -0,0 +1,8 @@ +--- +name: kubernetes_namespace_for_environment +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/125191 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/417129 +milestone: '16.2' +type: development +group: group::environments +default_enabled: false diff --git a/config/routes/directs/subscription_portal.rb b/config/routes/directs/subscription_portal.rb index 188725d16c1..cc6a3d6b5c0 100644 --- a/config/routes/directs/subscription_portal.rb +++ b/config/routes/directs/subscription_portal.rb @@ -1,14 +1,14 @@ # frozen_string_literal: true direct :subscription_portal_staging do - ENV.fetch('STAGING_CUSTOMER_PORTAL_URL', 'https://customers.staging.gitlab.com') + ENV.fetch('STAGING_CUSTOMER_PORTAL_URL', Gitlab::SubscriptionPortal.default_staging_customer_portal_url) end direct :subscription_portal do default_subscriptions_url = if ::Gitlab.dev_or_test_env? subscription_portal_staging_url else - 'https://customers.gitlab.com' + Gitlab::SubscriptionPortal.default_production_customer_portal_url end ENV.fetch('CUSTOMER_PORTAL_URL', default_subscriptions_url) diff --git a/data/deprecations/16-2-registry-size-estimated-graphql-field.yml b/data/deprecations/16-2-registry-size-estimated-graphql-field.yml new file mode 100644 index 00000000000..a0251040203 --- /dev/null +++ b/data/deprecations/16-2-registry-size-estimated-graphql-field.yml @@ -0,0 +1,11 @@ +- title: "GraphQL field `registrySizeEstimated` has been deprecated" # (required) Actionable title. e.g., The `confidential` field for a `Note` is deprecated. Use `internal` instead. + announcement_milestone: "16.2" # (required) The milestone when this feature was first announced as deprecated. + removal_milestone: "17.0" # (required) The milestone when this feature is planned to be removed + breaking_change: true # (required) If this deprecation is a breaking change, set this value to true + reporter: kpalchyk # (required) GitLab username of the person reporting the deprecation + stage: Package # (required) String value of the stage that the feature was created in. e.g., Growth + issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/416509 # (required) Link to the deprecation issue in GitLab + body: | # (required) Do not modify this line, instead modify the lines below. + For clarity, the GraphQL field `registrySizeEstimated` was renamed to `containerRegistrySizeIsEstimated`, to match its counterpart. + `registrySizeEstimated` was deprecated in GitLab 16.2 and will be removed in GitLab 17.0. + Use `containerRegistrySizeIsEstimated` introduced in GitLab 16.2 instead. diff --git a/data/deprecations/16_2-custom_sign_in_fields.yml b/data/deprecations/16_2-custom_sign_in_fields.yml new file mode 100644 index 00000000000..80184a3c096 --- /dev/null +++ b/data/deprecations/16_2-custom_sign_in_fields.yml @@ -0,0 +1,11 @@ +- title: 'Deprecated parameters related to custom text in the sign-in page' # (required) The name of the feature to be deprecated + announcement_milestone: '16.2' # (required) The milestone when this feature was first announced as deprecated. + announcement_date: '2023-07-22' # (required) The date of the milestone release when this feature was first announced as deprecated. This should almost always be the 22nd of a month (YYYY-MM-22), unless you did an out of band blog post. + removal_milestone: '17.0' # (required) The milestone when this feature is planned to be removed + removal_date: '2024-04-22' # (required) The date of the milestone release when this feature is planned to be removed. This should almost always be the 22nd of a month (YYYY-MM-22), unless you did an out of band blog post. + breaking_change: true # (required) If this deprecation is a breaking change, set this value to true + reporter: eduardosanz # (required) GitLab username of the person reporting the deprecation + stage: Manage # (required) String value of the stage that the feature was created in. e.g., Growth + issue_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/124461 # (required) Link to the deprecation issue in GitLab + body: | # (required) Do not modify this line, instead modify the lines below. + The parameters, `sign_in_text` and `help_text`, are deprecated in the [Settings API](https://docs.gitlab.com/ee/api/settings.html). To add a custom text to the sign-in and sign-up pages, use the `description` field in the [Appearance API](https://docs.gitlab.com/ee/api/appearance.html). diff --git a/doc/administration/auditor_users.md b/doc/administration/auditor_users.md index 3b6992c92e0..8482edaf7c6 100644 --- a/doc/administration/auditor_users.md +++ b/doc/administration/auditor_users.md @@ -50,9 +50,9 @@ users with auditor access have the same [permissions](../user/permissions.md) as If you are signed in with auditor access, you: -- Have full access to projects you own. -- Have read-only access to projects you aren't a member of. -- Have [permissions](../user/permissions.md) based on your role to projects you are a member of. For example, if you have the Developer role, +- Have full access to the projects and groups you own. +- Have read-only access to the projects and groups you are not a member of. +- Have [permissions](../user/permissions.md) based on your role to projects and groups you are a member of. For example, if you have the Developer role, you can push commits or comment on issues. - Can access the same resources using the GitLab UI or API. - Can't view the Admin Area, or perform any administration actions. diff --git a/doc/administration/monitoring/ip_allowlist.md b/doc/administration/monitoring/ip_allowlist.md index 72640cd6218..89caea691bb 100644 --- a/doc/administration/monitoring/ip_allowlist.md +++ b/doc/administration/monitoring/ip_allowlist.md @@ -12,7 +12,9 @@ that provide health check information when probed. To control access to those endpoints via IP whitelisting, you can add single hosts or use IP ranges: -**Omnibus** +::Tabs + +:::TabTitle Linux package (Omnibus) 1. Open `/etc/gitlab/gitlab.rb` and add or uncomment the following: @@ -22,9 +24,7 @@ hosts or use IP ranges: 1. Save the file and [reconfigure](../restart_gitlab.md#reconfigure-a-linux-package-installation) GitLab for the changes to take effect. ---- - -**Helm chart** +:::TabTitle Helm chart (Kubernetes) You can set the required IPs under the `gitlab.webservice.monitoring.ipWhitelist` key. For example: @@ -37,9 +37,7 @@ gitlab: - 0.0.0.0/0 # Default ``` ---- - -**Source** +:::TabTitle Self-compiled (source) 1. Edit `config/gitlab.yml`: @@ -52,3 +50,5 @@ gitlab: ``` 1. Save the file and [restart](../restart_gitlab.md#installations-from-source) GitLab for the changes to take effect. + +::EndTabs diff --git a/doc/administration/monitoring/performance/gitlab_configuration.md b/doc/administration/monitoring/performance/gitlab_configuration.md index 0d2037f3a92..a1def4764f6 100644 --- a/doc/administration/monitoring/performance/gitlab_configuration.md +++ b/doc/administration/monitoring/performance/gitlab_configuration.md @@ -16,8 +16,8 @@ settings: 1. Add the necessary configuration changes. 1. Restart all GitLab for the changes to take effect: - - For Omnibus GitLab installations: `sudo gitlab-ctl restart` - - For installations from source: `sudo service gitlab restart` + - For Linux package installations: `sudo gitlab-ctl restart` + - For self-compiled installations: `sudo service gitlab restart` NOTE: Removed [in GitLab 13.0](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/30786). Use the diff --git a/doc/administration/monitoring/performance/grafana_configuration.md b/doc/administration/monitoring/performance/grafana_configuration.md index b448ac461c8..8b3720ca8a9 100644 --- a/doc/administration/monitoring/performance/grafana_configuration.md +++ b/doc/administration/monitoring/performance/grafana_configuration.md @@ -18,10 +18,10 @@ and Grafana allows you to query the data to display graphs. ## Deprecation of bundled Grafana -Bundled Grafana was an optional Omnibus GitLab service that provided a user interface to GitLab metrics. +Bundled Grafana was an optional service for Linux package installations that provided a user interface to GitLab metrics. -The version of Grafana that is bundled with Omnibus GitLab is no longer supported. If you're using the bundled Grafana, you -should switch to a newer version from [Grafana Labs](https://grafana.com/grafana/). +The version of Grafana that is bundled with Linux package installations is no longer supported. If you're using the +bundled Grafana, you should switch to a newer version from [Grafana Labs](https://grafana.com/grafana/). ### Switch to new Grafana instance @@ -34,7 +34,8 @@ To switch away from bundled Grafana to a newer version of Grafana from Grafana L ### Temporary workaround -In GitLab versions 16.0 to 16.2, you can still force Omnibus GitLab to enable and configure Grafana by setting the following: +In GitLab versions 16.0 to 16.2, you can still force Linux package installations to enable and configure Grafana by +setting the following: - `grafana['enable'] = true`. - `grafana['enable_deprecated_service'] = true`. @@ -92,9 +93,9 @@ GitLab sidebar: and expand **Metrics - Grafana**. 1. Select the **Add a link to Grafana** checkbox. 1. Configure the **Grafana URL**: - - *If Grafana is enabled through Omnibus GitLab and on the same server,* + - If Grafana is enabled through a Linux package installation and on the same server, leave **Grafana URL** unchanged. It should be `/-/grafana`. - - *Otherwise,* enter the full URL of the Grafana instance. + - Otherwise, enter the full URL of the Grafana instance. 1. Select **Save changes**. GitLab displays your link in the **Main menu > Admin > Monitoring > Metrics Dashboard**. diff --git a/doc/administration/monitoring/prometheus/gitlab_exporter.md b/doc/administration/monitoring/prometheus/gitlab_exporter.md index 0bd13fe5a87..22b73378cab 100644 --- a/doc/administration/monitoring/prometheus/gitlab_exporter.md +++ b/doc/administration/monitoring/prometheus/gitlab_exporter.md @@ -9,12 +9,12 @@ info: To determine the technical writer assigned to the Stage/Group associated w > Renamed from `GitLab monitor exporter` to `GitLab exporter` in [GitLab 12.3](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/16511). The [GitLab exporter](https://gitlab.com/gitlab-org/gitlab-exporter) enables you to -measure various GitLab metrics pulled from Redis and the database in Omnibus GitLab +measure various GitLab metrics pulled from Redis and the database in Linux package instances. For installations from source you must install and configure it yourself. -To enable the GitLab exporter in an Omnibus GitLab instance: +To enable the GitLab exporter in a Linux package instance: 1. [Enable Prometheus](index.md#configuring-prometheus). 1. Edit `/etc/gitlab/gitlab.rb`. @@ -32,7 +32,7 @@ the GitLab exporter exposed at `localhost:9168`. ## Use a different Rack server -> - Introduced in [Omnibus GitLab 13.8](https://gitlab.com/gitlab-org/omnibus-gitlab/-/merge_requests/4896). +> - Introduced in [GitLab 13.8](https://gitlab.com/gitlab-org/omnibus-gitlab/-/merge_requests/4896). > - WEBrick is now the default Rack server instead of Puma. By default, the GitLab exporter runs on [WEBrick](https://github.com/ruby/webrick), a single-threaded Ruby web server. diff --git a/doc/administration/monitoring/prometheus/gitlab_metrics.md b/doc/administration/monitoring/prometheus/gitlab_metrics.md index 38b3d935993..061b1921e4f 100644 --- a/doc/administration/monitoring/prometheus/gitlab_metrics.md +++ b/doc/administration/monitoring/prometheus/gitlab_metrics.md @@ -23,8 +23,8 @@ GitLab monitors its own internal service metrics, and makes them available at th `/-/metrics` endpoint. Unlike other [Prometheus](https://prometheus.io) exporters, to access the metrics, the client IP address must be [explicitly allowed](../ip_allowlist.md). -These metrics are enabled and collected for [Omnibus GitLab](https://docs.gitlab.com/omnibus/) -and Chart installations. For source installations, these metrics must be enabled +These metrics are enabled and collected for [Linux package](https://docs.gitlab.com/omnibus/) +and Helm chart installations. For self-compiled installations, these metrics must be enabled manually and collected by a Prometheus server. For enabling and viewing metrics from Sidekiq nodes, see [Sidekiq metrics](#sidekiq-metrics). @@ -498,5 +498,5 @@ metrics can't function correctly. This directory's location is configured using environment variable `prometheus_multiproc_dir`. For best performance, create this directory in `tmpfs`. -If GitLab is installed using [Omnibus GitLab](https://docs.gitlab.com/omnibus/) +If GitLab is installed using the [Linux package](https://docs.gitlab.com/omnibus/) and `tmpfs` is available, then GitLab configures the metrics directory for you. diff --git a/doc/administration/monitoring/prometheus/index.md b/doc/administration/monitoring/prometheus/index.md index f22b71f7868..a9b393aab33 100644 --- a/doc/administration/monitoring/prometheus/index.md +++ b/doc/administration/monitoring/prometheus/index.md @@ -9,12 +9,11 @@ info: To determine the technical writer assigned to the Stage/Group associated w [Prometheus](https://prometheus.io) is a powerful time-series monitoring service, providing a flexible platform for monitoring GitLab and other software products. -GitLab provides out-of-the-box monitoring with Prometheus, providing easy -access to high quality time-series monitoring of GitLab services. +GitLab provides out-of-the-box monitoring with Prometheus, providing access to high quality time-series monitoring of +GitLab services. -Prometheus and the various exporters listed in this page are bundled in the -Omnibus GitLab package. Check each exporter's documentation for the timeline -they got added. For installations from source you must install them +Prometheus and the various exporters listed in this page are bundled in Linux packages. Check each exporter's +documentation for the timeline they got added. For installations from source you must install them yourself. Over subsequent releases additional GitLab metrics are captured. Prometheus services are on by default. @@ -85,7 +84,7 @@ listens on: ### Adding custom scrape configurations -You can configure additional scrape targets for the Omnibus GitLab-bundled +You can configure additional scrape targets for the Linux package-bundled Prometheus by editing `prometheus['scrape_configs']` in `/etc/gitlab/gitlab.rb` using the [Prometheus scrape target configuration](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#%3Cscrape_config%3E) syntax. @@ -108,16 +107,16 @@ prometheus['scrape_configs'] = [ ] ``` -### Standalone Prometheus using Omnibus GitLab +### Standalone Prometheus using the Linux package -The Omnibus GitLab package can be used to configure a standalone Monitoring node running Prometheus and [Grafana](../performance/grafana_configuration.md). +The Linux package can be used to configure a standalone Monitoring node running Prometheus and [Grafana](../performance/grafana_configuration.md). -The steps below are the minimum necessary to configure a Monitoring node running Prometheus and Grafana with Omnibus GitLab: +The steps below are the minimum necessary to configure a Monitoring node running Prometheus and Grafana with the Linux +package: 1. SSH into the Monitoring node. -1. [Install](https://about.gitlab.com/install/) the Omnibus GitLab - package you want using **steps 1 and 2** from the GitLab downloads page, but - do not follow the remaining steps. +1. [Install](https://about.gitlab.com/install/) the Linux package you want using **steps 1 and 2** from the GitLab + downloads page, but do not follow the remaining steps. 1. Make sure to collect the IP addresses or DNS records of the Consul server nodes, for the next step. 1. Edit `/etc/gitlab/gitlab.rb` and add the contents: @@ -388,8 +387,7 @@ Read more about the [GitLab Metrics](gitlab_metrics.md). ## Bundled software metrics -Many of the GitLab dependencies bundled in Omnibus GitLab are preconfigured to -export Prometheus metrics. +Many of the GitLab dependencies bundled in the Linux package are preconfigured to export Prometheus metrics. ### Node exporter diff --git a/doc/administration/operations/fast_ssh_key_lookup.md b/doc/administration/operations/fast_ssh_key_lookup.md index d54d286c19d..8382f3aa8b5 100644 --- a/doc/administration/operations/fast_ssh_key_lookup.md +++ b/doc/administration/operations/fast_ssh_key_lookup.md @@ -27,8 +27,8 @@ lookup of authorized SSH keys. ## Fast lookup is required for Geo **(PREMIUM)** -Unlike [Cloud Native GitLab](https://docs.gitlab.com/charts/), Omnibus GitLab by default -manages an `authorized_keys` file that is located in the +Unlike [Cloud Native GitLab](https://docs.gitlab.com/charts/), by default Linux package installations +manage an `authorized_keys` file that is located in the `git` user's home directory. For most installations, this file is located under `/var/opt/gitlab/.ssh/authorized_keys`, but you can use the following command to locate the `authorized_keys` on your system: @@ -74,7 +74,7 @@ able to accept a fingerprint. Check the version of OpenSSH on your server with ` Add the following to your `sshd_config` file. This file is usually located at `/etc/ssh/sshd_config`, but it is at `/assets/sshd_config` if you're using -Omnibus Docker: +Docker from a Linux package installation: ```plaintext Match User git # Apply the AuthorizedKeysCommands to the git user only @@ -146,7 +146,8 @@ This overview is brief. Refer to the above instructions for more context. 1. On the left sidebar, select **Settings > Network**. 1. Expand **Performance optimization**. 1. Select the **Use authorized_keys file to authenticate SSH keys** checkbox. -1. Remove the `AuthorizedKeysCommand` lines from `/etc/ssh/sshd_config` or from `/assets/sshd_config` if you are using Omnibus Docker. +1. Remove the `AuthorizedKeysCommand` lines from `/etc/ssh/sshd_config` or from `/assets/sshd_config` if you are using Docker + from a Linux package installation. 1. Reload `sshd`: `sudo service sshd reload`. ## SELinux support and limitations diff --git a/doc/administration/operations/gitlab_sshd.md b/doc/administration/operations/gitlab_sshd.md index 5c4af32fc3d..2707c8f08a0 100644 --- a/doc/administration/operations/gitlab_sshd.md +++ b/doc/administration/operations/gitlab_sshd.md @@ -7,7 +7,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w # `gitlab-sshd` **(FREE SELF)** > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/299109) in GitLab 14.5 as an Experiment for self-managed customers. -> - Ready for production use with [Cloud Native GitLab in GitLab 15.1](https://gitlab.com/gitlab-org/charts/gitlab/-/issues/2540) and [Omnibus GitLab in GitLab 15.9](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/5937). +> - Ready for production use with [Cloud Native GitLab in GitLab 15.1](https://gitlab.com/gitlab-org/charts/gitlab/-/issues/2540) and [Linux packages in GitLab 15.9](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/5937). `gitlab-sshd` is [a standalone SSH server](https://gitlab.com/gitlab-org/gitlab-shell/-/tree/main/internal/sshd) written in Go. It is provided as a part of the `gitlab-shell` package. It has a lower memory @@ -47,7 +47,7 @@ The following instructions enable `gitlab-sshd` on a different port than OpenSSH gitlab_sshd['listen_address'] = '[::]:2222' # Adjust the port accordingly ``` -1. Optional. By default, Omnibus GitLab generates SSH host keys for `gitlab-sshd` if +1. Optional. By default, Linux package installations generate SSH host keys for `gitlab-sshd` if they do not exist in `/var/opt/gitlab/gitlab-sshd`. If you wish to disable this automatic generation, add this line: ```ruby diff --git a/doc/administration/operations/puma.md b/doc/administration/operations/puma.md index c732365c57b..f471dcd44b0 100644 --- a/doc/administration/operations/puma.md +++ b/doc/administration/operations/puma.md @@ -173,7 +173,7 @@ optimal configuration: ## Configuring Puma to listen over SSL -Puma, when deployed with Omnibus GitLab, listens over a Unix socket by +Puma, when deployed with a Linux package installation, listens over a Unix socket by default. To configure Puma to listen over an HTTPS port instead, follow the steps below: @@ -386,7 +386,7 @@ downtime. Otherwise, skip to the next section. GDB reports an error if the Puma process terminates before you can run these commands. To buy more time, you can always raise the -Puma worker timeout. For omnibus users, you can edit `/etc/gitlab/gitlab.rb` and +Puma worker timeout. For Linux package installation users, you can edit `/etc/gitlab/gitlab.rb` and increase it from 60 seconds to 600: ```ruby diff --git a/doc/administration/operations/rails_console.md b/doc/administration/operations/rails_console.md index 8edd20edf57..311bae0a040 100644 --- a/doc/administration/operations/rails_console.md +++ b/doc/administration/operations/rails_console.md @@ -25,28 +25,34 @@ Rails experience is useful but not required. ## Starting a Rails console session -**For Omnibus installations** +The process for starting a Rails console session depends on the type of GitLab installation. + +::Tabs + +:::TabTitle Linux package (Omnibus) ```shell sudo gitlab-rails console ``` -**For Docker installations** +:::TabTitle Docker ```shell docker exec -it <container-id> gitlab-rails console ``` -**For installations from source** +:::TabTitle Self-compiled (source) ```shell sudo -u git -H bundle exec rails console -e production ``` -**For Kubernetes deployments** +:::TabTitle Helm chart (Kubernetes) The console is in the toolbox pod. Refer to our [Kubernetes cheat sheet](https://docs.gitlab.com/charts/troubleshooting/kubernetes_cheat_sheet.html#gitlab-specific-kubernetes-information) for details. +::EndTabs + To exit the console, type: `quit`. ## Enable Active Record logging @@ -130,31 +136,31 @@ environment, you can do so using the [Rails Runner](https://guides.rubyonrails.o When executing a script file, the script must be accessible by the `git` user. When the command or script completes, the Rails Runner process finishes. -It is useful for running within other scripts or cron jobs for example. +It is useful for running in other scripts or cron jobs for example. -**For Omnibus installations** +- For Linux package installations: -```shell -sudo gitlab-rails runner "RAILS_COMMAND" + ```shell + sudo gitlab-rails runner "RAILS_COMMAND" -# Example with a two-line Ruby script -sudo gitlab-rails runner "user = User.first; puts user.username" + # Example with a two-line Ruby script + sudo gitlab-rails runner "user = User.first; puts user.username" -# Example with a ruby script file (make sure to use the full path) -sudo gitlab-rails runner /path/to/script.rb -``` + # Example with a ruby script file (make sure to use the full path) + sudo gitlab-rails runner /path/to/script.rb + ``` -**For installations from source** +- For self-compiled installations: -```shell -sudo -u git -H bundle exec rails runner -e production "RAILS_COMMAND" + ```shell + sudo -u git -H bundle exec rails runner -e production "RAILS_COMMAND" -# Example with a two-line Ruby script -sudo -u git -H bundle exec rails runner -e production "user = User.first; puts user.username" + # Example with a two-line Ruby script + sudo -u git -H bundle exec rails runner -e production "user = User.first; puts user.username" -# Example with a ruby script file (make sure to use the full path) -sudo -u git -H bundle exec rails runner -e production /path/to/script.rb -``` + # Example with a ruby script file (make sure to use the full path) + sudo -u git -H bundle exec rails runner -e production /path/to/script.rb + ``` Rails Runner does not produce the same output as the console. diff --git a/doc/api/graphql/custom_emoji.md b/doc/api/graphql/custom_emoji.md index 69c16f57bce..e4e497db795 100644 --- a/doc/api/graphql/custom_emoji.md +++ b/doc/api/graphql/custom_emoji.md @@ -14,7 +14,7 @@ On self-managed GitLab, by default this feature is not available. To make it ava On GitLab.com, this feature is available. This feature is ready for production use. -To use custom emoji in comments and descriptions, you can add them to a top-level group using the GraphQL API. +To use [custom emoji](../../user/award_emojis.md) in comments and descriptions, you can add them to a top-level group using the GraphQL API. Parameters: diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 57b400b670e..761aafe1e65 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -22263,11 +22263,12 @@ Counts of requirements by their state. | ---- | ---- | ----------- | | <a id="rootstoragestatisticsbuildartifactssize"></a>`buildArtifactsSize` | [`Float!`](#float) | CI artifacts size in bytes. | | <a id="rootstoragestatisticscontainerregistrysize"></a>`containerRegistrySize` | [`Float!`](#float) | Container Registry size in bytes. | +| <a id="rootstoragestatisticscontainerregistrysizeisestimated"></a>`containerRegistrySizeIsEstimated` | [`Boolean!`](#boolean) | Indicates whether the deduplicated Container Registry size for the namespace is an estimated value or not. | | <a id="rootstoragestatisticsdependencyproxysize"></a>`dependencyProxySize` | [`Float!`](#float) | Dependency Proxy sizes in bytes. | | <a id="rootstoragestatisticslfsobjectssize"></a>`lfsObjectsSize` | [`Float!`](#float) | LFS objects size in bytes. | | <a id="rootstoragestatisticspackagessize"></a>`packagesSize` | [`Float!`](#float) | Packages size in bytes. | | <a id="rootstoragestatisticspipelineartifactssize"></a>`pipelineArtifactsSize` | [`Float!`](#float) | CI pipeline artifacts size in bytes. | -| <a id="rootstoragestatisticsregistrysizeestimated"></a>`registrySizeEstimated` | [`Boolean!`](#boolean) | Indicates whether the deduplicated Container Registry size for the namespace is an estimated value or not. | +| <a id="rootstoragestatisticsregistrysizeestimated"></a>`registrySizeEstimated` **{warning-solid}** | [`Boolean!`](#boolean) | **Deprecated** in 16.2. Use `container_registry_size_is_estimated`. | | <a id="rootstoragestatisticsrepositorysize"></a>`repositorySize` | [`Float!`](#float) | Git repository size in bytes. | | <a id="rootstoragestatisticssnippetssize"></a>`snippetsSize` | [`Float!`](#float) | Snippets size in bytes. | | <a id="rootstoragestatisticsstoragesize"></a>`storageSize` | [`Float!`](#float) | Total storage in bytes. | diff --git a/doc/api/settings.md b/doc/api/settings.md index 6d5fda0237a..598e4025b63 100644 --- a/doc/api/settings.md +++ b/doc/api/settings.md @@ -57,7 +57,6 @@ Example response: "default_project_visibility" : "private", "default_group_visibility" : "private", "gravatar_enabled" : true, - "sign_in_text" : null, "container_expiration_policies_enable_historic_entries": true, "container_registry_cleanup_tags_service_max_list_size": 200, "container_registry_delete_tags_service_timeout": 250, @@ -172,7 +171,6 @@ Example response: "signup_enabled": false, "password_authentication_enabled_for_web": true, "gravatar_enabled": true, - "sign_in_text": "", "created_at": "2015-06-12T15:51:55.432Z", "updated_at": "2015-06-30T13:22:42.210Z", "home_page_url": "", @@ -283,7 +281,8 @@ Example responses: **(PREMIUM SELF)** ## List of settings that can be accessed via API calls -> Fields `housekeeping_full_repack_period`, `housekeeping_gc_period`, and `housekeeping_incremental_repack_period` [deprecated](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/106963) in GitLab 15.8. Use `housekeeping_optimize_repository_period` instead. +> - Fields `housekeeping_full_repack_period`, `housekeeping_gc_period`, and `housekeeping_incremental_repack_period` [deprecated](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/106963) in GitLab 15.8. Use `housekeeping_optimize_repository_period` instead. +> - Parameters `sign_in_text` and `help_text` were [deprecated](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/124461) in GitLab 16.2. Use `description` parameter in the [Appearance API](../api/appearance.md) instead. In general, all settings are optional. Certain settings though, if enabled, require other settings to be set to function properly. These requirements are @@ -416,7 +415,7 @@ listed in the descriptions of the relevant settings. | `help_page_hide_commercial_content` | boolean | no | Hide marketing-related entries from help. | | `help_page_support_url` | string | no | Alternate support URL for help page and help dropdown list. | | `help_page_text` | string | no | Custom text displayed on the help page. | -| `help_text` **(PREMIUM)** | string | no | GitLab server administrator information. | +| `help_text` **(PREMIUM)** | string | no | Deprecated: Use `description` parameter in the [Appearance API](../api/appearance.md). Custom text in sign-in page. | | `hide_third_party_offers` | boolean | no | Do not display offers from third parties in GitLab. | | `home_page_url` | string | no | Redirect to this URL when not logged in. | | `housekeeping_bitmaps_enabled` | boolean | no | Deprecated. Git pack file bitmap creation is always enabled and cannot be changed via API and UI. Always returns `true`. | @@ -506,7 +505,7 @@ listed in the descriptions of the relevant settings. | `sidekiq_job_limiter_mode` | string | no | `track` or `compress`. Sets the behavior for [Sidekiq job size limits](../user/admin_area/settings/sidekiq_job_limits.md). Default: 'compress'. | | `sidekiq_job_limiter_compression_threshold_bytes` | integer | no | The threshold in bytes at which Sidekiq jobs are compressed before being stored in Redis. Default: 100,000 bytes (100 KB). | | `sidekiq_job_limiter_limit_bytes` | integer | no | The threshold in bytes at which Sidekiq jobs are rejected. Default: 0 bytes (doesn't reject any job). | -| `sign_in_text` | string | no | Text on the login page. | +| `sign_in_text` | string | no | Deprecated: Use `description` parameter in the [Appearance API](../api/appearance.md). Custom text in sign-in page. | | `signin_enabled` | string | no | (Deprecated: Use `password_authentication_enabled_for_web` instead) Flag indicating if password authentication is enabled for the web interface. | | `signup_enabled` | boolean | no | Enable registration. Default is `true`. | | `silent_mode_enabled` | boolean | no | Enable [Silent mode](../administration/silent_mode/index.md). Default is `false`. | diff --git a/doc/ci/test_cases/index.md b/doc/ci/test_cases/index.md index 9da0d06d1cd..9667daf7501 100644 --- a/doc/ci/test_cases/index.md +++ b/doc/ci/test_cases/index.md @@ -8,9 +8,6 @@ type: reference # Test cases **(ULTIMATE)** -> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/233479) in GitLab 13.6. -> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/241983) in GitLab 13.7. - Test cases in GitLab can help your teams create testing scenarios in their existing development platform. Now your Implementation and Testing teams can collaborate better, as they no longer have to @@ -30,7 +27,8 @@ Prerequisite: To create a test case in a GitLab project: -1. Go to **Build > Test cases**. +1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project. +1. Select **Build > Test cases**. 1. Select **New test case**. You are taken to the new test case form. Here you can enter the new case's title, [description](../../user/markdown.md), attach a file, and assign [labels](../../user/project/labels.md). 1. Select **Submit test case**. You are taken to view the new test case. @@ -40,16 +38,15 @@ To create a test case in a GitLab project: You can view all test cases in the project in the test cases list. Filter the issue list with a search query, including labels or the test case's title. -Prerequisite: - -Whether you can view an test case depends on the [project visibility level](../../user/public_access.md): +Prerequisites: -- Public project: You don't have to be a member of the project. -- Private project: You must have at least the Guest role for the project. +- In a public project: You don't have to be a member of the project. +- In a private project: You must have at least the Guest role for the project. To view a test case: -1. In a project, go to **Build > Test cases**. +1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project. +1. Select **Build > Test cases**. 1. Select the title of the test case you want to view. You are taken to the test case page. ![An example test case page](img/test_case_show_v13_10.png) @@ -58,11 +55,11 @@ To view a test case: You can edit a test case's title and description. -Prerequisite: +Prerequisites: - You must have at least the Reporter role. - Users demoted to the Guest role can continue to edit the test cases they created -when they were in the higher role. + when they were in the higher role. To edit a test case: @@ -83,13 +80,19 @@ To archive a test case, on the test case's page, select **Archive test case**. To view archived test cases: -1. Go to **Build > Test cases**. +1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project. +1. Select **Build > Test cases**. 1. Select **Archived**. ## Reopen an archived test case If you decide to start using an archived test case again, you can reopen it. -You must have at least the Reporter role. +Prerequisites: -To reopen an archived test case, on the test case's page, select **Reopen test case**. +- You must have at least the Reporter role. + +To reopen an archived test case: + +1. [View a test case](#view-a-test-case). +1. Select **Reopen test case**. diff --git a/doc/development/ai_features.md b/doc/development/ai_features.md index 5aa2fc1bd56..7c4035fc6e9 100644 --- a/doc/development/ai_features.md +++ b/doc/development/ai_features.md @@ -86,7 +86,7 @@ For features that use the embedding database, additional setup is needed. 1. Run `gdk reconfigure` 1. Run database migrations to create the embedding database -### Set up GitLab Chat +### Set up GitLab Duo Chat 1. [Enable Anthropic API features](#configure-anthropic-access). 1. [Enable OpenAI support](#configure-openai-access). @@ -100,7 +100,7 @@ For features that use the embedding database, additional setup is needed. ``` 1. Ensure that your current branch is up-to-date with `master`. -1. To access the GitLab Chat interface, in the lower-left corner of any page, select **Help** and **Ask GitLab Chat**. +1. To access the GitLab Duo Chat interface, in the lower-left corner of any page, select **Help** and **Ask GitLab Duo Chat**. #### Tips for local development @@ -172,9 +172,9 @@ Feature.enable(:anthropic_experimentation) Gitlab::CurrentSettings.update!(anthropic_api_key: <insert API key>) ``` -### Testing GitLab Chat with predefined questions +### Testing GitLab Duo Chat with predefined questions -Because success of answers to user questions in GitLab Chat heavily depends on toolchain and prompts of each tool, it's common that even a minor change in a prompt or a tool impacts processing of some questions. To make sure that a change in the toolchain doesn't break existing functionality, you can use following commands to validate answers to some predefined questions: +Because success of answers to user questions in GitLab Duo Chat heavily depends on toolchain and prompts of each tool, it's common that even a minor change in a prompt or a tool impacts processing of some questions. To make sure that a change in the toolchain doesn't break existing functionality, you can use following commands to validate answers to some predefined questions: 1. Rake task which iterates through questions defined in CSV file and checks tools used for evaluating each question. diff --git a/doc/development/api_graphql_styleguide.md b/doc/development/api_graphql_styleguide.md index d6492b7814e..57a8b7a3d29 100644 --- a/doc/development/api_graphql_styleguide.md +++ b/doc/development/api_graphql_styleguide.md @@ -1668,7 +1668,7 @@ should look like this: ### Mounting the mutation To make the mutation available it must be defined on the mutation -type that is stored in `graphql/types/mutation_types`. The +type that is stored in `graphql/types/mutation_type`. The `mount_mutation` helper method defines a field based on the GraphQL-name of the mutation: diff --git a/doc/development/pipelines/index.md b/doc/development/pipelines/index.md index 9e6abaa8eb3..5a9f706219c 100644 --- a/doc/development/pipelines/index.md +++ b/doc/development/pipelines/index.md @@ -609,18 +609,18 @@ Exceptions to this general guideline should be motivated and documented. ### Ruby versions testing We're running Ruby 3.0 on GitLab.com, as well as for merge requests and the default branch. -However, there are older versions for which we need to support Ruby 2.7, so we also run our -test suite against Ruby 2.7 on a dedicated 2-hourly scheduled pipelines. +To prepare for the next release, Ruby 3.1, we also run our test suite against Ruby 3.1 on +a dedicated 2-hourly scheduled pipelines. -For merge requests, you can add the `pipeline:run-in-ruby2` label to switch -the Ruby version used for running the whole test suite to 2.7. When you do +For merge requests, you can add the `pipeline:run-in-ruby3_1` label to switch +the Ruby version used for running the whole test suite to 3.1. When you do this, the test suite will no longer run in Ruby 3.0 (default), and an additional job `verify-ruby-3.0` will also run and always fail to remind us to remove the label and run in Ruby 3.0 before merging the merge request. This should let us: -- Test changes for Ruby 2.7 +- Test changes for Ruby 3.1 - Make sure it will not break anything when it's merged into the default branch ### PostgreSQL versions testing @@ -634,26 +634,26 @@ We also run our test suite against PostgreSQL 12 and PostgreSQL 13 upon specific #### Current versions testing -| Where? | PostgreSQL version | Ruby version | -|------------------------------------------------------------------------------------------------|-------------------------------------------------|-----------------------| -| Merge requests | 14 (default version), 13 for DB library changes | 3.0 (default version) | -| `master` branch commits | 14 (default version), 13 for DB library changes | 3.0 (default version) | -| `maintenance` scheduled pipelines for the `master` branch (every even-numbered hour) | 14 (default version), 13 for DB library changes | 3.0 (default version) | -| `maintenance` scheduled pipelines for the `ruby2` branch (every odd-numbered hour), see below. | 14 (default version), 13 for DB library changes | 2.7 | -| `nightly` scheduled pipelines for the `master` branch | 14 (default version), 12, 13, 15 | 3.0 (default version) | +| Where? | PostgreSQL version | Ruby version | +|--------------------------------------------------------------------------------------------------|-------------------------------------------------|-----------------------| +| Merge requests | 14 (default version), 13 for DB library changes | 3.0 (default version) | +| `master` branch commits | 14 (default version), 13 for DB library changes | 3.0 (default version) | +| `maintenance` scheduled pipelines for the `master` branch (every even-numbered hour) | 14 (default version), 13 for DB library changes | 3.0 (default version) | +| `maintenance` scheduled pipelines for the `ruby3_1` branch (every odd-numbered hour), see below. | 14 (default version), 13 for DB library changes | 3.1 | +| `nightly` scheduled pipelines for the `master` branch | 14 (default version), 12, 13, 15 | 3.0 (default version) | -There are 2 pipeline schedules used for testing Ruby 2.7. One is triggering a -pipeline in `ruby2-sync` branch, which updates the `ruby2` branch with latest +There are 2 pipeline schedules used for testing Ruby 3.1. One is triggering a +pipeline in `ruby3_1-sync` branch, which updates the `ruby3_1` branch with latest `master`, and no pipelines will be triggered by this push. The other schedule -is triggering a pipeline in `ruby2` 5 minutes after it, which is considered +is triggering a pipeline in `ruby3_1` 5 minutes after it, which is considered the maintenance schedule to run test suites and update cache. -The `ruby2` branch must not have any changes. The branch is only there to set -`RUBY_VERSION` to `2.7` in the maintenance pipeline schedule. +The `ruby3_1` branch must not have any changes. The branch is only there to set +`RUBY_VERSION` to `3.1` in the maintenance pipeline schedule. -The `gitlab` job in the `ruby2-sync` branch uses a `gitlab-org/gitlab` project +The `gitlab` job in the `ruby3_1-sync` branch uses a `gitlab-org/gitlab` project token with `write_repository` scope and `Maintainer` role with no expiration. -The token is stored in the `RUBY2_SYNC_TOKEN` variable in `gitlab-org/gitlab`. +The token is stored in the `RUBY3_1_SYNC_TOKEN` variable in `gitlab-org/gitlab`. ### Redis versions testing diff --git a/doc/update/deprecations.md b/doc/update/deprecations.md index 703d9465621..0e172a1d311 100644 --- a/doc/update/deprecations.md +++ b/doc/update/deprecations.md @@ -190,6 +190,20 @@ The runner's legacy escape sequence mechanism to handle variable expansion imple <div class="deprecation breaking-change" data-milestone="17.0"> +### Deprecated parameters related to custom text in the sign-in page + +<div class="deprecation-notes"> +- Announced in: GitLab <span class="milestone">16.2</span> +- This is a [breaking change](https://docs.gitlab.com/ee/update/terminology.html#breaking-change). +- To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/124461). +</div> + +The parameters, `sign_in_text` and `help_text`, are deprecated in the [Settings API](https://docs.gitlab.com/ee/api/settings.html). To add a custom text to the sign-in and sign-up pages, use the `description` field in the [Appearance API](https://docs.gitlab.com/ee/api/appearance.html). + +</div> + +<div class="deprecation breaking-change" data-milestone="17.0"> + ### DingTalk OmniAuth provider <div class="deprecation-notes"> @@ -308,6 +322,22 @@ Use `dependencyProxyTotalSizeBytes` instead, introduced in GitLab 16.1. <div class="deprecation breaking-change" data-milestone="17.0"> +### GraphQL field `registrySizeEstimated` has been deprecated + +<div class="deprecation-notes"> +- Announced in: GitLab <span class="milestone">16.2</span> +- This is a [breaking change](https://docs.gitlab.com/ee/update/terminology.html#breaking-change). +- To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/416509). +</div> + +For clarity, the GraphQL field `registrySizeEstimated` was renamed to `containerRegistrySizeIsEstimated`, to match its counterpart. +`registrySizeEstimated` was deprecated in GitLab 16.2 and will be removed in GitLab 17.0. +Use `containerRegistrySizeIsEstimated` introduced in GitLab 16.2 instead. + +</div> + +<div class="deprecation breaking-change" data-milestone="17.0"> + ### GraphQL type, `RunnerMembershipFilter` renamed to `CiRunnerMembershipFilter` <div class="deprecation-notes"> diff --git a/doc/user/ai_features.md b/doc/user/ai_features.md index 9f20aa7b9d4..be4bfc5468c 100644 --- a/doc/user/ai_features.md +++ b/doc/user/ai_features.md @@ -90,7 +90,7 @@ code in a merge request: 1. On the left sidebar, select **Code > Merge requests**, then select your merge request. 1. On the secondary menu, select **Changes**. 1. On the file you would like explained, select the three dots (**{ellipsis_v}**) and select **View File @ $SHA**. - + A separate browser tab opens and shows the full file with the latest changes. 1. On the new tab, select the lines that you want to have explained. @@ -134,16 +134,16 @@ Review the drawer on the right-hand side of your screen. We cannot guarantee that the large language model produces results that are correct. Use the explanation with caution. -### GitLab Chat **(ULTIMATE SAAS)** +### GitLab Duo Chat **(ULTIMATE SAAS)** > Introduced in GitLab 16.0 as an [Experiment](../policy/experiment-beta-support.md#experiment). This feature is an [Experiment](../policy/experiment-beta-support.md) on GitLab.com that is powered by OpenAI's GPT-3. It requires the [group-level third-party AI features setting](group/manage.md#enable-third-party-ai-features) to be enabled. -Getting help has never been easier. If you have a question about how the GitLab product works, you can ask product how-to questions and get AI generated support from GitLab Chat. +Getting help has never been easier. If you have a question about how the GitLab product works, you can ask product how-to questions and get AI generated support from GitLab Duo Chat. 1. In the lower-left corner, select the Help icon. -1. Select **Ask in GitLab Chat**. A drawer opens on the right side of your screen. +1. Select **Ask in GitLab Duo Chat**. A drawer opens on the right side of your screen. 1. Enter your question in the chat input box and press **Enter** or select **Send**. It may take a few seconds for the interactive AI chat to search the product documentation and produce an answer. To give feedback, select the **Give Feedback** link. diff --git a/doc/user/award_emojis.md b/doc/user/award_emojis.md index 6ddd2584ce1..0700bf93e2b 100644 --- a/doc/user/award_emojis.md +++ b/doc/user/award_emojis.md @@ -53,12 +53,24 @@ To remove an emoji reaction, select the emoji again. ## Custom emoji +> - [Introduced for GraphQL API](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/37911) in GitLab 13.6 [with a flag](../administration/feature_flags.md) named `custom_emoji`. Disabled by default. +> - Enabled on GitLab.com in GitLab 14.0. +> - [Added](https://gitlab.com/gitlab-org/gitlab/-/issues/333095) UI to add emoji in GitLab 16.2. + +![Custom emoji in emoji picker](img/custom_emoji_reactions_v16_2.png) + +Custom emoji show in the emoji picker everywhere you can react with emoji. +To add an emoji reaction to a comment or description: + +1. Select the smile (**{slight-smile}**). +1. Select the GitLab logo (**{tanuki}**). +1. Select an emoji from the emoji picker. + +To use them in a text box, type the filename between two colons. +For example, `:thank-you:`. + You can upload custom emoji to a GitLab instance with the GraphQL API. For more information, see [Use custom emoji with GraphQL](../api/graphql/custom_emoji.md). -Custom emoji don't show in the emoji picker. -To use them in a text box, type the filename without the extension and surrounded by colons. -For example, for a file named `thank-you.png`, type `:thank-you:`. - For a list of custom emoji available for GitLab.com, see [the `custom_emoji` project](https://gitlab.com/custom_emoji/custom_emoji/-/tree/main/img). diff --git a/doc/user/img/custom_emoji_reactions_v16_2.png b/doc/user/img/custom_emoji_reactions_v16_2.png Binary files differnew file mode 100644 index 00000000000..ce06edc66b5 --- /dev/null +++ b/doc/user/img/custom_emoji_reactions_v16_2.png diff --git a/doc/user/permissions.md b/doc/user/permissions.md index e50f72ef113..48ee1cc621c 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -223,7 +223,7 @@ The following table lists project permissions available for each role: <!-- markdownlint-disable MD029 --> -1. On self-managed GitLab instances, users with the Guest role are able to perform this action only on public and internal projects (not on private projects). [External users](../administration/external_users.md) must be given explicit access even if the project is internal. Users with the Guest role on GitLab.com are only able to perform this action on public projects because internal visibility is not available. +1. On self-managed GitLab instances, users with the Guest role are able to perform this action only on public and internal projects (not on private projects). [External users](../administration/external_users.md) must be given explicit access (at least the **Reporter** role) even if the project is internal. Users with the Guest role on GitLab.com are only able to perform this action on public projects because internal visibility is not available. 2. Guest users can only view the [confidential issues](project/issues/confidential_issues.md) they created themselves or are assigned to. 3. Not allowed for Guest, Reporter, Developer, Maintainer, or Owner. See [protected branches](project/protected_branches.md). 4. If the [branch is protected](project/protected_branches.md), this depends on the access given to Developers and Maintainers. diff --git a/lib/gitlab/subscription_portal.rb b/lib/gitlab/subscription_portal.rb index 1d9ecb624b2..bbcefabcb40 100644 --- a/lib/gitlab/subscription_portal.rb +++ b/lib/gitlab/subscription_portal.rb @@ -21,6 +21,14 @@ module Gitlab def self.renewal_service_email 'renewals-service@customers.gitlab.com' end + + def self.default_staging_customer_portal_url + 'https://customers.staging.gitlab.com' + end + + def self.default_production_customer_portal_url + 'https://customers.gitlab.com' + end end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index b0fbb0f8e9d..92bbc28876c 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -17531,6 +17531,9 @@ msgstr "" msgid "Environments|Job" msgstr "" +msgid "Environments|Kubernetes namespace (optional)" +msgstr "" + msgid "Environments|Learn more about stopping environments" msgstr "" @@ -17579,6 +17582,9 @@ msgstr "" msgid "Environments|Select agent" msgstr "" +msgid "Environments|Select namespace" +msgstr "" + msgid "Environments|Select which environments to clean up. Protected environments are excluded. Learn more about cleaning up environments." msgstr "" @@ -17633,6 +17639,9 @@ msgstr "" msgid "Environment|Cluster IP" msgstr "" +msgid "Environment|Cluster agent not found." +msgstr "" + msgid "Environment|CronJobs" msgstr "" @@ -17654,6 +17663,9 @@ msgstr "" msgid "Environment|Failed" msgstr "" +msgid "Environment|Forbidden to access the cluster agent from this environment." +msgstr "" + msgid "Environment|Healthy" msgstr "" @@ -17690,6 +17702,12 @@ msgstr "" msgid "Environment|Summary" msgstr "" +msgid "Environment|There was an error connecting to the cluster agent." +msgstr "" + +msgid "Environment|Unauthorized to access the cluster agent from this environment. Check your authentication and try again." +msgstr "" + msgid "Environment|Unhealthy" msgstr "" @@ -40426,9 +40444,6 @@ msgstr "" msgid "ScanExecutionPolicy|Select a scanner" msgstr "" -msgid "ScanExecutionPolicy|Select a variable" -msgstr "" - msgid "ScanExecutionPolicy|Select agent" msgstr "" @@ -40438,6 +40453,9 @@ msgstr "" msgid "ScanExecutionPolicy|Select namespaces" msgstr "" +msgid "ScanExecutionPolicy|Select or Create a Key" +msgstr "" + msgid "ScanExecutionPolicy|Select scanner profile" msgstr "" @@ -40456,6 +40474,9 @@ msgstr "" msgid "ScanExecutionPolicy|Triggers:" msgstr "" +msgid "ScanExecutionPolicy|Use a custom key" +msgstr "" + msgid "ScanExecutionPolicy|Value" msgstr "" @@ -41336,9 +41357,6 @@ msgstr "" msgid "SecurityOrchestration|No tags available" msgstr "" -msgid "SecurityOrchestration|Non-existing CI variables have been detected in the policy yaml. As a result, rule mode has been disabled. To enable rule mode, remove those non-existing CI variables from the policy yaml." -msgstr "" - msgid "SecurityOrchestration|Non-existing DAST profiles have been detected in the policy yaml. As a result, rule mode has been disabled. To enable rule mode, remove those non-existing profiles from the policy yaml." msgstr "" @@ -45307,7 +45325,7 @@ msgstr "" msgid "TanukiBot|For example, %{linkStart}what is a fork%{linkEnd}?" msgstr "" -msgid "TanukiBot|GitLab Chat" +msgid "TanukiBot|GitLab Duo Chat" msgstr "" msgid "TanukiBot|Give feedback" @@ -45318,7 +45336,7 @@ msgid_plural "TanukiBot|Sources" msgstr[0] "" msgstr[1] "" -msgid "TanukiBot|There was an error communicating with GitLab Chat. Please try again later." +msgid "TanukiBot|There was an error communicating with GitLab Duo Chat. Please try again later." msgstr "" msgid "TanukiBot|What is a fork?" diff --git a/qa/qa/resource/base.rb b/qa/qa/resource/base.rb index e12a8f97d95..459670add36 100644 --- a/qa/qa/resource/base.rb +++ b/qa/qa/resource/base.rb @@ -17,7 +17,8 @@ module QA class << self # Initialize new instance of class without fabrication # - # @param [Proc] prepare_block + # @yieldparam [self] instance of page object + # @return [self] def init(&prepare_block) new.tap(&prepare_block) end diff --git a/qa/qa/support/formatters/test_metrics_formatter.rb b/qa/qa/support/formatters/test_metrics_formatter.rb index 6e6cdc35af5..cdc0e83dc02 100644 --- a/qa/qa/support/formatters/test_metrics_formatter.rb +++ b/qa/qa/support/formatters/test_metrics_formatter.rb @@ -35,7 +35,7 @@ module QA # @param [Array<RSpec::Core::Example>] examples # @return [Array<Hash>] def execution_data(examples = nil) - @execution_metrics ||= examples.map { |example| test_stats(example) }.compact + @execution_metrics ||= examples.filter_map { |example| test_stats(example) } end alias_method :parse_execution_data, :execution_data @@ -171,11 +171,7 @@ module QA # # @return [Time] def time - @time ||= begin - return Time.now unless env('CI_PIPELINE_CREATED_AT') - - env('CI_PIPELINE_CREATED_AT').to_time - end + @time ||= env('CI_PIPELINE_CREATED_AT')&.to_time || Time.now end # Is a merge request execution diff --git a/qa/qa/support/influxdb_tools.rb b/qa/qa/support/influxdb_tools.rb index efdbe1cd129..e8f73540733 100644 --- a/qa/qa/support/influxdb_tools.rb +++ b/qa/qa/support/influxdb_tools.rb @@ -48,20 +48,21 @@ module QA # # @return [String, nil] def run_type - @run_type ||= begin - return env('QA_RUN_TYPE') if env('QA_RUN_TYPE') - return unless LIVE_ENVS.include?(ci_project_name) + @run_type ||= if env('QA_RUN_TYPE') + env('QA_RUN_TYPE') + elsif LIVE_ENVS.exclude?(ci_project_name) + nil + else + test_subset = if env('NO_ADMIN') == 'true' + 'sanity-no-admin' + elsif env('SMOKE_ONLY') == 'true' + 'sanity' + else + 'full' + end - test_subset = if env('NO_ADMIN') == 'true' - 'sanity-no-admin' - elsif env('SMOKE_ONLY') == 'true' - 'sanity' - else - 'full' - end - - "#{ci_project_name}-#{test_subset}" - end + "#{ci_project_name}-#{test_subset}" + end end # Merge request iid diff --git a/spec/frontend/environments/edit_environment_spec.js b/spec/frontend/environments/edit_environment_spec.js index 6f850f6e779..93fe9ed9400 100644 --- a/spec/frontend/environments/edit_environment_spec.js +++ b/spec/frontend/environments/edit_environment_spec.js @@ -7,6 +7,7 @@ import EditEnvironment from '~/environments/components/edit_environment.vue'; import { createAlert } from '~/alert'; import { visitUrl } from '~/lib/utils/url_utility'; import getEnvironment from '~/environments/graphql/queries/environment.query.graphql'; +import getEnvironmentWithNamespace from '~/environments/graphql/queries/environment_with_namespace.graphql'; import updateEnvironment from '~/environments/graphql/mutations/update_environment.mutation.graphql'; import { __ } from '~/locale'; import createMockApollo from '../__helpers__/mock_apollo_helper'; @@ -19,6 +20,7 @@ const environment = { name: 'foo', externalUrl: 'https://foo.example.com', clusterAgent: null, + kubernetesNamespace: null, }; const resolvedEnvironment = { project: { id: '1', environment } }; const environmentUpdateSuccess = { @@ -41,6 +43,10 @@ describe('~/environments/components/edit.vue', () => { let wrapper; const getEnvironmentQuery = jest.fn().mockResolvedValue({ data: resolvedEnvironment }); + const getEnvironmentWithNamespaceQuery = jest + .fn() + .mockResolvedValue({ data: resolvedEnvironment }); + const updateEnvironmentSuccess = jest .fn() .mockResolvedValue({ data: { environmentUpdate: environmentUpdateSuccess } }); @@ -53,16 +59,25 @@ describe('~/environments/components/edit.vue', () => { const mocks = [ [getEnvironment, getEnvironmentQuery], + [getEnvironmentWithNamespace, getEnvironmentWithNamespaceQuery], [updateEnvironment, mutationHandler], ]; return createMockApollo(mocks); }; - const createWrapperWithApollo = async ({ mutationHandler = updateEnvironmentSuccess } = {}) => { + const createWrapperWithApollo = async ({ + mutationHandler = updateEnvironmentSuccess, + kubernetesNamespaceForEnvironment = false, + } = {}) => { wrapper = mountExtended(EditEnvironment, { propsData: { environment: {} }, - provide, + provide: { + ...provide, + glFeatures: { + kubernetesNamespaceForEnvironment, + }, + }, apolloProvider: createMockApolloProvider(mutationHandler), }); @@ -154,4 +169,11 @@ describe('~/environments/components/edit.vue', () => { }); }); }); + + describe('when `kubernetesNamespaceForEnvironment` is enabled', () => { + it('calls the `getEnvironmentWithNamespace` query', () => { + createWrapperWithApollo({ kubernetesNamespaceForEnvironment: true }); + expect(getEnvironmentWithNamespaceQuery).toHaveBeenCalled(); + }); + }); }); diff --git a/spec/frontend/environments/environment_form_spec.js b/spec/frontend/environments/environment_form_spec.js index 1b603963599..98a8aec88cf 100644 --- a/spec/frontend/environments/environment_form_spec.js +++ b/spec/frontend/environments/environment_form_spec.js @@ -1,4 +1,4 @@ -import { GlLoadingIcon, GlCollapsibleListbox } from '@gitlab/ui'; +import { GlLoadingIcon, GlAlert } from '@gitlab/ui'; import Vue from 'vue'; import VueApollo from 'vue-apollo'; import waitForPromises from 'helpers/wait_for_promises'; @@ -6,6 +6,7 @@ import { mountExtended } from 'helpers/vue_test_utils_helper'; import EnvironmentForm from '~/environments/components/environment_form.vue'; import getUserAuthorizedAgents from '~/environments/graphql/queries/user_authorized_agents.query.graphql'; import createMockApollo from '../__helpers__/mock_apollo_helper'; +import { mockKasTunnelUrl } from './mock_data'; jest.mock('~/lib/utils/csrf'); @@ -15,7 +16,10 @@ const DEFAULT_PROPS = { cancelPath: '/cancel', }; -const PROVIDE = { protectedEnvironmentSettingsPath: '/projects/not_real/settings/ci_cd' }; +const PROVIDE = { + protectedEnvironmentSettingsPath: '/projects/not_real/settings/ci_cd', + kasTunnelUrl: mockKasTunnelUrl, +}; const userAccessAuthorizedAgents = [ { agent: { id: '1', name: 'agent-1' } }, { agent: { id: '2', name: 'agent-2' } }, @@ -24,6 +28,10 @@ const userAccessAuthorizedAgents = [ describe('~/environments/components/form.vue', () => { let wrapper; + const getNamespacesQueryResult = jest + .fn() + .mockReturnValue([{ metadata: { name: 'default' } }, { metadata: { name: 'agent' } }]); + const createWrapper = (propsData = {}, options = {}) => mountExtended(EnvironmentForm, { provide: PROVIDE, @@ -34,34 +42,59 @@ describe('~/environments/components/form.vue', () => { }, }); - const createWrapperWithApollo = ({ propsData = {} } = {}) => { + const createWrapperWithApollo = ({ + propsData = {}, + kasUserAccessProject = false, + kubernetesNamespaceForEnvironment = false, + queryResult = null, + } = {}) => { Vue.use(VueApollo); + const requestHandlers = [ + [ + getUserAuthorizedAgents, + jest.fn().mockResolvedValue({ + data: { + project: { + id: '1', + userAccessAuthorizedAgents: { nodes: userAccessAuthorizedAgents }, + }, + }, + }), + ], + ]; + + const mockResolvers = { + Query: { + k8sNamespaces: queryResult || getNamespacesQueryResult, + }, + }; + return mountExtended(EnvironmentForm, { provide: { ...PROVIDE, + glFeatures: { + kasUserAccessProject, + kubernetesNamespaceForEnvironment, + }, }, propsData: { ...DEFAULT_PROPS, ...propsData, }, - apolloProvider: createMockApollo([ - [ - getUserAuthorizedAgents, - jest.fn().mockResolvedValue({ - data: { - project: { - id: '1', - userAccessAuthorizedAgents: { nodes: userAccessAuthorizedAgents }, - }, - }, - }), - ], - ]), + apolloProvider: createMockApollo(requestHandlers, mockResolvers), }); }; - const findAgentSelector = () => wrapper.findComponent(GlCollapsibleListbox); + const findAgentSelector = () => wrapper.findByTestId('agent-selector'); + const findNamespaceSelector = () => wrapper.findByTestId('namespace-selector'); + const findAlert = () => wrapper.findComponent(GlAlert); + + const selectAgent = async () => { + findAgentSelector().vm.$emit('shown'); + await waitForPromises(); + await findAgentSelector().vm.$emit('select', '2'); + }; describe('default', () => { beforeEach(() => { @@ -250,24 +283,153 @@ describe('~/environments/components/form.vue', () => { }); it('updates agent selector field with the name of selected agent', async () => { - findAgentSelector().vm.$emit('shown'); - await waitForPromises(); - await findAgentSelector().vm.$emit('select', '2'); + await selectAgent(); expect(findAgentSelector().props('toggleText')).toBe('agent-2'); }); it('emits changes to the clusterAgentId', async () => { - findAgentSelector().vm.$emit('shown'); - await waitForPromises(); - await findAgentSelector().vm.$emit('select', '2'); + await selectAgent(); expect(wrapper.emitted('change')).toEqual([ - [{ name: '', externalUrl: '', clusterAgentId: '2' }], + [{ name: '', externalUrl: '', clusterAgentId: '2', kubernetesNamespace: null }], ]); }); }); + describe('namespace selector', () => { + it("doesn't render namespace selector if `kubernetesNamespaceForEnvironment` feature flag is disabled", () => { + wrapper = createWrapperWithApollo(); + expect(findNamespaceSelector().exists()).toBe(false); + }); + + it("doesn't render namespace selector if `kasUserAccessProject` feature flag is disabled", () => { + wrapper = createWrapperWithApollo(); + expect(findNamespaceSelector().exists()).toBe(false); + }); + + describe('when `kasUserAccessProject` and `kubernetesNamespaceForEnvironment` feature flags are enabled', () => { + beforeEach(() => { + wrapper = createWrapperWithApollo({ + kasUserAccessProject: true, + kubernetesNamespaceForEnvironment: true, + }); + }); + + it("doesn't render namespace selector by default", () => { + expect(findNamespaceSelector().exists()).toBe(false); + }); + + describe('when the agent was selected', () => { + beforeEach(async () => { + await selectAgent(); + }); + + it('renders namespace selector', () => { + expect(findNamespaceSelector().exists()).toBe(true); + }); + + it('requests the kubernetes namespaces with the correct configuration', async () => { + const configuration = { + basePath: mockKasTunnelUrl.replace(/\/$/, ''), + baseOptions: { + headers: { + 'GitLab-Agent-Id': 2, + }, + withCredentials: true, + }, + }; + + await waitForPromises(); + + expect(getNamespacesQueryResult).toHaveBeenCalledWith( + {}, + { configuration }, + expect.anything(), + expect.anything(), + ); + }); + + it('sets the loading prop while fetching the list', async () => { + expect(findNamespaceSelector().props('loading')).toBe(true); + + await waitForPromises(); + + expect(findNamespaceSelector().props('loading')).toBe(false); + }); + + it('renders a list of available namespaces', async () => { + await waitForPromises(); + + expect(findNamespaceSelector().props('items')).toEqual([ + { text: 'default', value: 'default' }, + { text: 'agent', value: 'agent' }, + ]); + }); + + it('filters the namespaces list on user search', async () => { + await waitForPromises(); + await findNamespaceSelector().vm.$emit('search', 'default'); + + expect(findNamespaceSelector().props('items')).toEqual([ + { value: 'default', text: 'default' }, + ]); + }); + + it('updates namespace selector field with the name of selected namespace', async () => { + await waitForPromises(); + await findNamespaceSelector().vm.$emit('select', 'agent'); + + expect(findNamespaceSelector().props('toggleText')).toBe('agent'); + }); + + it('emits changes to the kubernetesNamespace', async () => { + await waitForPromises(); + await findNamespaceSelector().vm.$emit('select', 'agent'); + + expect(wrapper.emitted('change')[1]).toEqual([ + { name: '', externalUrl: '', kubernetesNamespace: 'agent' }, + ]); + }); + + it('clears namespace selector when another agent was selected', async () => { + await waitForPromises(); + await findNamespaceSelector().vm.$emit('select', 'agent'); + + expect(findNamespaceSelector().props('toggleText')).toBe('agent'); + + await findAgentSelector().vm.$emit('select', '1'); + expect(findNamespaceSelector().props('toggleText')).toBe( + EnvironmentForm.i18n.namespaceHelpText, + ); + }); + }); + + describe('when cannot connect to the cluster', () => { + const error = new Error('Error from the cluster_client API'); + + beforeEach(async () => { + wrapper = createWrapperWithApollo({ + kasUserAccessProject: true, + kubernetesNamespaceForEnvironment: true, + queryResult: jest.fn().mockRejectedValueOnce(error), + }); + + await selectAgent(); + await waitForPromises(); + }); + + it("doesn't render the namespace selector", () => { + expect(findNamespaceSelector().exists()).toBe(false); + }); + + it('renders an alert', () => { + expect(findAlert().text()).toBe('Error from the cluster_client API'); + }); + }); + }); + }); + describe('when environment has an associated agent', () => { const environmentWithAgent = { ...DEFAULT_PROPS.environment, @@ -277,11 +439,48 @@ describe('~/environments/components/form.vue', () => { beforeEach(() => { wrapper = createWrapperWithApollo({ propsData: { environment: environmentWithAgent }, + kasUserAccessProject: true, + kubernetesNamespaceForEnvironment: true, }); }); it('updates agent selector field with the name of the associated agent', () => { expect(findAgentSelector().props('toggleText')).toBe('agent-1'); }); + + it('renders namespace selector', async () => { + await waitForPromises(); + expect(findNamespaceSelector().exists()).toBe(true); + }); + + it('renders a list of available namespaces', async () => { + await waitForPromises(); + + expect(findNamespaceSelector().props('items')).toEqual([ + { text: 'default', value: 'default' }, + { text: 'agent', value: 'agent' }, + ]); + }); + }); + + describe('when environment has an associated kubernetes namespace', () => { + const environmentWithAgentAndNamespace = { + ...DEFAULT_PROPS.environment, + clusterAgent: { id: '1', name: 'agent-1' }, + clusterAgentId: '1', + kubernetesNamespace: 'default', + }; + beforeEach(() => { + wrapper = createWrapperWithApollo({ + propsData: { environment: environmentWithAgentAndNamespace }, + kasUserAccessProject: true, + kubernetesNamespaceForEnvironment: true, + }); + }); + + it('updates namespace selector with the name of the associated namespace', async () => { + await waitForPromises(); + expect(findNamespaceSelector().props('toggleText')).toBe('default'); + }); }); }); diff --git a/spec/frontend/environments/graphql/mock_data.js b/spec/frontend/environments/graphql/mock_data.js index 91268ade1e9..c2eafa5f51e 100644 --- a/spec/frontend/environments/graphql/mock_data.js +++ b/spec/frontend/environments/graphql/mock_data.js @@ -909,3 +909,8 @@ export const k8sWorkloadsMock = { JobList: [completedJob, completedJob, failedJob], CronJobList: [completedCronJob, suspendedCronJob, failedCronJob], }; + +export const k8sNamespacesMock = [ + { metadata: { name: 'default' } }, + { metadata: { name: 'agent' } }, +]; diff --git a/spec/frontend/environments/graphql/resolvers_spec.js b/spec/frontend/environments/graphql/resolvers_spec.js index edffc00e185..be210ed619e 100644 --- a/spec/frontend/environments/graphql/resolvers_spec.js +++ b/spec/frontend/environments/graphql/resolvers_spec.js @@ -12,6 +12,7 @@ import pollIntervalQuery from '~/environments/graphql/queries/poll_interval.quer import isEnvironmentStoppingQuery from '~/environments/graphql/queries/is_environment_stopping.query.graphql'; import pageInfoQuery from '~/environments/graphql/queries/page_info.query.graphql'; import { TEST_HOST } from 'helpers/test_constants'; +import { CLUSTER_AGENT_ERROR_MESSAGES } from '~/environments/constants'; import { environmentsApp, resolvedEnvironmentsApp, @@ -20,6 +21,7 @@ import { resolvedFolder, k8sPodsMock, k8sServicesMock, + k8sNamespacesMock, } from './mock_data'; const ENDPOINT = `${TEST_HOST}/environments`; @@ -319,6 +321,50 @@ describe('~/frontend/environments/graphql/resolvers', () => { ); }); }); + describe('k8sNamespaces', () => { + const mockNamespacesListFn = jest.fn().mockImplementation(() => { + return Promise.resolve({ + data: { + items: k8sNamespacesMock, + }, + }); + }); + + beforeEach(() => { + jest + .spyOn(CoreV1Api.prototype, 'listCoreV1Namespace') + .mockImplementation(mockNamespacesListFn); + }); + + it('should request all namespaces from the cluster_client library', async () => { + const namespaces = await mockResolvers.Query.k8sNamespaces(null, { configuration }); + + expect(mockNamespacesListFn).toHaveBeenCalled(); + + expect(namespaces).toEqual(k8sNamespacesMock); + }); + it.each([ + ['Unauthorized', CLUSTER_AGENT_ERROR_MESSAGES.unauthorized], + ['Forbidden', CLUSTER_AGENT_ERROR_MESSAGES.forbidden], + ['Not found', CLUSTER_AGENT_ERROR_MESSAGES['not found']], + ['Unknown', CLUSTER_AGENT_ERROR_MESSAGES.other], + ])( + 'should throw an error if the API call fails with the reason "%s"', + async (reason, message) => { + jest.spyOn(CoreV1Api.prototype, 'listCoreV1Namespace').mockRejectedValue({ + response: { + data: { + reason, + }, + }, + }); + + await expect(mockResolvers.Query.k8sNamespaces(null, { configuration })).rejects.toThrow( + message, + ); + }, + ); + }); describe('stopEnvironmentREST', () => { it('should post to the stop environment path', async () => { mock.onPost(ENDPOINT).reply(HTTP_STATUS_OK); diff --git a/spec/frontend/environments/new_environment_item_spec.js b/spec/frontend/environments/new_environment_item_spec.js index eb6990ba8a8..aec44af0c73 100644 --- a/spec/frontend/environments/new_environment_item_spec.js +++ b/spec/frontend/environments/new_environment_item_spec.js @@ -13,6 +13,7 @@ import Deployment from '~/environments/components/deployment.vue'; import DeployBoardWrapper from '~/environments/components/deploy_board_wrapper.vue'; import KubernetesOverview from '~/environments/components/kubernetes_overview.vue'; import getEnvironmentClusterAgent from '~/environments/graphql/queries/environment_cluster_agent.query.graphql'; +import getEnvironmentClusterAgentWithNamespace from '~/environments/graphql/queries/environment_cluster_agent_with_namespace.query.graphql'; import { resolvedEnvironment, rolloutStatus, agent } from './graphql/mock_data'; import { mockKasTunnelUrl } from './mock_data'; @@ -21,6 +22,7 @@ Vue.use(VueApollo); describe('~/environments/components/new_environment_item.vue', () => { let wrapper; let queryResponseHandler; + let queryWithNamespaceResponseHandler; const projectPath = '/1'; @@ -37,7 +39,21 @@ describe('~/environments/components/new_environment_item.vue', () => { }, }; queryResponseHandler = jest.fn().mockResolvedValue(response); - return createMockApollo([[getEnvironmentClusterAgent, queryResponseHandler]]); + queryWithNamespaceResponseHandler = jest.fn().mockResolvedValue({ + data: { + project: { + id: response.data.project.id, + environment: { + ...response.data.project.environment, + kubernetesNamespace: 'default', + }, + }, + }, + }); + return createMockApollo([ + [getEnvironmentClusterAgent, queryResponseHandler], + [getEnvironmentClusterAgentWithNamespace, queryWithNamespaceResponseHandler], + ]); }; const createWrapper = ({ propsData = {}, provideData = {}, apolloProvider } = {}) => @@ -537,6 +553,26 @@ describe('~/environments/components/new_environment_item.vue', () => { }); }); + it('should request agent data with kubernetes namespace when `kasUserAccessProject` and `kubernetesNamespaceForEnvironment` feature flags are enabled', async () => { + wrapper = createWrapper({ + propsData: { environment: resolvedEnvironment }, + provideData: { + glFeatures: { + kasUserAccessProject: true, + kubernetesNamespaceForEnvironment: true, + }, + }, + apolloProvider: createApolloProvider(agent), + }); + + await expandCollapsedSection(); + + expect(queryWithNamespaceResponseHandler).toHaveBeenCalledWith({ + environmentName: resolvedEnvironment.name, + projectFullPath: projectPath, + }); + }); + it('should render if the feature flag is enabled and the environment has an agent associated', async () => { wrapper = createWrapper({ propsData: { environment: resolvedEnvironment }, @@ -556,6 +592,27 @@ describe('~/environments/components/new_environment_item.vue', () => { }); }); + it('should render with the namespace if `kasUserAccessProject` and `kubernetesNamespaceForEnvironment` feature flags are enabled and the environment has an agent associated', async () => { + wrapper = createWrapper({ + propsData: { environment: resolvedEnvironment }, + provideData: { + glFeatures: { + kasUserAccessProject: true, + kubernetesNamespaceForEnvironment: true, + }, + }, + apolloProvider: createApolloProvider(agent), + }); + + await expandCollapsedSection(); + await waitForPromises(); + + expect(findKubernetesOverview().props()).toMatchObject({ + clusterAgent: agent, + namespace: 'default', + }); + }); + it('should not render if the feature flag is not enabled', async () => { wrapper = createWrapper({ propsData: { environment: resolvedEnvironment }, diff --git a/spec/graphql/types/root_storage_statistics_type_spec.rb b/spec/graphql/types/root_storage_statistics_type_spec.rb index 56f58825db0..094eb9a8515 100644 --- a/spec/graphql/types/root_storage_statistics_type_spec.rb +++ b/spec/graphql/types/root_storage_statistics_type_spec.rb @@ -9,7 +9,7 @@ RSpec.describe GitlabSchema.types['RootStorageStatistics'] do expect(described_class).to have_graphql_fields(:storage_size, :repository_size, :lfs_objects_size, :build_artifacts_size, :packages_size, :wiki_size, :snippets_size, :pipeline_artifacts_size, :uploads_size, :dependency_proxy_size, - :container_registry_size, :registry_size_estimated) + :container_registry_size, :container_registry_size_is_estimated, :registry_size_estimated) end specify { expect(described_class).to require_graphql_authorizations(:read_statistics) } |