Commit f8d15ca6 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 3ab4feda
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
name: review-docs/$DOCS_GITLAB_REPO_SUFFIX-$CI_MERGE_REQUEST_IID name: review-docs/$DOCS_GITLAB_REPO_SUFFIX-$CI_MERGE_REQUEST_IID
# DOCS_REVIEW_APPS_DOMAIN and DOCS_GITLAB_REPO_SUFFIX are CI variables # 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 # 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 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 on_stop: review-docs-cleanup
before_script: before_script:
......
...@@ -337,7 +337,8 @@ const boardsStore = { ...@@ -337,7 +337,8 @@ const boardsStore = {
return ( return (
(listTo.type !== 'label' && listFrom.type === 'assignee') || (listTo.type !== 'label' && listFrom.type === 'assignee') ||
(listTo.type !== 'assignee' && listFrom.type === 'label') || (listTo.type !== 'assignee' && listFrom.type === 'label') ||
listFrom.type === 'backlog' listFrom.type === 'backlog' ||
listFrom.type === 'closed'
); );
}, },
moveIssueInList(list, issue, oldIndex, newIndex, idArray) { moveIssueInList(list, issue, oldIndex, newIndex, idArray) {
......
...@@ -39,7 +39,9 @@ export const GROUP_VISIBILITY_TYPE = { ...@@ -39,7 +39,9 @@ export const GROUP_VISIBILITY_TYPE = {
export const PROJECT_VISIBILITY_TYPE = { export const PROJECT_VISIBILITY_TYPE = {
public: __('Public - The project can be accessed without any authentication.'), public: __('Public - The project can be accessed without any authentication.'),
internal: __('Internal - The project can be accessed by any logged in user.'), 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 = { export const VISIBILITY_TYPE_ICON = {
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
module SystemNoteHelper module SystemNoteHelper
ICON_NAMES_BY_ACTION = { ICON_NAMES_BY_ACTION = {
'cherry_pick' => 'link', 'cherry_pick' => 'cherry-pick-commit',
'commit' => 'commit', 'commit' => 'commit',
'description' => 'pencil-square', 'description' => 'pencil-square',
'merge' => 'git-merge', 'merge' => 'git-merge',
......
...@@ -31,7 +31,7 @@ module VisibilityLevelHelper ...@@ -31,7 +31,7 @@ module VisibilityLevelHelper
def project_visibility_level_description(level) def project_visibility_level_description(level)
case level case level
when Gitlab::VisibilityLevel::PRIVATE 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 when Gitlab::VisibilityLevel::INTERNAL
_("The project can be accessed by any logged in user.") _("The project can be accessed by any logged in user.")
when Gitlab::VisibilityLevel::PUBLIC when Gitlab::VisibilityLevel::PUBLIC
......
...@@ -33,6 +33,12 @@ module Clusters ...@@ -33,6 +33,12 @@ module Clusters
FETCH_IP_ADDRESS_DELAY, application.name, application.id) FETCH_IP_ADDRESS_DELAY, application.name, application.id)
end end
end end
after_transition any => [:installed, :updated] do |application|
application.run_after_commit do
ClusterConfigureIstioWorker.perform_async(application.cluster_id)
end
end
end end
default_value_for :version, VERSION default_value_for :version, VERSION
...@@ -41,6 +47,8 @@ module Clusters ...@@ -41,6 +47,8 @@ module Clusters
scope :for_cluster, -> (cluster) { where(cluster: cluster) } scope :for_cluster, -> (cluster) { where(cluster: cluster) }
has_one :pages_domain, through: :serverless_domain_cluster
def chart def chart
'knative/knative' 'knative/knative'
end end
...@@ -49,6 +57,14 @@ module Clusters ...@@ -49,6 +57,14 @@ module Clusters
{ "domain" => hostname }.to_yaml { "domain" => hostname }.to_yaml
end 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? def allowed_to_uninstall?
!pre_installed? !pre_installed?
end end
......
...@@ -13,4 +13,6 @@ class ClusterApplicationEntity < Grape::Entity ...@@ -13,4 +13,6 @@ class ClusterApplicationEntity < Grape::Entity
expose :modsecurity_enabled, if: -> (e, _) { e.respond_to?(:modsecurity_enabled) } expose :modsecurity_enabled, if: -> (e, _) { e.respond_to?(:modsecurity_enabled) }
expose :update_available?, as: :update_available, if: -> (e, _) { e.respond_to?(:update_available?) } expose :update_available?, as: :update_available, if: -> (e, _) { e.respond_to?(:update_available?) }
expose :can_uninstall?, as: :can_uninstall 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 end
...@@ -94,7 +94,8 @@ class MergeRequestWidgetEntity < Grape::Entity ...@@ -94,7 +94,8 @@ class MergeRequestWidgetEntity < Grape::Entity
merge_request.source_project&.uses_default_ci_config? && merge_request.source_project&.uses_default_ci_config? &&
merge_request.all_pipelines.none? && merge_request.all_pipelines.none? &&
merge_request.commits_count.positive? && 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
end end
......
# frozen_string_literal: true
module Serverless
class DomainEntity < Grape::Entity
expose :id
expose :domain
end
end
...@@ -35,6 +35,12 @@ module Clusters ...@@ -35,6 +35,12 @@ module Clusters
application.oauth_application = create_oauth_application(application, request) application.oauth_application = create_oauth_application(application, request)
end 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) worker = worker_class(application)
application.make_scheduled! application.make_scheduled!
......
...@@ -27,6 +27,10 @@ module Clusters ...@@ -27,6 +27,10 @@ module Clusters
return configure_certificates if serverless_domain_cluster return configure_certificates if serverless_domain_cluster
configure_passthrough 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 end
private private
......
# 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
---
title: Resolves the disappearance of a ticket when it was moved from the closed list.
merge_request:
author: Gwen_
type: fixed
---
title: Add filepath to release links API
merge_request: 25533
author:
type: added
---
title: Change back internal api return code
merge_request: 26063
author:
type: fixed
---
title: Clarify private visibility for projects.
merge_request: 25852
author:
type: other
...@@ -7,7 +7,17 @@ module API ...@@ -7,7 +7,17 @@ module API
expose :id expose :id
expose :name expose :name
expose :url expose :url
expose :direct_asset_url
expose :external?, as: :external 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 end
end end
......
...@@ -50,7 +50,11 @@ module API ...@@ -50,7 +50,11 @@ module API
@project ||= access_checker.project @project ||= access_checker.project
result result
rescue Gitlab::GitAccess::ForbiddenError => e 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 rescue Gitlab::GitAccess::TimeoutError => e
return response_with_status(code: 503, success: false, message: e.message) return response_with_status(code: 503, success: false, message: e.message)
rescue Gitlab::GitAccess::NotFoundError => e rescue Gitlab::GitAccess::NotFoundError => e
......
...@@ -39,6 +39,7 @@ module API ...@@ -39,6 +39,7 @@ module API
params do params do
requires :name, type: String, desc: 'The name of the link' requires :name, type: String, desc: 'The name of the link'
requires :url, type: String, desc: 'The URL of the link' requires :url, type: String, desc: 'The URL of the link'
optional :filepath, type: String, desc: 'The filepath of the link'
end end
post 'links' do post 'links' do
authorize! :create_release, release authorize! :create_release, release
...@@ -73,6 +74,7 @@ module API ...@@ -73,6 +74,7 @@ module API
params do params do
optional :name, type: String, desc: 'The name of the link' optional :name, type: String, desc: 'The name of the link'
optional :url, type: String, desc: 'The URL 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 at_least_one_of :name, :url
end end
put do put do
......
# 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 ...@@ -60,7 +60,6 @@ module Gitlab
@logger = Checks::TimedLogger.new(timeout: INTERNAL_TIMEOUT, header: LOG_HEADER) @logger = Checks::TimedLogger.new(timeout: INTERNAL_TIMEOUT, header: LOG_HEADER)
@changes = changes @changes = changes
check_namespace!
check_protocol! check_protocol!
check_valid_actor! check_valid_actor!
check_active_user! check_active_user!
...@@ -72,11 +71,7 @@ module Gitlab ...@@ -72,11 +71,7 @@ module Gitlab
return custom_action if custom_action return custom_action if custom_action
check_db_accessibility!(cmd) check_db_accessibility!(cmd)
check_project!(changes, cmd)
ensure_project_on_push!(cmd, changes)
check_project_accessibility!
add_project_moved_message!
check_repository_existence! check_repository_existence!
case cmd case cmd
...@@ -113,6 +108,13 @@ module Gitlab ...@@ -113,6 +108,13 @@ module Gitlab
private 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) def check_custom_action(cmd)
nil nil
end end
......
...@@ -2,7 +2,13 @@ ...@@ -2,7 +2,13 @@
module Gitlab module Gitlab
class GitAccessSnippet < GitAccess class GitAccessSnippet < GitAccess
extend ::Gitlab::Utils::Override
ERROR_MESSAGES = { 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.', 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.' repository_not_found: 'The snippet repository you were looking for could not be found.'
}.freeze }.freeze
...@@ -12,25 +18,47 @@ module Gitlab ...@@ -12,25 +18,47 @@ module Gitlab
def initialize(actor, snippet, protocol, **kwargs) def initialize(actor, snippet, protocol, **kwargs)
@snippet = snippet @snippet = snippet
super(actor, project, protocol, **kwargs) super(actor, snippet&.project, protocol, **kwargs)
@auth_result_type = nil
@authentication_abilities &= [:download_code, :push_code]
end 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) unless Feature.enabled?(:version_snippets, user)
raise NotFoundError, ERROR_MESSAGES[:snippet_not_found] raise NotFoundError, ERROR_MESSAGES[:project_not_found]
end end
check_snippet_accessibility! check_snippet_accessibility!
success_result(cmd) super
end end
def project private
snippet&.project
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 end
private override :check_push_access!
def check_push_access!
raise UnauthorizedError, ERROR_MESSAGES[:update_snippet] unless user
check_change_access!
end
override :repository
def repository def repository
snippet&.repository snippet&.repository
end end
...@@ -39,10 +67,64 @@ module Gitlab ...@@ -39,10 +67,64 @@ module Gitlab
if snippet.blank? if snippet.blank?
raise NotFoundError, ERROR_MESSAGES[:snippet_not_found] raise NotFoundError, ERROR_MESSAGES[:snippet_not_found]
end end
end
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
unless repository&.exists? override :check_repository_existence!
def check_repository_existence!
unless repository.exists?
raise NotFoundError, ERROR_MESSAGES[:repository_not_found] raise NotFoundError, ERROR_MESSAGES[:repository_not_found]
end end
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
end end
...@@ -98,7 +98,7 @@ module Gitlab ...@@ -98,7 +98,7 @@ module Gitlab
@permission_cache ||= {} @permission_cache ||= {}
end end
def can_access_git? request_cache def can_access_git?
user && user.can?(:access_git) user && user.can?(:access_git)
end end
......
# 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 "" ...@@ -14396,7 +14396,7 @@ msgstr ""
msgid "Private" msgid "Private"
msgstr "" 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 "" msgstr ""
msgid "Private - The group and its projects can only be viewed by members." msgid "Private - The group and its projects can only be viewed by members."
...@@ -14834,7 +14834,7 @@ msgstr "" ...@@ -14834,7 +14834,7 @@ msgstr ""
msgid "Project URL" msgid "Project URL"
msgstr "" 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 "" msgstr ""
msgid "Project already deleted" msgid "Project already deleted"
......
...@@ -39,9 +39,15 @@ ...@@ -39,9 +39,15 @@
"stack": { "type": ["string", "null"] }, "stack": { "type": ["string", "null"] },
"modsecurity_enabled": { "type": ["boolean", "null"] }, "modsecurity_enabled": { "type": ["boolean", "null"] },
"update_available": { "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" ] "required" : [ "name", "status" ]
} },
"domain": { "id": "integer", "domain": "string" }
} }
} }
...@@ -4,7 +4,9 @@ ...@@ -4,7 +4,9 @@
"properties": { "properties": {
"id": { "type": "integer" }, "id": { "type": "integer" },
"name": { "type": "string" }, "name": { "type": "string" },
"filepath": { "type": "string" },
"url": { "type": "string" }, "url": { "type": "string" },
"direct_asset_url": { "type": "string" },
"external": { "type": "boolean" } "external": { "type": "boolean" }
}, },
"additionalProperties": false "additionalProperties": false
......
...@@ -59,7 +59,7 @@ describe VisibilityLevelHelper do ...@@ -59,7 +59,7 @@ describe VisibilityLevelHelper do
describe "#project_visibility_level_description" do describe "#project_visibility_level_description" do
it "describes private projects" do it "describes private projects" do
expect(project_visibility_level_description(Gitlab::VisibilityLevel::PRIVATE)) 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 end
it "describes public projects" do it "describes public projects" do
......
...@@ -14,7 +14,8 @@ export const GROUP_VISIBILITY_TYPE = { ...@@ -14,7 +14,8 @@ export const GROUP_VISIBILITY_TYPE = {
export const PROJECT_VISIBILITY_TYPE = { export const PROJECT_VISIBILITY_TYPE = {
public: 'Public - The project can be accessed without any authentication.', public: 'Public - The project can be accessed without any authentication.',
internal: 'Internal - The project can be accessed by any logged in user.', 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 = { export const VISIBILITY_TYPE_ICON = {
......
# 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 @@ ...@@ -3,24 +3,30 @@
require 'spec_helper' require 'spec_helper'
describe Gitlab::GitAccessSnippet do 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(: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(:protocol) { 'ssh' }
let(:changes) { Gitlab::GitAccess::ANY } let(:changes) { Gitlab::GitAccess::ANY }
let(:authentication_abilities) { [:download_code, :push_code] }
let(:push_access_check) { access.check('git-receive-pack', changes) } let(:push_access_check) { access.check('git-receive-pack', changes) }
let(:pull_access_check) { access.check('git-upload-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 subject(:access) { Gitlab::GitAccessSnippet.new(actor, snippet, protocol, authentication_abilities: authentication_abilities) }
it 'allows push and pull access' do
aggregate_failures do describe 'when actor is a DeployKey' do
expect { pull_access_check }.not_to raise_error let(:actor) { build(:deploy_key) }
expect { push_access_check }.not_to raise_error
end it 'does not allow push and pull access' do
expect { pull_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:authentication_mechanism])
end end
end end
...@@ -30,56 +36,186 @@ describe Gitlab::GitAccessSnippet do ...@@ -30,56 +36,186 @@ describe Gitlab::GitAccessSnippet do
end end
it 'does not allow push and pull access' do it 'does not allow push and pull access' do
aggregate_failures do expect { pull_access_check }.to raise_project_not_found
expect { push_access_check }.to raise_snippet_not_found
expect { pull_access_check }.to raise_snippet_not_found
end
end end
end end
describe '#check_snippet_accessibility!' do describe '#check_snippet_accessibility!' do
context 'when the snippet exists' do context 'when the snippet exists' do
it 'allows push and pull access' do it 'allows access' do
aggregate_failures do project.add_developer(actor)
expect { pull_access_check }.not_to raise_error
expect { push_access_check }.not_to raise_error expect { pull_access_check }.not_to raise_error
end
end end
end end
context 'when the snippet is nil' do context 'when the snippet is nil' do
let(:snippet) { nil } let(:snippet) { nil }
it 'blocks push and pull with "not found"' do it 'blocks access with "not found"' do
aggregate_failures do expect { pull_access_check }.to raise_snippet_not_found
expect { pull_access_check }.to raise_snippet_not_found
expect { push_access_check }.to raise_snippet_not_found
end
end end
end end
context 'when the snippet does not have a repository' do context 'when the snippet does not have a repository' do
let(:snippet) { build_stubbed(:personal_snippet) } let(:snippet) { build_stubbed(:personal_snippet) }
it 'blocks push and pull with "not found"' do it 'blocks access with "not found"' do
aggregate_failures do expect { pull_access_check }.to raise_snippet_not_found
expect { pull_access_check }.to raise_snippet_not_found end
expect { push_access_check }.to raise_snippet_not_found 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
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) }
where(:snippet_level, :membership, :_expected_count) do
permission_table_for_personal_snippet_access
end
def access with_them do
described_class.new(actor, snippet, protocol, it "respects accessibility" do
authentication_abilities: [], error_class = described_class::UnauthorizedError
namespace_path: nil, project_path: nil,
redirected_path: nil, auth_result_type: nil) 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 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 def raise_snippet_not_found
raise_error(Gitlab::GitAccess::NotFoundError, Gitlab::GitAccess::ERROR_MESSAGES[:snippet_not_found]) raise_error(Gitlab::GitAccess::NotFoundError, Gitlab::GitAccess::ERROR_MESSAGES[:snippet_not_found])
end 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 end
# 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 ...@@ -14,6 +14,7 @@ describe Clusters::Applications::Knative do
before do before do
allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_in) allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_in)
allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_async) allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_async)
allow(ClusterConfigureIstioWorker).to receive(:perform_async)
end end
describe 'associations' do describe 'associations' do
...@@ -47,6 +48,32 @@ describe Clusters::Applications::Knative do ...@@ -47,6 +48,32 @@ describe Clusters::Applications::Knative do
end end
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 describe '#can_uninstall?' do
subject { knative.can_uninstall? } subject { knative.can_uninstall? }
...@@ -196,4 +223,34 @@ describe Clusters::Applications::Knative do ...@@ -196,4 +223,34 @@ describe Clusters::Applications::Knative do
describe 'validations' do describe 'validations' do
it { is_expected.to validate_presence_of(:hostname) } it { is_expected.to validate_presence_of(:hostname) }
end 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 end
...@@ -409,7 +409,7 @@ describe API::Internal::Base do ...@@ -409,7 +409,7 @@ describe API::Internal::Base do
it do it do
pull(key, project) 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["status"]).to be_falsey
expect(user.reload.last_activity_on).to be_nil expect(user.reload.last_activity_on).to be_nil
end end
...@@ -419,7 +419,7 @@ describe API::Internal::Base do ...@@ -419,7 +419,7 @@ describe API::Internal::Base do
it do it do
push(key, project) 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["status"]).to be_falsey
expect(user.reload.last_activity_on).to be_nil expect(user.reload.last_activity_on).to be_nil
end end
...@@ -518,7 +518,7 @@ describe API::Internal::Base do ...@@ -518,7 +518,7 @@ describe API::Internal::Base do
it do it do
pull(key, personal_project) 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(json_response["status"]).to be_falsey
expect(user.reload.last_activity_on).to be_nil expect(user.reload.last_activity_on).to be_nil
end end
...@@ -528,7 +528,7 @@ describe API::Internal::Base do ...@@ -528,7 +528,7 @@ describe API::Internal::Base do
it do it do
push(key, personal_project) 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(json_response["status"]).to be_falsey
expect(user.reload.last_activity_on).to be_nil expect(user.reload.last_activity_on).to be_nil
end end
...@@ -572,7 +572,7 @@ describe API::Internal::Base do ...@@ -572,7 +572,7 @@ describe API::Internal::Base do
it do it do
push(key, project) 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["status"]).to be_falsey
end end
end end
...@@ -654,7 +654,7 @@ describe API::Internal::Base do ...@@ -654,7 +654,7 @@ describe API::Internal::Base do
it 'rejects the SSH push' do it 'rejects the SSH push' do
push(key, project) 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['status']).to be_falsey
expect(json_response['message']).to eq 'Git access over SSH is not allowed' expect(json_response['message']).to eq 'Git access over SSH is not allowed'
end end
...@@ -662,7 +662,7 @@ describe API::Internal::Base do ...@@ -662,7 +662,7 @@ describe API::Internal::Base do
it 'rejects the SSH pull' do it 'rejects the SSH pull' do
pull(key, project) 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['status']).to be_falsey
expect(json_response['message']).to eq 'Git access over SSH is not allowed' expect(json_response['message']).to eq 'Git access over SSH is not allowed'
end end
...@@ -676,7 +676,7 @@ describe API::Internal::Base do ...@@ -676,7 +676,7 @@ describe API::Internal::Base do
it 'rejects the HTTP push' do it 'rejects the HTTP push' do
push(key, project, 'http') 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['status']).to be_falsey
expect(json_response['message']).to eq 'Git access over HTTP is not allowed' expect(json_response['message']).to eq 'Git access over HTTP is not allowed'
end end
...@@ -684,7 +684,7 @@ describe API::Internal::Base do ...@@ -684,7 +684,7 @@ describe API::Internal::Base do
it 'rejects the HTTP pull' do it 'rejects the HTTP pull' do
pull(key, project, 'http') 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['status']).to be_falsey
expect(json_response['message']).to eq 'Git access over HTTP is not allowed' expect(json_response['message']).to eq 'Git access over HTTP is not allowed'
end end
......
...@@ -135,16 +135,44 @@ describe API::Release::Links do ...@@ -135,16 +135,44 @@ describe API::Release::Links do
end end
end 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 end
describe 'POST /projects/:id/releases/:tag_name/assets/links' do describe 'POST /projects/:id/releases/:tag_name/assets/links' do
let(:params) do let(:params) do
{ {
name: 'awesome-app.dmg', name: 'awesome-app.dmg',
filepath: '/binaries/awesome-app.dmg',
url: 'https://example.com/download/awesome-app.dmg' url: 'https://example.com/download/awesome-app.dmg'
} }
end end
let(:last_release_link) { release.links.last }
it 'accepts the request' do it 'accepts the request' do
post api("/projects/#{project.id}/releases/v0.1/assets/links", maintainer), params: params post api("/projects/#{project.id}/releases/v0.1/assets/links", maintainer), params: params
...@@ -157,8 +185,9 @@ describe API::Release::Links do ...@@ -157,8 +185,9 @@ describe API::Release::Links do
end.to change { Releases::Link.count }.by(1) end.to change { Releases::Link.count }.by(1)
release.reload release.reload
expect(release.links.last.name).to eq('awesome-app.dmg') expect(last_release_link.name).to eq('awesome-app.dmg')
expect(release.links.last.url).to eq('https://example.com/download/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 end
it 'matches response schema' do it 'matches response schema' do
......
...@@ -59,5 +59,23 @@ describe ClusterApplicationEntity do ...@@ -59,5 +59,23 @@ describe ClusterApplicationEntity do
expect(subject[:external_ip]).to eq('111.222.111.222') expect(subject[:external_ip]).to eq('111.222.111.222')
end end
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
end end
...@@ -123,6 +123,26 @@ describe MergeRequestWidgetEntity do ...@@ -123,6 +123,26 @@ describe MergeRequestWidgetEntity do
expect(subject[:merge_request_add_ci_config_path]).not_to be_nil expect(subject[:merge_request_add_ci_config_path]).not_to be_nil
end end
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 end
context 'when user does not have permissions' do context 'when user does not have permissions' do
......
# 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 ...@@ -137,10 +137,14 @@ describe Clusters::Applications::CreateService do
let(:params) do let(:params) do
{ {
application: 'knative', application: 'knative',
hostname: 'example.com' hostname: 'example.com',
pages_domain_id: domain.id
} }
end end
let(:domain) { create(:pages_domain, :instance_serverless) }
let(:associate_domain_service) { double('AssociateDomainService') }
before do before do
expect_any_instance_of(Clusters::Applications::Knative) expect_any_instance_of(Clusters::Applications::Knative)
.to receive(:make_scheduled!) .to receive(:make_scheduled!)
...@@ -158,6 +162,20 @@ describe Clusters::Applications::CreateService do ...@@ -158,6 +162,20 @@ describe Clusters::Applications::CreateService do
it 'sets the hostname' do it 'sets the hostname' do
expect(subject.hostname).to eq('example.com') expect(subject.hostname).to eq('example.com')
end 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 end
context 'elastic stack application' do context 'elastic stack application' do
......
...@@ -7,8 +7,9 @@ describe Clusters::Applications::UpdateService do ...@@ -7,8 +7,9 @@ describe Clusters::Applications::UpdateService do
let(:cluster) { create(:cluster, :project, :provided_by_gcp) } let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
let(:user) { create(:user) } 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(:service) { described_class.new(cluster, user, params) }
let(:domain) { create(:pages_domain, :instance_serverless) }
subject { service.execute(test_request) } subject { service.execute(test_request) }
...@@ -51,6 +52,24 @@ describe Clusters::Applications::UpdateService do ...@@ -51,6 +52,24 @@ describe Clusters::Applications::UpdateService do
subject subject
end 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 end
context 'application is not schedulable' do context 'application is not schedulable' do
......
...@@ -194,4 +194,36 @@ describe Clusters::Kubernetes::ConfigureIstioIngressService, '#execute' do ...@@ -194,4 +194,36 @@ describe Clusters::Kubernetes::ConfigureIstioIngressService, '#execute' do
) )
end end
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 end
# 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
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment