Init version of package manager backend.
This commit is contained in:
parent
ef2f55cfee
commit
e4dd4a0d5d
4 changed files with 531 additions and 0 deletions
296
app/models/package.rb
Normal file
296
app/models/package.rb
Normal file
|
@ -0,0 +1,296 @@
|
|||
require 'rexml/document'
|
||||
class Package < ApplicationModel
|
||||
@@root = Rails.root.to_s
|
||||
|
||||
def self.build_file(file)
|
||||
xml = self._read_file(file, true)
|
||||
package = self._parse(xml)
|
||||
self.build(package)
|
||||
end
|
||||
|
||||
def self.build(package)
|
||||
package.elements.each('zpm/filelist/file') do |element|
|
||||
location = element.attributes['location']
|
||||
content = self._read_file(location)
|
||||
base64 = Base64.encode64(content)
|
||||
element.text = base64
|
||||
end
|
||||
return package.to_s
|
||||
end
|
||||
|
||||
def self.install_file(file)
|
||||
xml = self._read_file(file)
|
||||
package = self._parse(xml)
|
||||
self.install(package)
|
||||
end
|
||||
|
||||
def self.install_string(xml)
|
||||
package = self._parse(xml)
|
||||
self.install(package)
|
||||
end
|
||||
|
||||
def self.install(package)
|
||||
|
||||
# package meta data
|
||||
data = {
|
||||
:name => package.elements["zpm/name"].text,
|
||||
:version => package.elements["zpm/version"].text,
|
||||
:vendor => package.elements["zpm/vendor"].text,
|
||||
:state => 'uninstalled',
|
||||
:created_by_id => 1,
|
||||
:updated_by_id => 1,
|
||||
}
|
||||
|
||||
# verify if package can get installed
|
||||
package_db = Package.where( :name => data[:name] ).first
|
||||
if package_db
|
||||
if Gem::Version.new( package_db.version ) == Gem::Version.new( data[:version] )
|
||||
raise "Package '#{data[:name]}' already installed!"
|
||||
end
|
||||
if Gem::Version.new( package_db.version ) > Gem::Version.new( data[:version] )
|
||||
raise "Newer version (#{package_db.version}) of package '#{data[:name]}-#{data[:version]}' already installed!"
|
||||
end
|
||||
|
||||
# uninstall old package
|
||||
self.uninstall_name( package_db.name, package_db.version, false )
|
||||
end
|
||||
|
||||
# store package
|
||||
record = Package.create( data )
|
||||
Store.add(
|
||||
:object => 'Package',
|
||||
:o_id => record.id,
|
||||
:data => package.to_s,
|
||||
:filename => data[:name] + '-' + data[:version] + '.zpm',
|
||||
:preferences => {},
|
||||
)
|
||||
|
||||
# write files
|
||||
package.elements.each('zpm/filelist/file') do |element|
|
||||
location = element.attributes['location']
|
||||
permission = element.attributes['permission'] || '644'
|
||||
base64 = element.text
|
||||
content = Base64.decode64(base64)
|
||||
content = self._write_file(location, permission, content)
|
||||
end
|
||||
|
||||
# update package state
|
||||
record.state = 'installed'
|
||||
record.save
|
||||
|
||||
# up migrations
|
||||
Package::Migration.migrate( data[:name] )
|
||||
|
||||
# prebuild assets
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
def self.uninstall_name( name, version, migration_down = true )
|
||||
file = self._get_bin( name, version )
|
||||
package = self._parse(file)
|
||||
self.uninstall( package, migration_down )
|
||||
end
|
||||
|
||||
def self.uninstall_string(xml)
|
||||
package = self._parse(xml)
|
||||
self.uninstall(package)
|
||||
end
|
||||
|
||||
def self.uninstall( package, migration_down = true )
|
||||
|
||||
# package meta data
|
||||
data = {
|
||||
:name => package.elements["zpm/name"].text,
|
||||
:version => package.elements["zpm/version"].text,
|
||||
}
|
||||
|
||||
# down migrations
|
||||
if migration_down
|
||||
Package::Migration.migrate( data[:name], 'reverse' )
|
||||
end
|
||||
|
||||
package.elements.each('zpm/filelist/file') do |element|
|
||||
location = element.attributes['location']
|
||||
permission = element.attributes['permission'] || '644'
|
||||
base64 = element.text
|
||||
content = Base64.decode64(base64)
|
||||
content = self._delete_file(location, permission, content)
|
||||
end
|
||||
|
||||
# prebuild assets
|
||||
|
||||
# delete package
|
||||
record = Package.where(
|
||||
:name => data[:name],
|
||||
:version => data[:version],
|
||||
).first
|
||||
record.destroy
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
def self._parse(xml)
|
||||
# puts xml.inspect
|
||||
begin
|
||||
package = REXML::Document.new( xml )
|
||||
rescue => e
|
||||
puts 'ERROR: ' + e.inspect
|
||||
return
|
||||
end
|
||||
# puts package.inspect
|
||||
return package
|
||||
end
|
||||
|
||||
def self._get_bin( name, version )
|
||||
package = Package.where(
|
||||
:name => name,
|
||||
:version => version,
|
||||
).first
|
||||
if !package
|
||||
raise "No such package '#{name}' version '#{version}'"
|
||||
end
|
||||
list = Store.list(
|
||||
:object => 'Package',
|
||||
:o_id => package.id,
|
||||
)
|
||||
|
||||
# find file
|
||||
return if !list
|
||||
list.first.store_file.data
|
||||
end
|
||||
|
||||
def self._read_file(file, fullpath = false)
|
||||
if !fullpath
|
||||
location = @@root + '/' + file
|
||||
else
|
||||
location = file
|
||||
end
|
||||
|
||||
begin
|
||||
data = File.open( location, 'rb' )
|
||||
contents = data.read
|
||||
rescue => e
|
||||
raise 'ERROR: ' + e.inspect
|
||||
end
|
||||
return contents
|
||||
end
|
||||
|
||||
def self._write_file(file, permission, data)
|
||||
location = @@root + '/' + file
|
||||
|
||||
# rename existing file
|
||||
if File.exist?( location )
|
||||
backup_location = location + '.save'
|
||||
puts "NOTICE: backup old file '#{location}' to #{backup_location}"
|
||||
File.rename( location, backup_location )
|
||||
end
|
||||
|
||||
# check if directories need to be created
|
||||
directories = location.split '/'
|
||||
(0..(directories.length-2) ).each {|position|
|
||||
tmp_path = ''
|
||||
(1..position).each {|count|
|
||||
tmp_path = tmp_path + '/' + directories[count].to_s
|
||||
}
|
||||
if tmp_path != ''
|
||||
if !File.exist?(tmp_path)
|
||||
Dir.mkdir( tmp_path, 0755)
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
# install file
|
||||
begin
|
||||
puts "NOTICE: install '#{location}' (#{permission})"
|
||||
file = File.new( location, 'wb' )
|
||||
file.write( data )
|
||||
file.close
|
||||
File.chmod( permission.to_i(8), location )
|
||||
rescue => e
|
||||
raise 'ERROR: ' + e.inspect
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
def self._delete_file(file, permission, data)
|
||||
location = @@root + '/' + file
|
||||
|
||||
# install file
|
||||
puts "NOTICE: uninstall '#{location}'"
|
||||
File.delete( location )
|
||||
|
||||
# rename existing file
|
||||
backup_location = location + '.save'
|
||||
if File.exist?( backup_location )
|
||||
puts "NOTICE: restore old file '#{backup_location}' to #{location}"
|
||||
File.rename( backup_location, location )
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
class Migration < ApplicationModel
|
||||
@@root = Rails.root.to_s
|
||||
|
||||
def self.migrate(package, direction = 'normal')
|
||||
location = @@root + '/db/addon/' + package.underscore
|
||||
|
||||
return true if !File.exists?( location )
|
||||
migrations_done = Package::Migration.where( :name => package )
|
||||
|
||||
# get existing migrations
|
||||
migrations_existing = []
|
||||
Dir.foreach(location) {|entry|
|
||||
next if entry == '.'
|
||||
next if entry == '..'
|
||||
migrations_existing.push entry
|
||||
}
|
||||
|
||||
# up
|
||||
migrations_existing = migrations_existing.sort
|
||||
|
||||
# down
|
||||
if direction == 'reverse'
|
||||
migrations_existing = migrations_existing.reverse
|
||||
end
|
||||
|
||||
migrations_existing.each {|migration|
|
||||
next if migration !~ /\.rb$/
|
||||
version = nil
|
||||
name = nil
|
||||
if migration =~ /^(.+?)_(.*)\.rb$/
|
||||
version = $1
|
||||
name = $2
|
||||
end
|
||||
if !version || !name
|
||||
raise "Invalid package migration '#{migration}'"
|
||||
end
|
||||
|
||||
# down
|
||||
if direction == 'reverse'
|
||||
done = Package::Migration.where( :name => name, :version => version ).first
|
||||
next if !done
|
||||
puts "NOTICE: down package migration '#{migration}'"
|
||||
load "#{location}/#{migration}"
|
||||
classname = name.camelcase
|
||||
Kernel.const_get(classname).down
|
||||
record = Package::Migration.where( :name => name, :version => version ).first
|
||||
if record
|
||||
record.destroy
|
||||
end
|
||||
|
||||
# up
|
||||
else
|
||||
done = Package::Migration.where( :name => name, :version => version ).first
|
||||
next if done
|
||||
puts "NOTICE: up package migration '#{migration}'"
|
||||
load "#{location}/#{migration}"
|
||||
classname = name.camelcase
|
||||
Kernel.const_get(classname).up
|
||||
Package::Migration.create( :name => name, :version => version )
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
23
db/migrate/20121225162100_create_package.rb
Normal file
23
db/migrate/20121225162100_create_package.rb
Normal file
|
@ -0,0 +1,23 @@
|
|||
class CreatePackage < ActiveRecord::Migration
|
||||
def up
|
||||
create_table :packages do |t|
|
||||
t.column :name, :string, :limit => 250, :null => false
|
||||
t.column :version, :string, :limit => 50, :null => false
|
||||
t.column :vendor, :string, :limit => 150, :null => false
|
||||
t.column :state, :string, :limit => 50, :null => false
|
||||
t.column :updated_by_id, :integer, :null => false
|
||||
t.column :created_by_id, :integer, :null => false
|
||||
t.timestamps
|
||||
end
|
||||
create_table :package_migrations do |t|
|
||||
t.column :name, :string, :limit => 250, :null => false
|
||||
t.column :version, :string, :limit => 250, :null => false
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
drop_table :packages
|
||||
drop_table :package_migrations
|
||||
end
|
||||
end
|
|
@ -13,6 +13,9 @@ class ActiveSupport::TestCase
|
|||
SimpleCov.start
|
||||
fixtures :all
|
||||
|
||||
# disable transactions
|
||||
self.use_transactional_fixtures = false
|
||||
|
||||
# load seeds
|
||||
load "#{Rails.root}/db/seeds.rb"
|
||||
|
||||
|
|
209
test/unit/package_test.rb
Normal file
209
test/unit/package_test.rb
Normal file
|
@ -0,0 +1,209 @@
|
|||
# encoding: utf-8
|
||||
require 'test_helper'
|
||||
|
||||
class PackageTest < ActiveSupport::TestCase
|
||||
test 'packages' do
|
||||
tests = [
|
||||
|
||||
# test 1
|
||||
{
|
||||
:zpm => '<?xml version="1.0"?>
|
||||
<zpm version="1.0">
|
||||
<name>UnitTestSample</name>
|
||||
<version>1.0.1</version>
|
||||
<vendor>Znuny GmbH</vendor>
|
||||
<url>http://znuny.org/</url>
|
||||
<license>ABC</license>
|
||||
<description lang="en">some description</description>
|
||||
<filelist>
|
||||
<file permission="644" location="test.txt">YWJjw6TDtsO8w58=</file>
|
||||
<file permission="644" location="some/dir/test.txt">YWJjw6TDtsO8w58=</file>
|
||||
<file permission="644" location="db/addon/unit_test_sample/20121212000001_create_base.rb">Y2xhc3MgQ3JlYXRlQmFzZSA8IEFjdGl2ZVJlY29yZDo6TWlncmF0aW9uDQogIGRlZiBzZWxmLnVw
|
||||
DQogICBjcmVhdGVfdGFibGUgOnNhbXBsZV90YWJsZXMgZG8gfHR8DQogICAgICB0LmNvbHVtbiA6
|
||||
bmFtZSwgICAgICAgICAgIDpzdHJpbmcsIDpsaW1pdCA9PiAxNTAsICA6bnVsbCA9PiB0cnVlDQog
|
||||
ICAgICB0LmNvbHVtbiA6ZGF0YSwgICAgICAgICAgIDpzdHJpbmcsIDpsaW1pdCA9PiA1MDAwLCA6
|
||||
bnVsbCA9PiB0cnVlDQogICAgZW5kDQogIGVuZA0KDQogIGRlZiBzZWxmLmRvd24NCiAgICBkcm9w
|
||||
X3RhYmxlIDpzYW1wbGVfdGFibGVzDQogIGVuZA0KZW5k</file>
|
||||
</filelist>
|
||||
</zpm>',
|
||||
:action => 'install',
|
||||
:result => true,
|
||||
:verify => {
|
||||
:package => {
|
||||
:name => 'UnitTestSample',
|
||||
:version => '1.0.1',
|
||||
},
|
||||
:check_files => [
|
||||
{
|
||||
:location => 'test.txt',
|
||||
:result => true,
|
||||
},
|
||||
{
|
||||
:location => 'test2.txt',
|
||||
:result => false,
|
||||
},
|
||||
{
|
||||
:location => 'some/dir/test.txt',
|
||||
:result => true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
# test 2
|
||||
{
|
||||
:zpm => '<?xml version="1.0"?>
|
||||
<zpm version="1.0">
|
||||
<name>UnitTestSample</name>
|
||||
<version>1.0.1</version>
|
||||
<vendor>Znuny GmbH</vendor>
|
||||
<url>http://znuny.org/</url>
|
||||
<license>ABC</license>
|
||||
<description lang="en">some description</description>
|
||||
<filelist>
|
||||
<file permission="644" location="test.txt">YWJjw6TDtsO8w58=</file>
|
||||
</filelist>
|
||||
</zpm>',
|
||||
:action => 'install',
|
||||
:result => false,
|
||||
},
|
||||
|
||||
# test 3
|
||||
{
|
||||
:zpm => '<?xml version="1.0"?>
|
||||
<zpm version="1.0">
|
||||
<name>UnitTestSample</name>
|
||||
<version>1.0.0</version>
|
||||
<vendor>Znuny GmbH</vendor>
|
||||
<url>http://znuny.org/</url>
|
||||
<license>ABC</license>
|
||||
<description lang="en">some description</description>
|
||||
<filelist>
|
||||
<file permission="644" location="test.txt">YWJjw6TDtsO8w58=</file>
|
||||
</filelist>
|
||||
</zpm>',
|
||||
:action => 'install',
|
||||
:result => false,
|
||||
},
|
||||
|
||||
# test 4
|
||||
{
|
||||
:zpm => '<?xml version="1.0"?>
|
||||
<zpm version="1.0">
|
||||
<name>UnitTestSample</name>
|
||||
<version>1.0.2</version>
|
||||
<vendor>Znuny GmbH</vendor>
|
||||
<url>http://znuny.org/</url>
|
||||
<license>ABC</license>
|
||||
<description lang="en">some description</description>
|
||||
<filelist>
|
||||
<file permission="644" location="test.txt2">YWJjw6TDtsO8w58=</file>
|
||||
<file permission="644" location="some/dir/test.txt2">YWJjw6TDtsO8w58=</file>
|
||||
<file permission="644" location="db/addon/unit_test_sample/20121212000001_create_base.rb">Y2xhc3MgQ3JlYXRlQmFzZSA8IEFjdGl2ZVJlY29yZDo6TWlncmF0aW9uDQogIGRlZiBzZWxmLnVw
|
||||
DQogICBjcmVhdGVfdGFibGUgOnNhbXBsZV90YWJsZXMgZG8gfHR8DQogICAgICB0LmNvbHVtbiA6
|
||||
bmFtZSwgICAgICAgICAgIDpzdHJpbmcsIDpsaW1pdCA9PiAxNTAsICA6bnVsbCA9PiB0cnVlDQog
|
||||
ICAgICB0LmNvbHVtbiA6ZGF0YSwgICAgICAgICAgIDpzdHJpbmcsIDpsaW1pdCA9PiA1MDAwLCA6
|
||||
bnVsbCA9PiB0cnVlDQogICAgZW5kDQogIGVuZA0KDQogIGRlZiBzZWxmLmRvd24NCiAgICBkcm9w
|
||||
X3RhYmxlIDpzYW1wbGVfdGFibGVzDQogIGVuZA0KZW5k</file>
|
||||
</filelist>
|
||||
</zpm>',
|
||||
:action => 'install',
|
||||
:result => true,
|
||||
:verify => {
|
||||
:package => {
|
||||
:name => 'UnitTestSample',
|
||||
:version => '1.0.2',
|
||||
},
|
||||
:check_files => [
|
||||
{
|
||||
:location => 'test.txt2',
|
||||
:result => true,
|
||||
},
|
||||
{
|
||||
:location => 'test.txt',
|
||||
:result => false,
|
||||
},
|
||||
{
|
||||
:location => 'test2.txt',
|
||||
:result => false,
|
||||
},
|
||||
{
|
||||
:location => 'some/dir/test.txt2',
|
||||
:result => true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
# test 4
|
||||
{
|
||||
:name => 'UnitTestSample',
|
||||
:version => '1.0.2',
|
||||
:action => 'uninstall',
|
||||
:result => true,
|
||||
:verify => {
|
||||
:check_files => [
|
||||
{
|
||||
:location => 'test.txt',
|
||||
:result => false,
|
||||
},
|
||||
{
|
||||
:location => 'test2.txt',
|
||||
:result => false,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
]
|
||||
tests.each { |test|
|
||||
if test[:action] == 'install'
|
||||
begin
|
||||
success = Package.install_string( test[:zpm] )
|
||||
rescue => e
|
||||
puts 'ERROR: ' + e.inspect
|
||||
success = false
|
||||
end
|
||||
if test[:result]
|
||||
assert( success, "install package not successful" )
|
||||
else
|
||||
assert( !success, "install package successful but should not" )
|
||||
end
|
||||
elsif test[:action] == 'uninstall'
|
||||
if test[:zpm]
|
||||
begin
|
||||
success = Package.uninstall_string( test[:zpm] )
|
||||
rescue
|
||||
success = false
|
||||
end
|
||||
else
|
||||
begin
|
||||
success = Package.uninstall_name( test[:name], test[:version] )
|
||||
rescue
|
||||
success = false
|
||||
end
|
||||
end
|
||||
if test[:result]
|
||||
assert( success, "uninstall package not successful" )
|
||||
else
|
||||
assert( !success, "uninstall package successful but should not" )
|
||||
end
|
||||
end
|
||||
if test[:verify] && test[:verify][:package]
|
||||
exists = Package.where( :name => test[:verify][:package][:name], :version => test[:verify][:package][:version] ).first
|
||||
assert( exists, "package '#{test[:verify][:package][:name]}' is not installed" )
|
||||
end
|
||||
if test[:verify] && test[:verify][:check_files]
|
||||
test[:verify][:check_files].each {|item|
|
||||
exists = File.exist?( item[:location] )
|
||||
if item[:result]
|
||||
assert( exists, "'#{item[:location]}' exists" )
|
||||
else
|
||||
assert( !exists, "'#{item[:location]}' doesn't exists" )
|
||||
end
|
||||
}
|
||||
end
|
||||
}
|
||||
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue