From c635976da16c0ad4a78be4a4ed4c089eac203202 Mon Sep 17 00:00:00 2001 From: Vishal Tak Date: Tue, 28 May 2024 10:24:29 +0530 Subject: [PATCH] Support for variables during workspace creation Changelog: added Co-authored-by: Dylan Griffith Co-authored-by: Chad Woolley Co-authored-by: Enrique Alcantara --- doc/api/graphql/reference/index.md | 21 ++ .../user/components/workspace_variables.vue | 178 ++++++++++++++ .../javascripts/workspaces/user/constants.js | 4 + .../workspaces/user/pages/create.vue | 50 ++-- .../workspaces_feature_flag_controller.rb | 10 +- .../remote_development/workspaces/create.rb | 14 +- .../workspace_variable_input.rb | 22 ++ .../workspace_variable_input_type_enum.rb | 15 ++ .../remote_development/enums/workspace.rb | 20 ++ .../remote_development/workspace_variable.rb | 14 +- .../workspaces/create/workspace_creator.rb | 27 ++- .../workspaces/create/workspace_variables.rb | 48 ++-- .../create/workspace_variables_creator.rb | 16 +- .../output/desired_config_generator.rb | 13 +- .../output/desired_config_generator_v2.rb | 10 +- .../remote_development/workspace_variables.rb | 2 +- .../remote_development/workspaces.rb | 3 +- .../remote_development/workspaces_spec.rb | 5 + .../components/workspace_variables_spec.js | 219 ++++++++++++++++++ .../workspaces/user/pages/create_spec.js | 38 +++ .../workspace_variable_input_spec.rb | 17 ++ .../agent_config/main_spec.rb | 14 +- .../create/main_spec.rb | 2 +- .../delete/main_spec.rb | 2 +- .../remote_development/rd_fast_spec_helper.rb | 1 + .../workspaces/create/creator_spec.rb | 6 +- .../create/main_integration_spec.rb | 19 +- .../workspace_variables_creator_spec.rb | 13 +- .../create/workspace_variables_spec.rb | 56 +++-- .../input/actual_state_calculator_spec.rb | 2 +- .../reconcile/input/factory_spec.rb | 2 +- .../reconcile/main_integration_spec.rb | 2 +- .../workspaces/reconcile/main_spec.rb | 2 +- .../reconcile/output/devfile_parser_spec.rb | 8 +- .../output/devfile_parser_v2_spec.rb | 8 +- .../workspaces/update/main_spec.rb | 10 +- .../workspace_variable_spec.rb | 23 +- .../remote_development/group_policy_spec.rb | 4 - .../workspaces/create_spec.rb | 6 +- .../remote_development_shared_contexts.rb | 73 ++---- .../settings/current_settings_reader.rb | 2 +- locale/gitlab.pot | 9 + qa/qa/ee/page/workspace/list.rb | 1 + qa/qa/ee/page/workspace/new.rb | 10 + spec/support/matchers/graphql_matchers.rb | 2 +- 45 files changed, 827 insertions(+), 196 deletions(-) create mode 100644 ee/app/assets/javascripts/workspaces/user/components/workspace_variables.vue create mode 100644 ee/app/graphql/types/remote_development/workspace_variable_input.rb create mode 100644 ee/app/graphql/types/remote_development/workspace_variable_input_type_enum.rb create mode 100644 ee/app/models/remote_development/enums/workspace.rb create mode 100644 ee/spec/frontend/workspaces/user/components/workspace_variables_spec.js create mode 100644 ee/spec/graphql/types/remote_development/workspace_variable_input_spec.rb diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index c70b19411225f3..04828973c239ae 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -10095,6 +10095,7 @@ Input type: `WorkspaceCreateInput` | `editor` | [`String!`](#string) | Editor to inject into the created workspace. Must match a configured template. | | `maxHoursBeforeTermination` | [`Int!`](#int) | Maximum hours the workspace can exist before it is automatically terminated. | | `projectId` | [`ProjectID!`](#projectid) | ID of the project that will provide the Devfile for the created workspace. | +| `variables` | [`[WorkspaceVariableInput!]`](#workspacevariableinput) | Variables to inject into the workspace. | #### Fields @@ -36063,6 +36064,14 @@ Type of a work item widget. | `TIME_TRACKING` | Time Tracking widget. | | `WEIGHT` | Weight widget. | +### `WorkspaceVariableInputType` + +Enum for the type of the variable to be injected in a workspace. + +| Value | Description | +| ----- | ----------- | +| `ENVIRONMENT` | Name type. | + ## Scalar types Scalar values are atomic values, and do not have fields of their own. @@ -39015,3 +39024,15 @@ Attributes for value stream stage. | Name | Type | Description | | ---- | ---- | ----------- | | `weight` | [`Int`](#int) | Weight of the work item. | + +### `WorkspaceVariableInput` + +Attributes for defining a variable to be injected in a workspace. + +#### Arguments + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `key` | [`String!`](#string) | Key of the variable. | +| `type` | [`WorkspaceVariableInputType!`](#workspacevariableinputtype) | Type of the variable to be injected in a workspace. | +| `value` | [`String!`](#string) | Value of the variable. | diff --git a/ee/app/assets/javascripts/workspaces/user/components/workspace_variables.vue b/ee/app/assets/javascripts/workspaces/user/components/workspace_variables.vue new file mode 100644 index 00000000000000..2bc23aaae47446 --- /dev/null +++ b/ee/app/assets/javascripts/workspaces/user/components/workspace_variables.vue @@ -0,0 +1,178 @@ + + diff --git a/ee/app/assets/javascripts/workspaces/user/constants.js b/ee/app/assets/javascripts/workspaces/user/constants.js index 409dd82263f1d9..7142590034e734 100644 --- a/ee/app/assets/javascripts/workspaces/user/constants.js +++ b/ee/app/assets/javascripts/workspaces/user/constants.js @@ -20,3 +20,7 @@ export const PROJECT_VISIBILITY = { export const DEFAULT_DESIRED_STATE = WORKSPACE_STATES.running; export const DEFAULT_DEVFILE_PATH = '.devfile.yaml'; export const DEFAULT_EDITOR = 'webide'; + +export const WORKSPACE_VARIABLE_INPUT_TYPE_ENUM = { + env: 'ENVIRONMENT', +}; diff --git a/ee/app/assets/javascripts/workspaces/user/pages/create.vue b/ee/app/assets/javascripts/workspaces/user/pages/create.vue index 5db9a5974aa8c0..5360d8eeb78b15 100644 --- a/ee/app/assets/javascripts/workspaces/user/pages/create.vue +++ b/ee/app/assets/javascripts/workspaces/user/pages/create.vue @@ -13,6 +13,7 @@ import { GlTooltipDirective, GlLink, } from '@gitlab/ui'; +import { omit } from 'lodash'; import { s__, __ } from '~/locale'; import { createAlert } from '~/alert'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; @@ -20,6 +21,7 @@ import { logError } from '~/lib/logger'; import { helpPagePath } from '~/helpers/help_page_helper'; import RefSelector from '~/ref/components/ref_selector.vue'; import GetProjectDetailsQuery from '../../common/components/get_project_details_query.vue'; +import WorkspaceVariables from '../components/workspace_variables.vue'; import SearchProjectsListbox from '../components/search_projects_listbox.vue'; import workspaceCreateMutation from '../graphql/mutations/workspace_create.mutation.graphql'; import { addWorkspace } from '../services/apollo_cache_mutators'; @@ -92,6 +94,7 @@ export default { RefSelector, SearchProjectsListbox, GetProjectDetailsQuery, + WorkspaceVariables, }, directives: { GlTooltip: GlTooltipDirective, @@ -107,6 +110,8 @@ export default { devfilePath: DEFAULT_DEVFILE_PATH, projectId: null, maxHoursBeforeTermination: this.defaultMaxHoursBeforeTermination, + workspaceVariables: [], + showWorkspaceVariableValidations: false, projectDetailsLoaded: false, error: '', }; @@ -178,7 +183,16 @@ export default { this.selectedAgent = null; this.projectDetailsLoaded = false; }, + validateWorkspaceVariables() { + this.showWorkspaceVariableValidations = true; + return this.workspaceVariables.every((variable) => { + return variable.valid === true; + }); + }, async createWorkspace() { + if (!this.validateWorkspaceVariables()) { + return; + } try { this.isCreatingWorkspace = true; @@ -194,6 +208,7 @@ export default { devfileRef: this.devfileRef, devfilePath: this.devfilePath, maxHoursBeforeTermination: parseInt(this.maxHoursBeforeTermination, 10), + variables: this.workspaceVariables.map((v) => omit(v, 'valid')), }, }, update(store, { data }) { @@ -242,9 +257,9 @@ export default { }; -
+
{{ $options.i18n.submitButton.create }} - + {{ $options.i18n.cancelButton }}
diff --git a/ee/app/controllers/remote_development/workspaces_feature_flag_controller.rb b/ee/app/controllers/remote_development/workspaces_feature_flag_controller.rb index eb159ef1fd84bb..c4313e48fa6f8a 100644 --- a/ee/app/controllers/remote_development/workspaces_feature_flag_controller.rb +++ b/ee/app/controllers/remote_development/workspaces_feature_flag_controller.rb @@ -15,11 +15,11 @@ class WorkspacesFeatureFlagController < ApplicationController ].freeze def show - flag = params[:flag] + flag = permitted_params[:flag] return render json: { enabled: false } unless ALLOWED_FLAGS.include?(flag.to_s) - namespace_id = params[:namespace_id] + namespace_id = permitted_params[:namespace_id] namespace = ::Namespace.find_by_id(namespace_id) return render json: { enabled: false } unless namespace @@ -30,5 +30,11 @@ def show render json: { enabled: false } end end + + private + + def permitted_params + params.permit(:flag, :namespace_id) + end end end diff --git a/ee/app/graphql/mutations/remote_development/workspaces/create.rb b/ee/app/graphql/mutations/remote_development/workspaces/create.rb index bda19dc4d44aa6..623ce4bd9fba2e 100644 --- a/ee/app/graphql/mutations/remote_development/workspaces/create.rb +++ b/ee/app/graphql/mutations/remote_development/workspaces/create.rb @@ -51,6 +51,12 @@ class Create < BaseMutation required: true, description: 'Project repo git path containing the devfile used to configure the workspace.' + argument :variables, [::Types::RemoteDevelopment::WorkspaceVariableInput], + required: false, + default_value: [], + replace_null_with_default: true, + description: 'Variables to inject into the workspace.' + def resolve(args) unless License.feature_available?(:remote_development) raise_resource_not_available_error!("'remote_development' licensed feature is not available") @@ -110,7 +116,13 @@ def resolve(args) track_usage_event(:users_creating_workspaces, current_user.id) service = ::RemoteDevelopment::Workspaces::CreateService.new(current_user: current_user) - params = args.merge(agent: agent, user: current_user, project: project) + variables = args.fetch(:variables, []).map(&:to_h) + params = args.merge( + agent: agent, + user: current_user, + project: project, + variables: variables + ) response = service.execute(params: params) response_object = response.success? ? response.payload[:workspace] : nil diff --git a/ee/app/graphql/types/remote_development/workspace_variable_input.rb b/ee/app/graphql/types/remote_development/workspace_variable_input.rb new file mode 100644 index 00000000000000..349bceaaac6060 --- /dev/null +++ b/ee/app/graphql/types/remote_development/workspace_variable_input.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Types + module RemoteDevelopment + class WorkspaceVariableInput < BaseInputObject + graphql_name 'WorkspaceVariableInput' + description 'Attributes for defining a variable to be injected in a workspace.' + + # do not allow empty values. also validate that the key contains only alphanumeric characters, -, _ or . + # https://kubernetes.io/docs/concepts/configuration/secret/#restriction-names-data + argument :key, GraphQL::Types::String, + description: 'Key of the variable.', + validates: { + allow_blank: false, + format: { with: /\A[a-zA-Z0-9\-_.]+\z/, message: 'must contain only alphanumeric characters, -, _ or .' } + } + argument :type, Types::RemoteDevelopment::WorkspaceVariableInputTypeEnum, + description: 'Type of the variable to be injected in a workspace.' + argument :value, GraphQL::Types::String, description: 'Value of the variable.' + end + end +end diff --git a/ee/app/graphql/types/remote_development/workspace_variable_input_type_enum.rb b/ee/app/graphql/types/remote_development/workspace_variable_input_type_enum.rb new file mode 100644 index 00000000000000..88de95f8c27586 --- /dev/null +++ b/ee/app/graphql/types/remote_development/workspace_variable_input_type_enum.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Types + module RemoteDevelopment + class WorkspaceVariableInputTypeEnum < BaseEnum + graphql_name 'WorkspaceVariableInputType' + description 'Enum for the type of the variable to be injected in a workspace.' + + from_rails_enum( + ::RemoteDevelopment::Enums::Workspace::WORKSPACE_VARIABLE_TYPES_FOR_GRAPHQL, + description: "#{%(name).capitalize} type." + ) + end + end +end diff --git a/ee/app/models/remote_development/enums/workspace.rb b/ee/app/models/remote_development/enums/workspace.rb new file mode 100644 index 00000000000000..225fa88a004fac --- /dev/null +++ b/ee/app/models/remote_development/enums/workspace.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module RemoteDevelopment + module Enums + module Workspace + extend ActiveSupport::Concern + + WORKSPACE_VARIABLE_TYPES = { + environment: 0, + file: 1 + }.freeze + + # TODO: Add support for file variables in GraphQL - https://gitlab.com/gitlab-org/gitlab/-/issues/465979 + WORKSPACE_VARIABLE_TYPES_FOR_GRAPHQL = WORKSPACE_VARIABLE_TYPES + .except(:file) + .transform_keys { |key| key.to_s.upcase } + .freeze + end + end +end diff --git a/ee/app/models/remote_development/workspace_variable.rb b/ee/app/models/remote_development/workspace_variable.rb index 5ac49cfc2547e3..30466b091159b6 100644 --- a/ee/app/models/remote_development/workspace_variable.rb +++ b/ee/app/models/remote_development/workspace_variable.rb @@ -5,26 +5,24 @@ class WorkspaceVariable < ApplicationRecord belongs_to :workspace, class_name: 'RemoteDevelopment::Workspace', inverse_of: :workspace_variables validates :variable_type, presence: true, inclusion: { - in: [ - RemoteDevelopment::Workspaces::Create::WorkspaceVariables::VARIABLE_TYPE_ENV_VAR, - RemoteDevelopment::Workspaces::Create::WorkspaceVariables::VARIABLE_TYPE_FILE - ] + in: RemoteDevelopment::Enums::Workspace::WORKSPACE_VARIABLE_TYPES.values } validates :encrypted_value, presence: true validates :key, presence: true, length: { maximum: 255 } - scope :with_variable_type_env_var, -> { - where(variable_type: RemoteDevelopment::Workspaces::Create::WorkspaceVariables::VARIABLE_TYPE_ENV_VAR) + scope :with_variable_type_environment, -> { + where(variable_type: RemoteDevelopment::Enums::Workspace::WORKSPACE_VARIABLE_TYPES[:environment]) } scope :with_variable_type_file, -> { - where(variable_type: RemoteDevelopment::Workspaces::Create::WorkspaceVariables::VARIABLE_TYPE_FILE) + where(variable_type: RemoteDevelopment::Enums::Workspace::WORKSPACE_VARIABLE_TYPES[:file]) } attr_encrypted :value, mode: :per_attribute_iv, key: ::Settings.attr_encrypted_db_key_base_32, - algorithm: 'aes-256-gcm' + algorithm: 'aes-256-gcm', + allow_empty_value: true end end diff --git a/ee/lib/remote_development/workspaces/create/workspace_creator.rb b/ee/lib/remote_development/workspaces/create/workspace_creator.rb index a578fd7cf5fdb4..596b6c98f14dce 100644 --- a/ee/lib/remote_development/workspaces/create/workspace_creator.rb +++ b/ee/lib/remote_development/workspaces/create/workspace_creator.rb @@ -26,25 +26,42 @@ def self.create(context) path: String => workspace_root, } params => { - project: Project => project, + desired_state: String => desired_state, + editor: String => editor, + max_hours_before_termination: Integer => max_hours_before_termination, + devfile_ref: String => devfile_ref, + devfile_path: String => devfile_path, agent: Clusters::Agent => agent, + user: User => user, + project: Project => project, } project_dir = "#{workspace_root}/#{project.path}" - workspace = RemoteDevelopment::Workspace.new(params) + workspace = RemoteDevelopment::Workspace.new workspace.name = workspace_name workspace.namespace = workspace_namespace - workspace.personal_access_token = personal_access_token - workspace.devfile = devfile_yaml - workspace.processed_devfile = YAML.dump(processed_devfile.deep_stringify_keys) + workspace.desired_state = desired_state workspace.actual_state = CREATION_REQUESTED workspace.config_version = RemoteDevelopment::Workspaces::ConfigVersion::LATEST_VERSION + workspace.editor = editor + workspace.max_hours_before_termination = max_hours_before_termination + workspace.devfile_ref = devfile_ref + workspace.devfile_path = devfile_path + workspace.devfile = devfile_yaml + workspace.processed_devfile = YAML.dump(processed_devfile.deep_stringify_keys) set_workspace_url( workspace: workspace, agent_dns_zone: agent.remote_development_agent_config.dns_zone, project_dir: project_dir ) + + # associations for workspace + workspace.user = user + workspace.project = project + workspace.agent = agent + workspace.personal_access_token = personal_access_token + workspace.save if workspace.errors.present? diff --git a/ee/lib/remote_development/workspaces/create/workspace_variables.rb b/ee/lib/remote_development/workspaces/create/workspace_variables.rb index ad529b4671517b..fd4eebcc7546cd 100644 --- a/ee/lib/remote_development/workspaces/create/workspace_variables.rb +++ b/ee/lib/remote_development/workspaces/create/workspace_variables.rb @@ -4,8 +4,6 @@ module RemoteDevelopment module Workspaces module Create class WorkspaceVariables - VARIABLE_TYPE_ENV_VAR = 0 - VARIABLE_TYPE_FILE = 1 GIT_CREDENTIAL_STORE_SCRIPT = <<~SH.chomp #!/bin/sh # This is a readonly store so we can exit cleanly when git attempts a store or erase action @@ -34,9 +32,10 @@ class WorkspaceVariables # @param [String] user_email # @param [Integer] workspace_id # @param [Hash] settings + # @param [Array] variables # @return [Array] def self.variables( - name:, dns_zone:, personal_access_token_value:, user_name:, user_email:, workspace_id:, settings: + name:, dns_zone:, personal_access_token_value:, user_name:, user_email:, workspace_id:, settings:, variables: ) settings => { vscode_extensions_gallery: Hash => vscode_extensions_gallery } vscode_extensions_gallery => { @@ -45,98 +44,109 @@ def self.variables( resource_url_template: String => vscode_extensions_gallery_resource_url_template, } - [ + static_variables = [ { key: File.basename(RemoteDevelopment::Workspaces::FileMounts::GITLAB_TOKEN_FILE), value: personal_access_token_value, - variable_type: VARIABLE_TYPE_FILE, + variable_type: RemoteDevelopment::Enums::Workspace::WORKSPACE_VARIABLE_TYPES[:file], workspace_id: workspace_id }, { key: File.basename(RemoteDevelopment::Workspaces::FileMounts::GITLAB_GIT_CREDENTIAL_STORE_FILE), value: GIT_CREDENTIAL_STORE_SCRIPT, - variable_type: VARIABLE_TYPE_FILE, + variable_type: RemoteDevelopment::Enums::Workspace::WORKSPACE_VARIABLE_TYPES[:file], workspace_id: workspace_id }, { key: 'GIT_CONFIG_COUNT', value: '3', - variable_type: VARIABLE_TYPE_ENV_VAR, + variable_type: RemoteDevelopment::Enums::Workspace::WORKSPACE_VARIABLE_TYPES[:environment], workspace_id: workspace_id }, { key: 'GIT_CONFIG_KEY_0', value: "credential.helper", - variable_type: VARIABLE_TYPE_ENV_VAR, + variable_type: RemoteDevelopment::Enums::Workspace::WORKSPACE_VARIABLE_TYPES[:environment], workspace_id: workspace_id }, { key: 'GIT_CONFIG_VALUE_0', value: RemoteDevelopment::Workspaces::FileMounts::GITLAB_GIT_CREDENTIAL_STORE_FILE, - variable_type: VARIABLE_TYPE_ENV_VAR, + variable_type: RemoteDevelopment::Enums::Workspace::WORKSPACE_VARIABLE_TYPES[:environment], workspace_id: workspace_id }, { key: 'GIT_CONFIG_KEY_1', value: "user.name", - variable_type: VARIABLE_TYPE_ENV_VAR, + variable_type: RemoteDevelopment::Enums::Workspace::WORKSPACE_VARIABLE_TYPES[:environment], workspace_id: workspace_id }, { key: 'GIT_CONFIG_VALUE_1', value: user_name, - variable_type: VARIABLE_TYPE_ENV_VAR, + variable_type: RemoteDevelopment::Enums::Workspace::WORKSPACE_VARIABLE_TYPES[:environment], workspace_id: workspace_id }, { key: 'GIT_CONFIG_KEY_2', value: "user.email", - variable_type: VARIABLE_TYPE_ENV_VAR, + variable_type: RemoteDevelopment::Enums::Workspace::WORKSPACE_VARIABLE_TYPES[:environment], workspace_id: workspace_id }, { key: 'GIT_CONFIG_VALUE_2', value: user_email, - variable_type: VARIABLE_TYPE_ENV_VAR, + variable_type: RemoteDevelopment::Enums::Workspace::WORKSPACE_VARIABLE_TYPES[:environment], workspace_id: workspace_id }, { key: 'GL_GIT_CREDENTIAL_STORE_FILE_PATH', value: RemoteDevelopment::Workspaces::FileMounts::GITLAB_GIT_CREDENTIAL_STORE_FILE, - variable_type: VARIABLE_TYPE_ENV_VAR, + variable_type: RemoteDevelopment::Enums::Workspace::WORKSPACE_VARIABLE_TYPES[:environment], workspace_id: workspace_id }, { key: 'GL_TOKEN_FILE_PATH', value: RemoteDevelopment::Workspaces::FileMounts::GITLAB_TOKEN_FILE, - variable_type: VARIABLE_TYPE_ENV_VAR, + variable_type: RemoteDevelopment::Enums::Workspace::WORKSPACE_VARIABLE_TYPES[:environment], workspace_id: workspace_id }, { key: 'GL_WORKSPACE_DOMAIN_TEMPLATE', value: "${PORT}-#{name}.#{dns_zone}", - variable_type: VARIABLE_TYPE_ENV_VAR, + variable_type: RemoteDevelopment::Enums::Workspace::WORKSPACE_VARIABLE_TYPES[:environment], workspace_id: workspace_id }, { key: 'GL_EDITOR_EXTENSIONS_GALLERY_SERVICE_URL', value: vscode_extensions_gallery_service_url, - variable_type: VARIABLE_TYPE_ENV_VAR, + variable_type: RemoteDevelopment::Enums::Workspace::WORKSPACE_VARIABLE_TYPES[:environment], workspace_id: workspace_id }, { key: 'GL_EDITOR_EXTENSIONS_GALLERY_ITEM_URL', value: vscode_extensions_gallery_item_url, - variable_type: VARIABLE_TYPE_ENV_VAR, + variable_type: RemoteDevelopment::Enums::Workspace::WORKSPACE_VARIABLE_TYPES[:environment], workspace_id: workspace_id }, { key: 'GL_EDITOR_EXTENSIONS_GALLERY_RESOURCE_URL_TEMPLATE', value: vscode_extensions_gallery_resource_url_template, - variable_type: VARIABLE_TYPE_ENV_VAR, + variable_type: RemoteDevelopment::Enums::Workspace::WORKSPACE_VARIABLE_TYPES[:environment], workspace_id: workspace_id } ] + + user_provided_variables = variables.map do |variable| + { + key: variable.fetch(:key), + value: variable.fetch(:value), + variable_type: variable.fetch(:type), + workspace_id: workspace_id + } + end + + static_variables + user_provided_variables end end end diff --git a/ee/lib/remote_development/workspaces/create/workspace_variables_creator.rb b/ee/lib/remote_development/workspaces/create/workspace_variables_creator.rb index e5cb5a45e27b6c..f9b6f3b702cb5b 100644 --- a/ee/lib/remote_development/workspaces/create/workspace_variables_creator.rb +++ b/ee/lib/remote_development/workspaces/create/workspace_variables_creator.rb @@ -13,19 +13,27 @@ def self.create(context) workspace: RemoteDevelopment::Workspace => workspace, personal_access_token: PersonalAccessToken => personal_access_token, current_user: User => user, - settings: Hash => settings + settings: Hash => settings, + params: Hash => params } - workspace_variables_params = WorkspaceVariables.variables( + params => { + variables: Array => user_provided_variables + } + # When we have the ability to define variables for workspaces + # at project/group/instance level, add them here. + variables = user_provided_variables + workspace_variables = WorkspaceVariables.variables( name: workspace.name, dns_zone: workspace.dns_zone, personal_access_token_value: personal_access_token.token, user_name: user.name, user_email: user.email, workspace_id: workspace.id, - settings: settings + settings: settings, + variables: variables ) - workspace_variables_params.each do |workspace_variable_params| + workspace_variables.each do |workspace_variable_params| workspace_variable = RemoteDevelopment::WorkspaceVariable.new(workspace_variable_params) workspace_variable.save diff --git a/ee/lib/remote_development/workspaces/reconcile/output/desired_config_generator.rb b/ee/lib/remote_development/workspaces/reconcile/output/desired_config_generator.rb index 6ea5007cc740a7..dd5d1ccb550b6c 100644 --- a/ee/lib/remote_development/workspaces/reconcile/output/desired_config_generator.rb +++ b/ee/lib/remote_development/workspaces/reconcile/output/desired_config_generator.rb @@ -13,6 +13,9 @@ class DesiredConfigGenerator # @return [Array] def self.generate_desired_config(workspace:, include_all_resources:, logger:) desired_config = [] + # NOTE: update env_secret_name to "#{workspace.name}-environment". This is to ensure naming consistency. + # Changing it now would require migration from old config version to a new one. + # Update this when a new desired config generator is created for some other reason. env_secret_name = "#{workspace.name}-env-var" file_secret_name = "#{workspace.name}-file" env_secret_names = [env_secret_name] @@ -129,16 +132,16 @@ def self.get_k8s_resources_for_secrets( agent_id: workspace.agent.id ) - data_for_env_var = workspace.workspace_variables.with_variable_type_env_var - data_for_env_var = data_for_env_var.each_with_object({}) do |workspace_variable, hash| + data_for_environment = workspace.workspace_variables.with_variable_type_environment + data_for_environment = data_for_environment.each_with_object({}) do |workspace_variable, hash| hash[workspace_variable.key] = workspace_variable.value end - k8s_secret_for_env_var = get_secret( + k8s_secret_for_environment = get_secret( name: env_secret_name, namespace: workspace.namespace, labels: labels, annotations: annotations, - data: data_for_env_var + data: data_for_environment ) data_for_file = workspace.workspace_variables.with_variable_type_file @@ -153,7 +156,7 @@ def self.get_k8s_resources_for_secrets( data: data_for_file ) - [k8s_inventory, k8s_secret_for_env_var, k8s_secret_for_file] + [k8s_inventory, k8s_secret_for_environment, k8s_secret_for_file] end # @param [String] desired_state diff --git a/ee/lib/remote_development/workspaces/reconcile/output/desired_config_generator_v2.rb b/ee/lib/remote_development/workspaces/reconcile/output/desired_config_generator_v2.rb index 61341c477822ef..6caa9f947c7474 100644 --- a/ee/lib/remote_development/workspaces/reconcile/output/desired_config_generator_v2.rb +++ b/ee/lib/remote_development/workspaces/reconcile/output/desired_config_generator_v2.rb @@ -102,16 +102,16 @@ def self.get_k8s_resources_for_secrets(workspace:, env_secret_name:, file_secret agent_id: workspace.agent.id ) - data_for_env_var = workspace.workspace_variables.with_variable_type_env_var - data_for_env_var = data_for_env_var.each_with_object({}) do |workspace_variable, hash| + data_for_environment = workspace.workspace_variables.with_variable_type_environment + data_for_environment = data_for_environment.each_with_object({}) do |workspace_variable, hash| hash[workspace_variable.key] = workspace_variable.value end - k8s_secret_for_env_var = get_secret( + k8s_secret_for_environment = get_secret( name: env_secret_name, namespace: workspace.namespace, labels: labels, annotations: annotations, - data: data_for_env_var + data: data_for_environment ) data_for_file = workspace.workspace_variables.with_variable_type_file @@ -126,7 +126,7 @@ def self.get_k8s_resources_for_secrets(workspace:, env_secret_name:, file_secret data: data_for_file ) - [k8s_inventory, k8s_secret_for_env_var, k8s_secret_for_file] + [k8s_inventory, k8s_secret_for_environment, k8s_secret_for_file] end # @param [String] desired_state diff --git a/ee/spec/factories/remote_development/workspace_variables.rb b/ee/spec/factories/remote_development/workspace_variables.rb index 8f3c64c35c8c7b..04b4ed3dcbffb1 100644 --- a/ee/spec/factories/remote_development/workspace_variables.rb +++ b/ee/spec/factories/remote_development/workspace_variables.rb @@ -6,6 +6,6 @@ key { 'my_key' } value { 'my_value' } - variable_type { RemoteDevelopment::Workspaces::Create::WorkspaceVariables::VARIABLE_TYPE_FILE } + variable_type { RemoteDevelopment::Enums::Workspace::WORKSPACE_VARIABLE_TYPES[:file] } end end diff --git a/ee/spec/factories/remote_development/workspaces.rb b/ee/spec/factories/remote_development/workspaces.rb index f9c44c966b427f..03870a22d4c972 100644 --- a/ee/spec/factories/remote_development/workspaces.rb +++ b/ee/spec/factories/remote_development/workspaces.rb @@ -89,7 +89,8 @@ item_url: "https://open-vsx.org/vscode/item", resource_url_template: "https://open-vsx.org/api/{publisher}/{name}/{version}/file/{path}" } - } + }, + variables: [] ) workspace_variables.each do |workspace_variable| diff --git a/ee/spec/features/remote_development/workspaces_spec.rb b/ee/spec/features/remote_development/workspaces_spec.rb index ed2a666e3ffe5f..00d00bcccf0e1d 100644 --- a/ee/spec/features/remote_development/workspaces_spec.rb +++ b/ee/spec/features/remote_development/workspaces_spec.rb @@ -22,6 +22,8 @@ let_it_be(:agent_token) { create(:cluster_agent_token, agent: agent, created_by_user: user) } let(:reconcile_url) { capybara_url(api('/internal/kubernetes/modules/remote_development/reconcile', user)) } + let(:variable_key) { "VAR1" } + let(:variable_value) { "value 1" } before do stub_licensed_features(remote_development: true) @@ -57,6 +59,9 @@ # noinspection RubyMismatchedArgumentType -- Rubymine is finding the wrong `select` select agent.name, from: 'Cluster agent' fill_in 'Workspace automatically terminates after', with: '20' + click_button 'Add variable' + fill_in 'Variable Key', with: variable_key + fill_in 'Variable Value', with: variable_value click_button 'Create workspace' # We look for the project GID because that's all we know about the workspace at this point. For the new UI, diff --git a/ee/spec/frontend/workspaces/user/components/workspace_variables_spec.js b/ee/spec/frontend/workspaces/user/components/workspace_variables_spec.js new file mode 100644 index 00000000000000..aa039d5d2646d4 --- /dev/null +++ b/ee/spec/frontend/workspaces/user/components/workspace_variables_spec.js @@ -0,0 +1,219 @@ +import { GlCard, GlTable, GlButton, GlFormInput, GlFormGroup } from '@gitlab/ui'; +import WorkspaceVariables from 'ee/workspaces/user/components/workspace_variables.vue'; +import { stubComponent } from 'helpers/stub_component'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import { extendedWrapper, mountExtended } from 'helpers/vue_test_utils_helper'; +import { WORKSPACE_VARIABLE_INPUT_TYPE_ENUM } from 'ee/workspaces/user/constants'; + +describe('workspaces/user/components/workspace_variables.vue', () => { + let wrapper; + let mockApollo; + + const buildMockApollo = () => { + mockApollo = createMockApollo([]); + }; + + const GlFormGroupStub = stubComponent(GlFormGroup, { + props: { + ...GlFormGroup.props, + state: { + type: Boolean, + required: false, + default: undefined, + }, + }, + }); + + const buildWrapper = ({ mountFn = mountExtended, variables, showValidations = false } = {}) => { + // noinspection JSCheckFunctionSignatures - TODO: Address in https://gitlab.com/gitlab-org/gitlab/-/issues/437600 + wrapper = mountFn(WorkspaceVariables, { + apolloProvider: mockApollo, + propsData: { + variables, + showValidations, + }, + stubs: { + GlTable, + GlCard, + GlButton, + GlFormInput, + GlFormGroup: GlFormGroupStub, + }, + }); + }; + + const findCard = () => extendedWrapper(wrapper.findComponent(GlCard)); + const findTable = () => extendedWrapper(wrapper.findComponent(GlTable)); + const findAddButton = () => + extendedWrapper(wrapper.findByRole('button', { name: /Add variable/i })); + + beforeEach(() => { + buildMockApollo(); + }); + + it('renders table with empty state', () => { + const variables = []; + buildWrapper({ variables }); + expect(findCard().props()).toMatchObject({ + bodyClass: expect.stringContaining('gl-new-card-body'), + footerClass: expect.stringContaining(''), + headerClass: expect.stringContaining(''), + }); + expect(findCard().text()).toContain('Variables'); + expect(findTable().text()).toContain('No variables'); + expect(findAddButton().exists()).toBe(true); + }); + + it('renders table with variables', () => { + const variables = [ + { + key: 'foo1', + value: 'bar1', + type: WORKSPACE_VARIABLE_INPUT_TYPE_ENUM.env, + valid: false, + }, + { + key: 'foo2', + value: 'bar2', + type: WORKSPACE_VARIABLE_INPUT_TYPE_ENUM.env, + valid: false, + }, + ]; + buildWrapper({ variables }); + + const keys = wrapper.findAllByTestId('key'); + const values = wrapper.findAllByTestId('value'); + const removeButtons = wrapper.findAllByTestId('remove-variable'); + variables.forEach((variable, index) => { + expect(keys.at(index).element.value).toBe(variable.key); + expect(values.at(index).element.value).toBe(variable.value); + expect(removeButtons.at(index).exists()).toBe(true); + }); + }); + + it('adds a new variable', () => { + const variables = [ + { + key: 'foo1', + value: 'bar1', + type: WORKSPACE_VARIABLE_INPUT_TYPE_ENUM.env, + valid: false, + }, + ]; + buildWrapper({ variables }); + + findAddButton().vm.$emit('click'); + const emittedEvents = wrapper.emitted(); + expect(emittedEvents.addVariable).toHaveLength(1); + expect(emittedEvents.input).toMatchObject([ + [[...variables, { key: '', value: '', valid: false }]], + ]); + }); + + it('removes a variable', () => { + const variables = [ + { + key: 'foo1', + value: 'bar1', + type: WORKSPACE_VARIABLE_INPUT_TYPE_ENUM.env, + valid: false, + }, + { + key: 'foo2', + value: 'bar2', + type: WORKSPACE_VARIABLE_INPUT_TYPE_ENUM.env, + valid: false, + }, + { + key: 'foo3', + value: 'bar3', + type: WORKSPACE_VARIABLE_INPUT_TYPE_ENUM.env, + valid: false, + }, + ]; + buildWrapper({ variables }); + + wrapper.findAllByTestId('remove-variable').at(1).vm.$emit('click'); + const emittedEvents = wrapper.emitted(); + expect(emittedEvents.input).toMatchObject([[[...variables.toSpliced(1, 1)]]]); + }); + + it('updates a variable key', async () => { + const variables = [ + { + key: '', + value: '', + type: WORKSPACE_VARIABLE_INPUT_TYPE_ENUM.env, + valid: false, + }, + ]; + const expectedVariables = [ + { + key: 'foo1', + value: '', + type: WORKSPACE_VARIABLE_INPUT_TYPE_ENUM.env, + valid: true, + }, + ]; + buildWrapper({ variables }); + + const keys = wrapper.findAllByTestId('key'); + await keys.at(0).vm.$emit('input', 'foo1'); + const emittedEvents = wrapper.emitted(); + expect(emittedEvents.input).toMatchObject([[[...expectedVariables]]]); + }); + + it('updates a variable value', async () => { + const variables = [ + { + key: '', + value: '', + type: WORKSPACE_VARIABLE_INPUT_TYPE_ENUM.env, + valid: false, + }, + ]; + const expectedVariables = [ + { + key: '', + value: 'bar1', + type: WORKSPACE_VARIABLE_INPUT_TYPE_ENUM.env, + valid: false, + }, + ]; + buildWrapper({ variables }); + + const values = wrapper.findAllByTestId('value'); + await values.at(0).vm.$emit('input', 'bar1'); + const emittedEvents = wrapper.emitted(); + expect(emittedEvents.input).toMatchObject([[[...expectedVariables]]]); + }); + + it('shows validations', () => { + const variables = [ + { + key: '', + value: '', + type: WORKSPACE_VARIABLE_INPUT_TYPE_ENUM.env, + valid: false, + }, + { + key: 'foo2', + value: '', + type: WORKSPACE_VARIABLE_INPUT_TYPE_ENUM.env, + valid: true, + }, + ]; + const showValidations = true; + buildWrapper({ variables, showValidations }); + + variables.forEach((variable, index) => { + const row = findTable().findAll('tbody tr').at(index); + const formGroups = row.findAllComponents(GlFormGroupStub); + const [keyFormGroup, valueFormGroup] = formGroups.wrappers; + + expect(keyFormGroup.props().state).toBe(variable.valid); + // Value form group is not validated + expect(valueFormGroup.props().state).toBe(undefined); + }); + }); +}); diff --git a/ee/spec/frontend/workspaces/user/pages/create_spec.js b/ee/spec/frontend/workspaces/user/pages/create_spec.js index e01ee32a3c097c..4f8e52a4677960 100644 --- a/ee/spec/frontend/workspaces/user/pages/create_spec.js +++ b/ee/spec/frontend/workspaces/user/pages/create_spec.js @@ -14,6 +14,7 @@ import { import RefSelector from '~/ref/components/ref_selector.vue'; import SearchProjectsListbox from 'ee/workspaces/user/components/search_projects_listbox.vue'; import GetProjectDetailsQuery from 'ee/workspaces/common/components/get_project_details_query.vue'; +import WorkspaceVariables from 'ee/workspaces/user/components/workspace_variables.vue'; import WorkspaceCreate, { devfileHelpPath, i18n } from 'ee/workspaces/user/pages/create.vue'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { stubComponent } from 'helpers/stub_component'; @@ -23,6 +24,7 @@ import { DEFAULT_DESIRED_STATE, DEFAULT_EDITOR, ROUTES, + WORKSPACE_VARIABLE_INPUT_TYPE_ENUM, WORKSPACES_LIST_PAGE_SIZE, } from 'ee/workspaces/user/constants'; import waitForPromises from 'helpers/wait_for_promises'; @@ -205,6 +207,7 @@ describe('workspaces/user/pages/create.vue', () => { findClusterAgentsFormSelect().vm.$emit('input', selectedClusterAgentIDFixture); const submitCreateWorkspaceForm = () => wrapper.findComponent(GlForm).vm.$emit('submit', { preventDefault: jest.fn() }); + const findWorkspaceVariables = () => wrapper.findComponent(WorkspaceVariables); beforeEach(() => { buildMockApollo(); @@ -331,6 +334,13 @@ describe('workspaces/user/pages/create.vue', () => { }); }); + it('renders workspace variables component', () => { + expect(findWorkspaceVariables().props()).toMatchObject({ + variables: [], + showValidations: false, + }); + }); + describe('when selecting a project again', () => { beforeEach(async () => { await selectProject({ nameWithNamespace: 'New Project', fullPath: 'new-project' }); @@ -367,6 +377,7 @@ describe('workspaces/user/pages/create.vue', () => { devfilePath, maxHoursBeforeTermination, devfileRef, + variables: findWorkspaceVariables().props().variables, }, }); }); @@ -377,6 +388,33 @@ describe('workspaces/user/pages/create.vue', () => { expect(findCreateWorkspaceButton().props().loading).toBe(true); }); + it('displays workspace variables validations', async () => { + expect(findWorkspaceVariables().props().showValidations).toBe(false); + + await submitCreateWorkspaceForm(); + + expect(findWorkspaceVariables().props().showValidations).toBe(true); + }); + + describe('when workspace variables are not valid', () => { + it('does not submit the Create Workspace mutation', async () => { + const variables = [ + { + key: '', + value: '', + type: WORKSPACE_VARIABLE_INPUT_TYPE_ENUM.env, + valid: false, + }, + ]; + + await findWorkspaceVariables().vm.$emit('input', variables); + + await submitCreateWorkspaceForm(); + + expect(workspaceCreateMutationHandler).not.toHaveBeenCalled(); + }); + }); + describe('when the workspaceCreate mutation succeeds', () => { it('when workspaces are not previously cached, does not update cache', async () => { await submitCreateWorkspaceForm(); diff --git a/ee/spec/graphql/types/remote_development/workspace_variable_input_spec.rb b/ee/spec/graphql/types/remote_development/workspace_variable_input_spec.rb new file mode 100644 index 00000000000000..cde8ac890243f2 --- /dev/null +++ b/ee/spec/graphql/types/remote_development/workspace_variable_input_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['WorkspaceVariableInput'], feature_category: :remote_development do + let(:arguments) do + %i[ + key + type + value + ] + end + + specify { expect(described_class.graphql_name).to eq('WorkspaceVariableInput') } + specify { expect(described_class.arguments.deep_symbolize_keys.keys).to(match_array(arguments)) } + specify { expect(described_class).to have_graphql_arguments(arguments) } +end diff --git a/ee/spec/lib/remote_development/agent_config/main_spec.rb b/ee/spec/lib/remote_development/agent_config/main_spec.rb index 3c966d36ffe5b8..f06910fe1990f3 100644 --- a/ee/spec/lib/remote_development/agent_config/main_spec.rb +++ b/ee/spec/lib/remote_development/agent_config/main_spec.rb @@ -2,7 +2,7 @@ require_relative '../rd_fast_spec_helper' -RSpec.describe RemoteDevelopment::AgentConfig::Main, :rd_fast, feature_category: :remote_development do # rubocop:disable RSpec/EmptyExampleGroup -- the context blocks are dynamically generated +RSpec.describe RemoteDevelopment::AgentConfig::Main, :rd_fast, feature_category: :remote_development do let(:context_passed_along_steps) { {} } let(:rop_steps) do @@ -30,7 +30,6 @@ end end - # rubocop:disable Style/TrailingCommaInArrayLiteral -- let the last element have a comma for simpler diffs # rubocop:disable Layout/LineLength -- we want to avoid excessive wrapping for RSpec::Parameterized Nested Array Style so we can have formatting consistency between entries where(:case_name, :result_for_step, :expected_response) do [ @@ -43,7 +42,7 @@ { status: :success, payload: lazy { ok_message_content } - }, + } ], [ "when Updater returns AgentConfigUpdateSkippedBecauseNoConfigFileEntryFound", @@ -58,6 +57,7 @@ ] ] end + # rubocop:enable Layout/LineLength with_them do it_behaves_like "rop invocation with successful response" end @@ -81,8 +81,6 @@ end end - # rubocop:disable Style/TrailingCommaInArrayLiteral -- let the last element have a comma for simpler diffs - # rubocop:disable Layout/LineLength -- we want to avoid excessive wrapping for RSpec::Parameterized Nested Array Style so we can have formatting consistency between entries where(:case_name, :err_result_for_step, :expected_response) do [ [ @@ -95,7 +93,7 @@ status: :error, message: lazy { "License check failed: #{error_details}" }, reason: :forbidden - }, + } ], [ "when Updater returns AgentConfigUpdateFailed", @@ -107,7 +105,7 @@ status: :error, message: lazy { "Agent config update failed: #{error_details}" }, reason: :bad_request - }, + } ], [ "when an unmatched error is returned, an exception is raised", @@ -119,8 +117,6 @@ ] ] end - # rubocop:enable Style/TrailingCommaInArrayLiteral - # rubocop:enable Layout/LineLength with_them do it_behaves_like "rop invocation with error response" diff --git a/ee/spec/lib/remote_development/namespace_cluster_agent_mappings/create/main_spec.rb b/ee/spec/lib/remote_development/namespace_cluster_agent_mappings/create/main_spec.rb index 14e23853ad6c87..ea6539fd98690d 100644 --- a/ee/spec/lib/remote_development/namespace_cluster_agent_mappings/create/main_spec.rb +++ b/ee/spec/lib/remote_development/namespace_cluster_agent_mappings/create/main_spec.rb @@ -2,7 +2,7 @@ require_relative '../../rd_fast_spec_helper' -RSpec.describe RemoteDevelopment::NamespaceClusterAgentMappings::Create::Main, :rd_fast, feature_category: :remote_development do # rubocop:disable RSpec/EmptyExampleGroup -- the context blocks are dynamically generated +RSpec.describe RemoteDevelopment::NamespaceClusterAgentMappings::Create::Main, :rd_fast, feature_category: :remote_development do let(:context_passed_along_steps) { {} } let(:rop_steps) do [ diff --git a/ee/spec/lib/remote_development/namespace_cluster_agent_mappings/delete/main_spec.rb b/ee/spec/lib/remote_development/namespace_cluster_agent_mappings/delete/main_spec.rb index cd3896bfa1a297..64a1e86500793f 100644 --- a/ee/spec/lib/remote_development/namespace_cluster_agent_mappings/delete/main_spec.rb +++ b/ee/spec/lib/remote_development/namespace_cluster_agent_mappings/delete/main_spec.rb @@ -2,7 +2,7 @@ require_relative '../../rd_fast_spec_helper' -RSpec.describe RemoteDevelopment::NamespaceClusterAgentMappings::Delete::Main, :rd_fast, feature_category: :remote_development do # rubocop:disable RSpec/EmptyExampleGroup -- the context blocks are dynamically generated +RSpec.describe RemoteDevelopment::NamespaceClusterAgentMappings::Delete::Main, :rd_fast, feature_category: :remote_development do let(:context_passed_along_steps) { {} } let(:rop_steps) do [ diff --git a/ee/spec/lib/remote_development/rd_fast_spec_helper.rb b/ee/spec/lib/remote_development/rd_fast_spec_helper.rb index dc5a5bf406c07d..d860fd46ed965b 100644 --- a/ee/spec/lib/remote_development/rd_fast_spec_helper.rb +++ b/ee/spec/lib/remote_development/rd_fast_spec_helper.rb @@ -6,3 +6,4 @@ # EE-specific helper logic require_relative '../../support/shared_contexts/remote_development/agent_info_status_fixture_not_implemented_error' require_relative '../../support/shared_contexts/remote_development/remote_development_shared_contexts' +require_relative '../../../app/models/remote_development/enums/workspace' diff --git a/ee/spec/lib/remote_development/workspaces/create/creator_spec.rb b/ee/spec/lib/remote_development/workspaces/create/creator_spec.rb index 718e1207fe385f..88dada325af331 100644 --- a/ee/spec/lib/remote_development/workspaces/create/creator_spec.rb +++ b/ee/spec/lib/remote_development/workspaces/create/creator_spec.rb @@ -6,7 +6,7 @@ # rubocop:disable Rails/SaveBang -- method shadowing Messages = RemoteDevelopment::Messages -RSpec.describe ::RemoteDevelopment::Workspaces::Create::Creator, feature_category: :remote_development do # rubocop:disable RSpec/EmptyExampleGroup -- the context blocks are dynamically generated +RSpec.describe ::RemoteDevelopment::Workspaces::Create::Creator, feature_category: :remote_development do let(:rop_steps) do [ [RemoteDevelopment::Workspaces::Create::PersonalAccessTokenCreator, :and_then], @@ -82,8 +82,6 @@ end end - # rubocop:disable Style/TrailingCommaInArrayLiteral -- let the last element have a comma for simpler diffs - # rubocop:disable Layout/LineLength -- we want to avoid excessive wrapping for RSpec::Parameterized Nested Array Style so we can have formatting consistency between entries where(:case_name, :err_result_for_step, :expected_response) do [ [ @@ -112,8 +110,6 @@ ] ] end - # rubocop:enable Style/TrailingCommaInArrayLiteral - # rubocop:enable Layout/LineLength # rubocop:enable Rails/SaveBang with_them do it_behaves_like "rop invocation with error response" diff --git a/ee/spec/lib/remote_development/workspaces/create/main_integration_spec.rb b/ee/spec/lib/remote_development/workspaces/create/main_integration_spec.rb index afbe26b09c9bf9..fd23f054170461 100644 --- a/ee/spec/lib/remote_development/workspaces/create/main_integration_spec.rb +++ b/ee/spec/lib/remote_development/workspaces/create/main_integration_spec.rb @@ -21,6 +21,12 @@ let(:processed_devfile) { YAML.safe_load(example_processed_devfile).to_h } let(:editor) { 'webide' } let(:workspace_root) { '/projects' } + let(:variables) do + [ + { key: 'VAR1', value: 'value 1', type: 'ENVIRONMENT' }, + { key: 'VAR2', value: 'value 2', type: 'ENVIRONMENT' } + ] + end let(:project) do files = { devfile_path => devfile_yaml } @@ -40,7 +46,8 @@ max_hours_before_termination: 24, desired_state: RemoteDevelopment::Workspaces::States::RUNNING, devfile_ref: devfile_ref, - devfile_path: devfile_path + devfile_path: devfile_path, + variables: variables } end @@ -106,6 +113,16 @@ actual_processed_devfile = YAML.safe_load(workspace.processed_devfile).to_h expect(actual_processed_devfile).to eq(processed_devfile) + + variables.each do |variable| + expect( + RemoteDevelopment::WorkspaceVariable.where( + workspace: workspace, + key: variable[:key], + variable_type: RemoteDevelopment::Enums::Workspace::WORKSPACE_VARIABLE_TYPES_FOR_GRAPHQL[variable[:type]] + ).first&.value + ).to eq(variable[:value]) + end end end diff --git a/ee/spec/lib/remote_development/workspaces/create/workspace_variables_creator_spec.rb b/ee/spec/lib/remote_development/workspaces/create/workspace_variables_creator_spec.rb index 7f503002fd6ecd..88cb887dcb5454 100644 --- a/ee/spec/lib/remote_development/workspaces/create/workspace_variables_creator_spec.rb +++ b/ee/spec/lib/remote_development/workspaces/create/workspace_variables_creator_spec.rb @@ -11,12 +11,13 @@ let_it_be(:personal_access_token) { create(:personal_access_token, user: user) } let_it_be(:workspace) { create(:workspace, user: user, personal_access_token: personal_access_token) } let(:settings) { { some_setting: "context" } } + let(:user_provided_variables) { [{ key: "VAR1", value: "value 1" }, { key: "VAR2", value: "value 2" }] } let(:returned_workspace_variables) do [ { key: "key1", value: "value1", - variable_type: RemoteDevelopment::Workspaces::Create::WorkspaceVariables::VARIABLE_TYPE_FILE, + variable_type: RemoteDevelopment::Enums::Workspace::WORKSPACE_VARIABLE_TYPES[:file], workspace_id: workspace.id }, { @@ -36,7 +37,8 @@ user_name: user.name, user_email: user.email, workspace_id: workspace.id, - settings: settings + settings: settings, + variables: user_provided_variables } end @@ -45,7 +47,10 @@ workspace: workspace, personal_access_token: personal_access_token, current_user: user, - settings: settings + settings: settings, + params: { + variables: user_provided_variables + } } end @@ -59,7 +64,7 @@ end context 'when workspace variables create is successful' do - let(:valid_variable_type) { RemoteDevelopment::Workspaces::Create::WorkspaceVariables::VARIABLE_TYPE_ENV_VAR } + let(:valid_variable_type) { RemoteDevelopment::Enums::Workspace::WORKSPACE_VARIABLE_TYPES[:environment] } let(:variable_type) { valid_variable_type } it 'creates the workspace variable records and returns ok result containing original context' do diff --git a/ee/spec/lib/remote_development/workspaces/create/workspace_variables_spec.rb b/ee/spec/lib/remote_development/workspaces/create/workspace_variables_spec.rb index 74471ec5d24e47..56a3ab0cd163fc 100644 --- a/ee/spec/lib/remote_development/workspaces/create/workspace_variables_spec.rb +++ b/ee/spec/lib/remote_development/workspaces/create/workspace_variables_spec.rb @@ -41,91 +41,103 @@ { key: "gl_token", value: "example-pat-value", - variable_type: RemoteDevelopment::Workspaces::Create::WorkspaceVariables::VARIABLE_TYPE_FILE, + variable_type: RemoteDevelopment::Enums::Workspace::WORKSPACE_VARIABLE_TYPES[:file], workspace_id: workspace_id }, { key: "gl_git_credential_store.sh", value: git_credential_store_script, - variable_type: RemoteDevelopment::Workspaces::Create::WorkspaceVariables::VARIABLE_TYPE_FILE, + variable_type: RemoteDevelopment::Enums::Workspace::WORKSPACE_VARIABLE_TYPES[:file], workspace_id: workspace_id }, { key: "GIT_CONFIG_COUNT", value: "3", - variable_type: RemoteDevelopment::Workspaces::Create::WorkspaceVariables::VARIABLE_TYPE_ENV_VAR, + variable_type: RemoteDevelopment::Enums::Workspace::WORKSPACE_VARIABLE_TYPES[:environment], workspace_id: workspace_id }, { key: "GIT_CONFIG_KEY_0", value: "credential.helper", - variable_type: RemoteDevelopment::Workspaces::Create::WorkspaceVariables::VARIABLE_TYPE_ENV_VAR, + variable_type: RemoteDevelopment::Enums::Workspace::WORKSPACE_VARIABLE_TYPES[:environment], workspace_id: workspace_id }, { key: "GIT_CONFIG_VALUE_0", value: "/.workspace-data/variables/file/gl_git_credential_store.sh", - variable_type: RemoteDevelopment::Workspaces::Create::WorkspaceVariables::VARIABLE_TYPE_ENV_VAR, + variable_type: RemoteDevelopment::Enums::Workspace::WORKSPACE_VARIABLE_TYPES[:environment], workspace_id: workspace_id }, { key: "GIT_CONFIG_KEY_1", value: "user.name", - variable_type: RemoteDevelopment::Workspaces::Create::WorkspaceVariables::VARIABLE_TYPE_ENV_VAR, + variable_type: RemoteDevelopment::Enums::Workspace::WORKSPACE_VARIABLE_TYPES[:environment], workspace_id: workspace_id }, { key: "GIT_CONFIG_VALUE_1", value: "example.user.name", - variable_type: RemoteDevelopment::Workspaces::Create::WorkspaceVariables::VARIABLE_TYPE_ENV_VAR, + variable_type: RemoteDevelopment::Enums::Workspace::WORKSPACE_VARIABLE_TYPES[:environment], workspace_id: workspace_id }, { key: "GIT_CONFIG_KEY_2", value: "user.email", - variable_type: RemoteDevelopment::Workspaces::Create::WorkspaceVariables::VARIABLE_TYPE_ENV_VAR, + variable_type: RemoteDevelopment::Enums::Workspace::WORKSPACE_VARIABLE_TYPES[:environment], workspace_id: workspace_id }, { key: "GIT_CONFIG_VALUE_2", value: "example@user.email", - variable_type: RemoteDevelopment::Workspaces::Create::WorkspaceVariables::VARIABLE_TYPE_ENV_VAR, + variable_type: RemoteDevelopment::Enums::Workspace::WORKSPACE_VARIABLE_TYPES[:environment], workspace_id: workspace_id }, { key: "GL_GIT_CREDENTIAL_STORE_FILE_PATH", value: "/.workspace-data/variables/file/gl_git_credential_store.sh", - variable_type: RemoteDevelopment::Workspaces::Create::WorkspaceVariables::VARIABLE_TYPE_ENV_VAR, + variable_type: RemoteDevelopment::Enums::Workspace::WORKSPACE_VARIABLE_TYPES[:environment], workspace_id: workspace_id }, { key: "GL_TOKEN_FILE_PATH", value: "/.workspace-data/variables/file/gl_token", - variable_type: RemoteDevelopment::Workspaces::Create::WorkspaceVariables::VARIABLE_TYPE_ENV_VAR, + variable_type: RemoteDevelopment::Enums::Workspace::WORKSPACE_VARIABLE_TYPES[:environment], workspace_id: workspace_id }, { key: "GL_WORKSPACE_DOMAIN_TEMPLATE", value: "${PORT}-name.example.dns.zone", - variable_type: RemoteDevelopment::Workspaces::Create::WorkspaceVariables::VARIABLE_TYPE_ENV_VAR, + variable_type: RemoteDevelopment::Enums::Workspace::WORKSPACE_VARIABLE_TYPES[:environment], workspace_id: workspace_id }, { key: "GL_EDITOR_EXTENSIONS_GALLERY_SERVICE_URL", value: vscode_extensions_gallery_service_url, - variable_type: RemoteDevelopment::Workspaces::Create::WorkspaceVariables::VARIABLE_TYPE_ENV_VAR, + variable_type: RemoteDevelopment::Enums::Workspace::WORKSPACE_VARIABLE_TYPES[:environment], workspace_id: workspace_id }, { key: "GL_EDITOR_EXTENSIONS_GALLERY_ITEM_URL", value: vscode_extensions_gallery_item_url, - variable_type: RemoteDevelopment::Workspaces::Create::WorkspaceVariables::VARIABLE_TYPE_ENV_VAR, + variable_type: RemoteDevelopment::Enums::Workspace::WORKSPACE_VARIABLE_TYPES[:environment], workspace_id: workspace_id }, { key: "GL_EDITOR_EXTENSIONS_GALLERY_RESOURCE_URL_TEMPLATE", value: vscode_extensions_gallery_resource_url_template, - variable_type: RemoteDevelopment::Workspaces::Create::WorkspaceVariables::VARIABLE_TYPE_ENV_VAR, + variable_type: RemoteDevelopment::Enums::Workspace::WORKSPACE_VARIABLE_TYPES[:environment], + workspace_id: workspace_id + }, + { + key: "VAR1", + value: "value 1", + variable_type: RemoteDevelopment::Enums::Workspace::WORKSPACE_VARIABLE_TYPES[:environment], + workspace_id: workspace_id + }, + { + key: "/path/to/file", + value: "value 2", + variable_type: RemoteDevelopment::Enums::Workspace::WORKSPACE_VARIABLE_TYPES[:file], workspace_id: workspace_id } ] @@ -145,7 +157,19 @@ item_url: vscode_extensions_gallery_item_url, resource_url_template: vscode_extensions_gallery_resource_url_template } - } + }, + variables: [ + { + key: "VAR1", + value: "value 1", + type: RemoteDevelopment::Enums::Workspace::WORKSPACE_VARIABLE_TYPES[:environment] + }, + { + key: "/path/to/file", + value: "value 2", + type: RemoteDevelopment::Enums::Workspace::WORKSPACE_VARIABLE_TYPES[:file] + } + ] ) end diff --git a/ee/spec/lib/remote_development/workspaces/reconcile/input/actual_state_calculator_spec.rb b/ee/spec/lib/remote_development/workspaces/reconcile/input/actual_state_calculator_spec.rb index 14c823387c1401..b653a52e243df7 100644 --- a/ee/spec/lib/remote_development/workspaces/reconcile/input/actual_state_calculator_spec.rb +++ b/ee/spec/lib/remote_development/workspaces/reconcile/input/actual_state_calculator_spec.rb @@ -54,7 +54,7 @@ previous_actual_state: previous_actual_state, current_actual_state: current_actual_state, workspace_exists: workspace_exists, - workspace_variables_env_var: {}, + workspace_variables_environment: {}, workspace_variables_file: {} ) workspace_agent_info_hash.fetch(:latest_k8s_deployment_info).to_h diff --git a/ee/spec/lib/remote_development/workspaces/reconcile/input/factory_spec.rb b/ee/spec/lib/remote_development/workspaces/reconcile/input/factory_spec.rb index c9f0b2afa5e4ca..e6c6f17596beb2 100644 --- a/ee/spec/lib/remote_development/workspaces/reconcile/input/factory_spec.rb +++ b/ee/spec/lib/remote_development/workspaces/reconcile/input/factory_spec.rb @@ -16,7 +16,7 @@ previous_actual_state: previous_actual_state, current_actual_state: current_actual_state, workspace_exists: false, - workspace_variables_env_var: {}, + workspace_variables_environment: {}, workspace_variables_file: {} ) end diff --git a/ee/spec/lib/remote_development/workspaces/reconcile/main_integration_spec.rb b/ee/spec/lib/remote_development/workspaces/reconcile/main_integration_spec.rb index 969ec5c90d7373..f182a738811b4a 100644 --- a/ee/spec/lib/remote_development/workspaces/reconcile/main_integration_spec.rb +++ b/ee/spec/lib/remote_development/workspaces/reconcile/main_integration_spec.rb @@ -513,7 +513,7 @@ previous_actual_state: RemoteDevelopment::Workspaces::States::STOPPING, current_actual_state: RemoteDevelopment::Workspaces::States::STOPPED, workspace_exists: false, - workspace_variables_env_var: {}, + workspace_variables_environment: {}, workspace_variables_file: {} ) end diff --git a/ee/spec/lib/remote_development/workspaces/reconcile/main_spec.rb b/ee/spec/lib/remote_development/workspaces/reconcile/main_spec.rb index 1776985ce7a5ab..ba5dd6f29125cf 100644 --- a/ee/spec/lib/remote_development/workspaces/reconcile/main_spec.rb +++ b/ee/spec/lib/remote_development/workspaces/reconcile/main_spec.rb @@ -2,7 +2,7 @@ require_relative '../../rd_fast_spec_helper' -RSpec.describe RemoteDevelopment::Workspaces::Reconcile::Main, :rd_fast, feature_category: :remote_development do # rubocop:disable RSpec/EmptyExampleGroup -- the context blocks are dynamically generated +RSpec.describe RemoteDevelopment::Workspaces::Reconcile::Main, :rd_fast, feature_category: :remote_development do let(:context_passed_along_steps) { {} } let(:response_payload) do { diff --git a/ee/spec/lib/remote_development/workspaces/reconcile/output/devfile_parser_spec.rb b/ee/spec/lib/remote_development/workspaces/reconcile/output/devfile_parser_spec.rb index 46c5e65dcedaf5..cb3a5c283d99bf 100644 --- a/ee/spec/lib/remote_development/workspaces/reconcile/output/devfile_parser_spec.rb +++ b/ee/spec/lib/remote_development/workspaces/reconcile/output/devfile_parser_spec.rb @@ -26,7 +26,7 @@ end let(:domain_template) { "{{.port}}-#{workspace.name}.#{workspace.dns_zone}" } - let(:env_var_secret_name) { "#{workspace.name}-env-var" } + let(:environment_secret_name) { "#{workspace.name}-env-var" } let(:file_secret_name) { "#{workspace.name}-file" } let(:egress_ip_rules) { RemoteDevelopment::AgentConfig::Updater::NETWORK_POLICY_EGRESS_DEFAULT } let(:max_resources_per_workspace) { {} } @@ -38,7 +38,7 @@ YAML.load_stream( create_config_to_apply( workspace: workspace, - workspace_variables_env_var: {}, + workspace_variables_environment: {}, workspace_variables_file: {}, started: true, include_inventory: false, @@ -70,7 +70,7 @@ 'workspaces.gitlab.com/max-resources-per-workspace-sha256' => Digest::SHA256.hexdigest(max_resources_per_workspace.sort.to_h.to_s) }, - env_secret_names: [env_var_secret_name], + env_secret_names: [environment_secret_name], file_secret_names: [file_secret_name], default_resources_per_workspace_container: default_resources_per_workspace_container, logger: logger @@ -101,7 +101,7 @@ domain_template: "", labels: {}, annotations: {}, - env_secret_names: [env_var_secret_name], + env_secret_names: [environment_secret_name], file_secret_names: [file_secret_name], default_resources_per_workspace_container: default_resources_per_workspace_container, logger: logger diff --git a/ee/spec/lib/remote_development/workspaces/reconcile/output/devfile_parser_v2_spec.rb b/ee/spec/lib/remote_development/workspaces/reconcile/output/devfile_parser_v2_spec.rb index 4f266a1f801b5b..43ee5c21c38309 100644 --- a/ee/spec/lib/remote_development/workspaces/reconcile/output/devfile_parser_v2_spec.rb +++ b/ee/spec/lib/remote_development/workspaces/reconcile/output/devfile_parser_v2_spec.rb @@ -27,7 +27,7 @@ end let(:domain_template) { "{{.port}}-#{workspace.name}.#{workspace.dns_zone}" } - let(:env_var_secret_name) { "#{workspace.name}-env-var" } + let(:environment_secret_name) { "#{workspace.name}-env-var" } let(:file_secret_name) { "#{workspace.name}-file" } let(:egress_ip_rules) { RemoteDevelopment::AgentConfig::Updater::NETWORK_POLICY_EGRESS_DEFAULT } @@ -35,7 +35,7 @@ YAML.load_stream( create_config_to_apply_v2( workspace: workspace, - workspace_variables_env_var: {}, + workspace_variables_environment: {}, workspace_variables_file: {}, started: true, include_inventory: false, @@ -64,7 +64,7 @@ 'workspaces.gitlab.com/host-template' => domain_template, 'workspaces.gitlab.com/id' => workspace.id }, - env_secret_names: [env_var_secret_name], + env_secret_names: [environment_secret_name], file_secret_names: [file_secret_name], logger: logger ) @@ -94,7 +94,7 @@ domain_template: "", labels: {}, annotations: {}, - env_secret_names: [env_var_secret_name], + env_secret_names: [environment_secret_name], file_secret_names: [file_secret_name], logger: logger ) diff --git a/ee/spec/lib/remote_development/workspaces/update/main_spec.rb b/ee/spec/lib/remote_development/workspaces/update/main_spec.rb index e6c2b5c4d4ffbd..9e3370a8b057b8 100644 --- a/ee/spec/lib/remote_development/workspaces/update/main_spec.rb +++ b/ee/spec/lib/remote_development/workspaces/update/main_spec.rb @@ -2,7 +2,7 @@ require_relative '../../rd_fast_spec_helper' -RSpec.describe RemoteDevelopment::Workspaces::Update::Main, :rd_fast, feature_category: :remote_development do # rubocop:disable RSpec/EmptyExampleGroup -- the context blocks are dynamically generated +RSpec.describe RemoteDevelopment::Workspaces::Update::Main, :rd_fast, feature_category: :remote_development do let(:context_passed_along_steps) { {} } let(:rop_steps) do @@ -59,8 +59,6 @@ end end - # rubocop:disable Style/TrailingCommaInArrayLiteral -- let the last element have a comma for simpler diffs - # rubocop:disable Layout/LineLength -- we want to avoid excessive wrapping for RSpec::Parameterized Nested Array Style so we can have formatting consistency between entries where(:case_name, :err_result_for_step, :expected_response) do [ [ @@ -73,7 +71,7 @@ status: :error, message: lazy { "Unauthorized: #{error_details}" }, reason: :unauthorized - }, + } ], [ "when Updater returns WorkspaceUpdateFailed", @@ -85,7 +83,7 @@ status: :error, message: lazy { "Workspace update failed: #{error_details}" }, reason: :bad_request - }, + } ], [ "when an unmatched error is returned, an exception is raised", @@ -98,8 +96,6 @@ ] end - # rubocop:enable Style/TrailingCommaInArrayLiteral - # rubocop:enable Layout/LineLength with_them do it_behaves_like "rop invocation with error response" diff --git a/ee/spec/models/remote_development/workspace_variable_spec.rb b/ee/spec/models/remote_development/workspace_variable_spec.rb index dfe5af8f850d69..26f3a5ff2a286e 100644 --- a/ee/spec/models/remote_development/workspace_variable_spec.rb +++ b/ee/spec/models/remote_development/workspace_variable_spec.rb @@ -4,13 +4,14 @@ RSpec.describe RemoteDevelopment::WorkspaceVariable, feature_category: :remote_development do let(:key) { 'key_1' } - let(:value) { 'value_1' } - let(:variable_type_env_var) { RemoteDevelopment::Workspaces::Create::WorkspaceVariables::VARIABLE_TYPE_ENV_VAR } - let(:variable_type_file) { RemoteDevelopment::Workspaces::Create::WorkspaceVariables::VARIABLE_TYPE_FILE } + let(:current_value) { 'value_1' } + let(:value) { current_value } + let(:variable_type_environment) { RemoteDevelopment::Enums::Workspace::WORKSPACE_VARIABLE_TYPES[:environment] } + let(:variable_type_file) { RemoteDevelopment::Enums::Workspace::WORKSPACE_VARIABLE_TYPES[:file] } let(:variable_type) { variable_type_file } let(:variable_type_values) do [ - variable_type_env_var, + variable_type_environment, variable_type_file ] end @@ -40,14 +41,22 @@ it 'can be decrypted' do expect(workspace_variable.value).to eq(value) end + + describe 'can be empty' do + let(:current_value) { '' } + + it 'is saved' do + expect(workspace_variable.value).to eq(value) + end + end end describe 'scopes' do - describe 'with_variable_type_env_var' do - let(:variable_type) { variable_type_env_var } + describe 'with_variable_type_environment' do + let(:variable_type) { variable_type_environment } it 'returns the record' do - expect(described_class.with_variable_type_env_var).to eq([workspace_variable]) + expect(described_class.with_variable_type_environment).to eq([workspace_variable]) end end diff --git a/ee/spec/policies/remote_development/group_policy_spec.rb b/ee/spec/policies/remote_development/group_policy_spec.rb index d667532afd62e4..03270777309bd6 100644 --- a/ee/spec/policies/remote_development/group_policy_spec.rb +++ b/ee/spec/policies/remote_development/group_policy_spec.rb @@ -18,7 +18,6 @@ describe ':admin_remote_development_cluster_agent_mapping' do let(:ability) { :admin_remote_development_cluster_agent_mapping } - # rubocop:disable Layout/LineLength -- TableSyntax should not be split across lines where(:policy_class, :user, :result) do # In the future, there is a possibility that a common policy module may have to be mixed in to multiple # target policy types for ex. ProjectNamespacePolicy or UserNamespacePolicy. As a result, the policy_class @@ -35,7 +34,6 @@ GroupPolicy | ref(:admin_in_admin_mode) | true GroupPolicy | ref(:admin_in_non_admin_mode) | false end - # rubocop:enable Layout/LineLength with_them do subject(:policy) { policy_class.new(user, group) } @@ -54,7 +52,6 @@ describe ':read_remote_development_cluster_agent_mapping' do let(:ability) { :read_remote_development_cluster_agent_mapping } - # rubocop:disable Layout/LineLength -- TableSyntax should not be split across lines where(:policy_class, :user, :result) do # In the future, there is a possibility that a common policy module may have to be mixed in to multiple # target policy types for ex. ProjectNamespacePolicy or UserNamespacePolicy. As a result, the policy_class @@ -71,7 +68,6 @@ GroupPolicy | ref(:admin_in_admin_mode) | true GroupPolicy | ref(:admin_in_non_admin_mode) | false end - # rubocop:enable Layout/LineLength with_them do subject(:policy) { policy_class.new(user, group) } diff --git a/ee/spec/requests/api/graphql/mutations/remote_development/workspaces/create_spec.rb b/ee/spec/requests/api/graphql/mutations/remote_development/workspaces/create_spec.rb index 8390b647fc8fae..bacfbc0d1feabb 100644 --- a/ee/spec/requests/api/graphql/mutations/remote_development/workspaces/create_spec.rb +++ b/ee/spec/requests/api/graphql/mutations/remote_development/workspaces/create_spec.rb @@ -45,7 +45,11 @@ cluster_agent_id: agent.to_global_id.to_s, project_id: workspace_project.to_global_id.to_s, devfile_ref: 'main', - devfile_path: '.devfile.yaml' + devfile_path: '.devfile.yaml', + variables: [ + { key: 'VAR1', value: 'value 1', type: 'ENVIRONMENT' }, + { key: 'VAR2', value: 'value 2', type: 'ENVIRONMENT' } + ] } 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 8b501768acd778..54174021ba4987 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 @@ -10,7 +10,7 @@ def create_workspace_agent_info_hash( current_actual_state:, # NOTE: workspace_exists is whether the workspace exists in the cluster at the time of the current_actual_state. workspace_exists:, - workspace_variables_env_var: nil, + workspace_variables_environment: nil, workspace_variables_file: nil, resource_version: '1', dns_zone: 'workspaces.localdev.me', @@ -241,19 +241,17 @@ def create_workspace_agent_info_hash( # updatedReplicas: 1 # STATUS_YAML else - # rubocop:todo Layout/LineEndStringConcatenationIndentation -- make this cop accept standard 2-character indentation msg = 'Unsupported state transition passed for create_workspace_agent_info_hash fixture creation: ' \ "actual_state: #{previous_actual_state} -> #{current_actual_state}, " \ "existing_workspace: #{workspace_exists}" - # rubocop:enable Layout/LineEndStringConcatenationIndentation raise RemoteDevelopment::AgentInfoStatusFixtureNotImplementedError, msg end # rubocop:enable Lint/DuplicateBranch config_to_apply_yaml = create_config_to_apply( workspace: workspace, - workspace_variables_env_var: workspace_variables_env_var, + workspace_variables_environment: workspace_variables_environment, workspace_variables_file: workspace_variables_file, started: started, include_inventory: false, @@ -304,7 +302,7 @@ def create_config_to_apply(workspace:, **args) def create_config_to_apply_v3( workspace:, started:, - workspace_variables_env_var: nil, + workspace_variables_environment: nil, workspace_variables_file: nil, include_inventory: true, include_network_policy: true, @@ -380,12 +378,12 @@ def create_config_to_apply_v3( agent_id: workspace.agent.id ) - workspace_secret_env_var = workspace_secret_env_var( + workspace_secret_environment = workspace_secret_environment( workspace_name: workspace.name, workspace_namespace: workspace.namespace, labels: labels, annotations: secrets_annotations, - workspace_variables_env_var: workspace_variables_env_var || get_workspace_variables_env_var( + workspace_variables_environment: workspace_variables_environment || get_workspace_variables_environment( workspace_variables: workspace.workspace_variables ) ) @@ -416,7 +414,7 @@ def create_config_to_apply_v3( resources << workspace_network_policy if include_network_policy resources << workspace_resource_quota if include_all_resources && !max_resources_per_workspace.blank? resources << workspace_secrets_inventory if include_all_resources && include_inventory - resources << workspace_secret_env_var if include_all_resources + resources << workspace_secret_environment if include_all_resources resources << workspace_secret_file if include_all_resources resources.map do |resource| @@ -427,7 +425,7 @@ def create_config_to_apply_v3( def create_config_to_apply_v2( workspace:, started:, - workspace_variables_env_var: nil, + workspace_variables_environment: nil, workspace_variables_file: nil, include_inventory: true, include_network_policy: true, @@ -494,12 +492,12 @@ def create_config_to_apply_v2( agent_id: workspace.agent.id ) - workspace_secret_env_var = workspace_secret_env_var( + workspace_secret_environment = workspace_secret_environment( workspace_name: workspace.name, workspace_namespace: workspace.namespace, labels: labels, annotations: secrets_annotations, - workspace_variables_env_var: workspace_variables_env_var || get_workspace_variables_env_var( + workspace_variables_environment: workspace_variables_environment || get_workspace_variables_environment( workspace_variables: workspace.workspace_variables ) ) @@ -521,7 +519,7 @@ def create_config_to_apply_v2( resources << workspace_pvc resources << workspace_network_policy if include_network_policy resources << workspace_secrets_inventory if include_all_resources && include_inventory - resources << workspace_secret_env_var if include_all_resources + resources << workspace_secret_environment if include_all_resources resources << workspace_secret_file if include_all_resources resources.map do |resource| @@ -1035,30 +1033,13 @@ def workspace_secrets_inventory( } end - def workspace_secret_env_var( + def workspace_secret_environment( workspace_name:, workspace_namespace:, labels:, annotations:, - workspace_variables_env_var: + workspace_variables_environment: ) - git_config_count = workspace_variables_env_var.fetch('GIT_CONFIG_COUNT', '') - git_config_key_0 = workspace_variables_env_var.fetch('GIT_CONFIG_KEY_0', '') - git_config_value_0 = workspace_variables_env_var.fetch('GIT_CONFIG_VALUE_0', '') - git_config_key_1 = workspace_variables_env_var.fetch('GIT_CONFIG_KEY_1', '') - git_config_value_1 = workspace_variables_env_var.fetch('GIT_CONFIG_VALUE_1', '') - git_config_key_2 = workspace_variables_env_var.fetch('GIT_CONFIG_KEY_2', '') - git_config_value_2 = workspace_variables_env_var.fetch('GIT_CONFIG_VALUE_2', '') - gl_git_credential_store_file_path = workspace_variables_env_var.fetch('GL_GIT_CREDENTIAL_STORE_FILE_PATH', '') - gl_token_file_path = workspace_variables_env_var.fetch('GL_TOKEN_FILE_PATH', '') - gl_workspace_domain_template = workspace_variables_env_var.fetch('GL_WORKSPACE_DOMAIN_TEMPLATE', '') - gl_editor_extensions_gallery_service_url = - workspace_variables_env_var.fetch('GL_EDITOR_EXTENSIONS_GALLERY_SERVICE_URL', '') - gl_editor_extensions_gallery_item_url = - workspace_variables_env_var.fetch('GL_EDITOR_EXTENSIONS_GALLERY_ITEM_URL', '') - gl_editor_extensions_gallery_resource_url_template = - workspace_variables_env_var.fetch('GL_EDITOR_EXTENSIONS_GALLERY_RESOURCE_URL_TEMPLATE', '') - # TODO: figure out why there is flakiness in the order of the environment variables -- https://gitlab.com/gitlab-org/gitlab/-/issues/451934 { kind: "Secret", @@ -1069,22 +1050,7 @@ def workspace_secret_env_var( labels: labels, annotations: annotations }, - data: { - GIT_CONFIG_COUNT: Base64.strict_encode64(git_config_count).to_s, - GIT_CONFIG_KEY_0: Base64.strict_encode64(git_config_key_0).to_s, - GIT_CONFIG_VALUE_0: Base64.strict_encode64(git_config_value_0).to_s, - GIT_CONFIG_KEY_1: Base64.strict_encode64(git_config_key_1).to_s, - GIT_CONFIG_VALUE_1: Base64.strict_encode64(git_config_value_1).to_s, - GIT_CONFIG_KEY_2: Base64.strict_encode64(git_config_key_2).to_s, - GIT_CONFIG_VALUE_2: Base64.strict_encode64(git_config_value_2).to_s, - GL_GIT_CREDENTIAL_STORE_FILE_PATH: Base64.strict_encode64(gl_git_credential_store_file_path).to_s, - GL_TOKEN_FILE_PATH: Base64.strict_encode64(gl_token_file_path).to_s, - GL_WORKSPACE_DOMAIN_TEMPLATE: Base64.strict_encode64(gl_workspace_domain_template).to_s, - GL_EDITOR_EXTENSIONS_GALLERY_SERVICE_URL: Base64.strict_encode64(gl_editor_extensions_gallery_service_url).to_s, - GL_EDITOR_EXTENSIONS_GALLERY_ITEM_URL: Base64.strict_encode64(gl_editor_extensions_gallery_item_url).to_s, - GL_EDITOR_EXTENSIONS_GALLERY_RESOURCE_URL_TEMPLATE: - Base64.strict_encode64(gl_editor_extensions_gallery_resource_url_template).to_s - } + data: workspace_variables_environment.transform_values { |v| Base64.strict_encode64(v).to_s } } end @@ -1095,8 +1061,6 @@ def workspace_secret_file( annotations:, workspace_variables_file: ) - gl_git_credential_store = workspace_variables_file.fetch('gl_git_credential_store.sh', '') - gl_token = workspace_variables_file.fetch('gl_token', '') { kind: "Secret", apiVersion: "v1", @@ -1106,15 +1070,12 @@ def workspace_secret_file( labels: labels, annotations: annotations }, - data: { - gl_token: Base64.strict_encode64(gl_token).to_s, - "gl_git_credential_store.sh": Base64.strict_encode64(gl_git_credential_store).to_s - } + data: workspace_variables_file.transform_values { |v| Base64.strict_encode64(v).to_s } } end - def get_workspace_variables_env_var(workspace_variables:) - workspace_variables.with_variable_type_env_var.each_with_object({}) do |workspace_variable, hash| + def get_workspace_variables_environment(workspace_variables:) + workspace_variables.with_variable_type_environment.each_with_object({}) do |workspace_variable, hash| hash[workspace_variable.key] = workspace_variable.value end end @@ -1129,7 +1090,7 @@ def get_workspace_host_template_annotation(workspace_name, dns_zone) "{{.port}}-#{workspace_name}.#{dns_zone}" end - def get_workspace_host_template_env_var(workspace_name, dns_zone) + def get_workspace_host_template_environment(workspace_name, dns_zone) "${PORT}-#{workspace_name}.#{dns_zone}" end diff --git a/lib/remote_development/settings/current_settings_reader.rb b/lib/remote_development/settings/current_settings_reader.rb index 6870db26f7e410..9d21ccb76a5a3c 100644 --- a/lib/remote_development/settings/current_settings_reader.rb +++ b/lib/remote_development/settings/current_settings_reader.rb @@ -22,7 +22,7 @@ def self.read(context) # err_result will be set to a non-nil Result.err if type check fails err_result = Result.err(SettingsCurrentSettingsReadFailed.new( details: "Gitlab::CurrentSettings.#{setting_name} type of '#{current_setting_value.class}' " \ - "did not match initialized Remote Development Settings type of '#{setting_type}'." # rubocop:disable Layout/LineEndStringConcatenationIndentation -- use default RubyMine formatting + "did not match initialized Remote Development Settings type of '#{setting_type}'." )) end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 8e0fd204c2594d..efc73c603849b5 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -3263,6 +3263,9 @@ msgstr "" msgid "Add topics to projects to help users find them." msgstr "" +msgid "Add variable" +msgstr "" + msgid "Add vulnerability finding" msgstr "" @@ -34770,6 +34773,9 @@ msgstr "" msgid "No user provided" msgstr "" +msgid "No variables" +msgstr "" + msgid "No vulnerabilities present" msgstr "" @@ -43675,6 +43681,9 @@ msgstr "" msgid "Remove user from project" msgstr "" +msgid "Remove variable" +msgstr "" + msgid "Remove weight" msgstr "" diff --git a/qa/qa/ee/page/workspace/list.rb b/qa/qa/ee/page/workspace/list.rb index 5af0593cf52037..12def2374a9796 100644 --- a/qa/qa/ee/page/workspace/list.rb +++ b/qa/qa/ee/page/workspace/list.rb @@ -35,6 +35,7 @@ def create_workspace(agent, project) QA::EE::Page::Workspace::New.perform do |new| new.select_devfile_project(project) new.select_cluster_agent(agent) + new.add_new_variable('VAR1', 'value 1') new.save_workspace end Support::WaitForRequests.wait_for_requests(skip_finished_loading_check: true) diff --git a/qa/qa/ee/page/workspace/new.rb b/qa/qa/ee/page/workspace/new.rb index 1bd4402ab3d5a2..c04aa5bff1d485 100644 --- a/qa/qa/ee/page/workspace/new.rb +++ b/qa/qa/ee/page/workspace/new.rb @@ -12,6 +12,10 @@ class New < QA::Page::Base element 'create-workspace' end + view 'ee/app/assets/javascripts/workspaces/user/components/workspace_variables.vue' do + element 'add-variable' + end + def select_devfile_project(project) click_element('workspace-devfile-project-id-field') search_and_select(project) @@ -26,6 +30,12 @@ def select_cluster_agent(agent) agent_selector.select agent end + def add_new_variable(key, value) + click_element('add-variable') + fill_in 'Variable Key', with: key + fill_in 'Variable Value', with: value + end + def save_workspace click_element('create-workspace', skip_finished_loading_check: true) end diff --git a/spec/support/matchers/graphql_matchers.rb b/spec/support/matchers/graphql_matchers.rb index 155a6dba52c901..3bd1dd102575d3 100644 --- a/spec/support/matchers/graphql_matchers.rb +++ b/spec/support/matchers/graphql_matchers.rb @@ -109,7 +109,7 @@ def expected_field_names def expected_names(field) @names ||= Array.wrap(expected).map { |name| GraphqlHelpers.fieldnamerize(name) } - if field.type.try(:ancestors)&.include?(GraphQL::Types::Relay::BaseConnection) + if field.try(:type).try(:ancestors)&.include?(GraphQL::Types::Relay::BaseConnection) @names | %w[after before first last] else @names -- GitLab