diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-01-09 00:08:08 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-01-09 00:08:08 +0300 |
commit | e0b84f4ba4b44c8ecf00be97843c40df2550b74c (patch) | |
tree | 7cecae4276358dd46d7fa15f84068f7b4c626089 | |
parent | 73391dcc368ef846c2960c1d0ef5e64ca78e1bee (diff) |
Add latest changes from gitlab-org/gitlab@master
48 files changed, 907 insertions, 259 deletions
diff --git a/.gitignore b/.gitignore index 7e038bff3b3..0513bcf19cc 100644 --- a/.gitignore +++ b/.gitignore @@ -86,4 +86,4 @@ jsdoc/ .projections.json /qa/.rakeTasks webpack-dev-server.json -.nvimrc +/.nvimrc diff --git a/app/assets/javascripts/pipelines/components/test_reports/test_suite_table.vue b/app/assets/javascripts/pipelines/components/test_reports/test_suite_table.vue index 28b2c706320..65c1f125b55 100644 --- a/app/assets/javascripts/pipelines/components/test_reports/test_suite_table.vue +++ b/app/assets/javascripts/pipelines/components/test_reports/test_suite_table.vue @@ -3,11 +3,13 @@ import { mapGetters } from 'vuex'; import Icon from '~/vue_shared/components/icon.vue'; import store from '~/pipelines/stores/test_reports'; import { __ } from '~/locale'; +import SmartVirtualList from '~/vue_shared/components/smart_virtual_list.vue'; export default { name: 'TestsSuiteTable', components: { Icon, + SmartVirtualList, }, store, props: { @@ -23,6 +25,8 @@ export default { return this.getSuiteTests.length > 0; }, }, + maxShownRows: 30, + typicalRowHeight: 75, }; </script> @@ -34,7 +38,7 @@ export default { </div> </div> - <div v-if="hasSuites" class="test-reports-table js-test-cases-table"> + <div v-if="hasSuites" class="test-reports-table append-bottom-default js-test-cases-table"> <div role="row" class="gl-responsive-table-row table-row-header font-weight-bold fgray"> <div role="rowheader" class="table-section section-20"> {{ __('Class') }} @@ -53,52 +57,58 @@ export default { </div> </div> - <div - v-for="(testCase, index) in getSuiteTests" - :key="index" - class="gl-responsive-table-row rounded align-items-md-start mt-sm-3 js-case-row" + <smart-virtual-list + :length="getSuiteTests.length" + :remain="$options.maxShownRows" + :size="$options.typicalRowHeight" > - <div class="table-section section-20 section-wrap"> - <div role="rowheader" class="table-mobile-header">{{ __('Class') }}</div> - <div class="table-mobile-content pr-md-1">{{ testCase.classname }}</div> - </div> + <div + v-for="(testCase, index) in getSuiteTests" + :key="index" + class="gl-responsive-table-row rounded align-items-md-start mt-xs-3 js-case-row" + > + <div class="table-section section-20 section-wrap"> + <div role="rowheader" class="table-mobile-header">{{ __('Class') }}</div> + <div class="table-mobile-content pr-md-1 text-truncate">{{ testCase.classname }}</div> + </div> - <div class="table-section section-20 section-wrap"> - <div role="rowheader" class="table-mobile-header">{{ __('Name') }}</div> - <div class="table-mobile-content">{{ testCase.name }}</div> - </div> + <div class="table-section section-20 section-wrap"> + <div role="rowheader" class="table-mobile-header">{{ __('Name') }}</div> + <div class="table-mobile-content">{{ testCase.name }}</div> + </div> - <div class="table-section section-10 section-wrap"> - <div role="rowheader" class="table-mobile-header">{{ __('Status') }}</div> - <div class="table-mobile-content text-center"> - <div - class="add-border ci-status-icon d-flex align-items-center justify-content-end justify-content-md-center" - :class="`ci-status-icon-${testCase.status}`" - > - <icon :size="24" :name="testCase.icon" /> + <div class="table-section section-10 section-wrap"> + <div role="rowheader" class="table-mobile-header">{{ __('Status') }}</div> + <div class="table-mobile-content text-center"> + <div + class="add-border ci-status-icon d-flex align-items-center justify-content-end justify-content-md-center" + :class="`ci-status-icon-${testCase.status}`" + > + <icon :size="24" :name="testCase.icon" /> + </div> </div> </div> - </div> - <div class="table-section flex-grow-1"> - <div role="rowheader" class="table-mobile-header">{{ __('Trace'), }}</div> - <div class="table-mobile-content"> - <pre - v-if="testCase.system_output" - class="build-trace build-trace-rounded text-left" - ><code class="bash p-0">{{testCase.system_output}}</code></pre> + <div class="table-section flex-grow-1"> + <div role="rowheader" class="table-mobile-header">{{ __('Trace'), }}</div> + <div class="table-mobile-content"> + <pre + v-if="testCase.system_output" + class="build-trace build-trace-rounded text-left" + ><code class="bash p-0">{{testCase.system_output}}</code></pre> + </div> </div> - </div> - <div class="table-section section-10 section-wrap"> - <div role="rowheader" class="table-mobile-header"> - {{ __('Duration') }} - </div> - <div class="table-mobile-content text-right"> - {{ testCase.formattedTime }} + <div class="table-section section-10 section-wrap"> + <div role="rowheader" class="table-mobile-header"> + {{ __('Duration') }} + </div> + <div class="table-mobile-content text-right pr-sm-1"> + {{ testCase.formattedTime }} + </div> </div> </div> - </div> + </smart-virtual-list> </div> <div v-else> diff --git a/app/assets/javascripts/pipelines/components/test_reports/test_summary_table.vue b/app/assets/javascripts/pipelines/components/test_reports/test_summary_table.vue index 96177512e35..6effd6e949d 100644 --- a/app/assets/javascripts/pipelines/components/test_reports/test_summary_table.vue +++ b/app/assets/javascripts/pipelines/components/test_reports/test_summary_table.vue @@ -2,9 +2,13 @@ import { mapGetters } from 'vuex'; import { s__ } from '~/locale'; import store from '~/pipelines/stores/test_reports'; +import SmartVirtualList from '~/vue_shared/components/smart_virtual_list.vue'; export default { name: 'TestsSummaryTable', + components: { + SmartVirtualList, + }, store, props: { heading: { @@ -24,6 +28,8 @@ export default { this.$emit('row-click', suite); }, }, + maxShownRows: 20, + typicalRowHeight: 55, }; </script> @@ -35,7 +41,7 @@ export default { </div> </div> - <div v-if="hasSuites" class="test-reports-table js-test-suites-table"> + <div v-if="hasSuites" class="test-reports-table append-bottom-default js-test-suites-table"> <div role="row" class="gl-responsive-table-row table-row-header font-weight-bold"> <div role="rowheader" class="table-section section-25 pl-3"> {{ __('Suite') }} @@ -60,66 +66,72 @@ export default { </div> </div> - <div - v-for="(testSuite, index) in getTestSuites" - :key="index" - role="row" - class="gl-responsive-table-row gl-responsive-table-row-clickable test-reports-summary-row rounded cursor-pointer js-suite-row" - @click="tableRowClick(testSuite)" + <smart-virtual-list + :length="getTestSuites.length" + :remain="$options.maxShownRows" + :size="$options.typicalRowHeight" > - <div class="table-section section-25"> - <div role="rowheader" class="table-mobile-header font-weight-bold"> - {{ __('Suite') }} - </div> - <div class="table-mobile-content underline cgray pl-3"> - {{ testSuite.name }} + <div + v-for="(testSuite, index) in getTestSuites" + :key="index" + role="row" + class="gl-responsive-table-row gl-responsive-table-row-clickable test-reports-summary-row rounded cursor-pointer js-suite-row" + @click="tableRowClick(testSuite)" + > + <div class="table-section section-25"> + <div role="rowheader" class="table-mobile-header font-weight-bold"> + {{ __('Suite') }} + </div> + <div class="table-mobile-content underline cgray pl-3"> + {{ testSuite.name }} + </div> </div> - </div> - <div class="table-section section-25"> - <div role="rowheader" class="table-mobile-header font-weight-bold"> - {{ __('Duration') }} + <div class="table-section section-25"> + <div role="rowheader" class="table-mobile-header font-weight-bold"> + {{ __('Duration') }} + </div> + <div class="table-mobile-content text-md-left"> + {{ testSuite.formattedTime }} + </div> </div> - <div class="table-mobile-content text-md-left"> - {{ testSuite.formattedTime }} - </div> - </div> - <div class="table-section section-10 text-center"> - <div role="rowheader" class="table-mobile-header font-weight-bold"> - {{ __('Failed') }} + <div class="table-section section-10 text-center"> + <div role="rowheader" class="table-mobile-header font-weight-bold"> + {{ __('Failed') }} + </div> + <div class="table-mobile-content">{{ testSuite.failed_count }}</div> </div> - <div class="table-mobile-content">{{ testSuite.failed_count }}</div> - </div> - <div class="table-section section-10 text-center"> - <div role="rowheader" class="table-mobile-header font-weight-bold"> - {{ __('Errors') }} + <div class="table-section section-10 text-center"> + <div role="rowheader" class="table-mobile-header font-weight-bold"> + {{ __('Errors') }} + </div> + <div class="table-mobile-content">{{ testSuite.error_count }}</div> </div> - <div class="table-mobile-content">{{ testSuite.error_count }}</div> - </div> - <div class="table-section section-10 text-center"> - <div role="rowheader" class="table-mobile-header font-weight-bold"> - {{ __('Skipped') }} + <div class="table-section section-10 text-center"> + <div role="rowheader" class="table-mobile-header font-weight-bold"> + {{ __('Skipped') }} + </div> + <div class="table-mobile-content">{{ testSuite.skipped_count }}</div> </div> - <div class="table-mobile-content">{{ testSuite.skipped_count }}</div> - </div> - <div class="table-section section-10 text-center"> - <div role="rowheader" class="table-mobile-header font-weight-bold"> - {{ __('Passed') }} + <div class="table-section section-10 text-center"> + <div role="rowheader" class="table-mobile-header font-weight-bold"> + {{ __('Passed') }} + </div> + <div class="table-mobile-content">{{ testSuite.success_count }}</div> </div> - <div class="table-mobile-content">{{ testSuite.success_count }}</div> - </div> - <div class="table-section section-10 text-right pr-md-3"> - <div role="rowheader" class="table-mobile-header font-weight-bold"> - {{ __('Total') }} + <div class="table-section section-10 text-right pr-md-3"> + <div role="rowheader" class="table-mobile-header font-weight-bold"> + {{ __('Total') }} + </div> + <div class="table-mobile-content">{{ testSuite.total_count }}</div> </div> - <div class="table-mobile-content">{{ testSuite.total_count }}</div> </div> - </div> + </smart-virtual-list> </div> <div v-else> diff --git a/app/graphql/gitlab_schema.rb b/app/graphql/gitlab_schema.rb index ccbb1d56030..ea5776534d5 100644 --- a/app/graphql/gitlab_schema.rb +++ b/app/graphql/gitlab_schema.rb @@ -57,13 +57,55 @@ class GitlabSchema < GraphQL::Schema object.to_global_id end + # Find an object by looking it up from its global ID, passed as a string. + # + # This is the composition of 'parse_gid' and 'find_by_gid', see these + # methods for further documentation. def object_from_id(global_id, ctx = {}) + gid = parse_gid(global_id, ctx) + + find_by_gid(gid) + end + + # Find an object by looking it up from its 'GlobalID'. + # + # * For `ApplicationRecord`s, this is equivalent to + # `global_id.model_class.find(gid.model_id)`, but more efficient. + # * For classes that implement `.lazy_find(global_id)`, this class method + # will be called. + # * All other classes will use `GlobalID#find` + def find_by_gid(gid) + if gid.model_class < ApplicationRecord + Gitlab::Graphql::Loaders::BatchModelLoader.new(gid.model_class, gid.model_id).find + elsif gid.model_class.respond_to?(:lazy_find) + gid.model_class.lazy_find(gid.model_id) + else + gid.find + end + end + + # Parse a string to a GlobalID, raising ArgumentError if there are problems + # with it. + # + # Problems that may occur: + # * it may not be syntactically valid + # * it may not match the expected type (see below) + # + # Options: + # * :expected_type [Class] - the type of object this GlobalID should refer to. + # + # e.g. + # + # ``` + # gid = GitlabSchema.parse_gid(my_string, expected_type: ::Project) + # project_id = gid.model_id + # gid.model_class == ::Project + # ``` + def parse_gid(global_id, ctx = {}) expected_type = ctx[:expected_type] gid = GlobalID.parse(global_id) - unless gid - raise Gitlab::Graphql::Errors::ArgumentError, "#{global_id} is not a valid GitLab id." - end + raise Gitlab::Graphql::Errors::ArgumentError, "#{global_id} is not a valid GitLab id." unless gid if expected_type && !gid.model_class.ancestors.include?(expected_type) vars = { global_id: global_id, expected_type: expected_type } @@ -71,13 +113,7 @@ class GitlabSchema < GraphQL::Schema raise Gitlab::Graphql::Errors::ArgumentError, msg end - if gid.model_class < ApplicationRecord - Gitlab::Graphql::Loaders::BatchModelLoader.new(gid.model_class, gid.model_id).find - elsif gid.model_class.respond_to?(:lazy_find) - gid.model_class.lazy_find(gid.model_id) - else - gid.find - end + gid end private diff --git a/app/models/clusters/applications/jupyter.rb b/app/models/clusters/applications/jupyter.rb index ca93bc15be0..42fa4a6f179 100644 --- a/app/models/clusters/applications/jupyter.rb +++ b/app/models/clusters/applications/jupyter.rb @@ -5,7 +5,7 @@ require 'securerandom' module Clusters module Applications class Jupyter < ApplicationRecord - VERSION = '0.9-174bbd5' + VERSION = '0.9.0-beta.2' self.table_name = 'clusters_applications_jupyter' diff --git a/app/models/project_services/external_wiki_service.rb b/app/models/project_services/external_wiki_service.rb index 593ce69b0fd..0a09000fff4 100644 --- a/app/models/project_services/external_wiki_service.rb +++ b/app/models/project_services/external_wiki_service.rb @@ -19,15 +19,20 @@ class ExternalWikiService < Service def fields [ - { type: 'text', name: 'external_wiki_url', placeholder: s_('ExternalWikiService|The URL of the external Wiki'), required: true } + { + type: 'text', + name: 'external_wiki_url', + placeholder: s_('ExternalWikiService|The URL of the external Wiki'), + required: true + } ] end def execute(_data) - @response = Gitlab::HTTP.get(properties['external_wiki_url'], verify: true) rescue nil - if @response != 200 - nil - end + response = Gitlab::HTTP.get(properties['external_wiki_url'], verify: true) + response.body if response.code == 200 + rescue + nil end def self.supported_events diff --git a/changelogs/unreleased/15398-expiration-policies-update-api.yml b/changelogs/unreleased/15398-expiration-policies-update-api.yml new file mode 100644 index 00000000000..250d3052a5d --- /dev/null +++ b/changelogs/unreleased/15398-expiration-policies-update-api.yml @@ -0,0 +1,5 @@ +--- +title: Container expiration policies can be updated with the project api +merge_request: 22180 +author: +type: added diff --git a/changelogs/unreleased/22171-fix-disabling-dependency-scanning.yml b/changelogs/unreleased/22171-fix-disabling-dependency-scanning.yml new file mode 100644 index 00000000000..f12d460a16a --- /dev/null +++ b/changelogs/unreleased/22171-fix-disabling-dependency-scanning.yml @@ -0,0 +1,5 @@ +--- +title: Check both DEPENDENCY_SCANNING_DISABLED and DS_DISABLE_DIND when executing Dependency Scanning job template +merge_request: 22172 +author: +type: fixed diff --git a/changelogs/unreleased/37725-test-reports-smart-list.yml b/changelogs/unreleased/37725-test-reports-smart-list.yml new file mode 100644 index 00000000000..48353e71e97 --- /dev/null +++ b/changelogs/unreleased/37725-test-reports-smart-list.yml @@ -0,0 +1,5 @@ +--- +title: Added smart virtual list component to test reports to enhance rendering performance +merge_request: 22381 +author: +type: performance diff --git a/changelogs/unreleased/37963-document-var-default.yml b/changelogs/unreleased/37963-document-var-default.yml new file mode 100644 index 00000000000..df5c1c66480 --- /dev/null +++ b/changelogs/unreleased/37963-document-var-default.yml @@ -0,0 +1,5 @@ +--- +title: Document MAVEN_CLI_OPTS defaults for maven project dependency scanning and update when the variable is used +merge_request: 22126 +author: +type: added diff --git a/changelogs/unreleased/update-jupyterhub-chart.yml b/changelogs/unreleased/update-jupyterhub-chart.yml new file mode 100644 index 00000000000..8e2e1ec090e --- /dev/null +++ b/changelogs/unreleased/update-jupyterhub-chart.yml @@ -0,0 +1,5 @@ +--- +title: Update jupyterhub chart +merge_request: 22127 +author: +type: changed diff --git a/doc/administration/gitaly/praefect.md b/doc/administration/gitaly/praefect.md index 597be6cc97e..72c3f996841 100644 --- a/doc/administration/gitaly/praefect.md +++ b/doc/administration/gitaly/praefect.md @@ -25,15 +25,22 @@ The most common architecture for Praefect is simplified in the diagram below: ```mermaid graph TB GitLab --> Praefect; - Praefect --> Gitaly-1; - Praefect --> Gitaly-2; - Praefect --> Gitaly-3; + Praefect --- PostgreSQL; + Praefect --> Gitaly1; + Praefect --> Gitaly2; + Praefect --> Gitaly3; ``` Where `GitLab` is the collection of clients that can request Git operations. The Praefect node has three storage nodes attached. Praefect itself doesn't store data, but connects to three Gitaly nodes, `Gitaly-1`, `Gitaly-2`, and `Gitaly-3`. +In order to keep track of replication state, Praefect relies on a +PostgreSQL database. This database is a single point of failure so you +should use a highly available PostgreSQL server for this. GitLab +itself needs a HA PostgreSQL server too, so you could optionally co-locate the Praefect +SQL database on the PostgreSQL server you use for the rest of GitLab. + Praefect may be enabled on its own node or can be run on the GitLab server. In the example below we will use a separate server, but the optimal configuration for Praefect is still being determined. @@ -62,6 +69,53 @@ We need to manage the following secrets and make them match across hosts: `PRAEFECT_EXTERNAL_TOKEN` because Gitaly clients must not be able to access internal nodes of the Praefect cluster directly; that could lead to data loss. +1. `PRAEFECT_SQL_PASSWORD`: this password is used by Praefect to connect to + PostgreSQL. + +#### Network addresses + +1. `POSTGRESQL_SERVER`: the host name or IP address of your PostgreSQL server + +#### PostgreSQL + +To set up a Praefect cluster you need a highly available PostgreSQL +server. You need PostgreSQL 9.6 or newer. Praefect needs to have a SQL +user with the right to create databases. + +In the instructions below we assume you have administrative access to +your PostgreSQL server via `psql`. Depending on your environment, you +may also be able to do this via the web interface of your cloud +platform, or via your configuration management system, etc. + +Below we assume that you have administrative access as the `postgres` +user. First open a `psql` session as the `postgres` user: + +```shell +psql -h POSTGRESQL_SERVER -U postgres -d template1 +``` + +Once you are connected, run the following command. Replace +`PRAEFECT_SQL_PASSWORD` with the actual (random) password you +generated for the `praefect` SQL user: + +```sql +CREATE ROLE praefect WITH LOGIN CREATEDB PASSWORD 'PRAEFECT_SQL_PASSWORD'; +\q # exit psql +``` + +Now connect as the `praefect` user to create the database. This has +the side effect of verifying that you have access: + +```shell +psql -h POSTGRESQL_SERVER -U praefect -d template1 +``` + +Once you have connected as the `praefect` user, run: + +```sql +CREATE DATABASE praefect_production WITH ENCODING=UTF8; +\q # quit psql +``` #### Praefect @@ -118,10 +172,39 @@ praefect['virtual_storages'] = { } } } + +praefect['database_host'] = 'POSTGRESQL_SERVER' +praefect['database_port'] = 5432 +praefect['database_user'] = 'praefect' +praefect['database_password'] = 'PRAEFECT_SQL_PASSWORD' +praefect['database_dbname'] = 'praefect_production' + +# Uncomment the line below if you do not want to use an encrypted +# connection to PostgreSQL +# praefect['database_sslmode'] = 'disable' + +# Uncomment and modify these lines if you are using a TLS client +# certificate to connect to PostgreSQL +# praefect['database_sslcert'] = '/path/to/client-cert' +# praefect['database_sslkey'] = '/path/to/client-key' + +# Uncomment and modify this line if your PostgreSQL server uses a custom +# CA +# praefect['database_sslrootcert'] = '/path/to/rootcert' ``` Save the file and [reconfigure Praefect](../restart_gitlab.md#omnibus-gitlab-reconfigure). +After you reconfigure, verify that Praefect can reach PostgreSQL: + +```shell +sudo -u git /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml sql-ping +``` + +If the check fails, make sure you have followed the steps correctly. If you edit `/etc/gitlab/gitlab.rb`, +remember to run `sudo gitlab-ctl reconfigure` again before trying the +`sql-ping` command. + #### Gitaly Next we will configure each Gitaly server assigned to Praefect. Configuration for these diff --git a/doc/administration/packages/container_registry.md b/doc/administration/packages/container_registry.md index 0c6caed4e55..e573699e856 100644 --- a/doc/administration/packages/container_registry.md +++ b/doc/administration/packages/container_registry.md @@ -631,7 +631,7 @@ mounting the docker-daemon and setting `privileged = false` in the Runner's ```toml [runners.docker] - image = "ruby:2.1" + image = "ruby:2.6" privileged = false volumes = ["/var/run/docker.sock:/var/run/docker.sock", "/cache"] ``` diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql index 5c6dd93a676..c04071faf9c 100644 --- a/doc/api/graphql/reference/gitlab_schema.graphql +++ b/doc/api/graphql/reference/gitlab_schema.graphql @@ -529,9 +529,9 @@ type CreateSnippetPayload { snippet: Snippet } -type Design implements Noteable { +type Design implements DesignFields & Noteable { """ - Diff refs of the design + The diff refs for this design """ diffRefs: DiffRefs! @@ -561,33 +561,32 @@ type Design implements Noteable { ): DiscussionConnection! """ - Type of change made to the design at the version specified by the `atVersion` - argument if supplied. Defaults to the latest version + How this design was changed in the current version """ event: DesignVersionEvent! """ - Filename of the design file + The filename of the design """ filename: String! """ - Full path of the design file + The full path to the design file """ fullPath: String! """ - ID of the design + The ID of this design """ id: ID! """ - Image of the design + The URL of the image """ image: String! """ - Issue associated with the design + The issue the design belongs to """ issue: Issue! @@ -617,17 +616,17 @@ type Design implements Noteable { ): NoteConnection! """ - Total count of user-created notes for the design + The total count of user-created notes for this design """ notesCount: Int! """ - Project associated with the design + The project the design belongs to """ project: Project! """ - All versions related to the design, ordered newest first + All versions related to this design ordered newest first """ versions( """ @@ -765,6 +764,53 @@ type DesignEdge { node: Design } +interface DesignFields { + """ + The diff refs for this design + """ + diffRefs: DiffRefs! + + """ + How this design was changed in the current version + """ + event: DesignVersionEvent! + + """ + The filename of the design + """ + filename: String! + + """ + The full path to the design file + """ + fullPath: String! + + """ + The ID of this design + """ + id: ID! + + """ + The URL of the image + """ + image: String! + + """ + The issue the design belongs to + """ + issue: Issue! + + """ + The total count of user-created notes for this design + """ + notesCount: Int! + + """ + The project the design belongs to + """ + project: Project! +} + """ Autogenerated input type of DesignManagementDelete """ diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json index 1357cab54fe..8730cccf1ed 100644 --- a/doc/api/graphql/reference/gitlab_schema.json +++ b/doc/api/graphql/reference/gitlab_schema.json @@ -10350,7 +10350,7 @@ "fields": [ { "name": "diffRefs", - "description": "Diff refs of the design", + "description": "The diff refs for this design", "args": [ ], @@ -10425,7 +10425,7 @@ }, { "name": "event", - "description": "Type of change made to the design at the version specified by the `atVersion` argument if supplied. Defaults to the latest version", + "description": "How this design was changed in the current version", "args": [ ], @@ -10443,7 +10443,7 @@ }, { "name": "filename", - "description": "Filename of the design file", + "description": "The filename of the design", "args": [ ], @@ -10461,7 +10461,7 @@ }, { "name": "fullPath", - "description": "Full path of the design file", + "description": "The full path to the design file", "args": [ ], @@ -10479,7 +10479,7 @@ }, { "name": "id", - "description": "ID of the design", + "description": "The ID of this design", "args": [ ], @@ -10497,7 +10497,7 @@ }, { "name": "image", - "description": "Image of the design", + "description": "The URL of the image", "args": [ ], @@ -10515,7 +10515,7 @@ }, { "name": "issue", - "description": "Issue associated with the design", + "description": "The issue the design belongs to", "args": [ ], @@ -10590,7 +10590,7 @@ }, { "name": "notesCount", - "description": "Total count of user-created notes for the design", + "description": "The total count of user-created notes for this design", "args": [ ], @@ -10608,7 +10608,7 @@ }, { "name": "project", - "description": "Project associated with the design", + "description": "The project the design belongs to", "args": [ ], @@ -10626,7 +10626,7 @@ }, { "name": "versions", - "description": "All versions related to the design, ordered newest first", + "description": "All versions related to this design ordered newest first", "args": [ { "name": "after", @@ -10688,12 +10688,196 @@ "kind": "INTERFACE", "name": "Noteable", "ofType": null + }, + { + "kind": "INTERFACE", + "name": "DesignFields", + "ofType": null } ], "enumValues": null, "possibleTypes": null }, { + "kind": "INTERFACE", + "name": "DesignFields", + "description": null, + "fields": [ + { + "name": "diffRefs", + "description": "The diff refs for this design", + "args": [ + + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "DiffRefs", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "event", + "description": "How this design was changed in the current version", + "args": [ + + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "DesignVersionEvent", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "filename", + "description": "The filename of the design", + "args": [ + + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "fullPath", + "description": "The full path to the design file", + "args": [ + + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "id", + "description": "The ID of this design", + "args": [ + + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "image", + "description": "The URL of the image", + "args": [ + + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "issue", + "description": "The issue the design belongs to", + "args": [ + + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Issue", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "notesCount", + "description": "The total count of user-created notes for this design", + "args": [ + + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "project", + "description": "The project the design belongs to", + "args": [ + + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Project", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": [ + { + "kind": "OBJECT", + "name": "Design", + "ofType": null + } + ] + }, + { "kind": "ENUM", "name": "DesignVersionEvent", "description": "Mutation event of a Design within a Version", diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 8ca17d4107d..086c80415ad 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -104,15 +104,15 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph | Name | Type | Description | | --- | ---- | ---------- | -| `id` | ID! | ID of the design | -| `project` | Project! | Project associated with the design | -| `issue` | Issue! | Issue associated with the design | -| `notesCount` | Int! | Total count of user-created notes for the design | -| `filename` | String! | Filename of the design file | -| `fullPath` | String! | Full path of the design file | -| `event` | DesignVersionEvent! | Type of change made to the design at the version specified by the `atVersion` argument if supplied. Defaults to the latest version | -| `image` | String! | Image of the design | -| `diffRefs` | DiffRefs! | Diff refs of the design | +| `id` | ID! | The ID of this design | +| `project` | Project! | The project the design belongs to | +| `issue` | Issue! | The issue the design belongs to | +| `filename` | String! | The filename of the design | +| `fullPath` | String! | The full path to the design file | +| `image` | String! | The URL of the image | +| `diffRefs` | DiffRefs! | The diff refs for this design | +| `event` | DesignVersionEvent! | How this design was changed in the current version | +| `notesCount` | Int! | The total count of user-created notes for this design | ### DesignCollection diff --git a/doc/api/projects.md b/doc/api/projects.md index 33308ff8905..a856c01eb00 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -761,6 +761,14 @@ GET /projects/:id "snippets_enabled": false, "resolve_outdated_diff_discussions": false, "container_registry_enabled": false, + "container_expiration_policy": { + "cadence": "7d", + "enabled": false, + "keep_n": null, + "older_than": null, + "name_regex": null, + "next_run_at": "2020-01-07T21:42:58.658Z" + }, "created_at": "2013-09-30T13:46:02Z", "last_activity_at": "2013-09-30T13:46:02Z", "creator_id": 3, @@ -986,6 +994,7 @@ POST /projects | `snippets_access_level` | string | no | One of `disabled`, `private` or `enabled` | | `resolve_outdated_diff_discussions` | boolean | no | Automatically resolve merge request diffs discussions on lines changed with a push | | `container_registry_enabled` | boolean | no | Enable container registry for this project | +| `container_expiration_policy_attributes` | hash | no | Update the container expiration policy for this project. Accepts: `cadence` (string), `keep_n` (string), `older_than` (string), `name_regex` (string), `enabled` (boolean) | | `shared_runners_enabled` | boolean | no | Enable shared runners for this project | | `visibility` | string | no | See [project visibility level](#project-visibility-level) | | `import_url` | string | no | URL to import repository from | @@ -1115,6 +1124,7 @@ PUT /projects/:id | `snippets_access_level` | string | no | One of `disabled`, `private` or `enabled` | | `resolve_outdated_diff_discussions` | boolean | no | Automatically resolve merge request diffs discussions on lines changed with a push | | `container_registry_enabled` | boolean | no | Enable container registry for this project | +| `container_expiration_policy_attributes` | hash | no | Update the container expiration policy for this project. Accepts: `cadence` (string), `keep_n` (string), `older_than` (string), `name_regex` (string), `enabled` (boolean) | | `shared_runners_enabled` | boolean | no | Enable shared runners for this project | | `visibility` | string | no | See [project visibility level](#project-visibility-level) | | `import_url` | string | no | URL to import repository from | diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md index 2d1abed0dc5..8c6069bd939 100644 --- a/doc/ci/docker/using_docker_images.md +++ b/doc/ci/docker/using_docker_images.md @@ -32,14 +32,14 @@ A one-line example can be seen below: sudo gitlab-runner register \ --url "https://gitlab.example.com/" \ --registration-token "PROJECT_REGISTRATION_TOKEN" \ - --description "docker-ruby-2.1" \ + --description "docker-ruby:2.6" \ --executor "docker" \ - --docker-image ruby:2.1 \ + --docker-image ruby:2.6 \ --docker-services postgres:latest \ --docker-services mysql:latest ``` -The registered runner will use the `ruby:2.1` Docker image and will run two +The registered runner will use the `ruby:2.6` Docker image and will run two services, `postgres:latest` and `mysql:latest`, both of which will be accessible during the build process. @@ -194,7 +194,7 @@ services that you want to use during build time: ```yaml default: - image: ruby:2.2 + image: ruby:2.6 services: - postgres:9.3 @@ -214,15 +214,15 @@ default: before_script: - bundle install -test:2.1: - image: ruby:2.1 +test:2.6: + image: ruby:2.6 services: - postgres:9.3 script: - bundle exec rake spec -test:2.2: - image: ruby:2.2 +test:2.7: + image: ruby:2.7 services: - postgres:9.4 script: @@ -235,7 +235,7 @@ for `image` and `services`: ```yaml default: image: - name: ruby:2.2 + name: ruby:2.6 entrypoint: ["/bin/bash"] services: @@ -277,7 +277,7 @@ services: command: ["postgres"] image: - name: ruby:2.2 + name: ruby:2.6 entrypoint: ["/bin/bash"] before_script: @@ -773,7 +773,7 @@ time. 1. Create any service container: `mysql`, `postgresql`, `mongodb`, `redis`. 1. Create cache container to store all volumes as defined in `config.toml` and - `Dockerfile` of build image (`ruby:2.1` as in above example). + `Dockerfile` of build image (`ruby:2.6` as in above example). 1. Create build container and link any service container to build container. 1. Start build container and send job script to the container. 1. Run job script. @@ -818,11 +818,11 @@ Finally, create a build container by executing the `build_script` file we created earlier: ```sh -docker run --name build -i --link=service-mysql:mysql --link=service-postgres:postgres ruby:2.1 /bin/bash < build_script +docker run --name build -i --link=service-mysql:mysql --link=service-postgres:postgres ruby:2.6 /bin/bash < build_script ``` The above command will create a container named `build` that is spawned from -the `ruby:2.1` image and has two services linked to it. The `build_script` is +the `ruby:2.6` image and has two services linked to it. The `build_script` is piped using STDIN to the bash interpreter which in turn executes the `build_script` in the `build` container. diff --git a/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md b/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md index 9a4fbfcce6d..66246a0fda2 100644 --- a/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md +++ b/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md @@ -71,12 +71,12 @@ gitlab-runner register \ --non-interactive \ --url "https://gitlab.com/" \ --registration-token "PROJECT_REGISTRATION_TOKEN" \ - --description "ruby-2.2" \ + --description "ruby:2.6" \ --executor "docker" \ - --docker-image ruby:2.2 \ + --docker-image ruby:2.6 \ --docker-postgres latest ``` -With the command above, you create a Runner that uses the [ruby:2.2](https://hub.docker.com/_/ruby) image and uses a [postgres](https://hub.docker.com/_/postgres) database. +With the command above, you create a Runner that uses the [ruby:2.6](https://hub.docker.com/_/ruby) image and uses a [postgres](https://hub.docker.com/_/postgres) database. To access the PostgreSQL database, connect to `host: postgres` as user `postgres` with no password. diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 3fc9f75808f..1d735f4e221 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -3645,7 +3645,7 @@ having their own custom `script` defined: ```yaml .job_template: &job_definition # Hidden key that defines an anchor named 'job_definition' - image: ruby:2.1 + image: ruby:2.6 services: - postgres - redis @@ -3667,13 +3667,13 @@ given hash into the current one", and `*` includes the named anchor ```yaml .job_template: - image: ruby:2.1 + image: ruby:2.6 services: - postgres - redis test1: - image: ruby:2.1 + image: ruby:2.6 services: - postgres - redis @@ -3681,7 +3681,7 @@ test1: - test1 project test2: - image: ruby:2.1 + image: ruby:2.6 services: - postgres - redis diff --git a/doc/development/performance.md b/doc/development/performance.md index 786b590ec70..94285efdf1e 100644 --- a/doc/development/performance.md +++ b/doc/development/performance.md @@ -382,7 +382,7 @@ end ## String Freezing In recent Ruby versions calling `freeze` on a String leads to it being allocated -only once and re-used. For example, on Ruby 2.3 this will only allocate the +only once and re-used. For example, on Ruby 2.3 or later this will only allocate the "foo" String once: ```ruby diff --git a/doc/user/application_security/dependency_scanning/index.md b/doc/user/application_security/dependency_scanning/index.md index 01feaaac423..59f241db7de 100644 --- a/doc/user/application_security/dependency_scanning/index.md +++ b/doc/user/application_security/dependency_scanning/index.md @@ -146,7 +146,7 @@ using environment variables. | `PIP_INDEX_URL` | Base URL of Python Package Index (default `https://pypi.org/simple`). | | `PIP_EXTRA_INDEX_URL` | Array of [extra URLs](https://pip.pypa.io/en/stable/reference/pip_install/#cmdoption-extra-index-url) of package indexes to use in addition to `PIP_INDEX_URL`. Comma separated. | | `PIP_REQUIREMENTS_FILE` | Pip requirements file to be scanned. | -| `MAVEN_CLI_OPTS` | List of command line arguments that will be passed to the maven analyzer during the project's build phase (see example for [using private repos](#using-private-maven-repos)). | +| `MAVEN_CLI_OPTS` | List of command line arguments that will be passed to `maven` by the analyzer. The default is `"-DskipTests --batch-mode"`. See an example for [using private repos](#using-private-maven-repos). | | `BUNDLER_AUDIT_UPDATE_DISABLED` | Disable automatic updates for the `bundler-audit` analyzer (default: `"false"`). Useful if you're running Dependency Scanning in an offline, air-gapped environment.| ### Using private Maven repos diff --git a/doc/user/project/pages/getting_started_part_four.md b/doc/user/project/pages/getting_started_part_four.md index 27bd9da8d18..263b20ea224 100644 --- a/doc/user/project/pages/getting_started_part_four.md +++ b/doc/user/project/pages/getting_started_part_four.md @@ -1,5 +1,5 @@ --- -last_updated: 2019-06-04 +last_updated: 2020-01-06 type: reference, howto --- @@ -158,7 +158,7 @@ first thing GitLab Runner will look for in your `.gitlab-ci.yml` is a your container to run that script: ```yaml -image: ruby:2.3 +image: ruby:2.7 pages: script: @@ -170,9 +170,9 @@ pages: ``` In this case, you're telling the Runner to pull this image, which -contains Ruby 2.3 as part of its file system. When you don't specify +contains Ruby 2.7 as part of its file system. When you don't specify this image in your configuration, the Runner will use a default -image, which is Ruby 2.1. +image, which is Ruby 2.6. If your SSG needs [NodeJS](https://nodejs.org/) to build, you'll need to specify which image you want to use, and this image should @@ -198,7 +198,7 @@ To do that, we need to add another line to our CI, telling the Runner to only perform that _job_ called `pages` on the `master` branch `only`: ```yaml -image: ruby:2.3 +image: ruby:2.6 pages: script: @@ -221,7 +221,7 @@ and deploy. To specify which stage your _job_ is running, simply add another line to your CI: ```yaml -image: ruby:2.3 +image: ruby:2.6 pages: stage: deploy @@ -244,7 +244,7 @@ let's add another task (_job_) to our CI, telling it to test every push to other branches, `except` the `master` branch: ```yaml -image: ruby:2.3 +image: ruby:2.6 pages: stage: deploy @@ -294,7 +294,7 @@ every single _job_. In our example, notice that we run We don't need to repeat it: ```yaml -image: ruby:2.3 +image: ruby:2.6 before_script: - bundle install @@ -329,7 +329,7 @@ cache Jekyll dependencies in a `vendor` directory when we run `bundle install`: ```yaml -image: ruby:2.3 +image: ruby:2.6 cache: paths: diff --git a/doc/user/project/pages/introduction.md b/doc/user/project/pages/introduction.md index 01e1909f6d6..37b5e77c062 100644 --- a/doc/user/project/pages/introduction.md +++ b/doc/user/project/pages/introduction.md @@ -1,6 +1,6 @@ --- type: reference -last_updated: 2018-06-04 +last_updated: 2020-01-06 --- # Exploring GitLab Pages @@ -156,7 +156,7 @@ Below is a copy of `.gitlab-ci.yml` where the most significant line is the last one, specifying to execute everything in the `pages` branch: ``` -image: ruby:2.1 +image: ruby:2.6 pages: script: diff --git a/lib/api/entities.rb b/lib/api/entities.rb index c28d6797c56..e6803d48a47 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -178,6 +178,15 @@ module API expose :only_protected_branches end + class ContainerExpirationPolicy < Grape::Entity + expose :cadence + expose :enabled + expose :keep_n + expose :older_than + expose :name_regex + expose :next_run_at + end + class ProjectImportStatus < ProjectIdentity expose :import_status @@ -276,6 +285,8 @@ module API expose :owner, using: Entities::UserBasic, unless: ->(project, options) { project.group } expose :resolve_outdated_diff_discussions expose :container_registry_enabled + expose :container_expiration_policy, using: Entities::ContainerExpirationPolicy, + if: -> (project, _) { project.container_expiration_policy } # Expose old field names with the new permissions methods to keep API compatible # TODO: remove in API v5, replaced by *_access_level @@ -341,6 +352,7 @@ module API # MR describing the solution: https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/20555 super(projects_relation).preload(:group) .preload(:ci_cd_settings) + .preload(:container_expiration_policy) .preload(:auto_devops) .preload(project_group_links: { group: :route }, fork_network: :root_project, diff --git a/lib/api/helpers/projects_helpers.rb b/lib/api/helpers/projects_helpers.rb index 0ca5b73e270..e4f943bd925 100644 --- a/lib/api/helpers/projects_helpers.rb +++ b/lib/api/helpers/projects_helpers.rb @@ -32,6 +32,9 @@ module API optional :resolve_outdated_diff_discussions, type: Boolean, desc: 'Automatically resolve merge request diffs discussions on lines changed with a push' optional :remove_source_branch_after_merge, type: Boolean, desc: 'Remove the source branch by default after merge' optional :container_registry_enabled, type: Boolean, desc: 'Flag indication if the container registry is enabled for that project' + optional :container_expiration_policy_attributes, type: Hash do + use :optional_container_expiration_policy_params + end optional :lfs_enabled, type: Boolean, desc: 'Flag indication if Git LFS is enabled for that project' optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The visibility of the project.' optional :public_builds, type: Boolean, desc: 'Perform public builds' @@ -72,6 +75,14 @@ module API params :optional_update_params_ee do end + params :optional_container_expiration_policy_params do + optional :cadence, type: String, desc: 'Container expiration policy cadence for recurring job' + optional :keep_n, type: String, desc: 'Container expiration policy number of images to keep' + optional :older_than, type: String, desc: 'Container expiration policy remove images older than value' + optional :name_regex, type: String, desc: 'Container expiration policy regex for image removal' + optional :enabled, type: Boolean, desc: 'Flag indication if container expiration policy is enabled' + end + def self.update_params_at_least_one_of [ :auto_devops_enabled, @@ -84,6 +95,7 @@ module API :ci_config_path, :ci_default_git_depth, :container_registry_enabled, + :container_expiration_policy_attributes, :default_branch, :description, :autoclose_referenced_issues, diff --git a/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml index e979a6f2de1..6f270897d93 100644 --- a/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml @@ -77,6 +77,7 @@ dependency_scanning: services: [] except: variables: + - $DEPENDENCY_SCANNING_DISABLED - $DS_DISABLE_DIND == 'false' script: - /analyzer run diff --git a/lib/gitlab/database_importers/self_monitoring/project/delete_service.rb b/lib/gitlab/database_importers/self_monitoring/project/delete_service.rb new file mode 100644 index 00000000000..bce0819b700 --- /dev/null +++ b/lib/gitlab/database_importers/self_monitoring/project/delete_service.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +module Gitlab + module DatabaseImporters + module SelfMonitoring + module Project + class DeleteService < ::BaseService + include Stepable + include SelfMonitoring::Helpers + + steps :validate_self_monitoring_project_exists, + :destroy_project_owner, + :delete_project_id + + def initialize + super(nil) + end + + def execute + execute_steps + end + + private + + def validate_self_monitoring_project_exists(result) + unless project_created? || self_monitoring_project_id.present? + return error(_('Self monitoring project does not exist')) + end + + success(result) + end + + def destroy_project_owner(result) + return success(result) unless project_created? + + if self_monitoring_project.owner.destroy + success(result) + else + log_error(self_monitoring_project.errors.full_messages) + error(_('Error deleting project. Check logs for error details.')) + end + end + + def delete_project_id(result) + update_result = application_settings.update( + instance_administration_project_id: nil + ) + + if update_result + success(result) + else + log_error("Could not delete self monitoring project ID, errors: %{errors}" % { errors: application_settings.errors.full_messages }) + error(_('Could not delete project ID')) + end + end + end + end + end + end +end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index c584defb2e4..56771654526 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -5121,6 +5121,9 @@ msgstr "" msgid "Could not delete chat nickname %{chat_name}." msgstr "" +msgid "Could not delete project ID" +msgstr "" + msgid "Could not fetch projects" msgstr "" @@ -7122,6 +7125,9 @@ msgstr "" msgid "Error deleting %{issuableType}" msgstr "" +msgid "Error deleting project. Check logs for error details." +msgstr "" + msgid "Error details" msgstr "" @@ -16262,6 +16268,9 @@ msgstr "" msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user." msgstr "" +msgid "Self monitoring project does not exist" +msgstr "" + msgid "Self-monitoring is not enabled on this GitLab server, contact your administrator." msgstr "" diff --git a/spec/graphql/gitlab_schema_spec.rb b/spec/graphql/gitlab_schema_spec.rb index dcf3c989047..2ec477fc494 100644 --- a/spec/graphql/gitlab_schema_spec.rb +++ b/spec/graphql/gitlab_schema_spec.rb @@ -124,14 +124,26 @@ describe GitlabSchema do describe '.object_from_id' do context 'for subclasses of `ApplicationRecord`' do - it 'returns the correct record' do - user = create(:user) + let_it_be(:user) { create(:user) } + it 'returns the correct record' do result = described_class.object_from_id(user.to_global_id.to_s) expect(result.sync).to eq(user) end + it 'returns the correct record, of the expected type' do + result = described_class.object_from_id(user.to_global_id.to_s, expected_type: ::User) + + expect(result.sync).to eq(user) + end + + it 'fails if the type does not match' do + expect do + described_class.object_from_id(user.to_global_id.to_s, expected_type: ::Project) + end.to raise_error(Gitlab::Graphql::Errors::ArgumentError) + end + it 'batchloads the queries' do user1 = create(:user) user2 = create(:user) diff --git a/spec/graphql/types/query_type_spec.rb b/spec/graphql/types/query_type_spec.rb index b2d0ba27d4e..39a363cb913 100644 --- a/spec/graphql/types/query_type_spec.rb +++ b/spec/graphql/types/query_type_spec.rb @@ -7,7 +7,16 @@ describe GitlabSchema.types['Query'] do expect(described_class.graphql_name).to eq('Query') end - it { is_expected.to have_graphql_fields(:project, :namespace, :group, :echo, :metadata, :current_user, :snippets) } + it do + is_expected.to have_graphql_fields(:project, + :namespace, + :group, + :echo, + :metadata, + :current_user, + :snippets + ).at_least + end describe 'namespace field' do subject { described_class.fields['namespace'] } diff --git a/spec/lib/gitlab/database_importers/self_monitoring/project/delete_service_spec.rb b/spec/lib/gitlab/database_importers/self_monitoring/project/delete_service_spec.rb new file mode 100644 index 00000000000..b0cec61ce06 --- /dev/null +++ b/spec/lib/gitlab/database_importers/self_monitoring/project/delete_service_spec.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::DatabaseImporters::SelfMonitoring::Project::DeleteService do + describe '#execute' do + let(:result) { subject.execute } + let(:application_setting) { Gitlab::CurrentSettings.current_application_settings } + + before do + allow(ApplicationSetting).to receive(:current_without_cache) { application_setting } + end + + context 'when project does not exist' do + it 'returns error' do + expect(result).to eq( + status: :error, + message: 'Self monitoring project does not exist', + last_step: :validate_self_monitoring_project_exists + ) + end + end + + context 'with project destroyed but ID still present in application settings' do + before do + application_setting.instance_administration_project_id = 1 + end + + it 'deletes project ID from application settings' do + subject.execute + + expect(application_setting.instance_administration_project_id).to be_nil + end + end + + context 'when self monitoring project exists' do + let(:group) { create(:group) } + let(:project) { create(:project, namespace: group) } + + before do + application_setting.instance_administration_project = project + end + + it 'destroys project' do + subject.execute + + expect { project.reload }.to raise_error(ActiveRecord::RecordNotFound) + end + + it 'deletes project ID from application settings' do + subject.execute + + expect(application_setting.instance_administration_project_id).to be_nil + end + end + end +end diff --git a/spec/models/clusters/applications/jupyter_spec.rb b/spec/models/clusters/applications/jupyter_spec.rb index 0ec9333d6a7..3bc5088d1ab 100644 --- a/spec/models/clusters/applications/jupyter_spec.rb +++ b/spec/models/clusters/applications/jupyter_spec.rb @@ -57,7 +57,8 @@ describe Clusters::Applications::Jupyter do it 'is initialized with 4 arguments' do expect(subject.name).to eq('jupyter') expect(subject.chart).to eq('jupyter/jupyterhub') - expect(subject.version).to eq('0.9-174bbd5') + expect(subject.version).to eq('0.9.0-beta.2') + expect(subject).to be_rbac expect(subject.repository).to eq('https://jupyterhub.github.io/helm-chart/') expect(subject.files).to eq(jupyter.files) @@ -75,7 +76,7 @@ describe Clusters::Applications::Jupyter do let(:jupyter) { create(:clusters_applications_jupyter, :errored, version: '0.0.1') } it 'is initialized with the locked version' do - expect(subject.version).to eq('0.9-174bbd5') + expect(subject.version).to eq('0.9.0-beta.2') end end end diff --git a/spec/models/cycle_analytics/code_spec.rb b/spec/models/cycle_analytics/code_spec.rb index 808659552ff..441f8265629 100644 --- a/spec/models/cycle_analytics/code_spec.rb +++ b/spec/models/cycle_analytics/code_spec.rb @@ -5,11 +5,12 @@ require 'spec_helper' describe 'CycleAnalytics#code' do extend CycleAnalyticsHelpers::TestGeneration - let(:project) { create(:project, :repository) } - let(:from_date) { 10.days.ago } - let(:user) { create(:user, :admin) } + let_it_be(:project) { create(:project, :repository) } + let_it_be(:from_date) { 10.days.ago } + let_it_be(:user) { create(:user, :admin) } + let_it_be(:project_level) { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) } - subject { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) } + subject { project_level } context 'with deployment' do generate_cycle_analytics_spec( @@ -24,8 +25,6 @@ describe 'CycleAnalytics#code' do context.create_merge_request_closing_issue(context.user, context.project, data[:issue]) end]], post_fn: -> (context, data) do - context.merge_merge_requests_closing_issue(context.user, context.project, data[:issue]) - context.deploy_master(context.user, context.project) end) context "when a regular merge request (that doesn't close the issue) is created" do @@ -56,7 +55,6 @@ describe 'CycleAnalytics#code' do context.create_merge_request_closing_issue(context.user, context.project, data[:issue]) end]], post_fn: -> (context, data) do - context.merge_merge_requests_closing_issue(context.user, context.project, data[:issue]) end) context "when a regular merge request (that doesn't close the issue) is created" do diff --git a/spec/models/cycle_analytics/group_level_spec.rb b/spec/models/cycle_analytics/group_level_spec.rb index 0d2c14c29dd..03fe8c3b50b 100644 --- a/spec/models/cycle_analytics/group_level_spec.rb +++ b/spec/models/cycle_analytics/group_level_spec.rb @@ -3,12 +3,12 @@ require 'spec_helper' describe CycleAnalytics::GroupLevel do - let(:group) { create(:group)} - let(:project) { create(:project, :repository, namespace: group) } - let(:from_date) { 10.days.ago } - let(:user) { create(:user, :admin) } + let_it_be(:group) { create(:group)} + let_it_be(:project) { create(:project, :repository, namespace: group) } + let_it_be(:from_date) { 10.days.ago } + let_it_be(:user) { create(:user, :admin) } let(:issue) { create(:issue, project: project, created_at: 2.days.ago) } - let(:milestone) { create(:milestone, project: project) } + let_it_be(:milestone) { create(:milestone, project: project) } let(:mr) { create_merge_request_closing_issue(user, project, issue, commit_message: "References #{issue.to_reference}") } let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: project, ref: mr.source_branch, sha: mr.source_branch_sha, head_pipeline_of: mr) } diff --git a/spec/models/cycle_analytics/issue_spec.rb b/spec/models/cycle_analytics/issue_spec.rb index 8cdf83b1292..726f2f8b018 100644 --- a/spec/models/cycle_analytics/issue_spec.rb +++ b/spec/models/cycle_analytics/issue_spec.rb @@ -5,11 +5,12 @@ require 'spec_helper' describe 'CycleAnalytics#issue' do extend CycleAnalyticsHelpers::TestGeneration - let(:project) { create(:project, :repository) } - let(:from_date) { 10.days.ago } - let(:user) { create(:user, :admin) } + let_it_be(:project) { create(:project, :repository) } + let_it_be(:from_date) { 10.days.ago } + let_it_be(:user) { create(:user, :admin) } + let_it_be(:project_level) { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) } - subject { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) } + subject { project_level } generate_cycle_analytics_spec( phase: :issue, @@ -28,10 +29,6 @@ describe 'CycleAnalytics#issue' do end end]], post_fn: -> (context, data) do - if data[:issue].persisted? - context.create_merge_request_closing_issue(context.user, context.project, data[:issue].reload) - context.merge_merge_requests_closing_issue(context.user, context.project, data[:issue]) - end end) context "when a regular label (instead of a list label) is added to the issue" do diff --git a/spec/models/cycle_analytics/plan_spec.rb b/spec/models/cycle_analytics/plan_spec.rb index 28ad9bd194d..3bd9f317ca7 100644 --- a/spec/models/cycle_analytics/plan_spec.rb +++ b/spec/models/cycle_analytics/plan_spec.rb @@ -5,17 +5,18 @@ require 'spec_helper' describe 'CycleAnalytics#plan' do extend CycleAnalyticsHelpers::TestGeneration - let(:project) { create(:project, :repository) } - let(:from_date) { 10.days.ago } - let(:user) { create(:user, :admin) } + let_it_be(:project) { create(:project, :repository) } + let_it_be(:from_date) { 10.days.ago } + let_it_be(:user) { create(:user, :admin) } + let_it_be(:project_level) { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) } - subject { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) } + subject { project_level } generate_cycle_analytics_spec( phase: :plan, data_fn: -> (context) do { - issue: context.create(:issue, project: context.project), + issue: context.build(:issue, project: context.project), branch_name: context.generate(:branch) } end, @@ -32,8 +33,6 @@ describe 'CycleAnalytics#plan' do context.create_commit_referencing_issue(data[:issue], branch_name: data[:branch_name]) end]], post_fn: -> (context, data) do - context.create_merge_request_closing_issue(context.user, context.project, data[:issue], source_branch: data[:branch_name]) - context.merge_merge_requests_closing_issue(context.user, context.project, data[:issue]) end) context "when a regular label (instead of a list label) is added to the issue" do diff --git a/spec/models/cycle_analytics/production_spec.rb b/spec/models/cycle_analytics/production_spec.rb index 613c1786540..01d88bbeec9 100644 --- a/spec/models/cycle_analytics/production_spec.rb +++ b/spec/models/cycle_analytics/production_spec.rb @@ -5,11 +5,12 @@ require 'spec_helper' describe 'CycleAnalytics#production' do extend CycleAnalyticsHelpers::TestGeneration - let(:project) { create(:project, :repository) } - let(:from_date) { 10.days.ago } - let(:user) { create(:user, :admin) } + let_it_be(:project) { create(:project, :repository) } + let_it_be(:from_date) { 10.days.ago } + let_it_be(:user) { create(:user, :admin) } + let_it_be(:project_level) { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) } - subject { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) } + subject { project_level } generate_cycle_analytics_spec( phase: :production, @@ -24,13 +25,7 @@ describe 'CycleAnalytics#production' do ["production deploy happens after merge request is merged (along with other changes)", lambda do |context, data| # Make other changes on master - sha = context.project.repository.create_file( - context.user, - context.generate(:branch), - 'content', - message: 'commit message', - branch_name: 'master') - context.project.repository.commit(sha) + context.project.repository.commit("sha_that_does_not_matter") context.deploy_master(context.user, context.project) end]]) @@ -47,7 +42,7 @@ describe 'CycleAnalytics#production' do context "when the deployment happens to a non-production environment" do it "returns nil" do - issue = create(:issue, project: project) + issue = build(:issue, project: project) merge_request = create_merge_request_closing_issue(user, project, issue) MergeRequests::MergeService.new(project, user).execute(merge_request) deploy_master(user, project, environment: 'staging') diff --git a/spec/models/cycle_analytics/project_level_spec.rb b/spec/models/cycle_analytics/project_level_spec.rb index 351eb139416..2fc81777746 100644 --- a/spec/models/cycle_analytics/project_level_spec.rb +++ b/spec/models/cycle_analytics/project_level_spec.rb @@ -3,11 +3,11 @@ require 'spec_helper' describe CycleAnalytics::ProjectLevel do - let(:project) { create(:project, :repository) } - let(:from_date) { 10.days.ago } - let(:user) { create(:user, :admin) } - let(:issue) { create(:issue, project: project, created_at: 2.days.ago) } - let(:milestone) { create(:milestone, project: project) } + let_it_be(:project) { create(:project, :repository) } + let_it_be(:from_date) { 10.days.ago } + let_it_be(:user) { create(:user, :admin) } + let_it_be(:issue) { create(:issue, project: project, created_at: 2.days.ago) } + let_it_be(:milestone) { create(:milestone, project: project) } let(:mr) { create_merge_request_closing_issue(user, project, issue, commit_message: "References #{issue.to_reference}") } let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: project, ref: mr.source_branch, sha: mr.source_branch_sha, head_pipeline_of: mr) } diff --git a/spec/models/cycle_analytics/review_spec.rb b/spec/models/cycle_analytics/review_spec.rb index ef88fd86340..50670188e85 100644 --- a/spec/models/cycle_analytics/review_spec.rb +++ b/spec/models/cycle_analytics/review_spec.rb @@ -5,9 +5,9 @@ require 'spec_helper' describe 'CycleAnalytics#review' do extend CycleAnalyticsHelpers::TestGeneration - let(:project) { create(:project, :repository) } - let(:from_date) { 10.days.ago } - let(:user) { create(:user, :admin) } + let_it_be(:project) { create(:project, :repository) } + let_it_be(:from_date) { 10.days.ago } + let_it_be(:user) { create(:user, :admin) } subject { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) } diff --git a/spec/models/cycle_analytics/staging_spec.rb b/spec/models/cycle_analytics/staging_spec.rb index 571792559d8..cf0695f175a 100644 --- a/spec/models/cycle_analytics/staging_spec.rb +++ b/spec/models/cycle_analytics/staging_spec.rb @@ -5,11 +5,12 @@ require 'spec_helper' describe 'CycleAnalytics#staging' do extend CycleAnalyticsHelpers::TestGeneration - let(:project) { create(:project, :repository) } - let(:from_date) { 10.days.ago } - let(:user) { create(:user, :admin) } + let_it_be(:project) { create(:project, :repository) } + let_it_be(:from_date) { 10.days.ago } + let_it_be(:user) { create(:user, :admin) } + let_it_be(:project_level) { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) } - subject { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) } + subject { project_level } generate_cycle_analytics_spec( phase: :staging, @@ -28,14 +29,7 @@ describe 'CycleAnalytics#staging' do ["production deploy happens after merge request is merged (along with other changes)", lambda do |context, data| # Make other changes on master - sha = context.project.repository.create_file( - context.user, - context.generate(:branch), - 'content', - message: 'commit message', - branch_name: 'master') - context.project.repository.commit(sha) - + context.project.repository.commit("this_sha_apparently_does_not_matter") context.deploy_master(context.user, context.project) end]]) diff --git a/spec/models/cycle_analytics/test_spec.rb b/spec/models/cycle_analytics/test_spec.rb index 7b3001d2bd8..24800aafca7 100644 --- a/spec/models/cycle_analytics/test_spec.rb +++ b/spec/models/cycle_analytics/test_spec.rb @@ -5,16 +5,19 @@ require 'spec_helper' describe 'CycleAnalytics#test' do extend CycleAnalyticsHelpers::TestGeneration - let(:project) { create(:project, :repository) } - let(:from_date) { 10.days.ago } - let(:user) { create(:user, :admin) } + let_it_be(:project) { create(:project, :repository) } + let_it_be(:from_date) { 10.days.ago } + let_it_be(:user) { create(:user, :admin) } + let_it_be(:issue) { create(:issue, project: project) } + let_it_be(:project_level) { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) } + let!(:merge_request) { create_merge_request_closing_issue(user, project, issue) } - subject { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) } + subject { project_level } generate_cycle_analytics_spec( phase: :test, data_fn: lambda do |context| - issue = context.create(:issue, project: context.project) + issue = context.issue merge_request = context.create_merge_request_closing_issue(context.user, context.project, issue) pipeline = context.create(:ci_pipeline, ref: merge_request.source_branch, sha: merge_request.diff_head_sha, project: context.project, head_pipeline_of: merge_request) { pipeline: pipeline, issue: issue } @@ -22,20 +25,15 @@ describe 'CycleAnalytics#test' do start_time_conditions: [["pipeline is started", -> (context, data) { data[:pipeline].run! }]], end_time_conditions: [["pipeline is finished", -> (context, data) { data[:pipeline].succeed! }]], post_fn: -> (context, data) do - context.merge_merge_requests_closing_issue(context.user, context.project, data[:issue]) end) context "when the pipeline is for a regular merge request (that doesn't close an issue)" do it "returns nil" do - issue = create(:issue, project: project) - merge_request = create_merge_request_closing_issue(user, project, issue) pipeline = create(:ci_pipeline, ref: "refs/heads/#{merge_request.source_branch}", sha: merge_request.diff_head_sha) pipeline.run! pipeline.succeed! - merge_merge_requests_closing_issue(user, project, issue) - expect(subject[:test].project_median).to be_nil end end @@ -53,30 +51,22 @@ describe 'CycleAnalytics#test' do context "when the pipeline is dropped (failed)" do it "returns nil" do - issue = create(:issue, project: project) - merge_request = create_merge_request_closing_issue(user, project, issue) pipeline = create(:ci_pipeline, ref: "refs/heads/#{merge_request.source_branch}", sha: merge_request.diff_head_sha) pipeline.run! pipeline.drop! - merge_merge_requests_closing_issue(user, project, issue) - expect(subject[:test].project_median).to be_nil end end context "when the pipeline is cancelled" do it "returns nil" do - issue = create(:issue, project: project) - merge_request = create_merge_request_closing_issue(user, project, issue) pipeline = create(:ci_pipeline, ref: "refs/heads/#{merge_request.source_branch}", sha: merge_request.diff_head_sha) pipeline.run! pipeline.cancel! - merge_merge_requests_closing_issue(user, project, issue) - expect(subject[:test].project_median).to be_nil end end diff --git a/spec/models/project_services/external_wiki_service_spec.rb b/spec/models/project_services/external_wiki_service_spec.rb index bdd8605436f..f8d88a944a5 100644 --- a/spec/models/project_services/external_wiki_service_spec.rb +++ b/spec/models/project_services/external_wiki_service_spec.rb @@ -26,4 +26,34 @@ describe ExternalWikiService do it { is_expected.not_to validate_presence_of(:external_wiki_url) } end end + + describe 'test' do + before do + subject.properties['external_wiki_url'] = url + end + + let(:url) { 'http://foo' } + let(:data) { nil } + let(:result) { subject.test(data) } + + context 'the URL is not reachable' do + before do + WebMock.stub_request(:get, url).to_return(status: 404, body: 'not a page') + end + + it 'is not successful' do + expect(result[:success]).to be_falsey + end + end + + context 'the URL is reachable' do + before do + WebMock.stub_request(:get, url).to_return(status: 200, body: 'foo') + end + + it 'is successful' do + expect(result[:success]).to be_truthy + end + end + end end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 03efbc6bce7..f2d2cdba480 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -2251,6 +2251,22 @@ describe API::Projects do put api("/projects/#{project3.id}", user4), params: project_param expect(response).to have_gitlab_http_status(403) end + + it 'updates container_expiration_policy' do + project_param = { + container_expiration_policy_attributes: { + cadence: '1month', + keep_n: 1 + } + } + + put api("/projects/#{project3.id}", user4), params: project_param + + expect(response).to have_gitlab_http_status(200) + + expect(json_response['container_expiration_policy']['cadence']).to eq('1month') + expect(json_response['container_expiration_policy']['keep_n']).to eq(1) + end end context 'when authenticated as project developer' do diff --git a/spec/support/cycle_analytics_helpers/test_generation.rb b/spec/support/cycle_analytics_helpers/test_generation.rb index 2096ec90c5b..2fac6bfb30b 100644 --- a/spec/support/cycle_analytics_helpers/test_generation.rb +++ b/spec/support/cycle_analytics_helpers/test_generation.rb @@ -27,6 +27,8 @@ module CycleAnalyticsHelpers scenarios = combinations_of_start_time_conditions.product(combinations_of_end_time_conditions) scenarios.each do |start_time_conditions, end_time_conditions| + let_it_be(:other_project) { create(:project, :repository) } + context "start condition: #{start_time_conditions.map(&:first).to_sentence}" do context "end condition: #{end_time_conditions.map(&:first).to_sentence}" do it "finds the median of available durations between the two conditions", :sidekiq_might_not_need_inline do @@ -56,8 +58,6 @@ module CycleAnalyticsHelpers end context "when the data belongs to another project" do - let(:other_project) { create(:project, :repository) } - it "returns nil" do # Use a stub to "trick" the data/condition functions # into using another project. This saves us from having to diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb index e21b3aea3da..6d9c27d0255 100644 --- a/spec/support/helpers/graphql_helpers.rb +++ b/spec/support/helpers/graphql_helpers.rb @@ -105,12 +105,15 @@ module GraphqlHelpers end def query_graphql_field(name, attributes = {}, fields = nil) - fields ||= all_graphql_fields_for(name.classify) - attributes = attributes_to_graphql(attributes) - attributes = "(#{attributes})" if attributes.present? + field_params = if attributes.present? + "(#{attributes_to_graphql(attributes)})" + else + '' + end + <<~QUERY - #{name}#{attributes} - #{wrap_fields(fields)} + #{GraphqlHelpers.fieldnamerize(name.to_s)}#{field_params} + #{wrap_fields(fields || all_graphql_fields_for(name.to_s.classify))} QUERY end @@ -301,6 +304,17 @@ module GraphqlHelpers def global_id_of(model) model.to_global_id.to_s end + + def missing_required_argument(path, argument) + a_hash_including( + 'path' => ['query'].concat(path), + 'extensions' => a_hash_including('code' => 'missingRequiredArguments', 'arguments' => argument.to_s) + ) + end + + def custom_graphql_error(path, msg) + a_hash_including('path' => path, 'message' => msg) + end end # This warms our schema, doing this as part of loading the helpers to avoid diff --git a/spec/support/matchers/graphql_matchers.rb b/spec/support/matchers/graphql_matchers.rb index 282deea4606..e151a934591 100644 --- a/spec/support/matchers/graphql_matchers.rb +++ b/spec/support/matchers/graphql_matchers.rb @@ -11,8 +11,22 @@ RSpec::Matchers.define :have_graphql_fields do |*expected| Array.wrap(expected).map { |name| GraphqlHelpers.fieldnamerize(name) } end + @allow_extra = false + + chain :only do + @allow_extra = false + end + + chain :at_least do + @allow_extra = true + end + match do |kls| - expect(kls.fields.keys).to contain_exactly(*expected_field_names) + if @allow_extra + expect(kls.fields.keys).to include(*expected_field_names) + else + expect(kls.fields.keys).to contain_exactly(*expected_field_names) + end end failure_message do |kls| @@ -22,7 +36,7 @@ RSpec::Matchers.define :have_graphql_fields do |*expected| message = [] message << "is missing fields: <#{missing.inspect}>" if missing.any? - message << "contained unexpected fields: <#{extra.inspect}>" if extra.any? + message << "contained unexpected fields: <#{extra.inspect}>" if extra.any? && !@allow_extra message.join("\n") end diff --git a/spec/support/shared_examples/graphql/failure_to_find_anything.rb b/spec/support/shared_examples/graphql/failure_to_find_anything.rb new file mode 100644 index 00000000000..b2533c992c1 --- /dev/null +++ b/spec/support/shared_examples/graphql/failure_to_find_anything.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require 'spec_helper' + +# Shared example for legal queries that are expected to return nil. +# Requires the following let bindings to be defined: +# - post_query: action to send the query +# - path: array of keys from query root to the result +shared_examples 'a failure to find anything' do + it 'finds nothing' do + post_query + + data = graphql_data.dig(*path) + + expect(data).to be_nil + end +end |