diff --git a/app/models/manageiq/providers/embedded_ansible/automation_manager.rb b/app/models/manageiq/providers/embedded_ansible/automation_manager.rb index 0de91c21dc8..f19f083f65e 100644 --- a/app/models/manageiq/providers/embedded_ansible/automation_manager.rb +++ b/app/models/manageiq/providers/embedded_ansible/automation_manager.rb @@ -11,6 +11,6 @@ def self.description end def self.catalog_types - {"generic_ansible_playbook" => N_("Ansible Playbook")} + {"generic_ansible_playbook" => N_("Ansible Playbook (deprecated)"), "embedded_ansible" => N_("Ansible Playbook")} end end diff --git a/app/models/manageiq/providers/embedded_ansible/automation_manager/provision.rb b/app/models/manageiq/providers/embedded_ansible/automation_manager/provision.rb new file mode 100644 index 00000000000..ebf0942b8df --- /dev/null +++ b/app/models/manageiq/providers/embedded_ansible/automation_manager/provision.rb @@ -0,0 +1,5 @@ +class ManageIQ::Providers::EmbeddedAnsible::AutomationManager::Provision < ManageIQ::Providers::AutomationManager::Provision + include StateMachine + + TASK_DESCRIPTION = N_("Ansible Playbook Provision") +end diff --git a/app/models/manageiq/providers/embedded_ansible/automation_manager/provision/state_machine.rb b/app/models/manageiq/providers/embedded_ansible/automation_manager/provision/state_machine.rb new file mode 100644 index 00000000000..202563710f5 --- /dev/null +++ b/app/models/manageiq/providers/embedded_ansible/automation_manager/provision/state_machine.rb @@ -0,0 +1,62 @@ +module ManageIQ::Providers::EmbeddedAnsible::AutomationManager::Provision::StateMachine + def run_provision + signal :provision + end + + def provision + opts = %i[become_enabled cloud_credential_id credential_id execution_ttl extra_vars hosts network_credential_id vault_credential_id verbosity].to_h do |key| + [key, get_option(key)] + end + + opts[:hosts] ||= %w[localhost] + stack = stack_klass.create_stack(source.parent, opts) + + phase_context[:stack_id] = stack.id + connect_to_service!(stack, :name => "Provision") + + save! + + signal :check_provisioned + end + + def check_provisioned + if running? + requeue_phase + else + signal :post_provision + end + end + + def post_provision + if succeeded? + signal :mark_as_completed + else + abort_job("Failed to provision playbook", "error") + end + end + + def running? + !stack.raw_status.completed? + end + + def succeeded? + stack.raw_status.succeeded? + end + + def mark_as_completed + update_and_notify_parent(:state => "finished", :message => "Playbook provision is complete") + signal :finish + end + + def finish + mark_execution_servers + end + + def stack_klass + ManageIQ::Providers::EmbeddedAnsible::AutomationManager::Job + end + + def stack + @stack ||= stack_klass.find(phase_context[:stack_id]) + end +end diff --git a/app/models/manageiq/providers/embedded_ansible/automation_manager/provision_workflow.rb b/app/models/manageiq/providers/embedded_ansible/automation_manager/provision_workflow.rb new file mode 100644 index 00000000000..9710a3bfc24 --- /dev/null +++ b/app/models/manageiq/providers/embedded_ansible/automation_manager/provision_workflow.rb @@ -0,0 +1,50 @@ +class ManageIQ::Providers::EmbeddedAnsible::AutomationManager::ProvisionWorkflow < ManageIQ::Providers::AutomationManager::ProvisionWorkflow + def self.default_dialog_file + "miq_provision_configuration_script_embedded_ansible_dialogs".freeze + end + + def dialog_name_from_automate(message = 'get_dialog_name', extra_attrs = {}) + extra_attrs['platform'] ||= 'embedded_ansible' + super + end + + def allowed_configuration_scripts(*_args) + self.class.module_parent::ConfigurationScript.all.map do |cs| + build_ci_hash_struct(cs, %w[name description manager_name]) + end + end + + def allowed_machine_credentials(*_args) + self.class.module_parent::MachineCredential.all.to_h do |mc| + [mc.id, mc.name] + end + end + + def allowed_vault_credentials(*_args) + self.class.module_parent::VaultCredential.all.to_h do |vc| + [vc.id, vc.name] + end + end + + def allowed_cloud_credential_types(*_args) + # If a cloud credential is selected then pre-populate the Cloud Type + cloud_credential_id = get_value(values[:cloud_credential_id]) + if cloud_credential_id && cloud_credential_id != 0 # TODO: Why is = 0 ?? + # TODO why does self.class.module_parent::CloudCredential.find(id) not work? + cloud_credential = ::Authentication.find(cloud_credential_id) + {cloud_credential.class.to_s => cloud_credential.class::API_OPTIONS[:label]} + else + self.class.module_parent::CloudCredential.descendants.to_h do |klass| + [klass.to_s, klass::API_OPTIONS[:label]] + end + end + end + + def allowed_cloud_credentials(*_args) + klass = get_value(values[:cloud_credential_type])&.safe_constantize + klass ||= self.class.module_parent::CloudCredential + klass.all.to_h do |cc| + [cc.id, "#{cc.name} - #{cc.type}"] + end + end +end diff --git a/app/models/service_embedded_ansible.rb b/app/models/service_embedded_ansible.rb new file mode 100644 index 00000000000..75cdfc689b0 --- /dev/null +++ b/app/models/service_embedded_ansible.rb @@ -0,0 +1,5 @@ +class ServiceEmbeddedAnsible < ServiceAutomation + def job(action = "Provision") + service_resources.find_by(:name => action, :resource_type => 'OrchestrationStack').try(:resource) + end +end diff --git a/app/models/service_template_embedded_ansible.rb b/app/models/service_template_embedded_ansible.rb new file mode 100644 index 00000000000..71b9e2c91d9 --- /dev/null +++ b/app/models/service_template_embedded_ansible.rb @@ -0,0 +1,2 @@ +class ServiceTemplateEmbeddedAnsible < ServiceTemplateAutomation +end diff --git a/product/dialogs/miq_dialogs/miq_provision_configuration_script_embedded_ansible_dialogs.yaml b/product/dialogs/miq_dialogs/miq_provision_configuration_script_embedded_ansible_dialogs.yaml new file mode 100644 index 00000000000..e1af7e45e87 --- /dev/null +++ b/product/dialogs/miq_dialogs/miq_provision_configuration_script_embedded_ansible_dialogs.yaml @@ -0,0 +1,227 @@ +--- +:name: miq_provision_configuration_script_embedded_ansible_dialogs +:description: Sample Configuration Script Embedded Ansible Provisioning Dialog +:dialog_type: MiqProvisionConfigurationScriptWorkflow +:content: + :buttons: + - :submit + - :cancel + :dialogs: + :requester: + :description: Request + :fields: + :owner_phone: + :description: Phone + :required: false + :display: :hide + :data_type: :string + :owner_country: + :description: Country/Region + :required: false + :display: :hide + :data_type: :string + :owner_phone_mobile: + :description: Mobile + :required: false + :display: :hide + :data_type: :string + :owner_title: + :description: Title + :required: false + :display: :hide + :data_type: :string + :owner_first_name: + :description: First Name + :required: false + :display: :edit + :data_type: :string + :owner_manager: + :description: Name + :required: false + :display: :edit + :data_type: :string + :owner_address: + :description: Address + :required: false + :display: :hide + :data_type: :string + :owner_company: + :description: Company + :required: false + :display: :hide + :data_type: :string + :owner_last_name: + :description: Last Name + :required: false + :display: :edit + :data_type: :string + :owner_manager_mail: + :description: E-Mail + :required: false + :display: :hide + :data_type: :string + :owner_city: + :description: City + :required: false + :display: :hide + :data_type: :string + :owner_department: + :description: Department + :required: false + :display: :hide + :data_type: :string + :owner_manager_phone: + :description: Phone + :required: false + :display: :hide + :data_type: :string + :owner_state: + :description: State + :required: false + :display: :hide + :data_type: :string + :owner_office: + :description: Office + :required: false + :display: :hide + :data_type: :string + :owner_zip: + :description: Zip code + :required: false + :display: :hide + :data_type: :string + :owner_email: + :description: E-Mail + :required_method: :validate_regex + :required_regex: !ruby/regexp /\A[\w!#$\%&'*+\/=?`\{|\}~^-]+(?:\.[\w!#$\%&'*+\/=?`\{|\}~^-]+)*@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}\Z/i + :required: true + :display: :edit + :data_type: :string + :request_notes: + :description: Notes + :required: false + :display: :edit + :data_type: :string + :display: :show + :field_order: + :purpose: + :description: Purpose + :fields: + :tag_ids: + :required_method: :validate_tags + :description: Tags + :required: false + :options: + :include: [] + :order: [] + :single_select: [] + :exclude: [] + :display: :edit + :required_tags: [] + :data_type: :integer + :display: :show + :field_order: + :provision: + :description: Provision + :fields: + :verbosity: + :description: Verbosity + :values: + 0: Normal + 1: Verbose + 2: More Verbose + 3: Debug + 4: Connection Debug + 5: WinRM Debug + :notes: + :required: false + :display: :edit + :default: 0 + :data_type: :integer + :log_output: + :description: Log Output + :values: + on_error: On Error + always: Always + never: Never + :notes: + :required: false + :display: :edit + :default: on_error + :data_type: :string + :execution_ttl: + :description: Max TTL (mins) + :notes: + :required: false + :display: :edit + :data_type: :integer + :credential_id: + :values_from: + :method: :allowed_machine_credentials + :description: Machine Credential + :required: true + :display: :edit + :data_type: :integer + :vault_credential_id: + :values_from: + :method: :allowed_vault_credentials + :description: Vault Credential + :required: false + :display: :edit + :data_type: :integer + :cloud_credential_type: + :values_from: + :method: :allowed_cloud_credential_types + :description: Cloud Type + :required: false + :display: :edit + :data_type: :string + :cloud_credential_id: + :values_from: + :method: :allowed_cloud_credentials + :description: Cloud Credential + :required: false + :display: :edit + :data_type: :integer + :display: :show + :service: + :description: Catalog + :fields: + :src_configuration_script_id: + :values_from: + :method: :allowed_configuration_scripts + :description: Configuration Script + :required: true + :notes: + :display: :edit + :data_type: :integer + :notes_display: :show + :display: :show + :schedule: + :description: Schedule + :fields: + :schedule_type: + :values: + schedule: Schedule + immediately: Immediately on Approval + :description: When to Provision + :required: false + :display: :edit + :default: immediately + :data_type: :string + :schedule_time: + :values_from: + :options: + :offset: 1.day + :method: :default_schedule_time + :description: Provision on + :required: false + :display: :edit + :data_type: :time + :display: :show + :dialog_order: + - :requester + - :purpose + - :service + - :provision + - :schedule diff --git a/spec/models/manageiq/providers/embedded_ansible/automation_manager/provision_workflow_spec.rb b/spec/models/manageiq/providers/embedded_ansible/automation_manager/provision_workflow_spec.rb new file mode 100644 index 00000000000..7b3c8df9c9f --- /dev/null +++ b/spec/models/manageiq/providers/embedded_ansible/automation_manager/provision_workflow_spec.rb @@ -0,0 +1,94 @@ +describe ManageIQ::Providers::EmbeddedAnsible::AutomationManager::ProvisionWorkflow do + let(:admin) { FactoryBot.create(:user_with_group) } + let(:manager) { FactoryBot.create(:provider_embedded_ansible, :default_organization => 1).managers.first } + let(:workflow) { described_class.new({}, admin.userid) } + + before do + EvmSpecHelper.assign_embedded_ansible_role + MiqDialog.seed_dialog(Rails.root.join("product/dialogs/miq_dialogs/miq_provision_configuration_script_embedded_ansible_dialogs.yaml")) + end + + describe "#allowed_configuration_scripts" do + let!(:config_script1) { FactoryBot.create(:embedded_ansible_configuration_script, :manager => manager) } + let!(:config_script2) { FactoryBot.create(:embedded_ansible_configuration_script, :manager => manager) } + + it "returns all configuration scripts" do + scripts = workflow.allowed_configuration_scripts + expect(scripts.count).to eq(2) + expect(scripts.map { |s| s[:id] }).to match_array([config_script1.id, config_script2.id]) + end + end + + describe "#allowed_machine_credentials" do + let!(:machine_cred1) { FactoryBot.create(:embedded_ansible_machine_credential, :manager => manager) } + let!(:machine_cred2) { FactoryBot.create(:embedded_ansible_machine_credential, :manager => manager) } + + it "returns all machine credentials" do + credentials = workflow.allowed_machine_credentials + expect(credentials).to be_a(Hash) + expect(credentials.count).to eq(2) + expect(credentials.keys).to match_array([machine_cred1.id, machine_cred2.id]) + expect(credentials.values).to match_array([machine_cred1.name, machine_cred2.name]) + end + end + + describe "#allowed_vault_credentials" do + let!(:vault_cred1) { FactoryBot.create(:embedded_ansible_vault_credential, :manager => manager) } + let!(:vault_cred2) { FactoryBot.create(:embedded_ansible_vault_credential, :manager => manager) } + + it "returns all vault credentials" do + credentials = workflow.allowed_vault_credentials + expect(credentials).to be_a(Hash) + expect(credentials.count).to eq(2) + expect(credentials.keys).to match_array([vault_cred1.id, vault_cred2.id]) + expect(credentials.values).to match_array([vault_cred1.name, vault_cred2.name]) + end + end + + describe "#allowed_cloud_credentials" do + let!(:amazon_cred1) { FactoryBot.create(:embedded_ansible_amazon_credential, :manager => manager) } + let!(:amazon_cred2) { FactoryBot.create(:embedded_ansible_amazon_credential, :manager => manager) } + let!(:azure_cred) { FactoryBot.create(:embedded_ansible_azure_credential, :manager => manager) } + + it "returns all cloud credentials when no type is selected" do + credentials = workflow.allowed_cloud_credentials + + expect(credentials).to be_a(Hash) + expect(credentials.count).to eq(3) + expect(credentials.keys).to match_array([amazon_cred1.id, amazon_cred2.id, azure_cred.id]) + end + + it "returns only credentials of the selected type" do + workflow.instance_variable_set(:@values, {:cloud_credential_type => "ManageIQ::Providers::EmbeddedAnsible::AutomationManager::AmazonCredential"}) + credentials = workflow.allowed_cloud_credentials + + expect(credentials).to be_a(Hash) + expect(credentials.count).to eq(2) + expect(credentials.keys).to match_array([amazon_cred1.id, amazon_cred2.id]) + end + end + + describe "#allowed_cloud_credential_types" do + let!(:amazon_cred) { FactoryBot.create(:embedded_ansible_amazon_credential, :manager => manager) } + let!(:azure_cred) { FactoryBot.create(:embedded_ansible_azure_credential, :manager => manager) } + + it "returns all cloud credential types when no credential is selected" do + types = workflow.allowed_cloud_credential_types + + expect(types).to be_a(Hash) + expect(types.keys).to include( + "ManageIQ::Providers::EmbeddedAnsible::AutomationManager::AmazonCredential", + "ManageIQ::Providers::EmbeddedAnsible::AutomationManager::AzureCredential" + ) + end + + it "returns only the selected credential type when a credential is selected" do + workflow.instance_variable_set(:@values, {:cloud_credential_id => amazon_cred.id}) + types = workflow.allowed_cloud_credential_types + + expect(types).to be_a(Hash) + expect(types.count).to eq(1) + expect(types.keys).to include("ManageIQ::Providers::EmbeddedAnsible::AutomationManager::AmazonCredential") + end + end +end