-
Notifications
You must be signed in to change notification settings - Fork 29
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fixes #36971 - GUI to allow cloning of Ansible roles from VCS #85
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,64 @@ | ||
# frozen_string_literal: true | ||
|
||
module Proxy | ||
module Ansible | ||
# Calls for the smart-proxy API to register the plugin | ||
class Plugin < Proxy::Plugin | ||
|
||
def self.runtime_dir | ||
Pathname(ENV['RUNTIME_DIRECTORY'] || '/run').join(plugin_name.to_s) | ||
end | ||
|
||
def self.state_dir | ||
Pathname(ENV['STATE_DIRECTORY'] || '/var/lib/foreman-proxy').join(plugin_name.to_s) | ||
end | ||
|
||
def self.cache_dir | ||
Pathname(ENV['CACHE_DIRECTORY'] || '/var/cache/foreman-proxy').join(plugin_name.to_s) | ||
end | ||
|
||
def self.logs_dir | ||
Pathname(ENV['LOGS_DIRECTORY'] || '/var/logs/foreman-proxy').join(plugin_name.to_s) | ||
end | ||
|
||
def self.config_dir | ||
Pathname(ENV['CONFIGURATION_DIRECTORY'] || '/etc/foreman-proxy').join(plugin_name.to_s) | ||
end | ||
|
||
rackup_path File.expand_path('http_config.ru', __dir__) | ||
settings_file 'ansible.yml' | ||
plugin :ansible, Proxy::Ansible::VERSION | ||
default_settings :ansible_dir => Dir.home, | ||
:ansible_environment_file => '/etc/foreman-proxy/ansible.env' | ||
# :working_dir => nil | ||
default_settings ansible_dir: Dir.home, | ||
ansible_environment_file: '/etc/foreman-proxy/ansible.env', | ||
vcs_integration: true, | ||
static_roles_paths: %w[/etc/ansible/roles /usr/share/ansible/roles] | ||
|
||
load_programmable_settings do |settings| | ||
mutable_roles_path = settings[:mutable_roles_path] || state_dir.join('roles') | ||
system_roles_path = settings[:static_roles_paths].join(':') | ||
if settings[:vcs_integration] | ||
unless Pathname.new(mutable_roles_path).exist? | ||
raise StandardError, | ||
"#{mutable_roles_path} does not exist. Create it or disable vcs_integration" | ||
end | ||
unless File.writable?(mutable_roles_path) | ||
raise StandardError, | ||
"#{mutable_roles_path} is not writable. Check permissions or disable vcs_integration" | ||
end | ||
|
||
settings[:all_roles_path] = "#{mutable_roles_path}:#{system_roles_path}" | ||
settings[:mutable_roles_path] = mutable_roles_path | ||
|
||
else | ||
settings[:all_roles_path] = system_roles_path | ||
end | ||
settings | ||
end | ||
|
||
load_classes ::Proxy::Ansible::ConfigurationLoader | ||
load_validators :validate_settings => ::Proxy::Ansible::ValidateSettings | ||
validate :validate!, :validate_settings => nil | ||
load_validators validate_settings: ::Proxy::Ansible::ValidateSettings | ||
capability :vcs_clone | ||
validate :validate!, validate_settings: nil | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
# frozen_string_literal: true | ||
|
||
require_relative 'vcs_cloner_helper' | ||
require 'net/http' | ||
|
||
module Proxy | ||
include ::Proxy::Log | ||
|
||
module Ansible | ||
Response = Struct.new(:status, :payload) | ||
|
||
# VCSCloner. This class performs cloning and updating of Ansible-Roles sourced from Git | ||
class VCSCloner | ||
class << self | ||
# Queries metadata about a given repository. | ||
# Requires parameter "vcs_url" | ||
# Returns 200 and the data if the query was successful | ||
# Returns 400 if a parameter is unfulfilled or invalid repo-info was provided | ||
def repo_information(payload) | ||
return Response.new(400, 'Check parameters') unless payload.key? 'vcs_url' | ||
|
||
vcs_url = payload['vcs_url'] | ||
remote = Git.ls_remote(vcs_url).slice('head', 'branches', 'tags') | ||
remote['vcs_url'] = vcs_url | ||
Response.new(200, remote) | ||
rescue Git::GitExecuteError => e | ||
Response.new(400, "Git Error: #{e}") | ||
end | ||
|
||
# Returns an array of installed roles | ||
# Uses RolesReader.list_roles | ||
def list_installed_roles | ||
Response.new(200, RolesReader.list_roles) | ||
end | ||
|
||
# Clones a new role from the provided information. | ||
# Requires hash with keys "vcs_url", "name" and "ref" | ||
# Returns 201 if a role was created | ||
# Returns 400 if a parameter is unfulfilled or invalid repo-info was provided | ||
# Returns 409 if a role with "name" already exists | ||
def install(repo_info) | ||
return Response.new(400, 'Check parameters') unless VcsClonerHelper.correct_repo_info(repo_info) | ||
|
||
if VcsClonerHelper.role_exists repo_info['name'] | ||
return Response.new(409, | ||
"Role \"#{repo_info['name']}\" already exists.") | ||
end | ||
|
||
begin VcsClonerHelper.install_role repo_info | ||
Response.new(201, "Role \"#{repo_info['name']}\" has been created.") | ||
rescue Git::GitExecuteError => e | ||
Response.new(400, "Git Error: #{e}") | ||
end | ||
end | ||
|
||
# Updates a role with the provided information. | ||
# Installs a role if it does not yet exist | ||
# Requires hash with keys "vcs_url", "name" and "ref" | ||
# Returns 200 if a role was updated | ||
# Returns 201 if a role was created | ||
# Returns 400 if a parameter is unfulfilled or invalid repo-info was provided | ||
def update(repo_info) | ||
return Response.new(400, 'Check parameters') unless VcsClonerHelper.correct_repo_info repo_info | ||
|
||
begin | ||
if VcsClonerHelper.role_exists repo_info['name'] | ||
VcsClonerHelper.update_role repo_info | ||
Response.new(200, "Role \"#{repo_info['name']}\" has been updated.") | ||
else | ||
VcsClonerHelper.install_role repo_info | ||
Response.new(201, "Role \"#{repo_info['name']}\" has been created.") | ||
end | ||
rescue Git::GitExecuteError => e | ||
Response.new(400, "Git Error: #{e}") | ||
end | ||
end | ||
|
||
# Deletes a role with the given name. | ||
# Installs a role if it does not yet exist | ||
# Requires parameter role_name | ||
# Returns 200 if a role was deleted / never existed | ||
# Returns 400 if a parameter is unfulfilled | ||
def delete(payload) | ||
return Response.new(400, 'Check parameters') unless payload.key? 'role_name' | ||
|
||
role_name = payload['role_name'] | ||
unless VcsClonerHelper.role_exists role_name | ||
return Response.new(200, | ||
"Role \"#{role_name}\" does not exist. Request ignored.") | ||
end | ||
|
||
VcsClonerHelper.delete_role role_name | ||
Response.new(200, "Role \"#{role_name}\" has been deleted.") | ||
end | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
# frozen_string_literal: true | ||
|
||
module Proxy | ||
module Ansible | ||
# Implements VCS-Cloning logic and helper functions | ||
class VcsClonerHelper | ||
class << self | ||
def repo_path(role_name) | ||
@base_path ||= Pathname(Proxy::Ansible::Plugin.settings[:mutable_roles_path]) | ||
@base_path.join(role_name) | ||
end | ||
|
||
def correct_repo_info(repo_info) | ||
%w[vcs_url name ref].all? { |param| repo_info.key?(param) } | ||
end | ||
|
||
def role_exists(role_name) | ||
repo_path(role_name).exist? | ||
end | ||
|
||
def install_role(repo_info) | ||
git = Git.init(repo_path(repo_info['name'])) | ||
git.add_remote('origin', repo_info['vcs_url']) | ||
git.fetch | ||
git.checkout(repo_info['ref']) | ||
end | ||
|
||
def update_role(repo_info) | ||
git = Git.open(repo_path(repo_info['name'])) | ||
git.remove_remote('origin') | ||
git.add_remote('origin', repo_info['vcs_url']) | ||
git.fetch | ||
git.checkout(repo_info['ref']) | ||
end | ||
|
||
def delete_role(role_name) | ||
FileUtils.rm_r repo_path(role_name) | ||
end | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,9 @@ | ||
--- | ||
:enabled: true | ||
:working_dir: '~/.foreman-ansible' | ||
:vcs_integration: true | ||
:static_roles_paths: [ | ||
'/etc/ansible/roles', | ||
'/usr/share/ansible/roles', | ||
] | ||
Comment on lines
+5
to
+8
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this work for you? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are your paths relative perhaps? I just tested it and it works fine with absolute paths, but not relative ones. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My path is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And I'm using my localhost as my smart-proxy. |
||
# :ansible_environment_file: /etc/foreman-proxy/ansible.env |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When setting
:vcs_integration: false
, I can still trigger the role cloning from the GUI. However, the task remains stuck in thepending
status.I believe the intended behavior here is to restrict access to the new form, correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that would be ideal but iirc, the functionality to inform Foreman of enabled/disabled features is not yet a thing as described in the last paragraph of @ekohl's comment here:
#85 (review)