Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-04-30 21:09:47 +00:00
parent adafb996ef
commit 3aeda4e614
39 changed files with 920 additions and 221 deletions

View File

@ -1,5 +1,14 @@
Please view this file on the master branch, on stable branches it's out of date.
## 12.10.2 (2020-04-30)
### Security (3 changes)
- Fix rendering failure of Audit Event generated by Releases API.
- Ensure that NuGet package versions are SemVer compliant.
- Ensure that NuGet package versions are validated before updating the stored file path.
## 12.10.1 (2020-04-24)
### Changed (1 change)
@ -50,6 +59,15 @@ Please view this file on the master branch, on stable branches it's out of date.
- Add health status counts to usage data. !28964
## 12.9.5 (2020-04-30)
### Security (3 changes)
- Fix rendering failure of Audit Event generated by Releases API.
- Ensure that NuGet package versions are SemVer compliant.
- Ensure that NuGet package versions are validated before updating the stored file path.
## 12.9.4 (2020-04-16)
- No changes.
@ -222,6 +240,15 @@ Please view this file on the master branch, on stable branches it's out of date.
- Allow users to be marked as service users. !202680
## 12.8.10 (2020-04-30)
### Security (3 changes)
- Fix rendering failure of Audit Event generated by Releases API.
- Ensure that NuGet package versions are SemVer compliant.
- Ensure that NuGet package versions are validated before updating the stored file path.
## 12.8.9 (2020-04-14)
### Security (1 change)

View File

@ -0,0 +1,37 @@
<script>
import { GlTabs, GlTab } from '@gitlab/ui';
import { s__ } from '~/locale';
export default {
i18n: {
fullAlertDetailsTitle: s__('AlertManagement|Full Alert Details'),
overviewTitle: s__('AlertManagement|Overview'),
},
components: {
GlTab,
GlTabs,
},
};
</script>
<template>
<div>
<div class="d-flex justify-content-between">
<gl-tabs>
<gl-tab data-testid="overviewTab" :title="$options.i18n.overviewTitle">
<ul class="pl-3">
<li data-testid="startTimeItem" class="font-weight-bold mb-3 mt-2">
{{ s__('AlertManagement|Start time:') }}
</li>
<li class="font-weight-bold my-3">
{{ s__('AlertManagement|End time:') }}
</li>
<li class="font-weight-bold my-3">
{{ s__('AlertManagement|Events:') }}
</li>
</ul>
</gl-tab>
<gl-tab data-testid="fullDetailsTab" :title="$options.i18n.fullAlertDetailsTitle" />
</gl-tabs>
</div>
</div>
</template>

View File

@ -0,0 +1,15 @@
import Vue from 'vue';
import AlertDetails from './components/alert_details.vue';
export default selector => {
// eslint-disable-next-line no-new
new Vue({
el: selector,
components: {
AlertDetails,
},
render(createElement) {
return createElement('alert-details', {});
},
});
};

View File

@ -24,13 +24,24 @@ let mermaidModule = {};
function importMermaidModule() {
return import(/* webpackChunkName: 'mermaid' */ 'mermaid')
.then(mermaid => {
let theme = 'neutral';
if (
window.gon?.user_color_scheme === 'dark' &&
window.gon?.features?.webideDarkTheme &&
// if on the Web IDE page
document.querySelector('.ide')
) {
theme = 'dark';
}
mermaid.initialize({
// mermaid core options
mermaid: {
startOnLoad: false,
},
// mermaidAPI options
theme: 'neutral',
theme,
flowchart: {
useMaxWidth: true,
htmlLabels: false,

View File

@ -552,6 +552,7 @@ export default {
<dashboard-panel
v-show="expandedPanel.panel"
ref="expandedPanel"
:settings-path="settingsPath"
:clipboard-text="generatePanelLink(expandedPanel.group, expandedPanel.panel)"
:graph-data="expandedPanel.panel"
:alerts-endpoint="alertsEndpoint"
@ -610,6 +611,7 @@ export default {
</div>
<dashboard-panel
:settings-path="settingsPath"
:clipboard-text="generatePanelLink(groupData.group, graphData)"
:graph-data="graphData"
:alerts-endpoint="alertsEndpoint"

View File

@ -82,6 +82,11 @@ export default {
required: false,
default: false,
},
settingsPath: {
type: String,
required: false,
default: null,
},
},
data() {
return {
@ -196,6 +201,9 @@ export default {
return Boolean(this.graphDataHasResult && !this.basicChartComponent);
},
editCustomMetricLink() {
if (this.graphData.metrics.length > 1) {
return this.settingsPath;
}
return this.graphData?.metrics[0].edit_path;
},
editCustomMetricLinkText() {

View File

@ -0,0 +1,5 @@
import AlertDetails from '~/alert_management/details';
document.addEventListener('DOMContentLoaded', () => {
AlertDetails('#js-alert_details');
});

View File

@ -27,6 +27,9 @@
$btn-disabled-border: rgba(223, 223, 223, 0.24);
$btn-disabled-color: rgba(145, 145, 145, 0.48);
$diff-insert: rgba(155, 185, 85, 0.2);
$diff-remove: rgba(255, 0, 0, 0.2);
a {
color: $link-color;
}
@ -37,6 +40,8 @@
h4:not(.modal-title),
h5,
h6,
code,
.md table:not(.code),
.md,
.md p,
.ide-view,
@ -86,6 +91,7 @@
background-color: transparent;
}
code,
.multi-file-commit-panel,
.multi-file-tabs,
.multi-file-tabs li,
@ -102,6 +108,10 @@
background-color: $background;
}
pre code {
background-color: inherit;
}
.ide-sidebar-link:hover {
background-color: $background-hover;
}
@ -111,6 +121,7 @@
}
&,
.md table:not(.code) tr th,
.multi-file-commit-panel-inner-content,
.multi-file-commit-form,
.multi-file-tabs li.active,
@ -141,6 +152,12 @@
border-color: $border-color;
}
.md h1,
.md h2,
.md blockquote,
pre,
.md table:not(.code) tbody td,
.md table:not(.code) tr th,
.multi-file-commit-form > .commit-form-compact,
.ide-tree-header,
.multi-file-commit-panel-header,
@ -267,6 +284,7 @@
}
.md-previewer,
.md table:not(.code) tbody,
.ide-empty-state {
background-color: $border-color;
}
@ -289,6 +307,14 @@
}
}
}
.idiff.addition {
background-color: $diff-insert;
}
.idiff.deletion {
background-color: $diff-remove;
}
}
.navbar.theme-dark {

View File

@ -18,20 +18,19 @@ class Clusters::ClustersController < Clusters::BaseController
STATUS_POLLING_INTERVAL = 10_000
def index
finder = ClusterAncestorsFinder.new(clusterable.subject, current_user)
clusters = finder.execute
@clusters = cluster_list
# Note: We are paginating through an array here but this should OK as:
#
# In CE, we can have a maximum group nesting depth of 21, so including
# project cluster, we can have max 22 clusters for a group hierarchy.
# In EE (Premium) we can have any number, as multiple clusters are
# supported, but the number of clusters are fairly low currently.
#
# See https://gitlab.com/gitlab-org/gitlab-foss/issues/55260 also.
@clusters = Kaminari.paginate_array(clusters).page(params[:page]).per(20)
respond_to do |format|
format.html
format.json do
serializer = ClusterSerializer.new(current_user: current_user)
@has_ancestor_clusters = finder.has_ancestor_clusters?
render json: {
clusters: serializer.with_pagination(request, response).represent_list(@clusters),
has_ancestor_clusters: @has_ancestor_clusters
}
end
end
end
def new
@ -158,6 +157,23 @@ class Clusters::ClustersController < Clusters::BaseController
private
def cluster_list
finder = ClusterAncestorsFinder.new(clusterable.subject, current_user)
clusters = finder.execute
@has_ancestor_clusters = finder.has_ancestor_clusters?
# Note: We are paginating through an array here but this should OK as:
#
# In CE, we can have a maximum group nesting depth of 21, so including
# project cluster, we can have max 22 clusters for a group hierarchy.
# In EE (Premium) we can have any number, as multiple clusters are
# supported, but the number of clusters are fairly low currently.
#
# See https://gitlab.com/gitlab-org/gitlab-foss/issues/55260 also.
Kaminari.paginate_array(clusters).page(params[:page]).per(20)
end
def destroy_params
params.permit(:cleanup)
end

View File

@ -5,6 +5,13 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
layout 'profile'
def index
respond_to do |format|
format.html { render "errors/not_found", layout: "errors", status: :not_found }
format.json { render json: "", status: :not_found }
end
end
def destroy
if params[:token_id].present?
current_resource_owner.oauth_authorized_tokens.find(params[:token_id]).revoke

View File

@ -626,6 +626,7 @@ module ProjectsHelper
def find_file_path
return unless @project && !@project.empty_repo?
return unless can?(current_user, :download_code, @project)
ref = @ref || @project.repository.root_ref

View File

@ -3,7 +3,16 @@
class ClusterEntity < Grape::Entity
include RequestAwareEntity
expose :cluster_type
expose :enabled
expose :environment_scope
expose :name
expose :status_name, as: :status
expose :status_reason
expose :path do |cluster|
Clusters::ClusterPresenter.new(cluster).show_path # rubocop: disable CodeReuse/Presenter
end
expose :applications, using: ClusterApplicationEntity
end

View File

@ -1,8 +1,22 @@
# frozen_string_literal: true
class ClusterSerializer < BaseSerializer
include WithPagination
entity ClusterEntity
def represent_list(resource)
represent(resource, {
only: [
:cluster_type,
:enabled,
:environment_scope,
:name,
:path,
:status
]
})
end
def represent_status(resource)
represent(resource, { only: [:status, :status_reason, :applications] })
end

View File

@ -2,7 +2,7 @@
class RemoteMirrorEntity < Grape::Entity
expose :id
expose :url
expose :safe_url, as: :url
expose :enabled
expose :auth_method

View File

@ -1 +1,3 @@
- page_title _('Alert Details')
#js-alert_details

View File

@ -0,0 +1,5 @@
---
title: Multiple metrics edit navigates to prom edit page
merge_request: 30666
author:
type: added

View File

@ -131,6 +131,7 @@ module Gitlab
encrypted_key
hook
import_url
elasticsearch_url
otp_attempt
sentry_dsn
trace

View File

@ -331,3 +331,82 @@ module Projects
wiki_repo_saver, lfs_saver].all?(&:save)
end
```
## Test fixtures
Fixtures used in Import/Export specs live in `spec/fixtures/lib/gitlab/import_export`. There are both Project and Group fixtures.
There are two versions of each of these fixtures:
- A human readable single JSON file with all objects, called either `project.json` or `group.json`.
- A tree.tar.gz file containing a tree of files in `ndjson` format. **Please do not edit this file manually unless strictly necessary.**
The tools to generate the NDJSON tree from the human-readable JSON files live in the [`gitlab-org/memory-team/team-tools`](https://gitlab.com/gitlab-org/memory-team/team-tools/-/blob/master/import-export/) project.
### Project
**Please use `legacy-project-json-to-ndjson.sh` to generate the NDJSON tree.**
Once you're done generating the files, please package them using `tar -czf tree.tar.gz tree` from the same directory as the `tree` directory generated is located.
The NDJSON tree will look like this:
```shell
tree
├── project
   ├── auto_devops.ndjson
│   ├── boards.ndjson
│   ├── ci_cd_settings.ndjson
│   ├── ci_pipelines.ndjson
│   ├── container_expiration_policy.ndjson
│   ├── custom_attributes.ndjson
│   ├── error_tracking_setting.ndjson
│   ├── external_pull_requests.ndjson
│   ├── issues.ndjson
│   ├── labels.ndjson
│   ├── merge_requests.ndjson
│   ├── milestones.ndjson
│   ├── pipeline_schedules.ndjson
│   ├── project_badges.ndjson
│   ├── project_feature.ndjson
│   ├── project_members.ndjson
│   ├── protected_branches.ndjson
│   ├── protected_tags.ndjson
│   ├── releases.ndjson
│   ├── services.ndjson
│   ├── snippets.ndjson
│   └── triggers.ndjson
└── project.json
```
### Group
**Please use `legacy-group-json-to-ndjson.rb` to generate the NDJSON tree.** This script can be found in [`gitlab-org/memory-team/team-tools!7`](https://gitlab.com/gitlab-org/memory-team/team-tools/-/merge_requests/7).
Once this MR is merged, the script will be found in the directory mentioned earlier.
Once you're done generating the files, please package them using `tar -czf tree.tar.gz tree` from the same directory as the `tree` directory generated is located.
The NDJSON tree will look like this:
```shell
tree
└── groups
├── 4351
│   ├── badges.ndjson
│   ├── boards.ndjson
│   ├── epics.ndjson
│   ├── labels.ndjson
│   ├── members.ndjson
│   └── milestones.ndjson
├── 4352
│   ├── badges.ndjson
│   ├── boards.ndjson
│   ├── epics.ndjson
│   ├── labels.ndjson
│   ├── members.ndjson
│   └── milestones.ndjson
├── _all.ndjson
├── 4351.json
└── 4352.json
```
CAUTION: **Caution:** When updating these fixtures, please ensure you update the `json` files and the `tar.gz` archives, as the tests apply to both.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

View File

@ -1,119 +1,156 @@
# Jenkins CI service **(STARTER)**
>**Note:**
In GitLab 8.3, Jenkins integration using the
[GitLab Hook Plugin](https://wiki.jenkins.io/display/JENKINS/GitLab+Hook+Plugin)
was deprecated in favor of the
[GitLab Plugin](https://wiki.jenkins.io/display/JENKINS/GitLab+Plugin).
The deprecated integration has been renamed to [Jenkins CI (Deprecated)](jenkins_deprecated.md) in the
integration settings. We may remove this in a future release and recommend
using the new 'Jenkins CI' integration instead which is described in this
document.
From GitLab, you can trigger a Jenkins build when you push code to a repository, or when a merge
request is created. In return, Jenkins shows the pipeline status on merge requests widgets and
on the GitLab project's home page.
## Overview
To better understand GitLab's Jenkins integration, watch the following videos:
[Jenkins](https://jenkins.io/) is a great Continuous Integration tool, similar to our built-in
[GitLab CI/CD](../ci/README.md).
- [GitLab workflow with Jira issues and Jenkins pipelines](https://youtu.be/Jn-_fyra7xQ)
- [Migrating from Jenkins to GitLab](https://www.youtube.com/watch?v=RlEVGOpYF5Y)
GitLab's Jenkins integration allows you to trigger a Jenkins build when you
push code to a repository, or when a merge request is created. Additionally,
it shows the pipeline status on merge requests widgets and on the project's home page.
Use the Jenkins integration with GitLab when:
Videos are also available on [GitLab workflow with Jira issues and Jenkins pipelines](https://youtu.be/Jn-_fyra7xQ)
and [Migrating from Jenkins to GitLab](https://www.youtube.com/watch?v=RlEVGOpYF5Y).
## Use cases
- Suppose you are new to GitLab, and want to keep using Jenkins until you prepare
your projects to build with [GitLab CI/CD](../ci/README.md). You set up the
integration between GitLab and Jenkins, then you migrate to GitLab CI/CD later. While
you organize yourself and your team to onboard GitLab, you keep your pipelines
running with Jenkins, but view the results in your project's repository in GitLab.
- Your team uses [Jenkins Plugins](https://plugins.jenkins.io/) for other proceedings,
therefore, you opt for keep using Jenkins to build your apps. Show the results of your
pipelines directly in GitLab.
- You plan to migrate your CI from Jenkins to [GitLab CI/CD](../ci/README.md) in the future, but
need an interim solution.
- You're invested in [Jenkins Plugins](https://plugins.jenkins.io/) and choose to keep using Jenkins
to build your apps.
For a real use case, read the blog post [Continuous integration: From Jenkins to GitLab using Docker](https://about.gitlab.com/blog/2017/07/27/docker-my-precious/).
NOTE: **Moving from a traditional CI plug-in to a single application for the entire software development lifecycle can decrease hours spent on maintaining toolchains by 10% or more.**
Visit the ['GitLab vs. Jenkins' comparison page](https://about.gitlab.com/devops-tools/jenkins-vs-gitlab.html) to learn how our built-in CI compares to Jenkins.
Moving from a traditional CI plug-in to a single application for the entire software development
life cycle can decrease hours spent on maintaining toolchains by 10% or more. For more details, see
the ['GitLab vs. Jenkins' comparison page](https://about.gitlab.com/devops-tools/jenkins-vs-gitlab.html).
## Requirements
## Configure GitLab integration with Jenkins
- [Jenkins GitLab Plugin](https://wiki.jenkins.io/display/JENKINS/GitLab+Plugin)
- [Jenkins Git Plugin](https://wiki.jenkins.io/display/JENKINS/Git+Plugin)
- Git clone access for Jenkins from the GitLab repository
- GitLab API access to report build status
GitLab's Jenkins integration requires installation and configuration in both GitLab and Jenkins.
In GitLab, you need to grant Jenkins access to the relevant projects. In Jenkins, you need to
install and configure several plugins.
## Configure GitLab users
### GitLab requirements
Create a user or choose an existing user that Jenkins will use to interact
through the GitLab API. This user will need to be a global Admin or added
as a member to each Group/Project. Developer permission is required for reporting
build status. This is because a successful build status can trigger a merge
when 'Merge when pipeline succeeds' feature is used. Some features of the GitLab
Plugin may require additional privileges. For example, there is an option to
accept a merge request if the build is successful. Using this feature would
require developer, maintainer or owner-level permission.
- [Grant Jenkins permission to GitLab project](#grant-jenkins-access-to-gitlab-project)
- [Configure GitLab API access](#configure-gitlab-api-access)
- [Configure the GitLab project](#configure-the-gitlab-project)
Copy the private API token from **Profile Settings -> Account**. You will need this
when configuring the Jenkins server later.
### Jenkins requirements
- [Configure the Jenkins server](#configure-the-jenkins-server)
- [Configure the Jenkins project](#configure-the-jenkins-project)
## Grant Jenkins access to GitLab project
Grant a GitLab user access to the select GitLab projects.
1. Create a new GitLab user, or choose an existing GitLab user.
This account will be used by Jenkins to access the GitLab projects. We recommend creating a GitLab
user for only this purpose. If you use a person's account, and their account is deactivated or
deleted, the GitLab-Jenkins integration will stop working.
1. Grant the user permission to the GitLab projects.
If you're integrating Jenkins with many GitLab projects, consider granting the user global
Admin permission. Otherwise, add the user to each project, and grant Developer permission.
## Configure GitLab API access
Create a personal access token to authorize Jenkins' access to GitLab.
1. Log in to GitLab as the user to be used with Jenkins.
1. Click your avatar, then **Settings.
1. Click **Access Tokens** in the sidebar.
1. Create a personal access token with the **API** scope checkbox checked. For more details, see
[Personal access tokens](../user/profile/personal_access_tokens.md).
1. Record the personal access token's value, because it's required in [Configure the Jenkins server](#configure-the-jenkins-server).
## Configure the Jenkins server
Install [Jenkins GitLab Plugin](https://wiki.jenkins.io/display/JENKINS/GitLab+Plugin)
and [Jenkins Git Plugin](https://wiki.jenkins.io/display/JENKINS/Git+Plugin).
Install and configure the Jenkins plugins. Both plugins must be installed and configured to
authorize the connection to GitLab.
Go to Manage Jenkins -> Configure System and scroll down to the 'GitLab' section.
Enter the GitLab server URL in the 'GitLab host URL' field and paste the API token
copied earlier in the 'API Token' field.
1. On the Jenkins server, go to **Manage Jenkins > Manage Plugins**.
1. Install the [Jenkins GitLab Plugin](https://wiki.jenkins.io/display/JENKINS/GitLab+Plugin).
1. Go to **Manage Jenkins > Configure System**.
1. In the **GitLab** section, check the **Enable authentication for /project end-point** checkbox.
1. Click **Add**, then choose **Jenkins Credential Provider**.
1. Choose **GitLab API token** as the token type.
1. Enter the GitLab personal access token's value in the **API Token** field and click **Add**.
1. Enter the GitLab server's URL in the **GitLab host URL** field.
1. Click **Test Connection**, ensuring the connection is successful before proceeding.
For more information, see GitLab Plugin documentation about
[Jenkins-to-GitLab authentication](https://github.com/jenkinsci/gitlab-plugin#jenkins-to-gitlab-authentication)
[Jenkins-to-GitLab authentication](https://github.com/jenkinsci/gitlab-plugin#jenkins-to-gitlab-authentication).
![Jenkins GitLab plugin configuration](img/jenkins_gitlab_plugin_config.png)
## Configure a Jenkins project
## Configure the Jenkins project
Follow the GitLab Plugin documentation about [Jenkins Job Configuration](https://github.com/jenkinsci/gitlab-plugin#jenkins-job-configuration).
Set up the Jenkins project youre going to run your build on.
NOTE: **Note:**
Be sure to include the steps about [Build status configuration](https://github.com/jenkinsci/gitlab-plugin#build-status-configuration).
The 'Publish build status to GitLab' post-build step is required to view
Jenkins build status in GitLab Merge Requests.
1. On your Jenkins instance, go to **New Item**.
1. Enter the project's name.
1. Choose between **Freestyle** or **Pipeline** and click **OK**.
We recommend a Freestyle project, because the Jenkins plugin will update the build status on
GitLab. In a Pipeline project, you must configure a script to update the status on GitLab.
1. Choose your GitLab connection from the dropdown.
1. Check the **Build when a change is pushed to GitLab** checkbox.
1. Check the following checkboxes:
- **Accepted Merge Request Events**
- **Closed Merge Request Events**
1. If you created a **Freestyle** project, choose Publish build status to GitLab in the Post-build Actions section.
If you created a **Pipeline** project, you must use a pipeline script to update the status on
GitLab. The following is an example pipeline script:
## Configure a GitLab project
```plaintext
pipeline {
agent any
Create a new GitLab project or choose an existing one. Then, go to **Integrations ->
Jenkins CI**.
stages {
stage('gitlab') {
steps {
echo 'Notify GitLab'
updateGitlabCommitStatus name: 'build', state: 'pending'
updateGitlabCommitStatus name: 'build', state: 'success'
}
}
}
}
```
Check the 'Active' box. Select whether you want GitLab to trigger a build
on push, Merge Request creation, tag push, or any combination of these. We
recommend unchecking 'Merge Request events' unless you have a specific use-case
that requires re-building a commit when a merge request is created. With 'Push
events' selected, GitLab will build the latest commit on each push and the build
status will be displayed in the merge request.
## Configure the GitLab project
Enter the Jenkins URL and Project name. The project name should be URL-friendly
where spaces are replaced with underscores. To be safe, copy the project name
from the URL bar of your browser while viewing the Jenkins project.
Configure the GitLab integration with Jenkins.
Optionally, enter a username and password if your Jenkins server requires
authentication.
1. Create a new GitLab project or choose an existing one.
1. Go to **Settings > Integrations**, then select **Jenkins CI**.
1. Turn on the **Active** toggle.
1. Select the events you want GitLab to trigger a Jenkins build for:
- Push
- Merge request
- Tag push
1. Enter the **Jenkins URL**.
1. Enter the **Project name**.
![GitLab service settings](img/jenkins_gitlab_service_settings.png)
The project name should be URL-friendly, where spaces are replaced with underscores. To ensure
the project name is valid, copy it from your browser's address bar while viewing the Jenkins
project.
1. Enter the **Username** and **Password** if your Jenkins server requires
authentication.
1. Click **Test settings and save changes**. GitLab tests the connection to Jenkins.
## Plugin functional overview
GitLab does not contain a database table listing commits. Commits are always
read from the repository directly. Therefore, it is not possible to retain the
read from the repository directly. Therefore, it's not possible to retain the
build status of a commit in GitLab. This is overcome by requesting build
information from the integrated CI tool. The CI tool is responsible for creating
and storing build status for Commits and Merge Requests.
### Steps required to implement a similar integration
>**Note:**
**Note:**
All steps are implemented using AJAX requests on the merge request page.
1. In order to display the build status in a merge request you must create a project service in GitLab.
@ -133,7 +170,7 @@ receive a build status update via the API. Either Jenkins was not properly
configured or there was an error reporting the status via the API.
1. [Configure the Jenkins server](#configure-the-jenkins-server) for GitLab API access
1. [Configure a Jenkins project](#configure-a-jenkins-project), including the
1. [Configure the Jenkins project](#configure-the-jenkins-project), including the
'Publish build status to GitLab' post-build action.
### Merge Request event does not trigger a Jenkins Pipeline

View File

@ -97,6 +97,15 @@ module API
handle_api_exception(exception)
end
# This is a specific exception raised by `rack-timeout` gem when Puma
# requests surpass its timeout. Given it inherits from Exception, we
# should rescue it separately. For more info, see:
# - https://github.com/sharpstone/rack-timeout/blob/master/doc/exceptions.md
# - https://github.com/ruby-grape/grape#exception-handling
rescue_from Rack::Timeout::RequestTimeoutException do |exception|
handle_api_exception(exception)
end
format :json
content_type :txt, "text/plain"

View File

@ -43,11 +43,13 @@ module Gitlab
raise "unexpected field: #{field.inspect}" unless parsed_field.count == 1
key, value = parsed_field.first
if value.nil?
value = open_file(@request.params, key)
if value.nil? # we have a top level param, eg. field = 'foo' and not 'foo[bar]'
raise "invalid field: #{field.inspect}" if field != key
value = open_file(@request.params, key, tmp_path.presence)
@open_files << value
else
value = decorate_params_value(value, @request.params[key])
value = decorate_params_value(value, @request.params[key], tmp_path.presence)
end
update_param(key, value)
@ -59,7 +61,7 @@ module Gitlab
end
# This function calls itself recursively
def decorate_params_value(path_hash, value_hash)
def decorate_params_value(path_hash, value_hash, path_override = nil)
unless path_hash.is_a?(Hash) && path_hash.count == 1
raise "invalid path: #{path_hash.inspect}"
end
@ -72,19 +74,19 @@ module Gitlab
case path_value
when nil
value_hash[path_key] = open_file(value_hash.dig(path_key), '')
value_hash[path_key] = open_file(value_hash.dig(path_key), '', path_override)
@open_files << value_hash[path_key]
value_hash
when Hash
decorate_params_value(path_value, value_hash[path_key])
decorate_params_value(path_value, value_hash[path_key], path_override)
value_hash
else
raise "unexpected path value: #{path_value.inspect}"
end
end
def open_file(params, key)
::UploadedFile.from_params(params, key, allowed_paths)
def open_file(params, key, path_override = nil)
::UploadedFile.from_params(params, key, allowed_paths, path_override)
end
# update_params ensures that both rails controllers and rack middleware can find

View File

@ -42,13 +42,14 @@ class UploadedFile
@remote_id = remote_id
end
def self.from_params(params, field, upload_paths)
path = params["#{field}.path"]
def self.from_params(params, field, upload_paths, path_override = nil)
path = path_override || params["#{field}.path"]
remote_id = params["#{field}.remote_id"]
return if path.blank? && remote_id.blank?
file_path = nil
if path.present?
if remote_id.present? # don't use file_path if remote_id is set
file_path = nil
elsif path.present?
file_path = File.realpath(path)
paths = Array(upload_paths) << Dir.tmpdir

View File

@ -1704,9 +1704,18 @@ msgstr ""
msgid "AlertManagement|End time"
msgstr ""
msgid "AlertManagement|End time:"
msgstr ""
msgid "AlertManagement|Events"
msgstr ""
msgid "AlertManagement|Events:"
msgstr ""
msgid "AlertManagement|Full Alert Details"
msgstr ""
msgid "AlertManagement|More information"
msgstr ""
@ -1716,12 +1725,18 @@ msgstr ""
msgid "AlertManagement|No alerts to display."
msgstr ""
msgid "AlertManagement|Overview"
msgstr ""
msgid "AlertManagement|Severity"
msgstr ""
msgid "AlertManagement|Start time"
msgstr ""
msgid "AlertManagement|Start time:"
msgstr ""
msgid "AlertManagement|Status"
msgstr ""

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
context 'Create', :smoke, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/215031', type: :investigating } do
context 'Create', :smoke do
describe 'Snippet creation' do
it 'User creates a snippet' do
Flow::Login.sign_in

View File

@ -27,7 +27,7 @@ describe Admin::ClustersController do
create(:cluster, :disabled, :provided_by_gcp, :production_environment, :instance)
end
it 'lists available clusters' do
it 'lists available clusters and displays html' do
get_index
expect(response).to have_gitlab_http_status(:ok)
@ -35,20 +35,39 @@ describe Admin::ClustersController do
expect(assigns(:clusters)).to match_array([enabled_cluster, disabled_cluster])
end
it 'lists available clusters and renders json serializer' do
get_index(format: :json)
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('cluster_list')
end
context 'when page is specified' do
let(:last_page) { Clusters::Cluster.instance_type.page.total_pages }
let(:total_count) { Clusters::Cluster.instance_type.page.total_count }
before do
allow(Clusters::Cluster).to receive(:paginates_per).and_return(1)
create_list(:cluster, 2, :provided_by_gcp, :production_environment, :instance)
create_list(:cluster, 30, :provided_by_gcp, :production_environment, :instance)
end
it 'redirects to the page' do
expect(last_page).to be > 1
get_index(page: last_page)
expect(response).to have_gitlab_http_status(:ok)
expect(assigns(:clusters).current_page).to eq(last_page)
end
it 'displays cluster list for associated page' do
expect(last_page).to be > 1
get_index(page: last_page, format: :json)
expect(response).to have_gitlab_http_status(:ok)
expect(response.headers['X-Page'].to_i).to eq(last_page)
expect(response.headers['X-Total'].to_i).to eq(total_count)
end
end
end

View File

@ -32,7 +32,7 @@ describe Groups::ClustersController do
create(:cluster, :disabled, :provided_by_gcp, :production_environment, cluster_type: :group_type, groups: [group])
end
it 'lists available clusters' do
it 'lists available clusters and renders html' do
go
expect(response).to have_gitlab_http_status(:ok)
@ -40,20 +40,39 @@ describe Groups::ClustersController do
expect(assigns(:clusters)).to match_array([enabled_cluster, disabled_cluster])
end
it 'lists available clusters with json serializer' do
go(format: :json)
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('cluster_list')
end
context 'when page is specified' do
let(:last_page) { group.clusters.page.total_pages }
let(:total_count) { group.clusters.page.total_count }
before do
allow(Clusters::Cluster).to receive(:paginates_per).and_return(1)
create_list(:cluster, 2, :provided_by_gcp, :production_environment, cluster_type: :group_type, groups: [group])
create_list(:cluster, 30, :provided_by_gcp, :production_environment, cluster_type: :group_type, groups: [group])
end
it 'redirects to the page' do
expect(last_page).to be > 1
go(page: last_page)
expect(response).to have_gitlab_http_status(:ok)
expect(assigns(:clusters).current_page).to eq(last_page)
end
it 'displays cluster list for associated page' do
expect(last_page).to be > 1
go(page: last_page, format: :json)
expect(response).to have_gitlab_http_status(:ok)
expect(response.headers['X-Page'].to_i).to eq(last_page)
expect(response.headers['X-Total'].to_i).to eq(total_count)
end
end
end

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
require 'spec_helper'
describe Oauth::AuthorizedApplicationsController do
let(:user) { create(:user) }
let(:guest) { create(:user) }
let(:application) { create(:oauth_application, owner: guest) }
before do
sign_in(user)
end
describe 'GET #index' do
it 'responds with 404' do
get :index
expect(response).to have_gitlab_http_status(:not_found)
end
end
end

View File

@ -26,7 +26,7 @@ describe Projects::ClustersController do
let!(:enabled_cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
let!(:disabled_cluster) { create(:cluster, :disabled, :provided_by_gcp, :production_environment, projects: [project]) }
it 'lists available clusters' do
it 'lists available clusters and renders html' do
go
expect(response).to have_gitlab_http_status(:ok)
@ -34,20 +34,39 @@ describe Projects::ClustersController do
expect(assigns(:clusters)).to match_array([enabled_cluster, disabled_cluster])
end
it 'lists available clusters with json serializer' do
go(format: :json)
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('cluster_list')
end
context 'when page is specified' do
let(:last_page) { project.clusters.page.total_pages }
let(:total_count) { project.clusters.page.total_count }
before do
allow(Clusters::Cluster).to receive(:paginates_per).and_return(1)
create_list(:cluster, 2, :provided_by_gcp, :production_environment, projects: [project])
create_list(:cluster, 30, :provided_by_gcp, :production_environment, projects: [project])
end
it 'redirects to the page' do
expect(last_page).to be > 1
go(page: last_page)
expect(response).to have_gitlab_http_status(:ok)
expect(assigns(:clusters).current_page).to eq(last_page)
end
it 'displays cluster list for associated page' do
expect(last_page).to be > 1
go(page: last_page, format: :json)
expect(response).to have_gitlab_http_status(:ok)
expect(response.headers['X-Page'].to_i).to eq(last_page)
expect(response.headers['X-Total'].to_i).to eq(total_count)
end
end
end
@ -68,9 +87,11 @@ describe Projects::ClustersController do
it 'is allowed for admin when admin mode enabled', :enable_admin_mode do
expect { go }.to be_allowed_for(:admin)
end
it 'is disabled for admin when admin mode disabled' do
expect { go }.to be_denied_for(:admin)
end
it { expect { go }.to be_allowed_for(:owner).of(project) }
it { expect { go }.to be_allowed_for(:maintainer).of(project) }
it { expect { go }.to be_denied_for(:developer).of(project) }

View File

@ -0,0 +1,14 @@
{
"clusters": {
"type": "array",
"items": {
"cluster_type": "string",
"enabled": "boolean",
"environment_scope": "string",
"name": "string",
"path": "string",
"status": "string"
}
},
"has_ancestor_clusters": { "type": ["boolean", "false"] }
}

View File

@ -0,0 +1,34 @@
import { shallowMount } from '@vue/test-utils';
import AlertDetails from '~/alert_management/components/alert_details.vue';
describe('AlertDetails', () => {
let wrapper;
function mountComponent() {
wrapper = shallowMount(AlertDetails);
}
beforeEach(() => {
mountComponent();
});
afterEach(() => {
if (wrapper) {
wrapper.destroy();
}
});
describe('Alert details', () => {
it('renders a tab with overview information', () => {
expect(wrapper.find('[data-testid="overviewTab"]').exists()).toBe(true);
});
it('renders a tab with full alert information', () => {
expect(wrapper.find('[data-testid="fullDetailsTab"]').exists()).toBe(true);
});
it('renders alert details', () => {
expect(wrapper.find('[data-testid="startTimeItem"]').exists()).toBe(true);
});
});
});

View File

@ -18,6 +18,7 @@ import {
singleStatMetricsResult,
graphDataPrometheusQueryRangeMultiTrack,
barMockData,
propsData,
} from '../mock_data';
import { panelTypes } from '~/monitoring/constants';
@ -60,6 +61,7 @@ describe('Dashboard Panel', () => {
wrapper = shallowMount(DashboardPanel, {
propsData: {
graphData,
settingsPath: propsData.settingsPath,
...props,
},
store,
@ -239,6 +241,7 @@ describe('Dashboard Panel', () => {
describe('Edit custom metric dropdown item', () => {
const findEditCustomMetricLink = () => wrapper.find({ ref: 'editMetricLink' });
const mockEditPath = '/root/kubernetes-gke-project/prometheus/metrics/23/edit';
beforeEach(() => {
createWrapper();
@ -257,7 +260,7 @@ describe('Dashboard Panel', () => {
metrics: [
{
...graphData.metrics[0],
edit_path: '/root/kubernetes-gke-project/prometheus/metrics/23/edit',
edit_path: mockEditPath,
},
],
},
@ -266,10 +269,11 @@ describe('Dashboard Panel', () => {
return wrapper.vm.$nextTick(() => {
expect(findEditCustomMetricLink().exists()).toBe(true);
expect(findEditCustomMetricLink().text()).toBe('Edit metric');
expect(findEditCustomMetricLink().attributes('href')).toBe(mockEditPath);
});
});
it('shows an "Edit metrics" link for a panel with multiple metrics', () => {
it('shows an "Edit metrics" link pointing to settingsPath for a panel with multiple metrics', () => {
wrapper.setProps({
graphData: {
...graphData,
@ -288,6 +292,7 @@ describe('Dashboard Panel', () => {
return wrapper.vm.$nextTick(() => {
expect(findEditCustomMetricLink().text()).toBe('Edit metrics');
expect(findEditCustomMetricLink().attributes('href')).toBe(propsData.settingsPath);
});
});
});
@ -396,6 +401,7 @@ describe('Dashboard Panel', () => {
wrapper = shallowMount(DashboardPanel, {
propsData: {
clipboardText: exampleText,
settingsPath: propsData.settingsPath,
graphData: {
y_label: 'metric',
...graphData,
@ -445,6 +451,7 @@ describe('Dashboard Panel', () => {
wrapper = shallowMount(DashboardPanel, {
propsData: {
graphData,
settingsPath: propsData.settingsPath,
namespace: mockNamespace,
},
store,
@ -529,12 +536,12 @@ describe('Dashboard Panel', () => {
});
describe.each`
desc | metricsSavedToDb | propsData | isShown
desc | metricsSavedToDb | props | isShown
${'with permission and no metrics in db'} | ${[]} | ${{}} | ${false}
${'with permission and related metrics in db'} | ${[graphData.metrics[0].metricId]} | ${{}} | ${true}
${'without permission and related metrics in db'} | ${[graphData.metrics[0].metricId]} | ${{ prometheusAlertsAvailable: false }} | ${false}
${'with permission and unrelated metrics in db'} | ${['another_metric_id']} | ${{}} | ${false}
`('$desc', ({ metricsSavedToDb, isShown, propsData }) => {
`('$desc', ({ metricsSavedToDb, isShown, props }) => {
const showsDesc = isShown ? 'shows' : 'does not show';
beforeEach(() => {
@ -542,7 +549,7 @@ describe('Dashboard Panel', () => {
createWrapper({
alertsEndpoint: '/endpoint',
prometheusAlertsAvailable: true,
...propsData,
...props,
});
return wrapper.vm.$nextTick();
});

View File

@ -0,0 +1,67 @@
import { shallowMount } from '@vue/test-utils';
import TimeAgo from '~/pipelines/components/time_ago.vue';
describe('Timeago component', () => {
let wrapper;
const createComponent = (props = {}) => {
wrapper = shallowMount(TimeAgo, {
propsData: {
...props,
},
data() {
return {
iconTimerSvg: `<svg></svg>`,
};
},
});
};
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
describe('with duration', () => {
beforeEach(() => {
createComponent({ duration: 10, finishedTime: '' });
});
it('should render duration and timer svg', () => {
expect(wrapper.find('.duration').exists()).toBe(true);
expect(wrapper.find('.duration svg').exists()).toBe(true);
});
});
describe('without duration', () => {
beforeEach(() => {
createComponent({ duration: 0, finishedTime: '' });
});
it('should not render duration and timer svg', () => {
expect(wrapper.find('.duration').exists()).toBe(false);
});
});
describe('with finishedTime', () => {
beforeEach(() => {
createComponent({ duration: 0, finishedTime: '2017-04-26T12:40:23.277Z' });
});
it('should render time and calendar icon', () => {
expect(wrapper.find('.finished-at').exists()).toBe(true);
expect(wrapper.find('.finished-at i.fa-calendar').exists()).toBe(true);
expect(wrapper.find('.finished-at time').exists()).toBe(true);
});
});
describe('without finishedTime', () => {
beforeEach(() => {
createComponent({ duration: 0, finishedTime: '' });
});
it('should not render time and calendar icon', () => {
expect(wrapper.find('.finished-at').exists()).toBe(false);
});
});
});

View File

@ -280,11 +280,16 @@ describe ApplicationHelper do
end
context 'when @project is set' do
it 'includes all possible body data elements and associates the project elements with project' do
project = create(:project)
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
before do
assign(:project, project)
allow(helper).to receive(:current_user).and_return(nil)
end
it 'includes all possible body data elements and associates the project elements with project' do
expect(helper).to receive(:can?).with(nil, :download_code, project)
expect(helper.body_data).to eq(
{
page: 'application',
@ -305,12 +310,11 @@ describe ApplicationHelper do
context 'when params[:id] is present and the issue exsits and action_name is show' do
it 'sets all project and id elements correctly related to the issue' do
issue = create(:issue)
issue = create(:issue, project: project)
stub_controller_method(:action_name, 'show')
stub_controller_method(:params, { id: issue.id })
assign(:project, issue.project)
expect(helper).to receive(:can?).with(nil, :download_code, project).and_return(false)
expect(helper.body_data).to eq(
{
page: 'projects:issues:show',
@ -325,6 +329,15 @@ describe ApplicationHelper do
end
end
end
context 'when current_user has download_code permission' do
it 'returns find_file with the default branch' do
allow(helper).to receive(:current_user).and_return(user)
expect(helper).to receive(:can?).with(user, :download_code, project).and_return(true)
expect(helper.body_data[:find_file]).to end_with(project.default_branch)
end
end
end
def stub_controller_method(method_name, value)

View File

@ -1,64 +0,0 @@
import Vue from 'vue';
import timeAgo from '~/pipelines/components/time_ago.vue';
describe('Timeago component', () => {
let TimeAgo;
beforeEach(() => {
TimeAgo = Vue.extend(timeAgo);
});
describe('with duration', () => {
it('should render duration and timer svg', () => {
const component = new TimeAgo({
propsData: {
duration: 10,
finishedTime: '',
},
}).$mount();
expect(component.$el.querySelector('.duration')).toBeDefined();
expect(component.$el.querySelector('.duration svg')).toBeDefined();
});
});
describe('without duration', () => {
it('should not render duration and timer svg', () => {
const component = new TimeAgo({
propsData: {
duration: 0,
finishedTime: '',
},
}).$mount();
expect(component.$el.querySelector('.duration')).toBe(null);
});
});
describe('with finishedTime', () => {
it('should render time and calendar icon', () => {
const component = new TimeAgo({
propsData: {
duration: 0,
finishedTime: '2017-04-26T12:40:23.277Z',
},
}).$mount();
expect(component.$el.querySelector('.finished-at')).toBeDefined();
expect(component.$el.querySelector('.finished-at i.fa-calendar')).toBeDefined();
expect(component.$el.querySelector('.finished-at time')).toBeDefined();
});
});
describe('without finishedTime', () => {
it('should not render time and calendar icon', () => {
const component = new TimeAgo({
propsData: {
duration: 0,
finishedTime: '',
},
}).$mount();
expect(component.$el.querySelector('.finished-at')).toBe(null);
});
});
});

View File

@ -7,11 +7,11 @@ require 'tempfile'
describe Gitlab::Middleware::Multipart do
include_context 'multipart middleware context'
shared_examples_for 'multipart upload files' do
RSpec.shared_examples_for 'multipart upload files' do
it 'opens top-level files' do
Tempfile.open('top-level') do |tempfile|
rewritten = { 'file' => tempfile.path }
in_params = { 'file.name' => original_filename, 'file.path' => tempfile.path, 'file.remote_id' => remote_id }
in_params = { 'file.name' => original_filename, 'file.path' => file_path, 'file.remote_id' => remote_id, 'file.size' => file_size }
env = post_env(rewritten, in_params, Gitlab::Workhorse.secret, 'gitlab-workhorse')
expect_uploaded_file(tempfile, %w(file))
@ -22,8 +22,8 @@ describe Gitlab::Middleware::Multipart do
it 'opens files one level deep' do
Tempfile.open('one-level') do |tempfile|
in_params = { 'user' => { 'avatar' => { '.name' => original_filename, '.path' => tempfile.path, '.remote_id' => remote_id } } }
rewritten = { 'user[avatar]' => tempfile.path }
in_params = { 'user' => { 'avatar' => { '.name' => original_filename, '.path' => file_path, '.remote_id' => remote_id, '.size' => file_size } } }
env = post_env(rewritten, in_params, Gitlab::Workhorse.secret, 'gitlab-workhorse')
expect_uploaded_file(tempfile, %w(user avatar))
@ -34,7 +34,7 @@ describe Gitlab::Middleware::Multipart do
it 'opens files two levels deep' do
Tempfile.open('two-levels') do |tempfile|
in_params = { 'project' => { 'milestone' => { 'themesong' => { '.name' => original_filename, '.path' => tempfile.path, '.remote_id' => remote_id } } } }
in_params = { 'project' => { 'milestone' => { 'themesong' => { '.name' => original_filename, '.path' => file_path, '.remote_id' => remote_id, '.size' => file_size } } } }
rewritten = { 'project[milestone][themesong]' => tempfile.path }
env = post_env(rewritten, in_params, Gitlab::Workhorse.secret, 'gitlab-workhorse')
@ -44,13 +44,61 @@ describe Gitlab::Middleware::Multipart do
end
end
def expect_uploaded_file(tempfile, path, remote: false)
def expect_uploaded_file(tempfile, path)
expect(app).to receive(:call) do |env|
file = get_params(env).dig(*path)
expect(file).to be_a(::UploadedFile)
expect(file.path).to eq(tempfile.path)
expect(file.original_filename).to eq(original_filename)
expect(file.remote_id).to eq(remote_id)
if remote_id
expect(file.remote_id).to eq(remote_id)
expect(file.path).to be_nil
else
expect(file.path).to eq(File.realpath(tempfile.path))
expect(file.remote_id).to be_nil
end
end
end
end
RSpec.shared_examples_for 'handling CI artifact upload' do
it 'uploads both file and metadata' do
Tempfile.open('file') do |file|
Tempfile.open('metadata') do |metadata|
rewritten = { 'file' => file.path, 'metadata' => metadata.path }
in_params = { 'file.name' => 'file.txt', 'file.path' => file_path, 'file.remote_id' => file_remote_id, 'file.size' => file_size, 'metadata.name' => 'metadata.gz' }
env = post_env(rewritten, in_params, Gitlab::Workhorse.secret, 'gitlab-workhorse')
with_expected_uploaded_artifact_files(file, metadata) do |uploaded_file, uploaded_metadata|
expect(uploaded_file).to be_a(::UploadedFile)
expect(uploaded_file.original_filename).to eq('file.txt')
if file_remote_id
expect(uploaded_file.remote_id).to eq(file_remote_id)
expect(uploaded_file.size).to eq(file_size)
expect(uploaded_file.path).to be_nil
else
expect(uploaded_file.path).to eq(File.realpath(file.path))
expect(uploaded_file.remote_id).to be_nil
end
expect(uploaded_metadata).to be_a(::UploadedFile)
expect(uploaded_metadata.original_filename).to eq('metadata.gz')
expect(uploaded_metadata.path).to eq(File.realpath(metadata.path))
expect(uploaded_metadata.remote_id).to be_nil
end
middleware.call(env)
end
end
end
def with_expected_uploaded_artifact_files(file, metadata)
expect(app).to receive(:call) do |env|
file = get_params(env).dig('file')
metadata = get_params(env).dig('metadata')
yield file, metadata
end
end
end
@ -67,18 +115,65 @@ describe Gitlab::Middleware::Multipart do
expect { middleware.call(env) }.to raise_error(JWT::InvalidIssuerError)
end
context 'with invalid rewritten field' do
invalid_field_names = [
'[file]',
';file',
'file]',
';file]',
'file]]',
'file;;'
]
invalid_field_names.each do |invalid_field_name|
it "rejects invalid rewritten field name #{invalid_field_name}" do
env = post_env({ invalid_field_name => nil }, {}, Gitlab::Workhorse.secret, 'gitlab-workhorse')
expect { middleware.call(env) }.to raise_error(RuntimeError, "invalid field: \"#{invalid_field_name}\"")
end
end
end
context 'with remote file' do
let(:remote_id) { 'someid' }
let(:file_size) { 300 }
let(:file_path) { '' }
it_behaves_like 'multipart upload files'
end
context 'with remote file and a file path set' do
let(:remote_id) { 'someid' }
let(:file_size) { 300 }
let(:file_path) { 'not_a_valid_file_path' } # file path will come from the rewritten_fields
it_behaves_like 'multipart upload files'
end
context 'with local file' do
let(:remote_id) { nil }
let(:file_size) { nil }
let(:file_path) { 'not_a_valid_file_path' } # file path will come from the rewritten_fields
it_behaves_like 'multipart upload files'
end
context 'with remote CI artifact upload' do
let(:file_remote_id) { 'someid' }
let(:file_size) { 300 }
let(:file_path) { 'not_a_valid_file_path' } # file path will come from the rewritten_fields
it_behaves_like 'handling CI artifact upload'
end
context 'with local CI artifact upload' do
let(:file_remote_id) { nil }
let(:file_size) { nil }
let(:file_path) { 'not_a_valid_file_path' } # file path will come from the rewritten_fields
it_behaves_like 'handling CI artifact upload'
end
it 'allows files in uploads/tmp directory' do
with_tmp_dir('public/uploads/tmp') do |dir, env|
expect(app).to receive(:call) do |env|

View File

@ -4,7 +4,7 @@ require 'spec_helper'
describe UploadedFile do
let(:temp_dir) { Dir.tmpdir }
let(:temp_file) { Tempfile.new("test", temp_dir) }
let(:temp_file) { Tempfile.new(%w[test test], temp_dir) }
before do
FileUtils.touch(temp_file)
@ -16,13 +16,14 @@ describe UploadedFile do
describe ".from_params" do
let(:upload_path) { nil }
let(:file_path_override) { nil }
after do
FileUtils.rm_r(upload_path) if upload_path
end
subject do
described_class.from_params(params, :file, upload_path)
described_class.from_params(params, :file, upload_path, file_path_override)
end
context 'when valid file is specified' do
@ -31,9 +32,7 @@ describe UploadedFile do
{ 'file.path' => temp_file.path }
end
it "succeeds" do
is_expected.not_to be_nil
end
it { is_expected.not_to be_nil }
it "generates filename from path" do
expect(subject.original_filename).to eq(::File.basename(temp_file.path))
@ -41,33 +40,153 @@ describe UploadedFile do
end
context 'all parameters are specified' do
let(:params) do
{ 'file.path' => temp_file.path,
'file.name' => 'dir/my file&.txt',
'file.type' => 'my/type',
'file.sha256' => 'sha256',
'file.remote_id' => 'remote_id' }
RSpec.shared_context 'filepath override' do
let(:temp_file_override) { Tempfile.new(%w[override override], temp_dir) }
let(:file_path_override) { temp_file_override.path }
before do
FileUtils.touch(temp_file_override)
end
after do
FileUtils.rm_f(temp_file_override)
end
end
it "succeeds" do
is_expected.not_to be_nil
RSpec.shared_examples 'using the file path' do |filename:, content_type:, sha256:, path_suffix:|
it 'sets properly the attributes' do
expect(subject.original_filename).to eq(filename)
expect(subject.content_type).to eq(content_type)
expect(subject.sha256).to eq(sha256)
expect(subject.remote_id).to be_nil
expect(subject.path).to end_with(path_suffix)
end
it 'handles a blank path' do
params['file.path'] = ''
# Not a real file, so can't determine size itself
params['file.size'] = 1.byte
expect { described_class.from_params(params, :file, upload_path) }
.not_to raise_error
end
end
it "generates filename from path" do
expect(subject.original_filename).to eq('my_file_.txt')
expect(subject.content_type).to eq('my/type')
expect(subject.sha256).to eq('sha256')
expect(subject.remote_id).to eq('remote_id')
RSpec.shared_examples 'using the remote id' do |filename:, content_type:, sha256:, size:, remote_id:|
it 'sets properly the attributes' do
expect(subject.original_filename).to eq(filename)
expect(subject.content_type).to eq('application/octet-stream')
expect(subject.sha256).to eq('sha256')
expect(subject.path).to be_nil
expect(subject.size).to eq(123456)
expect(subject.remote_id).to eq('1234567890')
end
end
it 'handles a blank path' do
params['file.path'] = ''
context 'with a filepath' do
let(:params) do
{ 'file.path' => temp_file.path,
'file.name' => 'dir/my file&.txt',
'file.type' => 'my/type',
'file.sha256' => 'sha256' }
end
# Not a real file, so can't determine size itself
params['file.size'] = 1.byte
it { is_expected.not_to be_nil }
expect { described_class.from_params(params, :file, upload_path) }
.not_to raise_error
it_behaves_like 'using the file path',
filename: 'my_file_.txt',
content_type: 'my/type',
sha256: 'sha256',
path_suffix: 'test'
end
context 'with a filepath override' do
include_context 'filepath override'
let(:params) do
{ 'file.path' => temp_file.path,
'file.name' => 'dir/my file&.txt',
'file.type' => 'my/type',
'file.sha256' => 'sha256' }
end
it { is_expected.not_to be_nil }
it_behaves_like 'using the file path',
filename: 'my_file_.txt',
content_type: 'my/type',
sha256: 'sha256',
path_suffix: 'override'
end
context 'with a remote id' do
let(:params) do
{
'file.name' => 'dir/my file&.txt',
'file.sha256' => 'sha256',
'file.remote_url' => 'http://localhost/file',
'file.remote_id' => '1234567890',
'file.etag' => 'etag1234567890',
'file.size' => '123456'
}
end
it { is_expected.not_to be_nil }
it_behaves_like 'using the remote id',
filename: 'my_file_.txt',
content_type: 'application/octet-stream',
sha256: 'sha256',
size: 123456,
remote_id: '1234567890'
end
context 'with a path and a remote id' do
let(:params) do
{
'file.path' => temp_file.path,
'file.name' => 'dir/my file&.txt',
'file.sha256' => 'sha256',
'file.remote_url' => 'http://localhost/file',
'file.remote_id' => '1234567890',
'file.etag' => 'etag1234567890',
'file.size' => '123456'
}
end
it { is_expected.not_to be_nil }
it_behaves_like 'using the remote id',
filename: 'my_file_.txt',
content_type: 'application/octet-stream',
sha256: 'sha256',
size: 123456,
remote_id: '1234567890'
end
context 'with a path override and a remote id' do
include_context 'filepath override'
let(:params) do
{
'file.name' => 'dir/my file&.txt',
'file.sha256' => 'sha256',
'file.remote_url' => 'http://localhost/file',
'file.remote_id' => '1234567890',
'file.etag' => 'etag1234567890',
'file.size' => '123456'
}
end
it { is_expected.not_to be_nil }
it_behaves_like 'using the remote id',
filename: 'my_file_.txt',
content_type: 'application/octet-stream',
sha256: 'sha256',
size: 123456,
remote_id: '1234567890'
end
end
end

View File

@ -7,7 +7,7 @@ describe ClusterEntity do
subject { described_class.new(cluster).as_json }
context 'when provider type is gcp' do
let(:cluster) { create(:cluster, provider_type: :gcp, provider_gcp: provider) }
let(:cluster) { create(:cluster, :instance, provider_type: :gcp, provider_gcp: provider) }
context 'when status is creating' do
let(:provider) { create(:cluster_provider_gcp, :creating) }
@ -29,7 +29,7 @@ describe ClusterEntity do
end
context 'when provider type is user' do
let(:cluster) { create(:cluster, provider_type: :user) }
let(:cluster) { create(:cluster, :instance, provider_type: :user) }
it 'has corresponded data' do
expect(subject[:status]).to eq(:created)
@ -38,7 +38,7 @@ describe ClusterEntity do
end
context 'when no application has been installed' do
let(:cluster) { create(:cluster) }
let(:cluster) { create(:cluster, :instance) }
subject { described_class.new(cluster).as_json[:applications]}

View File

@ -3,7 +3,7 @@
require 'spec_helper'
describe RemoteMirrorEntity do
let(:project) { create(:project, :repository, :remote_mirror) }
let(:project) { create(:project, :repository, :remote_mirror, url: "https://test:password@gitlab.com") }
let(:remote_mirror) { project.remote_mirrors.first }
let(:entity) { described_class.new(remote_mirror) }
@ -15,4 +15,9 @@ describe RemoteMirrorEntity do
:ssh_known_hosts, :ssh_public_key, :ssh_known_hosts_fingerprints
)
end
it 'does not expose password information' do
expect(subject[:url]).not_to include('password')
expect(subject[:url]).to eq(remote_mirror.safe_url)
end
end