src/api/app/models/project/update_from_xml_command.rb
class Project
class UpdateFromXmlCommand
include Project::Errors
attr_reader :project
def initialize(project)
@project = project
end
def run(xmlhash, force = nil)
project.check_write_access!
# check for raising read access permissions, which can't get ensured atm
raise ForbiddenError if !(project.new_record? || project.disabled_for?('access', nil, nil)) && (FlagHelper.xml_disabled_for?(xmlhash, 'access') && !User.admin_session?)
raise ForbiddenError if !(project.new_record? || project.disabled_for?('sourceaccess', nil, nil)) && (FlagHelper.xml_disabled_for?(xmlhash, 'sourceaccess') && !User.admin_session?)
new_record = project.new_record?
if ::Configuration.default_access_disabled == true && !new_record && (project.disabled_for?('access', nil,
nil) && !FlagHelper.xml_disabled_for?(xmlhash, 'access') && !User.admin_session?)
raise ForbiddenError
end
raise SaveError, "project name mismatch: #{project.name} != #{xmlhash['name']}" if project.name != xmlhash['name']
project.title = xmlhash.value('title')
project.description = xmlhash.value('description')
project.url = xmlhash.value('url')
project.remoteurl = xmlhash.value('remoteurl')
project.remoteproject = xmlhash.value('remoteproject')
project.scmsync = xmlhash.value('scmsync')
project.kind = xmlhash.value('kind') if xmlhash.value('kind').present?
#--- update flag group ---#
project.update_all_flags(xmlhash)
if ::Configuration.default_access_disabled == true && new_record && xmlhash.elements('access').empty?
# write a default access disable flag by default in this mode for projects if not defined
project.flags.new(status: 'disable', flag: 'access')
end
project.save!
update_linked_projects(xmlhash)
parse_develproject(xmlhash)
update_maintained_prjs_from_xml(xmlhash)
project.update_relationships_from_xml(xmlhash)
update_repositories(xmlhash, force)
end
private
# rubocop:disable Style/GuardClause
def update_linked_projects(xmlhash)
position = 1
# destroy all current linked projects
project.linking_to.destroy_all
# recreate linked projects from xml
xmlhash.elements('link') do |l|
link = Project.find_by_name(l['project'])
if link.nil?
if Project.find_remote_project(l['project'])
project.linking_to.create!(project: project,
linked_remote_project_name: l['project'],
vrevmode: l['vrevmode'],
position: position)
else
raise SaveError, "unable to link against project '#{l['project']}'"
end
else
raise SaveError, 'unable to link against myself' if link == project
project.linking_to.create!(project: project,
linked_db_project: link,
vrevmode: l['vrevmode'],
position: position)
end
position += 1
end
position
end
# rubocop:enable Style/GuardClause
def parse_develproject(xmlhash)
project.develproject = nil
devel = xmlhash['devel']
if devel
prj_name = devel['project']
if prj_name
develprj = Project.get_by_name(prj_name)
raise SaveError, "value of develproject has to be a existing project (project '#{prj_name}' does not exist)" unless develprj
raise SaveError, 'Devel project can not point to itself' if develprj == project
project.develproject = develprj
end
end
# cycle detection
prj = project
processed = {}
while prj && prj.develproject
raise CycleError, "There is a cycle in devel definition at #{processed.keys.join(' -- ')}" if processed[prj.name]
processed[prj.name] = 1
prj = prj.develproject
prj = project if prj && prj.id == project.id
end
end
def update_maintained_prjs_from_xml(xmlhash)
# First check all current maintained project relations
olds = {}
project.maintained_projects.each { |mp| olds[mp.project.name] = mp }
# Set this project as the maintenance project for all maintained projects found in the XML
xmlhash.get('maintenance').elements('maintains') do |maintains|
pn = maintains['project']
next if olds.delete(pn)
maintained_project = Project.get_by_name(pn)
MaintainedProject.create(project: maintained_project, maintenance_project: project)
end
project.maintained_projects.delete(olds.values)
end
def update_repositories(xmlhash, force)
fill_repo_cache
xmlhash.elements('repository') do |repo_xml_hash|
update_repository_without_path_element(repo_xml_hash)
end
# Some repositories might be refered by path elements before they appear in the
# xml tree. Thus we have 2 iterations. First one goes through all repository
# elements, second run handles path elements.
# This can be the case when creating multiple repositories in a project where one
# repository uses another one, eg. importing an existing config from elsewhere.
xmlhash.elements('repository') do |repo|
current_repo = project.repositories.find_by_name(repo['name'])
update_path_elements(current_repo, repo)
end
# delete remaining repositories in @repocache
@repocache.each do |name, object|
Rails.logger.debug { "offending repo: #{object.inspect}" }
unless force
# find repositories that link against this one and issue warning if found
list = PathElement.where(repository_id: object.id)
check_for_empty_repo_list(list, "Repository #{project.name}/#{name} cannot be deleted because following repos link against it:")
list = ReleaseTarget.where(target_repository_id: object.id)
check_for_empty_repo_list(
list,
"Repository #{project.name}/#{name} cannot be deleted because following repos define it as release target:/"
)
end
Rails.logger.debug { "deleting repository '#{name}'" }
project.repositories.destroy(object)
end
# save memory
@repocache = nil
end
def fill_repo_cache
@repocache = {}
project.repositories.each do |repo|
@repocache[repo.name] = repo if repo.remote_project_name.blank?
end
end
def update_repository_without_path_element(xml_hash)
current_repo = @repocache[xml_hash['name']]
unless current_repo
Rails.logger.debug { "adding repository '#{xml_hash['name']}'" }
current_repo = project.repositories.new(name: xml_hash['name'])
end
Rails.logger.debug { "modifying repository '#{xml_hash['name']}'" }
update_repository_flags(current_repo, xml_hash)
update_release_targets(current_repo, xml_hash)
current_repo.save! if current_repo.changed?
update_repository_architectures(current_repo, xml_hash)
update_download_repositories(current_repo, xml_hash)
current_repo.save!
@repocache.delete(xml_hash['name'])
end
def update_path_elements(current_repo, xml_hash)
# destroy all current pathelements
current_repo.path_elements.destroy_all
return unless xml_hash['path'] || xml_hash['hostsystem']
# recreate hostsystem elements from xml
position = 1
xml_hash.elements('hostsystem') do |hostsystem|
host_repo = Repository.find_by_project_and_name(hostsystem['project'], hostsystem['repository'])
raise SaveError, 'Using same repository as hostsystem element is not allowed' if hostsystem['project'] == project.name && hostsystem['repository'] == xml_hash['name']
raise SaveError, "Unknown hostsystem repository '#{hostsystem['project']}/#{hostsystem['repository']}'" unless host_repo
current_repo.path_elements.new(link: host_repo, position: position, kind: :hostsystem)
position += 1
end
# recreate path elements from xml
position = 1
xml_hash.elements('path') do |path|
link_repo = Repository.find_by_project_and_name(path['project'], path['repository'])
raise SaveError, 'Using same repository as path element is not allowed' if path['project'] == project.name && path['repository'] == xml_hash['name']
raise SaveError, "Cannot find repository '#{path['project']}/#{path['repository']}'" unless link_repo
current_repo.path_elements.new(link: link_repo, position: position)
position += 1
end
current_repo.save!
end
def check_for_empty_repo_list(list, error_prefix)
return if list.empty?
linking_repos = list.map { |x| "#{x.repository.project.name}/#{x.repository.name}" }.join("\n")
raise SaveError, "#{error_prefix}\n#{linking_repos}"
end
def update_repository_flags(current_repo, xml_hash)
current_repo.rebuild = xml_hash['rebuild']
current_repo.block = xml_hash['block']
current_repo.linkedbuild = xml_hash['linkedbuild']
end
def update_release_targets(current_repo, xml_hash)
# destroy all current releasetargets
current_repo.release_targets.destroy_all
# recreate release targets from xml
xml_hash.elements('releasetarget') do |release_target|
project = Project.find_by(name: release_target['project'])
repository = release_target['repository']
trigger = release_target['trigger']
raise SaveError, "Project '#{release_target['project']}' does not exist." unless project
raise SaveError, "Can not use remote repository as release target '#{project}/#{repository}'" if project.defines_remote_instance?
target_repo = Repository.find_by_project_and_name(project.name, repository)
raise SaveError, "Unknown target repository '#{project}/#{repository}'" unless target_repo
current_repo.release_targets.new(target_repository: target_repo, trigger: trigger)
end
end
def check_for_duplicated_archs!(architectures)
duplicated_architectures = architectures.uniq.select { |architecture| architectures.count(architecture) > 1 }
return if duplicated_architectures.empty?
raise SaveError, "double use of architecture: '#{duplicated_architectures.first}'"
end
def update_repository_architectures(current_repo, xml_hash)
xml_archs = xml_hash.elements('arch')
check_for_duplicated_archs!(xml_archs)
architectures = []
xml_archs.each_with_index do |archname, position|
architecture = Architecture.from_cache!(archname)
current_repo.repository_architectures.find_or_create_by(architecture: architecture).insert_at(position)
architectures << architecture
end
current_repo.repository_architectures.where.not(architecture: architectures).delete_all
end
def update_download_repositories(current_repo, xml_hash)
current_repo.download_repositories.delete_all
dod_repositories = xml_hash.elements('download').map do |dod|
dod_attributes = {
repository: current_repo,
arch: dod['arch'],
url: dod['url'],
repotype: dod['repotype'],
archfilter: dod['archfilter'],
pubkey: dod['pubkey']
}
if dod['master']
dod_attributes[:masterurl] = dod['master']['url']
dod_attributes[:mastersslfingerprint] = dod['master']['sslfingerprint'] if dod['master']['sslfingerprint'].present?
end
repository = DownloadRepository.new(dod_attributes)
raise SaveError, repository.errors.full_messages.to_sentence unless repository.valid?
repository
end
current_repo.download_repositories.replace(dod_repositories)
end
end
end