diff --git a/Gemfile b/Gemfile index 041489a9a280fbf6754450eb2a9022ce380422a2..2b1f4481437dfff6128bebe92cbad5fbb9f00385 100644 --- a/Gemfile +++ b/Gemfile @@ -645,7 +645,7 @@ gem 'spamcheck', '~> 1.3.0', feature_category: :insider_threat gem 'gitaly', '~> 17.8.0', feature_category: :gitaly # KAS GRPC protocol definitions -gem 'gitlab-kas-grpc', '~> 17.8.0', feature_category: :deployment_management +gem 'gitlab-kas-grpc', '~> 17.9.0.pre.rc2', feature_category: :deployment_management # Lock the version before issues below are resolved: # https://gitlab.com/gitlab-org/gitlab/-/issues/473169#note_2028352939 diff --git a/Gemfile.checksum b/Gemfile.checksum index 55c2f62bafeb57ba05a17545c336a5d62057eabf..a3c6699e7cf0f9d01bf29b7795cfbf8a5bc83043 100644 --- a/Gemfile.checksum +++ b/Gemfile.checksum @@ -233,7 +233,7 @@ {"name":"gitlab-glfm-markdown","version":"0.0.27","platform":"x86_64-darwin","checksum":"ec6775054481b3e07a97d4be945fe41d043f89dc1fa1d95cdfc6a70b439ea0e4"}, {"name":"gitlab-glfm-markdown","version":"0.0.27","platform":"x86_64-linux-gnu","checksum":"ebcccc617db4f669cd2de900e6d31fae5de67acedeb178d242063e338cb57050"}, {"name":"gitlab-glfm-markdown","version":"0.0.27","platform":"x86_64-linux-musl","checksum":"77cf356f2a2fc496ec17206d68a6a1fe4f4d680bc1aac2206c32ee5393611a15"}, -{"name":"gitlab-kas-grpc","version":"17.8.1","platform":"ruby","checksum":"fbb9cf7411d12a7dc491d688a80a1d4064bd92dfbbadce523a549b07d26226e6"}, +{"name":"gitlab-kas-grpc","version":"17.9.0.pre.rc2","platform":"ruby","checksum":"b4572e47d0807e51c7a8f7a43d44a196ebea40ed073b8cb95f72d20044ea4482"}, {"name":"gitlab-labkit","version":"0.37.0","platform":"ruby","checksum":"d2dd0a60db2149a9a8eebf2975dc23f54ac3ceb01bdba732eb1b26b86dfffa70"}, {"name":"gitlab-license","version":"2.6.0","platform":"ruby","checksum":"2c1f8ae73835640ec77bf758c1d0c9730635043c01cf77902f7976e826d7d016"}, {"name":"gitlab-mail_room","version":"0.0.25","platform":"ruby","checksum":"223ce7c3c0797b6015eaa37147884e6ddc7be9a7ee90a424358c96bc18613b1a"}, diff --git a/Gemfile.lock b/Gemfile.lock index 8b953f71cf439e512d420584f8215dc97b213599..826f590e2acf1ccc5e7a37d651f3d4b763a57e0b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -745,7 +745,7 @@ GEM nokogiri (~> 1, >= 1.10.8) gitlab-glfm-markdown (0.0.27) rb_sys (~> 0.9.109) - gitlab-kas-grpc (17.8.1) + gitlab-kas-grpc (17.9.0.pre.rc2) grpc (~> 1.0) gitlab-labkit (0.37.0) actionpack (>= 5.0.0, < 8.1.0) @@ -2094,7 +2094,7 @@ DEPENDENCIES gitlab-glfm-markdown (~> 0.0.21) gitlab-housekeeper! gitlab-http! - gitlab-kas-grpc (~> 17.8.0) + gitlab-kas-grpc (~> 17.9.0.pre.rc2) gitlab-labkit (~> 0.37.0) gitlab-license (~> 2.6) gitlab-mail_room (~> 0.0.24) diff --git a/Gemfile.next.checksum b/Gemfile.next.checksum index f79428fe10848894320dd60a5ac178cf3e53d161..ddc65e97d62982d39943d12172ef0e40b8e7a666 100644 --- a/Gemfile.next.checksum +++ b/Gemfile.next.checksum @@ -233,7 +233,7 @@ {"name":"gitlab-glfm-markdown","version":"0.0.27","platform":"x86_64-darwin","checksum":"ec6775054481b3e07a97d4be945fe41d043f89dc1fa1d95cdfc6a70b439ea0e4"}, {"name":"gitlab-glfm-markdown","version":"0.0.27","platform":"x86_64-linux-gnu","checksum":"ebcccc617db4f669cd2de900e6d31fae5de67acedeb178d242063e338cb57050"}, {"name":"gitlab-glfm-markdown","version":"0.0.27","platform":"x86_64-linux-musl","checksum":"77cf356f2a2fc496ec17206d68a6a1fe4f4d680bc1aac2206c32ee5393611a15"}, -{"name":"gitlab-kas-grpc","version":"17.8.1","platform":"ruby","checksum":"fbb9cf7411d12a7dc491d688a80a1d4064bd92dfbbadce523a549b07d26226e6"}, +{"name":"gitlab-kas-grpc","version":"17.9.0.pre.rc2","platform":"ruby","checksum":"b4572e47d0807e51c7a8f7a43d44a196ebea40ed073b8cb95f72d20044ea4482"}, {"name":"gitlab-labkit","version":"0.37.0","platform":"ruby","checksum":"d2dd0a60db2149a9a8eebf2975dc23f54ac3ceb01bdba732eb1b26b86dfffa70"}, {"name":"gitlab-license","version":"2.6.0","platform":"ruby","checksum":"2c1f8ae73835640ec77bf758c1d0c9730635043c01cf77902f7976e826d7d016"}, {"name":"gitlab-mail_room","version":"0.0.25","platform":"ruby","checksum":"223ce7c3c0797b6015eaa37147884e6ddc7be9a7ee90a424358c96bc18613b1a"}, diff --git a/Gemfile.next.lock b/Gemfile.next.lock index e43b98d99f3514b587c7edfa3e59fb4b0630e0b8..996ca6c3264af8ed33fdfa9df9aa1cd0898fff1e 100644 --- a/Gemfile.next.lock +++ b/Gemfile.next.lock @@ -757,7 +757,7 @@ GEM nokogiri (~> 1, >= 1.10.8) gitlab-glfm-markdown (0.0.27) rb_sys (~> 0.9.109) - gitlab-kas-grpc (17.8.1) + gitlab-kas-grpc (17.9.0.pre.rc2) grpc (~> 1.0) gitlab-labkit (0.37.0) actionpack (>= 5.0.0, < 8.1.0) @@ -2129,7 +2129,7 @@ DEPENDENCIES gitlab-glfm-markdown (~> 0.0.21) gitlab-housekeeper! gitlab-http! - gitlab-kas-grpc (~> 17.8.0) + gitlab-kas-grpc (~> 17.9.0.pre.rc2) gitlab-labkit (~> 0.37.0) gitlab-license (~> 2.6) gitlab-mail_room (~> 0.0.24) diff --git a/app/models/clusters/agents/managed_resource.rb b/app/models/clusters/agents/managed_resource.rb index 2989e526212d7b61924e4528dccfe7c194065b93..eb75dc4924b4f00ed151a2e7ae520c06e75b47cc 100644 --- a/app/models/clusters/agents/managed_resource.rb +++ b/app/models/clusters/agents/managed_resource.rb @@ -11,6 +11,12 @@ class ManagedResource < ApplicationRecord belongs_to :environment validates :template_name, length: { maximum: 1024 } + + enum :status, { + processing: 0, + completed: 1, + failed: 2 + } end end end diff --git a/lib/gitlab/ci/build/prerequisite/factory.rb b/lib/gitlab/ci/build/prerequisite/factory.rb index 60cdf7af418b223902db16e7cf2384659b2ba978..7b3cbd09de607609999e49e9d0a17fb39fd012cd 100644 --- a/lib/gitlab/ci/build/prerequisite/factory.rb +++ b/lib/gitlab/ci/build/prerequisite/factory.rb @@ -8,7 +8,7 @@ class Factory attr_reader :build def self.prerequisites - [KubernetesNamespace] + [KubernetesNamespace, ManagedResource] end def initialize(build) diff --git a/lib/gitlab/ci/build/prerequisite/managed_resource.rb b/lib/gitlab/ci/build/prerequisite/managed_resource.rb new file mode 100644 index 0000000000000000000000000000000000000000..6cfd05397daec871226c0b3786c991821b7d859c --- /dev/null +++ b/lib/gitlab/ci/build/prerequisite/managed_resource.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +module Gitlab + module Ci + module Build + module Prerequisite + class ManagedResource < Base + ManagedResourceError = Class.new(StandardError) + + DEFAULT_TEMPLATE_NAME = "default" + + def unmet? + return false unless resource_management_enabled? + + return false unless valid_for_managed_resources?(environment:, build:) + + !managed_resource&.completed? + end + + def complete! + return unless unmet? + + managed_resource = create_managed_resource + + response = ensure_environment + if response.errors.any? + managed_resource.update!(status: :failed) + raise ManagedResourceError, format_error_message(response.errors) + else + managed_resource.update!(status: :completed) + end + end + + private + + # TODO: Check "resource_management.enabled" flag in the follow-up MR. + def resource_management_enabled? + false + end + + def ensure_environment + template = begin + kas_client.get_environment_template(environment: environment, template_name: DEFAULT_TEMPLATE_NAME) + rescue GRPC::NotFound + kas_client.get_default_environment_template + end + + rendered_template = kas_client.render_environment_template( + template: template, + environment: environment, + build: build) + + kas_client.ensure_environment( + template: rendered_template, + environment: environment, + build: build) + end + + def valid_for_managed_resources?(environment:, build:) + environment&.cluster_agent && build.user + end + + def kas_client + @kas_client ||= Gitlab::Kas::Client.new + end + + def environment + build.deployment&.environment + end + + def managed_resource + Clusters::Agents::ManagedResource.find_by_build_id(build.id) + end + strong_memoize_attr :managed_resource + + def create_managed_resource + return managed_resource if managed_resource + + Clusters::Agents::ManagedResource.create!( + build: build, + project: build.project, + environment: environment, + cluster_agent: environment.cluster_agent) + end + + def format_error_message(object_errors) + "Failed to ensure the environment. #{object_errors.map(&:to_json).join(', ')}" + end + end + end + end + end +end diff --git a/lib/gitlab/kas/client.rb b/lib/gitlab/kas/client.rb index a19a69e69f5cb8db9c88996407e484db89346533..eea07f3fb2a89afda7fea9eb66541ee6f34e388e 100644 --- a/lib/gitlab/kas/client.rb +++ b/lib/gitlab/kas/client.rb @@ -10,7 +10,8 @@ class Client agent_tracker: Gitlab::Agent::AgentTracker::Rpc::AgentTracker::Stub, configuration_project: Gitlab::Agent::ConfigurationProject::Rpc::ConfigurationProject::Stub, autoflow: Gitlab::Agent::AutoFlow::Rpc::AutoFlow::Stub, - notifications: Gitlab::Agent::Notifications::Rpc::Notifications::Stub + notifications: Gitlab::Agent::Notifications::Rpc::Notifications::Stub, + managed_resources: Gitlab::Agent::ManagedResources::Rpc::Provisioner::Stub }.freeze AUTOFLOW_CI_VARIABLE_ENV_SCOPE = 'autoflow/internal-use' @@ -101,6 +102,51 @@ def send_autoflow_event(project:, type:, id:, data:) .cloud_event(request, metadata: metadata) end + def get_environment_template(environment:, template_name:) + project = environment.project + return unless project && environment.cluster_agent + + request = Gitlab::Agent::ManagedResources::Rpc::GetEnvironmentTemplateRequest.new( + template_name: template_name, + agent_name: environment.cluster_agent.name, + gitaly_info: gitaly_info(project), + gitaly_repository: repository(project), + default_branch: project.default_branch_or_main + ) + + stub_for(:managed_resources) + .get_environment_template(request, metadata: metadata) + .template + end + + def get_default_environment_template + request = Gitlab::Agent::ManagedResources::Rpc::GetDefaultEnvironmentTemplateRequest.new + stub_for(:managed_resources) + .get_default_environment_template(request, metadata: metadata) + .template + end + + def render_environment_template(template:, environment:, build:) + request = Gitlab::Agent::ManagedResources::Rpc::RenderEnvironmentTemplateRequest.new( + template: Gitlab::Agent::ManagedResources::EnvironmentTemplate.new( + name: template.name, + data: template.data), + info: templating_info(environment:, build:)) + stub_for(:managed_resources) + .render_environment_template(request, metadata: metadata) + .template + end + + def ensure_environment(template:, environment:, build:) + request = Gitlab::Agent::ManagedResources::Rpc::EnsureEnvironmentRequest.new( + template: Gitlab::Agent::ManagedResources::RenderedEnvironmentTemplate.new( + name: template.name, + data: template.data), + info: templating_info(environment:, build:)) + stub_for(:managed_resources) + .ensure_environment(request, metadata: metadata) + end + private def stub_for(service) @@ -148,6 +194,42 @@ def token def timeout Gitlab::Kas.client_timeout_seconds.seconds end + + def templating_info(environment:, build:) + agent = environment.cluster_agent + project = environment.project + return unless agent && project && build && build.user + + Gitlab::Agent::ManagedResources::TemplatingInfo.new( + agent: Gitlab::Agent::ManagedResources::Agent.new( + id: agent.id, + name: agent.name, + url: agent_url(project, agent.name)), + environment: Gitlab::Agent::ManagedResources::Environment.new( + id: environment.id, + name: environment.name, + slug: environment.slug, + page_url: environment_url(project, environment), + url: environment.external_url, + tier: environment.tier), + project: Gitlab::Agent::ManagedResources::Project.new( + id: project.id, + slug: project.path, + path: project.full_path, + url: project.web_url), + pipeline: Gitlab::Agent::ManagedResources::Pipeline.new(id: build.pipeline_id), + job: Gitlab::Agent::ManagedResources::Job.new(id: build.id), + user: Gitlab::Agent::ManagedResources::User.new(id: build.user_id, username: build.user.username) + ) + end + + def agent_url(project, agent_name) + Gitlab::Routing.url_helpers.project_cluster_agent_url(project, agent_name) + end + + def environment_url(project, environment) + Gitlab::Routing.url_helpers.project_environment_url(project, environment) + end end end end diff --git a/spec/factories/clusters/managed_resources.rb b/spec/factories/clusters/managed_resources.rb index 2c808a1e71bf020c06ba592ef7fcabcf03ace41a..f352bdccfad142640dbd3c51aa10247141d594c0 100644 --- a/spec/factories/clusters/managed_resources.rb +++ b/spec/factories/clusters/managed_resources.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true FactoryBot.define do - factory :clusters_managed_resource, class: 'Clusters::Agents::ManagedResource' do + factory :managed_resource, class: 'Clusters::Agents::ManagedResource' do project environment association :cluster_agent diff --git a/spec/lib/gitlab/ci/build/prerequisite/factory_spec.rb b/spec/lib/gitlab/ci/build/prerequisite/factory_spec.rb index a38ade4bfa513f0c77af660ea8506cc78b371333..20e1569a2f10a9ea275799e421c2efa4fd97eecc 100644 --- a/spec/lib/gitlab/ci/build/prerequisite/factory_spec.rb +++ b/spec/lib/gitlab/ci/build/prerequisite/factory_spec.rb @@ -12,17 +12,25 @@ unmet?: unmet) end + let(:managed_resource) do + instance_double( + Gitlab::Ci::Build::Prerequisite::ManagedResource, + unmet?: unmet) + end + subject { described_class.new(build).unmet } before do expect(Gitlab::Ci::Build::Prerequisite::KubernetesNamespace) .to receive(:new).with(build).and_return(kubernetes_namespace) + expect(Gitlab::Ci::Build::Prerequisite::ManagedResource) + .to receive(:new).with(build).and_return(managed_resource) end context 'prerequisite is unmet' do let(:unmet) { true } - it { is_expected.to eq [kubernetes_namespace] } + it { is_expected.to match_array [kubernetes_namespace, managed_resource] } end context 'prerequisite is met' do diff --git a/spec/lib/gitlab/ci/build/prerequisite/managed_resource_spec.rb b/spec/lib/gitlab/ci/build/prerequisite/managed_resource_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..6e7a2c4426d0445a08c838b5444e5b0b55af1dff --- /dev/null +++ b/spec/lib/gitlab/ci/build/prerequisite/managed_resource_spec.rb @@ -0,0 +1,225 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::Build::Prerequisite::ManagedResource, feature_category: :continuous_integration do + describe '#unmet?' do + let_it_be(:agent_management_project) { create(:project, :private, :repository) } + let_it_be(:cluster_agent) { create(:cluster_agent, project: agent_management_project) } + + let_it_be(:deployment_project) { create(:project, :private, :repository) } + let_it_be_with_reload(:environment) do + create(:environment, project: deployment_project, cluster_agent: cluster_agent) + end + + let_it_be(:user) { create(:user) } + let_it_be(:deployment) { create(:deployment, environment: environment, user: user) } + let_it_be_with_reload(:build) { create(:ci_build, environment: environment, user: user, deployment: deployment) } + let(:status) { :processing } + let!(:managed_resource) do + create(:managed_resource, + build: build, + project: deployment_project, + environment: environment, + cluster_agent: cluster_agent, + status: status) + end + + let(:instance) { described_class.new(build) } + + subject(:execute_unmet) { instance.unmet? } + + context 'when resource_management is not enabled' do + it 'returns false' do + expect(execute_unmet).to be_falsey + end + end + + context 'when resource_management is enabled' do + before do + allow(instance).to receive(:resource_management_enabled?).and_return(true) + end + + context 'when the build is valid for managed resources' do + context 'when the managed resource record does not exist' do + let!(:managed_resource) { nil } + + it { is_expected.to be_truthy } + end + + context 'when managed resources completed successfully' do + let!(:status) { :completed } + + it 'returns false`' do + expect(execute_unmet).to be_falsey + end + end + + context 'when managed resources failed' do + let!(:status) { :failed } + + it 'returns true' do + managed_resource.reload + expect(execute_unmet).to be_truthy + end + end + end + + context 'when the build does not have a deployment' do + let_it_be(:build) { create(:ci_build, deployment: nil) } + + it { is_expected.to be_falsey } + end + + context 'when the build does not have an environment' do + let_it_be(:build) { create(:ci_build, environment: nil) } + + it { is_expected.to be_falsey } + end + + context 'when the build does not have a cluster agent' do + before do + environment.update!(cluster_agent: nil) + end + + it { is_expected.to be_falsey } + end + end + end + + describe '#complete!' do + let_it_be(:agent_management_project) { create(:project, :private, :repository) } + let_it_be(:cluster_agent) { create(:cluster_agent, project: agent_management_project) } + + let_it_be(:deployment_project) { create(:project, :private, :repository) } + let_it_be(:environment) { create(:environment, project: deployment_project, cluster_agent: cluster_agent) } + let_it_be(:user) { create(:user) } + let_it_be(:deployment) { create(:deployment, environment: environment, user: user) } + let!(:build) { create(:ci_build, environment: environment, user: user, deployment: deployment) } + + let(:instance) { described_class.new(build) } + + subject(:execute_complete) { instance.complete! } + + before do + allow(Gitlab::Kas).to receive(:enabled?).and_return(true) + end + + context 'when resource_management is not enabled' do + it 'does nothing' do + expect(instance).not_to receive(:ensure_environment) + expect(execute_complete).to be_falsey + end + end + + context 'when resource_management is enabled' do + before do + allow(instance).to receive(:resource_management_enabled?).and_return(true) + end + + context 'when #unmet? returns false' do + before do + allow(instance).to receive(:unmet?).and_return(false) + end + + it 'does not ensure the environment and update the status' do + expect(instance).not_to receive(:ensure_environment) + expect(instance).not_to receive(:update_status) + + execute_complete + end + end + + context 'when #unmet? returns true' do + before do + allow(instance).to receive(:unmet?).and_return(true) + end + + context 'when the build is valid for managed resources' do + context 'when it successfully ensures the environment' do + before do + allow_next_instance_of(Gitlab::Kas::Client) do |kas_client| + allow(kas_client).to receive_messages(get_environment_template: double, + render_environment_template: double) + success_response = Gitlab::Agent::ManagedResources::Rpc::EnsureEnvironmentResponse.new(errors: []) + allow(kas_client).to receive(:ensure_environment).and_return(success_response) + end + end + + it 'creates the managed resource record with the completed status' do + expect { execute_complete }.to change { Clusters::Agents::ManagedResource.count }.by(1) + managed_resource = Clusters::Agents::ManagedResource.find_by_build_id(build.id) + expect(managed_resource.status).to eq("completed") + end + end + + context 'when get_environment_template raises GRPC::NotFound error' do + before do + allow_next_instance_of(Gitlab::Kas::Client) do |kas_client| + allow(kas_client).to receive(:get_environment_template).and_raise(GRPC::NotFound) + allow(kas_client).to receive_messages(get_default_environment_template: double, + render_environment_template: double) + success_response = Gitlab::Agent::ManagedResources::Rpc::EnsureEnvironmentResponse.new(errors: []) + allow(kas_client).to receive(:ensure_environment).and_return(success_response) + end + end + + it 'creates the managed resource record and ensures the environment' do + expect { execute_complete }.to change { Clusters::Agents::ManagedResource.count }.by(1) + managed_resource = Clusters::Agents::ManagedResource.find_by_build_id(build.id) + expect(managed_resource.status).to eq("completed") + end + end + + context 'when ensure_environment raises GRPC::InvalidArgument error' do + before do + allow_next_instance_of(Gitlab::Kas::Client) do |kas_client| + allow(kas_client).to receive_messages(get_environment_template: double, + render_environment_template: double) + allow(kas_client).to receive(:ensure_environment).and_raise(GRPC::InvalidArgument) + end + end + + it 'creates the managed resource record and leaves it with the processing status' do + expect { execute_complete }.to raise_error(GRPC::InvalidArgument) + managed_resource = Clusters::Agents::ManagedResource.find_by_build_id(build.id) + expect(managed_resource.status).to eq('processing') + end + end + + context 'when ensure_environment returns the successful response but with an error information' do + before do + allow_next_instance_of(Gitlab::Kas::Client) do |kas_client| + allow(kas_client).to receive_messages(get_environment_template: double, + render_environment_template: double) + object = { + group: 'group', + version: 'version', + kind: 'kind', + name: 'name', + namespace: 'namespace' + } + error_response = Gitlab::Agent::ManagedResources::Rpc::EnsureEnvironmentResponse.new( + errors: [Gitlab::Agent::ManagedResources::Rpc::ObjectError.new( + error: 'error message', + object: object + )] + ) + allow(kas_client).to receive(:ensure_environment).and_return(error_response) + end + end + + it 'tracks the error and creates the managed resource record with the failed status' do + error_message = 'Failed to ensure the environment. {"object":{"group":"group","apiVersion":"version",' \ + '"kind":"kind","namespace":"namespace","name":"name"},"error":"error message"}' + expect { execute_complete }.to raise_error( + Gitlab::Ci::Build::Prerequisite::ManagedResource::ManagedResourceError, error_message) + managed_resource = Clusters::Agents::ManagedResource.find_by_build_id(build.id) + expect(managed_resource.status).to eq('failed') + end + end + end + end + end + end +end diff --git a/spec/lib/gitlab/kas/client_spec.rb b/spec/lib/gitlab/kas/client_spec.rb index a163a3d5c53a449d63600e3a575fd2bfe9845ab6..4fda7e61e770c139f0c7488c9209b4dcbbb36ef5 100644 --- a/spec/lib/gitlab/kas/client_spec.rb +++ b/spec/lib/gitlab/kas/client_spec.rb @@ -253,5 +253,125 @@ client.list_agent_config_files(project: project) end end + + describe '#get_environment_template' do + let_it_be(:environment) { create(:environment, project: project, cluster_agent: agent) } + let(:stub) { instance_double(Gitlab::Agent::ManagedResources::Rpc::Provisioner::Stub) } + let(:request) { instance_double(Gitlab::Agent::ManagedResources::Rpc::GetEnvironmentTemplateRequest) } + let(:template) { double("templates", name: "test-template", data: "{}") } + let(:response) { double(Gitlab::Agent::ManagedResources::Rpc::GetEnvironmentTemplateResponse, template: template) } + let(:template_name) { 'default' } + + subject { client.get_environment_template(environment: environment, template_name: template_name) } + + before do + expect(Gitlab::Agent::ManagedResources::Rpc::Provisioner::Stub).to receive(:new) + .with('example.kas.internal', :this_channel_is_insecure, timeout: client.send(:timeout)) + .and_return(stub) + + expect(Gitlab::Agent::ManagedResources::Rpc::GetEnvironmentTemplateRequest).to receive(:new) + .with( + template_name: template_name, + agent_name: agent.name, + gitaly_info: instance_of(Gitlab::Agent::Entity::GitalyInfo), + gitaly_repository: instance_of(Gitlab::Agent::Entity::GitalyRepository), + default_branch: project.default_branch_or_main) + .and_return(request) + + expect(stub).to receive(:get_environment_template) + .with(request, metadata: { 'authorization' => 'bearer test-token' }) + .and_return(response) + end + + it { expect(subject).to eq(template) } + end + + describe '#get_default_environment_template' do + let(:stub) { instance_double(Gitlab::Agent::ManagedResources::Rpc::Provisioner::Stub) } + let(:request) { instance_double(Gitlab::Agent::ManagedResources::Rpc::GetDefaultEnvironmentTemplateRequest) } + let(:template) { double("templates", name: "test-template", data: "{}") } + let(:response) { double(Gitlab::Agent::ManagedResources::Rpc::GetDefaultEnvironmentTemplateResponse, template: template) } + + subject { client.get_default_environment_template } + + before do + expect(Gitlab::Agent::ManagedResources::Rpc::Provisioner::Stub).to receive(:new) + .with('example.kas.internal', :this_channel_is_insecure, timeout: client.send(:timeout)) + .and_return(stub) + + expect(Gitlab::Agent::ManagedResources::Rpc::GetDefaultEnvironmentTemplateRequest).to receive(:new) + .and_return(request) + + expect(stub).to receive(:get_default_environment_template) + .with(request, metadata: { 'authorization' => 'bearer test-token' }) + .and_return(response) + end + + it { expect(subject).to eq(template) } + end + + describe '#render_environment_template' do + let_it_be(:environment) { create(:environment, project: project, cluster_agent: agent) } + let_it_be(:user) { create(:user) } + let_it_be(:build) { create(:ci_build, user: user) } + let(:stub) { instance_double(Gitlab::Agent::ManagedResources::Rpc::Provisioner::Stub) } + let(:request) { instance_double(Gitlab::Agent::ManagedResources::Rpc::RenderEnvironmentTemplateRequest) } + let(:template) { double("templates", name: "test-template", data: "{}") } + let(:response) { double(Gitlab::Agent::ManagedResources::Rpc::RenderEnvironmentTemplateResponse, template: template) } + + subject { client.render_environment_template(template: template, environment: environment, build: build) } + + before do + expect(Gitlab::Agent::ManagedResources::Rpc::Provisioner::Stub).to receive(:new) + .with('example.kas.internal', :this_channel_is_insecure, timeout: client.send(:timeout)) + .and_return(stub) + + expect(Gitlab::Agent::ManagedResources::Rpc::RenderEnvironmentTemplateRequest).to receive(:new) + .with( + template: Gitlab::Agent::ManagedResources::EnvironmentTemplate.new( + name: template.name, + data: template.data), + info: instance_of(Gitlab::Agent::ManagedResources::TemplatingInfo)) + .and_return(request) + + expect(stub).to receive(:render_environment_template) + .with(request, metadata: { 'authorization' => 'bearer test-token' }) + .and_return(response) + end + + it { expect(subject).to eq(template) } + end + + describe '#ensure_environment' do + let_it_be(:environment) { create(:environment, project: project, cluster_agent: agent) } + let_it_be(:user) { create(:user) } + let_it_be(:build) { create(:ci_build, user: user) } + let(:stub) { instance_double(Gitlab::Agent::ManagedResources::Rpc::Provisioner::Stub) } + let(:request) { instance_double(Gitlab::Agent::ManagedResources::Rpc::EnsureEnvironmentRequest) } + let(:template) { double("templates", name: "test-template", data: "{}") } + let(:response) { double(Gitlab::Agent::ManagedResources::Rpc::EnsureEnvironmentResponse) } + + subject { client.ensure_environment(template: template, environment: environment, build: build) } + + before do + expect(Gitlab::Agent::ManagedResources::Rpc::Provisioner::Stub).to receive(:new) + .with('example.kas.internal', :this_channel_is_insecure, timeout: client.send(:timeout)) + .and_return(stub) + + expect(Gitlab::Agent::ManagedResources::Rpc::EnsureEnvironmentRequest).to receive(:new) + .with( + template: Gitlab::Agent::ManagedResources::RenderedEnvironmentTemplate.new( + name: template.name, + data: template.data), + info: instance_of(Gitlab::Agent::ManagedResources::TemplatingInfo)) + .and_return(request) + + expect(stub).to receive(:ensure_environment) + .with(request, metadata: { 'authorization' => 'bearer test-token' }) + .and_return(response) + end + + it { expect(subject).to eq(response) } + end end end