Commit c845aeb9 authored by Shinya Maeda's avatar Shinya Maeda

Add GraphQL API to update Canary Ingress Weight

Canary Ingress weight is to control the traffic
between stable and canary tracks in Auto Deployed
environment.
parent d3f5f211
......@@ -305,6 +305,10 @@ class Environment < ApplicationRecord
latest_opened_most_severe_alert.present?
end
def has_running_deployments?
all_deployments.running.exists?
end
def metrics
prometheus_adapter.query(:environment, self) if has_metrics_and_can_query?
end
......
......@@ -4,6 +4,11 @@ class EnvironmentEntity < Grape::Entity
include RequestAwareEntity
expose :id
expose :global_id do |environment|
environment.to_global_id.to_s
end
expose :name
expose :state
expose :external_url
......
......@@ -6618,6 +6618,41 @@ Identifier of Environment
"""
scalar EnvironmentID
"""
Autogenerated input type of EnvironmentsCanaryIngressUpdate
"""
input EnvironmentsCanaryIngressUpdateInput {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
The global ID of the environment to update
"""
id: EnvironmentID!
"""
The weight of the Canary Ingress
"""
weight: Int!
}
"""
Autogenerated return type of EnvironmentsCanaryIngressUpdate
"""
type EnvironmentsCanaryIngressUpdatePayload {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
Errors encountered during execution of the mutation.
"""
errors: [String!]!
}
"""
Represents an epic
"""
......@@ -13208,6 +13243,7 @@ type Mutation {
"""
discussionToggleResolve(input: DiscussionToggleResolveInput!): DiscussionToggleResolvePayload
dismissVulnerability(input: DismissVulnerabilityInput!): DismissVulnerabilityPayload @deprecated(reason: "Use vulnerabilityDismiss. Deprecated in 13.5")
environmentsCanaryIngressUpdate(input: EnvironmentsCanaryIngressUpdateInput!): EnvironmentsCanaryIngressUpdatePayload
epicAddIssue(input: EpicAddIssueInput!): EpicAddIssuePayload
epicSetSubscription(input: EpicSetSubscriptionInput!): EpicSetSubscriptionPayload
epicTreeReorder(input: EpicTreeReorderInput!): EpicTreeReorderPayload
......
......@@ -18349,6 +18349,108 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "INPUT_OBJECT",
"name": "EnvironmentsCanaryIngressUpdateInput",
"description": "Autogenerated input type of EnvironmentsCanaryIngressUpdate",
"fields": null,
"inputFields": [
{
"name": "id",
"description": "The global ID of the environment to update",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "EnvironmentID",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "weight",
"description": "The weight of the Canary Ingress",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "clientMutationId",
"description": "A unique identifier for the client performing the mutation.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
}
],
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "EnvironmentsCanaryIngressUpdatePayload",
"description": "Autogenerated return type of EnvironmentsCanaryIngressUpdate",
"fields": [
{
"name": "clientMutationId",
"description": "A unique identifier for the client performing the mutation.",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "errors",
"description": "Errors encountered during execution of the mutation.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
}
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "Epic",
......@@ -37454,6 +37556,33 @@
"isDeprecated": true,
"deprecationReason": "Use vulnerabilityDismiss. Deprecated in 13.5"
},
{
"name": "environmentsCanaryIngressUpdate",
"description": null,
"args": [
{
"name": "input",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "INPUT_OBJECT",
"name": "EnvironmentsCanaryIngressUpdateInput",
"ofType": null
}
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "EnvironmentsCanaryIngressUpdatePayload",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "epicAddIssue",
"description": null,
......@@ -1101,6 +1101,15 @@ Describes where code is deployed for a project.
| `path` | String! | The path to the environment. |
| `state` | String! | State of the environment, for example: available/stopped |
### EnvironmentsCanaryIngressUpdatePayload
Autogenerated return type of EnvironmentsCanaryIngressUpdate.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
### Epic
Represents an epic.
......
......@@ -13,6 +13,7 @@ module EE
mount_mutation ::Mutations::Issues::SetIteration
mount_mutation ::Mutations::Issues::SetWeight
mount_mutation ::Mutations::Issues::SetEpic
mount_mutation ::Mutations::Environments::CanaryIngress::Update
mount_mutation ::Mutations::EpicTree::Reorder
mount_mutation ::Mutations::Epics::Update
mount_mutation ::Mutations::Epics::Create
......
# frozen_string_literal: true
module Mutations
module Environments
module CanaryIngress
class Update < ::Mutations::BaseMutation
graphql_name 'EnvironmentsCanaryIngressUpdate'
authorize :update_environment
argument :id,
::Types::GlobalIDType[::Environment],
required: true,
description: 'The global ID of the environment to update'
argument :weight,
GraphQL::INT_TYPE,
required: true,
description: 'The weight of the Canary Ingress'
def resolve(id:, **kwargs)
environment = authorized_find!(id: id)
result = ::Environments::CanaryIngress::UpdateService
.new(environment.project, current_user, kwargs)
.execute(environment)
{ errors: Array.wrap(result[:message]) }
end
def find_object(id:)
# TODO: remove as part of https://gitlab.com/gitlab-org/gitlab/-/issues/257883
id = ::Types::GlobalIDType[::Environment].coerce_isolated_input(id)
GitlabSchema.find_by_gid(id)
end
end
end
end
end
......@@ -38,6 +38,15 @@ module EE
::Gitlab::Kubernetes::RolloutStatus.from_deployments(*deployments, pods_attrs: pods, ingresses: ingresses)
end
def ingresses(namespace)
ingresses = read_ingresses(namespace)
ingresses.map { |ingress| ::Gitlab::Kubernetes::Ingress.new(ingress) }
end
def patch_ingress(namespace, ingress, data)
kubeclient.patch_ingress(ingress.name, data, namespace)
end
private
def read_deployments(namespace)
......
......@@ -75,6 +75,18 @@ module EE
result || ::Gitlab::Kubernetes::RolloutStatus.loading
end
def ingresses
return unless rollout_status_available?
deployment_platform.ingresses(deployment_namespace)
end
def patch_ingress(ingress, data)
return unless rollout_status_available?
deployment_platform.patch_ingress(deployment_namespace, ingress, data)
end
private
def rollout_status_available?
......
# frozen_string_literal: true
module Environments
module CanaryIngress
class UpdateService < ::BaseService
def execute(environment)
result = validate(environment)
return result unless result[:status] == :success
canary_ingress = environment.ingresses&.find(&:canary?)
unless canary_ingress.present?
return error(_('Canary Ingress does not exist in the environment.'))
end
if environment.patch_ingress(canary_ingress, patch_data)
success
else
error(_('Failed to update the Canary Ingress.'), :bad_request)
end
end
private
def validate(environment)
unless Feature.enabled?(:canary_ingress_weight_control, environment.project)
return error(_("Feature flag is not enabled on the environment's project."))
end
unless can?(current_user, :update_environment, environment)
return error(_('You do not have permission to update the environment.'))
end
unless environment.project.feature_available?(:deploy_board)
return error(_('The license for Deploy Board is required to use this feature.'))
end
unless params[:weight].is_a?(Integer) && (0..100).cover?(params[:weight])
return error(_('Canary weight must be specified and valid range (0..100).'))
end
if environment.has_running_deployments?
return error(_('There are running deployments on the environment. Please retry later.'))
end
if ::Gitlab::ApplicationRateLimiter.throttled?(:update_environment_canary_ingress, scope: [environment])
return error(_("This environment's canary ingress has been updated recently. Please retry later."))
end
success
end
def patch_data
{
metadata: {
annotations: {
Gitlab::Kubernetes::Ingress::ANNOTATION_KEY_CANARY_WEIGHT => params[:weight].to_s
}
}
}
end
end
end
end
......@@ -28,6 +28,10 @@ module Gitlab
annotations[ANNOTATION_KEY_CANARY_WEIGHT].to_i
end
def name
metadata['name']
end
private
def metadata
......
......@@ -14,6 +14,7 @@
"id": {
"type": "integer"
},
"global_id": { "type": "string" },
"name": {
"type": "string"
},
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Mutations::Environments::CanaryIngress::Update do
let_it_be(:project) { create(:project) }
let_it_be(:environment) { create(:environment, project: project) }
let_it_be(:maintainer) { create(:user) }
let_it_be(:reporter) { create(:user) }
let(:user) { maintainer }
subject(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) }
before_all do
project.add_maintainer(maintainer)
project.add_reporter(reporter)
end
describe '#resolve' do
subject { mutation.resolve(id: environment_id, weight: weight) }
let(:environment_id) { environment.to_global_id.to_s }
let(:weight) { 50 }
let(:update_service) { double('update_service') }
before do
allow(Environments::CanaryIngress::UpdateService).to receive(:new) { update_service }
end
context 'when service execution succeeded' do
before do
allow(update_service).to receive(:execute) { { status: :success } }
end
it 'returns no errors' do
expect(subject[:errors]).to be_empty
end
end
context 'when service encounters a problem' do
before do
allow(update_service).to receive(:execute) { { status: :error, message: 'something went wrong' } }
end
it 'returns an error' do
expect(subject[:errors]).to eq(['something went wrong'])
end
end
context 'when environment is not found' do
let(:environment_id) { non_existing_record_id.to_s }
it 'raises an error' do
expect { subject }.to raise_error(GraphQL::CoercionError)
end
end
context 'when user is reporter who does not have permission to access the environment' do
let(:user) { reporter }
it 'raises an error' do
expect { subject }.to raise_error("The resource that you are attempting to access does not exist or you don't have permission to perform this action")
end
end
end
end
......@@ -39,6 +39,14 @@ RSpec.describe Gitlab::Kubernetes::Ingress do
end
end
describe '#name' do
subject { ingress.name }
let(:params) { stable_metadata }
it { is_expected.to eq('production-auto-deploy') }
end
def stable_metadata
kube_ingress(track: :stable)
end
......
......@@ -380,4 +380,62 @@ RSpec.describe Clusters::Platforms::Kubernetes do
it { is_expected.to include(deployments: [], ingresses: []) }
end
end
describe '#ingresses' do
subject { service.ingresses(namespace) }
let(:service) { create(:cluster_platform_kubernetes, :configured) }
let(:namespace) { 'project-namespace' }
context 'when there is an ingress in the namespace' do
before do
stub_kubeclient_ingresses(namespace)
end
it 'returns an ingress' do
expect(subject.count).to eq(1)
expect(subject.first).to be_kind_of(::Gitlab::Kubernetes::Ingress)
expect(subject.first.name).to eq('production-auto-deploy')
end
end
context 'when there are no ingresss in the namespace' do
before do
allow(service.kubeclient).to receive(:get_ingresses) { raise Kubeclient::ResourceNotFoundError.new(404, 'Not found', nil) }
end
it 'returns nothing' do
is_expected.to be_empty
end
end
end
describe '#patch_ingress' do
subject { service.patch_ingress(namespace, ingress, data) }
let(:service) { create(:cluster_platform_kubernetes, :configured) }
let(:namespace) { 'project-namespace' }
let(:ingress) { Gitlab::Kubernetes::Ingress.new(kube_ingress) }
let(:data) { { metadata: { annotations: { name: 'test' } } } }
context 'when there is an ingress in the namespace' do
before do
stub_kubeclient_ingresses(namespace, method: :patch, resource_path: "/#{ingress.name}")
end
it 'returns an ingress' do
expect(subject[:items][0][:metadata][:name]).to eq('production-auto-deploy')
end
end
context 'when there are no ingresss in the namespace' do
before do
allow(service.kubeclient).to receive(:patch_ingress) { raise Kubeclient::ResourceNotFoundError.new(404, 'Not found', nil) }
end
it 'raises an error' do
expect { subject }.to raise_error(Kubeclient::ResourceNotFoundError)
end
end
end
end
......@@ -229,4 +229,78 @@ RSpec.describe Environment, :use_clean_rails_memory_store_caching do
end
end
end
describe '#ingresses' do
subject { environment.ingresses }
let(:deployment_platform) { double(:deployment_platform) }
let(:deployment_namespace) { 'production' }
before do
allow(environment).to receive(:deployment_platform) { deployment_platform }
allow(environment).to receive(:deployment_namespace) { deployment_namespace }
end
context 'when rollout status is available' do
before do
allow(environment).to receive(:rollout_status_available?) { true }
end
it 'fetches ingresses from the deployment platform' do
expect(deployment_platform).to receive(:ingresses).with(deployment_namespace)
subject
end
end
context 'when rollout status is not available' do
before do
allow(environment).to receive(:rollout_status_available?) { false }
end
it 'does nothing' do
expect(deployment_platform).not_to receive(:ingresses)
subject
end
end
end
describe '#patch_ingress' do
subject { environment.patch_ingress(ingress, data) }
let(:ingress) { double(:ingress) }
let(:data) { double(:data) }
let(:deployment_platform) { double(:deployment_platform) }
let(:deployment_namespace) { 'production' }
before do
allow(environment).to receive(:deployment_platform) { deployment_platform }
allow(environment).to receive(:deployment_namespace) { deployment_namespace }
end
context 'when rollout status is available' do
before do
allow(environment).to receive(:rollout_status_available?) { true }
end
it 'fetches ingresses from the deployment platform' do
expect(deployment_platform).to receive(:patch_ingress).with(deployment_namespace, ingress, data)
subject
end
end
context 'when rollout status is not available' do
before do
allow(environment).to receive(:rollout_status_available?) { false }
end
it 'does nothing' do
expect(deployment_platform).not_to receive(:patch_ingress)
subject
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Update Environment Canary Ingress', :clean_gitlab_redis_cache do
include GraphqlHelpers
include KubernetesHelpers
let_it_be(:project) { create(:project, :repository) }
let_it_be(:cluster) { create(:cluster, :project, projects: [project]) }
let_it_be(:service) { create(:cluster_platform_kubernetes, :configured, cluster: cluster) }
let_it_be(:environment) { create(:environment, project: project) }
let_it_be(:deployment) { create(:deployment, :success, environment: environment, project: project) }
let_it_be(:maintainer) { create(:user) }
let_it_be(:developer) { create(:user) }
let(:environment_id) { environment.to_global_id.to_s }
let(:weight) { 25 }
let(:actor) { developer }
let(:mutation) do
graphql_mutation(:environments_canary_ingress_update, id: environment_id, weight: weight)
end
before_all do
project.add_maintainer(maintainer)
project.add_developer(developer)
end
before do
stub_licensed_features(deploy_board: true, protected_environments: true)
stub_kubeclient_ingresses(environment.deployment_namespace, response: kube_ingresses_response(with_canary: true))
end
context 'when kubernetes accepted the patch request' do
before do
stub_kubeclient_ingresses(environment.deployment_namespace, method: :patch, resource_path: "/production-auto-deploy")
end
it 'updates successfully' do
post_graphql_mutation(mutation, current_user: actor)
expect(graphql_mutation_response(:environments_canary_ingress_update)['errors'])
.to be_empty
end
context 'when environment is protected and allowed to be deployed by only operator' do
before do
create(:protected_environment, :maintainers_can_deploy, name: environment.name, project: project)
end
it 'fails to update' do
post_graphql_mutation(mutation, current_user: actor)
expect(graphql_errors.first)
.to include('message' => "The resource that you are attempting to access does not exist or you don't have permission to perform this action")
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Environments::CanaryIngress::UpdateService, :clean_gitlab_redis_cache do
include KubernetesHelpers
let_it_be(:project, refind: true) { create(:project) }
let_it_be(:maintainer) { create(:user) }
let_it_be(:reporter) { create(:user) }
let(:user) { maintainer }
let(:params) { {} }
let(:service) { described_class.new(project, user, params) }
before_all do
project.add_maintainer(maintainer)
project.add_reporter(reporter)
end
before do
stub_licensed_features(deploy_board: true)
end
describe '#execute' do
subject { service.execute(environment) }
let(:environment) { create(:environment, project: project) }
let(:params) { { weight: 50 } }
let(:canary_ingress) { ::Gitlab::Kubernetes::Ingress.new(kube_ingress(track: :canary)) }
shared_examples_for 'failed request' do
it 'returns an error' do
expect(subject[:status]).to eq(:error)
expect(subject[:message]).to eq(message)
end
end
context 'when canary ingress is present in the environment' do
before do
allow(environment).to receive(:ingresses) { [canary_ingress] }
end
context 'when patch request succeeds' do
let(:patch_data) do
{
metadata: {
annotations: {
Gitlab::Kubernetes::Ingress::ANNOTATION_KEY_CANARY_WEIGHT => params[:weight].to_s
}
}
}
end
before do
allow(environment).to receive(:patch_ingress).with(canary_ingress, patch_data) { true }
end
it 'returns success' do
expect(subject[:status]).to eq(:success)
expect(subject[:message]).to be_nil
end
end
context 'when patch request does not succeed' do
before do
allow(environment).to receive(:patch_ingress) { false }
end
it_behaves_like 'failed request' do
let(:message) { 'Failed to update the Canary Ingress.' }
end
end
end
context 'when canary ingress is not present in the environment' do
it_behaves_like 'failed request' do
let(:message) { 'Canary Ingress does not exist in the environment.' }
end
end
context 'when canary_ingress_weight_control feature flag is disabled' do
before do
stub_feature_flags(canary_ingress_weight_control: false)
end
it_behaves_like 'failed request' do
let(:message) { "Feature flag is not enabled on the environment's project." }
end
end
context 'when the actor does not have permission to update environment' do
let(:user) { reporter }
it_behaves_like 'failed request' do
let(:message) { "You do not have permission to update the environment." }
end
end
context 'when project does not have an sufficient license' do
before do
stub_licensed_features(deploy_board: false)
end
it_behaves_like 'failed request' do
let(:message) { 'The license for Deploy Board is required to use this feature.' }
end
end
context 'when weight parameter is invalid' do
let(:params) { { weight: 'unknown' } }
it_behaves_like 'failed request' do
let(:message) { 'Canary weight must be specified and valid range (0..100).' }
end
end
context 'when no parameters exist' do
let(:params) { {} }
it_behaves_like 'failed request' do
let(:message) { 'Canary weight must be specified and valid range (0..100).' }
end
end
context 'when environment has a running deployment' do
before do
allow(environment).to receive(:has_running_deployments?) { true }
end
it_behaves_like 'failed request' do
let(:message) { 'There are running deployments on the environment. Please retry later.' }
end
end
context 'when canary ingress was updated recently' do
before do
allow(::Gitlab::ApplicationRateLimiter).to receive(:throttled?) { true }
end
it_behaves_like 'failed request' do
let(:message) { "This environment's canary ingress has been updated recently. Please retry later." }
end
end
end
end
......@@ -33,7 +33,8 @@ module Gitlab
group_import: { threshold: -> { application_settings.group_import_limit }, interval: 1.minute },
group_testing_hook: { threshold: 5, interval: 1.minute },
profile_add_new_email: { threshold: 5, interval: 1.minute },
profile_resend_email_confirmation: { threshold: 5, interval: 1.minute }
profile_resend_email_confirmation: { threshold: 5, interval: 1.minute },
update_environment_canary_ingress: { threshold: 1, interval: 1.minute }
}.freeze
end
......
......@@ -175,6 +175,16 @@ module Gitlab
end
end
def patch_ingress(*args)
extensions_client.discover unless extensions_client.discovered
if extensions_client.respond_to?(:patch_ingress)
extensions_client.patch_ingress(*args)
else
networking_client.patch_ingress(*args)
end
end
def create_or_update_cluster_role_binding(resource)
update_cluster_role_binding(resource)
end
......
......@@ -4782,6 +4782,12 @@ msgstr ""
msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
msgstr ""
msgid "Canary Ingress does not exist in the environment."
msgstr ""
msgid "Canary weight must be specified and valid range (0..100)."
msgstr ""
msgid "Cancel"
msgstr ""
......@@ -11342,6 +11348,9 @@ msgstr ""
msgid "Failed to update tag!"
msgstr ""
msgid "Failed to update the Canary Ingress."
msgstr ""
msgid "Failed to update."
msgstr ""
......@@ -11381,6 +11390,9 @@ msgstr ""
msgid "Feature Flags"
msgstr ""
msgid "Feature flag is not enabled on the environment's project."
msgstr ""
msgid "Feature flag was not removed."
msgstr ""
......@@ -26695,6 +26707,9 @@ msgstr ""
msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage."
msgstr ""
msgid "The license for Deploy Board is required to use this feature."
msgstr ""
msgid "The license key is invalid. Make sure it is exactly as you received it from GitLab Inc."
msgstr ""
......@@ -26977,6 +26992,9 @@ msgstr ""
msgid "There are no variables yet."
msgstr ""
msgid "There are running deployments on the environment. Please retry later."
msgstr ""
msgid "There is a limit of %{ci_project_subscriptions_limit} subscriptions from or to a project."
msgstr ""
......@@ -27349,6 +27367,9 @@ msgstr ""
msgid "This environment is being re-deployed"
msgstr ""
msgid "This environment's canary ingress has been updated recently. Please retry later."
msgstr ""
msgid "This epic already has the maximum number of child epics."
msgstr ""
......@@ -30713,6 +30734,9 @@ msgstr ""
msgid "You do not have permission to run the Web Terminal. Please contact a project administrator."
msgstr ""
msgid "You do not have permission to update the environment."
msgstr ""
msgid "You do not have permissions to run the import."
msgstr ""
......
......@@ -16,6 +16,7 @@
],
"properties": {
"id": { "type": "integer" },
"global_id": { "type": "string" },
"name": { "type": "string" },
"state": { "type": "string" },
"external_url": { "$ref": "types/nullable_string.json" },
......
......@@ -19,7 +19,7 @@ RSpec.describe Gitlab::Kubernetes::KubeClient do
case method_name
when /\A(get_|delete_)/
client.public_send(method_name)
when /\A(create_|update_)/
when /\A(create_|update_|patch_)/
client.public_send(method_name, {})
else
raise "Unknown method name #{method_name}"
......@@ -377,6 +377,34 @@ RSpec.describe Gitlab::Kubernetes::KubeClient do
end
end
describe '#patch_ingress' do
let(:extensions_client) { client.extensions_client }
let(:networking_client) { client.networking_client }
include_examples 'redirection not allowed', 'patch_ingress'
include_examples 'dns rebinding not allowed', 'patch_ingress'
it 'delegates to the extensions client' do
expect(extensions_client).to receive(:patch_ingress)
client.patch_ingress
end
context 'extensions does not have ingress for Kubernetes 1.22+ clusters' do
before do
WebMock
.stub_request(:get, api_url + '/apis/extensions/v1beta1')
.to_return(kube_response(kube_1_22_extensions_v1beta1_discovery_body))
end
it 'delegates to the apps client' do
expect(networking_client).to receive(:patch_ingress)
client.patch_ingress
end
end
end
describe 'istio API group' do
let(:istio_client) { client.istio_client }
......
......@@ -982,6 +982,22 @@ RSpec.describe Environment, :use_clean_rails_memory_store_caching do
end
end
describe '#has_running_deployments?' do
subject { environment.has_running_deployments? }
it 'return false when no deployments exist' do
is_expected.to eq(false)
end
context 'when deployment is running on the environment' do
let!(:deployment) { create(:deployment, :running, environment: environment) }
it 'return true' do
is_expected.to eq(true)
end
end
end
describe '#metrics' do
let(:project) { create(:prometheus_project) }
......
......@@ -25,7 +25,7 @@ RSpec.describe EnvironmentEntity do
end
it 'exposes core elements of environment' do
expect(subject).to include(:id, :name, :state, :environment_path)
expect(subject).to include(:id, :global_id, :name, :state, :environment_path)
end
it 'exposes folder path' do
......
......@@ -33,8 +33,8 @@ module KubernetesHelpers
kube_response(kube_deployments_body)
end
def kube_ingresses_response
kube_response(kube_ingresses_body)
def kube_ingresses_response(with_canary: false)
kube_response(kube_ingresses_body(with_canary: with_canary))
end
def stub_kubeclient_discover_base(api_url)
......@@ -155,12 +155,12 @@ module KubernetesHelpers
WebMock.stub_request(:get, deployments_url).to_return(response || kube_deployments_response)
end
def stub_kubeclient_ingresses(namespace, status: nil)
def stub_kubeclient_ingresses(namespace, status: nil, method: :get, resource_path: "", response: kube_ingresses_response)
stub_kubeclient_discover(service.api_url)
ingresses_url = service.api_url + "/apis/extensions/v1beta1/namespaces/#{namespace}/ingresses"
ingresses_url = service.api_url + "/apis/extensions/v1beta1/namespaces/#{namespace}/ingresses#{resource_path}"
response = { status: status } if status
WebMock.stub_request(:get, ingresses_url).to_return(response || kube_ingresses_response)
WebMock.stub_request(method, ingresses_url).to_return(response)
end
def stub_kubeclient_knative_services(options = {})
......@@ -546,10 +546,12 @@ module KubernetesHelpers
}
end
def kube_ingresses_body
def kube_ingresses_body(with_canary: false)
items = with_canary ? [kube_ingress, kube_ingress(track: :canary)] : [kube_ingress]
{
"kind" => "List",
"items" => [kube_ingress]
"items" => items
}
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