diff --git a/doc/user/workspace/_index.md b/doc/user/workspace/_index.md index 963e73ba476753a441e288b6de520fb1b2a9d5db..549490b1b5e1137214c3d26bd31139ce623ec37f 100644 --- a/doc/user/workspace/_index.md +++ b/doc/user/workspace/_index.md @@ -214,16 +214,58 @@ You can specify the base image, dependencies, and other settings. The `container` component type supports the following schema properties only: -| Property | Description | -|----------------| -------------------------------------------------------------------------------------------------------------------------------| -| `image` | Name of the container image to use for the workspace. | -| `memoryRequest`| Minimum amount of memory the container can use. | -| `memoryLimit` | Maximum amount of memory the container can use. | -| `cpuRequest` | Minimum amount of CPU the container can use. | -| `cpuLimit` | Maximum amount of CPU the container can use. | -| `env` | Environment variables to use in the container. Names must not start with `gl-`. | -| `endpoints` | Port mappings to expose from the container. Names must not start with `gl-`. | -| `volumeMounts` | Storage volume to mount in the container. | +| Property | Description | +|---------------- | -------------------------------------------------------------------------------------------------------------------------------| +| `image` | Name of the container image to use for the workspace. | +| `memoryRequest` | Minimum amount of memory the container can use. | +| `memoryLimit` | Maximum amount of memory the container can use. | +| `cpuRequest` | Minimum amount of CPU the container can use. | +| `cpuLimit` | Maximum amount of CPU the container can use. | +| `env` | Environment variables to use in the container. Names must not start with `gl-`. | +| `endpoints` | Port mappings to expose from the container. Names must not start with `gl-`. | +| `volumeMounts` | Storage volume to mount in the container. | +| `overrideCommand` | Override the container entrypoint with a keep-alive command. Defaults vary by component type. | + +#### `overrideCommand` attribute + +The `overrideCommand` attribute is a boolean that controls how Workspaces handle container entrypoints. +This attribute determines whether the container's original entrypoint is preserved or replaced +with a keep-alive command. + +The default value for `overrideCommand` depends on the component type: + +- Main component with attribute `gl/inject-editor: true`: Defaults to `true` when not specified. +- All other components: Defaults to `false` when not specified. + +When `true`, the container entrypoint is replaced with `tail -f /dev/null` to keep the +container running. +When `false`, the container uses either the devfile component `command`/`args` or the built container +image's `Entrypoint`/`Cmd`. + +The following table shows how `overrideCommand` affects container behavior. For clarity, these terms +are used in the table: + +- Devfile component: The `command` and `args` properties in the devfile component entry. +- Container image: The `Entrypoint` and `Cmd` fields in the OCI container image. + +| `overrideCommand` | Devfile component | Container image | Result | +|-------------------|-------------------|-----------------|--------| +| `true` | Specified | Specified | Validation error: Devfile component `command`/`args` cannot be specified when `overrideCommand` is `true`. | +| `true` | Specified | Not specified | Validation error: Devfile component `command`/`args` cannot be specified when `overrideCommand` is `true`. | +| `true` | Not specified | Specified | Container entrypoint replaced with `tail -f /dev/null`. | +| `true` | Not specified | Not specified | Container entrypoint replaced with `tail -f /dev/null`. | +| `false` | Specified | Specified | Devfile component `command`/`args` used as entrypoint. | +| `false` | Specified | Not specified | Devfile component `command`/`args` used as entrypoint. | +| `false` | Not specified | Specified | Container image `Entrypoint`/`Cmd` used. | +| `false` | Not specified | Not specified | Container exits prematurely (`CrashLoopBackOff`). 1 | + +**Footnotes**: + +1. When you create a workspace, it cannot access container image details, for example, from private +or internal registries. When `overrideCommand` is `false` and the Devfile doesn't specify `command` +or `args`, GitLab does not validate container images or check for required `Entrypoint` or `Cmd` fields. +You must ensure that either the Devfile or container specifies these fields, or the container exits +prematurely and the workspace fails to start. ### User-defined `postStart` events diff --git a/ee/lib/remote_development/README.md b/ee/lib/remote_development/README.md index 8c312caa13166a62fba5adf8bc4ba599603f65e8..3eff887accf426f86721b90bcf840fa12dbe3edf 100644 --- a/ee/lib/remote_development/README.md +++ b/ee/lib/remote_development/README.md @@ -623,7 +623,7 @@ This approach ensures: The `RemoteDevelopment::Files` module contains constants for all the files (default devfile, shell scripts, script fragments, commands, etc) that are used in the Remote Development domain for various purposes, such as programatically injecting into container `ENTRYPOINT`/`CMD`, etc. -For example: `RemoteDevelopment::Files::MAIN_COMPONENT_UPDATER_CONTAINER_ARGS`. +For example: `RemoteDevelopment::Files::CONTAINER_KEEPALIVE_COMMAND_ARGS`. The contents of these files are pulled out to separate files in the filesystem, instead of being hardcoded via inline Ruby HEREDOC or other means. This allows them to have full support for syntax highlighting and refactoring in IDEs, diff --git a/ee/lib/remote_development/devfile_operations/restrictions_enforcer.rb b/ee/lib/remote_development/devfile_operations/restrictions_enforcer.rb index ac01916e4ad7575ad331d9f45dee34a3ab35a1e9..5f73e2ffa619a4c7c41c4a72a5b14f5e2cbd20ef 100644 --- a/ee/lib/remote_development/devfile_operations/restrictions_enforcer.rb +++ b/ee/lib/remote_development/devfile_operations/restrictions_enforcer.rb @@ -11,13 +11,8 @@ class RestrictionsEnforcer # Since this is called after flattening the devfile, we can safely assume that it has valid syntax # as per devfile standard. If you are validating something that is not available across all devfile versions, # add additional guard clauses. - # Devfile standard only allows name/id to be of the format /'^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'/ - # Hence, we do no need to restrict the prefix `gl_`. - # However, we do that for the 'variables' in the devfile since they do not have any such restriction - RESTRICTED_PREFIX = "gl-" # Currently, we only support 'container' and 'volume' type components. - # For container components, ensure no endpoint name starts with restricted_prefix UNSUPPORTED_COMPONENT_TYPES = %i[kubernetes openshift image].freeze # Currently, we only support 'exec' and 'apply' for validation @@ -32,6 +27,9 @@ class RestrictionsEnforcer # Currently, we only support the default value `false` for the `hotReloadCapable` option SUPPORTED_HOT_RELOAD_VALUE = false + # Override command must be a boolean value + SUPPORTED_OVERRIDE_COMMAND_VALUE = [true, false].freeze + # @param [Hash] parent_context # @return [Gitlab::Fp::Result] def self.enforce(parent_context) @@ -233,6 +231,34 @@ def self.validate_containers(context) append_err(format(_("Property 'dedicatedPod' of component '%{component}' is not yet supported"), component: component_name), context) end + + attributes = component.fetch(:attributes, {}) + unless attributes.is_a?(Hash) + return append_err( + format(_("'attributes' in component '%{component}' must be a Hash"), component: component_name), context) + end + + override_command = attributes.fetch(:overrideCommand, true) + unless SUPPORTED_OVERRIDE_COMMAND_VALUE.include?(override_command) + return append_err( + format( + _("Property 'overrideCommand' of component '%{name}' must be a boolean (true or false)"), + name: component.fetch(:name) + ), + context + ) + end + + if override_command == true && (container[:command].present? || container[:args].present?) + return append_err( + format( + _("Properties 'command', 'args' for component '%{name}' can only be specified " \ + "when the 'overrideCommand' attribute is set to false"), + name: component.fetch(:name) + ), + context + ) + end end context diff --git a/ee/lib/remote_development/files.rb b/ee/lib/remote_development/files.rb index 38fbdc8bcd360d7005d0f297a41be002fa79d03f..7d95e29dfde42476f46e428342cbcbddb2bb0086 100644 --- a/ee/lib/remote_development/files.rb +++ b/ee/lib/remote_development/files.rb @@ -41,8 +41,8 @@ def self.kubernetes_poststart_hook_command end # @return [String] content of the file - def self.main_component_updater_container_args - read_file("workspace_operations/create/main_component_updater_container_args.sh") + def self.container_keepalive_command_args + read_file("workspace_operations/create/container_keepalive_command_args.sh") end # @return [String] content of the file @@ -87,7 +87,7 @@ def self.internal_poststart_command_clone_unshallow_script INTERNAL_POSTSTART_COMMAND_START_SSHD_SCRIPT = internal_poststart_command_start_sshd_script KUBERNETES_LEGACY_POSTSTART_HOOK_COMMAND = kubernetes_legacy_poststart_hook_command KUBERNETES_POSTSTART_HOOK_COMMAND = kubernetes_poststart_hook_command - MAIN_COMPONENT_UPDATER_CONTAINER_ARGS = main_component_updater_container_args + CONTAINER_KEEPALIVE_COMMAND_ARGS = container_keepalive_command_args # @return [Array] def self.all_expected_file_constants @@ -103,7 +103,7 @@ def self.all_expected_file_constants :INTERNAL_POSTSTART_COMMAND_START_SSHD_SCRIPT, :KUBERNETES_LEGACY_POSTSTART_HOOK_COMMAND, :KUBERNETES_POSTSTART_HOOK_COMMAND, - :MAIN_COMPONENT_UPDATER_CONTAINER_ARGS + :CONTAINER_KEEPALIVE_COMMAND_ARGS ] end diff --git a/ee/lib/remote_development/remote_development_constants.rb b/ee/lib/remote_development/remote_development_constants.rb index 390c168c3ee88feab52fdbb464ebaa846e8400fc..da45fd401478f4133f97f72275dbd319d66248cd 100644 --- a/ee/lib/remote_development/remote_development_constants.rb +++ b/ee/lib/remote_development/remote_development_constants.rb @@ -11,5 +11,10 @@ module RemoteDevelopmentConstants # Please keep alphabetized MAIN_COMPONENT_INDICATOR_ATTRIBUTE = "gl/inject-editor" REQUIRED_DEVFILE_SCHEMA_VERSION = "2.2.0" + + # Devfile standard only allows name/id to be of the format /'^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'/ + # Hence, we do no need to restrict the prefix `gl_`. + # However, we do that for the 'variables' in the devfile since they do not have any such restriction + RESTRICTED_PREFIX = "gl-" end end diff --git a/ee/lib/remote_development/workspace_operations/create/container_command_updater.rb b/ee/lib/remote_development/workspace_operations/create/container_command_updater.rb new file mode 100644 index 0000000000000000000000000000000000000000..d85458060c13a756abeeeb03eadddb5d9b743141 --- /dev/null +++ b/ee/lib/remote_development/workspace_operations/create/container_command_updater.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module RemoteDevelopment + module WorkspaceOperations + module Create + class ContainerCommandUpdater + include Files + include RemoteDevelopmentConstants + + # @param [Hash] context + # @return [Hash] + def self.update(context) + context => { + processed_devfile: { + components: Array => components + } + } + + components.each do |component| + attributes = component[:attributes] ||= {} + + # If overrideCommand is not present, set it based on whether it's the main component + # Defaults to true for the main_component and false for all other components + unless attributes.key?(:overrideCommand) + is_main_component = attributes[MAIN_COMPONENT_INDICATOR_ATTRIBUTE.to_sym] + attributes[:overrideCommand] = is_main_component ? true : false + end + + override_enabled = component.dig(:attributes, :overrideCommand) == true + + # Skip if overrideCommand is not enabled or component name starts with restricted prefix + next unless override_enabled + + container = component[:container] + next unless container + + # This overrides the entrypoint command for the container + container[:command] = %w[/bin/sh -c] + container[:args] = [CONTAINER_KEEPALIVE_COMMAND_ARGS] + end + + context + end + end + 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/container_keepalive_command_args.sh similarity index 100% rename from ee/lib/remote_development/workspace_operations/create/main_component_updater_container_args.sh rename to ee/lib/remote_development/workspace_operations/create/container_keepalive_command_args.sh diff --git a/ee/lib/remote_development/workspace_operations/create/main.rb b/ee/lib/remote_development/workspace_operations/create/main.rb index 6f2a5743a123996f20a27b1104f33c2d4d76bdd8..f94c403cb773fb88d4971bd9ae22499fa62c71ce 100644 --- a/ee/lib/remote_development/workspace_operations/create/main.rb +++ b/ee/lib/remote_development/workspace_operations/create/main.rb @@ -21,6 +21,7 @@ def self.main(context) .map(VolumeDefiner.method(:define)) .map(ToolsInjectorComponentInserter.method(:insert)) .map(MainComponentUpdater.method(:update)) + .map(ContainerCommandUpdater.method(:update)) .map(InternalPoststartCommandsInserter.method(:insert)) .map(VolumeComponentInserter.method(:insert)) .and_then(Creator.method(:create)) 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 0b815e6075e33be88390e9c8855ef3f2333e4db7..b1912b7bb70fdeaab8953cb76145a6760662cc3f 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 @@ -43,10 +43,6 @@ def self.update(context) ssh_port: WORKSPACE_SSH_PORT ) - override_command_and_args( - main_component_container: main_component_container - ) - context end @@ -109,20 +105,7 @@ def self.update_endpoints(main_component_container:, editor_port:, ssh_port:) nil end - # @param [Hash] main_component_container - # @return [void] - def self.override_command_and_args(main_component_container:) - # This overrides the main container's command - # Open issue to support both starting the editor and running the default command: - # https://gitlab.com/gitlab-org/gitlab/-/issues/392853 - - main_component_container[:command] = %w[/bin/sh -c] - main_component_container[:args] = [MAIN_COMPONENT_UPDATER_CONTAINER_ARGS] - - nil - end - - private_class_method :update_env_vars, :update_endpoints, :override_command_and_args + private_class_method :update_env_vars, :update_endpoints end end end diff --git a/ee/spec/fixtures/remote_development/example.container-commands-updated-devfile.yaml.erb b/ee/spec/fixtures/remote_development/example.container-commands-updated-devfile.yaml.erb new file mode 100644 index 0000000000000000000000000000000000000000..1538644cea6f434b3842b5d0a707b04a468f27e0 --- /dev/null +++ b/ee/spec/fixtures/remote_development/example.container-commands-updated-devfile.yaml.erb @@ -0,0 +1,85 @@ +--- +schemaVersion: 2.2.0 +metadata: {} +components: + - name: tooling-container + attributes: + gl/inject-editor: true + overrideCommand: true + container: + image: quay.io/mloriedo/universal-developer-image:ubi8-dw-demo + args: + - | + <%= indent_yaml_literal(CONTAINER_KEEPALIVE_COMMAND_ARGS, 10) %> + command: + - "/bin/sh" + - "-c" + env: + - name: GL_TOOLS_DIR + value: "/projects/.gl-tools" + - name: GL_VSCODE_LOG_LEVEL + value: "info" + - name: GL_VSCODE_PORT + value: "<%= WORKSPACE_EDITOR_PORT %>" + - name: GL_SSH_PORT + value: "<%= WORKSPACE_SSH_PORT %>" + - name: GL_VSCODE_ENABLE_MARKETPLACE + value: "false" + endpoints: + - name: editor-server + targetPort: <%= WORKSPACE_EDITOR_PORT %> + exposure: public + secure: true + protocol: https + - name: ssh-server + targetPort: <%= WORKSPACE_SSH_PORT %> + exposure: internal + secure: true + dedicatedPod: false + mountSources: true + - name: database-container + attributes: + overrideCommand: false + container: + image: mysql + env: + - name: MYSQL_ROOT_PASSWORD + value: "my-secret-pw" + dedicatedPod: false + mountSources: true + - name: user-defined-entrypoint-cmd-component + attributes: + overrideCommand: false + container: + image: quay.io/mloriedo/universal-developer-image:ubi8-dw-demo + command: ["echo"] + args: ["-n", "user-defined entrypoint command"] + dedicatedPod: false + mountSources: true + - name: gl-tools-injector + attributes: + overrideCommand: false + container: + image: <%= WORKSPACE_TOOLS_IMAGE %> + env: + - name: GL_TOOLS_DIR + value: "/projects/.gl-tools" + memoryLimit: 512Mi + memoryRequest: 256Mi + cpuLimit: 500m + cpuRequest: 100m +events: + preStart: + - gl-tools-injector-command + postStart: + - user-defined-command +commands: + - id: user-defined-command + exec: + component: tooling-container + commandLine: echo 'user-defined postStart command' + hotReloadCapable: false + - id: gl-tools-injector-command + apply: + component: gl-tools-injector +variables: {} diff --git a/ee/spec/fixtures/remote_development/example.devfile.yaml.erb b/ee/spec/fixtures/remote_development/example.devfile.yaml.erb index b23ec15cc93a0f151d0c8722c721f4fd64aa1fb8..89469bb636852591f77f6339c4a772d5e7d51b36 100644 --- a/ee/spec/fixtures/remote_development/example.devfile.yaml.erb +++ b/ee/spec/fixtures/remote_development/example.devfile.yaml.erb @@ -12,6 +12,13 @@ components: env: - name: MYSQL_ROOT_PASSWORD value: "my-secret-pw" + - name: user-defined-entrypoint-cmd-component + attributes: + overrideCommand: false + container: + image: quay.io/mloriedo/universal-developer-image:ubi8-dw-demo + command: ["echo"] + args: ["-n", "user-defined entrypoint command"] commands: - id: user-defined-command exec: diff --git a/ee/spec/fixtures/remote_development/example.flattened-devfile.yaml.erb b/ee/spec/fixtures/remote_development/example.flattened-devfile.yaml.erb index a1ab14be9569e1575d7958f5abe39f85e65dfd36..1d9010790ed5449295b3c32c3fed92cb7ee17fd0 100644 --- a/ee/spec/fixtures/remote_development/example.flattened-devfile.yaml.erb +++ b/ee/spec/fixtures/remote_development/example.flattened-devfile.yaml.erb @@ -17,6 +17,15 @@ components: env: - name: MYSQL_ROOT_PASSWORD value: "my-secret-pw" + - name: user-defined-entrypoint-cmd-component + attributes: + overrideCommand: false + container: + dedicatedPod: false + mountSources: true + image: quay.io/mloriedo/universal-developer-image:ubi8-dw-demo + command: ["echo"] + args: ["-n", "user-defined entrypoint command"] commands: - id: user-defined-command exec: diff --git a/ee/spec/fixtures/remote_development/example.internal-poststart-commands-inserted-devfile.yaml.erb b/ee/spec/fixtures/remote_development/example.internal-poststart-commands-inserted-devfile.yaml.erb index 5e6fdf7b4e14bfc9efefa254e0c92c4233d81e28..b168db6c869e44e9791708958e6b65ab6e766bbf 100644 --- a/ee/spec/fixtures/remote_development/example.internal-poststart-commands-inserted-devfile.yaml.erb +++ b/ee/spec/fixtures/remote_development/example.internal-poststart-commands-inserted-devfile.yaml.erb @@ -5,11 +5,12 @@ components: - name: tooling-container attributes: gl/inject-editor: true + overrideCommand: true container: image: quay.io/mloriedo/universal-developer-image:ubi8-dw-demo args: - | - <%= indent_yaml_literal(MAIN_COMPONENT_UPDATER_CONTAINER_ARGS, 10) %> + <%= indent_yaml_literal(CONTAINER_KEEPALIVE_COMMAND_ARGS, 10) %> command: - "/bin/sh" - "-c" @@ -37,6 +38,8 @@ components: dedicatedPod: false mountSources: true - name: database-container + attributes: + overrideCommand: false container: image: mysql env: @@ -44,7 +47,18 @@ components: value: "my-secret-pw" dedicatedPod: false mountSources: true + - name: user-defined-entrypoint-cmd-component + attributes: + overrideCommand: false + container: + image: quay.io/mloriedo/universal-developer-image:ubi8-dw-demo + command: ["echo"] + args: ["-n", "user-defined entrypoint command"] + dedicatedPod: false + mountSources: true - name: gl-tools-injector + attributes: + overrideCommand: false container: image: <%= WORKSPACE_TOOLS_IMAGE %> env: diff --git a/ee/spec/fixtures/remote_development/example.invalid-attributes-override-command-with-command-args-present-devfile.yaml.erb b/ee/spec/fixtures/remote_development/example.invalid-attributes-override-command-with-command-args-present-devfile.yaml.erb new file mode 100644 index 0000000000000000000000000000000000000000..b25aa6ac46875261cea903a789949e0b03016b4c --- /dev/null +++ b/ee/spec/fixtures/remote_development/example.invalid-attributes-override-command-with-command-args-present-devfile.yaml.erb @@ -0,0 +1,14 @@ +--- +schemaVersion: 2.2.0 +components: + - name: tooling-container + attributes: + gl/inject-editor: true + overrideCommand: true + container: + image: quay.io/mloriedo/universal-developer-image:ubi8-dw-demo + command: ["echo"] + args: ["-n", "user-defined entrypoint command"] +commands: [] +events: {} +variables: {} \ No newline at end of file diff --git a/ee/spec/fixtures/remote_development/example.invalid-attributes-override-command-with-non-boolean-value-devfile.yaml.erb b/ee/spec/fixtures/remote_development/example.invalid-attributes-override-command-with-non-boolean-value-devfile.yaml.erb new file mode 100644 index 0000000000000000000000000000000000000000..c44f9ddbc50a9d561d55ea18803c4b3088bc0858 --- /dev/null +++ b/ee/spec/fixtures/remote_development/example.invalid-attributes-override-command-with-non-boolean-value-devfile.yaml.erb @@ -0,0 +1,14 @@ +--- +schemaVersion: 2.2.0 +components: + - name: tooling-container + attributes: + gl/inject-editor: true + overrideCommand: "true" + container: + image: quay.io/mloriedo/universal-developer-image:ubi8-dw-demo + command: ["echo"] + args: ["-n", "user-defined entrypoint command"] +commands: [] +events: {} +variables: {} \ No newline at end of file diff --git a/ee/spec/fixtures/remote_development/example.legacy-no-poststart-in-container-command-processed-devfile.yaml.erb b/ee/spec/fixtures/remote_development/example.legacy-no-poststart-in-container-command-processed-devfile.yaml.erb index e88d474cc571031c8613bff8c0f58a47dd37f2d8..985524f024d513f52707e7f86aeefda88016b359 100644 --- a/ee/spec/fixtures/remote_development/example.legacy-no-poststart-in-container-command-processed-devfile.yaml.erb +++ b/ee/spec/fixtures/remote_development/example.legacy-no-poststart-in-container-command-processed-devfile.yaml.erb @@ -51,6 +51,18 @@ components: value: "my-secret-pw" dedicatedPod: false mountSources: true + - name: user-defined-entrypoint-cmd-component + attributes: + overrideCommand: false + container: + image: quay.io/mloriedo/universal-developer-image:ubi8-dw-demo + command: ["echo"] + args: ["-n", "user-defined entrypoint command"] + volumeMounts: + - name: gl-workspace-data + path: /projects + dedicatedPod: false + mountSources: true - name: gl-tools-injector container: image: <%= WORKSPACE_TOOLS_IMAGE %> diff --git a/ee/spec/fixtures/remote_development/example.legacy-poststart-in-container-command-processed-devfile.yaml.erb b/ee/spec/fixtures/remote_development/example.legacy-poststart-in-container-command-processed-devfile.yaml.erb index 4dd6a01c23a8da81d360cb0a91bdc9b667fa7e5c..c76197c98c6d988ced112f90a1f39f2bd9c7e30c 100644 --- a/ee/spec/fixtures/remote_development/example.legacy-poststart-in-container-command-processed-devfile.yaml.erb +++ b/ee/spec/fixtures/remote_development/example.legacy-poststart-in-container-command-processed-devfile.yaml.erb @@ -9,7 +9,7 @@ components: image: quay.io/mloriedo/universal-developer-image:ubi8-dw-demo args: - | - <%= indent_yaml_literal(MAIN_COMPONENT_UPDATER_CONTAINER_ARGS, 10) %> + <%= indent_yaml_literal(CONTAINER_KEEPALIVE_COMMAND_ARGS, 10) %> command: - "/bin/sh" - "-c" @@ -50,6 +50,18 @@ components: value: "my-secret-pw" dedicatedPod: false mountSources: true + - name: user-defined-entrypoint-cmd-component + attributes: + overrideCommand: false + container: + image: quay.io/mloriedo/universal-developer-image:ubi8-dw-demo + command: ["echo"] + args: ["-n", "user-defined entrypoint command"] + volumeMounts: + - name: gl-workspace-data + path: /projects + dedicatedPod: false + mountSources: true - name: gl-tools-injector container: image: <%= WORKSPACE_TOOLS_IMAGE %> 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 65f71c915b09080adcaf3f648ebb2e3847303009..dee97fca4a270f7b9b4e30efc09666d9c277cf27 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 @@ -7,12 +7,6 @@ components: gl/inject-editor: true container: image: quay.io/mloriedo/universal-developer-image:ubi8-dw-demo - args: - - | - <%= indent_yaml_literal(MAIN_COMPONENT_UPDATER_CONTAINER_ARGS, 10) %> - command: - - "/bin/sh" - - "-c" env: - name: GL_TOOLS_DIR value: "/projects/.gl-tools" @@ -44,6 +38,15 @@ components: value: "my-secret-pw" dedicatedPod: false mountSources: true + - name: user-defined-entrypoint-cmd-component + attributes: + overrideCommand: false + container: + image: quay.io/mloriedo/universal-developer-image:ubi8-dw-demo + command: ["echo"] + args: ["-n", "user-defined entrypoint command"] + dedicatedPod: false + mountSources: true - name: gl-tools-injector container: image: <%= WORKSPACE_TOOLS_IMAGE %> 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 65f71c915b09080adcaf3f648ebb2e3847303009..dee97fca4a270f7b9b4e30efc09666d9c277cf27 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 @@ -7,12 +7,6 @@ components: gl/inject-editor: true container: image: quay.io/mloriedo/universal-developer-image:ubi8-dw-demo - args: - - | - <%= indent_yaml_literal(MAIN_COMPONENT_UPDATER_CONTAINER_ARGS, 10) %> - command: - - "/bin/sh" - - "-c" env: - name: GL_TOOLS_DIR value: "/projects/.gl-tools" @@ -44,6 +38,15 @@ components: value: "my-secret-pw" dedicatedPod: false mountSources: true + - name: user-defined-entrypoint-cmd-component + attributes: + overrideCommand: false + container: + image: quay.io/mloriedo/universal-developer-image:ubi8-dw-demo + command: ["echo"] + args: ["-n", "user-defined entrypoint command"] + dedicatedPod: false + mountSources: true - name: gl-tools-injector container: image: <%= WORKSPACE_TOOLS_IMAGE %> 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 6d683c9da64e5a7c83e529e6b144b92f7347d89f..3bca9e092e783263b5d416dfa16050b0a3148a61 100644 --- a/ee/spec/fixtures/remote_development/example.processed-devfile.yaml.erb +++ b/ee/spec/fixtures/remote_development/example.processed-devfile.yaml.erb @@ -5,11 +5,12 @@ components: - name: tooling-container attributes: gl/inject-editor: true + overrideCommand: true container: image: quay.io/mloriedo/universal-developer-image:ubi8-dw-demo args: - | - <%= indent_yaml_literal(MAIN_COMPONENT_UPDATER_CONTAINER_ARGS, 10) %> + <%= indent_yaml_literal(CONTAINER_KEEPALIVE_COMMAND_ARGS, 10) %> command: - "/bin/sh" - "-c" @@ -40,6 +41,8 @@ components: dedicatedPod: false mountSources: true - name: database-container + attributes: + overrideCommand: false container: image: mysql volumeMounts: @@ -50,7 +53,21 @@ components: value: "my-secret-pw" dedicatedPod: false mountSources: true + - name: user-defined-entrypoint-cmd-component + attributes: + overrideCommand: false + container: + image: quay.io/mloriedo/universal-developer-image:ubi8-dw-demo + volumeMounts: + - name: gl-workspace-data + path: /projects + command: ["echo"] + args: ["-n", "user-defined entrypoint command"] + dedicatedPod: false + mountSources: true - name: gl-tools-injector + attributes: + overrideCommand: false container: image: <%= WORKSPACE_TOOLS_IMAGE %> volumeMounts: diff --git a/ee/spec/fixtures/remote_development/example.tools-injector-inserted-devfile.yaml.erb b/ee/spec/fixtures/remote_development/example.tools-injector-inserted-devfile.yaml.erb index 57754eda21c8fce0f2f964fc70735fef630589da..d43e844f9768ec9b5e607f6c364aa6b56860df8a 100644 --- a/ee/spec/fixtures/remote_development/example.tools-injector-inserted-devfile.yaml.erb +++ b/ee/spec/fixtures/remote_development/example.tools-injector-inserted-devfile.yaml.erb @@ -17,6 +17,15 @@ components: value: "my-secret-pw" dedicatedPod: false mountSources: true + - name: user-defined-entrypoint-cmd-component + attributes: + overrideCommand: false + container: + image: quay.io/mloriedo/universal-developer-image:ubi8-dw-demo + command: ["echo"] + args: ["-n", "user-defined entrypoint command"] + dedicatedPod: false + mountSources: true - name: gl-tools-injector container: image: <%= WORKSPACE_TOOLS_IMAGE %> diff --git a/ee/spec/lib/remote_development/devfile_operations/restrictions_enforcer_spec.rb b/ee/spec/lib/remote_development/devfile_operations/restrictions_enforcer_spec.rb index 49c3ca9d4e0704d8749ce0331f0077fa0595094a..55cbb3a3c2d837a992560e6fcb269a265c7116d3 100644 --- a/ee/spec/lib/remote_development/devfile_operations/restrictions_enforcer_spec.rb +++ b/ee/spec/lib/remote_development/devfile_operations/restrictions_enforcer_spec.rb @@ -74,6 +74,8 @@ # rubocop:disable Layout/LineLength -- we want single lines for RSpec::Parameterized::TableSyntax where(:input_devfile_name, :error_str) do + "example.invalid-attributes-override-command-with-command-args-present-devfile.yaml.erb" | "Properties 'command', 'args' for component 'tooling-container' can only be specified when the 'overrideCommand' attribute is set to false" + "example.invalid-attributes-override-command-with-non-boolean-value-devfile.yaml.erb" | "Property 'overrideCommand' of component 'tooling-container' must be a boolean (true or false)" "example.invalid-attributes-tools-injector-absent-devfile.yaml.erb" | "No component has '#{main_component_indicator_attribute}' attribute" "example.invalid-attributes-tools-injector-multiple-devfile.yaml.erb" | "Multiple components '[\"tooling-container\", \"tooling-container-2\"]' have '#{main_component_indicator_attribute}' attribute" "example.invalid-component-missing-name.yaml.erb" | "A component must have a 'name'" diff --git a/ee/spec/lib/remote_development/workspace_operations/create/container_command_updater_spec.rb b/ee/spec/lib/remote_development/workspace_operations/create/container_command_updater_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..99de3c5d69a4c5f843e548dd6ef8de0d7a0fdb7c --- /dev/null +++ b/ee/spec/lib/remote_development/workspace_operations/create/container_command_updater_spec.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +require "fast_spec_helper" + +RSpec.describe RemoteDevelopment::WorkspaceOperations::Create::ContainerCommandUpdater, feature_category: :workspaces do + include_context 'with remote development shared fixtures' + + let(:input_processed_devfile) do + read_devfile("example.main-container-updated-devfile.yaml.erb") + end + + let(:expected_processed_devfile_name) { "example.container-commands-updated-devfile.yaml.erb" } + let(:expected_processed_devfile) { read_devfile(expected_processed_devfile_name) } + + let(:vscode_extension_marketplace_metadata_enabled) { false } + + let(:context) do + { + processed_devfile: input_processed_devfile, + tools_dir: "#{workspace_operations_constants_module::WORKSPACE_DATA_VOLUME_PATH}/" \ + "#{create_constants_module::TOOLS_DIR_NAME}", + vscode_extension_marketplace_metadata: { enabled: vscode_extension_marketplace_metadata_enabled } + } + end + + subject(:returned_value) do + described_class.update(context) # rubocop:disable Rails/SaveBang -- Silly rubocop, this isn't an ActiveRecord object + end + + it 'preserves script formatting' do + expected = expected_processed_devfile[:components].first[:container][:args].first + actual = returned_value[:processed_devfile][:components].first[:container][:args].first + expect(actual).to eq(expected) + end + + it "adds the overrideCommand attribute for components when not specified in the devfile" do + result = returned_value[:processed_devfile] + components = result[:components] + + # Find the main component + main_component = components.find { |c| c[:name] == "tooling-container" } + expect(main_component[:attributes][:overrideCommand]).to be true + + # Find non-main components that don't already have overrideCommand set + database_component = components.find { |c| c[:name] == "database-container" } + expect(database_component[:attributes][:overrideCommand]).to be false + + tools_injector_component = components.find { |c| c[:name] == "gl-tools-injector" } + expect(tools_injector_component[:attributes][:overrideCommand]).to be false + + # Verify that existing overrideCommand values are preserved + user_defined_component = components.find { |c| c[:name] == "user-defined-entrypoint-cmd-component" } + expect(user_defined_component[:attributes][:overrideCommand]).to be false + end + + it "updates container command and args for components with overrideCommand: true" do + result = returned_value[:processed_devfile] + components = result[:components] + + # Main component should have its command and args updated + main_component = components.find { |c| c[:name] == "tooling-container" } + expect(main_component[:container][:command]).to eq(%w[/bin/sh -c]) + expect(main_component[:container][:args]).to eq([files_module::CONTAINER_KEEPALIVE_COMMAND_ARGS]) + + # Non-main components should not have their command/args updated + database_component = components.find { |c| c[:name] == "database-container" } + expect(database_component[:container]).not_to have_key(:command) + expect(database_component[:container]).not_to have_key(:args) + end +end diff --git a/ee/spec/lib/remote_development/workspace_operations/create/desired_config/devfile_parser_getter_spec.rb b/ee/spec/lib/remote_development/workspace_operations/create/desired_config/devfile_parser_getter_spec.rb index 3d6215bd243979630e4c689761fd1a73f4b77c2b..0826979b45f5fc3e3107bf1b164e2c90e8072150 100644 --- a/ee/spec/lib/remote_development/workspace_operations/create/desired_config/devfile_parser_getter_spec.rb +++ b/ee/spec/lib/remote_development/workspace_operations/create/desired_config/devfile_parser_getter_spec.rb @@ -84,6 +84,20 @@ imagePullPolicy: Always name: database-container resources: {} + - args: + - -n + - user-defined entrypoint command + command: + - echo + env: + - name: PROJECTS_ROOT + value: /projects + - name: PROJECT_SOURCE + value: /projects + image: quay.io/mloriedo/universal-developer-image:ubi8-dw-demo + imagePullPolicy: Always + name: user-defined-entrypoint-cmd-component + resources: {} status: {} --- apiVersion: v1 diff --git a/ee/spec/lib/remote_development/workspace_operations/create/internal_poststart_commands_inserter_spec.rb b/ee/spec/lib/remote_development/workspace_operations/create/internal_poststart_commands_inserter_spec.rb index b7fe8073659d933f8627f80d1ed7ae97c27589d0..dc8df8225250d8635f0db8fd78d8dba363a2e0dd 100644 --- a/ee/spec/lib/remote_development/workspace_operations/create/internal_poststart_commands_inserter_spec.rb +++ b/ee/spec/lib/remote_development/workspace_operations/create/internal_poststart_commands_inserter_spec.rb @@ -6,7 +6,7 @@ include_context "with remote development shared fixtures" let(:input_processed_devfile) do - read_devfile("example.main-container-updated-devfile.yaml.erb") + read_devfile("example.container-commands-updated-devfile.yaml.erb") end let(:expected_processed_devfile_name) { "example.internal-poststart-commands-inserted-devfile.yaml.erb" } 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 d454ee7ed587de6273d835fd0e9616833af54d57..e2a8953cb5c682544639f2b8b87dbfc01cccc056 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 @@ -31,12 +31,6 @@ expect(returned_value[:processed_devfile]).to eq(expected_processed_devfile) end - it 'preserves script formatting' do - expected = expected_processed_devfile[:components].first[:container][:args].first - actual = returned_value[:processed_devfile][:components].first[:container][:args].first - expect(actual).to eq(expected) - end - context "when vscode_extension_marketplace_metadata Web IDE setting is disabled" do let(:expected_processed_devfile_name) { "example.main-container-updated-marketplace-disabled-devfile.yaml.erb" } let(:vscode_extension_marketplace_metadata_enabled) { false } diff --git a/ee/spec/lib/remote_development/workspace_operations/create/main_spec.rb b/ee/spec/lib/remote_development/workspace_operations/create/main_spec.rb index 0552c6ba8e7ddb1d07283a660bceb6f664fb6231..741b9b686a853845468fe1ecd67f01d06ca66cff 100644 --- a/ee/spec/lib/remote_development/workspace_operations/create/main_spec.rb +++ b/ee/spec/lib/remote_development/workspace_operations/create/main_spec.rb @@ -12,6 +12,7 @@ [RemoteDevelopment::WorkspaceOperations::Create::VolumeDefiner, :map], [RemoteDevelopment::WorkspaceOperations::Create::ToolsInjectorComponentInserter, :map], [RemoteDevelopment::WorkspaceOperations::Create::MainComponentUpdater, :map], + [RemoteDevelopment::WorkspaceOperations::Create::ContainerCommandUpdater, :map], [RemoteDevelopment::WorkspaceOperations::Create::InternalPoststartCommandsInserter, :map], [RemoteDevelopment::WorkspaceOperations::Create::VolumeComponentInserter, :map], [RemoteDevelopment::WorkspaceOperations::Create::Creator, :and_then], diff --git a/ee/spec/lib/remote_development/workspace_operations/create/tools_injector_component_inserter_spec.rb b/ee/spec/lib/remote_development/workspace_operations/create/tools_injector_component_inserter_spec.rb index d8d5b7477a0f4aed2cf01c9fddb8d7d9575ca63d..f3a07feb480e5045d06edd6b61d56cd30556053c 100644 --- a/ee/spec/lib/remote_development/workspace_operations/create/tools_injector_component_inserter_spec.rb +++ b/ee/spec/lib/remote_development/workspace_operations/create/tools_injector_component_inserter_spec.rb @@ -47,7 +47,12 @@ let(:tools_injector_image_from_settings) { 'my/awesome/image:42' } it 'uses image override' do - image_from_processed_devfile = returned_value.dig(:processed_devfile, :components, 2, :container, :image) + components = returned_value.dig(:processed_devfile, :components) + gl_tools_component = components.find do |component| + component[:name] == create_constants_module::TOOLS_INJECTOR_COMPONENT_NAME + end + image_from_processed_devfile = gl_tools_component.dig(:container, :image) + expect(image_from_processed_devfile).to eq(tools_injector_image_from_settings) end end 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 04c2c2323350054cc29974ac4360d759520ae249..f1cf2b5bc73a66fba2de3169420085705720989e 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 @@ -650,7 +650,7 @@ def workspace_deployment( runtimeClassName: default_runtime_class, containers: [ { - args: [files_module::MAIN_COMPONENT_UPDATER_CONTAINER_ARGS], + args: [files_module::CONTAINER_KEEPALIVE_COMMAND_ARGS], command: %w[/bin/sh -c], env: [ { @@ -782,6 +782,46 @@ def workspace_deployment( } } ] + }, + { + image: "quay.io/mloriedo/universal-developer-image:ubi8-dw-demo", + imagePullPolicy: "Always", + name: "user-defined-entrypoint-cmd-component", + resources: default_resources_per_workspace_container, + command: ["echo"], + args: ["-n", "user-defined entrypoint command"], + env: [ + { + name: "PROJECTS_ROOT", + value: workspace_operations_constants_module::WORKSPACE_DATA_VOLUME_PATH + }, + { + name: "PROJECT_SOURCE", + value: workspace_operations_constants_module::WORKSPACE_DATA_VOLUME_PATH + } + ], + securityContext: container_security_context, + envFrom: [ + { + secretRef: { + name: "#{workspace_name}-env-var" + } + } + ], + volumeMounts: [ + { + mountPath: workspace_operations_constants_module::WORKSPACE_DATA_VOLUME_PATH, + name: create_constants_module::WORKSPACE_DATA_VOLUME_NAME + }, + { + mountPath: workspace_operations_constants_module::VARIABLES_VOLUME_PATH, + name: workspace_operations_constants_module::VARIABLES_VOLUME_NAME + }, + { + mountPath: create_constants_module::WORKSPACE_SCRIPTS_VOLUME_PATH, + name: create_constants_module::WORKSPACE_SCRIPTS_VOLUME_NAME + } + ] } ], initContainers: [ diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 4e9ede9ea06f13a360285acf6db6c9558ae71a99..df98f2c52c88ebe7c1ed0c1ec7fc0cc6dff3a8cb 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -1744,6 +1744,9 @@ msgstr "" msgid "'attributes' for component '%{component_name}' must be a Hash" msgstr "" +msgid "'attributes' in component '%{component}' must be a Hash" +msgstr "" + msgid "'command' must be a Hash" msgstr "" @@ -50610,12 +50613,18 @@ msgstr "" msgid "Prompt users to upload SSH keys" msgstr "" +msgid "Properties 'command', 'args' for component '%{name}' can only be specified when the 'overrideCommand' attribute is set to false" +msgstr "" + msgid "Property 'dedicatedPod' of component '%{component}' is not yet supported" msgstr "" msgid "Property 'hotReloadCapable' for exec command '%{command}' must be false when specified" msgstr "" +msgid "Property 'overrideCommand' of component '%{name}' must be a boolean (true or false)" +msgstr "" + msgid "Protect" msgstr ""