From 6c63c81da08c3f56f4f59b0ca88ef5d71fbea641 Mon Sep 17 00:00:00 2001 From: Manoj <138657352+putmanoj@users.noreply.github.com> Date: Fri, 22 Mar 2024 01:03:11 +0530 Subject: [PATCH] Add more test for git sync (#2) * [draft] add spec for .create_in_provider * Add EmbeddedTerraform Provider class * add ensure_managers * fixed .create_in_provider test * Add more tests * Use basename of dir, as template-name pre-fix, & full git repo url details as suffix * Add more test - templates-in-repo & update-in-provider --- .../configuration_script_source.rb | 33 +- .../configuration_script_source_spec.rb | 287 +++++++++++++++++- 2 files changed, 303 insertions(+), 17 deletions(-) diff --git a/app/models/manageiq/providers/embedded_terraform/automation_manager/configuration_script_source.rb b/app/models/manageiq/providers/embedded_terraform/automation_manager/configuration_script_source.rb index 969806fb..f9bbf81c 100644 --- a/app/models/manageiq/providers/embedded_terraform/automation_manager/configuration_script_source.rb +++ b/app/models/manageiq/providers/embedded_terraform/automation_manager/configuration_script_source.rb @@ -37,6 +37,29 @@ def sync raise error end + # Return Template name, using relative_path's basename prefix, + # and suffix with git-repo url details. + # eg. basename(branch_name#hostname/path/relative_path_parent) + def self.template_name_from_git_repo_url(git_repo_url, branch_name, relative_path) + temp_url = git_repo_url + # URI library cannot handle git urls, so just convert it to a standard url. + temp_url = temp_url.sub(':', '/').sub('git@', 'https://') if temp_url.start_with?('git@') + temp_uri = URI.parse(temp_url) + hostname = temp_uri.hostname + path = temp_uri.path + path = path[0...-4] if path.end_with?('.git') + path = path[0...-5] if path.end_with?('.git/') + # if template dir is root, then use repo name as basename + if relative_path == '.' + basename = File.basename(path) + parent_path = '' + else + basename = File.basename(relative_path) + parent_path = File.dirname(relative_path) + end + "#{basename}(#{branch_name}##{hostname}#{path}/#{parent_path})" + end + private # Find Terraform Templates(dir) in the git repo. @@ -59,7 +82,11 @@ def find_templates_in_git_repo next unless filepath.end_with?(".tf", ".tf.json") parent_dir = File.dirname(filepath) - next if template_dirs.key?(parent_dir) + name = ManageIQ::Providers::EmbeddedTerraform::AutomationManager::ConfigurationScriptSource.template_name_from_git_repo_url( + git_repository.url, scm_branch, parent_dir + ) + + next if template_dirs.key?(name) full_path = File.join(git_checkout_tempdir, parent_dir) _log.info("Local full path : #{full_path}") @@ -69,13 +96,13 @@ def find_templates_in_git_repo input_vars = nil output_vars = nil - template_dirs[parent_dir] = { + template_dirs[name] = { :relative_path => parent_dir, :files => files, :input_vars => input_vars, :output_vars => output_vars } - _log.debug("=== Add Template:#{parent_dir}") + _log.debug("=== Add Template:#{name}") end end diff --git a/spec/models/manageiq/providers/embedded_terraform/automation_manager/configuration_script_source_spec.rb b/spec/models/manageiq/providers/embedded_terraform/automation_manager/configuration_script_source_spec.rb index 55a219ea..36100f9c 100644 --- a/spec/models/manageiq/providers/embedded_terraform/automation_manager/configuration_script_source_spec.rb +++ b/spec/models/manageiq/providers/embedded_terraform/automation_manager/configuration_script_source_spec.rb @@ -1,4 +1,8 @@ +# TODO: replace FakeAnsibleRepo with FakeTerraformRepo +FakeTerraformRepo = Spec::Support::FakeAnsibleRepo + RSpec.describe ManageIQ::Providers::EmbeddedTerraform::AutomationManager::ConfigurationScriptSource do + context "with a local repo" do let(:manager) do FactoryBot.create(:provider_embedded_terraform, :default_organization => 1).managers.first @@ -20,7 +24,7 @@ before do FileUtils.mkdir_p(local_repo) - repo = Spec::Support::FakeAnsibleRepo.new(local_repo, repo_dir_structure) + repo = FakeTerraformRepo.new(local_repo, repo_dir_structure) repo.generate repo.git_branch_create("other_branch") @@ -42,22 +46,278 @@ def files_in_repository(git_repo_dir) end describe ".create_in_provider" do - it "creates a record and initializes a git repo" do - result = described_class.create_in_provider(manager.id, params) - - expect(result).to(be_an(described_class)) - expect(result.scm_type).to eq("git") - expect(result.scm_branch).to eq("master") - expect(result.status).to eq("successful") - expect(result.last_updated_on).to be_an(Time) - expect(result.last_update_error).to be_nil - - git_repo_dir = repo_dir.join(result.git_repository.id.to_s) - expect(files_in_repository(git_repo_dir)).to eq ["hello_world.tf"] + # let(:notify_creation_args) { notification_args('creation') } + + context "with valid params" do + it "creates a record and initializes a git repo" do + # expect(Notification).to receive(:create!).with(notify_creation_args) + # expect(Notification).to receive(:create!).with(notification_args("syncing", {})) + + result = described_class.create_in_provider(manager.id, params) + + expect(result).to be_an(described_class) + expect(result.scm_type).to eq("git") + expect(result.scm_branch).to eq("master") + expect(result.status).to eq("successful") + expect(result.last_updated_on).to be_an(Time) + expect(result.last_update_error).to be_nil + + git_repo_dir = repo_dir.join(result.git_repository.id.to_s) + expect(files_in_repository(git_repo_dir)).to eq ["hello_world.tf"] + end + + # NOTE: Second `.notify` stub below prevents `.sync` from getting fired + it "sets the status to 'new' on create" do + pending # will fix later + + # expect(Notification).to receive(:create!).with(notify_creation_args) + # expect(described_class).to receive(:notify).with(any_args).and_call_original + # expect(described_class).to receive(:notify).with("syncing", any_args).and_return(true) + + result = described_class.create_in_provider(manager.id, params) + + expect(result).to be_an(described_class) + expect(result.scm_type).to eq("git") + expect(result.scm_branch).to eq("master") + expect(result.status).to eq("new") + expect(result.last_updated_on).to be_nil + expect(result.last_update_error).to be_nil + + expect(repos).to be_empty + end + end + end + + describe ".create_in_provider_queue" do + it "creates a task and queue item" do + EvmSpecHelper.local_miq_server + task_id = described_class.create_in_provider_queue(manager.id, params) + expect(MiqTask.find(task_id)).to have_attributes(:name => "Creating #{described_class::FRIENDLY_NAME} (name=#{params[:name]})") + expect(MiqQueue.first).to have_attributes( + :args => [manager.id, params], + :class_name => described_class.name, + :method_name => "create_in_provider", + :priority => MiqQueue::HIGH_PRIORITY, + # :role => "embedded_terraform", + :zone => nil + ) + end + end + + describe "#verify_ssl" do + it "defaults to OpenSSL::SSL::VERIFY_NONE" do + expect(subject.verify_ssl).to eq(OpenSSL::SSL::VERIFY_NONE) + end + + it "can be updated to OpenSSL::SSL::VERIFY_PEER" do + subject.verify_ssl = OpenSSL::SSL::VERIFY_PEER + expect(subject.verify_ssl).to eq(OpenSSL::SSL::VERIFY_PEER) + end + + context "with a created record" do + subject { described_class.last } + let(:create_params) { params.merge(:verify_ssl => OpenSSL::SSL::VERIFY_PEER) } + + before do + allow(Notification).to receive(:create!) + + described_class.create_in_provider(manager.id, create_params) + end + + it "pulls from the created record" do + expect(subject.verify_ssl).to eq(OpenSSL::SSL::VERIFY_PEER) + end + + it "pushes updates from the ConfigurationScriptSource to the GitRepository" do + subject.update(:verify_ssl => OpenSSL::SSL::VERIFY_NONE) + + expect(described_class.last.verify_ssl).to eq(OpenSSL::SSL::VERIFY_NONE) + expect(GitRepository.last.verify_ssl).to eq(OpenSSL::SSL::VERIFY_NONE) + end + + it "converts true/false values instead of integers" do + subject.update(:verify_ssl => false) + + expect(described_class.last.verify_ssl).to eq(OpenSSL::SSL::VERIFY_NONE) + expect(GitRepository.last.verify_ssl).to eq(OpenSSL::SSL::VERIFY_NONE) + + subject.update(:verify_ssl => true) + expect(described_class.last.verify_ssl).to eq(OpenSSL::SSL::VERIFY_PEER) + expect(GitRepository.last.verify_ssl).to eq(OpenSSL::SSL::VERIFY_PEER) + end + end + + end + + describe "#templates_in_git_repository" do + it "finds top level template" do + record = build_record + + expect(templates_for(record)).to eq(["hello_world_local(master##{local_repo}/)"]) + end + + it "saves the template payload" do + record = build_record + + payload = { + :relative_path => '.', + :files => ['hello_world.tf'], + :input_vars => nil, + :output_vars => nil + } + + name = described_class.template_name_from_git_repo_url(local_repo, 'master', '.') + + expect(record.configuration_script_payloads.first).to have_attributes( + :name => name, + :payload => payload.to_json, + :payload_type => "json" + ) + end + + context "with a nested templates dir" do + let(:nested_repo) { File.join(clone_dir, "hello_world_nested") } + + let(:nested_repo_structure) do + %w[ + templates/hello-world/main.tf + ] + end + + it "finds all templates" do + FakeTerraformRepo.generate(nested_repo, nested_repo_structure) + + params[:scm_url] = "file://#{nested_repo}" + record = build_record + + expect(templates_for(record)).to eq(["hello-world(master##{nested_repo}/templates)"]) + end + end + + context "with a multiple templates" do + let(:multiple_templates_repo) { File.join(clone_dir, "hello_world_nested") } + + let(:multiple_templates_repo_structure) do + %w[ + templates/hello-world/main.tf + templates/single-vm/main.tf + ] + end + + it "finds all playbooks" do + FakeTerraformRepo.generate(multiple_templates_repo, multiple_templates_repo_structure) + + params[:scm_url] = "file://#{multiple_templates_repo}" + record = build_record + + expect(templates_for(record)).to eq( + [ + "hello-world(master##{multiple_templates_repo}/templates)", + "single-vm(master##{multiple_templates_repo}/templates)" + ] + ) + end + end + + end + + describe "#update_in_provider" do + let(:update_params) { {:scm_branch => "other_branch"} } + # let(:notify_update_args) { notification_args('update', update_params) } + + context "with valid params" do + it "updates the record and initializes a git repo" do + record = build_record + + # expect(Notification).to receive(:create!).with(notify_update_args) + # expect(Notification).to receive(:create!).with(notification_args("syncing", {})) + + result = record.update_in_provider update_params + + expect(result).to be_an(described_class) + expect(result.scm_branch).to eq("other_branch") + + git_repo_dir = repo_dir.join(result.git_repository.id.to_s) + expect(files_in_repository(git_repo_dir)).to eq ["hello_world.tf"] + + expect(templates_for(record)).to eq( + [ + "hello_world_local(other_branch##{local_repo}/)" + ] + ) + end + end + + context "with invalid params" do + it "does not create a record and does not call git" do + record = build_record + update_params[:scm_type] = 'svn' # oh dear god... + + expect(AwesomeSpawn).to receive(:run!).never + # expect(Notification).to receive(:create!).with(notify_update_args) + + expect do + record.update_in_provider update_params + end.to raise_error(ActiveRecord::RecordInvalid) + end end + + context "when there is a network error fetching the repo" do + before do + record = build_record + + # sync_notification_args = notification_args("syncing", {}) + + # expect(Notification).to receive(:create!).with(notify_update_args) + # expect(Notification).to receive(:create!).with(sync_notification_args) + expect(record.git_repository).to receive(:update_repo).and_raise(Rugged::NetworkError) + + expect do + # described_class.last.update_in_provider update_params + record.update_in_provider update_params + end.to raise_error(Rugged::NetworkError) + end + + it "sets the status to 'error' if syncing has a network error" do + result = described_class.last + + expect(result).to be_an(described_class) + expect(result.scm_type).to eq("git") + expect(result.scm_branch).to eq("other_branch") + expect(result.status).to eq("error") + expect(result.last_updated_on).to be_an(Time) + expect(result.last_update_error).to start_with("Rugged::NetworkError") + end + + it "clears last_update_error on re-sync" do + result = described_class.last + + expect(result.status).to eq("error") + expect(result.last_updated_on).to be_an(Time) + expect(result.last_update_error).to start_with("Rugged::NetworkError") + + expect(result.git_repository).to receive(:update_repo).and_call_original + + result.sync + + expect(result.status).to eq("successful") + expect(result.last_update_error).to be_nil + end + end + + end + + def templates_for(repo) + repo.configuration_script_payloads.pluck(:name) + end + + def build_record + # expect(Notification).to receive(:create!).with(any_args).twice + described_class.create_in_provider manager.id, params end end + describe "git_repository interaction" do let(:auth) { FactoryBot.create(:embedded_terraform_scm_credential) } let(:configuration_script_source) do @@ -128,6 +388,5 @@ def files_in_repository(git_repo_dir) configuration_script_source.update!(:authentication => nil) expect(GitRepository.first.authentication).to be_nil end - end end