From 24842e76fd2ba79bf69fbd7a59f9aa649d5cc424 Mon Sep 17 00:00:00 2001 From: Keenan Brock Date: Mon, 24 Sep 2018 12:43:59 -0400 Subject: [PATCH] anonymous ftp upload option https://bugzilla.redhat.com/show_bug.cgi?id=1535345 https://bugzilla.redhat.com/show_bug.cgi?id=1632433 This will allow customers to upload files to support sites To add this option, add to `en.yml`: ```yml database_admin: menu_order: - local - ftp://ftp.example.com/incoming/999999-db.backup local: Local file prompts: ftp.example.com: filename_text: "The case number dash (-) filename. (e.g.: 12345-db.backup)" filename_validator: "^[0-9]{4,}-..*" ``` --- .../appliance_console/database_admin.rb | 42 +++++++- spec/database_admin_spec.rb | 101 ++++++++++++++++++ 2 files changed, 140 insertions(+), 3 deletions(-) diff --git a/lib/manageiq/appliance_console/database_admin.rb b/lib/manageiq/appliance_console/database_admin.rb index 9b4230c95..f3d456d35 100644 --- a/lib/manageiq/appliance_console/database_admin.rb +++ b/lib/manageiq/appliance_console/database_admin.rb @@ -1,4 +1,5 @@ require 'manageiq/appliance_console/errors' +require 'uri' module ManageIQ module ApplianceConsole @@ -57,8 +58,12 @@ def ask_file_location @backup_type = ask_with_menu(*file_menu_args) do |menu| menu.choice(CANCEL) { |_| raise MiqSignalError } end - # calling methods like ask_ftp_file_options and ask_s3_file_options - send("ask_#{backup_type}_file_options") + if URI(backup_type).scheme + ask_custom_file_options(backup_type) + else + # calling methods like ask_ftp_file_options and ask_s3_file_options + send("ask_#{backup_type}_file_options") + end end def ask_local_file_options @@ -134,6 +139,20 @@ def ask_ftp_file_options @task_params = ["--", params] end + def ask_custom_file_options(server_uri) + @filename = just_ask(*filename_prompt_args) unless action == :restore + sample_case = server_uri.split("/").last + hostname = URI(server_uri).host + uri_filename = ask_custom_prompt(hostname, 'filename', "Target filename (e.g.: #{sample_case})") + @uri = server_uri.gsub(sample_case, uri_filename) + + params = { :uri => uri } + params[:remote_file_name] = filename if filename + + @task = "evm:db:#{action}:remote" + @task_params = ["--", params] + end + def ask_to_delete_backup_after_restore if action == :restore && backup_type == LOCAL_FILE say("The local database restore file is located at: '#{uri}'.\n") @@ -177,13 +196,20 @@ def confirm_and_execute def allowed_to_execute? return true unless action == :restore + say("\nNote: A database restore cannot be undone. The restore will use the file: #{uri}.\n") agree("Are you sure you would like to restore the database? (Y/N): ") end def file_options @file_options ||= I18n.t("database_admin.menu_order").each_with_object({}) do |file_option, h| - h[I18n.t("database_admin.#{file_option}")] = file_option + # special anonymous ftp sites are defined by uri + uri = URI(file_option) + if uri.scheme + h["#{uri.scheme} to #{uri.host}"] = file_option + else + h[I18n.t("database_admin.#{file_option}")] = file_option + end end end @@ -202,6 +228,16 @@ def setting_header private + def ask_custom_prompt(type, prompt_name, default_prompt) + # type (domain name) has a period in it, so we need to look it up by [] instead of the traditional i18n method + prompts = I18n.t("database_admin.prompts", default: nil) + prompts = prompts && prompts[type.to_sym] + prompt_text = prompts && prompts["#{prompt_name}_text".to_sym] || default_prompt + prompt_regex = prompts && prompts["#{prompt_name}_validator".to_sym] + validator = prompt_regex ? ->(x) { x.to_s =~ /#{prompt_regex}/ } : ->(x) { x.to_s.present? } + just_ask(prompt_text, nil, validator) + end + def should_exclude_tables? ask_yn?("Would you like to exclude tables in the dump") do |q| q.readline = true diff --git a/spec/database_admin_spec.rb b/spec/database_admin_spec.rb index 976958438..1fbfa501b 100644 --- a/spec/database_admin_spec.rb +++ b/spec/database_admin_spec.rb @@ -1464,6 +1464,25 @@ def confirm_and_execute say "6" expect { subject.ask_file_location }.to raise_error signal_error end + + context "with localized file upload" do + it "displays anonymous ftp option" do + expect(I18n).to receive(:t).with("database_admin.menu_order").and_return(%w(local ftp://example.com/inbox/filename.txt)) + expect(I18n).to receive(:t).with("database_admin.local").and_return("The Local file") + expect(subject).to receive(:ask_local_file_options).once + say "" + subject.ask_file_location + expect_output <<-PROMPT.strip_heredoc.chomp + " " + Dump Output File Name + + 1) The Local file + 2) ftp to example.com + 3) Cancel + + Choose the dump output file name: |1| + PROMPT + end + end end describe "#ask_local_file_options" do @@ -1716,6 +1735,88 @@ def confirm_and_execute end end + describe "#ask_custom_file_options" do + let(:example_uri) { "ftp://example.com/inbox/sample.txt" } + let(:uri) { "ftp://example.com/inbox/sample.txt".gsub("sample.txt", target) } + let(:host) { URI(example_uri).host } + let(:filename) { "/tmp/localfile.txt" } + let(:target) { "123456-filename.txt" } + let(:uri_prompt) { "Enter the location to save the remote backup file to\nExample: #{example_uri}" } + let(:user_prompt) { "Enter the username with access to this file.\nExample: 'mydomain.com/user'" } + let(:pass_prompt) { "Enter the password for #{user}" } + let(:errmsg) { "a valid URI" } + + let(:expected_task_params) do + [ + "--", + { + :uri => uri, + :remote_file_name => filename + } + ] + end + + context "with a valid target" do + before do + say [filename, target] + expect(subject.ask_custom_file_options(example_uri)).to be_truthy + end + + it "sets @uri to point to the ftp share url" do + expect(subject.uri).to eq(uri) + end + + it "sets @filename to nil" do + expect(subject.filename).to eq(filename) + end + + it "sets @task to point to 'evm:db:dump:remote'" do + expect(subject.task).to eq("evm:db:dump:remote") + end + + it "sets @task_params to point to the ftp file" do + expect(subject.task_params).to eq(expected_task_params) + end + end + + context "with invalid target (then valid)" do + before do + say [filename, "", target] + expect(subject.ask_custom_file_options(example_uri)).to be_truthy + end + + it "sets @task_params to point to the ftp file" do + expect(subject.task_params).to eq(expected_task_params) + end + end + + context "with custom prompts" do + before do + expect(I18n).to receive(:t).with("database_admin.prompts").and_return( + host.to_sym => { + :filename_text => "Target please", + :filename_validator => "^[0-9]+-.+$" + } + ) + + # if it doesn't ask again, it won't get the right task_params + say [filename, "", "bad-2", target] + expect(subject.ask_custom_file_options(example_uri)).to be_truthy + expect_readline_question_asked "Enter the location to save the dump file to: |/tmp/evm_db.dump|" + expect_readline_question_asked "Target please: " + expect_output [ + "Please provide in the specified format", + "? Please provide in the specified format", + "? ", + ].join("\n") + end + + it "uses custom validation" do + expect(subject.task_params).to eq(expected_task_params) + end + end + end + describe "#ask_to_delete_backup_after_restore" do context "when @backup_type is LOCAL_FILE" do let(:uri) { described_class::DB_RESTORE_FILE }