diff --git a/ee/lib/remote_development/workspaces/create/workspace_variables.rb b/ee/lib/remote_development/workspaces/create/workspace_variables.rb index c45da94004e423400921196b37b0d49b2970d3b5..ad529b4671517b7dc4452e778dbb4dd34996f23e 100644 --- a/ee/lib/remote_development/workspaces/create/workspace_variables.rb +++ b/ee/lib/remote_development/workspaces/create/workspace_variables.rb @@ -33,8 +33,18 @@ class WorkspaceVariables # @param [String] user_name # @param [String] user_email # @param [Integer] workspace_id + # @param [Hash] settings # @return [Array<Hash>] - def self.variables(name:, dns_zone:, personal_access_token_value:, user_name:, user_email:, workspace_id:) + def self.variables( + name:, dns_zone:, personal_access_token_value:, user_name:, user_email:, workspace_id:, settings: + ) + settings => { vscode_extensions_gallery: Hash => vscode_extensions_gallery } + vscode_extensions_gallery => { + service_url: String => vscode_extensions_gallery_service_url, + item_url: String => vscode_extensions_gallery_item_url, + resource_url_template: String => vscode_extensions_gallery_resource_url_template, + } + [ { key: File.basename(RemoteDevelopment::Workspaces::FileMounts::GITLAB_TOKEN_FILE), @@ -107,6 +117,24 @@ def self.variables(name:, dns_zone:, personal_access_token_value:, user_name:, u value: "${PORT}-#{name}.#{dns_zone}", variable_type: VARIABLE_TYPE_ENV_VAR, workspace_id: workspace_id + }, + { + key: 'GL_EDITOR_EXTENSIONS_GALLERY_SERVICE_URL', + value: vscode_extensions_gallery_service_url, + variable_type: VARIABLE_TYPE_ENV_VAR, + workspace_id: workspace_id + }, + { + key: 'GL_EDITOR_EXTENSIONS_GALLERY_ITEM_URL', + value: vscode_extensions_gallery_item_url, + variable_type: VARIABLE_TYPE_ENV_VAR, + 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, + workspace_id: workspace_id } ] 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 d9098eaad50392dd7703a1d4eefaa8d63311e604..43f2466f76c439614c8625cd5d4476e5376afeeb 100644 --- a/ee/lib/remote_development/workspaces/create/workspace_variables_creator.rb +++ b/ee/lib/remote_development/workspaces/create/workspace_variables_creator.rb @@ -13,6 +13,7 @@ def self.create(value) workspace: RemoteDevelopment::Workspace => workspace, personal_access_token: PersonalAccessToken => personal_access_token, current_user: User => user, + settings: Hash => settings } workspace_variables_params = WorkspaceVariables.variables( name: workspace.name, @@ -20,7 +21,8 @@ def self.create(value) personal_access_token_value: personal_access_token.token, user_name: user.name, user_email: user.email, - workspace_id: workspace.id + workspace_id: workspace.id, + settings: settings ) workspace_variables_params.each do |workspace_variable_params| @@ -34,11 +36,7 @@ def self.create(value) end end - Result.ok( - value.merge({ - workspace_variables_params: workspace_variables_params - }) - ) + Result.ok(value) end end end diff --git a/ee/spec/factories/remote_development/workspaces.rb b/ee/spec/factories/remote_development/workspaces.rb index e7403813b8fd601f38b918eea9771204439cb81f..f9c44c966b427fe42e5b71bdf5681c3eb7476a4e 100644 --- a/ee/spec/factories/remote_development/workspaces.rb +++ b/ee/spec/factories/remote_development/workspaces.rb @@ -82,7 +82,14 @@ personal_access_token_value: workspace.personal_access_token.token, user_name: workspace.user.name, user_email: workspace.user.email, - workspace_id: workspace.id + workspace_id: workspace.id, + settings: { + vscode_extensions_gallery: { + service_url: "https://open-vsx.org/vscode/gallery", + item_url: "https://open-vsx.org/vscode/item", + resource_url_template: "https://open-vsx.org/api/{publisher}/{name}/{version}/file/{path}" + } + } ) workspace_variables.each do |workspace_variable| 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 b1f9aec9b947f5b243a51ae622f70c1b541305b7..5645dc6b73165bc64038a08047507bb44934b539 100644 --- a/ee/spec/lib/remote_development/workspaces/create/creator_spec.rb +++ b/ee/spec/lib/remote_development/workspaces/create/creator_spec.rb @@ -3,126 +3,147 @@ require 'spec_helper' RSpec.describe ::RemoteDevelopment::Workspaces::Create::Creator, feature_category: :remote_development do + include RemoteDevelopment::RailwayOrientedProgrammingHelpers include ResultMatchers include_context 'with remote development shared fixtures' let_it_be(:user) { create(:user) } - let_it_be(:project, reload: true) { create(:project, :in_group, :repository) } let_it_be(:agent) { create(:ee_cluster_agent, :with_remote_development_agent_config) } let(:random_string) { 'abcdef' } - let(:devfile_path) { '.devfile.yaml' } - let(:devfile_yaml) { example_devfile } - let(:processed_devfile) { YAML.safe_load(example_flattened_devfile) } - let(:desired_state) { RemoteDevelopment::Workspaces::States::RUNNING } - let(:processed_devfile_yaml) { YAML.safe_load(example_processed_devfile) } - let(:max_hours_before_termination) { 24 } - - let(:editor) { 'webide' } - let(:workspace_root) { '/projects' } + let(:params) do { - agent: agent, - user: user, - project: project, - editor: editor, - max_hours_before_termination: max_hours_before_termination, - desired_state: desired_state, - devfile_ref: 'main', - devfile_path: devfile_path + agent: agent } end let(:value) do { params: params, - current_user: user, - devfile_yaml: devfile_yaml, - processed_devfile: processed_devfile, - volume_mounts: { - data_volume: { - name: "gl-workspace-data", - path: workspace_root - } - } + current_user: user } end + let(:updated_value) do + value.merge( + { + workspace_name: "workspace-#{agent.id}-#{user.id}-#{random_string}", + workspace_namespace: "gl-rd-ns-#{agent.id}-#{user.id}-#{random_string}" + } + ) + end + + # Classes + + let(:personal_access_token_creator_class) { RemoteDevelopment::Workspaces::Create::PersonalAccessTokenCreator } + let(:workspace_creator_class) { RemoteDevelopment::Workspaces::Create::WorkspaceCreator } + let(:workspace_variables_creator_class) { RemoteDevelopment::Workspaces::Create::WorkspaceVariablesCreator } + + # Methods + + let(:personal_access_token_creator_method) { personal_access_token_creator_class.singleton_method(:create) } + let(:workspace_creator_method) { workspace_creator_class.singleton_method(:create) } + let(:workspace_variables_creator_method) { workspace_variables_creator_class.singleton_method(:create) } + subject(:result) do described_class.create(value) # rubocop:disable Rails/SaveBang -- we are testing validation, we don't want an exception end - context 'when all db records are created successfully' do + before do + allow(personal_access_token_creator_class).to receive(:method).with(:create) do + personal_access_token_creator_method + end + + allow(workspace_creator_class).to receive(:method).with(:create) do + workspace_creator_method + end + + allow(workspace_variables_creator_class).to receive(:method).with(:create) do + create(:workspace_variable) + workspace_variables_creator_method + end + end + + context 'when workspace create is successful' do before do allow(SecureRandom).to receive(:alphanumeric) { random_string } - end - it 'returns ok result containing successful message with created workspace' do - expect { result }.to change { - [ - project.workspaces.count, - user.personal_access_tokens.reload.count, - project.workspaces.last ? project.workspaces.last.workspace_variables.count : 0 - ] - } + stub_methods_to_return_ok_result( + personal_access_token_creator_method, + workspace_creator_method, + workspace_variables_creator_method + ) + end + it 'returns ok result containing successful message with updated value' do expect(result).to be_ok_result do |message| expect(message).to be_a(RemoteDevelopment::Messages::WorkspaceCreateSuccessful) - message.context => { workspace: RemoteDevelopment::Workspace => workspace } - expect(workspace).to eq(project.workspaces.last) + expect(message.context).to eq(updated_value) end end end - context 'when workspace fails on creation' do - shared_examples 'err result' do |expected_error_details:| - it 'does not create the db records and returns an error result containing a failed message with model errors' do - expect { result }.not_to change { - [ - project.workspaces.count, - user.personal_access_tokens.reload.count, - project.workspaces.last ? project.workspaces.last.workspace_variables.count : 0 - ] - } + context "when workspace create fails" do + let(:creation_errors) { 'some creation errors' } + let(:err_message_context) { { errors: creation_errors } } + + context 'when the PersonalAccessTokenCreator returns an err Result' do + before do + stub_methods_to_return_err_result( + method: personal_access_token_creator_method, + message_class: RemoteDevelopment::Messages::PersonalAccessTokenModelCreateFailed + ) + end + + it 'returns an error result containing creation errors' do expect(result).to be_err_result do |message| expect(message).to be_a(RemoteDevelopment::Messages::WorkspaceCreateFailed) - message.context => { errors: ActiveModel::Errors => errors } - expect(errors.full_messages).to match([/#{expected_error_details}/i]) + message.context => { errors: errors } + expect(errors).to eq(creation_errors) end end end - context 'when workspace db record fails on creation' do - let(:desired_state) { 'InvalidDesiredState' } - - it_behaves_like 'err result', expected_error_details: %(desired state) - end - - context 'when personal access token db record fails on creation' do - let(:max_hours_before_termination) { 999999999999 } + context 'when the WorkspaceCreator returns an err Result' do + before do + stub_methods_to_return_ok_result(personal_access_token_creator_method) - it_behaves_like 'err result', expected_error_details: %(expiration date) - end + stub_methods_to_return_err_result( + method: workspace_creator_method, + message_class: RemoteDevelopment::Messages::WorkspaceModelCreateFailed + ) + end - context 'when workspace variable db record fails on creation' do - let(:invalid_workspace_variables) do - [ - { - key: "does-not-matter", - value: "does-not-matter", - variable_type: 9999999, - workspace_id: 0 # workspace id does not matter in this case - } - ] + it 'returns an error result containing creation errors' do + expect(result).to be_err_result do |message| + expect(message).to be_a(RemoteDevelopment::Messages::WorkspaceCreateFailed) + message.context => { errors: errors } + expect(errors).to eq(creation_errors) + end end + end + context 'when the WorkspaceVariablesCreator returns an err Result' do before do - allow(RemoteDevelopment::Workspaces::Create::WorkspaceVariables) - .to receive(:variables) - .and_return(invalid_workspace_variables) + stub_methods_to_return_ok_result( + personal_access_token_creator_method, + workspace_creator_method + ) + + stub_methods_to_return_err_result( + method: workspace_variables_creator_method, + message_class: RemoteDevelopment::Messages::WorkspaceVariablesModelCreateFailed + ) end - it_behaves_like 'err result', expected_error_details: %(variable type) + it 'returns an error response containing creation errors' do + expect(result).to be_err_result do |message| + expect(message).to be_a(RemoteDevelopment::Messages::WorkspaceCreateFailed) + message.context => { errors: errors } + expect(errors).to eq(creation_errors) + end + end end end end 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 7fd325d8c5ded77deda6c285793da2bb5470cde1..218889eaa225694931c2c109f1e958740b50a29a 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 @@ -51,7 +51,12 @@ let(:settings) do { project_cloner_image: 'alpine/git:2.36.3', - tools_injector_image: tools_injector_image_from_settings + tools_injector_image: tools_injector_image_from_settings, + vscode_extensions_gallery: { + service_url: "https://open-vsx.org/vscode/gallery", + item_url: "https://open-vsx.org/vscode/item", + resource_url_template: "https://open-vsx.org/api/{publisher}/{name}/{version}/file/{path}" + } } 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 5a0193ae7f15a5adf698c2dc7520908abe84b0d5..aa97202b7b684ad93cc662d5b6633ba112258503 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 @@ -10,36 +10,42 @@ let_it_be(:user) { create(:user) } 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(:invalid_workspace_variables) do + let(:settings) { { some_setting: "value" } } + let(:returned_workspace_variables) do [ { - key: "does-not-matter", - value: "does-not-matter", - variable_type: 9999999, + key: "key1", + value: "value1", + variable_type: RemoteDevelopment::Workspaces::Create::WorkspaceVariables::VARIABLE_TYPE_FILE, + workspace_id: workspace.id + }, + { + key: "key2", + value: "value2", + variable_type: variable_type, workspace_id: workspace.id } ] end - let(:expected_workspace_variables_params) do - variables = RemoteDevelopment::Workspaces::Create::WorkspaceVariables.variables( + let(:workspace_variables_params) do + { 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 - ) - variables.each do |variable| - variable[:workspace_id] = workspace.id - end + workspace_id: workspace.id, + settings: settings + } end let(:value) do { workspace: workspace, + personal_access_token: personal_access_token, current_user: user, - personal_access_token: personal_access_token + settings: settings } end @@ -47,26 +53,36 @@ described_class.create(value) # rubocop:disable Rails/SaveBang -- this is not an ActiveRecord method end + before do + allow(RemoteDevelopment::Workspaces::Create::WorkspaceVariables) + .to receive(:variables).with(workspace_variables_params) { returned_workspace_variables } + end + context 'when workspace variables create is successful' do - it 'creates the workspace variables and returns ok result containing successful message with created variables' do - expect { result }.to change { workspace.workspace_variables.count } + let(:valid_variable_type) { RemoteDevelopment::Workspaces::Create::WorkspaceVariables::VARIABLE_TYPE_ENV_VAR } + let(:variable_type) { valid_variable_type } - expect(result).to be_ok_result do |message| - message => { workspace_variables_params: Array => workspace_variables_params } - expect(workspace_variables_params).to eq(expected_workspace_variables_params) - end + it 'creates the workspace variable records and returns ok result containing original value' do + expect { result }.to change { workspace.workspace_variables.count }.by(2) + + expect(RemoteDevelopment::WorkspaceVariable.find_by_key('key1').value).to eq('value1') + expect(RemoteDevelopment::WorkspaceVariable.find_by_key('key2').value).to eq('value2') + + expect(result).to be_ok_result(value) end end context 'when workspace create fails' do - before do - allow(RemoteDevelopment::Workspaces::Create::WorkspaceVariables) - .to receive(:variables) - .and_return(invalid_workspace_variables) - end + let(:invalid_variable_type) { 9999999 } + let(:variable_type) { invalid_variable_type } + + it 'does not create the invalid workspace variable records and returns an error result with model errors' do + # NOTE: Any valid records will be saved if they are first in the array before the invalid record, but that's OK, + # because if we return an err_result, the entire transaction will be rolled back at a higher level. + expect { result }.to change { workspace.workspace_variables.count }.by(1) - it 'does not create the workspace and returns an error result containing a failed message with model errors' do - expect { result }.not_to change { workspace.workspace_variables.count } + expect(RemoteDevelopment::WorkspaceVariable.find_by_key('key1').value).to eq('value1') + expect(RemoteDevelopment::WorkspaceVariable.find_by_key('key2')).to be_nil expect(result).to be_err_result do |message| expect(message).to be_a(RemoteDevelopment::Messages::WorkspaceVariablesModelCreateFailed) 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 61302a1db5723766967787fdc5af4d35c67be1d3..74471ec5d24e4703f39cf210c80fa4bcd92be274 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 @@ -10,6 +10,9 @@ let(:user_name) { "example.user.name" } let(:user_email) { "example@user.email" } let(:workspace_id) { 1 } + let(:vscode_extensions_gallery_service_url) { "https://open-vsx.org/vscode/gallery" } + let(:vscode_extensions_gallery_item_url) { "https://open-vsx.org/vscode/item" } + let(:vscode_extensions_gallery_resource_url_template) { "https://open-vsx.org/api/{publisher}/{name}/{version}/file/{path}" } let(:git_credential_store_script) do <<~SH.chomp #!/bin/sh @@ -106,6 +109,24 @@ value: "${PORT}-name.example.dns.zone", variable_type: RemoteDevelopment::Workspaces::Create::WorkspaceVariables::VARIABLE_TYPE_ENV_VAR, 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, + 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, + 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, + workspace_id: workspace_id } ] end @@ -117,7 +138,14 @@ personal_access_token_value: personal_access_token_value, user_name: user_name, user_email: user_email, - workspace_id: workspace_id + workspace_id: workspace_id, + settings: { + vscode_extensions_gallery: { + service_url: vscode_extensions_gallery_service_url, + item_url: vscode_extensions_gallery_item_url, + resource_url_template: vscode_extensions_gallery_resource_url_template + } + } ) 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 3610e25334916521a07a84f9204cb76deabf5f25..b49ad1346fdbe6a83c07ee9b465a7d752fe41193 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 @@ -1052,7 +1052,14 @@ def workspace_secret_env_var( 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', '') - # TODO: figure out why there is flakiness in the order of the environment variables? + 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", apiVersion: "v1", @@ -1072,7 +1079,11 @@ def workspace_secret_env_var( 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_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 } } end diff --git a/spec/graphql/types/subscription_type_spec.rb b/spec/graphql/types/subscription_type_spec.rb index 455685527c04a10cfc3d12ebb9c1d77b10f0b04f..dbc6b5c84042c827701fe645975b418e92362165 100644 --- a/spec/graphql/types/subscription_type_spec.rb +++ b/spec/graphql/types/subscription_type_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe GitlabSchema.types['Subscription'] do +RSpec.describe GitlabSchema.types['Subscription'], feature_category: :subscription_management do it 'has the expected fields' do expected_fields = %i[ issuable_assignees_updated