diff --git a/ee/lib/remote_development/files.rb b/ee/lib/remote_development/files.rb index ba3a32d5d28204f7f8fd8d64d6aec4eda3501bad..f535d6bcbdfd0c3ffe717ff98c0af383400cd6c3 100644 --- a/ee/lib/remote_development/files.rb +++ b/ee/lib/remote_development/files.rb @@ -30,10 +30,18 @@ def self.read_file(path) DEFAULT_DEVFILE_YAML = read_file("settings/default_devfile.yaml") GIT_CREDENTIAL_STORE_SCRIPT = read_file("workspace_operations/create/workspace_variables_git_credential_store.sh") - PROJECTS_CLONER_COMPONENT_INSERTER_CONTAINER_ARGS = - read_file("workspace_operations/create/project_cloner_component_inserter_container_args.sh") + KUBERNETES_POSTSTART_HOOK_COMMAND = + read_file("workspace_operations/reconcile/output/kubernetes_poststart_hook_command.sh") MAIN_COMPONENT_UPDATER_CONTAINER_ARGS = read_file("workspace_operations/create/main_component_updater_container_args.sh") + MAIN_COMPONENT_UPDATER_INIT_TOOLS_SCRIPT = + read_file("workspace_operations/create/main_component_updater_init_tools.sh") + MAIN_COMPONENT_UPDATER_SLEEP_UNTIL_CONTAINER_IS_RUNNING_SCRIPT = + read_file("workspace_operations/create/main_component_updater_sleep_until_workspace_is_running.sh") + MAIN_COMPONENT_UPDATER_START_SSHD_SCRIPT = + read_file("workspace_operations/create/main_component_updater_start_sshd.sh") + PROJECTS_CLONER_COMPONENT_INSERTER_CONTAINER_ARGS = + read_file("workspace_operations/create/project_cloner_component_inserter_container_args.sh") private_class_method :read_file end diff --git a/ee/lib/remote_development/workspace_operations/create/main_component_updater.rb b/ee/lib/remote_development/workspace_operations/create/main_component_updater.rb index 282bc511802a8708aeb1482f98762086fd05423f..7832d2d0fa396037a47f0491849c524ff511fe49 100644 --- a/ee/lib/remote_development/workspace_operations/create/main_component_updater.rb +++ b/ee/lib/remote_development/workspace_operations/create/main_component_updater.rb @@ -30,6 +30,14 @@ def self.update(context) main_component_container = main_component.fetch(:container) + # TODO: When user-defined postStart commands are supported, add validation in previous devfile validation + # steps of the ".../workspace_operations/create" ROP chain. + # See: https://gitlab.com/gitlab-org/gitlab/-/issues/505988 + add_poststart_commands( + processed_devfile: processed_devfile, + main_component: main_component + ) + update_env_vars( main_component_container: main_component_container, tools_dir: tools_dir, @@ -110,6 +118,61 @@ def self.update_endpoints(main_component_container:, editor_port:, ssh_port:) nil end + # @param [Hash] processed_devfile + # @param [Hash] main_component + # @return [void] + def self.add_poststart_commands(processed_devfile:, main_component:) + processed_devfile => { + commands: Array => commands, + events: { + postStart: Array => poststart_events + } + } + + main_component => { name: String => main_component_name } + + # Add the start_sshd event + start_sshd_command_id = "gl-start-sshd-command" + commands << { + id: start_sshd_command_id, + exec: { + commandLine: MAIN_COMPONENT_UPDATER_START_SSHD_SCRIPT, + component: main_component_name + } + } + poststart_events << start_sshd_command_id + + # Add the init_tools event + init_tools_command_id = "gl-init-tools-command" + commands << { + id: init_tools_command_id, + exec: { + commandLine: MAIN_COMPONENT_UPDATER_INIT_TOOLS_SCRIPT, + component: main_component_name + } + } + poststart_events << init_tools_command_id + + # Add the sleep_until_container_is_running event + sleep_until_container_is_running_command_id = "gl-sleep-until-container-is-running-command" + sleep_until_container_is_running_script = + format( + MAIN_COMPONENT_UPDATER_SLEEP_UNTIL_CONTAINER_IS_RUNNING_SCRIPT, + workspace_reconciled_actual_state_file_path: WORKSPACE_RECONCILED_ACTUAL_STATE_FILE_PATH + ) + + commands << { + id: sleep_until_container_is_running_command_id, + exec: { + commandLine: sleep_until_container_is_running_script, + component: main_component_name + } + } + poststart_events << sleep_until_container_is_running_command_id + + nil + end + # @param [Hash] main_component_container # @return [void] def self.override_command_and_args(main_component_container:) @@ -123,7 +186,7 @@ def self.override_command_and_args(main_component_container:) nil end - private_class_method :update_env_vars, :update_endpoints, :override_command_and_args + private_class_method :update_env_vars, :update_endpoints, :add_poststart_commands, :override_command_and_args end end end diff --git a/ee/lib/remote_development/workspace_operations/create/main_component_updater_container_args.sh b/ee/lib/remote_development/workspace_operations/create/main_component_updater_container_args.sh index 0b00f476a16a9742d778a121252349acb6183c90..cc66f3856513467c52323d69a1a25248d64d16a9 100644 --- a/ee/lib/remote_development/workspace_operations/create/main_component_updater_container_args.sh +++ b/ee/lib/remote_development/workspace_operations/create/main_component_updater_container_args.sh @@ -1,8 +1 @@ -sshd_path=$(which sshd) -if [ -x "$sshd_path" ]; then - echo "Starting sshd on port ${GL_SSH_PORT}" - $sshd_path -D -p "${GL_SSH_PORT}" & -else - echo "'sshd' not found in path. Not starting SSH server." -fi -"${GL_TOOLS_DIR}/init_tools.sh" +tail -f /dev/null diff --git a/ee/lib/remote_development/workspace_operations/create/main_component_updater_init_tools.sh b/ee/lib/remote_development/workspace_operations/create/main_component_updater_init_tools.sh new file mode 100644 index 0000000000000000000000000000000000000000..acf8892ead0d21452819bf014bde44723946cc4c --- /dev/null +++ b/ee/lib/remote_development/workspace_operations/create/main_component_updater_init_tools.sh @@ -0,0 +1,5 @@ +#!/bin/sh +echo "$(date -Iseconds): ----------------------------------------" +echo "$(date -Iseconds): Running ${GL_TOOLS_DIR}/init_tools.sh with output written to ${GL_WORKSPACE_LOGS_DIR}/init-tools.log..." +"${GL_TOOLS_DIR}/init_tools.sh" >> "${GL_WORKSPACE_LOGS_DIR}/init-tools.log" 2>&1 & +echo "$(date -Iseconds): Finished running ${GL_TOOLS_DIR}/init_tools.sh." diff --git a/ee/lib/remote_development/workspace_operations/create/main_component_updater_sleep_until_workspace_is_running.sh b/ee/lib/remote_development/workspace_operations/create/main_component_updater_sleep_until_workspace_is_running.sh new file mode 100644 index 0000000000000000000000000000000000000000..a4dc9035c950ac9627a38df0391ecff558828be8 --- /dev/null +++ b/ee/lib/remote_development/workspace_operations/create/main_component_updater_sleep_until_workspace_is_running.sh @@ -0,0 +1,11 @@ +#!/bin/sh +echo "$(date -Iseconds): ----------------------------------------" +echo "$(date -Iseconds): Sleeping until workspace is running..." +time_to_sleep=5 +status_file="%s" +while [ "$(cat ${status_file})" != "Running" ]; do + echo "$(date -Iseconds): Workspace state is '$(cat ${status_file})' from status file '${status_file}'. Blocking remaining postStart events execution for ${time_to_sleep} seconds until state is 'Running'..." + sleep ${time_to_sleep} +done +echo "$(date -Iseconds): Workspace state is now 'Running', continuing postStart hook execution." +echo "$(date -Iseconds): Finished sleeping until workspace is running." diff --git a/ee/lib/remote_development/workspace_operations/create/main_component_updater_start_sshd.sh b/ee/lib/remote_development/workspace_operations/create/main_component_updater_start_sshd.sh new file mode 100644 index 0000000000000000000000000000000000000000..cc4adb32df67dfdf8f3a5aac21751cf43400366b --- /dev/null +++ b/ee/lib/remote_development/workspace_operations/create/main_component_updater_start_sshd.sh @@ -0,0 +1,11 @@ +#!/bin/sh +echo "$(date -Iseconds): ----------------------------------------" +echo "$(date -Iseconds): Starting sshd if it is found..." +sshd_path=$(which sshd) +if [ -x "${sshd_path}" ]; then + echo "$(date -Iseconds): Starting ${sshd_path} on port ${GL_SSH_PORT} with output written to ${GL_WORKSPACE_LOGS_DIR}/start-sshd.log" + "${sshd_path}" -D -p "${GL_SSH_PORT}" >> "${GL_WORKSPACE_LOGS_DIR}/start-sshd.log" 2>&1 & +else + echo "$(date -Iseconds): 'sshd' not found in path. Not starting SSH server." >&2 +fi +echo "$(date -Iseconds): Finished starting sshd if it is found." diff --git a/ee/lib/remote_development/workspace_operations/create/project_cloner_component_inserter_container_args.sh b/ee/lib/remote_development/workspace_operations/create/project_cloner_component_inserter_container_args.sh index f7251ea89121c4e92b8f2c85c6bc0f0df72393d6..8f11fe70e9057e3dc828af96f90e5d21f124156f 100644 --- a/ee/lib/remote_development/workspace_operations/create/project_cloner_component_inserter_container_args.sh +++ b/ee/lib/remote_development/workspace_operations/create/project_cloner_component_inserter_container_args.sh @@ -1,23 +1,30 @@ -if [ -f "%s" ]; +echo "$(date -Iseconds): ----------------------------------------" +echo "$(date -Iseconds): Cloning project if necessary..." + +if [ -f "%s" ] then - echo "Project cloning was already successful"; - exit 0; + echo "$(date -Iseconds): Project cloning was already successful" + exit 0 fi -if [ -d "%s" ]; + +if [ -d "%s" ] then - echo "Removing unsuccessfully cloned project directory"; - rm -rf "%s"; + echo "$(date -Iseconds): Removing unsuccessfully cloned project directory" + rm -rf "%s" fi -echo "Cloning project"; -git clone --branch "%s" "%s" "%s"; + +echo "$(date -Iseconds): Cloning project" +git clone --branch "%s" "%s" "%s" exit_code=$? -if [ "${exit_code}" -eq 0 ]; + +if [ "${exit_code}" -eq 0 ] then - echo "Project cloning successful"; - touch "%s"; - echo "Updated file to indicate successful project cloning"; - exit 0; + echo "$(date -Iseconds): Project cloning successful" + touch "%s" + echo "$(date -Iseconds): Updated file to indicate successful project cloning" else - echo "Project cloning failed with exit code: ${exit_code}"; - exit "${exit_code}"; + echo "$(date -Iseconds): Project cloning failed with exit code: ${exit_code}" >&2 fi + +echo "$(date -Iseconds): Finished cloning project if necessary." +exit "${exit_code}" 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 cfc51fb53e915a2d98088acec8d66dd1871ff4ca..f156ba03be159ffcb7caff9e3398e162db551076 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 @@ -30,6 +30,15 @@ def self.build( internal_variables = [ + #------------------------------------------------------------------- + # The directory to which logs related to the creation and management of the workspace are written. + # For example, logs from the poststart events. + { + key: "GL_WORKSPACE_LOGS_DIR", + value: WORKSPACE_LOGS_DIR, + variable_type: ENVIRONMENT_TYPE, + workspace_id: workspace_id + }, #------------------------------------------------------------------- # 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. @@ -50,6 +59,7 @@ def self.build( #------------------------------------------------------------------- # Standard git ENV vars which configure git on the workspace. See https://git-scm.com/docs/git-config { + # TODO: Move this entry to the scripts volume: https://gitlab.com/gitlab-org/gitlab/-/issues/539045 # This script is set as the value of `credential.helper` below in `GIT_CONFIG_VALUE_0` key: GIT_CREDENTIAL_STORE_SCRIPT_FILE_NAME, value: GIT_CREDENTIAL_STORE_SCRIPT, diff --git a/ee/lib/remote_development/workspace_operations/create/workspace_variables_git_credential_store.sh b/ee/lib/remote_development/workspace_operations/create/workspace_variables_git_credential_store.sh index 704a9be35d094298649c01db07ed635bd6e85f84..517eeb6cee4ca109e87e4977cf02307e252f2109 100644 --- a/ee/lib/remote_development/workspace_operations/create/workspace_variables_git_credential_store.sh +++ b/ee/lib/remote_development/workspace_operations/create/workspace_variables_git_credential_store.sh @@ -7,7 +7,7 @@ fi if [ -z "${GL_TOKEN_FILE_PATH}" ]; then - echo "We could not find the GL_TOKEN_FILE_PATH variable" + echo "$(date -Iseconds): We could not find the GL_TOKEN_FILE_PATH variable" >&2 exit 1 fi password=$(cat "${GL_TOKEN_FILE_PATH}") diff --git a/ee/lib/remote_development/workspace_operations/reconcile/output/config_values_extractor.rb b/ee/lib/remote_development/workspace_operations/reconcile/output/config_values_extractor.rb index 9b9b4312bc86c145f10516a1b80e41b4cda5bd16..39735a3ee777e2e109b1f51c0e8f94f7a1a2929c 100644 --- a/ee/lib/remote_development/workspace_operations/reconcile/output/config_values_extractor.rb +++ b/ee/lib/remote_development/workspace_operations/reconcile/output/config_values_extractor.rb @@ -44,6 +44,7 @@ def self.extract(workspace:) workspace_inventory_name = "#{workspace_name}-workspace-inventory" secrets_inventory_name = "#{workspace_name}-secrets-inventory" + scripts_configmap_name = "#{workspace_name}-scripts-configmap" { # Please keep alphabetized @@ -69,6 +70,7 @@ def self.extract(workspace:) network_policy_egress: deep_sort_and_symbolize_hashes(workspaces_agent_config.network_policy_egress), processed_devfile_yaml: workspace.processed_devfile, replicas: workspace.desired_state_running? ? 1 : 0, + scripts_configmap_name: scripts_configmap_name, secrets_inventory_annotations: deep_sort_and_symbolize_hashes( common_annotations.merge("config.k8s.io/owning-inventory": secrets_inventory_name) 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 b97564e39c6b0603db9649e2015f1a9741fb3879..c0193527feab9e6a392ef078040663c4902a5f08 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 @@ -29,6 +29,7 @@ def self.generate_desired_config(workspace:, include_all_resources:, logger:) network_policy_egress: Array => network_policy_egress, processed_devfile_yaml: String => processed_devfile_yaml, replicas: Integer => replicas, + scripts_configmap_name: scripts_configmap_name, secrets_inventory_annotations: Hash => secrets_inventory_annotations, secrets_inventory_name: String => secrets_inventory_name, use_kubernetes_user_namespaces: TrueClass | FalseClass => use_kubernetes_user_namespaces, @@ -108,6 +109,15 @@ def self.generate_desired_config(workspace:, include_all_resources:, logger:) annotations: workspace_inventory_annotations ) + append_scripts_resources( + desired_config: desired_config, + processed_devfile_yaml: processed_devfile_yaml, + name: scripts_configmap_name, + namespace: workspace.namespace, + labels: labels, + annotations: workspace_inventory_annotations + ) + return desired_config unless include_all_resources append_inventory_config_map( @@ -337,6 +347,73 @@ def self.append_network_policy( nil end + # @param [Array] desired_config + # @param [String] processed_devfile_yaml + # @param [String] name + # @param [String] namespace + # @param [Hash] labels + # @param [Hash] annotations + # @return [void] + def self.append_scripts_resources( + desired_config:, + processed_devfile_yaml:, + name:, + namespace:, + labels:, + annotations: + ) + desired_config => [ + *_, + { + kind: "Deployment", + spec: { + template: { + spec: { + containers: Array => containers, + volumes: Array => volumes + } + } + } + }, + *_ + ] + + processed_devfile = YAML.safe_load(processed_devfile_yaml).deep_symbolize_keys.to_h + + devfile_commands = processed_devfile.fetch(:commands) + devfile_events = processed_devfile.fetch(:events) + + # NOTE: This guard clause ensures we still support older running workspaces which were started before we + # added support for devfile postStart events. In that case, we don't want to add any resources + # related to the postStart script handling, because that would cause those existing workspaces + # to restart because the deployment would be updated. + return unless devfile_events[:postStart].present? + + ScriptsConfigmapAppender.append( + desired_config: desired_config, + name: name, + namespace: namespace, + labels: labels, + annotations: annotations, + devfile_commands: devfile_commands, + devfile_events: devfile_events + ) + + ScriptsVolumeInserter.insert( + configmap_name: name, + containers: containers, + volumes: volumes + ) + + KubernetesPoststartHookInserter.insert( + containers: containers, + devfile_commands: devfile_commands, + devfile_events: devfile_events + ) + + nil + end + # @param [Array] desired_config # @param [String] name # @param [String] namespace 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 ba7ce58657c7e30ca91d575f37791ba15d7ff9d7..e61dcc265829b33858052a2d3abe2ea7de32a920 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 @@ -216,7 +216,7 @@ def self.inject_secrets(workspace_resources:, env_secret_names:, file_secret_nam volume = { name: VARIABLES_VOLUME_NAME, projected: { - defaultMode: 0o774, + defaultMode: VARIABLES_VOLUME_DEFAULT_MODE, sources: file_secret_names.map { |name| { secret: { name: name } } } } } diff --git a/ee/lib/remote_development/workspace_operations/reconcile/output/kubernetes_poststart_hook_command.sh b/ee/lib/remote_development/workspace_operations/reconcile/output/kubernetes_poststart_hook_command.sh new file mode 100644 index 0000000000000000000000000000000000000000..0bc343174530222549f18501112fd3e54807de34 --- /dev/null +++ b/ee/lib/remote_development/workspace_operations/reconcile/output/kubernetes_poststart_hook_command.sh @@ -0,0 +1,3 @@ +mkdir -p "${GL_WORKSPACE_LOGS_DIR}" +ln -sf "${GL_WORKSPACE_LOGS_DIR}" /tmp +"%s" 1>>"${GL_WORKSPACE_LOGS_DIR}/poststart-stdout.log" 2>>"${GL_WORKSPACE_LOGS_DIR}/poststart-stderr.log" & diff --git a/ee/lib/remote_development/workspace_operations/reconcile/output/kubernetes_poststart_hook_inserter.rb b/ee/lib/remote_development/workspace_operations/reconcile/output/kubernetes_poststart_hook_inserter.rb new file mode 100644 index 0000000000000000000000000000000000000000..408f5fd59484db543f4692761a301865bd1c9e5c --- /dev/null +++ b/ee/lib/remote_development/workspace_operations/reconcile/output/kubernetes_poststart_hook_inserter.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +module RemoteDevelopment + module WorkspaceOperations + module Reconcile + module Output + # NOTE: This class has "Kubernetes" prepended to "Poststart" in the name to make it explicit that it + # deals with Kubernetes postStart hooks in the Kubernetes Deployment resource, and that + # it is NOT dealing with the postStart events which are found in devfiles. + class KubernetesPoststartHookInserter + include Files + include ReconcileConstants + + # @param [Array] containers + # @param [Array] devfile_commands + # @param [Hash] devfile_events + # @return [void] + def self.insert(containers:, devfile_commands:, devfile_events:) + devfile_events => { postStart: Array => poststart_command_ids } + + containers_with_devfile_poststart_commands = + poststart_command_ids.each_with_object([]) do |poststart_command_id, accumulator| + command = devfile_commands.find { |command| command.fetch(:id) == poststart_command_id } + command => { + exec: { + component: String => container_name + } + } + accumulator << container_name + end.uniq + + containers.each do |container| + container_name = container.fetch(:name) + + next unless containers_with_devfile_poststart_commands.include?(container_name) + + kubernetes_poststart_hook_script = + format( + KUBERNETES_POSTSTART_HOOK_COMMAND, + run_poststart_commands_script_file_path: + "#{WORKSPACE_SCRIPTS_VOLUME_PATH}/#{RUN_POSTSTART_COMMANDS_SCRIPT_NAME}" + ) + + container[:lifecycle] = { + postStart: { + exec: { + command: ["/bin/sh", "-c", kubernetes_poststart_hook_script] + } + } + } + end + + nil + end + end + end + end + end +end diff --git a/ee/lib/remote_development/workspace_operations/reconcile/output/scripts_configmap_appender.rb b/ee/lib/remote_development/workspace_operations/reconcile/output/scripts_configmap_appender.rb new file mode 100644 index 0000000000000000000000000000000000000000..717b60d1b711295a52868a59b1b0bddb954d4945 --- /dev/null +++ b/ee/lib/remote_development/workspace_operations/reconcile/output/scripts_configmap_appender.rb @@ -0,0 +1,109 @@ +# frozen_string_literal: true + +module RemoteDevelopment + module WorkspaceOperations + module Reconcile + module Output + class ScriptsConfigmapAppender + include ReconcileConstants + + # @param [Array] desired_config + # @param [String] name + # @param [String] namespace + # @param [Hash] labels + # @param [Hash] annotations + # @param [Array] devfile_commands + # @param [Hash] devfile_events + # @return [void] + def self.append(desired_config:, name:, namespace:, labels:, annotations:, devfile_commands:, devfile_events:) + configmap_data = {} + + configmap = + { + kind: "ConfigMap", + apiVersion: "v1", + metadata: { + name: name, + namespace: namespace, + labels: labels, + annotations: annotations + }, + data: configmap_data + } + + add_devfile_command_scripts_to_configmap_data( + configmap_data: configmap_data, + devfile_commands: devfile_commands, + devfile_events: devfile_events + ) + + add_run_poststart_commands_script_to_configmap_data( + configmap_data: configmap_data, + devfile_events: devfile_events + ) + + # noinspection RubyMismatchedArgumentType - RubyMine is misinterpreting types for Hash values + configmap[:data] = Gitlab::Utils.deep_sort_hashes(configmap_data).to_h + + desired_config.append(configmap) + + nil + end + + # @param [Hash] configmap_data + # @param [Array] devfile_commands + # @param [Hash] devfile_events + # @return [void] + def self.add_devfile_command_scripts_to_configmap_data(configmap_data:, devfile_commands:, devfile_events:) + devfile_events => { postStart: Array => poststart_command_ids } + + poststart_command_ids.each do |poststart_command_id| + command = devfile_commands.find { |command| command.fetch(:id) == poststart_command_id } + command => { + exec: { + commandLine: String => command_line + } + } + + configmap_data[poststart_command_id.to_sym] = command_line + end + + nil + end + + # @param [Hash] configmap_data + # @param [Array] devfile_commands + # @param [Hash] devfile_events + # @return [void] + def self.add_run_poststart_commands_script_to_configmap_data(configmap_data:, devfile_events:) + devfile_events => { postStart: Array => poststart_command_ids } + + script_command_lines = + poststart_command_ids.map do |poststart_command_id| + # NOTE: We force all the poststart scripts to exit successfully with `|| true`, to + # prevent the Kubernetes poststart hook from failing, and thus prevent the + # container from exitin. Then users can view logs to debug failures. + # See https://github.com/eclipse-che/che/issues/23404#issuecomment-2787779571 + # for more context. + <<~CMD + echo "$(date -Iseconds): Running #{WORKSPACE_SCRIPTS_VOLUME_PATH}/#{poststart_command_id}..." + #{WORKSPACE_SCRIPTS_VOLUME_PATH}/#{poststart_command_id} || true + CMD + end.join + + configmap_data[RUN_POSTSTART_COMMANDS_SCRIPT_NAME.to_sym] = + <<~SH.chomp + #!/bin/sh + #{script_command_lines} + SH + + nil + end + + private_class_method :add_devfile_command_scripts_to_configmap_data, + :add_run_poststart_commands_script_to_configmap_data + end + end + end + end +end diff --git a/ee/lib/remote_development/workspace_operations/reconcile/output/scripts_volume_inserter.rb b/ee/lib/remote_development/workspace_operations/reconcile/output/scripts_volume_inserter.rb new file mode 100644 index 0000000000000000000000000000000000000000..9bd2ca40a9a5cd4736b9500bb7adabff9704916b --- /dev/null +++ b/ee/lib/remote_development/workspace_operations/reconcile/output/scripts_volume_inserter.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +module RemoteDevelopment + module WorkspaceOperations + module Reconcile + module Output + class ScriptsVolumeInserter + include ReconcileConstants + + # @param [String] configmap_name + # @param [Array] containers + # @param [Array] volumes + # @return [void] + def self.insert(configmap_name:, containers:, volumes:) + volume = + { + name: WORKSPACE_SCRIPTS_VOLUME_NAME, + projected: { + defaultMode: WORKSPACE_SCRIPTS_VOLUME_DEFAULT_MODE, + sources: [ + { + configMap: { + name: configmap_name + } + } + ] + } + } + volume_mount = + { + name: WORKSPACE_SCRIPTS_VOLUME_NAME, + mountPath: WORKSPACE_SCRIPTS_VOLUME_PATH + } + + volumes << volume + containers.each do |container| + container.fetch(:volumeMounts) << volume_mount + end + + nil + end + end + end + end + end +end 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 d6e2393b792bf0be3682abe48be9982900a3c872..bd31907166842bb8d369e4e7ee6dc927a8ec3125 100644 --- a/ee/lib/remote_development/workspace_operations/reconcile/reconcile_constants.rb +++ b/ee/lib/remote_development/workspace_operations/reconcile/reconcile_constants.rb @@ -14,9 +14,10 @@ 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_VOLUME_PATH}/#{WORKSPACE_RECONCILED_ACTUAL_STATE_FILE_NAME}".freeze + RUN_POSTSTART_COMMANDS_SCRIPT_NAME = "gl-run-poststart-commands.sh" + WORKSPACE_SCRIPTS_VOLUME_DEFAULT_MODE = 0o774 + WORKSPACE_SCRIPTS_VOLUME_NAME = "gl-workspace-scripts" + WORKSPACE_SCRIPTS_VOLUME_PATH = "/workspace-scripts" end end end diff --git a/ee/lib/remote_development/workspace_operations/workspace_operations_constants.rb b/ee/lib/remote_development/workspace_operations/workspace_operations_constants.rb index 9795c7266d391ebeaedecfd1e5e1ce483270e6f0..e7ffb5eea5a5f7876fd5610832242f231609e8b7 100644 --- a/ee/lib/remote_development/workspace_operations/workspace_operations_constants.rb +++ b/ee/lib/remote_development/workspace_operations/workspace_operations_constants.rb @@ -17,9 +17,14 @@ module WorkspaceOperations # See documentation at ../README.md#constant-declarations for more information. module WorkspaceOperationsConstants # Please keep alphabetized + VARIABLES_VOLUME_DEFAULT_MODE = 0o774 VARIABLES_VOLUME_NAME = "gl-workspace-variables" VARIABLES_VOLUME_PATH = "/.workspace-data/variables/file" WORKSPACE_DATA_VOLUME_PATH = "/projects" + WORKSPACE_LOGS_DIR = "#{WORKSPACE_DATA_VOLUME_PATH}/workspace-logs".freeze + WORKSPACE_RECONCILED_ACTUAL_STATE_FILE_NAME = "gl_workspace_reconciled_actual_state.txt" + WORKSPACE_RECONCILED_ACTUAL_STATE_FILE_PATH = + "#{VARIABLES_VOLUME_PATH}/#{WORKSPACE_RECONCILED_ACTUAL_STATE_FILE_NAME}".freeze WORKSPACE_TOOLS_IMAGE = "registry.gitlab.com/gitlab-org/workspaces/gitlab-workspaces-tools:12.0.0" end end diff --git a/ee/spec/features/remote_development/open_in_workspace_spec.rb b/ee/spec/features/remote_development/open_in_workspace_spec.rb index 37deed1921a046e2489638a41147d572b6b4af5f..c9500c859c4d632aae28f88a70f48ec1e7497887 100644 --- a/ee/spec/features/remote_development/open_in_workspace_spec.rb +++ b/ee/spec/features/remote_development/open_in_workspace_spec.rb @@ -18,6 +18,7 @@ end it 'they should be able to click on Open in Workspace' do + # noinspection RubyArgCount -- Rubymine is incorrectly thinking this is an invalid block argument within '.merge-request' do click_button 'Code' end diff --git a/ee/spec/fixtures/remote_development/example.legacy-scripts-in-container-command-processed-devfile.yaml.erb b/ee/spec/fixtures/remote_development/example.legacy-scripts-in-container-command-processed-devfile.yaml.erb index f2a98e5bb58c65bb11bcd1979806ab385481724d..c0e3a8f5929404a7f0f3dc8dc87d67a0b41dee3d 100644 --- a/ee/spec/fixtures/remote_development/example.legacy-scripts-in-container-command-processed-devfile.yaml.erb +++ b/ee/spec/fixtures/remote_development/example.legacy-scripts-in-container-command-processed-devfile.yaml.erb @@ -9,7 +9,8 @@ components: image: quay.io/mloriedo/universal-developer-image:ubi8-dw-demo args: - | - <%= indent_yaml_literal(MAIN_COMPONENT_UPDATER_CONTAINER_ARGS, 10) %> + <%= indent_yaml_literal(MAIN_COMPONENT_UPDATER_START_SSHD_SCRIPT, 10) %> + <%= indent_yaml_literal(MAIN_COMPONENT_UPDATER_INIT_TOOLS_SCRIPT, 10) %> command: - "/bin/sh" - "-c" @@ -75,8 +76,8 @@ components: command: - "/bin/sh" - "-c" - memoryLimit: 512Mi - memoryRequest: 256Mi + memoryLimit: 1000Mi + memoryRequest: 500Mi cpuLimit: 500m cpuRequest: 100m - name: gl-workspace-data diff --git a/ee/spec/fixtures/remote_development/example.main-container-updated-devfile.yaml.erb b/ee/spec/fixtures/remote_development/example.main-container-updated-devfile.yaml.erb index d11ae389409b7b7f139f9adeb74893713086c651..1aae1f0a36e433d34502f70c39dc78b46111a534 100644 --- a/ee/spec/fixtures/remote_development/example.main-container-updated-devfile.yaml.erb +++ b/ee/spec/fixtures/remote_development/example.main-container-updated-devfile.yaml.erb @@ -58,8 +58,32 @@ commands: - id: gl-tools-injector-command apply: component: gl-tools-injector + - id: gl-start-sshd-command + exec: + commandLine: | + <%= indent_yaml_literal(MAIN_COMPONENT_UPDATER_START_SSHD_SCRIPT, 8) %> + component: tooling-container + - id: gl-init-tools-command + exec: + commandLine: | + <%= indent_yaml_literal(MAIN_COMPONENT_UPDATER_INIT_TOOLS_SCRIPT, 8) %> + component: tooling-container + - id: gl-sleep-until-container-is-running-command + exec: + commandLine: | + <%= + script = format( + MAIN_COMPONENT_UPDATER_SLEEP_UNTIL_CONTAINER_IS_RUNNING_SCRIPT, + workspace_reconciled_actual_state_file_path: WORKSPACE_RECONCILED_ACTUAL_STATE_FILE_PATH + ) + indent_yaml_literal(script, 8) + %> + component: tooling-container events: preStart: - gl-tools-injector-command - postStart: [] + postStart: + - gl-start-sshd-command + - gl-init-tools-command + - gl-sleep-until-container-is-running-command variables: {} diff --git a/ee/spec/fixtures/remote_development/example.main-container-updated-marketplace-disabled-devfile.yaml.erb b/ee/spec/fixtures/remote_development/example.main-container-updated-marketplace-disabled-devfile.yaml.erb index d11ae389409b7b7f139f9adeb74893713086c651..1aae1f0a36e433d34502f70c39dc78b46111a534 100644 --- a/ee/spec/fixtures/remote_development/example.main-container-updated-marketplace-disabled-devfile.yaml.erb +++ b/ee/spec/fixtures/remote_development/example.main-container-updated-marketplace-disabled-devfile.yaml.erb @@ -58,8 +58,32 @@ commands: - id: gl-tools-injector-command apply: component: gl-tools-injector + - id: gl-start-sshd-command + exec: + commandLine: | + <%= indent_yaml_literal(MAIN_COMPONENT_UPDATER_START_SSHD_SCRIPT, 8) %> + component: tooling-container + - id: gl-init-tools-command + exec: + commandLine: | + <%= indent_yaml_literal(MAIN_COMPONENT_UPDATER_INIT_TOOLS_SCRIPT, 8) %> + component: tooling-container + - id: gl-sleep-until-container-is-running-command + exec: + commandLine: | + <%= + script = format( + MAIN_COMPONENT_UPDATER_SLEEP_UNTIL_CONTAINER_IS_RUNNING_SCRIPT, + workspace_reconciled_actual_state_file_path: WORKSPACE_RECONCILED_ACTUAL_STATE_FILE_PATH + ) + indent_yaml_literal(script, 8) + %> + component: tooling-container events: preStart: - gl-tools-injector-command - postStart: [] + postStart: + - gl-start-sshd-command + - gl-init-tools-command + - gl-sleep-until-container-is-running-command variables: {} diff --git a/ee/spec/fixtures/remote_development/example.processed-devfile.yaml.erb b/ee/spec/fixtures/remote_development/example.processed-devfile.yaml.erb index 5678b7927f07eefd890fcb17c6352d7ca43f1500..eb9a0482cb358f2a4a7a1545b09ba616bdb16b74 100644 --- a/ee/spec/fixtures/remote_development/example.processed-devfile.yaml.erb +++ b/ee/spec/fixtures/remote_development/example.processed-devfile.yaml.erb @@ -82,16 +82,40 @@ components: - name: gl-workspace-data volume: size: 50Gi -events: - preStart: - - gl-tools-injector-command - - gl-project-cloner-command - postStart: [] commands: - id: gl-tools-injector-command apply: component: gl-tools-injector + - id: gl-start-sshd-command + exec: + commandLine: | + <%= indent_yaml_literal(MAIN_COMPONENT_UPDATER_START_SSHD_SCRIPT, 8) %> + component: tooling-container + - id: gl-init-tools-command + exec: + commandLine: | + <%= indent_yaml_literal(MAIN_COMPONENT_UPDATER_INIT_TOOLS_SCRIPT, 8) %> + component: tooling-container + - id: gl-sleep-until-container-is-running-command + exec: + commandLine: | + <%= + script = format( + MAIN_COMPONENT_UPDATER_SLEEP_UNTIL_CONTAINER_IS_RUNNING_SCRIPT, + workspace_reconciled_actual_state_file_path: WORKSPACE_RECONCILED_ACTUAL_STATE_FILE_PATH + ) + indent_yaml_literal(script, 8) + %> + component: tooling-container - id: gl-project-cloner-command apply: component: gl-project-cloner +events: + preStart: + - gl-tools-injector-command + - gl-project-cloner-command + postStart: + - gl-start-sshd-command + - gl-init-tools-command + - gl-sleep-until-container-is-running-command variables: {} diff --git a/ee/spec/fixtures/remote_development/example.project-cloner-inserted-devfile.yaml.erb b/ee/spec/fixtures/remote_development/example.project-cloner-inserted-devfile.yaml.erb index 2afdda81f39d5fe3886111aa995273ef13bdc284..3bbfc3d01c79268283d0d54644485798fe0194b0 100644 --- a/ee/spec/fixtures/remote_development/example.project-cloner-inserted-devfile.yaml.erb +++ b/ee/spec/fixtures/remote_development/example.project-cloner-inserted-devfile.yaml.erb @@ -67,16 +67,40 @@ components: memoryRequest: 500Mi cpuLimit: 500m cpuRequest: 100m -events: - preStart: - - gl-tools-injector-command - - gl-project-cloner-command - postStart: [] commands: - id: gl-tools-injector-command apply: component: gl-tools-injector + - id: gl-start-sshd-command + exec: + commandLine: | + <%= indent_yaml_literal(MAIN_COMPONENT_UPDATER_START_SSHD_SCRIPT, 8) %> + component: tooling-container + - id: gl-init-tools-command + exec: + commandLine: | + <%= indent_yaml_literal(MAIN_COMPONENT_UPDATER_INIT_TOOLS_SCRIPT, 8) %> + component: tooling-container + - id: gl-sleep-until-container-is-running-command + exec: + commandLine: | + <%= + script = format( + MAIN_COMPONENT_UPDATER_SLEEP_UNTIL_CONTAINER_IS_RUNNING_SCRIPT, + workspace_reconciled_actual_state_file_path: WORKSPACE_RECONCILED_ACTUAL_STATE_FILE_PATH + ) + indent_yaml_literal(script, 8) + %> + component: tooling-container - id: gl-project-cloner-command apply: component: gl-project-cloner +events: + preStart: + - gl-tools-injector-command + - gl-project-cloner-command + postStart: + - gl-start-sshd-command + - gl-init-tools-command + - gl-sleep-until-container-is-running-command variables: {} diff --git a/ee/spec/lib/remote_development/workspace_operations/create/main_component_updater_spec.rb b/ee/spec/lib/remote_development/workspace_operations/create/main_component_updater_spec.rb index 8f57157318a45bfb20db78845cc66ad99ba60d7a..0c955d53f5dba279ad26bca3c82e3a3141440804 100644 --- a/ee/spec/lib/remote_development/workspace_operations/create/main_component_updater_spec.rb +++ b/ee/spec/lib/remote_development/workspace_operations/create/main_component_updater_spec.rb @@ -9,7 +9,7 @@ read_devfile("example.tools-injector-inserted-devfile.yaml.erb") end - let(:expected_processed_devfile_name) { 'example.main-container-updated-devfile.yaml.erb' } + let(:expected_processed_devfile_name) { "example.main-container-updated-devfile.yaml.erb" } let(:expected_processed_devfile) { read_devfile(expected_processed_devfile_name) } let(:vscode_extension_marketplace_metadata_enabled) { false } 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 316368a8833aff12f345acd553307e0a3bed7576..9e164169170498491336a4bd3990721b1193bf0b 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 @@ -17,6 +17,12 @@ let(:git_credential_store_script) { RemoteDevelopment::Files::GIT_CREDENTIAL_STORE_SCRIPT } let(:expected_variables) do [ + { + key: "GL_WORKSPACE_LOGS_DIR", + value: RemoteDevelopment::WorkspaceOperations::WorkspaceOperationsConstants::WORKSPACE_LOGS_DIR, + variable_type: RemoteDevelopment::Enums::WorkspaceVariable::ENVIRONMENT_TYPE, + workspace_id: workspace_id + }, { key: create_constants_module::TOKEN_FILE_NAME, value: "example-pat-value", diff --git a/ee/spec/lib/remote_development/workspace_operations/create/workspace_variables_creator_spec.rb b/ee/spec/lib/remote_development/workspace_operations/create/workspace_variables_creator_spec.rb index 2bfddb68f2b91fb31795c4a3c093619efd1fe160..0035a98dc14cfab13e538ab843a91f5b8594fd33 100644 --- a/ee/spec/lib/remote_development/workspace_operations/create/workspace_variables_creator_spec.rb +++ b/ee/spec/lib/remote_development/workspace_operations/create/workspace_variables_creator_spec.rb @@ -48,7 +48,7 @@ context "when workspace variables create is successful" do let(:valid_variable_type) { RemoteDevelopment::Enums::WorkspaceVariable::ENVIRONMENT_TYPE } let(:variable_type) { valid_variable_type } - let(:expected_number_of_records_saved) { 18 } + let(:expected_number_of_records_saved) { 19 } it "creates the workspace variable records and returns ok result containing original context" do expect { result }.to change { workspace.workspace_variables.count }.by(expected_number_of_records_saved) @@ -63,10 +63,10 @@ context "when workspace create fails" do let(:invalid_variable_type) { 9999999 } let(:variable_type) { invalid_variable_type } - let(:expected_number_of_records_saved) { 16 } + let(:expected_number_of_records_saved) { 17 } it "does not create the invalid workspace variable records and returns an error result with model errors" do - # NOTE: Any valid records will be saved if they are first in the array before the invalid record, but that"s OK, + # NOTE: Any valid records will be saved if they are first in the array before the invalid record, but that's OK, # because if we return an err_result, the entire transaction will be rolled back at a higher level. expect { result }.to change { workspace.workspace_variables.count }.by(expected_number_of_records_saved) diff --git a/ee/spec/lib/remote_development/workspace_operations/reconcile/output/config_values_extractor_spec.rb b/ee/spec/lib/remote_development/workspace_operations/reconcile/output/config_values_extractor_spec.rb index a820517ba736b30b5ac277ff6c62db2d3144c84e..db35f2f478f0daafe7a6fbda5b0b4125393cddd4 100644 --- a/ee/spec/lib/remote_development/workspace_operations/reconcile/output/config_values_extractor_spec.rb +++ b/ee/spec/lib/remote_development/workspace_operations/reconcile/output/config_values_extractor_spec.rb @@ -114,6 +114,7 @@ network_policy_egress processed_devfile_yaml replicas + scripts_configmap_name secrets_inventory_annotations secrets_inventory_name use_kubernetes_user_namespaces @@ -163,6 +164,8 @@ expect(extracted_values[:processed_devfile_yaml]).to eq(workspace.processed_devfile) + expect(extracted_values[:scripts_configmap_name]).to eq("#{workspace.name}-scripts-configmap") + expect(extracted_values[:secrets_inventory_annotations]).to eq( { "config.k8s.io/owning-inventory": "#{workspace.name}-secrets-inventory", 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 00ef6b2eb51af5a8c4d926b6e92e384721b1d2a3..880ea53cf7e3aea8a81a1afc6df5da80e2fdd4c0 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 @@ -279,8 +279,8 @@ def input_processed_devfile_yaml_without_poststart_event command: - "/bin/sh" - "-c" - memoryLimit: 512Mi - memoryRequest: 256Mi + memoryLimit: 1000Mi + memoryRequest: 500Mi cpuLimit: 500m cpuRequest: 100m - name: gl-workspace-data @@ -489,8 +489,23 @@ def golden_master_desired_config_with_include_all_resources_true { mountPath: "/.workspace-data/variables/file", name: "gl-workspace-variables" + }, + { + name: "gl-workspace-scripts", + mountPath: "/workspace-scripts" } - ] + ], + lifecycle: { + postStart: { + exec: { + command: [ + "/bin/sh", + "-c", + "mkdir -p \"${GL_WORKSPACE_LOGS_DIR}\"\nln -sf \"${GL_WORKSPACE_LOGS_DIR}\" /tmp\n\"/workspace-scripts/gl-run-poststart-commands.sh\" 1>>\"${GL_WORKSPACE_LOGS_DIR}/poststart-stdout.log\" 2>>\"${GL_WORKSPACE_LOGS_DIR}/poststart-stderr.log\" &\n" + ] + } + } + } } ], initContainers: [ @@ -568,7 +583,7 @@ def golden_master_desired_config_with_include_all_resources_true { name: "gl-workspace-variables", projected: { - defaultMode: 508, + defaultMode: 0o774, sources: [ { secret: { @@ -577,6 +592,19 @@ def golden_master_desired_config_with_include_all_resources_true } ] } + }, + { + name: "gl-workspace-scripts", + projected: { + defaultMode: 0o774, + sources: [ + { + configMap: { + name: "workspace-991-990-fedcba-scripts-configmap" + } + } + ] + } } ] } @@ -766,6 +794,31 @@ def golden_master_desired_config_with_include_all_resources_true ] } }, + { + apiVersion: "v1", + 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": "24aefc317e11db538ede450d1773e273966b9801b988d49e1219f2a9bf8e7f66" + }, + labels: { + app: "workspace", + tier: "development", + "agent.gitlab.com/id": "991" + }, + name: "workspace-991-990-fedcba-scripts-configmap", + namespace: "gl-rd-ns-991-990-fedcba" + }, + data: { + "gl-run-poststart-commands.sh": "#!/bin/sh\necho \"$(date -Iseconds): Running /workspace-scripts/gl-example-tooling-container-internal-command...\"\n/workspace-scripts/gl-example-tooling-container-internal-command || true\n", + "gl-example-tooling-container-internal-command": "echo 'example tooling container internal command'" + } + }, { apiVersion: "v1", kind: "ConfigMap", @@ -1010,8 +1063,23 @@ def golden_master_desired_config_with_include_all_resources_false { mountPath: "/.workspace-data/variables/file", name: "gl-workspace-variables" + }, + { + name: "gl-workspace-scripts", + mountPath: "/workspace-scripts" } - ] + ], + lifecycle: { + postStart: { + exec: { + command: [ + "/bin/sh", + "-c", + "mkdir -p \"${GL_WORKSPACE_LOGS_DIR}\"\nln -sf \"${GL_WORKSPACE_LOGS_DIR}\" /tmp\n\"/workspace-scripts/gl-run-poststart-commands.sh\" 1>>\"${GL_WORKSPACE_LOGS_DIR}/poststart-stdout.log\" 2>>\"${GL_WORKSPACE_LOGS_DIR}/poststart-stderr.log\" &\n" + ] + } + } + } } ], initContainers: [ @@ -1089,7 +1157,7 @@ def golden_master_desired_config_with_include_all_resources_false { name: "gl-workspace-variables", projected: { - defaultMode: 508, + defaultMode: 0o774, sources: [ { secret: { @@ -1098,6 +1166,19 @@ def golden_master_desired_config_with_include_all_resources_false } ] } + }, + { + name: "gl-workspace-scripts", + projected: { + defaultMode: 0o774, + sources: [ + { + configMap: { + name: "workspace-991-990-fedcba-scripts-configmap" + } + } + ] + } } ] } @@ -1286,6 +1367,31 @@ def golden_master_desired_config_with_include_all_resources_false "Egress" ] } + }, + { + apiVersion: "v1", + 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": "24aefc317e11db538ede450d1773e273966b9801b988d49e1219f2a9bf8e7f66" + }, + labels: { + app: "workspace", + tier: "development", + "agent.gitlab.com/id": "991" + }, + name: "workspace-991-990-fedcba-scripts-configmap", + namespace: "gl-rd-ns-991-990-fedcba" + }, + data: { + "gl-run-poststart-commands.sh": "#!/bin/sh\necho \"$(date -Iseconds): Running /workspace-scripts/gl-example-tooling-container-internal-command...\"\n/workspace-scripts/gl-example-tooling-container-internal-command || true\n", + "gl-example-tooling-container-internal-command": "echo 'example tooling container internal command'" + } } ] end @@ -1468,11 +1574,11 @@ def golden_master_desired_config_from_legacy_devfile_with_no_poststart_and_with_ resources: { limits: { cpu: "500m", - memory: "512Mi" + memory: "1000Mi" }, requests: { cpu: "100m", - memory: "256Mi" + memory: "500Mi" } }, securityContext: { @@ -1511,7 +1617,7 @@ def golden_master_desired_config_from_legacy_devfile_with_no_poststart_and_with_ { name: "gl-workspace-variables", projected: { - defaultMode: 508, + defaultMode: 0o774, sources: [ { secret: { 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 1b3c1d0cb7c4538176c0f69355491694c3ffb3f9..6268f8b8b4e024b7a2368cb4222a108b0882816c 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 @@ -15,12 +15,15 @@ let(:started) { true } let(:desired_state_is_terminated) { false } let(:include_all_resources) { false } + let(:include_scripts_resources) { true } + let(:legacy_scripts_in_container_command) { false } let(:deployment_resource_version_from_agent) { workspace.deployment_resource_version } let(:network_policy_enabled) { true } let(:gitlab_workspaces_proxy_namespace) { 'gitlab-workspaces' } let(:max_resources_per_workspace) { {} } let(:default_resources_per_workspace_container) { {} } let(:image_pull_secrets) { [] } + let(:processed_devfile_yaml) { example_processed_devfile_yaml } let(:workspaces_agent_config) do config = create( :workspaces_agent_config, @@ -41,7 +44,8 @@ agent: agent, user: user, desired_state: desired_state, - actual_state: actual_state + actual_state: actual_state, + processed_devfile: processed_devfile_yaml ) end @@ -52,6 +56,8 @@ desired_state_is_terminated: desired_state_is_terminated, include_network_policy: workspace.workspaces_agent_config.network_policy_enabled, include_all_resources: include_all_resources, + include_scripts_resources: include_scripts_resources, + legacy_scripts_in_container_command: legacy_scripts_in_container_command, egress_ip_rules: workspace.workspaces_agent_config.network_policy_egress.map(&:deep_symbolize_keys), max_resources_per_workspace: max_resources_per_workspace, default_resources_per_workspace_container: default_resources_per_workspace_container, @@ -77,6 +83,14 @@ expect(workspace_resources).to eq(expected_config) end + it "includes the workspace name in all resource names" do + resources_without_workspace_name_in_name = workspace_resources.reject do |resource| + resource[:metadata][:name].include?(workspace.name) + end + + expect(resources_without_workspace_name_in_name).to be_empty + end + context 'when desired_state terminated' do let(:desired_state_is_terminated) { true } let(:desired_state) { states_module::TERMINATED } @@ -201,6 +215,18 @@ expect(resource_quota).not_to be_nil end end + + context "when postStart events are not present in devfile" do + let(:include_scripts_resources) { false } + let(:legacy_scripts_in_container_command) { true } + let(:processed_devfile_yaml) do + read_devfile_yaml("example.legacy-scripts-in-container-command-processed-devfile.yaml.erb") + end + + it 'returns expected config without script resources' do + expect(workspace_resources).to eq(expected_config) + end + end end context 'when DevfileParser returns empty array' do 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 11f37197bf5aaa6bf92cdf34e11277e98b306a95..adceb0e4a00c36208f73cdb56e7f6f9ed0b3a9dd 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 @@ -113,6 +113,7 @@ agent_labels: agent_labels, agent_annotations: agent_annotations, image_pull_secrets: image_pull_secrets, + include_scripts_resources: false, core_resources_only: true ) end diff --git a/ee/spec/lib/remote_development/workspace_operations/reconcile/output/kubernetes_poststart_hook_inserter_spec.rb b/ee/spec/lib/remote_development/workspace_operations/reconcile/output/kubernetes_poststart_hook_inserter_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..c562a5a17c82d1bb0cbc6642358a1a2b59e44211 --- /dev/null +++ b/ee/spec/lib/remote_development/workspace_operations/reconcile/output/kubernetes_poststart_hook_inserter_spec.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +require "fast_spec_helper" + +RSpec.describe RemoteDevelopment::WorkspaceOperations::Reconcile::Output::KubernetesPoststartHookInserter, feature_category: :workspaces do + include_context 'with remote development shared fixtures' + + let(:processed_devfile) { example_processed_devfile } + let(:devfile_commands) { processed_devfile.fetch(:commands) } + let(:devfile_events) { processed_devfile.fetch(:events) } + let(:input_containers) do + deployment = create_deployment(include_scripts_resources: false) + deployment => { + spec: { + template: { + spec: { + containers: Array => containers + } + } + } + } + containers + end + + let(:expected_containers) do + deployment = create_deployment(include_scripts_resources: true) + deployment => { + spec: { + template: { + spec: { + containers: Array => containers + } + } + } + } + containers + end + + subject(:invoke_insert) do + described_class.insert( + # pass input containers without resources for scripts added, then assert they get added by the described_class + containers: input_containers, + devfile_commands: devfile_commands, + devfile_events: devfile_events + ) + end + + it "has valid fixtures with no lifecycle in any input_containers" do + expect(input_containers.any? { |c| c[:lifecycle] }).to be false + end + + it "inserts postStart lifecycle hooks", :unlimited_max_formatted_output_length do + invoke_insert + + expected_containers => [ + *_, + { + lifecycle: Hash => first_container_expected_lifecycle_hooks + }, + *_ + ] + + input_containers => [ + *_, + { + lifecycle: Hash => first_container_updated_lifecycle_hooks + }, + *_ + ] + + expect(first_container_updated_lifecycle_hooks).to eq(first_container_expected_lifecycle_hooks) + end + + private + + # @param [Boolean] include_scripts_resources + # @return [Hash] + def create_deployment(include_scripts_resources:) + workspace_deployment( + workspace_name: "name", + workspace_namespace: "namespace", + include_scripts_resources: include_scripts_resources + ) + end +end diff --git a/ee/spec/lib/remote_development/workspace_operations/reconcile/output/scripts_configmap_appender_spec.rb b/ee/spec/lib/remote_development/workspace_operations/reconcile/output/scripts_configmap_appender_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..0a41e31a76402f096e79efec35c0cdfb23a715f8 --- /dev/null +++ b/ee/spec/lib/remote_development/workspace_operations/reconcile/output/scripts_configmap_appender_spec.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +require "fast_spec_helper" + +RSpec.describe RemoteDevelopment::WorkspaceOperations::Reconcile::Output::ScriptsConfigmapAppender, feature_category: :workspaces do + include_context 'with remote development shared fixtures' + + let(:files) { RemoteDevelopment::Files } + let(:annotations) { { a: "1" } } + let(:labels) { { b: "2" } } + let(:name) { "workspacename-scripts-configmap" } + let(:namespace) { "namespace" } + let(:processed_devfile) { example_processed_devfile } + let(:devfile_commands) { processed_devfile.fetch(:commands) } + let(:devfile_events) { processed_devfile.fetch(:events) } + let(:expected_postart_commands_script) do + <<~SCRIPT + #!/bin/sh + echo "$(date -Iseconds): Running #{reconcile_constants_module::WORKSPACE_SCRIPTS_VOLUME_PATH}/gl-start-sshd-command..." + #{reconcile_constants_module::WORKSPACE_SCRIPTS_VOLUME_PATH}/gl-start-sshd-command || true + echo "$(date -Iseconds): Running #{reconcile_constants_module::WORKSPACE_SCRIPTS_VOLUME_PATH}/gl-init-tools-command..." + #{reconcile_constants_module::WORKSPACE_SCRIPTS_VOLUME_PATH}/gl-init-tools-command || true + echo "$(date -Iseconds): Running #{reconcile_constants_module::WORKSPACE_SCRIPTS_VOLUME_PATH}/gl-sleep-until-container-is-running-command..." + #{reconcile_constants_module::WORKSPACE_SCRIPTS_VOLUME_PATH}/gl-sleep-until-container-is-running-command || true + SCRIPT + end + + let(:main_component_updater_sleep_until_container_is_running_script) do + format( + files::MAIN_COMPONENT_UPDATER_SLEEP_UNTIL_CONTAINER_IS_RUNNING_SCRIPT, + workspace_reconciled_actual_state_file_path: + workspace_operations_constants_module::WORKSPACE_RECONCILED_ACTUAL_STATE_FILE_PATH + ) + end + + subject(:updated_desired_config) do + # Make a fake desired config with one existing fake element, to prove we are appending + desired_config = [ + {} + ] + + described_class.append( + desired_config: desired_config, + name: name, + namespace: namespace, + labels: labels, + annotations: annotations, + devfile_commands: devfile_commands, + devfile_events: devfile_events + ) + + desired_config + end + + it "appends ConfigMap to desired_config" do + expect(updated_desired_config.length).to eq(2) + + updated_desired_config => [ + {}, # existing fake element + { + apiVersion: api_version, + metadata: { + name: configmap_name + }, + data: data + }, + ] + + expect(api_version).to eq("v1") + expect(configmap_name).to eq(name) + expect(data).to eq( + "gl-init-tools-command": files::MAIN_COMPONENT_UPDATER_INIT_TOOLS_SCRIPT, + reconcile_constants_module::RUN_POSTSTART_COMMANDS_SCRIPT_NAME.to_sym => + expected_postart_commands_script, + "gl-sleep-until-container-is-running-command": + main_component_updater_sleep_until_container_is_running_script, + "gl-start-sshd-command": files::MAIN_COMPONENT_UPDATER_START_SSHD_SCRIPT + ) + end +end diff --git a/ee/spec/lib/remote_development/workspace_operations/reconcile/output/scripts_volume_inserter_spec.rb b/ee/spec/lib/remote_development/workspace_operations/reconcile/output/scripts_volume_inserter_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..0ffb71392f0cdd68e7bb138eaada78858a36852c --- /dev/null +++ b/ee/spec/lib/remote_development/workspace_operations/reconcile/output/scripts_volume_inserter_spec.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +require "fast_spec_helper" + +RSpec.describe RemoteDevelopment::WorkspaceOperations::Reconcile::Output::ScriptsVolumeInserter, feature_category: :workspaces do + include_context 'with remote development shared fixtures' + + let(:name) { "workspacename-scripts-configmap" } + let(:processed_devfile) { example_processed_devfile } + let(:input_containers) do + [ + { volumeMounts: [{}] }, + { volumeMounts: [{}] } + ] + end + + let(:input_volumes) { [{}] } + + subject(:invoke_insert) do + described_class.insert( + configmap_name: name, + containers: input_containers, + volumes: input_volumes + ) + end + + it "inserts volume" do + invoke_insert + + expect(input_volumes.length).to eq(2) + + input_volumes => [ + {}, # existing fake element + { + name: volume_name, + projected: { + defaultMode: mode, + sources: [ + { + configMap: { + name: configmap_name + } + } + ] + } + } + ] + + expect(volume_name) + .to eq(reconcile_constants_module::WORKSPACE_SCRIPTS_VOLUME_NAME) + expect(mode).to eq(reconcile_constants_module::WORKSPACE_SCRIPTS_VOLUME_DEFAULT_MODE) + expect(configmap_name).to eq(name) + end + + it "inserts volumeMounts" do + invoke_insert + + expect(input_containers.all? { |c| c[:volumeMounts].length == 2 }).to be true + + input_containers => [ + { + volumeMounts: [ + {}, # existing fake element + inserted_mount_1 + ] + }, + { + volumeMounts: [ + {}, # existing fake element + inserted_mount_2 + ] + } + ] + + expected_mount = { + name: reconcile_constants_module::WORKSPACE_SCRIPTS_VOLUME_NAME, + mountPath: reconcile_constants_module::WORKSPACE_SCRIPTS_VOLUME_PATH + } + + expect(inserted_mount_1).to eq(expected_mount) + expect(inserted_mount_2).to eq(expected_mount) + end +end diff --git a/ee/spec/requests/remote_development/integration_spec.rb b/ee/spec/requests/remote_development/integration_spec.rb index eede75954da165e779f9f1fe0f27c342f5adc385..562e6d5ba4fc579a95d67d57a281922863ea211d 100644 --- a/ee/spec/requests/remote_development/integration_spec.rb +++ b/ee/spec/requests/remote_development/integration_spec.rb @@ -129,6 +129,7 @@ def expected_internal_variables(random_string:) # rubocop:disable Layout/LineLength -- keep them on one line for easier readability and editability [ + { key: "GL_WORKSPACE_LOGS_DIR", type: :environment, value: workspace_operations_constants_module::WORKSPACE_LOGS_DIR }, { key: create_constants_module::TOKEN_FILE_NAME, type: :file, value: /glpat-.+/ }, { 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 }, @@ -237,6 +238,7 @@ def do_create_org_mapping organization_id: common_organization.to_global_id.to_s, cluster_agent_id: agent.to_global_id.to_s } + # noinspection RubyMismatchedArgumentType -- RubyMine is not detecting that FactoryBot returns a User type do_graphql_mutation_post( name: :organization_create_cluster_agent_mapping, input: organization_create_cluster_agent_mapping_create_mutation_args, 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 1d1a92fa166c1346dc909ef3eb49103dbd019fdd..2552049afb335c906eef9544ec1cb1ad62d1c340 100644 --- a/ee/spec/support/helpers/remote_development/integration_spec_helpers.rb +++ b/ee/spec/support/helpers/remote_development/integration_spec_helpers.rb @@ -163,7 +163,7 @@ def simulate_first_poll( workspace: workspace, include_all_resources: true, workspace_variables_additional_data: { - "#{reconcile_constants_module::WORKSPACE_RECONCILED_ACTUAL_STATE_FILE_NAME}": actual_state + "#{workspace_operations_constants_module::WORKSPACE_RECONCILED_ACTUAL_STATE_FILE_NAME}": actual_state }, **additional_args_for_create_config_to_apply_yaml_stream ) @@ -205,7 +205,7 @@ def simulate_second_poll( started: true, include_all_resources: true, workspace_variables_additional_data: { - "#{reconcile_constants_module::WORKSPACE_RECONCILED_ACTUAL_STATE_FILE_NAME}": actual_state + "#{workspace_operations_constants_module::WORKSPACE_RECONCILED_ACTUAL_STATE_FILE_NAME}": actual_state }, **additional_args_for_create_config_to_apply_yaml_stream ) @@ -250,7 +250,7 @@ def simulate_third_poll( workspace: workspace, started: false, workspace_variables_additional_data: { - "#{reconcile_constants_module::WORKSPACE_RECONCILED_ACTUAL_STATE_FILE_NAME}": actual_state + "#{workspace_operations_constants_module::WORKSPACE_RECONCILED_ACTUAL_STATE_FILE_NAME}": actual_state }, **additional_args_for_create_config_to_apply_yaml_stream ) @@ -296,7 +296,7 @@ def simulate_fourth_poll( started: false, include_all_resources: true, workspace_variables_additional_data: { - "#{reconcile_constants_module::WORKSPACE_RECONCILED_ACTUAL_STATE_FILE_NAME}": actual_state + "#{workspace_operations_constants_module::WORKSPACE_RECONCILED_ACTUAL_STATE_FILE_NAME}": actual_state }, **additional_args_for_create_config_to_apply_yaml_stream ) @@ -342,7 +342,7 @@ def simulate_fifth_poll( started: false, include_all_resources: true, workspace_variables_additional_data: { - "#{reconcile_constants_module::WORKSPACE_RECONCILED_ACTUAL_STATE_FILE_NAME}": actual_state + "#{workspace_operations_constants_module::WORKSPACE_RECONCILED_ACTUAL_STATE_FILE_NAME}": actual_state }, **additional_args_for_create_config_to_apply_yaml_stream ) @@ -409,7 +409,7 @@ def simulate_seventh_poll( started: false, include_all_resources: true, workspace_variables_additional_data: { - "#{reconcile_constants_module::WORKSPACE_RECONCILED_ACTUAL_STATE_FILE_NAME}": actual_state + "#{workspace_operations_constants_module::WORKSPACE_RECONCILED_ACTUAL_STATE_FILE_NAME}": actual_state }, **additional_args_for_create_config_to_apply_yaml_stream ) @@ -454,7 +454,7 @@ def simulate_eighth_poll( workspace: workspace, started: true, workspace_variables_additional_data: { - "#{reconcile_constants_module::WORKSPACE_RECONCILED_ACTUAL_STATE_FILE_NAME}": actual_state + "#{workspace_operations_constants_module::WORKSPACE_RECONCILED_ACTUAL_STATE_FILE_NAME}": actual_state }, **additional_args_for_create_config_to_apply_yaml_stream ) @@ -505,7 +505,7 @@ def simulate_ninth_poll( started: true, include_all_resources: true, workspace_variables_additional_data: { - "#{reconcile_constants_module::WORKSPACE_RECONCILED_ACTUAL_STATE_FILE_NAME}": actual_state + "#{workspace_operations_constants_module::WORKSPACE_RECONCILED_ACTUAL_STATE_FILE_NAME}": actual_state }, **additional_args_for_create_config_to_apply_yaml_stream ) @@ -551,7 +551,7 @@ def simulate_tenth_poll( workspace: workspace, started: false, workspace_variables_additional_data: { - "#{reconcile_constants_module::WORKSPACE_RECONCILED_ACTUAL_STATE_FILE_NAME}": actual_state + "#{workspace_operations_constants_module::WORKSPACE_RECONCILED_ACTUAL_STATE_FILE_NAME}": actual_state }, **additional_args_for_create_config_to_apply_yaml_stream ) @@ -597,7 +597,7 @@ def simulate_eleventh_poll( started: false, include_all_resources: true, workspace_variables_additional_data: { - "#{reconcile_constants_module::WORKSPACE_RECONCILED_ACTUAL_STATE_FILE_NAME}": actual_state + "#{workspace_operations_constants_module::WORKSPACE_RECONCILED_ACTUAL_STATE_FILE_NAME}": actual_state }, **additional_args_for_create_config_to_apply_yaml_stream ) @@ -645,7 +645,7 @@ def simulate_twelfth_poll( started: false, include_all_resources: true, workspace_variables_additional_data: { - "#{reconcile_constants_module::WORKSPACE_RECONCILED_ACTUAL_STATE_FILE_NAME}": actual_state + "#{workspace_operations_constants_module::WORKSPACE_RECONCILED_ACTUAL_STATE_FILE_NAME}": actual_state }, **additional_args_for_create_config_to_apply_yaml_stream ) @@ -692,7 +692,7 @@ def simulate_thirteenth_poll( started: false, desired_state_is_terminated: true, workspace_variables_additional_data: { - "#{reconcile_constants_module::WORKSPACE_RECONCILED_ACTUAL_STATE_FILE_NAME}": actual_state + "#{workspace_operations_constants_module::WORKSPACE_RECONCILED_ACTUAL_STATE_FILE_NAME}": actual_state }, **additional_args_for_create_config_to_apply_yaml_stream ) @@ -738,7 +738,7 @@ def simulate_fourteenth_poll( desired_state_is_terminated: true, include_all_resources: true, workspace_variables_additional_data: { - "#{reconcile_constants_module::WORKSPACE_RECONCILED_ACTUAL_STATE_FILE_NAME}": actual_state + "#{workspace_operations_constants_module::WORKSPACE_RECONCILED_ACTUAL_STATE_FILE_NAME}": actual_state }, **additional_args_for_create_config_to_apply_yaml_stream ) 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 f5173e21a5967e01cd92bd2d34772f380fc70650..8295e28be85fa4eedb249f1e7e514853f8568372 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 @@ -330,7 +330,7 @@ def create_config_to_apply(workspace:, **args) # rubocop:enable GitlabSecurity/PublicSend end - # rubocop:disable Metrics/ParameterLists, Metrics/AbcSize -- Cleanup as part of https://gitlab.com/gitlab-org/gitlab/-/issues/421687 + # rubocop:disable Metrics/ParameterLists, Metrics/AbcSize, Metrics/PerceivedComplexity -- Cleanup as part of https://gitlab.com/gitlab-org/gitlab/-/issues/421687 # @param [RemoteDevelopment::Workspace] workspace # @param [Boolean] started @@ -353,6 +353,8 @@ def create_config_to_apply(workspace:, **args) # @param [String] project_name # @param [String] namespace_path # @param [Array] image_pull_secrets + # @param [Boolean] include_scripts_resources + # @param [Boolean] legacy_scripts_in_container_command # @param [Boolean] core_resources_only # @return [Array] def create_config_to_apply_v3( @@ -380,6 +382,8 @@ def create_config_to_apply_v3( project_name: "test-project", namespace_path: "test-group", image_pull_secrets: [], + include_scripts_resources: true, + legacy_scripts_in_container_command: false, core_resources_only: false ) spec_replicas = started ? 1 : 0 @@ -427,7 +431,9 @@ def create_config_to_apply_v3( default_resources_per_workspace_container: default_resources_per_workspace_container, allow_privilege_escalation: allow_privilege_escalation, use_kubernetes_user_namespaces: use_kubernetes_user_namespaces, - default_runtime_class: default_runtime_class + default_runtime_class: default_runtime_class, + include_scripts_resources: include_scripts_resources, + legacy_scripts_in_container_command: legacy_scripts_in_container_command ) workspace_service = workspace_service( @@ -460,6 +466,13 @@ def create_config_to_apply_v3( egress_ip_rules: egress_ip_rules ) + scripts_configmap = scripts_configmap( + workspace_name: workspace.name, + workspace_namespace: workspace.namespace, + labels: labels, + annotations: workspace_inventory_annotations + ) + secrets_inventory_config_map = secrets_inventory_config_map( workspace_name: workspace.name, workspace_namespace: workspace.namespace, @@ -485,7 +498,10 @@ def create_config_to_apply_v3( workspace_variables_file: workspace_variables_file || get_workspace_variables_file(workspace_variables: workspace.workspace_variables), additional_data: workspace_variables_additional_data || - { "#{reconcile_constants_module::WORKSPACE_RECONCILED_ACTUAL_STATE_FILE_NAME}": workspace.actual_state } + { + "#{workspace_operations_constants_module::WORKSPACE_RECONCILED_ACTUAL_STATE_FILE_NAME}": + workspace.actual_state + } ) if max_resources_per_workspace.present? @@ -513,6 +529,7 @@ def create_config_to_apply_v3( unless core_resources_only resources << workspace_service_account resources << workspace_network_policy if include_network_policy + resources << scripts_configmap if include_scripts_resources if include_all_resources resources << secrets_inventory_config_map if include_inventory @@ -525,7 +542,7 @@ def create_config_to_apply_v3( normalize_resources(namespace_path, project_name, resources) end - # rubocop:enable Metrics/ParameterLists, Metrics/CyclomaticComplexity, Metrics/AbcSize + # rubocop:enable Metrics/ParameterLists, Metrics/CyclomaticComplexity, Metrics/AbcSize, Metrics/PerceivedComplexity # @param [String] workspace_name # @param [String] workspace_namespace @@ -555,24 +572,28 @@ def workspace_inventory_config_map(workspace_name:, workspace_namespace:, labels # @param [String] workspace_name # @param [String] workspace_namespace - # @param [Hash] labels + # @param [Boolean] allow_privilege_escalation # @param [Hash] annotations - # @param [Integer] spec_replicas # @param [Hash] default_resources_per_workspace_container - # @param [Boolean] allow_privilege_escalation - # @param [Boolean] use_kubernetes_user_namespaces # @param [String] default_runtime_class + # @param [Boolean] include_scripts_resources + # @param [Boolean] legacy_scripts_in_container_command + # @param [Hash] labels + # @param [Integer] spec_replicas + # @param [Boolean] use_kubernetes_user_namespaces # @return [Hash] def workspace_deployment( workspace_name:, workspace_namespace:, - labels:, - annotations:, - spec_replicas:, - default_resources_per_workspace_container:, - allow_privilege_escalation:, - use_kubernetes_user_namespaces:, - default_runtime_class: + allow_privilege_escalation: false, + annotations: {}, + default_resources_per_workspace_container: {}, + default_runtime_class: "", + include_scripts_resources: true, + legacy_scripts_in_container_command: false, + labels: {}, + spec_replicas: 1, + use_kubernetes_user_namespaces: false ) container_security_context = { 'allowPrivilegeEscalation' => allow_privilege_escalation, @@ -672,6 +693,10 @@ def workspace_deployment( { mountPath: workspace_operations_constants_module::VARIABLES_VOLUME_PATH, name: workspace_operations_constants_module::VARIABLES_VOLUME_NAME + }, + { + mountPath: reconcile_constants_module::WORKSPACE_SCRIPTS_VOLUME_PATH, + name: reconcile_constants_module::WORKSPACE_SCRIPTS_VOLUME_NAME } ], securityContext: container_security_context, @@ -681,7 +706,23 @@ def workspace_deployment( name: "#{workspace_name}-env-var" } } - ] + ], + lifecycle: { + postStart: { + exec: { + command: [ + "/bin/sh", + "-c", + format( + files_module::KUBERNETES_POSTSTART_HOOK_COMMAND, + run_poststart_commands_script_file_path: + "#{reconcile_constants_module::WORKSPACE_SCRIPTS_VOLUME_PATH}/" \ + "#{reconcile_constants_module::RUN_POSTSTART_COMMANDS_SCRIPT_NAME}" # rubocop:disable Layout/LineEndStringConcatenationIndentation -- Match default RubyMien formatting + ) + ] + } + } + } }, { env: [ @@ -710,6 +751,10 @@ def workspace_deployment( { mountPath: workspace_operations_constants_module::VARIABLES_VOLUME_PATH, name: workspace_operations_constants_module::VARIABLES_VOLUME_NAME + }, + { + mountPath: reconcile_constants_module::WORKSPACE_SCRIPTS_VOLUME_PATH, + name: reconcile_constants_module::WORKSPACE_SCRIPTS_VOLUME_NAME } ], securityContext: container_security_context, @@ -828,7 +873,7 @@ def workspace_deployment( { name: workspace_operations_constants_module::VARIABLES_VOLUME_NAME, projected: { - defaultMode: 508, + defaultMode: workspace_operations_constants_module::VARIABLES_VOLUME_DEFAULT_MODE, sources: [ { secret: { @@ -837,6 +882,19 @@ def workspace_deployment( } ] } + }, + { + name: reconcile_constants_module::WORKSPACE_SCRIPTS_VOLUME_NAME, + projected: { + defaultMode: reconcile_constants_module::WORKSPACE_SCRIPTS_VOLUME_DEFAULT_MODE, + sources: [ + { + configMap: { + name: "#{workspace_name}-scripts-configmap" + } + } + ] + } } ], securityContext: { @@ -851,6 +909,26 @@ def workspace_deployment( status: {} } + unless include_scripts_resources + deployment[:spec][:template][:spec][:containers].each do |container| + container[:volumeMounts].delete_if do |volume_mount| + volume_mount[:name] == reconcile_constants_module::WORKSPACE_SCRIPTS_VOLUME_NAME + end + end + deployment[:spec][:template][:spec][:volumes].delete_if do |volume| + volume[:name] == reconcile_constants_module::WORKSPACE_SCRIPTS_VOLUME_NAME + end + deployment[:spec][:template][:spec][:containers][0].delete(:lifecycle) + end + + if legacy_scripts_in_container_command + deployment[:spec][:template][:spec][:containers][0][:args][0] = + <<~YAML.chomp + #{files_module::MAIN_COMPONENT_UPDATER_START_SSHD_SCRIPT} + #{files_module::MAIN_COMPONENT_UPDATER_INIT_TOOLS_SCRIPT} + YAML + end + deployment[:spec][:template][:spec].delete(:runtimeClassName) if default_runtime_class.empty? deployment[:spec][:template][:spec].delete(:hostUsers) unless use_kubernetes_user_namespaces @@ -919,7 +997,7 @@ def pvc( annotations: annotations, creationTimestamp: nil, labels: labels, - name: "#{workspace_name}-gl-workspace-data", + name: "#{workspace_name}-#{create_constants_module::WORKSPACE_DATA_VOLUME_NAME}", namespace: workspace_namespace }, spec: { @@ -1032,6 +1110,69 @@ def workspace_network_policy( } end + # @param [String] workspace_name + # @param [String] workspace_namespace + # @param [Hash] labels + # @param [Hash] annotations + # @return [Hash] + def scripts_configmap(workspace_name:, workspace_namespace:, labels:, annotations:) + postart_commands_script = + <<~SCRIPT + #!/bin/sh + echo "$(date -Iseconds): Running #{reconcile_constants_module::WORKSPACE_SCRIPTS_VOLUME_PATH}/gl-start-sshd-command..." + #{reconcile_constants_module::WORKSPACE_SCRIPTS_VOLUME_PATH}/gl-start-sshd-command || true + echo "$(date -Iseconds): Running #{reconcile_constants_module::WORKSPACE_SCRIPTS_VOLUME_PATH}/gl-init-tools-command..." + #{reconcile_constants_module::WORKSPACE_SCRIPTS_VOLUME_PATH}/gl-init-tools-command || true + echo "$(date -Iseconds): Running #{reconcile_constants_module::WORKSPACE_SCRIPTS_VOLUME_PATH}/gl-sleep-until-container-is-running-command..." + #{reconcile_constants_module::WORKSPACE_SCRIPTS_VOLUME_PATH}/gl-sleep-until-container-is-running-command || true + SCRIPT + + sleep_until_container_is_running_script = + format( + RemoteDevelopment::Files::MAIN_COMPONENT_UPDATER_SLEEP_UNTIL_CONTAINER_IS_RUNNING_SCRIPT, + workspace_reconciled_actual_state_file_path: + workspace_operations_constants_module::WORKSPACE_RECONCILED_ACTUAL_STATE_FILE_PATH + ) + + { + apiVersion: "v1", + kind: "ConfigMap", + metadata: { + annotations: annotations, + labels: labels, + name: "#{workspace_name}-scripts-configmap", + namespace: workspace_namespace + }, + data: { + "gl-init-tools-command": files_module::MAIN_COMPONENT_UPDATER_INIT_TOOLS_SCRIPT, + reconcile_constants_module::RUN_POSTSTART_COMMANDS_SCRIPT_NAME.to_sym => postart_commands_script, + "gl-sleep-until-container-is-running-command": sleep_until_container_is_running_script, + "gl-start-sshd-command": files_module::MAIN_COMPONENT_UPDATER_START_SSHD_SCRIPT + } + } + end + + # @param [String] workspace_name + # @param [String] workspace_namespace + # @param [Hash] labels + # @param [Hash] annotations + # @return [Hash] + def secrets_inventory_config_map(workspace_name:, workspace_namespace:, labels:, annotations:) + { + apiVersion: "v1", + kind: "ConfigMap", + metadata: { + annotations: annotations, + labels: + Gitlab::Utils.deep_sort_hashes( + labels.merge({ "cli-utils.sigs.k8s.io/inventory-id": "#{workspace_name}-secrets-inventory" }) + ), + name: "#{workspace_name}-secrets-inventory", + namespace: workspace_namespace + } + } + end + # @param [String] workspace_name # @param [String] workspace_namespace # @param [Hash] labels @@ -1076,27 +1217,6 @@ def workspace_resource_quota( } end - # @param [String] workspace_name - # @param [String] workspace_namespace - # @param [Hash] labels - # @param [Hash] annotations - # @return [Hash] - def secrets_inventory_config_map(workspace_name:, workspace_namespace:, labels:, annotations:) - { - apiVersion: "v1", - kind: "ConfigMap", - metadata: { - annotations: annotations, - labels: - Gitlab::Utils.deep_sort_hashes( - labels.merge({ "cli-utils.sigs.k8s.io/inventory-id": "#{workspace_name}-secrets-inventory" }) - ), - name: "#{workspace_name}-secrets-inventory", - namespace: workspace_namespace - } - } - end - # @param [String] workspace_name # @param [String] workspace_namespace # @param [Hash] labels