Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
3ab4feda4d
commit
f8d15ca653
|
|
@ -16,6 +16,7 @@
|
|||
name: review-docs/$DOCS_GITLAB_REPO_SUFFIX-$CI_MERGE_REQUEST_IID
|
||||
# DOCS_REVIEW_APPS_DOMAIN and DOCS_GITLAB_REPO_SUFFIX are CI variables
|
||||
# Discussion: https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/14236/diffs#note_40140693
|
||||
auto_stop_in: 2 weeks
|
||||
url: http://docs-preview-$DOCS_GITLAB_REPO_SUFFIX-$CI_MERGE_REQUEST_IID.$DOCS_REVIEW_APPS_DOMAIN/$DOCS_GITLAB_REPO_SUFFIX
|
||||
on_stop: review-docs-cleanup
|
||||
before_script:
|
||||
|
|
|
|||
|
|
@ -337,7 +337,8 @@ const boardsStore = {
|
|||
return (
|
||||
(listTo.type !== 'label' && listFrom.type === 'assignee') ||
|
||||
(listTo.type !== 'assignee' && listFrom.type === 'label') ||
|
||||
listFrom.type === 'backlog'
|
||||
listFrom.type === 'backlog' ||
|
||||
listFrom.type === 'closed'
|
||||
);
|
||||
},
|
||||
moveIssueInList(list, issue, oldIndex, newIndex, idArray) {
|
||||
|
|
|
|||
|
|
@ -39,7 +39,9 @@ export const GROUP_VISIBILITY_TYPE = {
|
|||
export const PROJECT_VISIBILITY_TYPE = {
|
||||
public: __('Public - The project can be accessed without any authentication.'),
|
||||
internal: __('Internal - The project can be accessed by any logged in user.'),
|
||||
private: __('Private - Project access must be granted explicitly to each user.'),
|
||||
private: __(
|
||||
'Private - Project access must be granted explicitly to each user. If this project is part of a group, access will be granted to members of the group.',
|
||||
),
|
||||
};
|
||||
|
||||
export const VISIBILITY_TYPE_ICON = {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
module SystemNoteHelper
|
||||
ICON_NAMES_BY_ACTION = {
|
||||
'cherry_pick' => 'link',
|
||||
'cherry_pick' => 'cherry-pick-commit',
|
||||
'commit' => 'commit',
|
||||
'description' => 'pencil-square',
|
||||
'merge' => 'git-merge',
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ module VisibilityLevelHelper
|
|||
def project_visibility_level_description(level)
|
||||
case level
|
||||
when Gitlab::VisibilityLevel::PRIVATE
|
||||
_("Project access must be granted explicitly to each user.")
|
||||
_("Project access must be granted explicitly to each user. If this project is part of a group, access will be granted to members of the group.")
|
||||
when Gitlab::VisibilityLevel::INTERNAL
|
||||
_("The project can be accessed by any logged in user.")
|
||||
when Gitlab::VisibilityLevel::PUBLIC
|
||||
|
|
|
|||
|
|
@ -33,6 +33,12 @@ module Clusters
|
|||
FETCH_IP_ADDRESS_DELAY, application.name, application.id)
|
||||
end
|
||||
end
|
||||
|
||||
after_transition any => [:installed, :updated] do |application|
|
||||
application.run_after_commit do
|
||||
ClusterConfigureIstioWorker.perform_async(application.cluster_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
default_value_for :version, VERSION
|
||||
|
|
@ -41,6 +47,8 @@ module Clusters
|
|||
|
||||
scope :for_cluster, -> (cluster) { where(cluster: cluster) }
|
||||
|
||||
has_one :pages_domain, through: :serverless_domain_cluster
|
||||
|
||||
def chart
|
||||
'knative/knative'
|
||||
end
|
||||
|
|
@ -49,6 +57,14 @@ module Clusters
|
|||
{ "domain" => hostname }.to_yaml
|
||||
end
|
||||
|
||||
def available_domains
|
||||
PagesDomain.instance_serverless
|
||||
end
|
||||
|
||||
def find_available_domain(pages_domain_id)
|
||||
available_domains.find_by(id: pages_domain_id)
|
||||
end
|
||||
|
||||
def allowed_to_uninstall?
|
||||
!pre_installed?
|
||||
end
|
||||
|
|
|
|||
|
|
@ -13,4 +13,6 @@ class ClusterApplicationEntity < Grape::Entity
|
|||
expose :modsecurity_enabled, if: -> (e, _) { e.respond_to?(:modsecurity_enabled) }
|
||||
expose :update_available?, as: :update_available, if: -> (e, _) { e.respond_to?(:update_available?) }
|
||||
expose :can_uninstall?, as: :can_uninstall
|
||||
expose :available_domains, using: Serverless::DomainEntity, if: -> (e, _) { e.respond_to?(:available_domains) }
|
||||
expose :pages_domain, using: Serverless::DomainEntity, if: -> (e, _) { e.respond_to?(:pages_domain) }
|
||||
end
|
||||
|
|
|
|||
|
|
@ -94,7 +94,8 @@ class MergeRequestWidgetEntity < Grape::Entity
|
|||
merge_request.source_project&.uses_default_ci_config? &&
|
||||
merge_request.all_pipelines.none? &&
|
||||
merge_request.commits_count.positive? &&
|
||||
can?(current_user, :push_code, merge_request.source_project)
|
||||
can?(current_user, :read_build, merge_request.source_project) &&
|
||||
can?(current_user, :create_pipeline, merge_request.source_project)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Serverless
|
||||
class DomainEntity < Grape::Entity
|
||||
expose :id
|
||||
expose :domain
|
||||
end
|
||||
end
|
||||
|
|
@ -35,6 +35,12 @@ module Clusters
|
|||
application.oauth_application = create_oauth_application(application, request)
|
||||
end
|
||||
|
||||
if application.instance_of?(Knative)
|
||||
Serverless::AssociateDomainService
|
||||
.new(application, pages_domain_id: params[:pages_domain_id], creator: current_user)
|
||||
.execute
|
||||
end
|
||||
|
||||
worker = worker_class(application)
|
||||
|
||||
application.make_scheduled!
|
||||
|
|
|
|||
|
|
@ -27,6 +27,10 @@ module Clusters
|
|||
return configure_certificates if serverless_domain_cluster
|
||||
|
||||
configure_passthrough
|
||||
rescue Kubeclient::HttpError => e
|
||||
knative.make_errored!(_('Kubernetes error: %{error_code}') % { error_code: e.error_code })
|
||||
rescue StandardError
|
||||
knative.make_errored!(_('Failed to update.'))
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Serverless
|
||||
class AssociateDomainService
|
||||
PLACEHOLDER_HOSTNAME = 'example.com'.freeze
|
||||
|
||||
def initialize(knative, pages_domain_id:, creator:)
|
||||
@knative = knative
|
||||
@pages_domain_id = pages_domain_id
|
||||
@creator = creator
|
||||
end
|
||||
|
||||
def execute
|
||||
return if unchanged?
|
||||
|
||||
knative.hostname ||= PLACEHOLDER_HOSTNAME
|
||||
|
||||
knative.pages_domain = knative.find_available_domain(pages_domain_id)
|
||||
knative.serverless_domain_cluster.update(creator: creator) if knative.pages_domain
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :knative, :pages_domain_id, :creator
|
||||
|
||||
def unchanged?
|
||||
knative.pages_domain&.id == pages_domain_id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Resolves the disappearance of a ticket when it was moved from the closed list.
|
||||
merge_request:
|
||||
author: Gwen_
|
||||
type: fixed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add filepath to release links API
|
||||
merge_request: 25533
|
||||
author:
|
||||
type: added
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Change back internal api return code
|
||||
merge_request: 26063
|
||||
author:
|
||||
type: fixed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Clarify private visibility for projects.
|
||||
merge_request: 25852
|
||||
author:
|
||||
type: other
|
||||
|
|
@ -7,7 +7,17 @@ module API
|
|||
expose :id
|
||||
expose :name
|
||||
expose :url
|
||||
expose :direct_asset_url
|
||||
expose :external?, as: :external
|
||||
|
||||
def direct_asset_url
|
||||
return object.url unless object.filepath
|
||||
|
||||
release = object.release
|
||||
project = release.project
|
||||
|
||||
Gitlab::Routing.url_helpers.project_release_url(project, release) << object.filepath
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -50,7 +50,11 @@ module API
|
|||
@project ||= access_checker.project
|
||||
result
|
||||
rescue Gitlab::GitAccess::ForbiddenError => e
|
||||
return response_with_status(code: 403, success: false, message: e.message)
|
||||
# The return code needs to be 401. If we return 403
|
||||
# the custom message we return won't be shown to the user
|
||||
# and, instead, the default message 'GitLab: API is not accessible'
|
||||
# will be displayed
|
||||
return response_with_status(code: 401, success: false, message: e.message)
|
||||
rescue Gitlab::GitAccess::TimeoutError => e
|
||||
return response_with_status(code: 503, success: false, message: e.message)
|
||||
rescue Gitlab::GitAccess::NotFoundError => e
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ module API
|
|||
params do
|
||||
requires :name, type: String, desc: 'The name of the link'
|
||||
requires :url, type: String, desc: 'The URL of the link'
|
||||
optional :filepath, type: String, desc: 'The filepath of the link'
|
||||
end
|
||||
post 'links' do
|
||||
authorize! :create_release, release
|
||||
|
|
@ -73,6 +74,7 @@ module API
|
|||
params do
|
||||
optional :name, type: String, desc: 'The name of the link'
|
||||
optional :url, type: String, desc: 'The URL of the link'
|
||||
optional :filepath, type: String, desc: 'The filepath of the link'
|
||||
at_least_one_of :name, :url
|
||||
end
|
||||
put do
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Checks
|
||||
class SnippetCheck < BaseChecker
|
||||
ERROR_MESSAGES = {
|
||||
create_delete_branch: 'You can not create or delete branches.'
|
||||
}.freeze
|
||||
|
||||
ATTRIBUTES = %i[oldrev newrev ref branch_name tag_name logger].freeze
|
||||
attr_reader(*ATTRIBUTES)
|
||||
|
||||
def initialize(change, logger:)
|
||||
@oldrev, @newrev, @ref = change.values_at(:oldrev, :newrev, :ref)
|
||||
@branch_name = Gitlab::Git.branch_name(@ref)
|
||||
@tag_name = Gitlab::Git.tag_name(@ref)
|
||||
|
||||
@logger = logger
|
||||
@logger.append_message("Running checks for ref: #{@branch_name || @tag_name}")
|
||||
end
|
||||
|
||||
def exec
|
||||
if creation? || deletion?
|
||||
raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:create_delete_branch]
|
||||
end
|
||||
|
||||
# TODO: https://gitlab.com/gitlab-org/gitlab/issues/205628
|
||||
# Check operation will not result in more than one file in the repository
|
||||
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -60,7 +60,6 @@ module Gitlab
|
|||
@logger = Checks::TimedLogger.new(timeout: INTERNAL_TIMEOUT, header: LOG_HEADER)
|
||||
@changes = changes
|
||||
|
||||
check_namespace!
|
||||
check_protocol!
|
||||
check_valid_actor!
|
||||
check_active_user!
|
||||
|
|
@ -72,11 +71,7 @@ module Gitlab
|
|||
return custom_action if custom_action
|
||||
|
||||
check_db_accessibility!(cmd)
|
||||
|
||||
ensure_project_on_push!(cmd, changes)
|
||||
|
||||
check_project_accessibility!
|
||||
add_project_moved_message!
|
||||
check_project!(changes, cmd)
|
||||
check_repository_existence!
|
||||
|
||||
case cmd
|
||||
|
|
@ -113,6 +108,13 @@ module Gitlab
|
|||
|
||||
private
|
||||
|
||||
def check_project!(changes, cmd)
|
||||
check_namespace!
|
||||
ensure_project_on_push!(cmd, changes)
|
||||
check_project_accessibility!
|
||||
add_project_moved_message!
|
||||
end
|
||||
|
||||
def check_custom_action(cmd)
|
||||
nil
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,7 +2,13 @@
|
|||
|
||||
module Gitlab
|
||||
class GitAccessSnippet < GitAccess
|
||||
extend ::Gitlab::Utils::Override
|
||||
|
||||
ERROR_MESSAGES = {
|
||||
authentication_mechanism: 'The authentication mechanism is not supported.',
|
||||
read_snippet: 'You are not allowed to read this snippet.',
|
||||
update_snippet: 'You are not allowed to update this snippet.',
|
||||
project_not_found: 'The project you were looking for could not be found.',
|
||||
snippet_not_found: 'The snippet you were looking for could not be found.',
|
||||
repository_not_found: 'The snippet repository you were looking for could not be found.'
|
||||
}.freeze
|
||||
|
|
@ -12,25 +18,47 @@ module Gitlab
|
|||
def initialize(actor, snippet, protocol, **kwargs)
|
||||
@snippet = snippet
|
||||
|
||||
super(actor, project, protocol, **kwargs)
|
||||
super(actor, snippet&.project, protocol, **kwargs)
|
||||
|
||||
@auth_result_type = nil
|
||||
@authentication_abilities &= [:download_code, :push_code]
|
||||
end
|
||||
|
||||
def check(cmd, _changes)
|
||||
def check(cmd, changes)
|
||||
# TODO: Investigate if expanding actor/authentication types are needed.
|
||||
# https://gitlab.com/gitlab-org/gitlab/issues/202190
|
||||
if actor && !actor.is_a?(User) && !actor.instance_of?(Key)
|
||||
raise UnauthorizedError, ERROR_MESSAGES[:authentication_mechanism]
|
||||
end
|
||||
|
||||
unless Feature.enabled?(:version_snippets, user)
|
||||
raise NotFoundError, ERROR_MESSAGES[:snippet_not_found]
|
||||
raise NotFoundError, ERROR_MESSAGES[:project_not_found]
|
||||
end
|
||||
|
||||
check_snippet_accessibility!
|
||||
|
||||
success_result(cmd)
|
||||
end
|
||||
|
||||
def project
|
||||
snippet&.project
|
||||
super
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
override :check_project!
|
||||
def check_project!(cmd, changes)
|
||||
if snippet.is_a?(ProjectSnippet)
|
||||
check_namespace!
|
||||
check_project_accessibility!
|
||||
# TODO add add_project_moved_message! to handle non-project repo https://gitlab.com/gitlab-org/gitlab/issues/205646
|
||||
end
|
||||
end
|
||||
|
||||
override :check_push_access!
|
||||
def check_push_access!
|
||||
raise UnauthorizedError, ERROR_MESSAGES[:update_snippet] unless user
|
||||
|
||||
check_change_access!
|
||||
end
|
||||
|
||||
override :repository
|
||||
def repository
|
||||
snippet&.repository
|
||||
end
|
||||
|
|
@ -39,10 +67,64 @@ module Gitlab
|
|||
if snippet.blank?
|
||||
raise NotFoundError, ERROR_MESSAGES[:snippet_not_found]
|
||||
end
|
||||
end
|
||||
|
||||
unless repository&.exists?
|
||||
override :check_download_access!
|
||||
def check_download_access!
|
||||
passed = guest_can_download_code? || user_can_download_code?
|
||||
|
||||
unless passed
|
||||
raise UnauthorizedError, ERROR_MESSAGES[:read_snippet]
|
||||
end
|
||||
end
|
||||
|
||||
override :guest_can_download_code?
|
||||
def guest_can_download_code?
|
||||
Guest.can?(:read_snippet, snippet)
|
||||
end
|
||||
|
||||
override :user_can_download_code?
|
||||
def user_can_download_code?
|
||||
authentication_abilities.include?(:download_code) && user_access.can_do_action?(:read_snippet)
|
||||
end
|
||||
|
||||
override :check_change_access!
|
||||
def check_change_access!
|
||||
unless user_access.can_do_action?(:update_snippet)
|
||||
raise UnauthorizedError, ERROR_MESSAGES[:update_snippet]
|
||||
end
|
||||
|
||||
changes_list.each do |change|
|
||||
# If user does not have access to make at least one change, cancel all
|
||||
# push by allowing the exception to bubble up
|
||||
check_single_change_access(change)
|
||||
end
|
||||
end
|
||||
|
||||
def check_single_change_access(change)
|
||||
change_access = Checks::SnippetCheck.new(change, logger: logger)
|
||||
|
||||
change_access.exec
|
||||
rescue Checks::TimedLogger::TimeoutError
|
||||
raise TimeoutError, logger.full_message
|
||||
end
|
||||
|
||||
override :check_repository_existence!
|
||||
def check_repository_existence!
|
||||
unless repository.exists?
|
||||
raise NotFoundError, ERROR_MESSAGES[:repository_not_found]
|
||||
end
|
||||
end
|
||||
|
||||
override :user_access
|
||||
def user_access
|
||||
@user_access ||= UserAccessSnippet.new(user, snippet: snippet)
|
||||
end
|
||||
|
||||
# TODO: Implement EE/Geo https://gitlab.com/gitlab-org/gitlab/issues/205629
|
||||
override :check_custom_action
|
||||
def check_custom_action(cmd)
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ module Gitlab
|
|||
@permission_cache ||= {}
|
||||
end
|
||||
|
||||
def can_access_git?
|
||||
request_cache def can_access_git?
|
||||
user && user.can?(:access_git)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
class UserAccessSnippet < UserAccess
|
||||
extend ::Gitlab::Cache::RequestCache
|
||||
# TODO: apply override check https://gitlab.com/gitlab-org/gitlab/issues/205677
|
||||
|
||||
request_cache_key do
|
||||
[user&.id, snippet&.id]
|
||||
end
|
||||
|
||||
attr_reader :snippet
|
||||
|
||||
def initialize(user, snippet: nil)
|
||||
@user = user
|
||||
@snippet = snippet
|
||||
@project = snippet&.project
|
||||
end
|
||||
|
||||
def can_do_action?(action)
|
||||
return false unless can_access_git?
|
||||
|
||||
permission_cache[action] =
|
||||
permission_cache.fetch(action) do
|
||||
Ability.allowed?(user, action, snippet)
|
||||
end
|
||||
end
|
||||
|
||||
def can_create_tag?(ref)
|
||||
false
|
||||
end
|
||||
|
||||
def can_delete_branch?(ref)
|
||||
false
|
||||
end
|
||||
|
||||
def can_push_to_branch?(ref)
|
||||
super
|
||||
return false unless snippet
|
||||
return false unless can_do_action?(:update_snippet)
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def can_merge_to_branch?(ref)
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -14396,7 +14396,7 @@ msgstr ""
|
|||
msgid "Private"
|
||||
msgstr ""
|
||||
|
||||
msgid "Private - Project access must be granted explicitly to each user."
|
||||
msgid "Private - Project access must be granted explicitly to each user. If this project is part of a group, access will be granted to members of the group."
|
||||
msgstr ""
|
||||
|
||||
msgid "Private - The group and its projects can only be viewed by members."
|
||||
|
|
@ -14834,7 +14834,7 @@ msgstr ""
|
|||
msgid "Project URL"
|
||||
msgstr ""
|
||||
|
||||
msgid "Project access must be granted explicitly to each user."
|
||||
msgid "Project access must be granted explicitly to each user. If this project is part of a group, access will be granted to members of the group."
|
||||
msgstr ""
|
||||
|
||||
msgid "Project already deleted"
|
||||
|
|
|
|||
|
|
@ -39,9 +39,15 @@
|
|||
"stack": { "type": ["string", "null"] },
|
||||
"modsecurity_enabled": { "type": ["boolean", "null"] },
|
||||
"update_available": { "type": ["boolean", "null"] },
|
||||
"can_uninstall": { "type": "boolean" }
|
||||
"can_uninstall": { "type": "boolean" },
|
||||
"available_domains": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/definitions/domain" }
|
||||
},
|
||||
"pages_domain": { "type": [ { "$ref": "#/definitions/domain" }, "null"] }
|
||||
},
|
||||
"required" : [ "name", "status" ]
|
||||
}
|
||||
},
|
||||
"domain": { "id": "integer", "domain": "string" }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@
|
|||
"properties": {
|
||||
"id": { "type": "integer" },
|
||||
"name": { "type": "string" },
|
||||
"filepath": { "type": "string" },
|
||||
"url": { "type": "string" },
|
||||
"direct_asset_url": { "type": "string" },
|
||||
"external": { "type": "boolean" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ describe VisibilityLevelHelper do
|
|||
describe "#project_visibility_level_description" do
|
||||
it "describes private projects" do
|
||||
expect(project_visibility_level_description(Gitlab::VisibilityLevel::PRIVATE))
|
||||
.to eq _('Project access must be granted explicitly to each user.')
|
||||
.to eq _('Project access must be granted explicitly to each user. If this project is part of a group, access will be granted to members of the group.')
|
||||
end
|
||||
|
||||
it "describes public projects" do
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@ export const GROUP_VISIBILITY_TYPE = {
|
|||
export const PROJECT_VISIBILITY_TYPE = {
|
||||
public: 'Public - The project can be accessed without any authentication.',
|
||||
internal: 'Internal - The project can be accessed by any logged in user.',
|
||||
private: 'Private - Project access must be granted explicitly to each user.',
|
||||
private:
|
||||
'Private - Project access must be granted explicitly to each user. If this project is part of a group, access will be granted to members of the group.',
|
||||
};
|
||||
|
||||
export const VISIBILITY_TYPE_ICON = {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::Checks::SnippetCheck do
|
||||
include_context 'change access checks context'
|
||||
|
||||
let(:snippet) { create(:personal_snippet, :repository) }
|
||||
let(:user_access) { Gitlab::UserAccessSnippet.new(user, snippet: snippet) }
|
||||
|
||||
subject { Gitlab::Checks::SnippetCheck.new(changes, logger: logger) }
|
||||
|
||||
describe '#exec' do
|
||||
it 'does not raise any error' do
|
||||
expect { subject.exec }.not_to raise_error
|
||||
end
|
||||
|
||||
context 'trying to delete the branch' do
|
||||
let(:newrev) { '0000000000000000000000000000000000000000' }
|
||||
|
||||
it 'raises an error' do
|
||||
expect { subject.exec }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You can not create or delete branches.')
|
||||
end
|
||||
end
|
||||
|
||||
context 'trying to create the branch' do
|
||||
let(:oldrev) { '0000000000000000000000000000000000000000' }
|
||||
|
||||
it 'raises an error' do
|
||||
expect { subject.exec }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You can not create or delete branches.')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -3,24 +3,30 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::GitAccessSnippet do
|
||||
include GitHelpers
|
||||
include ProjectHelpers
|
||||
include TermsHelper
|
||||
include_context 'ProjectPolicyTable context'
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:personal_snippet) { create(:personal_snippet, :private, :repository) }
|
||||
let_it_be(:project) { create(:project, :public) }
|
||||
let_it_be(:snippet) { create(:project_snippet, :public, :repository, project: project) }
|
||||
|
||||
let(:actor) { user }
|
||||
let(:protocol) { 'ssh' }
|
||||
let(:changes) { Gitlab::GitAccess::ANY }
|
||||
let(:authentication_abilities) { [:download_code, :push_code] }
|
||||
|
||||
let(:push_access_check) { access.check('git-receive-pack', changes) }
|
||||
let(:pull_access_check) { access.check('git-upload-pack', changes) }
|
||||
let(:snippet) { personal_snippet }
|
||||
let(:actor) { personal_snippet.author }
|
||||
|
||||
describe 'when feature flag :version_snippets is enabled' do
|
||||
it 'allows push and pull access' do
|
||||
aggregate_failures do
|
||||
expect { pull_access_check }.not_to raise_error
|
||||
expect { push_access_check }.not_to raise_error
|
||||
end
|
||||
subject(:access) { Gitlab::GitAccessSnippet.new(actor, snippet, protocol, authentication_abilities: authentication_abilities) }
|
||||
|
||||
describe 'when actor is a DeployKey' do
|
||||
let(:actor) { build(:deploy_key) }
|
||||
|
||||
it 'does not allow push and pull access' do
|
||||
expect { pull_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:authentication_mechanism])
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -30,56 +36,186 @@ describe Gitlab::GitAccessSnippet do
|
|||
end
|
||||
|
||||
it 'does not allow push and pull access' do
|
||||
aggregate_failures do
|
||||
expect { push_access_check }.to raise_snippet_not_found
|
||||
expect { pull_access_check }.to raise_snippet_not_found
|
||||
end
|
||||
expect { pull_access_check }.to raise_project_not_found
|
||||
end
|
||||
end
|
||||
|
||||
describe '#check_snippet_accessibility!' do
|
||||
context 'when the snippet exists' do
|
||||
it 'allows push and pull access' do
|
||||
aggregate_failures do
|
||||
expect { pull_access_check }.not_to raise_error
|
||||
expect { push_access_check }.not_to raise_error
|
||||
end
|
||||
it 'allows access' do
|
||||
project.add_developer(actor)
|
||||
|
||||
expect { pull_access_check }.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the snippet is nil' do
|
||||
let(:snippet) { nil }
|
||||
|
||||
it 'blocks push and pull with "not found"' do
|
||||
aggregate_failures do
|
||||
expect { pull_access_check }.to raise_snippet_not_found
|
||||
expect { push_access_check }.to raise_snippet_not_found
|
||||
end
|
||||
it 'blocks access with "not found"' do
|
||||
expect { pull_access_check }.to raise_snippet_not_found
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the snippet does not have a repository' do
|
||||
let(:snippet) { build_stubbed(:personal_snippet) }
|
||||
|
||||
it 'blocks push and pull with "not found"' do
|
||||
aggregate_failures do
|
||||
expect { pull_access_check }.to raise_snippet_not_found
|
||||
expect { push_access_check }.to raise_snippet_not_found
|
||||
it 'blocks access with "not found"' do
|
||||
expect { pull_access_check }.to raise_snippet_not_found
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'terms are enforced', :aggregate_failures do
|
||||
before do
|
||||
enforce_terms
|
||||
end
|
||||
|
||||
let(:user) { snippet.author }
|
||||
|
||||
it 'blocks access when the user did not accept terms' do
|
||||
message = /must accept the Terms of Service in order to perform this action/
|
||||
|
||||
expect { push_access_check }.to raise_unauthorized(message)
|
||||
expect { pull_access_check }.to raise_unauthorized(message)
|
||||
end
|
||||
|
||||
it 'allows access when the user accepted the terms' do
|
||||
accept_terms(user)
|
||||
|
||||
expect { push_access_check }.not_to raise_error
|
||||
expect { pull_access_check }.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
context 'project snippet accessibility', :aggregate_failures do
|
||||
let(:snippet) { create(:project_snippet, :private, :repository, project: project) }
|
||||
let(:user) { membership == :author ? snippet.author : create_user_from_membership(project, membership) }
|
||||
|
||||
shared_examples_for 'checks accessibility' do
|
||||
[:anonymous, :non_member, :guest, :reporter, :maintainer, :admin, :author].each do |membership|
|
||||
context membership.to_s do
|
||||
let(:membership) { membership }
|
||||
|
||||
it 'respects accessibility' do
|
||||
if Ability.allowed?(user, :update_snippet, snippet)
|
||||
expect { push_access_check }.not_to raise_error
|
||||
else
|
||||
expect { push_access_check }.to raise_error(described_class::UnauthorizedError)
|
||||
end
|
||||
|
||||
if Ability.allowed?(user, :read_snippet, snippet)
|
||||
expect { pull_access_check }.not_to raise_error
|
||||
else
|
||||
expect { pull_access_check }.to raise_error(described_class::UnauthorizedError)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when project is public' do
|
||||
it_behaves_like 'checks accessibility'
|
||||
end
|
||||
|
||||
context 'when project is public but snippet feature is private' do
|
||||
let(:project) { create(:project, :public) }
|
||||
|
||||
before do
|
||||
update_feature_access_level(project, :private)
|
||||
end
|
||||
|
||||
it_behaves_like 'checks accessibility'
|
||||
end
|
||||
|
||||
context 'when project is not accessible' do
|
||||
let(:project) { create(:project, :private) }
|
||||
|
||||
[:anonymous, :non_member].each do |membership|
|
||||
context membership.to_s do
|
||||
let(:membership) { membership }
|
||||
|
||||
it 'respects accessibility' do
|
||||
expect { push_access_check }.to raise_error(described_class::NotFoundError)
|
||||
expect { pull_access_check }.to raise_error(described_class::NotFoundError)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
context 'personal snippet accessibility', :aggregate_failures do
|
||||
let(:snippet) { create(:personal_snippet, snippet_level, :repository) }
|
||||
let(:user) { membership == :author ? snippet.author : create_user_from_membership(nil, membership) }
|
||||
|
||||
def access
|
||||
described_class.new(actor, snippet, protocol,
|
||||
authentication_abilities: [],
|
||||
namespace_path: nil, project_path: nil,
|
||||
redirected_path: nil, auth_result_type: nil)
|
||||
where(:snippet_level, :membership, :_expected_count) do
|
||||
permission_table_for_personal_snippet_access
|
||||
end
|
||||
|
||||
with_them do
|
||||
it "respects accessibility" do
|
||||
error_class = described_class::UnauthorizedError
|
||||
|
||||
if Ability.allowed?(user, :update_snippet, snippet)
|
||||
expect { push_access_check }.not_to raise_error
|
||||
else
|
||||
expect { push_access_check }.to raise_error(error_class)
|
||||
end
|
||||
|
||||
if Ability.allowed?(user, :read_snippet, snippet)
|
||||
expect { pull_access_check }.not_to raise_error
|
||||
else
|
||||
expect { pull_access_check }.to raise_error(error_class)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when geo is enabled', if: Gitlab.ee? do
|
||||
let(:user) { snippet.author }
|
||||
let!(:primary_node) { FactoryBot.create(:geo_node, :primary) }
|
||||
|
||||
# Without override, push access would return Gitlab::GitAccessResult::CustomAction
|
||||
it 'skips geo for snippet' do
|
||||
allow(::Gitlab::Database).to receive(:read_only?).and_return(true)
|
||||
allow(::Gitlab::Geo).to receive(:secondary_with_primary?).and_return(true)
|
||||
|
||||
expect { push_access_check }.to raise_unauthorized(/You can't push code to a read-only GitLab instance/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when changes are specific' do
|
||||
let(:changes) { 'oldrev newrev ref' }
|
||||
let(:user) { snippet.author }
|
||||
|
||||
it 'does not raise error if SnippetCheck does not raise error' do
|
||||
expect_next_instance_of(Gitlab::Checks::SnippetCheck) do |check|
|
||||
expect(check).to receive(:exec).and_call_original
|
||||
end
|
||||
|
||||
expect { push_access_check }.not_to raise_error
|
||||
end
|
||||
|
||||
it 'raises error if SnippetCheck raises error' do
|
||||
expect_next_instance_of(Gitlab::Checks::SnippetCheck) do |check|
|
||||
allow(check).to receive(:exec).and_raise(Gitlab::GitAccess::UnauthorizedError, 'foo')
|
||||
end
|
||||
|
||||
expect { push_access_check }.to raise_unauthorized('foo')
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def raise_snippet_not_found
|
||||
raise_error(Gitlab::GitAccess::NotFoundError, Gitlab::GitAccess::ERROR_MESSAGES[:snippet_not_found])
|
||||
end
|
||||
|
||||
def raise_project_not_found
|
||||
raise_error(Gitlab::GitAccess::NotFoundError, Gitlab::GitAccess::ERROR_MESSAGES[:project_not_found])
|
||||
end
|
||||
|
||||
def raise_unauthorized(message)
|
||||
raise_error(Gitlab::GitAccess::UnauthorizedError, message)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,95 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::UserAccessSnippet do
|
||||
subject(:access) { described_class.new(user, snippet: snippet) }
|
||||
|
||||
let_it_be(:project) { create(:project, :private) }
|
||||
let_it_be(:snippet) { create(:project_snippet, :private, project: project) }
|
||||
let(:user) { create(:user) }
|
||||
|
||||
describe '#can_do_action?' do
|
||||
before do
|
||||
allow(Ability).to receive(:allowed?).and_call_original
|
||||
allow(Ability).to receive(:allowed?).with(user, :ability, snippet).and_return(:foo)
|
||||
end
|
||||
|
||||
context 'when can access_git' do
|
||||
it 'calls Ability#allowed? and returns its result' do
|
||||
expect(access.can_do_action?(:ability)).to eq(:foo)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when can not access_git' do
|
||||
it 'disallows access' do
|
||||
expect(Ability).to receive(:allowed?).with(user, :access_git, :global).and_return(false)
|
||||
|
||||
expect(access.can_do_action?(:ability)).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is nil' do
|
||||
let(:user) { nil }
|
||||
|
||||
it 'disallows access' do
|
||||
expect(access.can_do_action?(:ability)).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#can_push_to_branch?' do
|
||||
include ProjectHelpers
|
||||
|
||||
[:anonymous, :non_member, :guest, :reporter, :maintainer, :admin, :author].each do |membership|
|
||||
context membership.to_s do
|
||||
let(:user) do
|
||||
membership == :author ? snippet.author : create_user_from_membership(project, membership)
|
||||
end
|
||||
|
||||
context 'when can access_git' do
|
||||
it 'respects accessibility' do
|
||||
expected_result = Ability.allowed?(user, :update_snippet, snippet)
|
||||
|
||||
expect(access.can_push_to_branch?('random_branch')).to eq(expected_result)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when can not access_git' do
|
||||
it 'disallows access' do
|
||||
expect(Ability).to receive(:allowed?).with(user, :access_git, :global).and_return(false) if user
|
||||
|
||||
expect(access.can_push_to_branch?('random_branch')).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when snippet is nil' do
|
||||
let(:user) { create_user_from_membership(project, :admin) }
|
||||
let(:snippet) { nil }
|
||||
|
||||
it 'disallows access' do
|
||||
expect(access.can_push_to_branch?('random_branch')).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#can_create_tag?' do
|
||||
it 'returns false' do
|
||||
expect(access.can_create_tag?('random_tag')).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
describe '#can_delete_branch?' do
|
||||
it 'returns false' do
|
||||
expect(access.can_delete_branch?('random_branch')).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
describe '#can_merge_to_branch?' do
|
||||
it 'returns false' do
|
||||
expect(access.can_merge_to_branch?('random_branch')).to be_falsey
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -14,6 +14,7 @@ describe Clusters::Applications::Knative do
|
|||
before do
|
||||
allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_in)
|
||||
allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_async)
|
||||
allow(ClusterConfigureIstioWorker).to receive(:perform_async)
|
||||
end
|
||||
|
||||
describe 'associations' do
|
||||
|
|
@ -47,6 +48,32 @@ describe Clusters::Applications::Knative do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'configuring istio ingress gateway' do
|
||||
context 'after installed' do
|
||||
let(:application) { create(:clusters_applications_knative, :installing) }
|
||||
|
||||
before do
|
||||
application.make_installed!
|
||||
end
|
||||
|
||||
it 'schedules a ClusterConfigureIstioWorker' do
|
||||
expect(ClusterConfigureIstioWorker).to have_received(:perform_async).with(application.cluster_id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'after updated' do
|
||||
let(:application) { create(:clusters_applications_knative, :updating) }
|
||||
|
||||
before do
|
||||
application.make_installed!
|
||||
end
|
||||
|
||||
it 'schedules a ClusterConfigureIstioWorker' do
|
||||
expect(ClusterConfigureIstioWorker).to have_received(:perform_async).with(application.cluster_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#can_uninstall?' do
|
||||
subject { knative.can_uninstall? }
|
||||
|
||||
|
|
@ -196,4 +223,34 @@ describe Clusters::Applications::Knative do
|
|||
describe 'validations' do
|
||||
it { is_expected.to validate_presence_of(:hostname) }
|
||||
end
|
||||
|
||||
describe '#available_domains' do
|
||||
let!(:domain) { create(:pages_domain, :instance_serverless) }
|
||||
|
||||
it 'returns all instance serverless domains' do
|
||||
expect(PagesDomain).to receive(:instance_serverless).and_call_original
|
||||
|
||||
domains = subject.available_domains
|
||||
|
||||
expect(domains.length).to eq(1)
|
||||
expect(domains).to include(domain)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#find_available_domain' do
|
||||
let!(:domain) { create(:pages_domain, :instance_serverless) }
|
||||
|
||||
it 'returns the domain scoped to available domains' do
|
||||
expect(subject).to receive(:available_domains).and_call_original
|
||||
expect(subject.find_available_domain(domain.id)).to eq(domain)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#pages_domain' do
|
||||
let!(:sdc) { create(:serverless_domain_cluster, knative: knative) }
|
||||
|
||||
it 'returns the the associated pages domain' do
|
||||
expect(knative.reload.pages_domain).to eq(sdc.pages_domain)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -409,7 +409,7 @@ describe API::Internal::Base do
|
|||
it do
|
||||
pull(key, project)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
expect(response).to have_gitlab_http_status(:unauthorized)
|
||||
expect(json_response["status"]).to be_falsey
|
||||
expect(user.reload.last_activity_on).to be_nil
|
||||
end
|
||||
|
|
@ -419,7 +419,7 @@ describe API::Internal::Base do
|
|||
it do
|
||||
push(key, project)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
expect(response).to have_gitlab_http_status(:unauthorized)
|
||||
expect(json_response["status"]).to be_falsey
|
||||
expect(user.reload.last_activity_on).to be_nil
|
||||
end
|
||||
|
|
@ -518,7 +518,7 @@ describe API::Internal::Base do
|
|||
it do
|
||||
pull(key, personal_project)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
expect(response).to have_gitlab_http_status(:unauthorized)
|
||||
expect(json_response["status"]).to be_falsey
|
||||
expect(user.reload.last_activity_on).to be_nil
|
||||
end
|
||||
|
|
@ -528,7 +528,7 @@ describe API::Internal::Base do
|
|||
it do
|
||||
push(key, personal_project)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
expect(response).to have_gitlab_http_status(:unauthorized)
|
||||
expect(json_response["status"]).to be_falsey
|
||||
expect(user.reload.last_activity_on).to be_nil
|
||||
end
|
||||
|
|
@ -572,7 +572,7 @@ describe API::Internal::Base do
|
|||
it do
|
||||
push(key, project)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
expect(response).to have_gitlab_http_status(:unauthorized)
|
||||
expect(json_response["status"]).to be_falsey
|
||||
end
|
||||
end
|
||||
|
|
@ -654,7 +654,7 @@ describe API::Internal::Base do
|
|||
it 'rejects the SSH push' do
|
||||
push(key, project)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
expect(response).to have_gitlab_http_status(:unauthorized)
|
||||
expect(json_response['status']).to be_falsey
|
||||
expect(json_response['message']).to eq 'Git access over SSH is not allowed'
|
||||
end
|
||||
|
|
@ -662,7 +662,7 @@ describe API::Internal::Base do
|
|||
it 'rejects the SSH pull' do
|
||||
pull(key, project)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
expect(response).to have_gitlab_http_status(:unauthorized)
|
||||
expect(json_response['status']).to be_falsey
|
||||
expect(json_response['message']).to eq 'Git access over SSH is not allowed'
|
||||
end
|
||||
|
|
@ -676,7 +676,7 @@ describe API::Internal::Base do
|
|||
it 'rejects the HTTP push' do
|
||||
push(key, project, 'http')
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
expect(response).to have_gitlab_http_status(:unauthorized)
|
||||
expect(json_response['status']).to be_falsey
|
||||
expect(json_response['message']).to eq 'Git access over HTTP is not allowed'
|
||||
end
|
||||
|
|
@ -684,7 +684,7 @@ describe API::Internal::Base do
|
|||
it 'rejects the HTTP pull' do
|
||||
pull(key, project, 'http')
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
expect(response).to have_gitlab_http_status(:unauthorized)
|
||||
expect(json_response['status']).to be_falsey
|
||||
expect(json_response['message']).to eq 'Git access over HTTP is not allowed'
|
||||
end
|
||||
|
|
|
|||
|
|
@ -135,16 +135,44 @@ describe API::Release::Links do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#direct_asset_url' do
|
||||
let!(:link) { create(:release_link, release: release, url: url, filepath: filepath) }
|
||||
let(:url) { 'https://google.com/-/jobs/140463678/artifacts/download' }
|
||||
|
||||
context 'when filepath is provided' do
|
||||
let(:filepath) { '/bin/bigfile.exe' }
|
||||
|
||||
specify do
|
||||
get api("/projects/#{project.id}/releases/v0.1/assets/links/#{link.id}", maintainer)
|
||||
|
||||
expect(json_response['direct_asset_url']).to eq("http://localhost/#{project.namespace.path}/#{project.name}/-/releases/#{release.tag}/bin/bigfile.exe")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when filepath is not provided' do
|
||||
let(:filepath) { nil }
|
||||
|
||||
specify do
|
||||
get api("/projects/#{project.id}/releases/v0.1/assets/links/#{link.id}", maintainer)
|
||||
|
||||
expect(json_response['direct_asset_url']).to eq(url)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /projects/:id/releases/:tag_name/assets/links' do
|
||||
let(:params) do
|
||||
{
|
||||
name: 'awesome-app.dmg',
|
||||
filepath: '/binaries/awesome-app.dmg',
|
||||
url: 'https://example.com/download/awesome-app.dmg'
|
||||
}
|
||||
end
|
||||
|
||||
let(:last_release_link) { release.links.last }
|
||||
|
||||
it 'accepts the request' do
|
||||
post api("/projects/#{project.id}/releases/v0.1/assets/links", maintainer), params: params
|
||||
|
||||
|
|
@ -157,8 +185,9 @@ describe API::Release::Links do
|
|||
end.to change { Releases::Link.count }.by(1)
|
||||
|
||||
release.reload
|
||||
expect(release.links.last.name).to eq('awesome-app.dmg')
|
||||
expect(release.links.last.url).to eq('https://example.com/download/awesome-app.dmg')
|
||||
expect(last_release_link.name).to eq('awesome-app.dmg')
|
||||
expect(last_release_link.filepath).to eq('/binaries/awesome-app.dmg')
|
||||
expect(last_release_link.url).to eq('https://example.com/download/awesome-app.dmg')
|
||||
end
|
||||
|
||||
it 'matches response schema' do
|
||||
|
|
|
|||
|
|
@ -59,5 +59,23 @@ describe ClusterApplicationEntity do
|
|||
expect(subject[:external_ip]).to eq('111.222.111.222')
|
||||
end
|
||||
end
|
||||
|
||||
context 'for knative application' do
|
||||
let(:pages_domain) { create(:pages_domain, :instance_serverless) }
|
||||
let(:application) { build(:clusters_applications_knative, :installed) }
|
||||
|
||||
before do
|
||||
create(:serverless_domain_cluster, knative: application, pages_domain: pages_domain)
|
||||
end
|
||||
|
||||
it 'includes available domains' do
|
||||
expect(subject[:available_domains].length).to eq(1)
|
||||
expect(subject[:available_domains].first).to eq(id: pages_domain.id, domain: pages_domain.domain)
|
||||
end
|
||||
|
||||
it 'includes pages_domain' do
|
||||
expect(subject[:pages_domain]).to eq(id: pages_domain.id, domain: pages_domain.domain)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -123,6 +123,26 @@ describe MergeRequestWidgetEntity do
|
|||
expect(subject[:merge_request_add_ci_config_path]).not_to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when build feature is disabled' do
|
||||
before do
|
||||
project.project_feature.update(builds_access_level: ProjectFeature::DISABLED)
|
||||
end
|
||||
|
||||
it 'has no path' do
|
||||
expect(subject[:merge_request_add_ci_config_path]).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when creating the pipeline is not allowed' do
|
||||
before do
|
||||
user.state = 'blocked'
|
||||
end
|
||||
|
||||
it 'has no path' do
|
||||
expect(subject[:merge_request_add_ci_config_path]).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user does not have permissions' do
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Serverless::DomainEntity do
|
||||
describe '#as_json' do
|
||||
let(:domain) { create(:pages_domain, :instance_serverless) }
|
||||
|
||||
subject { described_class.new(domain).as_json }
|
||||
|
||||
it 'has an id' do
|
||||
expect(subject[:id]).to eq(domain.id)
|
||||
end
|
||||
|
||||
it 'has a domain' do
|
||||
expect(subject[:domain]).to eq(domain.domain)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -137,10 +137,14 @@ describe Clusters::Applications::CreateService do
|
|||
let(:params) do
|
||||
{
|
||||
application: 'knative',
|
||||
hostname: 'example.com'
|
||||
hostname: 'example.com',
|
||||
pages_domain_id: domain.id
|
||||
}
|
||||
end
|
||||
|
||||
let(:domain) { create(:pages_domain, :instance_serverless) }
|
||||
let(:associate_domain_service) { double('AssociateDomainService') }
|
||||
|
||||
before do
|
||||
expect_any_instance_of(Clusters::Applications::Knative)
|
||||
.to receive(:make_scheduled!)
|
||||
|
|
@ -158,6 +162,20 @@ describe Clusters::Applications::CreateService do
|
|||
it 'sets the hostname' do
|
||||
expect(subject.hostname).to eq('example.com')
|
||||
end
|
||||
|
||||
it 'executes AssociateDomainService' do
|
||||
expect(Serverless::AssociateDomainService).to receive(:new) do |knative, args|
|
||||
expect(knative).to be_a(Clusters::Applications::Knative)
|
||||
expect(args[:pages_domain_id]).to eq(params[:pages_domain_id])
|
||||
expect(args[:creator]).to eq(user)
|
||||
|
||||
associate_domain_service
|
||||
end
|
||||
|
||||
expect(associate_domain_service).to receive(:execute)
|
||||
|
||||
subject
|
||||
end
|
||||
end
|
||||
|
||||
context 'elastic stack application' do
|
||||
|
|
|
|||
|
|
@ -7,8 +7,9 @@ describe Clusters::Applications::UpdateService do
|
|||
|
||||
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
|
||||
let(:user) { create(:user) }
|
||||
let(:params) { { application: 'knative', hostname: 'udpate.example.com' } }
|
||||
let(:params) { { application: 'knative', hostname: 'update.example.com', pages_domain_id: domain.id } }
|
||||
let(:service) { described_class.new(cluster, user, params) }
|
||||
let(:domain) { create(:pages_domain, :instance_serverless) }
|
||||
|
||||
subject { service.execute(test_request) }
|
||||
|
||||
|
|
@ -51,6 +52,24 @@ describe Clusters::Applications::UpdateService do
|
|||
|
||||
subject
|
||||
end
|
||||
|
||||
context 'knative application' do
|
||||
let(:associate_domain_service) { double('AssociateDomainService') }
|
||||
|
||||
it 'executes AssociateDomainService' do
|
||||
expect(Serverless::AssociateDomainService).to receive(:new) do |knative, args|
|
||||
expect(knative.id).to eq(application.id)
|
||||
expect(args[:pages_domain_id]).to eq(params[:pages_domain_id])
|
||||
expect(args[:creator]).to eq(user)
|
||||
|
||||
associate_domain_service
|
||||
end
|
||||
|
||||
expect(associate_domain_service).to receive(:execute)
|
||||
|
||||
subject
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'application is not schedulable' do
|
||||
|
|
|
|||
|
|
@ -194,4 +194,36 @@ describe Clusters::Kubernetes::ConfigureIstioIngressService, '#execute' do
|
|||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there is an error' do
|
||||
before do
|
||||
cluster.application_knative = create(:clusters_applications_knative)
|
||||
|
||||
allow_next_instance_of(described_class) do |instance|
|
||||
allow(instance).to receive(:configure_passthrough).and_raise(error)
|
||||
end
|
||||
end
|
||||
|
||||
context 'Kubeclient::HttpError' do
|
||||
let(:error) { Kubeclient::HttpError.new(404, nil, nil) }
|
||||
|
||||
it 'puts Knative into an errored state' do
|
||||
subject
|
||||
|
||||
expect(cluster.application_knative).to be_errored
|
||||
expect(cluster.application_knative.status_reason).to eq('Kubernetes error: 404')
|
||||
end
|
||||
end
|
||||
|
||||
context 'StandardError' do
|
||||
let(:error) { RuntimeError.new('something went wrong') }
|
||||
|
||||
it 'puts Knative into an errored state' do
|
||||
subject
|
||||
|
||||
expect(cluster.application_knative).to be_errored
|
||||
expect(cluster.application_knative.status_reason).to eq('Failed to update.')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,74 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Serverless::AssociateDomainService do
|
||||
subject { described_class.new(knative, pages_domain_id: pages_domain_id, creator: creator) }
|
||||
|
||||
let(:sdc) { create(:serverless_domain_cluster, pages_domain: create(:pages_domain, :instance_serverless)) }
|
||||
let(:knative) { sdc.knative }
|
||||
let(:creator) { sdc.creator }
|
||||
let(:pages_domain_id) { sdc.pages_domain_id }
|
||||
|
||||
context 'when the domain is unchanged' do
|
||||
let(:creator) { create(:user) }
|
||||
|
||||
it 'does not update creator' do
|
||||
expect { subject.execute }.not_to change { sdc.reload.creator }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when domain is changed to nil' do
|
||||
let(:pages_domain_id) { nil }
|
||||
let(:creator) { create(:user) }
|
||||
|
||||
it 'removes the association between knative and the domain' do
|
||||
expect { subject.execute }.to change { knative.reload.pages_domain }.from(sdc.pages_domain).to(nil)
|
||||
end
|
||||
|
||||
it 'does not attempt to update creator' do
|
||||
expect { subject.execute }.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a new domain is associated' do
|
||||
let(:pages_domain_id) { create(:pages_domain, :instance_serverless).id }
|
||||
let(:creator) { create(:user) }
|
||||
|
||||
it 'creates an association with the domain' do
|
||||
expect { subject.execute }.to change { knative.pages_domain.id }.from(sdc.pages_domain.id).to(pages_domain_id)
|
||||
end
|
||||
|
||||
it 'updates creator' do
|
||||
expect { subject.execute }.to change { sdc.reload.creator }.from(sdc.creator).to(creator)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when knative is not authorized to use the pages domain' do
|
||||
let(:pages_domain_id) { create(:pages_domain).id }
|
||||
|
||||
before do
|
||||
expect(knative).to receive(:available_domains).and_return(PagesDomain.none)
|
||||
end
|
||||
|
||||
it 'sets pages_domain_id to nil' do
|
||||
expect { subject.execute }.to change { knative.reload.pages_domain }.from(sdc.pages_domain).to(nil)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when knative hostname is nil' do
|
||||
let(:knative) { build(:clusters_applications_knative, hostname: nil) }
|
||||
|
||||
it 'sets hostname to a placeholder value' do
|
||||
expect { subject.execute }.to change { knative.hostname }.to('example.com')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when knative hostname exists' do
|
||||
let(:knative) { build(:clusters_applications_knative, hostname: 'hostname.com') }
|
||||
|
||||
it 'does not change hostname' do
|
||||
expect { subject.execute }.not_to change { knative.hostname }
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Reference in New Issue