diff options
60 files changed, 606 insertions, 167 deletions
diff --git a/.gitlab/issue_templates/Security Release.md b/.gitlab/issue_templates/Security Release.md index ae469d3b125..3e60274623e 100644 --- a/.gitlab/issue_templates/Security Release.md +++ b/.gitlab/issue_templates/Security Release.md @@ -1,7 +1,7 @@ <!-- # Read me first! -Set the title to: `Security Release: 11.4.X, 11.3.X, and 11.2.X` +Set the title to: `Security Release: 12.2.X, 12.1.X, and 12.0.X` --> ## Releases tasks @@ -12,9 +12,9 @@ Set the title to: `Security Release: 11.4.X, 11.3.X, and 11.2.X` ## Version issues: -* 11.4.X: {release task link} -* 11.3.X: {release task link} -* 11.2.X: {release task link} +* 12.2.X: {release task link} +* 12.1.X: {release task link} +* 12.0.X: {release task link} ## Security Issues: @@ -34,9 +34,9 @@ Set the title to: `Security Release: 11.4.X, 11.3.X, and 11.2.X` | Version | MR | |---------|----| -| 11.4 | {https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/ link} | -| 11.3 | {https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/ link} | -| 11.2 | {https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/ link} | +| 12.2 | {https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/ link} | +| 12.1 | {https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/ link} | +| 12.0 | {https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/ link} | | master | {https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/ link} | @@ -48,9 +48,9 @@ Set the title to: `Security Release: 11.4.X, 11.3.X, and 11.2.X` | Version | MR | |---------|----| -| 11.4| {https://dev.gitlab.org/gitlab/gitlab-ee/merge_requests/ link} | -| 11.3 | {https://dev.gitlab.org/gitlab/gitlab-ee/merge_requests/ link} | -| 11.2 | {https://dev.gitlab.org/gitlab/gitlab-ee/merge_requests/ link} | +| 12.2 | {https://dev.gitlab.org/gitlab/gitlab-ee/merge_requests/ link} | +| 12.1 | {https://dev.gitlab.org/gitlab/gitlab-ee/merge_requests/ link} | +| 12.0 | {https://dev.gitlab.org/gitlab/gitlab-ee/merge_requests/ link} | | master | {https://dev.gitlab.org/gitlab/gitlab-ee/merge_requests/ link} | diff --git a/.gitlab/issue_templates/Security developer workflow.md b/.gitlab/issue_templates/Security developer workflow.md index 7857afb66c2..3e634de4f0c 100644 --- a/.gitlab/issue_templates/Security developer workflow.md +++ b/.gitlab/issue_templates/Security developer workflow.md @@ -17,7 +17,7 @@ Set the title to: `Description of the original issue` #### Backports -- [ ] Once the MR is ready to be merged, create MRs targeting the last 3 releases, plus the current RC if between the 7th and 22nd of the month. +- [ ] Once the MR is ready to be merged, create MRs targeting the latest 3 stable branches - [ ] At this point, it might be easy to squash the commits from the MR into one - You can use the script `bin/secpick` instead of the following steps, to help you cherry-picking. See the [secpick documentation] - [ ] Create each MR targeting the stable branch `X-Y-stable`, using the "Security Release" merge request template. diff --git a/CHANGELOG.md b/CHANGELOG.md index 15b55f01c32..0752708d5e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,19 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 12.1.4 + +### Fixed (3 changes, 1 of them is from the community) + +- Properly translate term in projects list. !30958 +- Add exclusive lease to mergeability check process. !31082 +- Fix Docker in Docker (DIND) listen port behavior change by adding DOCKER_TLS_CERTDIR in CI job templates. !31201 (Cameron Boulton) + +### Performance (1 change) + +- Improve job log rendering performance. !31262 + + ## 12.1.3 ### Fixed (11 changes) diff --git a/app/assets/javascripts/notes/stores/utils.js b/app/assets/javascripts/notes/stores/utils.js index ed4cef4a917..97dcd54fe88 100644 --- a/app/assets/javascripts/notes/stores/utils.js +++ b/app/assets/javascripts/notes/stores/utils.js @@ -21,7 +21,7 @@ export const getQuickActionText = note => { text = __('Applying multiple commands'); } else { const commandDescription = executedCommands[0].description.toLowerCase(); - text = sprintf(__('Applying command to %{commandDescription}', { commandDescription })); + text = sprintf(__('Applying command to %{commandDescription}'), { commandDescription }); } } diff --git a/app/assets/javascripts/operation_settings/components/external_dashboard.vue b/app/assets/javascripts/operation_settings/components/external_dashboard.vue index ed518611d0b..3c5de189d51 100644 --- a/app/assets/javascripts/operation_settings/components/external_dashboard.vue +++ b/app/assets/javascripts/operation_settings/components/external_dashboard.vue @@ -50,9 +50,11 @@ export default { <form> <gl-form-group :label="s__('ExternalMetrics|Full dashboard URL')" + label-for="full-dashboard-url" :description="s__('ExternalMetrics|Enter the URL of the dashboard you want to link to')" > <gl-form-input + id="full-dashboard-url" v-model="userDashboardUrl" placeholder="https://my-org.gitlab.io/my-dashboards" @keydown.enter.native.prevent="updateExternalDashboardUrl" diff --git a/app/assets/javascripts/reports/components/report_section.vue b/app/assets/javascripts/reports/components/report_section.vue index 9bc3e6388e3..24612c8681a 100644 --- a/app/assets/javascripts/reports/components/report_section.vue +++ b/app/assets/javascripts/reports/components/report_section.vue @@ -166,7 +166,7 @@ export default { <section class="media-section"> <div class="media"> <status-icon :status="statusIconName" :size="24" /> - <div class="media-body d-flex flex-align-self-center prepend-left-default"> + <div class="media-body d-flex flex-align-self-center"> <span class="js-code-text code-text"> {{ headerText }} <slot :name="slotName"></slot> diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js index 930c0d5e958..40a2158de78 100644 --- a/app/assets/javascripts/right_sidebar.js +++ b/app/assets/javascripts/right_sidebar.js @@ -101,10 +101,12 @@ Sidebar.prototype.toggleTodo = function(e) { this.todoUpdateDone(data); }) .catch(() => - flash(sprintf(__('There was an error %{message} todo.')), { - message: - ajaxType === 'post' ? s__('RightSidebar|adding a') : s__('RightSidebar|deleting the'), - }), + flash( + sprintf(__('There was an error %{message} todo.'), { + message: + ajaxType === 'post' ? s__('RightSidebar|adding a') : s__('RightSidebar|deleting the'), + }), + ), ); }; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue index 8dbd9e52cfe..13e4b061fda 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue @@ -32,10 +32,13 @@ export default { }; </script> <template> - <div class="d-flex widget-status-icon"> - <div v-if="isLoading" class="mr-widget-icon"><gl-loading-icon size="sm" /></div> - - <ci-icon v-else :status="statusObj" :size="24" /> + <div class="d-flex align-self-start"> + <div class="square s24 h-auto d-flex-center append-right-default"> + <div v-if="isLoading" class="mr-widget-icon"> + <gl-loading-icon size="sm" /> + </div> + <ci-icon v-else :status="statusObj" :size="24" /> + </div> <button v-if="showDisabledButton" diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 2780afa11fa..cb7913ee54e 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -88,7 +88,7 @@ } .mr-widget-info { - padding-left: $gl-padding-50 - $gl-padding-32; + padding-left: $gl-padding; padding-right: $gl-padding; } @@ -262,23 +262,11 @@ } } - .widget-status-icon { - align-self: flex-start; - - button { - margin-left: $gl-padding; - } - } - .mr-widget-body { line-height: 28px; @include clearfix; - button { - margin-left: $gl-padding; - } - .approve-btn { margin-right: 5px; } @@ -527,7 +515,7 @@ } .mr-links { - padding-left: $status-icon-size + $gl-btn-padding; + padding-left: $gl-padding-8 + $status-icon-size + $gl-btn-padding; &:last-child { padding-bottom: $gl-padding; diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb index 141a7dfb923..e7bdb4b2042 100644 --- a/app/controllers/projects/branches_controller.rb +++ b/app/controllers/projects/branches_controller.rb @@ -49,7 +49,7 @@ class Projects::BranchesController < Projects::ApplicationController branches = BranchesFinder.new(repository, params.permit(names: [])).execute Gitlab::GitalyClient.allow_n_plus_1_calls do - render json: branches.to_h { |branch| [branch.name, service.call(branch)] } + render json: branches.map { |branch| [branch.name, service.call(branch)] }.to_h end end end diff --git a/app/finders/branches_finder.rb b/app/finders/branches_finder.rb index b462c8053fa..291a24c1405 100644 --- a/app/finders/branches_finder.rb +++ b/app/finders/branches_finder.rb @@ -69,7 +69,7 @@ class BranchesFinder return branches unless names branch_names = names.to_set - branches.filter do |branch| + branches.select do |branch| branch_names.include?(branch.name) end end diff --git a/app/finders/container_repositories_finder.rb b/app/finders/container_repositories_finder.rb new file mode 100644 index 00000000000..eb91d7f825b --- /dev/null +++ b/app/finders/container_repositories_finder.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +class ContainerRepositoriesFinder + # id: group or project id + # container_type: :group or :project + def initialize(id:, container_type:) + @id = id + @type = container_type.to_sym + end + + def execute + if project_type? + project.container_repositories + else + group.container_repositories + end + end + + private + + attr_reader :id, :type + + def project_type? + type == :project + end + + def project + Project.find(id) + end + + def group + Group.find(id) + end +end diff --git a/app/models/group.rb b/app/models/group.rb index 74eb556b1b5..6c868b1d1f0 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -44,6 +44,8 @@ class Group < Namespace has_many :cluster_groups, class_name: 'Clusters::Group' has_many :clusters, through: :cluster_groups, class_name: 'Clusters::Cluster' + has_many :container_repositories, through: :projects + has_many :todos accepts_nested_attributes_for :variables, allow_destroy: true diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb index 84b1873c05d..52c944491bf 100644 --- a/app/policies/group_policy.rb +++ b/app/policies/group_policy.rb @@ -68,6 +68,7 @@ class GroupPolicy < BasePolicy rule { developer }.enable :admin_milestone rule { reporter }.policy do + enable :read_container_image enable :admin_label enable :admin_list enable :admin_issue diff --git a/app/serializers/analytics_issue_entity.rb b/app/serializers/analytics_issue_entity.rb index 29d4a6ae1d0..307ce14a921 100644 --- a/app/serializers/analytics_issue_entity.rb +++ b/app/serializers/analytics_issue_entity.rb @@ -26,6 +26,6 @@ class AnalyticsIssueEntity < Grape::Entity private def url_to(route, object) - public_send("#{route}_url", object[:path], object[:name], object[:iid].to_s) # rubocop:disable GitlabSecurity/PublicSend + public_send("#{route}_url", object[:namespace_path], object[:project_path], object[:iid].to_s) # rubocop:disable GitlabSecurity/PublicSend end end diff --git a/app/views/admin/users/_access_levels.html.haml b/app/views/admin/users/_access_levels.html.haml index 77729636f9d..bb1e22cc610 100644 --- a/app/views/admin/users/_access_levels.html.haml +++ b/app/views/admin/users/_access_levels.html.haml @@ -19,7 +19,7 @@ - editing_current_user = (current_user == @user) = f.radio_button :access_level, :regular, disabled: editing_current_user - = label_tag :regular, class: 'font-weight-bold' do + = f.label :access_level_regular, class: 'font-weight-bold' do Regular %p.light Regular users have access to their groups and projects @@ -27,7 +27,7 @@ = render_if_exists 'admin/users/auditor_access_level_radio', f: f, disabled: editing_current_user = f.radio_button :access_level, :admin, disabled: editing_current_user - = label_tag :admin, class: 'font-weight-bold' do + = f.label :access_level_admin, class: 'font-weight-bold' do Admin %p.light Administrators have access to all groups, projects and users and can manage all features in this installation diff --git a/changelogs/unreleased/26866-api-endpoint-to-list-the-docker-images-tags-of-a-group.yml b/changelogs/unreleased/26866-api-endpoint-to-list-the-docker-images-tags-of-a-group.yml new file mode 100644 index 00000000000..adbd7971a14 --- /dev/null +++ b/changelogs/unreleased/26866-api-endpoint-to-list-the-docker-images-tags-of-a-group.yml @@ -0,0 +1,6 @@ +--- +title: Add API endpoints to return container repositories and tags from the group + level +merge_request: 30817 +author: +type: added diff --git a/changelogs/unreleased/64675-Dashboard-URL-legend-border.yml b/changelogs/unreleased/64675-Dashboard-URL-legend-border.yml new file mode 100644 index 00000000000..f35261fcd6c --- /dev/null +++ b/changelogs/unreleased/64675-Dashboard-URL-legend-border.yml @@ -0,0 +1,5 @@ +--- +title: Removed extrenal dashboard legend border +merge_request: 31407 +author: +type: fixed diff --git a/changelogs/unreleased/64831-add-padding-to-merged-by-widget.yml b/changelogs/unreleased/64831-add-padding-to-merged-by-widget.yml new file mode 100644 index 00000000000..2a45eec78ef --- /dev/null +++ b/changelogs/unreleased/64831-add-padding-to-merged-by-widget.yml @@ -0,0 +1,5 @@ +--- +title: Add space to "merged by" widget +merge_request: 30972 +author: +type: fixed diff --git a/changelogs/unreleased/dblessing-fix-admin-user-radio-labels.yml b/changelogs/unreleased/dblessing-fix-admin-user-radio-labels.yml new file mode 100644 index 00000000000..4f119d46a1f --- /dev/null +++ b/changelogs/unreleased/dblessing-fix-admin-user-radio-labels.yml @@ -0,0 +1,5 @@ +--- +title: Fix admin area user access level radio button labels +merge_request: 31154 +author: +type: fixed diff --git a/changelogs/unreleased/fix-i18n-updated-projects.yml b/changelogs/unreleased/fix-i18n-updated-projects.yml deleted file mode 100644 index 408ee438480..00000000000 --- a/changelogs/unreleased/fix-i18n-updated-projects.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Properly translate term in projects list -merge_request: 30958 -author: -type: fixed diff --git a/changelogs/unreleased/fix-name-vs-path-problem-for-cycle-analytics.yml b/changelogs/unreleased/fix-name-vs-path-problem-for-cycle-analytics.yml new file mode 100644 index 00000000000..7d171c2cf5b --- /dev/null +++ b/changelogs/unreleased/fix-name-vs-path-problem-for-cycle-analytics.yml @@ -0,0 +1,5 @@ +--- +title: Fix broken issue links and possible 500 error on cycle analytics page when project name and path are different +merge_request: 31471 +author: +type: fixed diff --git a/changelogs/unreleased/leipert-improve-ansi2html.yml b/changelogs/unreleased/leipert-improve-ansi2html.yml deleted file mode 100644 index dd3582b3434..00000000000 --- a/changelogs/unreleased/leipert-improve-ansi2html.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Improve job log rendering performance -merge_request: 31262 -author: -type: performance diff --git a/changelogs/unreleased/osw-avoid-errors-due-to-concurrent-calls.yml b/changelogs/unreleased/osw-avoid-errors-due-to-concurrent-calls.yml deleted file mode 100644 index 17ff1b012cf..00000000000 --- a/changelogs/unreleased/osw-avoid-errors-due-to-concurrent-calls.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add exclusive lease to mergeability check process -merge_request: 31082 -author: -type: fixed diff --git a/changelogs/unreleased/patch-72.yml b/changelogs/unreleased/patch-72.yml deleted file mode 100644 index ff2bac2fc29..00000000000 --- a/changelogs/unreleased/patch-72.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix Docker in Docker (DIND) listen port behavior change by adding DOCKER_TLS_CERTDIR in CI job templates. -merge_request: 31201 -author: Cameron Boulton -type: fixed diff --git a/changelogs/unreleased/sh-disable-redis-peek.yml b/changelogs/unreleased/sh-disable-redis-peek.yml new file mode 100644 index 00000000000..de86c0031c7 --- /dev/null +++ b/changelogs/unreleased/sh-disable-redis-peek.yml @@ -0,0 +1,5 @@ +--- +title: Only track Redis calls if Peek is enabled +merge_request: 31438 +author: +type: performance diff --git a/doc/administration/index.md b/doc/administration/index.md index 00c8863f200..febee3e5af8 100644 --- a/doc/administration/index.md +++ b/doc/administration/index.md @@ -185,3 +185,5 @@ Learn how to install, configure, update, and maintain your GitLab instance. - [Debugging tips](troubleshooting/debug.md): Tips to debug problems when things go wrong - [Log system](logs.md): Where to look for logs. - [Sidekiq Troubleshooting](troubleshooting/sidekiq.md): Debug when Sidekiq appears hung and is not processing jobs. +- Useful [diagnostics tools](troubleshooting/diagnostics_tools.md) that are sometimes used by the GitLab + Support team. diff --git a/doc/administration/plugins.md b/doc/administration/plugins.md index 4302667caf5..4cf3c607dae 100644 --- a/doc/administration/plugins.md +++ b/doc/administration/plugins.md @@ -52,7 +52,37 @@ as appropriate. The plugins file list is updated for each event, there is no need to restart GitLab to apply a new plugin. If a plugin executes with non-zero exit code or GitLab fails to execute it, a -message will be logged to `plugin.log`. +message will be logged to: + +- `gitlab-rails/plugin.log` in an Omnibus installation. +- `log/plugin.log` in a source installation. + +## Creating plugins + +Below is an example that will only response on the event `project_create` and +will inform the admins from the GitLab instance that a new project has been created. + +```ruby +# By using the embedded ruby version we eliminate the possibility that our chosen language +# would be unavailable from +#!/opt/gitlab/embedded/bin/ruby +require 'json' +require 'mail' + +# The incoming variables are in JSON format so we need to parse it first. +ARGS = JSON.parse(STDIN.read) + +# We only want to trigger this plugin on the event project_create +return unless ARGS['event_name'] == 'project_create' + +# We will inform our admins of our gitlab instance that a new project is created +Mail.deliver do + from 'info@gitlab_instance.com' + to 'admin@gitlab_instance.com' + subject "new project " + ARGS['name'] + body ARGS['owner_name'] + 'created project ' + ARGS['name'] +end +``` ## Validation diff --git a/doc/administration/troubleshooting/diagnostics_tools.md b/doc/administration/troubleshooting/diagnostics_tools.md new file mode 100644 index 00000000000..ab3b25f0e97 --- /dev/null +++ b/doc/administration/troubleshooting/diagnostics_tools.md @@ -0,0 +1,27 @@ +--- +type: reference +--- + +# Diagnostics tools + +These are some of the diagnostics tools the GitLab Support team uses during troubleshooting. +They are listed here for transparency, and they may be useful for users with experience +with troubleshooting GitLab. If you are currently having an issue with GitLab, you +may want to check your [support options](https://about.gitlab.com/support/) first, +before attempting to use these tools. + +## gitlabsos + +The [gitlabsos](https://gitlab.com/gitlab-com/support/toolbox/gitlabsos/) utility +provides a unified method of gathering info and logs from GitLab and the system it's +running on. + +## strace-parser + +[strace-parser](https://gitlab.com/wchandler/strace-parser) is a small tool to analyze +and summarize raw strace data. + +## Pritaly + +[Pritaly](https://gitlab.com/wchandler/pritaly) takes Gitaly logs and colorizes output +or converts the logs to JSON. diff --git a/doc/api/container_registry.md b/doc/api/container_registry.md index 174b93a4f7a..bf544f64178 100644 --- a/doc/api/container_registry.md +++ b/doc/api/container_registry.md @@ -6,6 +6,8 @@ This is the API docs of the [GitLab Container Registry](../user/project/containe ## List registry repositories +### Within a project + Get a list of registry repositories in a project. ``` @@ -14,7 +16,8 @@ GET /projects/:id/registry/repositories | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) accessible by the authenticated user. | +| `tags` | boolean | no | If the param is included as true, each repository will include an array of `"tags"` in the response. | ```bash curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/registry/repositories" @@ -28,6 +31,7 @@ Example response: "id": 1, "name": "", "path": "group/project", + "project_id": 9, "location": "gitlab.example.com:5000/group/project", "created_at": "2019-01-10T13:38:57.391Z" }, @@ -35,12 +39,77 @@ Example response: "id": 2, "name": "releases", "path": "group/project/releases", + "project_id": 9, "location": "gitlab.example.com:5000/group/project/releases", "created_at": "2019-01-10T13:39:08.229Z" } ] ``` +### Within a group + +Get a list of registry repositories in a group. + +``` +GET /groups/:id/registry/repositories +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) accessible by the authenticated user. | +| `tags` | boolean | no | If the param is included as true, each repository will include an array of `"tags"` in the response. | + +```bash +curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/2/registry/repositories?tags=1" +``` + +Example response: + +```json +[ + { + "id": 1, + "name": "", + "path": "group/project", + "project_id": 9, + "location": "gitlab.example.com:5000/group/project", + "created_at": "2019-01-10T13:38:57.391Z", + "tags": [ + { + "name": "0.0.1", + "path": "group/project:0.0.1", + "location": "gitlab.example.com:5000/group/project:0.0.1" + } + ] + }, + { + "id": 2, + "name": "", + "path": "group/other_project", + "project_id": 11, + "location": "gitlab.example.com:5000/group/other_project", + "created_at": "2019-01-10T13:39:08.229Z", + "tags": [ + { + "name": "0.0.1", + "path": "group/other_project:0.0.1", + "location": "gitlab.example.com:5000/group/other_project:0.0.1" + }, + { + "name": "0.0.2", + "path": "group/other_project:0.0.2", + "location": "gitlab.example.com:5000/group/other_project:0.0.2" + }, + { + "name": "latest", + "path": "group/other_project:latest", + "location": "gitlab.example.com:5000/group/other_project:latest" + } + ] + } +] +``` + ## Delete registry repository Delete a repository in registry. @@ -62,6 +131,8 @@ curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://git ## List repository tags +### Within a project + Get a list of tags for given registry repository. ``` @@ -70,7 +141,7 @@ GET /projects/:id/registry/repositories/:repository_id/tags | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) accessible by the authenticated user. | | `repository_id` | integer | yes | The ID of registry repository. | ```bash @@ -104,7 +175,7 @@ GET /projects/:id/registry/repositories/:repository_id/tags/:tag_name | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) accessible by the authenticated user. | | `repository_id` | integer | yes | The ID of registry repository. | | `tag_name` | string | yes | The name of tag. | diff --git a/doc/user/project/pipelines/settings.md b/doc/user/project/pipelines/settings.md index 45f27b3c811..456209c2180 100644 --- a/doc/user/project/pipelines/settings.md +++ b/doc/user/project/pipelines/settings.md @@ -76,6 +76,13 @@ Here are some valid examples: - `my/path/.gitlab-ci.yml` - `my/path/.my-custom-file.yml` +The path can be customized at a project level. To customize the path: + +1. Go to the project's **Settings > CI / CD**. +1. Expand the **General pipelines** section. +1. Provide a value in the **Custom CI config path** field. +1. Click **Save changes**. + ## Test coverage parsing If you use test coverage in your code, GitLab can capture its output in the diff --git a/doc/user/project/quick_actions.md b/doc/user/project/quick_actions.md index b8e0ef8d12f..437899dce1e 100644 --- a/doc/user/project/quick_actions.md +++ b/doc/user/project/quick_actions.md @@ -70,6 +70,19 @@ Many quick actions require a parameter, for example: username, milestone, and label. [Autocomplete characters](autocomplete_characters.md) can make it easier to enter a parameter, compared to selecting items from a list. +## Quick actions parameters + +The easiest way to set parameters for quick actions is to use autocomplete. If +you manually enter a parameter, it must be enclosed in double quotation marks +(`"`), unless it contains only: + +1. ASCII letters. +2. Numerals. +3. Underscore, hyphen, question mark, dot, and ampersand. + +Parameters are also case-sensitive. Autocomplete handles this, and the insertion +of quotation marks, automatically. + ## Quick actions for commit messages The following quick actions are applicable for commit messages: diff --git a/lib/api/api.rb b/lib/api/api.rb index 223ae13bd2d..e500a93b31e 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -104,7 +104,6 @@ module API mount ::API::BroadcastMessages mount ::API::Commits mount ::API::CommitStatuses - mount ::API::ContainerRegistry mount ::API::DeployKeys mount ::API::Deployments mount ::API::Environments @@ -116,6 +115,7 @@ module API mount ::API::GroupLabels mount ::API::GroupMilestones mount ::API::Groups + mount ::API::GroupContainerRepositories mount ::API::GroupVariables mount ::API::ImportGithub mount ::API::Internal @@ -138,6 +138,7 @@ module API mount ::API::Pipelines mount ::API::PipelineSchedules mount ::API::ProjectClusters + mount ::API::ProjectContainerRepositories mount ::API::ProjectEvents mount ::API::ProjectExport mount ::API::ProjectImport diff --git a/lib/api/entities/container_registry.rb b/lib/api/entities/container_registry.rb index 00833ca7480..6250f35c7cb 100644 --- a/lib/api/entities/container_registry.rb +++ b/lib/api/entities/container_registry.rb @@ -3,18 +3,20 @@ module API module Entities module ContainerRegistry - class Repository < Grape::Entity - expose :id + class Tag < Grape::Entity expose :name expose :path expose :location - expose :created_at end - class Tag < Grape::Entity + class Repository < Grape::Entity + expose :id expose :name expose :path + expose :project_id expose :location + expose :created_at + expose :tags, using: Tag, if: -> (_, options) { options[:tags] } end class TagDetails < Tag diff --git a/lib/api/group_container_repositories.rb b/lib/api/group_container_repositories.rb new file mode 100644 index 00000000000..fd24662cc9a --- /dev/null +++ b/lib/api/group_container_repositories.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module API + class GroupContainerRepositories < Grape::API + include PaginationParams + + before { authorize_read_group_container_images! } + + REPOSITORY_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge( + tag_name: API::NO_SLASH_URL_PART_REGEX) + + params do + requires :id, type: String, desc: "Group's ID or path" + end + resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do + desc 'Get a list of all repositories within a group' do + detail 'This feature was introduced in GitLab 12.2.' + success Entities::ContainerRegistry::Repository + end + params do + use :pagination + optional :tags, type: Boolean, default: false, desc: 'Determines if tags should be included' + end + get ':id/registry/repositories' do + repositories = ContainerRepositoriesFinder.new( + id: user_group.id, container_type: :group + ).execute + + present paginate(repositories), with: Entities::ContainerRegistry::Repository, tags: params[:tags] + end + end + + helpers do + def authorize_read_group_container_images! + authorize! :read_container_image, user_group + end + end + end +end diff --git a/lib/api/container_registry.rb b/lib/api/project_container_repositories.rb index 7dad20a822a..6d53abcc500 100644 --- a/lib/api/container_registry.rb +++ b/lib/api/project_container_repositories.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true module API - class ContainerRegistry < Grape::API + class ProjectContainerRepositories < Grape::API include PaginationParams - REGISTRY_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge( + REPOSITORY_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge( tag_name: API::NO_SLASH_URL_PART_REGEX) before { error!('404 Not Found', 404) unless Feature.enabled?(:container_registry_api, user_project, default_enabled: true) } @@ -20,11 +20,14 @@ module API end params do use :pagination + optional :tags, type: Boolean, default: false, desc: 'Determines if tags should be included' end get ':id/registry/repositories' do - repositories = user_project.container_repositories.ordered + repositories = ContainerRepositoriesFinder.new( + id: user_project.id, container_type: :project + ).execute - present paginate(repositories), with: Entities::ContainerRegistry::Repository + present paginate(repositories), with: Entities::ContainerRegistry::Repository, tags: params[:tags] end desc 'Delete repository' do @@ -33,7 +36,7 @@ module API params do requires :repository_id, type: Integer, desc: 'The ID of the repository' end - delete ':id/registry/repositories/:repository_id', requirements: REGISTRY_ENDPOINT_REQUIREMENTS do + delete ':id/registry/repositories/:repository_id', requirements: REPOSITORY_ENDPOINT_REQUIREMENTS do authorize_admin_container_image! DeleteContainerRepositoryWorker.perform_async(current_user.id, repository.id) @@ -49,7 +52,7 @@ module API requires :repository_id, type: Integer, desc: 'The ID of the repository' use :pagination end - get ':id/registry/repositories/:repository_id/tags', requirements: REGISTRY_ENDPOINT_REQUIREMENTS do + get ':id/registry/repositories/:repository_id/tags', requirements: REPOSITORY_ENDPOINT_REQUIREMENTS do authorize_read_container_image! tags = Kaminari.paginate_array(repository.tags) @@ -65,7 +68,7 @@ module API optional :keep_n, type: Integer, desc: 'Keep n of latest tags with matching name' optional :older_than, type: String, desc: 'Delete older than: 1h, 1d, 1month' end - delete ':id/registry/repositories/:repository_id/tags', requirements: REGISTRY_ENDPOINT_REQUIREMENTS do + delete ':id/registry/repositories/:repository_id/tags', requirements: REPOSITORY_ENDPOINT_REQUIREMENTS do authorize_admin_container_image! message = 'This request has already been made. You can run this at most once an hour for a given container repository' @@ -85,7 +88,7 @@ module API requires :repository_id, type: Integer, desc: 'The ID of the repository' requires :tag_name, type: String, desc: 'The name of the tag' end - get ':id/registry/repositories/:repository_id/tags/:tag_name', requirements: REGISTRY_ENDPOINT_REQUIREMENTS do + get ':id/registry/repositories/:repository_id/tags/:tag_name', requirements: REPOSITORY_ENDPOINT_REQUIREMENTS do authorize_read_container_image! validate_tag! @@ -99,7 +102,7 @@ module API requires :repository_id, type: Integer, desc: 'The ID of the repository' requires :tag_name, type: String, desc: 'The name of the tag' end - delete ':id/registry/repositories/:repository_id/tags/:tag_name', requirements: REGISTRY_ENDPOINT_REQUIREMENTS do + delete ':id/registry/repositories/:repository_id/tags/:tag_name', requirements: REPOSITORY_ENDPOINT_REQUIREMENTS do authorize_destroy_container_image! validate_tag! diff --git a/lib/gitlab/background_migration/migrate_legacy_artifacts.rb b/lib/gitlab/background_migration/migrate_legacy_artifacts.rb index 5cd638083b0..4377ec2987c 100644 --- a/lib/gitlab/background_migration/migrate_legacy_artifacts.rb +++ b/lib/gitlab/background_migration/migrate_legacy_artifacts.rb @@ -39,10 +39,10 @@ module Gitlab SELECT project_id, id, - artifacts_expire_at, + artifacts_expire_at #{add_missing_db_timezone}, #{LEGACY_PATH_FILE_LOCATION}, - created_at, - created_at, + created_at #{add_missing_db_timezone}, + created_at #{add_missing_db_timezone}, artifacts_file, artifacts_size, COALESCE(artifacts_file_store, #{FILE_LOCAL_STORE}), @@ -81,10 +81,10 @@ module Gitlab SELECT project_id, id, - artifacts_expire_at, + artifacts_expire_at #{add_missing_db_timezone}, #{LEGACY_PATH_FILE_LOCATION}, - created_at, - created_at, + created_at #{add_missing_db_timezone}, + created_at #{add_missing_db_timezone}, artifacts_metadata, NULL, COALESCE(artifacts_metadata_store, #{FILE_LOCAL_STORE}), @@ -121,6 +121,12 @@ module Gitlab AND artifacts_file <> '' SQL end + + def add_missing_db_timezone + return '' unless Gitlab::Database.postgresql? + + 'at time zone \'UTC\'' + end end end end diff --git a/lib/gitlab/cycle_analytics/base_query.rb b/lib/gitlab/cycle_analytics/base_query.rb index 9c98c0bfbf2..459bb5177b5 100644 --- a/lib/gitlab/cycle_analytics/base_query.rb +++ b/lib/gitlab/cycle_analytics/base_query.rb @@ -19,9 +19,10 @@ module Gitlab .join(projects_table).on(issue_table[:project_id].eq(projects_table[:id])) .join(routes_table).on(projects_table[:namespace_id].eq(routes_table[:source_id])) .project(issue_table[:project_id].as("project_id")) - .where(issue_table[:project_id].in(project_ids)) - .where(routes_table[:source_type].eq('Namespace')) - .where(issue_table[:created_at].gteq(options[:from])) + .project(projects_table[:path].as("project_path")) + .project(routes_table[:path].as("namespace_path")) + + query = limit_query(query, project_ids) # Load merge_requests @@ -30,6 +31,12 @@ module Gitlab query end + def limit_query(query, project_ids) + query.where(issue_table[:project_id].in(project_ids)) + .where(routes_table[:source_type].eq('Namespace')) + .where(issue_table[:created_at].gteq(options[:from])) + end + def load_merge_requests(query) query.join(mr_table, Arel::Nodes::OuterJoin) .on(mr_table[:id].eq(mr_closing_issues_table[:merge_request_id])) diff --git a/lib/gitlab/cycle_analytics/code_event_fetcher.rb b/lib/gitlab/cycle_analytics/code_event_fetcher.rb index 1e4e9b9e02c..fcc282bf7a6 100644 --- a/lib/gitlab/cycle_analytics/code_event_fetcher.rb +++ b/lib/gitlab/cycle_analytics/code_event_fetcher.rb @@ -11,9 +11,7 @@ module Gitlab mr_table[:id], mr_table[:created_at], mr_table[:state], - mr_table[:author_id], - projects_table[:name], - routes_table[:path]] + mr_table[:author_id]] @order = mr_table[:created_at] super(*args) diff --git a/lib/gitlab/cycle_analytics/issue_event_fetcher.rb b/lib/gitlab/cycle_analytics/issue_event_fetcher.rb index 2d03e425a6a..6914cf24c19 100644 --- a/lib/gitlab/cycle_analytics/issue_event_fetcher.rb +++ b/lib/gitlab/cycle_analytics/issue_event_fetcher.rb @@ -10,9 +10,7 @@ module Gitlab issue_table[:iid], issue_table[:id], issue_table[:created_at], - issue_table[:author_id], - projects_table[:name], - routes_table[:path]] + issue_table[:author_id]] super(*args) end diff --git a/lib/gitlab/cycle_analytics/issue_helper.rb b/lib/gitlab/cycle_analytics/issue_helper.rb index 0fc4f1dd41a..295eca5edca 100644 --- a/lib/gitlab/cycle_analytics/issue_helper.rb +++ b/lib/gitlab/cycle_analytics/issue_helper.rb @@ -8,12 +8,19 @@ module Gitlab .join(projects_table).on(issue_table[:project_id].eq(projects_table[:id])) .join(routes_table).on(projects_table[:namespace_id].eq(routes_table[:source_id])) .project(issue_table[:project_id].as("project_id")) - .where(issue_table[:project_id].in(project_ids)) + .project(projects_table[:path].as("project_path")) + .project(routes_table[:path].as("namespace_path")) + + query = limit_query(query, project_ids) + + query + end + + def limit_query(query, project_ids) + query.where(issue_table[:project_id].in(project_ids)) .where(routes_table[:source_type].eq('Namespace')) .where(issue_table[:created_at].gteq(options[:from])) .where(issue_metrics_table[:first_added_to_board_at].not_eq(nil).or(issue_metrics_table[:first_associated_with_milestone_at].not_eq(nil))) - - query end end end diff --git a/lib/gitlab/cycle_analytics/plan_event_fetcher.rb b/lib/gitlab/cycle_analytics/plan_event_fetcher.rb index 77cc358daa9..bad02e00a13 100644 --- a/lib/gitlab/cycle_analytics/plan_event_fetcher.rb +++ b/lib/gitlab/cycle_analytics/plan_event_fetcher.rb @@ -10,9 +10,7 @@ module Gitlab issue_table[:iid], issue_table[:id], issue_table[:created_at], - issue_table[:author_id], - projects_table[:name], - routes_table[:path]] + issue_table[:author_id]] super(*args) end diff --git a/lib/gitlab/cycle_analytics/plan_helper.rb b/lib/gitlab/cycle_analytics/plan_helper.rb index c3f742503a9..a63ae58ad21 100644 --- a/lib/gitlab/cycle_analytics/plan_helper.rb +++ b/lib/gitlab/cycle_analytics/plan_helper.rb @@ -8,14 +8,16 @@ module Gitlab .join(projects_table).on(issue_table[:project_id].eq(projects_table[:id])) .join(routes_table).on(projects_table[:namespace_id].eq(routes_table[:source_id])) .project(issue_table[:project_id].as("project_id")) + .project(projects_table[:path].as("project_path")) + .project(routes_table[:path].as("namespace_path")) .where(issue_table[:project_id].in(project_ids)) .where(routes_table[:source_type].eq('Namespace')) - query = add_conditions_to_query(query) + query = limit_query(query) query end - def add_conditions_to_query(query) + def limit_query(query) query.where(issue_table[:created_at].gteq(options[:from])) .where(issue_metrics_table[:first_added_to_board_at].not_eq(nil).or(issue_metrics_table[:first_associated_with_milestone_at].not_eq(nil))) .where(issue_metrics_table[:first_mentioned_in_commit_at].not_eq(nil)) diff --git a/lib/gitlab/cycle_analytics/production_event_fetcher.rb b/lib/gitlab/cycle_analytics/production_event_fetcher.rb index 404b2460814..8843ab2bcb9 100644 --- a/lib/gitlab/cycle_analytics/production_event_fetcher.rb +++ b/lib/gitlab/cycle_analytics/production_event_fetcher.rb @@ -11,7 +11,6 @@ module Gitlab issue_table[:id], issue_table[:created_at], issue_table[:author_id], - projects_table[:name], routes_table[:path]] super(*args) diff --git a/lib/gitlab/cycle_analytics/review_event_fetcher.rb b/lib/gitlab/cycle_analytics/review_event_fetcher.rb index 6acd12517fa..4b5d79097b7 100644 --- a/lib/gitlab/cycle_analytics/review_event_fetcher.rb +++ b/lib/gitlab/cycle_analytics/review_event_fetcher.rb @@ -11,9 +11,7 @@ module Gitlab mr_table[:id], mr_table[:created_at], mr_table[:state], - mr_table[:author_id], - projects_table[:name], - routes_table[:path]] + mr_table[:author_id]] super(*args) end diff --git a/lib/peek/views/redis_detailed.rb b/lib/peek/views/redis_detailed.rb index b95307deddb..f36f581d5e9 100644 --- a/lib/peek/views/redis_detailed.rb +++ b/lib/peek/views/redis_detailed.rb @@ -16,6 +16,7 @@ module Gitlab private def add_call_details(duration, args) + return unless peek_enabled? # redis-rb passes an array (e.g. [:get, key]) return unless args.length == 1 @@ -26,6 +27,10 @@ module Gitlab } end + def peek_enabled? + Gitlab::SafeRequestStore.store[:peek_enabled] + end + def detail_store ::Gitlab::SafeRequestStore['redis_call_details'] ||= [] end diff --git a/package.json b/package.json index bf6000dc53d..4ff99f29e24 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "@babel/preset-env": "^7.4.4", "@gitlab/csslab": "^1.9.0", "@gitlab/svgs": "^1.67.0", - "@gitlab/ui": "5.12.1", + "@gitlab/ui": "5.14.0", "apollo-cache-inmemory": "^1.5.1", "apollo-client": "^2.5.1", "apollo-link": "^1.2.11", diff --git a/spec/factories/clusters/applications/helm.rb b/spec/factories/clusters/applications/helm.rb index 24c22ef3928..89f7bc15217 100644 --- a/spec/factories/clusters/applications/helm.rb +++ b/spec/factories/clusters/applications/helm.rb @@ -4,6 +4,20 @@ FactoryBot.define do factory :clusters_applications_helm, class: Clusters::Applications::Helm do cluster factory: %i(cluster provided_by_gcp) + before(:create) do + allow(Gitlab::Kubernetes::Helm::Certificate).to receive(:generate_root) + .and_return( + double( + key_string: File.read(Rails.root.join('spec/fixtures/clusters/sample_key.key')), + cert_string: File.read(Rails.root.join('spec/fixtures/clusters/sample_cert.pem')) + ) + ) + end + + after(:create) do + allow(Gitlab::Kubernetes::Helm::Certificate).to receive(:generate_root).and_call_original + end + trait :not_installable do status(-2) end diff --git a/spec/finders/container_repositories_finder_spec.rb b/spec/finders/container_repositories_finder_spec.rb new file mode 100644 index 00000000000..deec62d6598 --- /dev/null +++ b/spec/finders/container_repositories_finder_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ContainerRepositoriesFinder do + let(:group) { create(:group) } + let(:project) { create(:project, group: group) } + let(:project_repository) { create(:container_repository, project: project) } + + describe '#execute' do + let(:id) { nil } + + subject { described_class.new(id: id, container_type: container_type).execute } + + context 'when container_type is group' do + let(:other_project) { create(:project, group: group) } + + let(:other_repository) do + create(:container_repository, name: 'test_repository2', project: other_project) + end + + let(:container_type) { :group } + let(:id) { group.id } + + it { is_expected.to match_array([project_repository, other_repository]) } + end + + context 'when container_type is project' do + let(:container_type) { :project } + let(:id) { project.id } + + it { is_expected.to match_array([project_repository]) } + end + + context 'with invalid id' do + let(:container_type) { :project } + let(:id) { 123456789 } + + it 'raises an error' do + expect { subject.execute }.to raise_error(ActiveRecord::RecordNotFound) + end + end + end +end diff --git a/spec/fixtures/api/schemas/registry/repository.json b/spec/fixtures/api/schemas/registry/repository.json index e0fd4620c43..d0a068b65a7 100644 --- a/spec/fixtures/api/schemas/registry/repository.json +++ b/spec/fixtures/api/schemas/registry/repository.json @@ -17,6 +17,9 @@ "path": { "type": "string" }, + "project_id": { + "type": "integer" + }, "location": { "type": "string" }, @@ -28,7 +31,8 @@ }, "destroy_path": { "type": "string" - } + }, + "tags": { "$ref": "tags.json" } }, "additionalProperties": false } diff --git a/spec/fixtures/clusters/sample_key.key b/spec/fixtures/clusters/sample_key.key new file mode 100644 index 00000000000..4ddb20b0922 --- /dev/null +++ b/spec/fixtures/clusters/sample_key.key @@ -0,0 +1,9 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIBOgIBAAJBAMA5sXIBE0HwgIB40iNidN4PGWzOyLQK0bsdOBNgpEXkDlZBvnak +OUgAPF+rME4PB0Yl415DabUI40T5UNmlwxcCAwEAAQJAZtY2pSwIFm3JAXIh0cZZ +iXcAfiJ+YzuqinUOS+eW2sBCAEzjcARlU/o6sFQgtsOi4FOMczAd1Yx8UDMXMmrw +2QIhAPBgVhJiTF09pdmeFWutCvTJDlFFAQNbrbo2X2x/9WF9AiEAzLgqMKeStSRu +H9N16TuDrUoO8R+DPqriCwkKrSHaWyMCIFzMhE4inuKcSywBaLmiG4m3GQzs++Al +A6PRG/PSTpQtAiBxtBg6zdf+JC3GH3zt/dA0/10tL4OF2wORfYQghRzyYQIhAL2l +0ZQW+yLIZAGrdBFWYEAa52GZosncmzBNlsoTgwE4 +-----END RSA PRIVATE KEY-----
\ No newline at end of file diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 7e9bbf5a407..1c41ceb7deb 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -23,6 +23,7 @@ describe Group do it { is_expected.to have_many(:badges).class_name('GroupBadge') } it { is_expected.to have_many(:cluster_groups).class_name('Clusters::Group') } it { is_expected.to have_many(:clusters).class_name('Clusters::Cluster') } + it { is_expected.to have_many(:container_repositories) } describe '#members & #requesters' do let(:requester) { create(:user) } diff --git a/spec/requests/api/group_container_repositories_spec.rb b/spec/requests/api/group_container_repositories_spec.rb new file mode 100644 index 00000000000..0a41e455d01 --- /dev/null +++ b/spec/requests/api/group_container_repositories_spec.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe API::GroupContainerRepositories do + set(:group) { create(:group, :private) } + set(:project) { create(:project, :private, group: group) } + let(:reporter) { create(:user) } + let(:guest) { create(:user) } + + let(:root_repository) { create(:container_repository, :root, project: project) } + let(:test_repository) { create(:container_repository, project: project) } + + let(:users) do + { + anonymous: nil, + guest: guest, + reporter: reporter + } + end + + let(:api_user) { reporter } + + before do + group.add_reporter(reporter) + group.add_guest(guest) + + stub_feature_flags(container_registry_api: true) + stub_container_registry_config(enabled: true) + + root_repository + test_repository + end + + describe 'GET /groups/:id/registry/repositories' do + let(:url) { "/groups/#{group.id}/registry/repositories" } + + subject { get api(url, api_user) } + + it_behaves_like 'rejected container repository access', :guest, :forbidden + it_behaves_like 'rejected container repository access', :anonymous, :not_found + + it_behaves_like 'returns repositories for allowed users', :reporter, 'group' do + let(:object) { group } + end + + context 'with invalid group id' do + let(:url) { '/groups/123412341234/registry/repositories' } + + it 'returns not found' do + subject + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end +end diff --git a/spec/requests/api/container_registry_spec.rb b/spec/requests/api/project_container_repositories_spec.rb index b64f3ea1081..f1dc4e6f0b2 100644 --- a/spec/requests/api/container_registry_spec.rb +++ b/spec/requests/api/project_container_repositories_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::ContainerRegistry do +describe API::ProjectContainerRepositories do include ExclusiveLeaseHelpers set(:project) { create(:project, :private) } @@ -12,6 +12,16 @@ describe API::ContainerRegistry do let(:root_repository) { create(:container_repository, :root, project: project) } let(:test_repository) { create(:container_repository, project: project) } + let(:users) do + { + anonymous: nil, + developer: developer, + guest: guest, + maintainer: maintainer, + reporter: reporter + } + end + let(:api_user) { maintainer } before do @@ -27,57 +37,24 @@ describe API::ContainerRegistry do test_repository end - shared_examples 'being disallowed' do |param| - context "for #{param}" do - let(:api_user) { public_send(param) } - - it 'returns access denied' do - subject - - expect(response).to have_gitlab_http_status(:forbidden) - end - end - - context "for anonymous" do - let(:api_user) { nil } - - it 'returns not found' do - subject - - expect(response).to have_gitlab_http_status(:not_found) - end - end - end - describe 'GET /projects/:id/registry/repositories' do - subject { get api("/projects/#{project.id}/registry/repositories", api_user) } - - it_behaves_like 'being disallowed', :guest - - context 'for reporter' do - let(:api_user) { reporter } - - it 'returns a list of repositories' do - subject + let(:url) { "/projects/#{project.id}/registry/repositories" } - expect(json_response.length).to eq(2) - expect(json_response.map { |repository| repository['id'] }).to contain_exactly( - root_repository.id, test_repository.id) - end + subject { get api(url, api_user) } - it 'returns a matching schema' do - subject + it_behaves_like 'rejected container repository access', :guest, :forbidden + it_behaves_like 'rejected container repository access', :anonymous, :not_found - expect(response).to have_gitlab_http_status(:ok) - expect(response).to match_response_schema('registry/repositories') - end + it_behaves_like 'returns repositories for allowed users', :reporter, 'project' do + let(:object) { project } end end describe 'DELETE /projects/:id/registry/repositories/:repository_id' do subject { delete api("/projects/#{project.id}/registry/repositories/#{root_repository.id}", api_user) } - it_behaves_like 'being disallowed', :developer + it_behaves_like 'rejected container repository access', :developer, :forbidden + it_behaves_like 'rejected container repository access', :anonymous, :not_found context 'for maintainer' do let(:api_user) { maintainer } @@ -96,7 +73,8 @@ describe API::ContainerRegistry do describe 'GET /projects/:id/registry/repositories/:repository_id/tags' do subject { get api("/projects/#{project.id}/registry/repositories/#{root_repository.id}/tags", api_user) } - it_behaves_like 'being disallowed', :guest + it_behaves_like 'rejected container repository access', :guest, :forbidden + it_behaves_like 'rejected container repository access', :anonymous, :not_found context 'for reporter' do let(:api_user) { reporter } @@ -124,10 +102,13 @@ describe API::ContainerRegistry do describe 'DELETE /projects/:id/registry/repositories/:repository_id/tags' do subject { delete api("/projects/#{project.id}/registry/repositories/#{root_repository.id}/tags", api_user), params: params } - it_behaves_like 'being disallowed', :developer do + context 'disallowed' do let(:params) do { name_regex: 'v10.*' } end + + it_behaves_like 'rejected container repository access', :developer, :forbidden + it_behaves_like 'rejected container repository access', :anonymous, :not_found end context 'for maintainer' do @@ -191,7 +172,8 @@ describe API::ContainerRegistry do describe 'GET /projects/:id/registry/repositories/:repository_id/tags/:tag_name' do subject { get api("/projects/#{project.id}/registry/repositories/#{root_repository.id}/tags/rootA", api_user) } - it_behaves_like 'being disallowed', :guest + it_behaves_like 'rejected container repository access', :guest, :forbidden + it_behaves_like 'rejected container repository access', :anonymous, :not_found context 'for reporter' do let(:api_user) { reporter } @@ -222,7 +204,8 @@ describe API::ContainerRegistry do describe 'DELETE /projects/:id/registry/repositories/:repository_id/tags/:tag_name' do subject { delete api("/projects/#{project.id}/registry/repositories/#{root_repository.id}/tags/rootA", api_user) } - it_behaves_like 'being disallowed', :reporter + it_behaves_like 'rejected container repository access', :reporter, :forbidden + it_behaves_like 'rejected container repository access', :anonymous, :not_found context 'for developer' do let(:api_user) { developer } diff --git a/spec/serializers/analytics_issue_entity_spec.rb b/spec/serializers/analytics_issue_entity_spec.rb index dd5e43a4b62..c5b03bdd8c1 100644 --- a/spec/serializers/analytics_issue_entity_spec.rb +++ b/spec/serializers/analytics_issue_entity_spec.rb @@ -10,12 +10,12 @@ describe AnalyticsIssueEntity do id: "1", created_at: "2016-11-12 15:04:02.948604", author: user, - name: project.name, - path: project.namespace + project_path: project.path, + namespace_path: project.namespace.route.path } end - let(:project) { create(:project) } + let(:project) { create(:project, name: 'my project') } let(:request) { EntityRequest.new(entity: :merge_request) } let(:entity) do diff --git a/spec/serializers/analytics_issue_serializer_spec.rb b/spec/serializers/analytics_issue_serializer_spec.rb index c9ffe1c5dad..9cb2ce13d12 100644 --- a/spec/serializers/analytics_issue_serializer_spec.rb +++ b/spec/serializers/analytics_issue_serializer_spec.rb @@ -8,7 +8,7 @@ describe AnalyticsIssueSerializer do end let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, name: 'my project') } let(:resource) do { total_time: "172802.724419", @@ -17,8 +17,8 @@ describe AnalyticsIssueSerializer do id: "1", created_at: "2016-11-12 15:04:02.948604", author: user, - name: project.name, - path: project.namespace + project_path: project.path, + namespace_path: project.namespace.route.path } end diff --git a/spec/serializers/analytics_merge_request_serializer_spec.rb b/spec/serializers/analytics_merge_request_serializer_spec.rb index 123d7d795ce..a864051b2a3 100644 --- a/spec/serializers/analytics_merge_request_serializer_spec.rb +++ b/spec/serializers/analytics_merge_request_serializer_spec.rb @@ -8,7 +8,7 @@ describe AnalyticsMergeRequestSerializer do end let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, name: 'my project') } let(:resource) do { total_time: "172802.724419", @@ -18,8 +18,8 @@ describe AnalyticsMergeRequestSerializer do state: 'open', created_at: "2016-11-12 15:04:02.948604", author: user, - name: project.name, - path: project.namespace + project_path: project.path, + namespace_path: project.namespace.route.path } end diff --git a/spec/support/shared_contexts/policies/group_policy_shared_context.rb b/spec/support/shared_contexts/policies/group_policy_shared_context.rb index c11725c63d2..fd24c443288 100644 --- a/spec/support/shared_contexts/policies/group_policy_shared_context.rb +++ b/spec/support/shared_contexts/policies/group_policy_shared_context.rb @@ -16,7 +16,7 @@ RSpec.shared_context 'GroupPolicy context' do read_group_merge_requests ] end - let(:reporter_permissions) { [:admin_label] } + let(:reporter_permissions) { %i[admin_label read_container_image] } let(:developer_permissions) { [:admin_milestone] } let(:maintainer_permissions) do %i[ diff --git a/spec/support/shared_examples/container_repositories_shared_examples.rb b/spec/support/shared_examples/container_repositories_shared_examples.rb new file mode 100644 index 00000000000..946b130fca2 --- /dev/null +++ b/spec/support/shared_examples/container_repositories_shared_examples.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +shared_examples 'rejected container repository access' do |user_type, status| + context "for #{user_type}" do + let(:api_user) { users[user_type] } + + it "returns #{status}" do + subject + + expect(response).to have_gitlab_http_status(status) + end + end +end + +shared_examples 'returns repositories for allowed users' do |user_type, scope| + context "for #{user_type}" do + it 'returns a list of repositories' do + subject + + expect(json_response.length).to eq(2) + expect(json_response.map { |repository| repository['id'] }).to contain_exactly( + root_repository.id, test_repository.id) + expect(response.body).not_to include('tags') + end + + it 'returns a matching schema' do + subject + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('registry/repositories') + end + + context 'with tags param' do + let(:url) { "/#{scope}s/#{object.id}/registry/repositories?tags=true" } + + before do + stub_container_registry_tags(repository: root_repository.path, tags: %w(rootA latest), with_manifest: true) + stub_container_registry_tags(repository: test_repository.path, tags: %w(rootA latest), with_manifest: true) + end + + it 'returns a list of repositories and their tags' do + subject + + expect(json_response.length).to eq(2) + expect(json_response.map { |repository| repository['id'] }).to contain_exactly( + root_repository.id, test_repository.id) + expect(response.body).to include('tags') + end + + it 'returns a matching schema' do + subject + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('registry/repositories') + end + end + end +end diff --git a/yarn.lock b/yarn.lock index d8193af1310..acf1a4d8652 100644 --- a/yarn.lock +++ b/yarn.lock @@ -996,10 +996,10 @@ resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.67.0.tgz#c7b94eca13b99fd3aaa737fb6dcc0abc41d3c579" integrity sha512-hJOmWEs6RkjzyKkb1vc9wwKGZIBIP0coHkxu/KgOoxhBVudpGk4CH7xJ6UuB2TKpb0SEh5CC1CzRZfBYaFhsaA== -"@gitlab/ui@5.12.1": - version "5.12.1" - resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-5.12.1.tgz#70035747cec96a729e012924ab2d3e3b6067a558" - integrity sha512-W4rvZj2Fab1UpXR0Wyi7wSvj+5Ko+TWHibC/q/FSRHMsbeSLq77lljd7rQWeXXNMBvEKwr4NqSmckWsjaSOLfw== +"@gitlab/ui@5.14.0": + version "5.14.0" + resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-5.14.0.tgz#850214cfc6bb57f7ce672dc1cc60ea3d39ad41f3" + integrity sha512-JXUmk+hT4Rj2GBh0xAF43dYeloBEDX22rgeaDU6/RzD3JEA353yEm2+HOsBjPkQFDAh6Zp7OZSBuhDFrQe8sbg== dependencies: "@babel/standalone" "^7.0.0" "@gitlab/vue-toasted" "^1.2.1" |