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