Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-08-07 15:10:17 +00:00
parent 5958e399de
commit 61d62a2960
29 changed files with 432 additions and 51 deletions

View File

@ -20,12 +20,8 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
def revoke
@personal_access_token = finder.find(params[:id])
if @personal_access_token.revoke!
flash[:notice] = _("Revoked personal access token %{personal_access_token_name}!") % { personal_access_token_name: @personal_access_token.name }
else
flash[:alert] = _("Could not revoke personal access token %{personal_access_token_name}.") % { personal_access_token_name: @personal_access_token.name }
end
service = PersonalAccessTokens::RevokeService.new(current_user, token: @personal_access_token).execute
service.success? ? flash[:notice] = service.message : flash[:alert] = service.message
redirect_to profile_personal_access_tokens_path
end

View File

@ -194,6 +194,10 @@ module ApplicationHelper
'https://' + promo_host
end
def contact_sales_url
promo_url + '/sales'
end
def support_url
Gitlab::CurrentSettings.current_application_settings.help_page_support_url.presence || promo_url + '/getting-help/'
end

View File

@ -284,7 +284,7 @@ module Ci
def expire_in=(value)
self.expire_at =
if value
ChronicDuration.parse(value)&.seconds&.from_now
::Gitlab::Ci::Build::Artifacts::ExpireInParser.new(value).seconds_from_now
end
end

View File

@ -3,7 +3,8 @@
class PersonalAccessTokenPolicy < BasePolicy
condition(:is_owner) { user && subject.user_id == user.id }
rule { is_owner | admin }.policy do
rule { is_owner | admin & ~blocked }.policy do
enable :read_token
enable :revoke_token
end
end

View File

@ -7,7 +7,7 @@ module Metrics
DASHBOARD_NAME = N_('K8s pod health')
# SHA256 hash of dashboard content
DASHBOARD_VERSION = '0515db7a99078a2423b037f99251ba16bd163603c0a30229ae8aa7386e96421c'
DASHBOARD_VERSION = '3a91b32f91b2dd3d90275333c0ea3630b3f3f37c4296ede5b5eef59bf523d66b'
SEQUENCE = [
STAGES::MetricEndpointInserter,

View File

@ -0,0 +1,36 @@
# frozen_string_literal: true
module PersonalAccessTokens
class RevokeService
attr_reader :token, :current_user
def initialize(current_user = nil, params = { token: nil })
@current_user = current_user
@token = params[:token]
end
def execute
return ServiceResponse.error(message: 'Not permitted to revoke') unless revocation_permitted?
if token.revoke!
ServiceResponse.success(message: success_message)
else
ServiceResponse.error(message: error_message)
end
end
private
def error_message
_("Could not revoke personal access token %{personal_access_token_name}.") % { personal_access_token_name: token.name }
end
def success_message
_("Revoked personal access token %{personal_access_token_name}!") % { personal_access_token_name: token.name }
end
def revocation_permitted?
Ability.allowed?(current_user, :revoke_token, token)
end
end
end

View File

@ -0,0 +1,5 @@
---
title: Replace deprecated button on vulnerability details page
merge_request: 38679
author:
type: other

View File

@ -0,0 +1,5 @@
---
title: Add support for never keyword in expire_in job artifacts
merge_request: 38578
author: Fabio Huser
type: added

View File

@ -15,55 +15,101 @@ panel_groups:
panels:
- title: "CPU usage"
type: "line-chart"
y_label: "Cores per pod"
y_label: "Cores per container"
metrics:
- id: pod_cpu_usage_seconds_total
query_range: 'rate(container_cpu_usage_seconds_total{pod="{{pod}}",container="POD"}[5m])'
query_range: >-
sum(
rate(container_cpu_usage_seconds_total{pod="{{pod}}",container!="POD"}[5m])
)
by (container)
unit: "cores"
label: pod
label: container
- title: "CPU throttling"
type: "line-chart"
y_label: "Cores per container"
metrics:
- id: pod_cpu_cfs_throttle
query_range: >-
sum(
rate(container_cpu_cfs_throttled_seconds_total{pod="{{pod}}"}[5m])
)
by (container)
unit: "cores"
label: container
- group: Memory metrics
panels:
- title: "Memory usage working set"
type: "line-chart"
y_label: "Working set memory (MiB)"
y_label: "Working set memory"
metrics:
- id: pod_memory_working_set
query_range: 'container_memory_working_set_bytes{pod="{{pod}}",container="POD"}/1024/1024'
unit: "MiB"
label: pod
query_range: >-
sum(
container_memory_working_set_bytes{pod="{{pod}}",container!="POD"}
) by (container)
unit: "bytes"
label: container
- group: Network metrics
panels:
- title: "Network Receive (In)"
type: "line-chart"
y_label: "Received (KiB/sec)"
y_label: "Received (bytes/sec)"
metrics:
- id: pod_network_receive
query_range: 'rate(container_network_receive_bytes_total{pod="{{pod}}",container="POD"}[5m])/1024'
unit: "KiB / sec"
query_range: >-
sum(
rate(
container_network_receive_bytes_total{pod="{{pod}}"}[5m]
)
) by (pod)
unit: "bytes"
label: pod
- title: "Network Transmit (Out)"
type: "line-chart"
y_label: "Transmitted (KiB/sec)"
y_label: "Transmitted (bytes/sec)"
metrics:
- id: pod_network_transmit
query_range: 'rate(container_network_transmit_bytes_total{pod="{{pod}}",container="POD"}[5m])/1024'
unit: "KiB / sec"
query_range: >-
sum(
rate(
container_network_transmit_bytes_total{pod="{{pod}}"}[5m]
)
) by (pod)
unit: bytes
label: pod
- group: Disk metrics
panels:
- title: "Disk Reads"
type: "line-chart"
y_label: "Disk reads (KiB/sec)"
y_label: "Disk reads (bytes/sec)"
metrics:
- id: pod_disk_reads
query_range: 'rate(container_fs_reads_bytes_total{container="POD",pod="{{pod}}"}[5m])/1024'
unit: "KiB / sec"
label: pod
query_range: >-
sum(
rate(
container_fs_reads_bytes_total{pod="{{pod}}", container!="POD"}[5m]
)
) by (container,device)
unit: "bytes / sec"
label: "{{container}} {{device}}"
- title: "Disk Writes"
type: "line-chart"
y_label: "Disk writes (KiB/sec)"
y_label: "Disk writes (bytes/sec)"
metrics:
- id: pod_disk_writes
query_range: 'rate(container_fs_writes_bytes_total{container="POD",pod="{{pod}}"}[5m])/1024'
unit: "KiB / sec"
label: pod
query_range: >-
sum(
rate(
container_fs_writes_bytes_total{pod="{{pod}}", container!="POD"}[5m]
)
) by (container,device)
unit: "bytes / sec"
label: "{{container}} {{device}}"

View File

@ -194,14 +194,13 @@ keys must be manually replicated to the **secondary** node.
1. Visit the **primary** node's **Admin Area > Geo**
(`/admin/geo/nodes`) in your browser.
1. Click the **New node** button.
![Add secondary node](img/adding_a_secondary_node.png)
![Add secondary node](img/adding_a_secondary_node_v13_3.png)
1. Fill in **Name** with the `gitlab_rails['geo_node_name']` in
`/etc/gitlab/gitlab.rb`. These values must always match *exactly*, character
for character.
1. Fill in **URL** with the `external_url` in `/etc/gitlab/gitlab.rb`. These
values must always match, but it doesn't matter if one ends with a `/` and
the other doesn't.
1. **Do NOT** check the **This is a primary node** checkbox.
1. Optionally, choose which groups or storage shards should be replicated by the
**secondary** node. Leave blank to replicate all. Read more in
[selective synchronization](#selective-synchronization).

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

View File

@ -204,6 +204,7 @@ control over how the Pages daemon runs and serves content in your environment.
| `external_https` | Configure Pages to bind to one or more secondary IP addresses, serving HTTPS requests. Multiple addresses can be given as an array, along with exact ports, for example `['1.2.3.4', '1.2.3.5:8063']`. Sets value for `listen_https`.
| `gitlab_client_http_timeout` | GitLab API HTTP client connection timeout in seconds (default: 10s).
| `gitlab_client_jwt_expiry` | JWT Token expiry time in seconds (default: 30s).
| `domain_config_source` | Domain configuration source (default: `disk`)
| `gitlab_id` | The OAuth application public ID. Leave blank to automatically fill when Pages authenticates with GitLab.
| `gitlab_secret` | The OAuth application secret. Leave blank to automatically fill when Pages authenticates with GitLab.
| `gitlab_server` | Server to use for authentication when access control is enabled; defaults to GitLab `external_url`.
@ -601,6 +602,43 @@ configuring a load balancer to work at the IP level, and so on. If you wish to
set up GitLab Pages on multiple servers, perform the above procedure for each
Pages server.
## Domain source configuration
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/217912) in GitLab 13.3.
GitLab Pages can use different sources to get domain configuration.
The default value is `nil`; however, GitLab Pages will default to `disk`.
```ruby
gitlab_pages['domain_config_source'] = nil
```
You can specify `gitlab` to enable [API-based configuration](#gitlab-api-based-configuration).
For more details see this [blog post](https://about.gitlab.com/blog/2020/08/03/how-gitlab-pages-uses-the-gitlab-api-to-serve-content/).
### GitLab API-based configuration
GitLab Pages can use an API-based configuration. This replaces disk source configuration, which
was used prior to GitLab 13.0. Follow these steps to enable it:
1. Add the following to your `/etc/gitlab/gitlab.erb` file:
```ruby
gitlab_pages['domain_config_source'] = "gitlab"
```
1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
If you encounter an issue, you can disable it by choosing `disk` or `nil`:
```ruby
gitlab_pages['domain_config_source'] = nil
```
For other common issues, see the [troubleshooting section](#failed-to-connect-to-the-internal-gitlab-api)
or report an issue.
## Backup
GitLab Pages are part of the [regular backup](../../raketasks/backup_restore.md), so there is no separate backup to configure.
@ -696,3 +734,24 @@ date > /var/opt/gitlab/gitlab-rails/shared/pages/.update
```
If you've customized the Pages storage path, adjust the command above to use your custom path.
### Failed to connect to the internal GitLab API
If you have enabled [API-based configuration](#gitlab-api-based-configuration) and see the following error:
```plaintext
ERRO[0010] Failed to connect to the internal GitLab API after 0.50s error="failed to connect to internal Pages API: HTTP status: 401"
```
If you are [Running GitLab Pages on a separate server](#running-gitlab-pages-on-a-separate-server)
you must copy the `/etc/gitlab/gitlab-secrets.json` file
from the **GitLab server** to the **Pages server** after upgrading to GitLab 13.3,
as described in that section.
Other reasons may include network connectivity issues between your
**GitLab server** and your **Pages server** such as firewall configurations or closed ports.
For example, if there is a connection timeout:
```plaintext
error="failed to connect to internal Pages API: Get \"https://gitlab.example.com:3000/api/v4/internal/pages/status\": net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)"
```

View File

@ -3184,8 +3184,11 @@ stored on GitLab. If the expiry time is not defined, it defaults to the
[instance wide setting](../../user/admin_area/settings/continuous_integration.md#default-artifacts-expiration-core-only)
(30 days by default).
You can use the **Keep** button on the job page to override expiration and
keep artifacts forever.
To override the expiration time and keep artifacts forever:
- Use the **Keep** button on the job page.
- Set the value of `expire_in` to `never`. [Available](https://gitlab.com/gitlab-org/gitlab/-/issues/22761)
in GitLab 13.3 and later.
After their expiry, artifacts are deleted hourly by default (via a cron job),
and are not accessible anymore.
@ -3200,6 +3203,7 @@ provided. Examples of valid values:
- `6 mos 1 day`
- `47 yrs 6 mos and 4d`
- `3 weeks and 2 days`
- `never`
To expire artifacts 1 week after being uploaded:

View File

@ -65,7 +65,7 @@ To create and add a new Kubernetes cluster to your project, group, or instance:
1. In the [IAM Management Console](https://console.aws.amazon.com/iam/home), create an EKS management IAM role.
To do so, follow the [Amazon EKS cluster IAM role](https://docs.aws.amazon.com/eks/latest/userguide/service_IAM_role.html) instructions
to create a IAM role suitable for managing the AWS EKS cluster's resources on your behalf.
In addition to the policies that guide suggests, you must also include the `AmazonEKSServicePolicy`
In addition to the policies that guide suggests, you must also include the `AmazonEKSClusterPolicy`
policy for this role in order for GitLab to manage the EKS cluster correctly.
1. In the [IAM Management Console](https://console.aws.amazon.com/iam/home), create an IAM role:
1. From the left panel, select **Roles**.
@ -208,7 +208,7 @@ NOTE: **Note:**
This role should be the role you created by following the
[EKS cluster IAM role](https://docs.aws.amazon.com/eks/latest/userguide/service_IAM_role.html) guide.
In addition to the policies that guide suggests, you must also include the
`AmazonEKSServicePolicy` policy for this role in order for GitLab to manage the EKS cluster correctly.
`AmazonEKSClusterPolicy` policy for this role in order for GitLab to manage the EKS cluster correctly.
## Existing EKS cluster

View File

@ -0,0 +1,45 @@
# frozen_string_literal: true
module Gitlab
module Ci
module Build
module Artifacts
class ExpireInParser
def self.validate_duration(value)
new(value).validate_duration
end
def initialize(value)
@value = value
end
def validate_duration
return true if never?
parse
rescue ChronicDuration::DurationParseError
false
end
def seconds_from_now
parse&.seconds&.from_now
end
private
attr_reader :value
def parse
return if never?
ChronicDuration.parse(value)
end
def never?
value.to_s.casecmp('never') == 0
end
end
end
end
end
end

View File

@ -42,7 +42,7 @@ module Gitlab
inclusion: { in: %w[on_success on_failure always],
message: 'should be on_success, on_failure ' \
'or always' }
validates :expire_in, duration: true
validates :expire_in, duration: { parser: ::Gitlab::Ci::Build::Artifacts::ExpireInParser }
end
end

View File

@ -6,17 +6,27 @@ module Gitlab
module LegacyValidationHelpers
private
def validate_duration(value)
value.is_a?(String) && ChronicDuration.parse(value)
def validate_duration(value, parser = nil)
return false unless value.is_a?(String)
if parser && parser.respond_to?(:validate_duration)
parser.validate_duration(value)
else
ChronicDuration.parse(value)
end
rescue ChronicDuration::DurationParseError
false
end
def validate_duration_limit(value, limit)
def validate_duration_limit(value, limit, parser = nil)
return false unless value.is_a?(String)
ChronicDuration.parse(value).second.from_now <
ChronicDuration.parse(limit).second.from_now
if parser && parser.respond_to?(:validate_duration_limit)
parser.validate_duration_limit(value, limit)
else
ChronicDuration.parse(value).second.from_now <
ChronicDuration.parse(limit).second.from_now
end
rescue ChronicDuration::DurationParseError
false
end

View File

@ -106,12 +106,12 @@ module Gitlab
include LegacyValidationHelpers
def validate_each(record, attribute, value)
unless validate_duration(value)
unless validate_duration(value, options[:parser])
record.errors.add(attribute, 'should be a duration')
end
if options[:limit]
unless validate_duration_limit(value, options[:limit])
unless validate_duration_limit(value, options[:limit], options[:parser])
record.errors.add(attribute, 'should not exceed the limit')
end
end

View File

@ -56,6 +56,9 @@ module Gitlab
},
terms_opt_in: {
tracking_category: 'Growth::Acquisition::Experiment::TermsOptIn'
},
contact_sales_btn_in_app: {
tracking_category: 'Growth::Conversion::Experiment::ContactSalesInApp'
}
}.freeze

View File

@ -3824,6 +3824,9 @@ msgstr ""
msgid "BillingPlans|per user"
msgstr ""
msgid "BillingPlan|Contact sales"
msgstr ""
msgid "BillingPlan|Upgrade"
msgstr ""
@ -21303,6 +21306,9 @@ msgstr ""
msgid "SecurityConfiguration|Could not retrieve configuration data. Please refresh the page, or try again later."
msgstr ""
msgid "SecurityConfiguration|Create Merge Request"
msgstr ""
msgid "SecurityConfiguration|Customize common SAST settings to suit your requirements. Configuration changes made here override those provided by GitLab and are excluded from updates. For details of more advanced configuration options, see the %{linkStart}GitLab SAST documentation%{linkEnd}."
msgstr ""

View File

@ -100,14 +100,11 @@ RSpec.describe 'Profile > Personal Access Tokens', :js do
context "when revocation fails" do
it "displays an error message" do
visit profile_personal_access_tokens_path
allow_any_instance_of(PersonalAccessToken).to receive(:update!).and_return(false)
errors = ActiveModel::Errors.new(PersonalAccessToken.new).tap { |e| e.add(:name, "cannot be nil") }
allow_any_instance_of(PersonalAccessToken).to receive(:errors).and_return(errors)
allow_any_instance_of(PersonalAccessTokens::RevokeService).to receive(:revocation_permitted?).and_return(false)
accept_confirm { click_on "Revoke" }
expect(active_personal_access_tokens).to have_text(personal_access_token.name)
expect(page).to have_content("Could not revoke")
expect(page).to have_content("Not permitted to revoke")
end
end
end

View File

@ -168,6 +168,19 @@ RSpec.describe ApplicationHelper do
it { expect(helper.active_when(false)).to eq(nil) }
end
describe '#contact_sales_url' do
subject { helper.contact_sales_url }
it 'passes a smoke test' do
is_expected.to eq('https://about.gitlab.com/sales')
end
it 'changes if promo_url changes' do
allow(helper).to receive(:promo_url).and_return('https://somewhere.else')
is_expected.to eq('https://somewhere.else/sales')
end
end
describe '#support_url' do
context 'when alternate support url is specified' do
let(:alternate_url) { 'http://company.example.com/getting-help' }

View File

@ -0,0 +1,55 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Ci::Build::Artifacts::ExpireInParser do
describe '.validate_duration' do
subject { described_class.validate_duration(value) }
context 'with never' do
let(:value) { 'never' }
it { is_expected.to be_truthy }
end
context 'with never value camelized' do
let(:value) { 'Never' }
it { is_expected.to be_truthy }
end
context 'with a duration' do
let(:value) { '1 Day' }
it { is_expected.to be_truthy }
end
context 'without a duration' do
let(:value) { 'something' }
it { is_expected.to be_falsy }
end
end
describe '#seconds_from_now' do
subject { described_class.new(value).seconds_from_now }
context 'with never' do
let(:value) { 'never' }
it { is_expected.to be_nil }
end
context 'with an empty string' do
let(:value) { '' }
it { is_expected.to be_nil }
end
context 'with a duration' do
let(:value) { '1 day' }
it { is_expected.to be_like_time(1.day.from_now) }
end
end
end

View File

@ -1559,6 +1559,21 @@ module Gitlab
})
end
it "returns artifacts with expire_in never keyword" do
config = YAML.dump({
rspec: {
script: "rspec",
artifacts: { paths: ["releases/"], expire_in: "never" }
}
})
config_processor = Gitlab::Ci::YamlProcessor.new(config)
builds = config_processor.stage_builds_attributes("test")
expect(builds.size).to eq(1)
expect(builds.first[:options][:artifacts][:expire_in]).to eq('never')
end
%w[on_success on_failure always].each do |when_state|
it "returns artifacts for when #{when_state} defined" do
config = YAML.dump({

View File

@ -14,7 +14,7 @@ RSpec.describe PersonalAccessTokenPolicy do
end
with_them do
context 'determine if a token is readable by a user' do
context 'determine if a token is readable or revocable by a user' do
let(:user) { build_stubbed(user_type) }
let(:token_owner) { owned_by_same_user ? user : build(:user) }
let(:token) { build(:personal_access_token, user: token_owner) }
@ -26,6 +26,17 @@ RSpec.describe PersonalAccessTokenPolicy do
end
it { is_expected.to(expected_permitted? ? be_allowed(:read_token) : be_disallowed(:read_token)) }
it { is_expected.to(expected_permitted? ? be_allowed(:revoke_token) : be_disallowed(:revoke_token)) }
end
end
context 'current_user is a blocked administrator', :enable_admin_mode do
subject { described_class.new(current_user, token) }
let(:current_user) { create(:user, :admin, :blocked) }
let(:token) { create(:personal_access_token) }
it { is_expected.to be_disallowed(:revoke_token) }
it { is_expected.to be_disallowed(:read_token) }
end
end

View File

@ -479,6 +479,16 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
expect(job.reload.artifacts_expire_at).to be_nil
end
end
context 'when value is never' do
let(:expire_in) { 'never' }
let(:default_artifacts_expire_in) { '5 days' }
it 'does not set expire_in' do
expect(response).to have_gitlab_http_status(:created)
expect(job.reload.artifacts_expire_at).to be_nil
end
end
end
end
end

View File

@ -73,7 +73,7 @@ RSpec.describe Ci::CreateJobArtifactsService do
expect(metadata_artifact.expire_at).to be_within(1.minute).of(expected_expire_at)
end
context 'when expire_in params is set' do
context 'when expire_in params is set to a specific value' do
before do
params.merge!('expire_in' => '2 hours')
end
@ -89,6 +89,23 @@ RSpec.describe Ci::CreateJobArtifactsService do
expect(metadata_artifact.expire_at).to be_within(1.minute).of(expected_expire_at)
end
end
context 'when expire_in params is set to `never`' do
before do
params.merge!('expire_in' => 'never')
end
it 'sets expiration date according to the parameter' do
expected_expire_at = nil
expect(subject).to be_truthy
archive_artifact, metadata_artifact = job.job_artifacts.last(2)
expect(job.artifacts_expire_at).to eq(expected_expire_at)
expect(archive_artifact.expire_at).to eq(expected_expire_at)
expect(metadata_artifact.expire_at).to eq(expected_expire_at)
end
end
end
end

View File

@ -0,0 +1,44 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe PersonalAccessTokens::RevokeService do
shared_examples_for 'a successfully revoked token' do
it { expect(subject.success?).to be true }
it { expect(service.token.revoked?).to be true }
end
shared_examples_for 'an unsuccessfully revoked token' do
it { expect(subject.success?).to be false }
it { expect(service.token.revoked?).to be false }
end
describe '#execute' do
subject { service.execute }
let(:service) { described_class.new(current_user, token: token) }
context 'when current_user is an administrator' do
let_it_be(:current_user) { create(:admin) }
let_it_be(:token) { create(:personal_access_token) }
it_behaves_like 'a successfully revoked token'
end
context 'when current_user is not an administrator' do
let_it_be(:current_user) { create(:user) }
context 'token belongs to a different user' do
let_it_be(:token) { create(:personal_access_token) }
it_behaves_like 'an unsuccessfully revoked token'
end
context 'token belongs to current_user' do
let_it_be(:token) { create(:personal_access_token, user: current_user) }
it_behaves_like 'a successfully revoked token'
end
end
end
end