From 3bf8b3d9d4653b6343788b04de28e1f45b9d0cd6 Mon Sep 17 00:00:00 2001 From: Chad Woolley Date: Mon, 10 Mar 2025 16:28:26 -0700 Subject: [PATCH] Introduce gl_workspace_reconciled_actual_state file var --- ee/lib/remote_development/files.rb | 2 +- .../create/create_constants.rb | 6 +- .../create/workspace_variables_builder.rb | 12 +- .../output/desired_config_generator.rb | 16 +- .../reconcile/output/devfile_parser.rb | 8 +- .../output/response_payload_builder.rb | 18 +- .../workspaces_to_be_returned_finder.rb | 7 +- .../reconcile/reconcile_constants.rb | 3 + .../remote_development/workspaces_spec.rb | 55 ++- .../workspace_variables_builder_spec.rb | 10 +- .../input/actual_state_calculator_spec.rb | 1 + .../reconcile/main_integration_spec.rb | 24 +- .../main_reconcile_scenarios_spec.rb | 10 +- ...red_config_generator_golden_master_spec.rb | 404 +++++++++--------- .../output/desired_config_generator_spec.rb | 4 + .../reconcile/output/devfile_parser_spec.rb | 2 +- .../output/response_payload_builder_spec.rb | 158 ++++--- .../workspaces_to_be_returned_finder_spec.rb | 34 +- .../remote_development/integration_spec.rb | 79 +++- .../integration_spec_helpers.rb | 188 +++++++- .../remote_development_shared_contexts.rb | 27 +- 21 files changed, 704 insertions(+), 364 deletions(-) diff --git a/ee/lib/remote_development/files.rb b/ee/lib/remote_development/files.rb index 9c1116bed3dad4..b813b598a3fc8e 100644 --- a/ee/lib/remote_development/files.rb +++ b/ee/lib/remote_development/files.rb @@ -29,7 +29,7 @@ def self.read_file(path) read_file("workspace_operations/create/project_cloner_component_inserter_container_args.sh") MAIN_COMPONENT_UPDATER_CONTAINER_ARGS = read_file("workspace_operations/create/main_component_updater_container_args.sh") - WORKSPACE_VARIABLES_GIT_CREDENTIAL_STORE_SCRIPT = + GIT_CREDENTIAL_STORE_SCRIPT = read_file("workspace_operations/create/workspace_variables_git_credential_store_script.sh") private_class_method :read_file diff --git a/ee/lib/remote_development/workspace_operations/create/create_constants.rb b/ee/lib/remote_development/workspace_operations/create/create_constants.rb index 88c68dfe5cff0c..d1cfc6277c6da9 100644 --- a/ee/lib/remote_development/workspace_operations/create/create_constants.rb +++ b/ee/lib/remote_development/workspace_operations/create/create_constants.rb @@ -13,11 +13,13 @@ module CreateConstants include WorkspaceOperationsConstants # Please keep alphabetized - GIT_CREDENTIAL_STORE_SCRIPT_FILE = "#{VARIABLES_FILE_DIR}/gl_git_credential_store.sh".freeze + GIT_CREDENTIAL_STORE_SCRIPT_FILE_NAME = "gl_git_credential_store.sh" + GIT_CREDENTIAL_STORE_SCRIPT_FILE_PATH = "#{VARIABLES_FILE_DIR}/#{GIT_CREDENTIAL_STORE_SCRIPT_FILE_NAME}".freeze MAIN_COMPONENT_INDICATOR_ATTRIBUTE = "gl/inject-editor" NAMESPACE_PREFIX = "gl-rd-ns" PROJECT_CLONING_SUCCESSFUL_FILENAME = ".gl_project_cloning_successful" - TOKEN_FILE = "#{VARIABLES_FILE_DIR}/gl_token".freeze + TOKEN_FILE_NAME = "gl_token" + TOKEN_FILE_PATH = "#{VARIABLES_FILE_DIR}/#{TOKEN_FILE_NAME}".freeze TOOLS_DIR_NAME = ".gl-tools" TOOLS_DIR_ENV_VAR = "GL_TOOLS_DIR" TOOLS_INJECTOR_COMPONENT_NAME = "gl-tools-injector" diff --git a/ee/lib/remote_development/workspace_operations/create/workspace_variables_builder.rb b/ee/lib/remote_development/workspace_operations/create/workspace_variables_builder.rb index 40847128f73350..b27d4e198cc414 100644 --- a/ee/lib/remote_development/workspace_operations/create/workspace_variables_builder.rb +++ b/ee/lib/remote_development/workspace_operations/create/workspace_variables_builder.rb @@ -34,14 +34,14 @@ def self.build( # The user's workspace-specific personal access token which is injected into the workspace, and used for # authentication. For example, in the credential.helper script below. { - key: File.basename(TOKEN_FILE), + key: File.basename(TOKEN_FILE_PATH), value: personal_access_token_value, variable_type: FILE_TYPE, workspace_id: workspace_id }, { key: "GL_TOKEN_FILE_PATH", - value: TOKEN_FILE, + value: TOKEN_FILE_PATH, variable_type: ENVIRONMENT_TYPE, workspace_id: workspace_id }, @@ -51,8 +51,8 @@ def self.build( # Standard git ENV vars which configure git on the workspace. See https://git-scm.com/docs/git-config { # This script is set as the value of `credential.helper` below in `GIT_CONFIG_VALUE_0` - key: File.basename(GIT_CREDENTIAL_STORE_SCRIPT_FILE), - value: WORKSPACE_VARIABLES_GIT_CREDENTIAL_STORE_SCRIPT, + key: File.basename(GIT_CREDENTIAL_STORE_SCRIPT_FILE_PATH), + value: GIT_CREDENTIAL_STORE_SCRIPT, variable_type: FILE_TYPE, workspace_id: workspace_id }, @@ -70,7 +70,7 @@ def self.build( }, { key: "GIT_CONFIG_VALUE_0", - value: GIT_CREDENTIAL_STORE_SCRIPT_FILE, + value: GIT_CREDENTIAL_STORE_SCRIPT_FILE_PATH, variable_type: ENVIRONMENT_TYPE, workspace_id: workspace_id }, @@ -146,7 +146,7 @@ def self.build( }, { key: "GITLAB_WORKFLOW_TOKEN_FILE", - value: TOKEN_FILE, + value: TOKEN_FILE_PATH, variable_type: ENVIRONMENT_TYPE, workspace_id: workspace_id } diff --git a/ee/lib/remote_development/workspace_operations/reconcile/output/desired_config_generator.rb b/ee/lib/remote_development/workspace_operations/reconcile/output/desired_config_generator.rb index 56923448587856..d0b6972d94f7ab 100644 --- a/ee/lib/remote_development/workspace_operations/reconcile/output/desired_config_generator.rb +++ b/ee/lib/remote_development/workspace_operations/reconcile/output/desired_config_generator.rb @@ -5,12 +5,14 @@ module WorkspaceOperations module Reconcile module Output class DesiredConfigGenerator + include ReconcileConstants include States # @param [RemoteDevelopment::Workspace] workspace # @param [Boolean] include_all_resources # @param [RemoteDevelopment::Logger] logger # @return [Array] + # rubocop:disable Metrics/AbcSize -- This is getting cleaned up in https://gitlab.com/gitlab-org/gitlab/-/merge_requests/185207 def self.generate_desired_config(workspace:, include_all_resources:, logger:) # NOTE: update env_secret_name to "#{workspace.name}-environment". This is to ensure naming consistency. # Changing it now would require migration from old config version to a new one. @@ -62,8 +64,10 @@ def self.generate_desired_config(workspace:, include_all_resources:, logger:) return desired_config end + processed_devfile_yaml = workspace.processed_devfile + resources_from_devfile_parser = DevfileParser.get_all( - processed_devfile: workspace.processed_devfile, + processed_devfile_yaml: processed_devfile_yaml, params: get_devfile_parser_params( workspace: workspace, workspaces_agent_config: workspaces_agent_config, @@ -102,9 +106,6 @@ def self.generate_desired_config(workspace:, include_all_resources:, logger:) annotations: workspace_inventory_annotations ) - # NOTE: We will perform append_secret here in order to complete - # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/182392 - return desired_config unless include_all_resources append_inventory_config_map( @@ -152,8 +153,15 @@ def self.generate_desired_config(workspace:, include_all_resources:, logger:) variables: workspace.workspace_variables.with_variable_type_file ) + append_secret_data( + desired_config: desired_config, + secret_name: file_secret_name, + data: { File.basename(WORKSPACE_RECONCILED_ACTUAL_STATE_FILE_PATH).to_sym => workspace.actual_state } + ) + desired_config end + # rubocop:enable Metrics/AbcSize # @param [RemoteDevelopment::Workspace] workspace # @param [RemoteDevelopment::WorkspacesAgentConfig] workspaces_agent_config diff --git a/ee/lib/remote_development/workspace_operations/reconcile/output/devfile_parser.rb b/ee/lib/remote_development/workspace_operations/reconcile/output/devfile_parser.rb index a6f87e8875039b..d626870779201f 100644 --- a/ee/lib/remote_development/workspace_operations/reconcile/output/devfile_parser.rb +++ b/ee/lib/remote_development/workspace_operations/reconcile/output/devfile_parser.rb @@ -10,11 +10,11 @@ class DevfileParser include WorkspaceOperationsConstants include ReconcileConstants - # @param [String] processed_devfile + # @param [String] processed_devfile_yaml # @param [Hash] params # @param [RemoteDevelopment::Logger] logger # @return [Array] - def self.get_all(processed_devfile:, params:, logger:) + def self.get_all(processed_devfile_yaml:, params:, logger:) params => { name: String => name, namespace: String => namespace, @@ -33,7 +33,7 @@ def self.get_all(processed_devfile:, params:, logger:) begin workspace_resources_yaml = Devfile::Parser.get_all( - processed_devfile, + processed_devfile_yaml, name, namespace, YAML.dump(labels.deep_stringify_keys), @@ -219,7 +219,7 @@ def self.inject_secrets(workspace_resources:, env_secret_names:, file_secret_nam name: volume_name, projected: { defaultMode: 0o774, - sources: file_secret_names.map { |v| { secret: { name: v } } } + sources: file_secret_names.map { |name| { secret: { name: name } } } } } ] diff --git a/ee/lib/remote_development/workspace_operations/reconcile/output/response_payload_builder.rb b/ee/lib/remote_development/workspace_operations/reconcile/output/response_payload_builder.rb index 081e97d23b1bc2..47af9b9fdd9e2a 100644 --- a/ee/lib/remote_development/workspace_operations/reconcile/output/response_payload_builder.rb +++ b/ee/lib/remote_development/workspace_operations/reconcile/output/response_payload_builder.rb @@ -81,7 +81,7 @@ def self.generate_config_to_apply(workspace:, update_type:, logger:) return nil, NO_RESOURCES_INCLUDED unless should_include_config_to_apply?(update_type: update_type, workspace: workspace) - include_all_resources = update_type == FULL || workspace.force_include_all_resources + include_all_resources = should_include_all_resources?(update_type: update_type, workspace: workspace) resources_include_type = include_all_resources ? ALL_RESOURCES_INCLUDED : PARTIAL_RESOURCES_INCLUDED workspace_resources = @@ -120,10 +120,24 @@ def self.should_include_config_to_apply?(update_type:, workspace:) update_type == FULL || workspace.force_include_all_resources || workspace.desired_state_updated_more_recently_than_last_response_to_agent? || + workspace.actual_state_updated_more_recently_than_last_response_to_agent? || workspace.desired_state_terminated_and_actual_state_not_terminated? end - private_class_method :should_include_config_to_apply?, :generate_config_to_apply + # @param [String (frozen)] update_type + # @param [RemoteDevelopment::Workspace] workspace + # @return [Boolean] + def self.should_include_all_resources?(update_type:, workspace:) + update_type == FULL || + workspace.force_include_all_resources || + # We include all resources if actual_state_updated_more_recently_than_last_response_to_agent?, + # so that the file secret for WORKSPACE_RECONCILED_ACTUAL_STATE_FILE_PATH is always updated when + # the actual_state changes + workspace.actual_state_updated_more_recently_than_last_response_to_agent? + end + + private_class_method :generate_config_to_apply, :should_include_config_to_apply?, + :should_include_all_resources? end end end diff --git a/ee/lib/remote_development/workspace_operations/reconcile/persistence/workspaces_to_be_returned_finder.rb b/ee/lib/remote_development/workspace_operations/reconcile/persistence/workspaces_to_be_returned_finder.rb index c2a450178f6920..275cc56971b0dd 100644 --- a/ee/lib/remote_development/workspace_operations/reconcile/persistence/workspaces_to_be_returned_finder.rb +++ b/ee/lib/remote_development/workspace_operations/reconcile/persistence/workspaces_to_be_returned_finder.rb @@ -43,15 +43,12 @@ def self.generate_workspaces_to_be_returned_query(agent:, update_type:, workspac .order_id_asc end - # For a PARTIAL update, return: - # 1. Workspaces with_desired_state_updated_more_recently_than_last_response_to_agent - # 2. Workspaces with_desired_state_terminated_and_actual_state_not_terminated - # 3. Workspaces which we received from the agent in the agent_infos array - # 4. Workspaces which have force_include_all_resources set to true + # For a PARTIAL update, return only specific workspaces which match criteria workspaces_from_agent_infos_ids = workspaces_from_agent_infos.map(&:id) agent .workspaces .with_desired_state_updated_more_recently_than_last_response_to_agent + .or(agent.workspaces.with_actual_state_updated_more_recently_than_last_response_to_agent) .or(agent.workspaces.with_desired_state_terminated_and_actual_state_not_terminated) .or(agent.workspaces.id_in(workspaces_from_agent_infos_ids)) .or(agent.workspaces.forced_to_include_all_resources) diff --git a/ee/lib/remote_development/workspace_operations/reconcile/reconcile_constants.rb b/ee/lib/remote_development/workspace_operations/reconcile/reconcile_constants.rb index e31e2fc27c4c39..525b4711991f47 100644 --- a/ee/lib/remote_development/workspace_operations/reconcile/reconcile_constants.rb +++ b/ee/lib/remote_development/workspace_operations/reconcile/reconcile_constants.rb @@ -14,6 +14,9 @@ module ReconcileConstants # Please keep alphabetized RUN_AS_USER = 5001 + WORKSPACE_RECONCILED_ACTUAL_STATE_FILE_NAME = "gl_workspace_reconciled_actual_state.txt" + WORKSPACE_RECONCILED_ACTUAL_STATE_FILE_PATH = + "#{VARIABLES_FILE_DIR}/#{WORKSPACE_RECONCILED_ACTUAL_STATE_FILE_NAME}".freeze end end end diff --git a/ee/spec/features/remote_development/workspaces_spec.rb b/ee/spec/features/remote_development/workspaces_spec.rb index 7a6675d8908cda..2d76b3ad4d808f 100644 --- a/ee/spec/features/remote_development/workspaces_spec.rb +++ b/ee/spec/features/remote_development/workspaces_spec.rb @@ -172,11 +172,17 @@ def do_reconcile_post(params:, agent_token:) simulate_first_poll( workspace: workspace.reload, agent_token: agent_token, + actual_state: states::CREATION_REQUESTED, **additional_args_for_expected_config_to_apply ) # SIMULATE RECONCILE REQUEST FROM AGENTK UPDATING WORKSPACE TO RUNNING ACTUAL_STATE - simulate_second_poll(workspace: workspace.reload, agent_token: agent_token) + simulate_second_poll( + workspace: workspace.reload, + agent_token: agent_token, + actual_state: states::RUNNING, + **additional_args_for_expected_config_to_apply + ) # ASSERT WORKSPACE SHOWS RUNNING STATE IN UI AND UPDATES URL expect_workspace_state_indicator(states::RUNNING) @@ -195,11 +201,17 @@ def do_reconcile_post(params:, agent_token:) simulate_third_poll( workspace: workspace.reload, agent_token: agent_token, + actual_state: states::RUNNING, **additional_args_for_expected_config_to_apply ) # SIMULATE RECONCILE REQUEST FROM AGENTK UPDATING WORKSPACE TO STOPPING ACTUAL_STATE - simulate_fourth_poll(workspace: workspace.reload, agent_token: agent_token) + simulate_fourth_poll( + workspace: workspace.reload, + agent_token: agent_token, + actual_state: states::STOPPING, + **additional_args_for_expected_config_to_apply + ) # ASSERT WORKSPACE SHOWS STOPPING STATE IN UI expect_workspace_state_indicator(states::STOPPING) @@ -209,7 +221,12 @@ def do_reconcile_post(params:, agent_token:) expect(page).to have_button('Terminate') # SIMULATE RECONCILE REQUEST FROM AGENTK UPDATING WORKSPACE TO STOPPED ACTUAL_STATE - simulate_fifth_poll(workspace: workspace.reload, agent_token: agent_token) + simulate_fifth_poll( + workspace: workspace.reload, + agent_token: agent_token, + actual_state: states::STOPPED, + **additional_args_for_expected_config_to_apply + ) # ASSERT WORKSPACE SHOWS STOPPED STATE IN UI expect_workspace_state_indicator(states::STOPPED) @@ -225,6 +242,7 @@ def do_reconcile_post(params:, agent_token:) simulate_seventh_poll( workspace: workspace.reload, agent_token: agent_token, + actual_state: states::STOPPED, **additional_args_for_expected_config_to_apply ) @@ -235,6 +253,7 @@ def do_reconcile_post(params:, agent_token:) simulate_eighth_poll( workspace: workspace.reload, agent_token: agent_token, + actual_state: states::STOPPED, **additional_args_for_expected_config_to_apply ) @@ -242,8 +261,10 @@ def do_reconcile_post(params:, agent_token:) simulate_ninth_poll( workspace: workspace.reload, agent_token: agent_token, + actual_state: states::RUNNING, # TRAVEL FORWARD IN TIME MAX_ACTIVE_HOURS_BEFORE_STOP HOURS - time_to_travel_after_poll: workspace.workspaces_agent_config.max_active_hours_before_stop.hours + time_to_travel_after_poll: workspace.workspaces_agent_config&.max_active_hours_before_stop&.hours, + **additional_args_for_expected_config_to_apply ) # ASSERT WORKSPACE SHOWS RUNNING STATE IN UI AND UPDATES URL @@ -253,11 +274,17 @@ def do_reconcile_post(params:, agent_token:) simulate_tenth_poll( workspace: workspace.reload, agent_token: agent_token, + actual_state: states::RUNNING, **additional_args_for_expected_config_to_apply ) # SIMULATE RECONCILE REQUEST FROM AGENTK UPDATING WORKSPACE TO STOPPING ACTUAL_STATE - simulate_eleventh_poll(workspace: workspace.reload, agent_token: agent_token) + simulate_eleventh_poll( + workspace: workspace.reload, + agent_token: agent_token, + actual_state: states::STOPPING, + **additional_args_for_expected_config_to_apply + ) # ASSERT WORKSPACE SHOWS STOPPING STATE IN UI expect_workspace_state_indicator(states::STOPPING) @@ -266,8 +293,10 @@ def do_reconcile_post(params:, agent_token:) simulate_twelfth_poll( workspace: workspace.reload, agent_token: agent_token, + actual_state: states::STOPPED, # TRAVEL FORWARD IN TIME MAX_STOPPED_HOURS_BEFORE_TERMINATION HOURS - time_to_travel_after_poll: workspace.workspaces_agent_config.max_stopped_hours_before_termination.hours + time_to_travel_after_poll: workspace.workspaces_agent_config&.max_stopped_hours_before_termination&.hours, + **additional_args_for_expected_config_to_apply ) # ASSERT WORKSPACE SHOWS STOPPED STATE IN UI @@ -277,16 +306,24 @@ def do_reconcile_post(params:, agent_token:) simulate_thirteenth_poll( workspace: workspace.reload, agent_token: agent_token, + actual_state: states::TERMINATED, **additional_args_for_expected_config_to_apply ) # SIMULATE RECONCILE REQUEST FROM AGENTK UPDATING WORKSPACE TO TERMINATING ACTUAL_STATE - simulate_fourteenth_poll(workspace: workspace.reload, - agent_token: agent_token + simulate_fourteenth_poll( + workspace: workspace.reload, + agent_token: agent_token, + actual_state: states::TERMINATING, + **additional_args_for_expected_config_to_apply ) # SIMULATE RECONCILE REQUEST FROM AGENTK UPDATING WORKSPACE TO TERMINATED ACTUAL_STATE - simulate_fifteenth_poll(workspace: workspace.reload, agent_token: agent_token) + simulate_fifteenth_poll( + workspace: workspace.reload, + agent_token: agent_token, + **additional_args_for_expected_config_to_apply + ) end end diff --git a/ee/spec/lib/remote_development/workspace_operations/create/workspace_variables_builder_spec.rb b/ee/spec/lib/remote_development/workspace_operations/create/workspace_variables_builder_spec.rb index dbbf5102df569c..b0d767f69dd9a6 100644 --- a/ee/spec/lib/remote_development/workspace_operations/create/workspace_variables_builder_spec.rb +++ b/ee/spec/lib/remote_development/workspace_operations/create/workspace_variables_builder_spec.rb @@ -38,19 +38,19 @@ let(:expected_variables) do [ { - key: "gl_token", + key: RemoteDevelopment::WorkspaceOperations::Create::CreateConstants::TOKEN_FILE_NAME, value: "example-pat-value", variable_type: RemoteDevelopment::Enums::WorkspaceVariable::FILE_TYPE, workspace_id: workspace_id }, { key: "GL_TOKEN_FILE_PATH", - value: "/.workspace-data/variables/file/gl_token", + value: RemoteDevelopment::WorkspaceOperations::Create::CreateConstants::TOKEN_FILE_PATH, variable_type: RemoteDevelopment::Enums::WorkspaceVariable::ENVIRONMENT_TYPE, workspace_id: workspace_id }, { - key: "gl_git_credential_store.sh", + key: RemoteDevelopment::WorkspaceOperations::Create::CreateConstants::GIT_CREDENTIAL_STORE_SCRIPT_FILE_NAME, value: git_credential_store_script, variable_type: RemoteDevelopment::Enums::WorkspaceVariable::FILE_TYPE, workspace_id: workspace_id @@ -69,7 +69,7 @@ }, { key: "GIT_CONFIG_VALUE_0", - value: "/.workspace-data/variables/file/gl_git_credential_store.sh", + value: RemoteDevelopment::WorkspaceOperations::Create::CreateConstants::GIT_CREDENTIAL_STORE_SCRIPT_FILE_PATH, variable_type: RemoteDevelopment::Enums::WorkspaceVariable::ENVIRONMENT_TYPE, workspace_id: workspace_id }, @@ -129,7 +129,7 @@ }, { key: "GITLAB_WORKFLOW_TOKEN_FILE", - value: "/.workspace-data/variables/file/gl_token", + value: RemoteDevelopment::WorkspaceOperations::Create::CreateConstants::TOKEN_FILE_PATH, variable_type: RemoteDevelopment::Enums::WorkspaceVariable::ENVIRONMENT_TYPE, workspace_id: workspace_id }, diff --git a/ee/spec/lib/remote_development/workspace_operations/reconcile/input/actual_state_calculator_spec.rb b/ee/spec/lib/remote_development/workspace_operations/reconcile/input/actual_state_calculator_spec.rb index 5a8c88d80f0153..e635ffa1492cfa 100644 --- a/ee/spec/lib/remote_development/workspace_operations/reconcile/input/actual_state_calculator_spec.rb +++ b/ee/spec/lib/remote_development/workspace_operations/reconcile/input/actual_state_calculator_spec.rb @@ -50,6 +50,7 @@ instance_double( "RemoteDevelopment::Workspace", # rubocop:disable RSpec/VerifiedDoubleReference -- We're using the quoted version so we can use fast_spec_helper id: 1, name: 'name', namespace: 'namespace', agent: agent, + actual_state: previous_actual_state, desired_config_generator_version: ::RemoteDevelopment::WorkspaceOperations::DesiredConfigGeneratorVersion::LATEST_VERSION ) diff --git a/ee/spec/lib/remote_development/workspace_operations/reconcile/main_integration_spec.rb b/ee/spec/lib/remote_development/workspace_operations/reconcile/main_integration_spec.rb index cb07f06aa82c43..ffce960368ee09 100644 --- a/ee/spec/lib/remote_development/workspace_operations/reconcile/main_integration_spec.rb +++ b/ee/spec/lib/remote_development/workspace_operations/reconcile/main_integration_spec.rb @@ -279,6 +279,18 @@ ) end + let(:expected_config_to_apply_yaml_stream) do + create_config_to_apply_yaml_stream( + workspace: workspace, + started: true, + include_all_resources: true, + egress_ip_rules: egress_ip_rules, + max_resources_per_workspace: max_resources_per_workspace, + default_resources_per_workspace_container: default_resources_per_workspace_container, + image_pull_secrets: workspace.workspaces_agent_config&.image_pull_secrets + ) + end + it 'returns proper workspace_rails_info entry with no config to apply' do # verify initial states in db (sanity check of match between factory and fixtures) expect(workspace.desired_state).to eq(desired_state) @@ -299,8 +311,9 @@ # test the config to apply first to get a more specific diff if it fails provisioned_workspace_rails_info = workspace_rails_infos.detect { |info| info.fetch(:name) == workspace.name } - # Since the workspace is now in Error state, the config should not be returned to the agent - expect(provisioned_workspace_rails_info.fetch(:config_to_apply)).to eq("") + # Even though the workspace is now in Error state, we will continue returning the config to the agent + # if it would have been returned in the normal case, in case the error is transient + expect(provisioned_workspace_rails_info.fetch(:config_to_apply)).to eq(expected_config_to_apply_yaml_stream) # then test everything in the infos expect(workspace_rails_infos).to eq(expected_workspace_rails_infos) @@ -382,10 +395,10 @@ expect(workspace.responded_to_agent_at).to be_before(Time.current) end + # noinspection RubyResolve - https://handbook.gitlab.com/handbook/tools-and-tips/editors-and-ides/jetbrains-ides/tracked-jetbrains-issues/#ruby-31542 after do # After processing, the responded_to_agent_at should always have been updated workspace.reload - # noinspection RubyResolve - https://handbook.gitlab.com/handbook/tools-and-tips/editors-and-ides/jetbrains-ides/tracked-jetbrains-issues/#ruby-31542 expect(workspace.responded_to_agent_at).not_to be_before(workspace.desired_state_updated_at) expect(workspace.responded_to_agent_at).not_to be_before(workspace.actual_state_updated_at) end @@ -619,7 +632,8 @@ current_actual_state: RemoteDevelopment::WorkspaceOperations::States::STOPPED, workspace_exists: false, workspace_variables_environment: {}, - workspace_variables_file: {} + workspace_variables_file: {}, + workspace_variables_additional_data: {} ) end @@ -662,7 +676,7 @@ egress_ip_rules: egress_ip_rules, max_resources_per_workspace: max_resources_per_workspace, default_resources_per_workspace_container: default_resources_per_workspace_container, - image_pull_secrets: unprovisioned_workspace.workspaces_agent_config.image_pull_secrets + image_pull_secrets: unprovisioned_workspace.workspaces_agent_config&.image_pull_secrets ) end diff --git a/ee/spec/lib/remote_development/workspace_operations/reconcile/main_reconcile_scenarios_spec.rb b/ee/spec/lib/remote_development/workspace_operations/reconcile/main_reconcile_scenarios_spec.rb index fdd9ebfa619e9f..96246f4bff07d7 100644 --- a/ee/spec/lib/remote_development/workspace_operations/reconcile/main_reconcile_scenarios_spec.rb +++ b/ee/spec/lib/remote_development/workspace_operations/reconcile/main_reconcile_scenarios_spec.rb @@ -49,7 +49,7 @@ [ # # desired: Running / actual: CreationRequested -> desired: Running / actual: Running - [nil, :running, [nil, [:creation_requested, :starting, false], [:starting, :running, true]], [[true, false], [false, true], [false, true]], [[:running, :creation_requested], [:running, :creation_requested], [:running, :starting], [:running, :running]]], + [nil, :running, [nil, [:creation_requested, :starting, false], [:starting, :running, true]], [[true, false], [true, true], [true, true]], [[:running, :creation_requested], [:running, :creation_requested], [:running, :starting], [:running, :running]]], # # TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/409784 - Fixture not yet implemented... # desired: Running / actual: CreationRequested -> desired: Running / actual: Failed @@ -59,7 +59,7 @@ # desired: Running / actual: CreationRequested -> desired: Running / actual: Error # # desired: Running / actual: Running -> desired: Stopped / actual: Stopped - [[:running, :running], :stopped, [nil, [:running, :stopping, true], [:stopping, :stopped, false]], [[true, true], [false, true], [false, true]], [[:running, :running], [:stopped, :running], [:stopped, :running], [:stopped, :stopping], [:stopped, :stopped]]], + [[:running, :running], :stopped, [nil, [:running, :stopping, true], [:stopping, :stopped, false]], [[true, true], [true, true], [true, true]], [[:running, :running], [:stopped, :running], [:stopped, :running], [:stopped, :stopping], [:stopped, :stopped]]], # # TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/409784 - Fixture not yet implemented... # desired: Running / actual: Running -> desired: Stopped / actual: Failed @@ -76,7 +76,7 @@ # [[:running, :running], :terminated, [nil, [:running, :terminated, false]], [[true, true], [false, true]], [[:running, :running], [:terminated, :running], [:terminated, :running], [:terminated, :terminated]]], # # desired: Stopped / actual: Stopped -> desired: Running / actual: Running - [[:stopped, :stopped], :running, [nil, [:stopped, :starting, false], [:starting, :running, true]], [[true, true], [false, true], [false, true]], [[:stopped, :stopped], [:running, :stopped], [:running, :stopped], [:running, :starting], [:running, :running]]], + [[:stopped, :stopped], :running, [nil, [:stopped, :starting, false], [:starting, :running, true]], [[true, true], [true, true], [true, true]], [[:stopped, :stopped], [:running, :stopped], [:running, :stopped], [:running, :starting], [:running, :running]]], # # TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/409784 - Fixture not yet implemented... # desired: Running / actual: Running -> desired: Terminated / actual: Failed @@ -85,7 +85,7 @@ # desired: Running / actual: Running -> desired: Terminated / actual: Error # # desired: Stopped / actual: Stopped -> desired: Running / actual: Running - [[:stopped, :stopped], :running, [nil, [:stopped, :starting, false], [:starting, :running, true]], [[true, true], [false, true], [false, true]], [[:stopped, :stopped], [:running, :stopped], [:running, :stopped], [:running, :starting], [:running, :running]]], + [[:stopped, :stopped], :running, [nil, [:stopped, :starting, false], [:starting, :running, true]], [[true, true], [true, true], [true, true]], [[:stopped, :stopped], [:running, :stopped], [:running, :stopped], [:running, :starting], [:running, :running]]], # # TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/409784 - Fixture not yet implemented... # desired: Stopped / actual: Stopped -> desired: Running / actual: Failed @@ -138,7 +138,7 @@ # Agent reports update for a workspace and user has also updated desired state of the workspace # # Restarting a workspace - [[:running, :running], :restart_requested, [nil, [:running, :stopping, true], [:stopping, :stopped, false], [:stopped, :starting, false], [:starting, :running, true]], [[true, true], [false, true], [true, true], [false, true], [false, true]], [[:running, :running], [:restart_requested, :running], [:restart_requested, :running], [:restart_requested, :stopping], [:running, :stopped], [:running, :starting], [:running, :running]]], + [[:running, :running], :restart_requested, [nil, [:running, :stopping, true], [:stopping, :stopped, false], [:stopped, :starting, false], [:starting, :running, true]], [[true, true], [true, true], [true, true], [true, true], [true, true]], [[:running, :running], [:restart_requested, :running], [:restart_requested, :running], [:restart_requested, :stopping], [:running, :stopped], [:running, :starting], [:running, :running]]], # # No update for workspace from agentk or from user # diff --git a/ee/spec/lib/remote_development/workspace_operations/reconcile/output/desired_config_generator_golden_master_spec.rb b/ee/spec/lib/remote_development/workspace_operations/reconcile/output/desired_config_generator_golden_master_spec.rb index 0d54558995e95e..c8d80dbb54a19b 100644 --- a/ee/spec/lib/remote_development/workspace_operations/reconcile/output/desired_config_generator_golden_master_spec.rb +++ b/ee/spec/lib/remote_development/workspace_operations/reconcile/output/desired_config_generator_golden_master_spec.rb @@ -75,6 +75,7 @@ name: "workspace-991-990-fedcba", namespace: "gl-rd-ns-991-990-fedcba", desired_state: desired_state, + actual_state: RemoteDevelopment::WorkspaceOperations::States::RUNNING, workspace_variables: workspace_variables, processed_devfile: input_processed_devfile_yaml ) @@ -92,17 +93,9 @@ shared_examples "generated desired_config golden master checks" do it "exactly matches the generated desired_config", :unlimited_max_formatted_output_length do - desired_config_sorted = - desired_config - .map(&:deep_symbolize_keys) - .sort_by { |resource| [resource.fetch(:kind), resource.fetch(:metadata).fetch(:name)] } - .map { |hash| Gitlab::Utils.deep_sort_hash(hash) } + desired_config_sorted = desired_config.map(&:deep_symbolize_keys) - golden_master_desired_config_sorted = - golden_master_desired_config - .map(&:deep_symbolize_keys) - .sort_by { |resource| [resource.fetch(:kind), resource.fetch(:metadata).fetch(:name)] } - .map { |hash| Gitlab::Utils.deep_sort_hash(hash) } + golden_master_desired_config_sorted = golden_master_desired_config.map(&:deep_symbolize_keys) # compare the names and kinds of the resources expect(desired_config_sorted.map { |c| [c.fetch(:kind), c.fetch(:metadata).fetch(:name)] }) @@ -236,9 +229,9 @@ def golden_master_desired_config_with_desired_state_terminated app: "workspace", tier: "development", "agent.gitlab.com/id": "991", - "cli-utils.sigs.k8s.io/inventory-id": "workspace-991-990-fedcba-secrets-inventory" + "cli-utils.sigs.k8s.io/inventory-id": "workspace-991-990-fedcba-workspace-inventory" }, - name: "workspace-991-990-fedcba-secrets-inventory", + name: "workspace-991-990-fedcba-workspace-inventory", namespace: "gl-rd-ns-991-990-fedcba" } }, @@ -257,9 +250,9 @@ def golden_master_desired_config_with_desired_state_terminated app: "workspace", tier: "development", "agent.gitlab.com/id": "991", - "cli-utils.sigs.k8s.io/inventory-id": "workspace-991-990-fedcba-workspace-inventory" + "cli-utils.sigs.k8s.io/inventory-id": "workspace-991-990-fedcba-secrets-inventory" }, - name: "workspace-991-990-fedcba-workspace-inventory", + name: "workspace-991-990-fedcba-secrets-inventory", namespace: "gl-rd-ns-991-990-fedcba" } } @@ -269,27 +262,6 @@ def golden_master_desired_config_with_desired_state_terminated # @return [Array] def golden_master_desired_config_with_include_all_resources_true [ - { - apiVersion: "v1", - kind: "ConfigMap", - metadata: { - annotations: { - environment: "production", - team: "engineering", - "workspaces.gitlab.com/host-template": "{{.port}}-workspace-991-990-fedcba.workspaces.localdev.me", - "workspaces.gitlab.com/id": "993", - "workspaces.gitlab.com/max-resources-per-workspace-sha256": "06879e20c353a4d871fb360635f6a87483987d44953ac6384af0451e8faa47ca" - }, - labels: { - app: "workspace", - tier: "development", - "agent.gitlab.com/id": "991", - "cli-utils.sigs.k8s.io/inventory-id": "workspace-991-990-fedcba-secrets-inventory" - }, - name: "workspace-991-990-fedcba-secrets-inventory", - namespace: "gl-rd-ns-991-990-fedcba" - } - }, { apiVersion: "v1", kind: "ConfigMap", @@ -524,6 +496,105 @@ def golden_master_desired_config_with_include_all_resources_true }, status: {} }, + { + apiVersion: "v1", + kind: "Service", + metadata: { + annotations: { + environment: "production", + team: "engineering", + "config.k8s.io/owning-inventory": "workspace-991-990-fedcba-workspace-inventory", + "workspaces.gitlab.com/host-template": "{{.port}}-workspace-991-990-fedcba.workspaces.localdev.me", + "workspaces.gitlab.com/id": "993", + "workspaces.gitlab.com/max-resources-per-workspace-sha256": "06879e20c353a4d871fb360635f6a87483987d44953ac6384af0451e8faa47ca" + }, + creationTimestamp: nil, + labels: { + app: "workspace", + tier: "development", + "agent.gitlab.com/id": "991" + }, + name: "workspace-991-990-fedcba", + namespace: "gl-rd-ns-991-990-fedcba" + }, + spec: { + ports: [ + { + name: "server", + port: 60001, + targetPort: 60001 + } + ], + selector: { + app: "workspace", + tier: "development", + "agent.gitlab.com/id": "991" + } + }, + status: { + loadBalancer: {} + } + }, + { + apiVersion: "v1", + kind: "PersistentVolumeClaim", + metadata: { + annotations: { + environment: "production", + team: "engineering", + "config.k8s.io/owning-inventory": "workspace-991-990-fedcba-workspace-inventory", + "workspaces.gitlab.com/host-template": "{{.port}}-workspace-991-990-fedcba.workspaces.localdev.me", + "workspaces.gitlab.com/id": "993", + "workspaces.gitlab.com/max-resources-per-workspace-sha256": "06879e20c353a4d871fb360635f6a87483987d44953ac6384af0451e8faa47ca" + }, + creationTimestamp: nil, + labels: { + app: "workspace", + tier: "development", + "agent.gitlab.com/id": "991" + }, + name: "workspace-991-990-fedcba-gl-workspace-data", + namespace: "gl-rd-ns-991-990-fedcba" + }, + spec: { + accessModes: [ + "ReadWriteOnce" + ], + resources: { + requests: { + storage: "50Gi" + } + } + }, + status: {} + }, + { + apiVersion: "v1", + automountServiceAccountToken: false, + imagePullSecrets: [ + { + name: "registry-secret" + } + ], + kind: "ServiceAccount", + metadata: { + annotations: { + environment: "production", + team: "engineering", + "config.k8s.io/owning-inventory": "workspace-991-990-fedcba-workspace-inventory", + "workspaces.gitlab.com/host-template": "{{.port}}-workspace-991-990-fedcba.workspaces.localdev.me", + "workspaces.gitlab.com/id": "993", + "workspaces.gitlab.com/max-resources-per-workspace-sha256": "06879e20c353a4d871fb360635f6a87483987d44953ac6384af0451e8faa47ca" + }, + labels: { + app: "workspace", + tier: "development", + "agent.gitlab.com/id": "991" + }, + name: "workspace-991-990-fedcba", + namespace: "gl-rd-ns-991-990-fedcba" + } + }, { apiVersion: "networking.k8s.io/v1", kind: "NetworkPolicy", @@ -609,36 +680,24 @@ def golden_master_desired_config_with_include_all_resources_true }, { apiVersion: "v1", - kind: "PersistentVolumeClaim", + kind: "ConfigMap", metadata: { annotations: { environment: "production", team: "engineering", - "config.k8s.io/owning-inventory": "workspace-991-990-fedcba-workspace-inventory", "workspaces.gitlab.com/host-template": "{{.port}}-workspace-991-990-fedcba.workspaces.localdev.me", "workspaces.gitlab.com/id": "993", "workspaces.gitlab.com/max-resources-per-workspace-sha256": "06879e20c353a4d871fb360635f6a87483987d44953ac6384af0451e8faa47ca" }, - creationTimestamp: nil, labels: { app: "workspace", tier: "development", - "agent.gitlab.com/id": "991" + "agent.gitlab.com/id": "991", + "cli-utils.sigs.k8s.io/inventory-id": "workspace-991-990-fedcba-secrets-inventory" }, - name: "workspace-991-990-fedcba-gl-workspace-data", + name: "workspace-991-990-fedcba-secrets-inventory", namespace: "gl-rd-ns-991-990-fedcba" - }, - spec: { - accessModes: [ - "ReadWriteOnce" - ], - resources: { - requests: { - storage: "50Gi" - } - } - }, - status: {} + } }, { apiVersion: "v1", @@ -696,7 +755,8 @@ def golden_master_desired_config_with_include_all_resources_true { apiVersion: "v1", data: { - FILE_VAR1: "ZmlsZS12YXItdmFsdWUx" + FILE_VAR1: "ZmlsZS12YXItdmFsdWUx", + "gl_workspace_reconciled_actual_state.txt": "UnVubmluZw==" }, kind: "Secret", metadata: { @@ -716,72 +776,6 @@ def golden_master_desired_config_with_include_all_resources_true name: "workspace-991-990-fedcba-file", namespace: "gl-rd-ns-991-990-fedcba" } - }, - { - apiVersion: "v1", - kind: "Service", - metadata: { - annotations: { - environment: "production", - team: "engineering", - "config.k8s.io/owning-inventory": "workspace-991-990-fedcba-workspace-inventory", - "workspaces.gitlab.com/host-template": "{{.port}}-workspace-991-990-fedcba.workspaces.localdev.me", - "workspaces.gitlab.com/id": "993", - "workspaces.gitlab.com/max-resources-per-workspace-sha256": "06879e20c353a4d871fb360635f6a87483987d44953ac6384af0451e8faa47ca" - }, - creationTimestamp: nil, - labels: { - app: "workspace", - tier: "development", - "agent.gitlab.com/id": "991" - }, - name: "workspace-991-990-fedcba", - namespace: "gl-rd-ns-991-990-fedcba" - }, - spec: { - ports: [ - { - name: "server", - port: 60001, - targetPort: 60001 - } - ], - selector: { - app: "workspace", - tier: "development", - "agent.gitlab.com/id": "991" - } - }, - status: { - loadBalancer: {} - } - }, - { - apiVersion: "v1", - automountServiceAccountToken: false, - imagePullSecrets: [ - { - name: "registry-secret" - } - ], - kind: "ServiceAccount", - metadata: { - annotations: { - environment: "production", - team: "engineering", - "config.k8s.io/owning-inventory": "workspace-991-990-fedcba-workspace-inventory", - "workspaces.gitlab.com/host-template": "{{.port}}-workspace-991-990-fedcba.workspaces.localdev.me", - "workspaces.gitlab.com/id": "993", - "workspaces.gitlab.com/max-resources-per-workspace-sha256": "06879e20c353a4d871fb360635f6a87483987d44953ac6384af0451e8faa47ca" - }, - labels: { - app: "workspace", - tier: "development", - "agent.gitlab.com/id": "991" - }, - name: "workspace-991-990-fedcba", - namespace: "gl-rd-ns-991-990-fedcba" - } } ] end @@ -1024,8 +1018,8 @@ def golden_master_desired_config_with_include_all_resources_false status: {} }, { - apiVersion: "networking.k8s.io/v1", - kind: "NetworkPolicy", + apiVersion: "v1", + kind: "Service", metadata: { annotations: { environment: "production", @@ -1035,6 +1029,7 @@ def golden_master_desired_config_with_include_all_resources_false "workspaces.gitlab.com/id": "993", "workspaces.gitlab.com/max-resources-per-workspace-sha256": "06879e20c353a4d871fb360635f6a87483987d44953ac6384af0451e8faa47ca" }, + creationTimestamp: nil, labels: { app: "workspace", tier: "development", @@ -1044,66 +1039,21 @@ def golden_master_desired_config_with_include_all_resources_false namespace: "gl-rd-ns-991-990-fedcba" }, spec: { - egress: [ - { - ports: [ - { - port: 53, - protocol: "TCP" - }, - { - port: 53, - protocol: "UDP" - } - ], - to: [ - { - namespaceSelector: { - matchLabels: { - "kubernetes.io/metadata.name": "kube-system" - } - } - } - ] - }, - { - to: [ - { - ipBlock: { - cidr: "0.0.0.0/0", - except: [ - "10.0.0.0/8", - "172.16.0.0/12", - "192.168.0.0/16" - ] - } - } - ] - } - ], - ingress: [ + ports: [ { - from: [ - { - namespaceSelector: { - matchLabels: { - "kubernetes.io/metadata.name": "gitlab-workspaces" - } - }, - podSelector: { - matchLabels: { - "app.kubernetes.io/name": "gitlab-workspaces-proxy" - } - } - } - ] + name: "server", + port: 60001, + targetPort: 60001 } ], - podSelector: {}, - policyTypes: [ - "Ingress", - "Egress" - ] + selector: { + app: "workspace", + tier: "development", + "agent.gitlab.com/id": "991" + } + }, + status: { + loadBalancer: {} } }, { @@ -1141,7 +1091,13 @@ def golden_master_desired_config_with_include_all_resources_false }, { apiVersion: "v1", - kind: "Service", + automountServiceAccountToken: false, + imagePullSecrets: [ + { + name: "registry-secret" + } + ], + kind: "ServiceAccount", metadata: { annotations: { environment: "production", @@ -1151,7 +1107,6 @@ def golden_master_desired_config_with_include_all_resources_false "workspaces.gitlab.com/id": "993", "workspaces.gitlab.com/max-resources-per-workspace-sha256": "06879e20c353a4d871fb360635f6a87483987d44953ac6384af0451e8faa47ca" }, - creationTimestamp: nil, labels: { app: "workspace", tier: "development", @@ -1159,34 +1114,11 @@ def golden_master_desired_config_with_include_all_resources_false }, name: "workspace-991-990-fedcba", namespace: "gl-rd-ns-991-990-fedcba" - }, - spec: { - ports: [ - { - name: "server", - port: 60001, - targetPort: 60001 - } - ], - selector: { - app: "workspace", - tier: "development", - "agent.gitlab.com/id": "991" - } - }, - status: { - loadBalancer: {} } }, { - apiVersion: "v1", - automountServiceAccountToken: false, - imagePullSecrets: [ - { - name: "registry-secret" - } - ], - kind: "ServiceAccount", + apiVersion: "networking.k8s.io/v1", + kind: "NetworkPolicy", metadata: { annotations: { environment: "production", @@ -1203,6 +1135,68 @@ def golden_master_desired_config_with_include_all_resources_false }, name: "workspace-991-990-fedcba", namespace: "gl-rd-ns-991-990-fedcba" + }, + spec: { + egress: [ + { + ports: [ + { + port: 53, + protocol: "TCP" + }, + { + port: 53, + protocol: "UDP" + } + ], + to: [ + { + namespaceSelector: { + matchLabels: { + "kubernetes.io/metadata.name": "kube-system" + } + } + } + ] + }, + { + to: [ + { + ipBlock: { + cidr: "0.0.0.0/0", + except: [ + "10.0.0.0/8", + "172.16.0.0/12", + "192.168.0.0/16" + ] + } + } + ] + } + ], + ingress: [ + { + from: [ + { + namespaceSelector: { + matchLabels: { + "kubernetes.io/metadata.name": "gitlab-workspaces" + } + }, + podSelector: { + matchLabels: { + "app.kubernetes.io/name": "gitlab-workspaces-proxy" + } + } + } + ] + } + ], + podSelector: {}, + policyTypes: [ + "Ingress", + "Egress" + ] } } ] diff --git a/ee/spec/lib/remote_development/workspace_operations/reconcile/output/desired_config_generator_spec.rb b/ee/spec/lib/remote_development/workspace_operations/reconcile/output/desired_config_generator_spec.rb index ff4f9628ecdeee..43b7e4952f1249 100644 --- a/ee/spec/lib/remote_development/workspace_operations/reconcile/output/desired_config_generator_spec.rb +++ b/ee/spec/lib/remote_development/workspace_operations/reconcile/output/desired_config_generator_spec.rb @@ -72,6 +72,10 @@ ) end + it "generates the expected config" do + expect(workspace_resources).to eq(expected_config) + end + context 'when desired_state terminated' do let(:desired_state_is_terminated) { true } let(:desired_state) { RemoteDevelopment::WorkspaceOperations::States::TERMINATED } diff --git a/ee/spec/lib/remote_development/workspace_operations/reconcile/output/devfile_parser_spec.rb b/ee/spec/lib/remote_development/workspace_operations/reconcile/output/devfile_parser_spec.rb index 207852fabf1abc..5039598837a060 100644 --- a/ee/spec/lib/remote_development/workspace_operations/reconcile/output/devfile_parser_spec.rb +++ b/ee/spec/lib/remote_development/workspace_operations/reconcile/output/devfile_parser_spec.rb @@ -121,7 +121,7 @@ subject(:resources_from_devfile_parser) do described_class.get_all( - processed_devfile: processed_devfile_yaml, + processed_devfile_yaml: processed_devfile_yaml, params: params, logger: logger ) diff --git a/ee/spec/lib/remote_development/workspace_operations/reconcile/output/response_payload_builder_spec.rb b/ee/spec/lib/remote_development/workspace_operations/reconcile/output/response_payload_builder_spec.rb index 9ccc1be08e0ff6..c37789e6a2a576 100644 --- a/ee/spec/lib/remote_development/workspace_operations/reconcile/output/response_payload_builder_spec.rb +++ b/ee/spec/lib/remote_development/workspace_operations/reconcile/output/response_payload_builder_spec.rb @@ -60,7 +60,7 @@ # value of `include_all_resources` using simple `let` statements, and avoid having to write complex mocks. # NOTE: The generated_config_to_apply can include string keys because users can provide labels and annotations in # their agent configuration which we use in the DesiredConfigGenerator which is being mocked here. - let(:single_resource) do + let(:generated_config_to_apply) do [ { include_all_resources: expected_include_all_resources, @@ -70,35 +70,10 @@ ] end - let(:multiple_resources) do - [ - { - a: 1 - }, - { - b: 2 - }, - { - c: 3 - } - ] - end - - let(:no_resource) { nil } - - let(:generated_config_to_apply) do - case generated_config_to_apply_type - when :single_resource - single_resource - when :multiple_resources - multiple_resources - when :no_resource - nil - end - end + let(:expected_generated_config_to_apply) { generated_config_to_apply } let(:expected_returned_workspace_rails_infos) do - config_to_apply_yaml_stream = generated_config_to_apply&.map do |resource| + config_to_apply_yaml_stream = expected_generated_config_to_apply&.map do |resource| YAML.dump(resource.deep_stringify_keys) end&.join @@ -142,6 +117,8 @@ .to receive_messages( desired_state_updated_more_recently_than_last_response_to_agent?: desired_state_updated_more_recently_than_last_response_to_agent, + actual_state_updated_more_recently_than_last_response_to_agent?: + actual_state_updated_more_recently_than_last_response_to_agent, desired_state_terminated_and_actual_state_not_terminated?: desired_state_terminated_and_actual_state_not_terminated ) @@ -156,52 +133,106 @@ .with(hash_including(include_all_resources: expected_include_all_resources)) { generated_config_to_apply } end - using RSpec::Parameterized::TableSyntax - - # rubocop:disable Layout/LineLength -- Required for formatting of table - where( - :update_type, - :force_include_all_resources, - :desired_state_updated_more_recently_than_last_response_to_agent, - :desired_state_terminated_and_actual_state_not_terminated, - :generated_config_to_apply_type, - :expected_include_all_resources, - :expected_workspace_resources_included_type - ) do - update_types::FULL | false | false | false | :multiple_resources | true | described_class::ALL_RESOURCES_INCLUDED - update_types::FULL | true | true | true | :single_resource | true | described_class::ALL_RESOURCES_INCLUDED - update_types::FULL | true | true | false | :single_resource | true | described_class::ALL_RESOURCES_INCLUDED - update_types::FULL | true | false | true | :single_resource | true | described_class::ALL_RESOURCES_INCLUDED - update_types::FULL | true | false | false | :single_resource | true | described_class::ALL_RESOURCES_INCLUDED - update_types::FULL | false | true | true | :single_resource | true | described_class::ALL_RESOURCES_INCLUDED - update_types::FULL | false | true | false | :single_resource | true | described_class::ALL_RESOURCES_INCLUDED - update_types::FULL | false | false | true | :single_resource | true | described_class::ALL_RESOURCES_INCLUDED - update_types::FULL | false | false | false | :single_resource | true | described_class::ALL_RESOURCES_INCLUDED - update_types::PARTIAL | true | true | true | :single_resource | true | described_class::ALL_RESOURCES_INCLUDED - update_types::PARTIAL | true | true | false | :single_resource | true | described_class::ALL_RESOURCES_INCLUDED - update_types::PARTIAL | true | false | true | :single_resource | true | described_class::ALL_RESOURCES_INCLUDED - update_types::PARTIAL | true | false | false | :single_resource | true | described_class::ALL_RESOURCES_INCLUDED - update_types::PARTIAL | false | true | true | :single_resource | false | described_class::PARTIAL_RESOURCES_INCLUDED - update_types::PARTIAL | false | true | false | :single_resource | false | described_class::PARTIAL_RESOURCES_INCLUDED - update_types::PARTIAL | false | false | true | :single_resource | false | described_class::PARTIAL_RESOURCES_INCLUDED - update_types::PARTIAL | false | false | false | :no_resource | false | described_class::NO_RESOURCES_INCLUDED - end - # rubocop:enable Layout/LineLength + context "when update_type is FULL" do + let(:desired_state_updated_more_recently_than_last_response_to_agent) { false } + let(:actual_state_updated_more_recently_than_last_response_to_agent) { false } + let(:desired_state_terminated_and_actual_state_not_terminated) { false } + let(:update_type) { RemoteDevelopment::WorkspaceOperations::Reconcile::UpdateTypes::FULL } + let(:expected_include_all_resources) { true } - with_them do - it "expected resources are included in config_to_apply" do + it "includes config_to_apply with all resources included" do expect(returned_value).to eq(expected_returned_value) end + + context "when config_to_apply contains multiple resources" do + let(:generated_config_to_apply) do + [ + { + a: { + z: 1, + a: 1 + } + }, + { + b: 2 + } + ] + end + + let(:expected_generated_config_to_apply) do + [ + { + a: { + a: 1, + z: 1 + } + }, + { + b: 2 + } + ] + end + + it "includes all resources with hashes deep sorted" do + expect(returned_value).to eq(expected_returned_value) + returned_value[:response_payload][:workspace_rails_infos].first[:config_to_apply] + returned_value => { + response_payload: { + workspace_rails_infos: [ + { + config_to_apply: config_to_apply_yaml_stream + }, + ] + } + } + loaded_multiple_docs = YAML.load_stream(config_to_apply_yaml_stream) + expect(loaded_multiple_docs.size).to eq(expected_generated_config_to_apply.size) + end + end + end + + context "when update_type is PARTIAL" do + let(:update_type) { RemoteDevelopment::WorkspaceOperations::Reconcile::UpdateTypes::PARTIAL } + + using RSpec::Parameterized::TableSyntax + + where( + :force_include_all_resources, + :desired_state_updated_more_recently_than_last_response_to_agent, + :actual_state_updated_more_recently_than_last_response_to_agent, + :desired_state_terminated_and_actual_state_not_terminated, + :expected_include_all_resources, + :expected_workspace_resources_included_type, + :expect_config_to_apply_to_be_included + ) do + # @formatter:off - Turn off RubyMine autoformatting + true | true | false | false | true | described_class::ALL_RESOURCES_INCLUDED | true + true | false | false | false | true | described_class::ALL_RESOURCES_INCLUDED | true + false | true | false | false | false | described_class::PARTIAL_RESOURCES_INCLUDED | true + false | false | false | false | false | described_class::NO_RESOURCES_INCLUDED | false + false | false | false | true | false | described_class::PARTIAL_RESOURCES_INCLUDED | true + true | true | true | false | true | described_class::ALL_RESOURCES_INCLUDED | true + true | false | true | false | true | described_class::ALL_RESOURCES_INCLUDED | true + false | true | true | false | true | described_class::ALL_RESOURCES_INCLUDED | true + false | false | true | false | true | described_class::ALL_RESOURCES_INCLUDED | true + # @formatter:on + end + + with_them do + let(:generated_config_to_apply) { nil } unless params[:expect_config_to_apply_to_be_included] + + it { expect(returned_value).to eq(expected_returned_value) } + end end end - # rubocop:disable RSpec/MultipleMemoizedHelpers -- needed helpers for multiple cases context "when workspace.desired_config_generator_version is a previous version" do let(:previous_desired_config_generator_version) { 2 } - let(:generated_config_to_apply_type) { :single_resource } + let(:desired_config_generator_version) { previous_desired_config_generator_version } let(:update_type) { update_types::FULL } let(:desired_state_updated_more_recently_than_last_response_to_agent) { false } + let(:actual_state_updated_more_recently_than_last_response_to_agent) { false } let(:desired_state_terminated_and_actual_state_not_terminated) { false } let(:expected_include_all_resources) { true } @@ -226,5 +257,4 @@ def self.generate_desired_config(_) expect(returned_value).to eq(expected_returned_value) end end - # rubocop:enable RSpec/MultipleMemoizedHelpers end diff --git a/ee/spec/lib/remote_development/workspace_operations/reconcile/persistence/workspaces_to_be_returned_finder_spec.rb b/ee/spec/lib/remote_development/workspace_operations/reconcile/persistence/workspaces_to_be_returned_finder_spec.rb index d5f3d0fc25a500..24336d6d37f4f6 100644 --- a/ee/spec/lib/remote_development/workspace_operations/reconcile/persistence/workspaces_to_be_returned_finder_spec.rb +++ b/ee/spec/lib/remote_development/workspace_operations/reconcile/persistence/workspaces_to_be_returned_finder_spec.rb @@ -72,6 +72,18 @@ ) end + let_it_be(:workspace_with_new_update_to_actual_state, reload: true) do + create( + :workspace, + :without_realistic_after_create_timestamp_updates, + name: "workspace_with_new_update_to_actual_state", + agent: agent, + user: user, + force_include_all_resources: false, + responded_to_agent_at: 2.hours.ago + ) + end + let_it_be(:workspace_with_force_include_all_resources, reload: true) do create( :workspace, @@ -101,15 +113,15 @@ agent.reload # desired_state_updated_at IS NOT more recent than responded_to_agent_at - workspace_only_returned_by_full_update.update_attribute( - :desired_state_updated_at, - workspace_only_returned_by_full_update.responded_to_agent_at - 1.hour + workspace_only_returned_by_full_update.update!( + desired_state_updated_at: workspace_only_returned_by_full_update.responded_to_agent_at - 1.hour, + actual_state_updated_at: workspace_only_returned_by_full_update.responded_to_agent_at - 1.hour ) # desired_state_updated_at IS NOT more recent than responded_to_agent_at - workspace_that_is_terminated.update_attribute( - :desired_state_updated_at, - workspace_that_is_terminated.responded_to_agent_at - 1.hour + workspace_that_is_terminated.update!( + desired_state_updated_at: workspace_only_returned_by_full_update.responded_to_agent_at - 1.hour, + actual_state_updated_at: workspace_only_returned_by_full_update.responded_to_agent_at - 1.hour ) # desired_state_updated_at IS NOT more recent than responded_to_agent_at @@ -130,6 +142,12 @@ workspace_with_new_update_to_desired_state.responded_to_agent_at + 1.hour ) + # actual_state_updated_at IS more recent than responded_to_agent_at + workspace_with_new_update_to_actual_state.update_attribute( + :actual_state_updated_at, + workspace_with_new_update_to_actual_state.responded_to_agent_at + 1.hour + ) + # desired_state_updated_at IS more recent than responded_to_agent_at workspace_with_force_include_all_resources.update_attribute( :desired_state_updated_at, @@ -151,6 +169,8 @@ .to be < workspace_desired_is_terminated_actual_is_terminating.responded_to_agent_at expect(workspace_with_new_update_to_desired_state.desired_state_updated_at) .to be > workspace_with_new_update_to_desired_state.responded_to_agent_at + expect(workspace_with_new_update_to_actual_state.actual_state_updated_at) + .to be > workspace_with_new_update_to_actual_state.responded_to_agent_at expect(workspace_from_agent_info.desired_state_updated_at) .to be < workspace_from_agent_info.responded_to_agent_at end @@ -166,6 +186,7 @@ workspace_desired_is_terminated_actual_is_terminating, workspace_from_agent_info, workspace_with_new_update_to_desired_state, + workspace_with_new_update_to_actual_state, workspace_with_force_include_all_resources ] end @@ -195,6 +216,7 @@ workspace_desired_is_terminated_actual_is_terminating, workspace_from_agent_info, workspace_with_new_update_to_desired_state, + workspace_with_new_update_to_actual_state, workspace_with_force_include_all_resources ] end diff --git a/ee/spec/requests/remote_development/integration_spec.rb b/ee/spec/requests/remote_development/integration_spec.rb index 7dcad139b320be..942144642300e8 100644 --- a/ee/spec/requests/remote_development/integration_spec.rb +++ b/ee/spec/requests/remote_development/integration_spec.rb @@ -9,6 +9,17 @@ include RemoteDevelopment::IntegrationSpecHelpers include_context "with remote development shared fixtures" + let(:token_file_name) { RemoteDevelopment::WorkspaceOperations::Create::CreateConstants::TOKEN_FILE_NAME } + let(:token_file_path) { RemoteDevelopment::WorkspaceOperations::Create::CreateConstants::TOKEN_FILE_PATH } + let(:git_credential_store_script) { RemoteDevelopment::Files::GIT_CREDENTIAL_STORE_SCRIPT } + let(:git_credential_store_script_file_name) do + RemoteDevelopment::WorkspaceOperations::Create::CreateConstants::GIT_CREDENTIAL_STORE_SCRIPT_FILE_NAME + end + + let(:git_credential_store_script_file_path) do + RemoteDevelopment::WorkspaceOperations::Create::CreateConstants::GIT_CREDENTIAL_STORE_SCRIPT_FILE_PATH + end + let(:states) { ::RemoteDevelopment::WorkspaceOperations::States } let(:agent_admin_user) { create(:user, name: "Agent Admin User") } # Agent setup @@ -89,13 +100,13 @@ # rubocop:disable Layout/LineLength -- keep them on one line for easier readability and editability [ { key: "gl_token", type: :file, value: /glpat-.+/ }, - { key: "GL_TOKEN_FILE_PATH", type: :environment, value: "/.workspace-data/variables/file/gl_token" }, - { key: "gl_git_credential_store.sh", type: :file, value: RemoteDevelopment::Files::WORKSPACE_VARIABLES_GIT_CREDENTIAL_STORE_SCRIPT }, + { key: "GL_TOKEN_FILE_PATH", type: :environment, value: token_file_path }, + { key: git_credential_store_script_file_name, type: :file, value: git_credential_store_script }, { key: "GIT_CONFIG_COUNT", type: :environment, value: "3" }, { key: "GIT_CONFIG_KEY_0", type: :environment, value: "credential.helper" }, { key: "GIT_CONFIG_KEY_1", type: :environment, value: "user.name" }, { key: "GIT_CONFIG_KEY_2", type: :environment, value: "user.email" }, - { key: "GIT_CONFIG_VALUE_0", type: :environment, value: "/.workspace-data/variables/file/gl_git_credential_store.sh" }, + { key: "GIT_CONFIG_VALUE_0", type: :environment, value: git_credential_store_script_file_path }, { key: "GIT_CONFIG_VALUE_1", type: :environment, value: "Workspaces User" }, { key: "GIT_CONFIG_VALUE_2", type: :environment, value: "workspaces-user@example.org" }, { key: "GL_EDITOR_EXTENSIONS_GALLERY_ITEM_URL", type: :environment, value: "https://open-vsx.org/vscode/item" }, @@ -103,7 +114,7 @@ { key: "GL_EDITOR_EXTENSIONS_GALLERY_SERVICE_URL", type: :environment, value: "https://open-vsx.org/vscode/gallery" }, { key: "GL_WORKSPACE_DOMAIN_TEMPLATE", type: :environment, value: "${PORT}-workspace-#{agent.id}-#{user.id}-#{random_string}.#{dns_zone}" }, { key: "GITLAB_WORKFLOW_INSTANCE_URL", type: :environment, value: Gitlab::Routing.url_helpers.root_url }, - { key: "GITLAB_WORKFLOW_TOKEN_FILE", type: :environment, value: "/.workspace-data/variables/file/gl_token" } + { key: "GITLAB_WORKFLOW_TOKEN_FILE", type: :environment, value: token_file_path } ] # rubocop:enable Layout/LineLength end @@ -260,12 +271,12 @@ def do_create_workspace expect(all_actual_vars.pluck(:key)).to match_array(all_expected_vars.pluck(:key)) # Then check the full attributes for all vars except gl_token, which must be compared as a regex - expected_without_regexes = all_expected_vars.reject { |v| v[:key] == "gl_token" } - actual_without_regexes = all_actual_vars.reject { |v| v[:key] == "gl_token" } + expected_without_regexes = all_expected_vars.reject { |v| v[:key] == token_file_name } + actual_without_regexes = all_actual_vars.reject { |v| v[:key] == token_file_name } expect(expected_without_regexes).to match(actual_without_regexes) - expected_gl_token_value = expected_internal_variables.find { |var| var[:key] == "gl_token" }[:value] - actual_gl_token_value = all_actual_vars.find { |var| var[:key] == "gl_token" }[:value] + expected_gl_token_value = expected_internal_variables.find { |var| var[:key] == token_file_name }[:value] + actual_gl_token_value = all_actual_vars.find { |var| var[:key] == token_file_name }[:value] expect(actual_gl_token_value).to match(expected_gl_token_value) workspace @@ -398,11 +409,17 @@ def do_reconcile_post(params:, agent_token:) simulate_first_poll( workspace: workspace.reload, agent_token: agent_token, + actual_state: states::CREATION_REQUESTED, **additional_args_for_expected_config_to_apply ) # SIMULATE RECONCILE REQUEST FROM AGENTK UPDATING WORKSPACE TO RUNNING ACTUAL_STATE - simulate_second_poll(workspace: workspace.reload, agent_token: agent_token) + simulate_second_poll( + workspace: workspace.reload, + agent_token: agent_token, + actual_state: states::RUNNING, + **additional_args_for_expected_config_to_apply + ) # UPDATE WORKSPACE DESIRED_STATE TO STOPPED VIA GRAPHQL API do_stop_workspace(workspace) @@ -411,14 +428,25 @@ def do_reconcile_post(params:, agent_token:) simulate_third_poll( workspace: workspace.reload, agent_token: agent_token, + actual_state: states::RUNNING, **additional_args_for_expected_config_to_apply ) # SIMULATE RECONCILE REQUEST FROM AGENTK UPDATING WORKSPACE TO STOPPING ACTUAL_STATE - simulate_fourth_poll(workspace: workspace.reload, agent_token: agent_token) + simulate_fourth_poll( + workspace: workspace.reload, + agent_token: agent_token, + actual_state: states::STOPPING, + **additional_args_for_expected_config_to_apply + ) # SIMULATE RECONCILE REQUEST FROM AGENTK UPDATING WORKSPACE TO STOPPED ACTUAL_STATE - simulate_fifth_poll(workspace: workspace.reload, agent_token: agent_token) + simulate_fifth_poll( + workspace: workspace.reload, + agent_token: agent_token, + actual_state: states::STOPPED, + **additional_args_for_expected_config_to_apply + ) # SIMULATE RECONCILE RESPONSE TO AGENTK FOR PARTIAL RECONCILE TO SHOW NO RAILS_INFOS ARE SENT simulate_sixth_poll(agent_token: agent_token) @@ -427,6 +455,7 @@ def do_reconcile_post(params:, agent_token:) simulate_seventh_poll( workspace: workspace.reload, agent_token: agent_token, + actual_state: states::STOPPED, **additional_args_for_expected_config_to_apply ) @@ -437,39 +466,50 @@ def do_reconcile_post(params:, agent_token:) simulate_eighth_poll( workspace: workspace.reload, agent_token: agent_token, + actual_state: states::STOPPED, **additional_args_for_expected_config_to_apply ) - # SIMULATE RECONCILE REQUEST FROM AGENTK UPDATING WORKSPACE TO RUNNING ACTUAL_STATE + # SIMULATE TRAVEL FORWARD IN TIME MAX_ACTIVE_HOURS_BEFORE_STOP HOURS simulate_ninth_poll( workspace: workspace.reload, agent_token: agent_token, - # TRAVEL FORWARD IN TIME MAX_ACTIVE_HOURS_BEFORE_STOP HOURS - time_to_travel_after_poll: workspace.workspaces_agent_config.max_active_hours_before_stop.hours + actual_state: states::RUNNING, + time_to_travel_after_poll: workspace.workspaces_agent_config&.max_active_hours_before_stop&.hours, + **additional_args_for_expected_config_to_apply ) # SIMULATE RECONCILE RESPONSE TO AGENTK UPDATING WORKSPACE TO STOPPED DESIRED_STATE simulate_tenth_poll( workspace: workspace.reload, agent_token: agent_token, + actual_state: states::RUNNING, **additional_args_for_expected_config_to_apply ) # SIMULATE RECONCILE REQUEST FROM AGENTK UPDATING WORKSPACE TO STOPPING ACTUAL_STATE - simulate_eleventh_poll(workspace: workspace.reload, agent_token: agent_token) + simulate_eleventh_poll( + workspace: workspace.reload, + agent_token: agent_token, + actual_state: states::STOPPING, + **additional_args_for_expected_config_to_apply + ) # SIMULATE RECONCILE REQUEST FROM AGENTK UPDATING WORKSPACE TO STOPPED ACTUAL_STATE simulate_twelfth_poll( workspace: workspace.reload, agent_token: agent_token, + actual_state: states::STOPPED, # TRAVEL FORWARD IN TIME MAX_STOPPED_HOURS_BEFORE_TERMINATION HOURS - time_to_travel_after_poll: workspace.workspaces_agent_config.max_stopped_hours_before_termination.hours + time_to_travel_after_poll: workspace.workspaces_agent_config&.max_stopped_hours_before_termination&.hours, + **additional_args_for_expected_config_to_apply ) # SIMULATE RECONCILE RESPONSE TO AGENTK UPDATING WORKSPACE TO TERMINATED DESIRED_STATE simulate_thirteenth_poll( workspace: workspace.reload, agent_token: agent_token, + actual_state: states::TERMINATED, **additional_args_for_expected_config_to_apply ) @@ -477,11 +517,16 @@ def do_reconcile_post(params:, agent_token:) simulate_fourteenth_poll( workspace: workspace.reload, agent_token: agent_token, + actual_state: states::TERMINATING, **additional_args_for_expected_config_to_apply ) # SIMULATE RECONCILE REQUEST FROM AGENTK UPDATING WORKSPACE TO TERMINATED ACTUAL_STATE - simulate_fifteenth_poll(workspace: workspace.reload, agent_token: agent_token) + simulate_fifteenth_poll( + workspace: workspace.reload, + agent_token: agent_token, + **additional_args_for_expected_config_to_apply + ) end end diff --git a/ee/spec/support/helpers/remote_development/integration_spec_helpers.rb b/ee/spec/support/helpers/remote_development/integration_spec_helpers.rb index 8af750cc78a8dd..dbf00f5688e2af 100644 --- a/ee/spec/support/helpers/remote_development/integration_spec_helpers.rb +++ b/ee/spec/support/helpers/remote_development/integration_spec_helpers.rb @@ -150,17 +150,27 @@ def assert_response( nil end + # @return [String (frozen)] + def workspace_reconciled_actual_state_file_name + RemoteDevelopment::WorkspaceOperations::Reconcile::ReconcileConstants::WORKSPACE_RECONCILED_ACTUAL_STATE_FILE_NAME + end + # @param [Workspace] workspace # @param [QA::Resource::Clusters::AgentToken] agent_token + # @param [String] actual_state # @param [Hash] additional_args_for_create_config_to_apply_yaml_stream # @return [void] - def simulate_first_poll(workspace:, agent_token:, **additional_args_for_create_config_to_apply_yaml_stream) + def simulate_first_poll( + workspace:, agent_token:, actual_state:, **additional_args_for_create_config_to_apply_yaml_stream) # SIMULATE RECONCILE RESPONSE TO AGENTK SENDING NEW WORKSPACE expected_config_to_apply_yaml_stream = create_config_to_apply_yaml_stream( workspace: workspace, started: true, include_all_resources: true, + workspace_variables_additional_data: { + "#{workspace_reconciled_actual_state_file_name}": actual_state + }, **additional_args_for_create_config_to_apply_yaml_stream ) @@ -180,8 +190,11 @@ def simulate_first_poll(workspace:, agent_token:, **additional_args_for_create_c # @param [Workspace] workspace # @param [QA::Resource::Clusters::AgentToken] agent_token + # @param [String] actual_state + # @param [Hash] additional_args_for_create_config_to_apply_yaml_stream # @return [void] - def simulate_second_poll(workspace:, agent_token:) + def simulate_second_poll( + workspace:, agent_token:, actual_state:, **additional_args_for_create_config_to_apply_yaml_stream) # SIMULATE RECONCILE REQUEST FROM AGENTK UPDATING WORKSPACE TO RUNNING ACTUAL_STATE resource_version = '1' @@ -193,6 +206,16 @@ def simulate_second_poll(workspace:, agent_token:) resource_version: resource_version ) + expected_config_to_apply_yaml_stream = create_config_to_apply_yaml_stream( + workspace: workspace, + started: true, + include_all_resources: true, + workspace_variables_additional_data: { + "#{workspace_reconciled_actual_state_file_name}": actual_state + }, + **additional_args_for_create_config_to_apply_yaml_stream + ) + simulate_poll( workspace: workspace, agent_token: agent_token, @@ -201,7 +224,7 @@ def simulate_second_poll(workspace:, agent_token:) expected_desired_state: RUNNING, expected_actual_state: RUNNING, expected_resource_version: resource_version, - expected_config_to_apply_yaml_stream: "" + expected_config_to_apply_yaml_stream: expected_config_to_apply_yaml_stream ) nil @@ -209,11 +232,13 @@ def simulate_second_poll(workspace:, agent_token:) # @param [Workspace] workspace # @param [QA::Resource::Clusters::AgentToken] agent_token + # @param [String] actual_state # @param [Hash] additional_args_for_create_config_to_apply_yaml_stream # @return [void] def simulate_third_poll( workspace:, agent_token:, + actual_state:, **additional_args_for_create_config_to_apply_yaml_stream ) # SIMULATE RECONCILE RESPONSE TO AGENTK UPDATING WORKSPACE TO STOPPED DESIRED_STATE @@ -230,6 +255,9 @@ def simulate_third_poll( expected_config_to_apply_yaml_stream = create_config_to_apply_yaml_stream( workspace: workspace, started: false, + workspace_variables_additional_data: { + "#{workspace_reconciled_actual_state_file_name}": actual_state + }, **additional_args_for_create_config_to_apply_yaml_stream ) @@ -249,8 +277,15 @@ def simulate_third_poll( # @param [Workspace] workspace # @param [QA::Resource::Clusters::AgentToken] agent_token + # @param [String] actual_state + # @param [Hash] additional_args_for_create_config_to_apply_yaml_stream # @return [void] - def simulate_fourth_poll(workspace:, agent_token:) + def simulate_fourth_poll( + workspace:, + agent_token:, + actual_state:, + **additional_args_for_create_config_to_apply_yaml_stream + ) # SIMULATE RECONCILE REQUEST FROM AGENTK UPDATING WORKSPACE TO STOPPING ACTUAL_STATE resource_version = '2' @@ -262,6 +297,16 @@ def simulate_fourth_poll(workspace:, agent_token:) resource_version: resource_version ) + expected_config_to_apply_yaml_stream = create_config_to_apply_yaml_stream( + workspace: workspace, + started: false, + include_all_resources: true, + workspace_variables_additional_data: { + "#{workspace_reconciled_actual_state_file_name}": actual_state + }, + **additional_args_for_create_config_to_apply_yaml_stream + ) + simulate_poll( workspace: workspace, agent_token: agent_token, @@ -270,7 +315,7 @@ def simulate_fourth_poll(workspace:, agent_token:) expected_desired_state: STOPPED, expected_actual_state: STOPPING, expected_resource_version: resource_version, - expected_config_to_apply_yaml_stream: "" + expected_config_to_apply_yaml_stream: expected_config_to_apply_yaml_stream ) nil @@ -278,8 +323,15 @@ def simulate_fourth_poll(workspace:, agent_token:) # @param [Workspace] workspace # @param [QA::Resource::Clusters::AgentToken] agent_token + # @param [String] actual_state + # @param [Hash] additional_args_for_create_config_to_apply_yaml_stream # @return [void] - def simulate_fifth_poll(workspace:, agent_token:) + def simulate_fifth_poll( + workspace:, + agent_token:, + actual_state:, + **additional_args_for_create_config_to_apply_yaml_stream + ) # SIMULATE RECONCILE REQUEST FROM AGENTK UPDATING WORKSPACE TO STOPPED ACTUAL_STATE resource_version = '3' @@ -291,6 +343,16 @@ def simulate_fifth_poll(workspace:, agent_token:) resource_version: resource_version ) + expected_config_to_apply_yaml_stream = create_config_to_apply_yaml_stream( + workspace: workspace, + started: false, + include_all_resources: true, + workspace_variables_additional_data: { + "#{workspace_reconciled_actual_state_file_name}": actual_state + }, + **additional_args_for_create_config_to_apply_yaml_stream + ) + simulate_poll( workspace: workspace, agent_token: agent_token, @@ -299,7 +361,7 @@ def simulate_fifth_poll(workspace:, agent_token:) expected_desired_state: STOPPED, expected_actual_state: STOPPED, expected_resource_version: resource_version, - expected_config_to_apply_yaml_stream: "" + expected_config_to_apply_yaml_stream: expected_config_to_apply_yaml_stream ) nil @@ -328,11 +390,13 @@ def simulate_sixth_poll(agent_token:) # @param [Workspace] workspace # @param [QA::Resource::Clusters::AgentToken] agent_token + # @param [String] actual_state # @param [Hash] additional_args_for_create_config_to_apply_yaml_stream # @return [void] def simulate_seventh_poll( workspace:, agent_token:, + actual_state:, **additional_args_for_create_config_to_apply_yaml_stream ) # SIMULATE RECONCILE RESPONSE TO AGENTK FOR FULL RECONCILE TO SHOW ALL WORKSPACES ARE SENT IN RAILS_INFOS @@ -350,6 +414,9 @@ def simulate_seventh_poll( workspace: workspace, started: false, include_all_resources: true, + workspace_variables_additional_data: { + "#{workspace_reconciled_actual_state_file_name}": actual_state + }, **additional_args_for_create_config_to_apply_yaml_stream ) @@ -369,11 +436,13 @@ def simulate_seventh_poll( # @param [Workspace] workspace # @param [QA::Resource::Clusters::AgentToken] agent_token + # @param [String] actual_state # @param [Hash] additional_args_for_create_config_to_apply_yaml_stream # @return [void] def simulate_eighth_poll( workspace:, agent_token:, + actual_state:, **additional_args_for_create_config_to_apply_yaml_stream ) # SIMULATE RECONCILE RESPONSE TO AGENTK UPDATING WORKSPACE TO RUNNING DESIRED_STATE @@ -390,6 +459,9 @@ def simulate_eighth_poll( expected_config_to_apply_yaml_stream = create_config_to_apply_yaml_stream( workspace: workspace, started: true, + workspace_variables_additional_data: { + "#{workspace_reconciled_actual_state_file_name}": actual_state + }, **additional_args_for_create_config_to_apply_yaml_stream ) @@ -409,20 +481,41 @@ def simulate_eighth_poll( # @param [Workspace] workspace # @param [QA::Resource::Clusters::AgentToken] agent_token + # @param [String] actual_state # @param [Integer] time_to_travel_after_poll + # @param [Hash] additional_args_for_create_config_to_apply_yaml_stream # @return [void] - def simulate_ninth_poll(workspace:, agent_token:, time_to_travel_after_poll:) + def simulate_ninth_poll( + workspace:, + agent_token:, + actual_state:, + time_to_travel_after_poll:, + **additional_args_for_create_config_to_apply_yaml_stream + ) # SIMULATE RECONCILE REQUEST FROM AGENTK UPDATING WORKSPACE TO RUNNING ACTUAL_STATE resource_version = '4' workspace_agent_info = create_workspace_agent_info_hash( workspace: workspace, + # NOTE: previous_actual_state is the cluster state IMMEDIATELY prior to the current actual_state, NOT the + # previous actual_state that was last received and saved in the database (which may miss some state + # transitions that happen between reconciles). previous_actual_state: STARTING, current_actual_state: RUNNING, workspace_exists: true, resource_version: resource_version ) + expected_config_to_apply_yaml_stream = create_config_to_apply_yaml_stream( + workspace: workspace, + started: true, + include_all_resources: true, + workspace_variables_additional_data: { + "#{workspace_reconciled_actual_state_file_name}": actual_state + }, + **additional_args_for_create_config_to_apply_yaml_stream + ) + simulate_poll( workspace: workspace, agent_token: agent_token, @@ -431,7 +524,7 @@ def simulate_ninth_poll(workspace:, agent_token:, time_to_travel_after_poll:) expected_desired_state: RUNNING, expected_actual_state: RUNNING, expected_resource_version: resource_version, - expected_config_to_apply_yaml_stream: "", + expected_config_to_apply_yaml_stream: expected_config_to_apply_yaml_stream, time_to_travel_after_poll: time_to_travel_after_poll ) @@ -440,11 +533,13 @@ def simulate_ninth_poll(workspace:, agent_token:, time_to_travel_after_poll:) # @param [Workspace] workspace # @param [QA::Resource::Clusters::AgentToken] agent_token + # @param [String] actual_state # @param [Hash] additional_args_for_create_config_to_apply_yaml_stream # @return [void] def simulate_tenth_poll( workspace:, agent_token:, + actual_state:, **additional_args_for_create_config_to_apply_yaml_stream ) # SIMULATE RECONCILE RESPONSE TO AGENTK UPDATING WORKSPACE TO STOPPED DESIRED_STATE @@ -461,6 +556,9 @@ def simulate_tenth_poll( expected_config_to_apply_yaml_stream = create_config_to_apply_yaml_stream( workspace: workspace, started: false, + workspace_variables_additional_data: { + "#{workspace_reconciled_actual_state_file_name}": actual_state + }, **additional_args_for_create_config_to_apply_yaml_stream ) @@ -480,8 +578,15 @@ def simulate_tenth_poll( # @param [Workspace] workspace # @param [QA::Resource::Clusters::AgentToken] agent_token + # @param [String] actual_state + # @param [Hash] additional_args_for_create_config_to_apply_yaml_stream # @return [void] - def simulate_eleventh_poll(workspace:, agent_token:) + def simulate_eleventh_poll( + workspace:, + agent_token:, + actual_state:, + **additional_args_for_create_config_to_apply_yaml_stream + ) # SIMULATE RECONCILE REQUEST FROM AGENTK UPDATING WORKSPACE TO STOPPING ACTUAL_STATE resource_version = '5' @@ -493,6 +598,16 @@ def simulate_eleventh_poll(workspace:, agent_token:) resource_version: resource_version ) + expected_config_to_apply_yaml_stream = create_config_to_apply_yaml_stream( + workspace: workspace, + started: false, + include_all_resources: true, + workspace_variables_additional_data: { + "#{workspace_reconciled_actual_state_file_name}": actual_state + }, + **additional_args_for_create_config_to_apply_yaml_stream + ) + simulate_poll( workspace: workspace, agent_token: agent_token, @@ -501,7 +616,7 @@ def simulate_eleventh_poll(workspace:, agent_token:) expected_desired_state: STOPPED, expected_actual_state: STOPPING, expected_resource_version: resource_version, - expected_config_to_apply_yaml_stream: "" + expected_config_to_apply_yaml_stream: expected_config_to_apply_yaml_stream ) nil @@ -509,9 +624,17 @@ def simulate_eleventh_poll(workspace:, agent_token:) # @param [Workspace] workspace # @param [QA::Resource::Clusters::AgentToken] agent_token + # @param [String] actual_state # @param [Integer] time_to_travel_after_poll + # @param [Hash] additional_args_for_create_config_to_apply_yaml_stream # @return [void] - def simulate_twelfth_poll(workspace:, agent_token:, time_to_travel_after_poll:) + def simulate_twelfth_poll( + workspace:, + agent_token:, + actual_state:, + time_to_travel_after_poll:, + **additional_args_for_create_config_to_apply_yaml_stream + ) # SIMULATE RECONCILE REQUEST FROM AGENTK UPDATING WORKSPACE TO STOPPED ACTUAL_STATE resource_version = '6' @@ -523,6 +646,16 @@ def simulate_twelfth_poll(workspace:, agent_token:, time_to_travel_after_poll:) resource_version: resource_version ) + expected_config_to_apply_yaml_stream = create_config_to_apply_yaml_stream( + workspace: workspace, + started: false, + include_all_resources: true, + workspace_variables_additional_data: { + "#{workspace_reconciled_actual_state_file_name}": actual_state + }, + **additional_args_for_create_config_to_apply_yaml_stream + ) + simulate_poll( workspace: workspace, agent_token: agent_token, @@ -531,7 +664,7 @@ def simulate_twelfth_poll(workspace:, agent_token:, time_to_travel_after_poll:) expected_desired_state: STOPPED, expected_actual_state: STOPPED, expected_resource_version: resource_version, - expected_config_to_apply_yaml_stream: "", + expected_config_to_apply_yaml_stream: expected_config_to_apply_yaml_stream, time_to_travel_after_poll: time_to_travel_after_poll ) @@ -540,11 +673,13 @@ def simulate_twelfth_poll(workspace:, agent_token:, time_to_travel_after_poll:) # @param [Workspace] workspace # @param [QA::Resource::Clusters::AgentToken] agent_token + # @param [String] actual_state # @param [Hash] additional_args_for_create_config_to_apply_yaml_stream # @return [void] def simulate_thirteenth_poll( workspace:, agent_token:, + actual_state:, **additional_args_for_create_config_to_apply_yaml_stream ) # SIMULATE RECONCILE RESPONSE TO AGENTK UPDATING WORKSPACE TO TERMINATED DESIRED_STATE @@ -562,6 +697,9 @@ def simulate_thirteenth_poll( workspace: workspace, started: false, desired_state_is_terminated: true, + workspace_variables_additional_data: { + "#{workspace_reconciled_actual_state_file_name}": actual_state + }, **additional_args_for_create_config_to_apply_yaml_stream ) @@ -581,14 +719,16 @@ def simulate_thirteenth_poll( # @param [Workspace] workspace # @param [QA::Resource::Clusters::AgentToken] agent_token + # @param [String] actual_state # @param [Hash] additional_args_for_create_config_to_apply_yaml_stream # @return [void] def simulate_fourteenth_poll( workspace:, agent_token:, + actual_state:, **additional_args_for_create_config_to_apply_yaml_stream ) - # SIMULATE RECONCILE REQUEST FROM AGENTK UPDATING WORKSPACE TO STOPPING ACTUAL_STATE + # SIMULATE RECONCILE REQUEST FROM AGENTK UPDATING WORKSPACE TO TERMINATING ACTUAL_STATE workspace_agent_info = create_workspace_agent_info_hash( workspace: workspace, @@ -602,6 +742,10 @@ def simulate_fourteenth_poll( workspace: workspace, started: false, desired_state_is_terminated: true, + include_all_resources: true, + workspace_variables_additional_data: { + "#{workspace_reconciled_actual_state_file_name}": actual_state + }, **additional_args_for_create_config_to_apply_yaml_stream ) @@ -621,8 +765,13 @@ def simulate_fourteenth_poll( # @param [Workspace] workspace # @param [QA::Resource::Clusters::AgentToken] agent_token + # @param [Hash] additional_args_for_create_config_to_apply_yaml_stream # @return [void] - def simulate_fifteenth_poll(workspace:, agent_token:) + def simulate_fifteenth_poll( + workspace:, + agent_token:, + **additional_args_for_create_config_to_apply_yaml_stream + ) # SIMULATE RECONCILE REQUEST FROM AGENTK UPDATING WORKSPACE TO TERMINATED ACTUAL_STATE workspace_agent_info = create_workspace_agent_info_hash( @@ -633,6 +782,13 @@ def simulate_fifteenth_poll(workspace:, agent_token:) resource_version: nil ) + expected_config_to_apply_yaml_stream = create_config_to_apply_yaml_stream( + workspace: workspace, + started: false, + desired_state_is_terminated: true, + **additional_args_for_create_config_to_apply_yaml_stream + ) + simulate_poll( workspace: workspace, agent_token: agent_token, @@ -641,7 +797,7 @@ def simulate_fifteenth_poll(workspace:, agent_token:) expected_desired_state: TERMINATED, expected_actual_state: TERMINATED, expected_resource_version: '6', - expected_config_to_apply_yaml_stream: "" + expected_config_to_apply_yaml_stream: expected_config_to_apply_yaml_stream ) nil diff --git a/ee/spec/support/shared_contexts/remote_development/remote_development_shared_contexts.rb b/ee/spec/support/shared_contexts/remote_development/remote_development_shared_contexts.rb index 514bac8fddeb00..7c689bef009092 100644 --- a/ee/spec/support/shared_contexts/remote_development/remote_development_shared_contexts.rb +++ b/ee/spec/support/shared_contexts/remote_development/remote_development_shared_contexts.rb @@ -14,9 +14,11 @@ # @param [Boolean] workspace_exists # @param [Hash] workspace_variables_environment # @param [Hash] workspace_variables_file + # @param [Hash] workspace_variables_additional_data # @param [String] resource_version # @param [String] dns_zone # @param [Hash] error_details + # @return [Hash] def create_workspace_agent_info_hash( workspace:, # NOTE: previous_actual_state is the actual state of the workspace IMMEDIATELY prior to the current state. We don't @@ -27,6 +29,7 @@ def create_workspace_agent_info_hash( workspace_exists:, workspace_variables_environment: nil, workspace_variables_file: nil, + workspace_variables_additional_data: nil, resource_version: '1', dns_zone: 'workspaces.localdev.me', error_details: nil @@ -287,6 +290,7 @@ def create_workspace_agent_info_hash( workspace: workspace, workspace_variables_environment: workspace_variables_environment, workspace_variables_file: workspace_variables_file, + workspace_variables_additional_data: workspace_variables_additional_data, started: started, include_inventory: false, include_network_policy: false, @@ -303,7 +307,7 @@ def create_workspace_agent_info_hash( info end - # rubocop:enable Metrics/ParameterLists, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity + # rubocop:enable Metrics/ParameterLists, Metrics/PerceivedComplexity # @param [RemoteDevelopment::Workspace] workspace # @param [Hash] args @@ -331,6 +335,7 @@ def create_config_to_apply(workspace:, **args) # @param [Boolean] desired_state_is_terminated # @param [Hash] workspace_variables_environment # @param [Hash] workspace_variables_file + # @param [Hash] workspace_variables_additional_data # @param [Boolean] include_inventory # @param [Boolean] include_network_policy # @param [Boolean] include_all_resources @@ -354,6 +359,7 @@ def create_config_to_apply_v3( desired_state_is_terminated: false, workspace_variables_environment: nil, workspace_variables_file: nil, + workspace_variables_additional_data: nil, include_inventory: true, include_network_policy: true, include_all_resources: false, @@ -453,14 +459,17 @@ def create_config_to_apply_v3( ) ) + workspace_reconciled_actual_state_file_name = + RemoteDevelopment::WorkspaceOperations::Reconcile::ReconcileConstants::WORKSPACE_RECONCILED_ACTUAL_STATE_FILE_NAME secret_file = secret_file( workspace_name: workspace.name, workspace_namespace: workspace.namespace, labels: labels, annotations: secrets_inventory_annotations, - workspace_variables_file: workspace_variables_file || get_workspace_variables_file( - workspace_variables: workspace.workspace_variables - ) + workspace_variables_file: workspace_variables_file || + get_workspace_variables_file(workspace_variables: workspace.workspace_variables), + additional_data: workspace_variables_additional_data || + { "#{workspace_reconciled_actual_state_file_name}": workspace.actual_state } ) if max_resources_per_workspace.present? @@ -508,7 +517,7 @@ def create_config_to_apply_v3( normalize_resources(namespace_path, project_name, resources) end - # rubocop:enable Metrics/ParameterLists, Metrics/AbcSize + # rubocop:enable Metrics/ParameterLists, Metrics/CyclomaticComplexity, Metrics/AbcSize # @param [String] workspace_name # @param [String] workspace_namespace @@ -1108,14 +1117,18 @@ def secret_environment( # @param [Hash] labels # @param [Hash] annotations # @param [Hash] workspace_variables_file + # @param [Hash] additional_data # @return [Hash] def secret_file( workspace_name:, workspace_namespace:, labels:, annotations:, - workspace_variables_file: + workspace_variables_file:, + additional_data: ) + data = workspace_variables_file.merge(additional_data) + { kind: "Secret", apiVersion: "v1", @@ -1125,7 +1138,7 @@ def secret_file( labels: labels, annotations: annotations }, - data: workspace_variables_file.transform_values { |v| Base64.strict_encode64(v).to_s } + data: data.transform_values { |v| Base64.strict_encode64(v).to_s } } end -- GitLab