Skip to content

Commit

Permalink
Fixes #36971 - GUI to allow cloning of Ansible roles from VCS
Browse files Browse the repository at this point in the history
  • Loading branch information
Thorben-D committed Dec 29, 2023
1 parent 2870271 commit ae45011
Show file tree
Hide file tree
Showing 10 changed files with 352 additions and 150 deletions.
132 changes: 101 additions & 31 deletions app/controllers/api/v2/vcs_clone_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,72 +9,142 @@ class VcsCloneController < ::Api::V2::BaseController
render json: { 'error' => e.message }, status: :bad_request
end

rescue_from Git::GitExecuteError do |_e|
rescue_from Foreman::Exception do |_e|
head :internal_server_error
end

api :GET, '/vcs_clone/get_repo_info', N_('Returns information about the repo')
skip_before_action :verify_authenticity_token

before_action :set_proxy_api

api :GET, '/smart_proxies/:proxy_name/ansible/vcs_clone/repo_information',
N_('Queries metadata about the repo')
param :proxy_name, Array, N_('Name of the SmartProxy'), :required => true
param :vcs_url, String, N_('Url of the repo'), :required => true
error 400, :desc => N_('Parameter unfulfilled')
error 500, :desc => N_('Git error')
error 400, :desc => N_('Parameter unfulfilled / invalid repo-info')
def repo_information
vcs_url = params.require(:vcs_url)
remote = Git.ls_remote(vcs_url).slice('head', 'branches', 'tags')
remote['vcs_url'] = vcs_url
render json: remote
render json: @proxy_api.repo_information(vcs_url)
end

api :GET, '/vcs_clone/get_installed_roles', N_('Returns an array of roles installed on the provided proxy')
api :GET, '/smart_proxies/:proxy_name/ansible/vcs_clone/roles',
N_('Returns an array of roles installed on the provided proxy')
formats ['json']
param :smart_proxy, Array, N_('Name of the SmartProxy'), :required => true
param :proxy_name, Array, N_('Name of the SmartProxy'), :required => true
error 400, :desc => N_('Parameter unfulfilled')
error 500, :desc => N_('Internal server error')
def installed_roles
smart_proxy = params.require(:smart_proxy)
ansible_proxy = SmartProxy.find_by(name: smart_proxy)
proxy_api = find_proxy_api(ansible_proxy)
installed = proxy_api.list_installed
render json: installed
rescue Foreman::Exception
head :internal_server_error
render json: @proxy_api.list_installed
end

api :POST, '/vcs_clone/install', N_('Launches a task to install the provided role')
api :POST, '/smart_proxies/:proxy_name/ansible/vcs_clone/roles',
N_('Launches a task to install the provided role')
formats ['json']
param :repo_info, Hash, :desc => N_('Dictionary containing info about the role to be installed') do
param :vcs_url, String, :desc => N_('Url of the repo'), :required => true
param :name, String, :desc => N_('Name of the repo'), :required => true
param :ref, String, :desc => N_('Branch / Tag / Commit reference'), :required => true
param :update, String, :desc => N_('Whether an existing role with the provided name should be updated'), :default => false
end
param :smart_proxy, Array, N_('Array of SmartProxies the role should get installed to')
param :smart_proxy, Array, N_('SmartProxy the role should get installed to')
error 400, :desc => N_('Parameter unfulfilled')
error 500, :desc => N_('Internal server error')
def install_role
payload = verify_task_parameters(params)
start_vcs_task(payload, :install)
end

api :PUT, '/smart_proxies/:proxy_name/ansible/vcs_clone/roles',
N_('Launches a task to update the provided role')
formats ['json']
param :repo_info, Hash, :desc => N_('Dictionary containing info about the role to be installed') do
param :vcs_url, String, :desc => N_('Url of the repo'), :required => true
param :name, String, :desc => N_('Name of the repo'), :required => true
param :ref, String, :desc => N_('Branch / Tag / Commit reference'), :required => true
end
param :smart_proxy, Array, N_('SmartProxy the role should get installed to')
error 400, :desc => N_('Parameter unfulfilled')
def update_role
payload = verify_task_parameters(params)
start_vcs_task(payload, :update)
end

api :DELETE, '/smart_proxies/:proxy_name/ansible/vcs_clone/roles/:role_name',
N_('Launches a task to delete the provided role')
formats ['json']
param :role_name, String, :desc => N_('Name of the role that should be deleted')
param :smart_proxy, Array, N_('SmartProxy the role should get deleted from')
error 400, :desc => N_('Parameter unfulfilled')
def delete_role
payload = params.require(:role_name)
start_vcs_task(payload, :delete)
end

private

def set_proxy_api
unless params[:id]
msg = _('Smart proxy id is required')
return render_error('custom_error', :status => :unprocessable_entity, :locals => { :message => msg })
end
ansible_proxy = SmartProxy.find_by(name: params[:id])
if ansible_proxy.nil?
msg = _('Smart proxy does not exist')
return render_error('custom_error', :status => :bad_request, :locals => { :message => msg })
else unless ansible_proxy.has_capability?('Ansible', 'vcs_clone')
msg = _('Smart proxy does not have foreman_ansible installed / is not capable of cloning from VCS')
return render_error('custom_error', :status => :bad_request, :locals => { :message => msg })
end
end
@proxy = ansible_proxy
@proxy_api = find_proxy_api(ansible_proxy)
end

def verify_task_parameters(params)
payload = params.require(:vcs_clone).
permit(
repo_info: [
:vcs_url,
:name,
:ref,
:update
],
smart_proxy: []
)

payload['repo_info']['update'] = false unless payload['repo_info'].key? 'update'
:ref
]
).to_h
%w[vcs_url name ref].each do |param|
raise ActionController::ParameterMissing.new(param) unless payload['repo_info'].key?(param)
end
payload
end

# 'smart_proxy' is an array to later allow a role to be installed on multiple SPs
ansible_proxy = SmartProxy.find_by(name: payload['smart_proxy'][0])
def start_vcs_task(op_info, operation)
case operation
when :update
job = UpdateAnsibleRole.perform_later(op_info, @proxy)
when :install
job = CloneAnsibleRole.perform_later(op_info, @proxy)
when :delete
job = DeleteAnsibleRole.perform_later(op_info, @proxy)
else
raise Foreman::Exception.new(N_('Unsupported operation'))
end

job = CloneAnsibleRole.perform_later(payload['repo_info'], ansible_proxy)
task = ForemanTasks::Task.find_by(external_id: job.provider_job_id)

render json: {
task: task
}, status: :ok
rescue Foreman::Exception
head :internal_server_error
end

private

def set_proxy_api
unless params[:id]
msg = _('Smart proxy id is required')
return render_error('custom_error', :status => :unprocessable_entity, :locals => { :message => msg })
end
ansible_proxy = SmartProxy.find_by(id: params[:id])
raise Foreman::Exception.new(N_('Proxy does not support cloning from VCS')) unless ansible_proxy.has_capability?('Ansible', 'vcs_clone')
@proxy_api = find_proxy_api(ansible_proxy)
end

end
end
end
2 changes: 1 addition & 1 deletion app/jobs/clone_ansible_role.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ class CloneAnsibleRole < ::ApplicationJob
queue_as :default

def humanized_name
'Clone Ansible Role from VCS'
_('Clone Ansible Role from VCS')
end

def perform(repo_info, proxy)
Expand Down
14 changes: 14 additions & 0 deletions app/jobs/delete_ansible_role.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# frozen_string_literal: true

class DeleteAnsibleRole < ::ApplicationJob
queue_as :default

def humanized_name
_('Delete Ansible Role')
end

def perform(role_name, proxy)
vcs_cloner = ForemanAnsible::VcsCloner.new(proxy)
vcs_cloner.delete_role role_name
end
end
14 changes: 14 additions & 0 deletions app/jobs/update_ansible_role.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# frozen_string_literal: true

class UpdateAnsibleRole < ::ApplicationJob
queue_as :default

def humanized_name
_('Update Ansible Role from VCS')
end

def perform(repo_info, proxy)
vcs_cloner = ForemanAnsible::VcsCloner.new(proxy)
vcs_cloner.update_role repo_info
end
end
42 changes: 39 additions & 3 deletions app/lib/proxy_api/ansible.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,24 +54,60 @@ def playbooks(playbooks_names = [])
raise ProxyException.new(url, e, N_('Unable to get playbooks from Ansible'))
end

def repo_information(vcs_url)
parse(get("vcs_clone/repo_information?vcs_url=#{vcs_url}"))
rescue *PROXY_ERRORS, RestClient::Exception => e
raise e unless e.is_a? RestClient::RequestFailed
case e.http_code
when 400
raise Foreman::Exception.new N_('Error requesting repo-info. Check Smartproxy log.')
else
raise e
end
end

def list_installed
parse(get('vcs_clone/get_installed'))
rescue *PROXY_ERRORS
raise Foreman::Exception.new N_('Error requesting installed roles. Check log.')
end

def vcs_clone_install(repo_info)
def install_role(repo_info)
parse(post(repo_info, 'vcs_clone/install'))
rescue *PROXY_ERRORS, RestClient::Exception => e
raise e unless e.is_a? RestClient::RequestFailed
case e.http_code
when 409
raise Foreman::Exception.new N_('A repo with the name %rName already exists.') % repo_info['repo_name']
when 500
raise Foreman::Exception.new N_('A repo with the name %rName already exists.') % repo_info['name']
when 400
raise Foreman::Exception.new N_('Git Error. Check log.')
else
raise e
end
end

def update_role(repo_info)
parse(put(repo_info, 'vcs_clone/update'))
rescue *PROXY_ERRORS, RestClient::Exception => e
raise e unless e.is_a? RestClient::RequestFailed
case e.http_code
when 400
raise Foreman::Exception.new N_('Error updating %rName. Check Smartproxy log.') % repo_info['name']
else
raise e
end
end

def delete_role(role_name)
parse(delete("vcs_clone/delete/#{role_name}"))
rescue *PROXY_ERRORS, RestClient::Exception => e
raise e unless e.is_a? RestClient::RequestFailed
case e.http_code
when 400
raise Foreman::Exception.new N_('Error deleting %rName. Check Smartproxy log.') % role_name
else
raise e
end
end
end
end
8 changes: 5 additions & 3 deletions app/services/foreman_ansible/vcs_cloner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ def initialize(proxy = nil)
@ansible_proxy = proxy
end

def install_role(info)
proxy_api.vcs_clone_install info
end
delegate :install_role, to: :proxy_api

delegate :update_role, to: :proxy_api

delegate :delete_role, to: :proxy_api
end
end
19 changes: 11 additions & 8 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,17 @@
post :multiple_play_roles
end
end
resources :smart_proxies, :only => [] do
member do
scope '/ansible' do
get 'repo_information', to: 'vcs_clone#repo_information'
get 'roles', to: 'vcs_clone#installed_roles'
post 'roles', to: 'vcs_clone#install_role'
put 'roles', to: 'vcs_clone#update_role'
delete 'roles/:role_name', to: 'vcs_clone#delete_role', constraints: { role_name: %r{[^\/]+} }
end
end
end
end
end
end
Expand Down Expand Up @@ -100,14 +111,6 @@
end
end

resources :vcs_clone, :only => [] do
collection do
get 'get_repo_information', to: 'vcs_clone#repo_information'
post 'install_role', to: 'vcs_clone#install_role'
get 'get_installed_roles', to: 'vcs_clone#installed_roles'
end
end

resources :ansible_override_values, :only => [:create, :destroy]

resources :ansible_inventories, :only => [] do
Expand Down
4 changes: 2 additions & 2 deletions lib/foreman_ansible/register.rb
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@
permission :import_ansible_playbooks,
{ :'api/v2/ansible_playbooks' => [:sync, :fetch] }
permission :clone_from_vcs,
{ :'api/v2/vcs_clone' => [:repo_information, :installed_roles, :install_role]}
{ :'api/v2/vcs_clone' => [:repo_information, :installed_roles, :install_role, :update_role, :delete_role] }
end

role 'Ansible Roles Manager',
Expand All @@ -172,7 +172,7 @@
:import_ansible_roles, :view_ansible_variables, :view_lookup_values,
:create_lookup_values, :edit_lookup_values, :destroy_lookup_values,
:create_ansible_variables, :import_ansible_variables,
:edit_ansible_variables, :destroy_ansible_variables, :import_ansible_playbooks]
:edit_ansible_variables, :destroy_ansible_variables, :import_ansible_playbooks, :clone_from_vcs]

role 'Ansible Tower Inventory Reader',
[:view_hosts, :view_hostgroups, :view_facts, :generate_report_templates, :generate_ansible_inventory,
Expand Down
Loading

0 comments on commit ae45011

Please sign in to comment.