-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fixes #36971 - GUI to allow cloning of Ansible roles from VCS
- Loading branch information
Showing
8 changed files
with
302 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
# frozen_string_literal: true | ||
|
||
module Proxy | ||
module Ansible | ||
# Implements VCS-Cloning logic and helper functions | ||
class VcsClonerHelper | ||
class << self | ||
DEFAULT_BASE_PATH = Pathname('/var/lib/foreman-proxy/ansible/roles') | ||
|
||
def repo_path(role_name) | ||
DEFAULT_BASE_PATH.join(role_name) | ||
end | ||
|
||
def correct_repo_info(repo_info) | ||
%w[vcs_url name ref].each do |param| | ||
return false unless repo_info.key?(param) | ||
end | ||
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
# frozen_string_literal: true | ||
|
||
require 'test_helper' | ||
require 'git' | ||
|
||
require_relative '../lib/smart_proxy_ansible/vcs_cloner' | ||
require_relative '../lib/smart_proxy_ansible/roles_reader' | ||
|
||
# Tests VCSCloner class | ||
class VcsClonerTest < Minitest::Test | ||
Response = Proxy::Ansible::Response | ||
|
||
describe '#repo_information' do | ||
payload = { | ||
'vcs_url' => 'https://github.com/theforeman/smart_proxy_ansible.git' | ||
} | ||
demo_info = { | ||
'head' => {}, | ||
'branches' => {}, | ||
'tags' => {} | ||
} | ||
test 'requests repo information' do | ||
Git.stubs(:ls_remote).returns(demo_info) | ||
response = Proxy::Ansible::VCSCloner.repo_information payload | ||
assert_equal Response.new(200, payload.merge(demo_info)), response | ||
end | ||
|
||
test 'handles a missing parameter correctly' do | ||
response = Proxy::Ansible::VCSCloner.repo_information({}) | ||
assert_equal Response.new(400, 'Check parameters'), response | ||
end | ||
end | ||
describe '#list_installed_roles' do | ||
demo_roles = %w[role1 role2 role3] | ||
test 'correctly lists installed roles' do | ||
Proxy::Ansible::RolesReader.stubs(:list_roles).returns(demo_roles) | ||
response = Proxy::Ansible::VCSCloner.list_installed_roles | ||
assert_equal Response.new(200, demo_roles), response | ||
end | ||
end | ||
describe '#install' do | ||
demo_repo_info = { | ||
'vcs_url' => 'https://some.git.url', | ||
'name' => 'best.role.ever', | ||
'ref' => 'master' | ||
} | ||
test 'installs a role' do | ||
Proxy::Ansible::VcsClonerHelper.stubs(:role_exists).returns(false) | ||
Proxy::Ansible::VcsClonerHelper.stubs(:install_role).returns(true) | ||
response = Proxy::Ansible::VCSCloner.install demo_repo_info | ||
assert_equal Response.new(201, 'Role "best.role.ever" has been created.'), response | ||
end | ||
|
||
test 'handles a conflict properly' do | ||
Proxy::Ansible::VcsClonerHelper.stubs(:role_exists).returns(true) | ||
response = Proxy::Ansible::VCSCloner.install demo_repo_info | ||
assert_equal Response.new(409, 'Role "best.role.ever" already exists.'), response | ||
end | ||
|
||
test 'handles missing parameter properly' do | ||
response = Proxy::Ansible::VCSCloner.install({ | ||
'name' => 'best.role.ever', | ||
'ref' => 'master' | ||
}) | ||
assert_equal Response.new(400, 'Check parameters'), response | ||
end | ||
test 'handles Git error' do | ||
Proxy::Ansible::VcsClonerHelper.stubs(:install_role).raises(Git::GitExecuteError.new) | ||
response = Proxy::Ansible::VCSCloner.install demo_repo_info | ||
assert_equal Response.new(400, 'Git Error: Git::GitExecuteError'), response | ||
end | ||
end | ||
describe '#update' do | ||
demo_repo_info = { | ||
'vcs_url' => 'https://some.git.url', | ||
'name' => 'best.role.ever', | ||
'ref' => 'master' | ||
} | ||
test 'updates a role' do | ||
Proxy::Ansible::VcsClonerHelper.stubs(:role_exists).returns(true) | ||
Proxy::Ansible::VcsClonerHelper.stubs(:update_role).returns(true) | ||
response = Proxy::Ansible::VCSCloner.update demo_repo_info | ||
assert_equal Response.new(200, 'Role "best.role.ever" has been updated.'), response | ||
end | ||
test 'installs a role' do | ||
Proxy::Ansible::VcsClonerHelper.stubs(:role_exists).returns(false) | ||
Proxy::Ansible::VcsClonerHelper.stubs(:install_role).returns(true) | ||
response = Proxy::Ansible::VCSCloner.update demo_repo_info | ||
assert_equal Response.new(201, 'Role "best.role.ever" has been created.'), response | ||
end | ||
test 'handles missing parameter properly' do | ||
response = Proxy::Ansible::VCSCloner.update({ | ||
'name' => 'best.role.ever', | ||
'ref' => 'master' | ||
}) | ||
assert_equal Response.new(400, 'Check parameters'), response | ||
end | ||
test 'handles Git error' do | ||
Proxy::Ansible::VcsClonerHelper.stubs(:role_exists).returns(true) | ||
Proxy::Ansible::VcsClonerHelper.stubs(:update_role).raises(Git::GitExecuteError.new) | ||
response = Proxy::Ansible::VCSCloner.update demo_repo_info | ||
assert_equal Response.new(400, 'Git Error: Git::GitExecuteError'), response | ||
end | ||
end | ||
describe '#delete' do | ||
test 'deletes a role' do | ||
Proxy::Ansible::VcsClonerHelper.stubs(:role_exists).returns(true) | ||
Proxy::Ansible::VcsClonerHelper.stubs(:delete_role).returns(true) | ||
response = Proxy::Ansible::VCSCloner.delete({ 'role_name' => 'best.role.ever' }) | ||
assert_equal Response.new(200, 'Role "best.role.ever" has been deleted.'), response | ||
end | ||
|
||
test 'skips deleting a role' do | ||
Proxy::Ansible::VcsClonerHelper.stubs(:role_exists).returns(false) | ||
response = Proxy::Ansible::VCSCloner.delete({ 'role_name' => 'best.role.ever' }) | ||
assert_equal Response.new(200, 'Role "best.role.ever" does not exist. Request ignored.'), response | ||
end | ||
test 'handles missing parameter properly' do | ||
response = Proxy::Ansible::VCSCloner.delete({}) | ||
assert_equal Response.new(400, 'Check parameters'), response | ||
end | ||
end | ||
end |