Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-01-14 00:16:06 +00:00
parent 3e64e1af8d
commit 0426ca208d
23 changed files with 757 additions and 80 deletions

View File

@ -145,7 +145,7 @@ export default {
},
setScope(scope) {
this.scope = scope;
this.resetPolling();
this.moveToPage(1);
},
movePage(direction) {
this.moveToPage(this.pageInfo[`${direction}Page`]);

View File

@ -1,5 +1,5 @@
<script>
import { GlSkeletonLoader } from '@gitlab/ui';
import { GlButton, GlSkeletonLoader } from '@gitlab/ui';
import createFlash from '~/flash';
import { __ } from '~/locale';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
@ -29,6 +29,7 @@ export default {
statusIcon,
GlSkeletonLoader,
ActionsButton,
GlButton,
},
mixins: [glFeatureFlagMixin(), mergeRequestQueryVariablesMixin],
props: {
@ -53,6 +54,9 @@ export default {
isLoading() {
return this.glFeatures.mergeRequestWidgetGraphql && this.$apollo.queries.state.loading;
},
showRebaseWithoutCi() {
return this.glFeatures?.rebaseWithoutCiUi;
},
rebaseInProgress() {
if (this.glFeatures.mergeRequestWidgetGraphql) {
return this.state.rebaseInProgress;
@ -196,8 +200,18 @@ export default {
v-if="!rebaseInProgress && canPushToSourceBranch && !isMakingRequest"
class="accept-merge-holder clearfix js-toggle-container accept-action media space-children"
>
<gl-button
v-if="!glFeatures.restructuredMrWidget && !showRebaseWithoutCi"
:loading="isMakingRequest"
variant="confirm"
data-qa-selector="mr_rebase_button"
data-testid="standard-rebase-button"
@click="rebase"
>
{{ __('Rebase') }}
</gl-button>
<actions-button
v-if="!glFeatures.restructuredMrWidget"
v-if="!glFeatures.restructuredMrWidget && showRebaseWithoutCi"
:actions="actions"
:selected-key="selectedRebaseAction"
variant="confirm"

View File

@ -43,6 +43,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_frontend_feature_flag(:mr_changes_fluid_layout, project, default_enabled: :yaml)
push_frontend_feature_flag(:mr_attention_requests, project, default_enabled: :yaml)
push_frontend_feature_flag(:refactor_mr_widgets_extensions, @project, default_enabled: :yaml)
push_frontend_feature_flag(:rebase_without_ci_ui, @project, default_enabled: :yaml)
# Usage data feature flags
push_frontend_feature_flag(:users_expanding_widgets_usage_data, @project, default_enabled: :yaml)
push_frontend_feature_flag(:diff_settings_usage_data, default_enabled: :yaml)

View File

@ -17,11 +17,14 @@ class RegistrationsController < Devise::RegistrationsController
check_rate_limit!(:user_sign_up, scope: request.ip) if Feature.enabled?(:rate_limit_user_sign_up_endpoint, default_enabled: :yaml)
end
before_action only: [:new] do
push_frontend_feature_flag(:gitlab_gtm_datalayer, type: :ops)
end
feature_category :authentication_and_authorization
def new
@resource = build_resource
push_frontend_feature_flag(:gitlab_gtm_datalayer, type: :ops)
end
def create

View File

@ -6,8 +6,8 @@ module Resolvers
type Types::WorkItems::TypeType.connection_type, null: true
def resolve
# This will require a finder in the future when groups get their work item types
# All groups use the default types for now
# This will require a finder in the future when groups/projects get their work item types
# All groups/projects use the default types for now
::WorkItems::Type.default.order_by_name_asc
end
end

View File

@ -406,6 +406,11 @@ module Types
description: 'Labels available on this project.',
resolver: Resolvers::LabelsResolver
field :work_item_types, Types::WorkItems::TypeType.connection_type,
resolver: Resolvers::WorkItems::TypesResolver,
description: 'Work item types available to the project.',
feature_flag: :work_items
def avatar_url
object.avatar_url(only_path: false)
end

View File

@ -0,0 +1,8 @@
---
name: ci_decompose_for_namespace_monthly_usage_query
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/77952
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/350146
milestone: '14.7'
type: development
group: group::pipeline execution
default_enabled: false

View File

@ -0,0 +1,8 @@
---
name: rebase_without_ci_ui
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78194
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/350262
milestone: '14.7'
type: development
group: group::pipeline execution
default_enabled: false

View File

@ -0,0 +1,16 @@
- name: "Removal of `artifacts:report:cobertura` keyword"
announcement_milestone: "14.8"
announcement_date: "2022-02-22"
removal_milestone: "15.0"
removal_date: "2022-06-22"
breaking_change: false
body: |
Currently, test coverage visualizations in GitLab only support Cobertura reports. Starting 15.0, the
`artifacts:report:cobertura` keyword will be replaced by
[`artifacts:reports:coverage_report`](https://gitlab.com/gitlab-org/gitlab/-/issues/344533). Cobertura will be the
only supported report file in 15.0, but this is the first step towards GitLab supporting other report types.
# The following items are not published on the docs page, but may be used in the future.
stage: Verify
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/348980
documentation_url: https://docs.gitlab.com/ee/ci/yaml/artifacts_reports.html#artifactsreportscobertura

View File

@ -490,7 +490,15 @@ The following are useful queries for monitoring Gitaly:
### Monitor Gitaly Cluster
To monitor Gitaly Cluster (Praefect), you can use these Prometheus metrics:
To monitor Gitaly Cluster (Praefect), you can use these Prometheus metrics. There are two separate metrics
endpoints from which metrics can be scraped:
- The default `/metrics` endpoint.
- `/db_metrics`, which contains metrics that require database queries.
#### Default Prometheus `/metrics` endpoint
The following metrics are available from the `/metrics` endpoint:
- `gitaly_praefect_read_distribution`, a counter to track [distribution of reads](#distributed-reads).
It has two labels:
@ -523,6 +531,16 @@ To monitor [strong consistency](#strong-consistency), you can use the following
You can also monitor the [Praefect logs](../logs.md#praefect-logs).
#### Database metrics `/db_metrics` endpoint
The following metrics are available from the `/db_metrics` endpoint:
- `gitaly_praefect_unavailable_repositories`, the number of repositories that have no healthy, up to date replicas.
- `gitaly_praefect_read_only_repositories`, the number of repositories in read-only mode within a virtual storage.
This is an older metric that is still available for backwards compatibility reasons. `gitaly_praefect_unavailable_repositories`
is a more accurate.
- `gitaly_praefect_replication_queue_depth`, the number of jobs in the replication queue.
## Recover from failure
Gitaly Cluster can [recover from certain types of failure](recovery.md).

View File

@ -20,6 +20,9 @@ Configure Gitaly Cluster using either:
Smaller GitLab installations may need only [Gitaly itself](index.md).
To upgrade a Gitaly Cluster, follow the documentation for
[zero downtime upgrades](../../update/zero_downtime.md#gitaly-cluster).
## Requirements
The minimum recommended configuration for a Gitaly Cluster requires:
@ -376,8 +379,8 @@ configuration option is set. For more details, consult the PgBouncer documentati
If there are multiple Praefect nodes:
- Complete the following steps for **each** node.
- Designate one node as the "deploy node", and configure it first.
1. Designate one node as the deploy node, and configure it using the following steps.
1. Complete the following steps for each additional node.
To complete this section you need a [configured PostgreSQL server](#postgresql), including:
@ -415,10 +418,21 @@ On the **Praefect** node:
```ruby
praefect['listen_addr'] = '0.0.0.0:2305'
```
1. Configure Prometheus metrics by editing
`/etc/gitlab/gitlab.rb`:
```ruby
# Enable Prometheus metrics access to Praefect. You must use firewalls
# to restrict access to this address/port.
# The default metrics endpoint is /metrics
praefect['prometheus_listen_addr'] = '0.0.0.0:9652'
# Some metrics run queries against the database. Enabling separate database metrics allows
# these metrics to be collected when the metrics are
# scraped on a separate /db_metrics endpoint.
praefect['separate_database_metrics'] = true
```
1. Configure a strong `auth_token` for **Praefect** by editing
@ -556,8 +570,6 @@ On the **Praefect** node:
edit `/etc/gitlab/gitlab.rb`, remember to run `sudo gitlab-ctl reconfigure`
again before trying the `sql-ping` command.
**The steps above must be completed for each Praefect node!**
#### Enabling TLS support
> [Introduced](https://gitlab.com/gitlab-org/gitaly/-/issues/1698) in GitLab 13.2.

View File

@ -289,6 +289,8 @@ control over how the Pages daemon runs and serves content in your environment.
| `use_legacy_storage` | Temporarily-introduced parameter allowing to use legacy domain configuration source and storage. [Removed in 14.3](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/6166). |
| `rate_limit_source_ip` | Rate limit per source IP in number of requests per second. Set to `0` to disable this feature. |
| `rate_limit_source_ip_burst` | Rate limit per source IP maximum burst allowed per second. |
| `rate_limit_domain` | Rate limit per domain in number of requests per second. Set to `0` to disable this feature. |
| `rate_limit_domain_burst` | Rate limit per domain maximum burst allowed per second. |
## Advanced configuration
@ -1077,15 +1079,22 @@ than GitLab to prevent XSS attacks.
> [Introduced](https://gitlab.com/gitlab-org/gitlab-pages/-/issues/631) in GitLab 14.5.
You can enforce source-IP rate limits to help minimize the risk of a Denial of Service (DoS) attack. GitLab Pages
You can enforce rate limits to help minimize the risk of a Denial of Service (DoS) attack. GitLab Pages
uses a [token bucket algorithm](https://en.wikipedia.org/wiki/Token_bucket) to enforce rate limiting. By default,
requests that exceed the specified limits are reported but not rejected.
Source-IP rate limits are enforced using the following:
GitLab Pages supports the following types of rate limiting:
- `rate_limit_source_ip`: Set the maximum threshold in number of requests per second. Set to 0 to disable this feature.
- `rate_limit_source_ip_burst`: Sets the maximum threshold of number of requests allowed in an initial outburst of requests.
- Per `source_ip`. It limits how many requests are allowed from the single client IP address.
- Per `domain`. It limits how many requests are allowed per domain hosted on GitLab Pages. It can be a custom domain like `example.com`, or group domain like `group.gitlab.io`.
Rate limits are enforced using the following:
- `rate_limit_source_ip`: Set the maximum threshold in number of requests per client IP per second. Set to 0 to disable this feature.
- `rate_limit_source_ip_burst`: Sets the maximum threshold of number of requests allowed in an initial outburst of requests per client IP.
For example, when you load a web page that loads a number of resources at the same time.
- `rate_limit_domain_ip`: Set the maximum threshold in number of requests per hosted pages domain per second. Set to 0 to disable this feature.
- `rate_limit_domain_burst`: Sets the maximum threshold of number of requests allowed in an initial outburst of requests per hosted pages domain.
#### Enable source-IP rate limits
@ -1105,6 +1114,24 @@ Source-IP rate limits are enforced using the following:
1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
#### Enable domain rate limits
1. Set rate limits in `/etc/gitlab/gitlab.rb`:
```ruby
gitlab_pages['rate_limit_domain'] = 1000
gitlab_pages['rate_limit_domain_burst'] = 5000
```
1. To reject requests that exceed the specified limits, enable the `FF_ENFORCE_DOMAIN_RATE_LIMITS` feature flag in
`/etc/gitlab/gitlab.rb`:
```ruby
gitlab_pages['env'] = {'FF_ENFORCE_DOMAIN_RATE_LIMITS' => 'true'}
```
1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
<!-- ## Troubleshooting
Include any troubleshooting steps that you can foresee. If you know beforehand what issues

View File

@ -13224,6 +13224,7 @@ Represents vulnerability finding of a security report on the pipeline.
| <a id="projectvulnerabilityscanners"></a>`vulnerabilityScanners` | [`VulnerabilityScannerConnection`](#vulnerabilityscannerconnection) | Vulnerability scanners reported on the project vulnerabilities. (see [Connections](#connections)) |
| <a id="projectweburl"></a>`webUrl` | [`String`](#string) | Web URL of the project. |
| <a id="projectwikienabled"></a>`wikiEnabled` | [`Boolean`](#boolean) | Indicates if Wikis are enabled for the current user. |
| <a id="projectworkitemtypes"></a>`workItemTypes` | [`WorkItemTypeConnection`](#workitemtypeconnection) | Work item types available to the project. Available only when feature flag `work_items` is enabled. This flag is disabled by default, because the feature is experimental and is subject to change without notice. (see [Connections](#connections)) |
#### Fields with arguments

View File

@ -1669,24 +1669,6 @@ If a feature is moved to another tier:
> - [Moved](<link-to-issue>) from GitLab Premium to GitLab Free in 12.0.
```
If a feature is deprecated, include a link to a replacement (when available):
```markdown
> - [Deprecated](<link-to-issue>) in GitLab 11.3. Replaced by [meaningful text](<link-to-appropriate-documentation>).
```
You can also describe the replacement in surrounding text, if available. If the
deprecation isn't obvious in existing text, you may want to include a warning:
```markdown
WARNING:
This feature was [deprecated](link-to-issue) in GitLab 12.3 and replaced by
[Feature name](link-to-feature-documentation).
```
In the first major GitLab version after the feature was deprecated, be sure to
remove information about that deprecated feature.
#### Inline version text
If you're adding content to an existing topic, you can add version information
@ -1786,6 +1768,47 @@ To view historical information about a feature, review GitLab
[release posts](https://about.gitlab.com/releases/), or search for the issue or
merge request where the work was done.
### Deprecated features
When a feature is deprecated, add `(DEPRECATED)` to the page title or to
the heading of the section documenting the feature, immediately before
the tier badge:
```markdown
<!-- Page title example: -->
# Feature A (DEPRECATED) **(ALL TIERS)**
<!-- Doc section example: -->
## Feature B (DEPRECATED) **(PREMIUM SELF)**
```
Add the deprecation to the version history note (you can include a link
to a replacement when available):
```markdown
> - [Deprecated](<link-to-issue>) in GitLab 11.3. Replaced by [meaningful text](<link-to-appropriate-documentation>).
```
You can also describe the replacement in surrounding text, if available. If the
deprecation isn't obvious in existing text, you may want to include a warning:
```markdown
WARNING:
This feature was [deprecated](link-to-issue) in GitLab 12.3 and replaced by
[Feature name](link-to-feature-documentation).
```
If you add `(DEPRECATED)` to the page's title and the document is linked from the docs
navigation, either remove the page from the nav or update the nav item to include the
same text before the feature name:
```yaml
- doc_title: (DEPRECATED) Feature A
```
In the first major GitLab version after the feature was deprecated, be sure to
remove information about that deprecated feature.
## Products and features
Refer to the information in this section when describing products and features

View File

@ -767,3 +767,354 @@ In the example above, the `is_admin?` method is overwritten when passing it to t
- If you must, be **very** confident that you've sanitized the values correctly.
Consider creating an allowlist of values, and validating the user input against that.
- When extending classes that use metaprogramming, make sure you don't inadvertently override any method definition safety checks.
## Working with archive files
Working with archive files like `zip`, `tar`, `jar`, `war`, `cpio`, `apk`, `rar` and `7z` presents an area where potentially critical security vulnerabilities can sneak into an application.
### Zip Slip
In 2018, the security company Snyk [released a blog post](https://snyk.io/research/zip-slip-vulnerability) describing research into a widespread and critical vulnerability present in many libraries and applications which allows an attacker to overwrite arbitrary files on the server file system which, in many cases, can be leveraged to achieve remote code execution. The vulnerability was dubbed Zip Slip.
A Zip Slip vulnerability happens when an application extracts an archive without validating and sanitizing the filenames inside the archive for directory traversal sequences that change the file location when the file is extracted.
Example malicious file names:
- `../../etc/passwd`
- `../../root/.ssh/authorized_keys`
- `../../etc/gitlab/gitlab.rb`
If a vulnerable application extracts an archive file with any of these file names, the attacker can overwrite these files with arbitrary content.
### Insecure archive extraction examples
#### Ruby
For zip files, the [rubyzip](https://rubygems.org/gems/rubyzip) Ruby gem is already patched against the Zip Slip vulnerability and will refuse to extract files that try to perform directory traversal, so for this vulnerable example we will extract a `tar.gz` file with `Gem::Package::TarReader`:
```ruby
# Vulnerable tar.gz extraction example!
begin
tar_extract = Gem::Package::TarReader.new(Zlib::GzipReader.open("/tmp/uploaded.tar.gz"))
rescue Errno::ENOENT
STDERR.puts("archive file does not exist or is not readable")
exit(false)
end
tar_extract.rewind
tar_extract.each do |entry|
next unless entry.file? # Only process files in this example for simplicity.
destination = "/tmp/extracted/#{entry.full_name}" # Oops! We blindly use the entry file name for the destination.
File.open(destination, "wb") do |out|
out.write(entry.read)
end
end
```
#### Go
```golang
// unzip INSECURELY extracts source zip file to destination.
func unzip(src, dest string) error {
r, err := zip.OpenReader(src)
if err != nil {
return err
}
defer r.Close()
os.MkdirAll(dest, 0750)
for _, f := range r.File {
if f.FileInfo().IsDir() { // Skip directories in this example for simplicity.
continue
}
rc, err := f.Open()
if err != nil {
return err
}
defer rc.Close()
path := filepath.Join(dest, f.Name) // Oops! We blindly use the entry file name for the destination.
os.MkdirAll(filepath.Dir(path), f.Mode())
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
return err
}
defer f.Close()
if _, err := io.Copy(f, rc); err != nil {
return err
}
}
return nil
}
```
#### Best practices
Always expand the destination file path by resolving all potential directory traversals and other sequences that can alter the path and refuse extraction if the final destination path does not start with the intended destination directory.
##### Ruby
```ruby
# tar.gz extraction example with protection against Zip Slip attacks.
begin
tar_extract = Gem::Package::TarReader.new(Zlib::GzipReader.open("/tmp/uploaded.tar.gz"))
rescue Errno::ENOENT
STDERR.puts("archive file does not exist or is not readable")
exit(false)
end
tar_extract.rewind
tar_extract.each do |entry|
next unless entry.file? # Only process files in this example for simplicity.
# safe_destination will raise an exception in case of Zip Slip / directory traversal.
destination = safe_destination(entry.full_name, "/tmp/extracted")
File.open(destination, "wb") do |out|
out.write(entry.read)
end
end
def safe_destination(filename, destination_dir)
raise "filename cannot start with '/'" if filename.start_with?("/")
destination_dir = File.realpath(destination_dir)
destination = File.expand_path(filename, destination_dir)
raise "filename is outside of destination directory" unless
destination.start_with?(destination_dir + "/"))
destination
end
```
```ruby
# zip extraction example using rubyzip with built-in protection against Zip Slip attacks.
require 'zip'
Zip::File.open("/tmp/uploaded.zip") do |zip_file|
zip_file.each do |entry|
# Extract entry to /tmp/extracted directory.
entry.extract("/tmp/extracted")
end
end
```
##### Go
You are encouraged to use the secure archive utilities provided by [LabSec](https://gitlab.com/gitlab-com/gl-security/appsec/labsec) which will handle Zip Slip and other types of vulnerabilities for you. The LabSec utilities are also context aware which makes it possible to cancel or timeout extractions:
```golang
package main
import "gitlab-com/gl-security/appsec/labsec/archive/zip"
func main() {
f, err := os.Open("/tmp/uploaded.zip")
if err != nil {
panic(err)
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
panic(err)
}
if err := zip.Extract(context.Background(), f, fi.Size(), "/tmp/extracted"); err != nil {
panic(err)
}
}
```
In case the LabSec utilities do not fit your needs, here is an example for extracting a zip file with protection against Zip Slip attacks:
```golang
// unzip extracts source zip file to destination with protection against Zip Slip attacks.
func unzip(src, dest string) error {
r, err := zip.OpenReader(src)
if err != nil {
return err
}
defer r.Close()
os.MkdirAll(dest, 0750)
for _, f := range r.File {
if f.FileInfo().IsDir() { // Skip directories in this example for simplicity.
continue
}
rc, err := f.Open()
if err != nil {
return err
}
defer rc.Close()
path := filepath.Join(dest, f.Name)
// Check for Zip Slip / directory traversal
if !strings.HasPrefix(path, filepath.Clean(dest) + string(os.PathSeparator)) {
return fmt.Errorf("illegal file path: %s", path)
}
os.MkdirAll(filepath.Dir(path), f.Mode())
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
return err
}
defer f.Close()
if _, err := io.Copy(f, rc); err != nil {
return err
}
}
return nil
}
```
### Symlink attacks
Symlink attacks makes it possible for an attacker to read the contents of arbitrary files on the server of a vulnerable application. While it is a high-severity vulnerability that can often lead to remote code execution and other critical vulnerabilities, it is only exploitable in scenarios where a vulnerable application accepts archive files from the attacker and somehow displays the extracted contents back to the attacker without any validation or sanitization of symbolic links inside the archive.
### Insecure archive symlink extraction examples
#### Ruby
For zip files, the [rubyzip](https://rubygems.org/gems/rubyzip) Ruby gem is already patched against symlink attacks as it simply ignores symbolic links, so for this vulnerable example we will extract a `tar.gz` file with `Gem::Package::TarReader`:
```ruby
# Vulnerable tar.gz extraction example!
begin
tar_extract = Gem::Package::TarReader.new(Zlib::GzipReader.open("/tmp/uploaded.tar.gz"))
rescue Errno::ENOENT
STDERR.puts("archive file does not exist or is not readable")
exit(false)
end
tar_extract.rewind
# Loop over each entry and output file contents
tar_extract.each do |entry|
next if entry.directory?
# Oops! We don't check if the file is actually a symbolic link to a potentially sensitive file.
puts entry.read
end
```
#### Go
```golang
// printZipContents INSECURELY prints contents of files in a zip file.
func printZipContents(src string) error {
r, err := zip.OpenReader(src)
if err != nil {
return err
}
defer r.Close()
// Loop over each entry and output file contents
for _, f := range r.File {
if f.FileInfo().IsDir() {
continue
}
rc, err := f.Open()
if err != nil {
return err
}
defer rc.Close()
// Oops! We don't check if the file is actually a symbolic link to a potentially sensitive file.
buf, err := ioutil.ReadAll(rc)
if err != nil {
return err
}
fmt.Println(buf.String())
}
return nil
}
```
#### Best practices
Always check the type of the archive entry before reading the contents and ignore entries that are not plain files. If you absolutely must support symbolic links, ensure that they only point to files inside the archive and nowhere else.
##### Ruby
```ruby
# tar.gz extraction example with protection against symlink attacks.
begin
tar_extract = Gem::Package::TarReader.new(Zlib::GzipReader.open("/tmp/uploaded.tar.gz"))
rescue Errno::ENOENT
STDERR.puts("archive file does not exist or is not readable")
exit(false)
end
tar_extract.rewind
# Loop over each entry and output file contents
tar_extract.each do |entry|
next if entry.directory?
# By skipping symbolic links entirely, we are sure they can't cause any trouble!
next if entry.symlink?
puts entry.read
end
```
##### Go
You are encouraged to use the secure archive utilities provided by [LabSec](https://gitlab.com/gitlab-com/gl-security/appsec/labsec) which will handle Zip Slip and symlink vulnerabilities for you. The LabSec utilities are also context aware which makes it possible to cancel or timeout extractions.
In case the LabSec utilities do not fit your needs, here is an example for extracting a zip file with protection against symlink attacks:
```golang
// printZipContents prints contents of files in a zip file with protection against symlink attacks.
func printZipContents(src string) error {
r, err := zip.OpenReader(src)
if err != nil {
return err
}
defer r.Close()
// Loop over each entry and output file contents
for _, f := range r.File {
if f.FileInfo().IsDir() {
continue
}
// By skipping all irregular file types (including symbolic links), we are sure they can't cause any trouble!
if !zf.Mode().IsRegular() {
continue
}
rc, err := f.Open()
if err != nil {
return err
}
defer rc.Close()
buf, err := ioutil.ReadAll(rc)
if err != nil {
return err
}
fmt.Println(buf.String())
}
return nil
}
```

View File

@ -11,25 +11,32 @@ GitLab SaaS is the GitLab software-as-a-service offering, which is available at
You don't need to install anything to use GitLab SaaS, you only need to
[sign up](https://gitlab.com/users/sign_up). When you sign up, you choose:
- [A license tier](https://about.gitlab.com/pricing/).
- [A subscription](https://about.gitlab.com/pricing/).
- [The number of seats you want](#how-seat-usage-is-determined).
All GitLab SaaS public projects, regardless of the subscription, get access to features in the **Ultimate** tier.
The subscription determines which features are available for your private projects. Public projects automatically get **Ultimate** tier features.
Qualifying open source projects also get 50,000 CI/CD minutes and free access to the **Ultimate** tier
through the [GitLab for Open Source program](https://about.gitlab.com/solutions/open-source/).
## Obtain a GitLab SaaS subscription
A GitLab SaaS subscription applies to a top-level group.
Members of every subgroup and project in the group:
- Can use the features of the subscription.
- Consume seats in the subscription.
To subscribe to GitLab SaaS:
1. View the [GitLab SaaS feature comparison](https://about.gitlab.com/pricing/gitlab-com/feature-comparison/)
and decide which tier you want.
1. Create a user account for yourself by using the
[sign up page](https://gitlab.com/users/sign_up).
1. Create a [group](../../user/group/index.md#create-a-group). You use the group to grant users access to several projects
at once. A group is not required if you plan to have projects in a personal namespace instead.
1. Create a [group](../../user/group/index.md#create-a-group). Your license tier applies to the top-level group, its subgroups, and projects.
1. Create additional users and
[add them to the group](../../user/group/index.md#add-users-to-a-group).
[add them to the group](../../user/group/index.md#add-users-to-a-group). The users in this group, its subgroups, and projects can use
the features of your license tier, and they consume a seat in your subscription.
1. On the left sidebar, select **Billing** and choose a tier.
1. Fill out the form to complete your purchase.
@ -62,10 +69,12 @@ The following information is displayed:
email address.
A GitLab SaaS subscription uses a concurrent (_seat_) model. You pay for a
subscription according to the maximum number of users enabled at one time. You can
subscription according to the maximum number of users assigned to the top-level group or its children during the billing period. You can
add and remove users during the subscription period, as long as the total users
at any given time doesn't exceed the subscription count.
A top-level group can be [changed](../../user/group/index.md#change-a-groups-path) like any other group.
Every user is included in seat usage, with the following exceptions:
- Users who are pending approval.
@ -77,6 +86,12 @@ Every user is included in seat usage, with the following exceptions:
Seat usage is reviewed [quarterly or annually](../quarterly_reconciliation.md).
If a user navigates to a different top-level group (one they have created themselves, for example)
and that group does not have a paid subscription, they would not see any of the paid features.
It is also possible for users to belong to two different top-level groups with different subscriptions.
In this case, they would see only the features available to that subscription.
### View seat usage
To view a list of seats being used:
@ -124,7 +139,7 @@ and is not affected by the current search.
A GitLab subscription is valid for a specific number of users.
If the number of billable users exceeds the number included in the subscription, known
as the number of **seats owed**, you must pay for the excess number of users before renewal.
as the number of **seats owed**, you must pay for the excess number of users.
For example, if you purchase a subscription for 10 users:
@ -138,9 +153,9 @@ Seats owed = 12 - 10 (Maximum users - users in subscription)
### Add users to your subscription
You can add users to your subscription at any time during the subscription period. The cost of
additional users added during the subscription period is prorated from the date of purchase through
the end of the subscription period.
Your subscription cost is based on the maximum number of seats you use during the billing period.
Even if you reach the number of seats in your subscription, you can continue to add users.
GitLab [bills you for the overage](../quarterly_reconciliation.md).
To add users to a subscription:

View File

@ -333,3 +333,14 @@ Planned removal milestone: 15.0 (2021-06-22)
Tracing in GitLab is an integration with Jaeger, an open-source end-to-end distributed tracing system. GitLab users can navigate to their Jaeger instance to gain insight into the performance of a deployed application, tracking each function or microservice that handles a given request. Tracing in GitLab is deprecated in GitLab 14.7, and scheduled for removal in 15.0. To track work on a possible replacement, see the issue for [Opstrace integration with GitLab](https://gitlab.com/groups/gitlab-org/-/epics/6976).
Planned removal milestone: 15.0 (2022-05-22)
## 14.8
### Removal of `artifacts:report:cobertura` keyword
Currently, test coverage visualizations in GitLab only support Cobertura reports. Starting 15.0, the
`artifacts:report:cobertura` keyword will be replaced by
[`artifacts:reports:coverage_report`](https://gitlab.com/gitlab-org/gitlab/-/issues/344533). Cobertura will be the
only supported report file in 15.0, but this is the first step towards GitLab supporting other report types.
Planned removal milestone: 15.0 (2022-06-22)

View File

@ -918,6 +918,8 @@ gemnasium-dependency_scanning:
## Warnings
We recommend that you use the most recent version of all containers, and the most recent supported version of all package managers and languages. Using previous versions carries an increased security risk because unsupported versions may no longer benefit from active security reporting and backporting of security fixes.
### Python projects
Extra care needs to be taken when using the [`PIP_EXTRA_INDEX_URL`](https://pipenv.pypa.io/en/latest/cli/#envvar-PIP_EXTRA_INDEX_URL)

View File

@ -187,7 +187,7 @@ describe('~/environments/components/new_environments_app.vue', () => {
expect(environmentAppMock).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({ scope: 'stopped' }),
expect.objectContaining({ scope: 'stopped', page: 1 }),
expect.anything(),
expect.anything(),
);

View File

@ -10,7 +10,7 @@ import {
let wrapper;
function createWrapper(propsData, mergeRequestWidgetGraphql) {
function createWrapper(propsData, mergeRequestWidgetGraphql, rebaseWithoutCiUi) {
wrapper = shallowMount(WidgetRebase, {
propsData,
data() {
@ -24,7 +24,7 @@ function createWrapper(propsData, mergeRequestWidgetGraphql) {
},
};
},
provide: { glFeatures: { mergeRequestWidgetGraphql } },
provide: { glFeatures: { mergeRequestWidgetGraphql, rebaseWithoutCiUi } },
mocks: {
$apollo: {
queries: {
@ -38,7 +38,8 @@ function createWrapper(propsData, mergeRequestWidgetGraphql) {
describe('Merge request widget rebase component', () => {
const findRebaseMessage = () => wrapper.find('[data-testid="rebase-message"]');
const findRebaseMessageText = () => findRebaseMessage().text();
const findRebaseButton = () => wrapper.find(ActionsButton);
const findRebaseButtonActions = () => wrapper.find(ActionsButton);
const findStandardRebaseButton = () => wrapper.find('[data-testid="standard-rebase-button"]');
afterEach(() => {
wrapper.destroy();
@ -65,7 +66,7 @@ describe('Merge request widget rebase component', () => {
const rebaseMock = jest.fn().mockResolvedValue();
const pollMock = jest.fn().mockResolvedValue({});
beforeEach(() => {
it('renders the warning message', () => {
createWrapper(
{
mr: {
@ -79,9 +80,7 @@ describe('Merge request widget rebase component', () => {
},
mergeRequestWidgetGraphql,
);
});
it('renders the warning message', () => {
const text = findRebaseMessageText();
expect(text).toContain('Merge blocked');
@ -91,6 +90,20 @@ describe('Merge request widget rebase component', () => {
});
it('renders an error message when rebasing has failed', async () => {
createWrapper(
{
mr: {
rebaseInProgress: false,
canPushToSourceBranch: true,
},
service: {
rebase: rebaseMock,
poll: pollMock,
},
},
mergeRequestWidgetGraphql,
);
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({ rebasingError: 'Something went wrong!' });
@ -99,13 +112,31 @@ describe('Merge request widget rebase component', () => {
expect(findRebaseMessageText()).toContain('Something went wrong!');
});
describe('"Rebase" button', () => {
it('is rendered', () => {
expect(findRebaseButton().exists()).toBe(true);
describe('Rebase button with flag rebaseWithoutCiUi', () => {
beforeEach(() => {
createWrapper(
{
mr: {
rebaseInProgress: false,
canPushToSourceBranch: true,
},
service: {
rebase: rebaseMock,
poll: pollMock,
},
},
mergeRequestWidgetGraphql,
{ rebaseWithoutCiUi: true },
);
});
it('rebase button with actions is rendered', () => {
expect(findRebaseButtonActions().exists()).toBe(true);
expect(findStandardRebaseButton().exists()).toBe(false);
});
it('has rebase and rebase without CI actions', () => {
const actionNames = findRebaseButton()
const actionNames = findRebaseButtonActions()
.props('actions')
.map((action) => action.key);
@ -113,13 +144,13 @@ describe('Merge request widget rebase component', () => {
});
it('defaults to rebase action', () => {
expect(findRebaseButton().props('selectedKey')).toStrictEqual(REBASE_BUTTON_KEY);
expect(findRebaseButtonActions().props('selectedKey')).toStrictEqual(REBASE_BUTTON_KEY);
});
it('starts the rebase when clicking', async () => {
// ActionButtons use the actions props instead of emitting
// a click event, therefore simulating the behavior here:
findRebaseButton()
findRebaseButtonActions()
.props('actions')
.find((x) => x.key === REBASE_BUTTON_KEY)
.handle();
@ -132,7 +163,7 @@ describe('Merge request widget rebase component', () => {
it('starts the CI-skipping rebase when clicking on "Rebase without CI"', async () => {
// ActionButtons use the actions props instead of emitting
// a click event, therefore simulating the behavior here:
findRebaseButton()
findRebaseButtonActions()
.props('actions')
.find((x) => x.key === REBASE_WITHOUT_CI_BUTTON_KEY)
.handle();
@ -142,12 +173,91 @@ describe('Merge request widget rebase component', () => {
expect(rebaseMock).toHaveBeenCalledWith({ skipCi: true });
});
});
describe('Rebase button with rebaseWithoutCiUI flag disabled', () => {
beforeEach(() => {
createWrapper(
{
mr: {
rebaseInProgress: false,
canPushToSourceBranch: true,
},
service: {
rebase: rebaseMock,
poll: pollMock,
},
},
mergeRequestWidgetGraphql,
);
});
it('standard rebase button is rendered', () => {
expect(findStandardRebaseButton().exists()).toBe(true);
expect(findRebaseButtonActions().exists()).toBe(false);
});
it('calls rebase method with skip_ci false', () => {
findStandardRebaseButton().vm.$emit('click');
expect(rebaseMock).toHaveBeenCalledWith({ skipCi: false });
});
});
});
describe('without permissions', () => {
const exampleTargetBranch = 'fake-branch-to-test-with';
beforeEach(() => {
describe('UI text', () => {
beforeEach(() => {
createWrapper(
{
mr: {
rebaseInProgress: false,
canPushToSourceBranch: false,
targetBranch: exampleTargetBranch,
},
service: {},
},
mergeRequestWidgetGraphql,
);
});
it('renders a message explaining user does not have permissions', () => {
const text = findRebaseMessageText();
expect(text).toContain(
'Merge blocked: the source branch must be rebased onto the target branch.',
);
expect(text).toContain('the source branch must be rebased');
});
it('renders the correct target branch name', () => {
const elem = findRebaseMessage();
expect(elem.text()).toContain(
'Merge blocked: the source branch must be rebased onto the target branch.',
);
});
});
it('does not render the rebase actions button with rebaseWithoutCiUI flag enabled', () => {
createWrapper(
{
mr: {
rebaseInProgress: false,
canPushToSourceBranch: false,
targetBranch: exampleTargetBranch,
},
service: {},
},
mergeRequestWidgetGraphql,
{ rebaseWithoutCiUi: true },
);
expect(findRebaseButtonActions().exists()).toBe(false);
});
it('does not render the standard rebase button with rebaseWithoutCiUI flag disabled', () => {
createWrapper(
{
mr: {
@ -159,27 +269,8 @@ describe('Merge request widget rebase component', () => {
},
mergeRequestWidgetGraphql,
);
});
it('renders a message explaining user does not have permissions', () => {
const text = findRebaseMessageText();
expect(text).toContain(
'Merge blocked: the source branch must be rebased onto the target branch.',
);
expect(text).toContain('the source branch must be rebased');
});
it('renders the correct target branch name', () => {
const elem = findRebaseMessage();
expect(elem.text()).toContain(
`Merge blocked: the source branch must be rebased onto the target branch.`,
);
});
it('does not render the "Rebase" button', () => {
expect(findRebaseButton().exists()).toBe(false);
expect(findStandardRebaseButton().exists()).toBe(false);
});
});

View File

@ -22,7 +22,7 @@ RSpec.describe GitlabSchema.types['Group'] do
dependency_proxy_blobs dependency_proxy_image_count
dependency_proxy_blob_count dependency_proxy_total_size
dependency_proxy_image_prefix dependency_proxy_image_ttl_policy
shared_runners_setting timelogs organizations contacts
shared_runners_setting timelogs organizations contacts work_item_types
]
expect(described_class).to include_graphql_fields(*expected_fields)

View File

@ -34,7 +34,7 @@ RSpec.describe GitlabSchema.types['Project'] do
container_repositories container_repositories_count
pipeline_analytics squash_read_only sast_ci_configuration
cluster_agent cluster_agents agent_configurations
ci_template timelogs merge_commit_template squash_commit_template
ci_template timelogs merge_commit_template squash_commit_template work_item_types
]
expect(described_class).to include_graphql_fields(*expected_fields)

View File

@ -0,0 +1,71 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'getting a list of work item types for a project' do
include GraphqlHelpers
let_it_be(:developer) { create(:user) }
let_it_be(:project) { create(:project) }
before_all do
project.add_developer(developer)
end
let(:current_user) { developer }
let(:fields) do
<<~GRAPHQL
workItemTypes{
nodes { id name iconName }
}
GRAPHQL
end
let(:query) do
graphql_query_for(
'project',
{ 'fullPath' => project.full_path },
fields
)
end
context 'when user has access to the project' do
before do
post_graphql(query, current_user: current_user)
end
it_behaves_like 'a working graphql query'
it 'returns all default work item types' do
expect(graphql_data.dig('project', 'workItemTypes', 'nodes')).to match_array(
WorkItems::Type.default.map do |type|
hash_including('id' => type.to_global_id.to_s, 'name' => type.name, 'iconName' => type.icon_name)
end
)
end
end
context "when user doesn't have access to the project" do
let(:current_user) { create(:user) }
before do
post_graphql(query, current_user: current_user)
end
it 'does not return the project' do
expect(graphql_data).to eq('project' => nil)
end
end
context 'when the work_items feature flag is disabled' do
before do
stub_feature_flags(work_items: false)
post_graphql(query, current_user: current_user)
end
it 'makes the workItemTypes field unavailable' do
expect(graphql_errors).to contain_exactly(hash_including("message" => "Field 'workItemTypes' doesn't exist on type 'Project'"))
end
end
end