Merge pull request #799 from warpc/794-move-all-keys-into-abf-bd

#794: move all keys into abf bd
This commit is contained in:
Alexander Machehin 2013-01-22 00:17:42 -08:00
commit eeb58ccd8e
35 changed files with 1029 additions and 248 deletions

View File

@ -56,6 +56,8 @@ gem 'rails-backbone', '~> 0.7.2'
gem 'rack-throttle'
gem 'rest-client', '~> 1.6.6'
gem 'attr_encrypted', '1.2.1'
group :assets do
gem 'sass-rails', '~> 3.2.5'
gem 'coffee-rails', '~> 3.2.2'

View File

@ -52,23 +52,27 @@ GEM
activesupport (3.2.11)
i18n (~> 0.6)
multi_json (~> 1.0)
airbrake (3.1.2)
activesupport
airbrake (3.1.6)
builder
girl_friday
ancestry (1.3.0)
activerecord (>= 2.3.14)
arel (3.0.2)
attr_encrypted (1.2.1)
encryptor (>= 1.1.1)
bcrypt-ruby (3.0.1)
blankslate (2.1.2.4)
blankslate (3.1.2)
bluepill (0.0.60)
activesupport (>= 3.0.0)
daemons (~> 1.1.4, <= 1.1.6)
i18n (>= 0.5.0)
state_machine (~> 1.1.0)
bourne (1.1.2)
mocha (= 0.10.5)
builder (3.0.4)
cancan (1.6.7)
cape (1.4.0)
capistrano (2.12.0)
cape (1.6.0)
capistrano (2.14.1)
highline
net-scp (>= 1.0.0)
net-sftp (>= 2.0.0)
@ -77,22 +81,26 @@ GEM
capistrano_colors (0.5.5)
charlock_holmes (0.6.9)
chronic (0.6.7)
chunky_png (1.2.6)
cocaine (0.2.1)
chunky_png (1.2.7)
climate_control (0.0.3)
activesupport (>= 3.0)
cocaine (0.5.1)
climate_control (>= 0.0.3, < 1.0)
coffee-rails (3.2.2)
coffee-script (>= 2.2.0)
railties (~> 3.2.0)
coffee-script (2.2.0)
coffee-script-source
execjs
coffee-script-source (1.3.3)
coffee-script-source (1.4.0)
compass (0.12.2)
chunky_png (~> 1.2)
fssm (>= 0.2.7)
sass (~> 3.1)
compass-rails (1.0.3)
compass (>= 0.12.2, < 0.14)
creole (0.4.2)
connection_pool (1.0.0)
creole (0.5.0)
daemons (1.1.6)
devise (2.1.2)
bcrypt-ruby (~> 3.0)
@ -102,9 +110,10 @@ GEM
diff-display (0.0.1)
diff-lcs (1.1.3)
ejs (1.0.0)
encryptor (1.1.3)
erubis (2.7.0)
escape_utils (0.2.4)
eventmachine (0.12.10)
eventmachine (1.0.0)
execjs (1.4.0)
multi_json (~> 1.0)
expression_parser (0.9.0)
@ -115,14 +124,17 @@ GEM
railties (>= 3.0.0)
ffi (1.0.11)
fssm (0.2.9)
girl_friday (0.11.2)
connection_pool (~> 1.0)
rubinius-actor
github-linguist (2.2.1)
charlock_holmes (~> 0.6.6)
escape_utils (~> 0.2.3)
mime-types (~> 1.18)
pygments.rb (>= 0.2.13)
github-markdown (0.5.0)
github-markup (0.7.4)
gollum (2.1.3)
github-markdown (0.5.3)
github-markup (0.7.5)
gollum (2.1.10)
github-markdown
github-markup (>= 0.7.0, < 1.0.0)
grit (~> 2.5.0)
@ -135,24 +147,24 @@ GEM
stringex (~> 1.4.0)
useragent (~> 0.4.9)
haml (3.1.7)
haml-rails (0.3.4)
actionpack (~> 3.0)
activesupport (~> 3.0)
haml (~> 3.0)
railties (~> 3.0)
haml-rails (0.3.5)
actionpack (>= 3.1, < 4.1)
activesupport (>= 3.1, < 4.1)
haml (~> 3.1)
railties (>= 3.1, < 4.1)
hashie (1.2.0)
highline (1.6.13)
highline (1.6.15)
hike (1.2.1)
hirb (0.7.0)
i18n (0.6.1)
jbuilder (0.8.2)
jbuilder (0.8.3)
activesupport (>= 3.0.0)
journey (1.0.4)
jquery-rails (2.0.2)
railties (>= 3.2.0, < 5.0)
jquery-rails (2.0.3)
railties (>= 3.1.0, < 5.0)
thor (~> 0.14)
json (1.7.6)
kgio (2.7.4)
kgio (2.8.0)
libv8 (3.3.10.4)
macaddr (1.6.1)
systemu (~> 2.5.0)
@ -160,18 +172,21 @@ GEM
i18n (>= 0.4.0)
mime-types (~> 1.16)
treetop (~> 1.4.8)
mailcatcher (0.5.8)
mailcatcher (0.5.10)
activesupport (~> 3.0)
eventmachine (~> 0.12)
eventmachine (~> 1.0.0)
haml (~> 3.1)
mail (~> 2.3)
sinatra (~> 1.2)
skinny (~> 0.2, >= 0.2.1)
skinny (~> 0.2.3)
sqlite3 (~> 1.3)
thin (~> 1.2)
thin (~> 1.5.0)
meta-tags (1.2.6)
actionpack
metaclass (0.0.1)
mime-types (1.19)
mocha (0.10.5)
metaclass (~> 0.0.1)
mock_redis (0.6.2)
multi_json (1.5.0)
mustache (0.99.4)
@ -179,12 +194,12 @@ GEM
net-ssh (>= 1.99.1)
net-sftp (2.0.5)
net-ssh (>= 2.0.9)
net-ssh (2.5.2)
net-ssh (2.6.3)
net-ssh-gateway (1.1.0)
net-ssh (>= 1.99.1)
newrelic_rpm (3.4.1)
nokogiri (1.5.5)
omniauth (1.1.0)
newrelic_rpm (3.4.2.1)
nokogiri (1.5.6)
omniauth (1.1.1)
hashie (~> 1.2)
rack
omniauth-openid (1.0.1)
@ -201,18 +216,18 @@ GEM
rails (~> 3.0)
redis
resque
pg (0.14.0)
pg (0.14.1)
polyglot (0.3.3)
posix-spawn (0.3.6)
pygments.rb (0.2.13)
rubypython (~> 0.5.3)
rack (1.4.3)
rack (1.4.4)
rack-cache (1.2)
rack (>= 0.4)
rack-openid (1.3.1)
rack (>= 1.1.0)
ruby-openid (>= 2.1.8)
rack-protection (1.2.0)
rack-protection (1.3.2)
rack
rack-ssl (1.3.2)
rack
@ -232,9 +247,9 @@ GEM
coffee-script (~> 2.2.0)
ejs (~> 1.0.0)
railties (>= 3.1.0)
rails3-generators (0.17.4)
rails3-generators (1.0.0)
railties (>= 3.0.0)
rails3-jquery-autocomplete (1.0.7)
rails3-jquery-autocomplete (1.0.10)
rails (~> 3.0)
railties (3.2.11)
actionpack (= 3.2.11)
@ -249,7 +264,7 @@ GEM
rdoc (3.12)
json (~> 1.4)
redcarpet (2.1.1)
redis (3.0.1)
redis (3.0.2)
redis-namespace (1.2.1)
redis (~> 3.0.0)
redisk (0.2.2)
@ -274,63 +289,67 @@ GEM
rspec-expectations (~> 2.11.0)
rspec-mocks (~> 2.11.0)
rspec-core (2.11.1)
rspec-expectations (2.11.2)
rspec-expectations (2.11.3)
diff-lcs (~> 1.1.3)
rspec-mocks (2.11.2)
rspec-rails (2.11.0)
rspec-mocks (2.11.3)
rspec-rails (2.11.4)
actionpack (>= 3.0)
activesupport (>= 3.0)
railties (>= 3.0)
rspec (~> 2.11.0)
rubinius-actor (0.0.2)
rubinius-core-api
rubinius-core-api (0.0.1)
ruby-haml-js (0.0.3)
execjs
sprockets (>= 2.0.0)
ruby-openid (2.2.0)
ruby-openid (2.2.2)
rubypython (0.5.3)
blankslate (>= 2.1.2.3)
ffi (~> 1.0.7)
russian (0.6.0)
i18n (>= 0.5.0)
rvm-capistrano (1.2.5)
rvm-capistrano (1.2.7)
capistrano (>= 2.0.0)
sanitize (2.0.3)
nokogiri (>= 1.4.4, < 1.6)
sass (3.2.0)
sass-rails (3.2.5)
sass (3.2.5)
sass-rails (3.2.6)
railties (~> 3.2.0)
sass (>= 3.1.10)
tilt (~> 1.3)
shotgun (0.9)
rack (>= 1.0)
shoulda (3.1.1)
shoulda-context (~> 1.0)
shoulda-matchers (~> 1.2)
shoulda-context (1.0.0)
shoulda-matchers (1.2.0)
shoulda (3.3.2)
shoulda-context (~> 1.0.1)
shoulda-matchers (~> 1.4.1)
shoulda-context (1.0.2)
shoulda-matchers (1.4.2)
activesupport (>= 3.0.0)
sinatra (1.3.2)
bourne (~> 1.1.2)
sinatra (1.3.3)
rack (~> 1.3, >= 1.3.6)
rack-protection (~> 1.2)
tilt (~> 1.3, >= 1.3.3)
skinny (0.2.1)
eventmachine (~> 0.12)
thin (~> 1.2)
skinny (0.2.3)
eventmachine (~> 1.0.0)
thin (~> 1.5.0)
sprockets (2.2.2)
hike (~> 1.2)
multi_json (~> 1.0)
rack (~> 1.0)
tilt (~> 1.1, != 1.3.0)
sqlite3 (1.3.6)
sqlite3 (1.3.7)
state_machine (1.1.2)
stringex (1.4.0)
systemu (2.5.2)
therubyracer (0.10.2)
libv8 (~> 3.3.10)
thin (1.4.1)
thin (1.5.0)
daemons (>= 1.0.9)
eventmachine (>= 0.12.6)
rack (>= 1.0.0)
thor (0.16.0)
thor (0.15.4)
tilt (1.3.3)
treetop (1.4.12)
polyglot
@ -343,8 +362,8 @@ GEM
kgio (~> 2.6)
rack
raindrops (~> 0.7)
useragent (0.4.10)
uuid (2.3.5)
useragent (0.4.16)
uuid (2.3.6)
macaddr (~> 1.0)
vegas (0.1.11)
rack (>= 1.0.0)
@ -356,7 +375,7 @@ GEM
wikicloth (0.8.0)
builder
expression_parser
will_paginate (3.0.3)
will_paginate (3.0.4)
PLATFORMS
ruby
@ -365,6 +384,7 @@ DEPENDENCIES
RedCloth
airbrake (~> 3.1.2)
ancestry (~> 1.3.0)
attr_encrypted (= 1.2.1)
bluepill (~> 0.0.60)
cancan (= 1.6.7)
cape

View File

@ -30,6 +30,9 @@ class Api::V1::RepositoriesController < Api::V1::BaseController
destroy_subject @repository
end
def key_pair
end
def add_project
project = Project.where(:id => params[:project_id]).first
if project

View File

@ -154,6 +154,12 @@ class Ability
cannot([:get_list, :create], MassBuild) {|mass_build| mass_build.platform.personal?}
cannot(:cancel, MassBuild) {|mass_build| mass_build.platform.personal? || mass_build.stop_build}
if @user.system?
can :key_pair, Repository
else
cannot :key_pair, Repository
end
can :create, Subscribe do |subscribe|
!subscribe.subscribeable.subscribes.exists?(:user_id => user.id)
end

View File

@ -1,35 +1,84 @@
# -*- encoding : utf-8 -*-
class KeyPair < ActiveRecord::Base
belongs_to :repository
belongs_to :user
attr_accessor :secret
attr_accessor :fingerprint
attr_accessible :public, :secret, :repository_id
attr_encrypted :secret, :key => APP_CONFIG['secret_key']
validates :repository_id, :public, :user_id, :presence => true
validates :secret, :presence => true, :on => :create
validates :repository_id, :user_id, :presence => true
validates :secret, :public, :presence => true, :length => { :maximum => 10000 }, :on => :create
validates :repository_id, :uniqueness => {:message => I18n.t("activerecord.errors.key_pair.repo_key_exists")}
validate :check_keys
before_create :key_create_call
before_destroy :rm_key_call
before_create { |record| record.key_id = @fingerprint }
after_create { |record|
AbfWorker::BuildListsPublishTaskManager.resign_repository(record) unless record.repository.platform.personal?
}
protected
def key_create_call
result, self.key_id = BuildServer.import_gpg_key_pair(public, secret)
raise "Failed to create key_pairs for repository #{repository_id} with code #{result}." if result == 4
if result != 0 || self.key_id.nil?
errors.add(:public, I18n.t("activerecord.errors.key_pair.rpc_error_#{result}"))
return false
def check_keys
dir = Dir.mktmpdir('keys-', "#{APP_CONFIG['root_path']}/tmp")
begin
%w(pubring secring).each do |kind|
filename = "#{dir}/#{kind}"
open("#{filename}.txt", "w") { |f| f.write self.send(kind == 'pubring' ? :public : :secret) }
system "gpg --homedir #{dir} --dearmor < #{filename}.txt > #{filename}.gpg"
end
public_key = get_info_of_key "#{dir}/pubring.gpg"
secret_key = get_info_of_key "#{dir}/secring.gpg"
if correct_key?(public_key, :public) & correct_key?(secret_key, :secret)
if public_key[:fingerprint] != secret_key[:fingerprint]
errors.add :secret, I18n.t('activerecord.errors.key_pair.wrong_keys')
else
stdin, stdout, stderr = Open3.popen3("echo '\n\n\n\n\nsave' | LC_ALL=en gpg --command-fd 0 --homedir #{dir} --edit-key #{secret_key[:keyid]} passwd")
output = stderr.read
if output =~ /Invalid\spassphrase/
errors.add :secret, I18n.t('activerecord.errors.key_pair.key_has_passphrase')
else
@fingerprint = secret_key[:fingerprint]
end
end
end
ensure
# remove the directory.
FileUtils.remove_entry_secure dir
end
result = BuildServer.set_repository_key(repository.platform.name, repository.name, self.key_id)
raise "Failed to sign repository #{repository.name} in platform #{repository.platform.name}
using key_id #{self.key_id} with code #{result}." unless result.zero?
end
def rm_key_call
result = BuildServer.rm_repository_key(repository.platform.name, repository.name)
raise "Failed to desroy repository key #{repository.name} in platform
#{repository.platform.name} with code #{result}." unless result.zero?
def correct_key?(info, field)
if info.empty? || info[:type].blank? || info[:fingerprint].blank? || info[:keyid].blank?
errors.add field, I18n.t('activerecord.errors.key_pair.wrong_key')
return false
else
if info[:type] != field
errors.add field, I18n.t("activerecord.errors.key_pair.wrong_#{field}_key")
return false
end
end
return true
end
def get_info_of_key(file_path)
results = {}
str = %x[ gpg --with-fingerprint #{file_path} | sed -n 1,2p]
info = str.strip.split("\n")
if info.size == 2
results[:fingerprint] = info[1].gsub(/.*\=/, '').strip.gsub(/\s/, ':')
results[:type] = info[0] =~ /^pub\s/ ? :public : nil
results[:type] ||= info[0] =~ /^sec\s/ ? :secret : nil
if keyid = info[0].match(/\/[\w]+\s/)
results[:keyid] = keyid[0].strip[1..-1]
end
end
return results
end
end

View File

@ -30,9 +30,8 @@ class Platform < ActiveRecord::Base
end
}
before_create :create_directory, :if => lambda {Thread.current[:skip]} # TODO remove this when core will be ready
before_create :xml_rpc_create, :unless => lambda {Thread.current[:skip]}
before_destroy :xml_rpc_destroy
before_create :create_directory
before_destroy :detele_directory
after_update :freeze_platform_and_update_repos
after_update :update_owner_relation
@ -145,7 +144,7 @@ class Platform < ActiveRecord::Base
def full_clone(attrs = {})
base_clone(attrs).tap do |c|
with_skip {c.save} and c.clone_relations(self) and c.xml_rpc_clone # later with resque
with_skip {c.save} and c.clone_relations(self) and c.fs_clone # later with resque
end
end
@ -159,10 +158,6 @@ class Platform < ActiveRecord::Base
end
end
def create_directory
system("sudo mkdir -p -m 0777 #{path}")
end
def symlink_directory
# umount_directory_for_rsync # TODO ignore errors
system("ln -s #{path} #{symlink_path}")
@ -188,8 +183,13 @@ class Platform < ActiveRecord::Base
end
later :destroy, :queue => :clone_build
protected
def create_directory
system("mkdir -p -m 0777 #{build_path([name, 'repository'])}")
end
def default_host
EventLog.current_controller.request.host_with_port rescue ::Rosa::Application.config.action_mailer.default_url_options[:host]
end
@ -198,38 +198,17 @@ class Platform < ActiveRecord::Base
File.join(APP_CONFIG['root_path'], 'platforms', dir)
end
def xml_rpc_create
result = BuildServer.add_platform name, APP_CONFIG['root_path'] + '/platforms' , distrib_type
if result == BuildServer::SUCCESS
return true
else
raise "Failed to create platform #{name} with code #{result}. Path: #{build_path(name)}"
end
def detele_directory
FileUtils.rm_rf path
end
def xml_rpc_destroy
result = BuildServer.delete_platform name
if result == BuildServer::SUCCESS
return true
else
raise "Failed to delete platform #{name} with code #{result}."
end
def fs_clone(old_name = parent.name, new_name = name)
FileUtils.cp_r "#{parent.path}/repository", path
end
def xml_rpc_clone(old_name = parent.name, new_name = name)
result = BuildServer.clone_platform new_name, old_name, APP_CONFIG['root_path'] + '/platforms'
if result == BuildServer::SUCCESS
return true
else
raise "Failed to clone platform #{old_name} with code #{result}. Path: #{build_path(old_name)} to platform #{new_name}"
end
end
later :xml_rpc_clone, :loner => true, :queue => :clone_build
later :fs_clone, :loner => true, :queue => :clone_build
def freeze_platform_and_update_repos
if released_changed? && released == true
result = BuildServer.freeze(name)
raise "Failed freeze platform #{name} with code #{result}" if result != BuildServer::SUCCESS
repositories.update_all(:publish_without_qa => false)
end
end

View File

@ -193,6 +193,10 @@ class Project < ActiveRecord::Base
end
end
def destroy_project_from_repository(repository)
AbfWorker::BuildListsPublishTaskManager.destroy_project_from_repository self, repository
end
protected
def truncate_name

View File

@ -5,9 +5,7 @@ class ProjectToRepository < ActiveRecord::Base
delegate :path, :to => :project
after_create lambda { project.xml_rpc_create(repository) }, :unless => lambda {Thread.current[:skip]}
after_destroy lambda { project.xml_rpc_destroy(repository) }, :unless => lambda {Thread.current[:skip]}
# after_rollback lambda { project.xml_rpc_destroy(repository) rescue true if new_record? }
after_destroy lambda { project.destroy_project_from_repository(repository) }, :unless => lambda {Thread.current[:skip]}
validate :one_project_in_platform_repositories

View File

@ -16,8 +16,7 @@ class Repository < ActiveRecord::Base
scope :recent, order("name ASC")
before_create :xml_rpc_create, :unless => lambda {Thread.current[:skip]}
before_destroy :xml_rpc_destroy, :unless => lambda {Thread.current[:skip]}
before_destroy :detele_directory, :unless => lambda {Thread.current[:skip]}
attr_accessible :name, :description, :publish_without_qa
attr_readonly :name, :platform_id
@ -59,23 +58,27 @@ class Repository < ActiveRecord::Base
end
end
def destroy
with_skip {super} # avoid cascade XML RPC requests
end
later :destroy, :queue => :clone_build
protected
def xml_rpc_create
result = BuildServer.create_repo name, platform.name
if result == BuildServer::SUCCESS
return true
def detele_directory
repository_path = platform.path << '/repository'
if platform.personal?
Platform.main.pluck(:name).each do |main_platform_name|
detele_repositories_directory "#{repository_path}/#{main_platform_name}"
end
else
raise "Failed to create repository #{name} inside platform #{platform.name} with code #{result}."
detele_repositories_directory repository_path
end
end
def xml_rpc_destroy
result = BuildServer.delete_repo name, platform.name
if result == BuildServer::SUCCESS
return true
else
raise "Failed to delete repository #{name} inside platform #{platform.name} with code #{result}."
end
def detele_repositories_directory(repository_path)
srpm_and_arches = (['SRPM'] << Arch.pluck(:name)).join(',')
`bash -c 'rm -rf #{repository_path}/{#{srpm_and_arches}}/#{name}'`
end
end

View File

@ -0,0 +1,11 @@
json.repository do |json|
json.partial! 'repository', :repository => @repository, :json => json
json.key_pair do |json_key_pair|
if @repository.key_pair
json_key_pair.(@repository.key_pair, :public, :secret)
else
json_key_pair.public ''
json_key_pair.secret ''
end
end
end

View File

@ -42,8 +42,4 @@ Rosa::Application.configure do
config.active_record.auto_explain_threshold_in_seconds = 0.5
end
require 'stub_xml_rpc'
Rails.application.config.to_prepare {
Platform.send :include, Modules::Models::SymlinkStub
}
require 'stub_xml_rpc'

View File

@ -17,10 +17,11 @@ en:
errors:
key_pair:
repo_key_exists: Repository has been signed already! Please remove old signature and try again
rpc_error_0: an unexpected error
rpc_error_1: could not import public key
rpc_error_2: could not import secret key
rpc_error_3: keys are imported, but it is not a key pair (ids differ)
wrong_keys: keys are imported, but it is not a key pair (ids differ)
wrong_key: wrong
wrong_public_key: contains secret key
wrong_secret_key: contains public key
key_has_passphrase: contains passphrase
models:
key_pair: Key Pair
attributes:

View File

@ -17,10 +17,11 @@ ru:
errors:
key_pair:
repo_key_exists: Репозиторий уже подписан! Пожалуйста, удалите старую подпись и попробуйте снова
rpc_error_0: Неизвестная ошибка
rpc_error_1: Проблемы с импортром публичного ключа
rpc_error_2: Проблемы с импортром секретного ключа
rpc_error_3: Ключи импортированы, но не являются парой (идентификаторы не совпадают)
wrong_keys: Ключи импортированы, но не являются парой (идентификаторы не совпадают)
wrong_key: неправильный
wrong_public_key: содержит секретный ключ
wrong_secret_key: содержит публичный ключ
key_has_passphrase: содержит пароль
models:
key_pair: Подпись
attributes:

View File

@ -38,6 +38,7 @@ Rosa::Application.routes.draw do
resources :repositories, :only => [:show, :update, :destroy] do
member {
get :projects
get :key_pair
put :add_member
delete :remove_member
put :add_project

View File

@ -0,0 +1,22 @@
class AddEncryptedSecretToKeyPairs < ActiveRecord::Migration
def up
rename_table :key_pairs, :key_pairs_backup
rename_index :key_pairs_backup, 'index_key_pairs_on_repository_id', 'index_key_pairs_backup_on_repository_id'
create_table :key_pairs do |t|
t.text :public, :null => false
t.text :encrypted_secret, :null => false
t.string :key_id, :null => false
t.references :user, :null => false
t.references :repository, :null => false
t.timestamps
end
add_index :key_pairs, :repository_id, :unique => true
end
def down
drop_table :key_pairs
rename_table :key_pairs_backup, :key_pairs
rename_index :key_pairs, 'index_key_pairs_backup_on_repository_id', 'index_key_pairs_on_repository_id'
end
end

View File

@ -207,6 +207,18 @@ ActiveRecord::Schema.define(:version => 20130119125710) do
add_index "issues", ["project_id", "serial_id"], :name => "index_issues_on_project_id_and_serial_id", :unique => true
create_table "key_pairs", :force => true do |t|
t.text "public", :null => false
t.text "encrypted_secret", :null => false
t.string "key_id", :null => false
t.integer "user_id", :null => false
t.integer "repository_id", :null => false
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
add_index "key_pairs", ["repository_id"], :name => "index_key_pairs_on_repository_id", :unique => true
create_table "key_pairs_backup", :force => true do |t|
t.integer "repository_id", :null => false
t.integer "user_id", :null => false
t.string "key_id", :null => false
@ -215,7 +227,7 @@ ActiveRecord::Schema.define(:version => 20130119125710) do
t.datetime "updated_at", :null => false
end
add_index "key_pairs", ["repository_id"], :name => "index_key_pairs_on_repository_id", :unique => true
add_index "key_pairs_backup", ["repository_id"], :name => "index_key_pairs_backup_on_repository_id", :unique => true
create_table "labelings", :force => true do |t|
t.integer "label_id", :null => false

View File

@ -2,8 +2,15 @@
module AbfWorker
class BuildListsPublishTaskManager
REDIS_MAIN_KEY = 'abf-worker::build-lists-publish-task-manager::'
LOCKED_REP_AND_PLATFORMS = "#{REDIS_MAIN_KEY}locked-repositories-and-platforms"
LOCKED_BUILD_LISTS = "#{REDIS_MAIN_KEY}locked-build-lists"
%w(RESIGN_REPOSITORIES
PROJECTS_FOR_CLEANUP
LOCKED_PROJECTS_FOR_CLEANUP
LOCKED_REPOSITORIES
LOCKED_REP_AND_PLATFORMS
LOCKED_BUILD_LISTS).each do |kind|
const_set kind, "#{REDIS_MAIN_KEY}#{kind.downcase.gsub('_', '-')}"
end
def initialize
@redis = Resque.redis
@ -11,6 +18,91 @@ module AbfWorker
end
def run
create_tasks_for_resign_repositories
create_tasks_for_build_rpms
end
class << self
def destroy_project_from_repository(project, repository)
if repository.platform.personal?
Platform.main.each do |main_platform|
redis.lpush PROJECTS_FOR_CLEANUP, "#{project.id}-#{repository.id}-#{main_platform.id}"
end
else
redis.lpush PROJECTS_FOR_CLEANUP, "#{project.id}-#{repository.id}-#{repository.platform.id}"
end
end
def cleanup_completed(projects_for_cleanup)
projects_for_cleanup.each do |key|
redis.lrem LOCKED_PROJECTS_FOR_CLEANUP, 0, key
end
end
def cleanup_failed(projects_for_cleanup)
projects_for_cleanup.each do |key|
redis.lrem LOCKED_PROJECTS_FOR_CLEANUP, 0, key
redis.lpush PROJECTS_FOR_CLEANUP, key
end
end
def resign_repository(key_pair)
redis.lpush RESIGN_REPOSITORIES, key_pair.repository_id
end
def unlock_repository(repository_id)
redis.lrem LOCKED_REPOSITORIES, 0, repository_id
end
def unlock_build_list(build_list)
redis.lrem LOCKED_BUILD_LISTS, 0, build_list.id
end
def unlock_rep_and_platform(build_list)
redis.lrem LOCKED_REP_AND_PLATFORMS, 0, "#{build_list.save_to_repository_id}-#{build_list.build_for_platform_id}"
end
def redis
Resque.redis
end
end
private
def locked_repositories
@redis.lrange LOCKED_REPOSITORIES, 0, -1
end
def create_tasks_for_resign_repositories
resign_repos = @redis.lrange RESIGN_REPOSITORIES, 0, -1
Repository.where(:id => (resign_repos - locked_repositories)).each do |r|
@redis.lrem RESIGN_REPOSITORIES, 0, r.id
@redis.lpush LOCKED_REPOSITORIES, r.id
Resque.push(
'publish_worker_default',
'class' => "AbfWorker::PublishWorkerDefault",
'args' => [{
:id => r.id,
:arch => 'x86_64',
:distrib_type => r.platform.distrib_type,
:platform => {
:platform_path => "#{r.platform.path}/repository",
:released => r.platform.released
},
:repository => {
:name => r.name,
:id => r.id
},
:type => :resign,
:skip_feedback => true,
:time_living => 2400 # 40 min
}]
)
end
end
def create_tasks_for_build_rpms
available_repos = BuildList.
select('MIN(updated_at) as min_updated_at, save_to_repository_id, build_for_platform_id').
where(:new_core => true, :status => BuildList::BUILD_PUBLISH).
@ -18,34 +110,33 @@ module AbfWorker
order(:min_updated_at).
limit(@workers_count * 2) # because some repos may be locked
locked_rep = locked_repositories
available_repos = available_repos.where('save_to_repository_id NOT IN (?)', locked_rep) unless locked_rep.empty?
counter = 1
# looks like:
# ['save_to_repository_id-build_for_platform_id', ...]
locked_rep_and_pl = @redis.lrange(LOCKED_REP_AND_PLATFORMS, 0, -1)
available_repos.each do |el|
key = "#{el.save_to_repository_id}-#{el.build_for_platform_id}"
next if locked_rep_and_pl.include?(key)
break if counter > @workers_count
if create_task(el.save_to_repository_id, el.build_for_platform_id)
@redis.lpush(LOCKED_REP_AND_PLATFORMS, key)
counter += 1
for_cleanup = @redis.lrange(PROJECTS_FOR_CLEANUP, 0, -1).map do |key|
pr, rep, pl = *key.split('-')
if locked_rep.present? && locked_rep.include?(rep)
nil
else
[rep.to_i, pl.to_i]
end
end
end.compact
available_repos = available_repos.map{ |bl| [bl.save_to_repository_id, bl.build_for_platform_id] } | for_cleanup
available_repos.each do |save_to_repository_id, build_for_platform_id|
next if locked_rep_and_pl.include?("#{save_to_repository_id}-#{build_for_platform_id}")
break if counter > @workers_count
counter += 1 if create_rpm_build_task(save_to_repository_id, build_for_platform_id)
end
end
def self.unlock_build_list(build_list)
Resque.redis.lrem(LOCKED_BUILD_LISTS, 0, build_list.id)
end
def self.unlock_rep_and_platform(build_list)
key = "#{build_list.save_to_repository_id}-#{build_list.build_for_platform_id}"
Resque.redis.lrem(LOCKED_REP_AND_PLATFORMS, 0, key)
end
private
def create_task(save_to_repository_id, build_for_platform_id)
def create_rpm_build_task(save_to_repository_id, build_for_platform_id)
build_lists = BuildList.
where(:new_core => true, :status => BuildList::BUILD_PUBLISH).
where(:save_to_repository_id => save_to_repository_id).
@ -54,7 +145,24 @@ module AbfWorker
locked_ids = @redis.lrange(LOCKED_BUILD_LISTS, 0, -1)
build_lists = build_lists.where('build_lists.id NOT IN (?)', locked_ids) unless locked_ids.empty?
bl = build_lists.first
projects_for_cleanup = @redis.lrange(PROJECTS_FOR_CLEANUP, 0, -1).
select{ |k| k =~ /#{save_to_repository_id}\-#{build_for_platform_id}$/ }
build_lists_for_cleanup = projects_for_cleanup.map do |key|
pr, rep, pl = *key.split('-')
bl = BuildList.where(:project_id => pr).
where(:new_core => true, :status => BuildList::BUILD_PUBLISHED).
where(:save_to_repository_id => save_to_repository_id).
where(:build_for_platform_id => build_for_platform_id).
order(:updated_at).first
unless bl
# No packages for removing
@redis.lrem PROJECTS_FOR_CLEANUP, 0, key
end
bl
end.compact
bl = build_lists.first || build_lists_for_cleanup.first
return false unless bl
platform_path = "#{bl.save_to_platform.path}/repository"
@ -96,23 +204,37 @@ module AbfWorker
build_list_ids << bl.id
@redis.lpush(LOCKED_BUILD_LISTS, bl.id)
end
packages[:sources] = new_sources.values.compact
build_lists_for_cleanup.each do |bl|
bl.last_published.includes(:packages).limit(5).each{ |old_bl|
fill_packages(old_bl, old_packages, :fullname)
}
end
packages[:sources] = new_sources.values
Resque.push(
worker_queue,
'class' => worker_class,
'args' => [options.merge({
:packages => packages,
:old_packages => old_packages,
:build_list_ids => build_list_ids
:build_list_ids => build_list_ids,
:projects_for_cleanup => projects_for_cleanup
})]
)
projects_for_cleanup.each do |key|
@redis.lrem PROJECTS_FOR_CLEANUP, 0, key
@redis.lpush LOCKED_PROJECTS_FOR_CLEANUP, key
end
@redis.lpush(LOCKED_REP_AND_PLATFORMS, "#{save_to_repository_id}-#{build_for_platform_id}")
return true
end
def fill_packages(bl, results_map, field = :sha1)
results_map[:sources] |= bl.packages.by_package_type('source').pluck(field) if field != :sha1
results_map[:binaries][bl.arch.name.to_sym] |= bl.packages.by_package_type('binary').pluck(field)
results_map[:sources] |= bl.packages.by_package_type('source').pluck(field).compact if field != :sha1
results_map[:binaries][bl.arch.name.to_sym] |= bl.packages.by_package_type('binary').pluck(field).compact
end
end

View File

@ -2,28 +2,45 @@ module AbfWorker
class PublishObserver < AbfWorker::BaseObserver
@queue = :publish_observer
def self.perform(options)
status = options['status'].to_i
return if status == STARTED # do nothing when publication started
build_lists = BuildList.where(:id => options['build_list_ids'])
build_lists.each do |bl|
update_results(bl, options)
case status
when COMPLETED
bl.published
when FAILED, CANCELED
bl.fail_publish
end
AbfWorker::BuildListsPublishTaskManager.unlock_build_list bl
end
AbfWorker::BuildListsPublishTaskManager.unlock_rep_and_platform build_lists.first
end
def self.update_results(subject, options)
results = (subject.results || []).
select{ |r| r['file_name'] !~ /^abfworker\:\:publish\-worker.*\.log$/ }
results |= options['results']
sort_results_and_save(subject, results)
class << self
def perform(options)
status = options['status'].to_i
return if status == STARTED # do nothing when publication started
if options['type'] == 'resign'
AbfWorker::BuildListsPublishTaskManager.unlock_repository options['id']
else
update_rpm_builds options
end
end
protected
def update_rpm_builds(options)
build_lists = BuildList.where(:id => options['build_list_ids'])
build_lists.each do |bl|
update_results(bl, options)
case status
when COMPLETED
bl.published
AbfWorker::BuildListsPublishTaskManager.cleanup_completed options['projects_for_cleanup']
when FAILED, CANCELED
bl.fail_publish
AbfWorker::BuildListsPublishTaskManager.cleanup_failed options['projects_for_cleanup']
end
AbfWorker::BuildListsPublishTaskManager.unlock_build_list bl
end
bl = build_lists.first || BuildList.find(options['id'])
AbfWorker::BuildListsPublishTaskManager.unlock_rep_and_platform bl
end
def update_results(subject, options)
results = (subject.results || []).
select{ |r| r['file_name'] !~ /^abfworker\:\:publish\-worker.*\.log$/ }
results |= options['results']
sort_results_and_save(subject, results)
end
end
end

View File

@ -1,25 +0,0 @@
# -*- encoding : utf-8 -*-
module Modules
module Models
module SymlinkStub
extend ActiveSupport::Concern
included do
def create_directory
true
end
def symlink_directory
true
end
def remove_symlink_directory
true
end
end
module ClassMethods
end
end
end
end

View File

@ -26,7 +26,16 @@ Capistrano::Configuration.instance(:must_exist).load do
end
def start_workers
run "cd #{fetch :current_path} && COUNT=#{workers_count} QUEUE=fork_import,hook,clone_build,notification,iso_worker_observer,rpm_worker_observer,publish_observer #{rails_env} BACKGROUND=yes bundle exec rake resque:workers"
queue = [
:fork_import,
:hook,
:clone_build,
:notification,
:iso_worker_observer,
:rpm_worker_observer,
:publish_observer
].join(',')
run "cd #{fetch :current_path} && COUNT=#{workers_count} QUEUE=#{queue} #{rails_env} BACKGROUND=yes bundle exec rake resque:workers"
end
end
end

View File

@ -39,6 +39,13 @@ shared_examples_for "api repository user without show rights" do
end
end
shared_examples_for "api repository user without key_pair rights" do
it 'should not be able to perform key_pair action' do
get :key_pair, :id => @repository.id, :format => :json
response.should_not be_success
end
end
shared_examples_for 'api repository user with writer rights' do
context 'api repository user with update rights' do
@ -127,8 +134,8 @@ shared_examples_for 'api repository user with writer rights' do
context 'api repository user with update signatures rights' do
before do
stub_key_pairs_calls
put :signatures, :id => @repository.id, :repository => {:public => 'iampublic', :secret => 'iamsecret'}, :format => :json
kp = FactoryGirl.build(:key_pair)
put :signatures, :id => @repository.id, :repository => {:public => kp.public, :secret => kp.secret}, :format => :json
end
it 'should be able to perform signatures action' do
response.should be_success
@ -228,8 +235,8 @@ shared_examples_for 'api repository user without writer rights' do
context 'api repository user without update signatures rights' do
before do
stub_key_pairs_calls
put :signatures, :id => @repository.id, :repository => {:public => 'iampublic', :secret => 'iamsecret'}, :format => :json
kp = FactoryGirl.build(:key_pair)
put :signatures, :id => @repository.id, :repository => {:public => kp.public, :secret => kp.secret}, :format => :json
end
it 'should not be able to perform signatures action' do
response.should_not be_success
@ -245,6 +252,7 @@ end
describe Api::V1::RepositoriesController do
before(:each) do
stub_symlink_methods
stub_redis
@platform = FactoryGirl.create(:platform)
@repository = FactoryGirl.create(:repository, :platform => @platform)
@ -264,6 +272,7 @@ describe Api::V1::RepositoriesController do
it_should_behave_like 'api repository user with show rights'
end
it_should_behave_like 'api repository user without writer rights'
it_should_behave_like 'api repository user without key_pair rights'
it 'should not be able to perform projects action', :anonymous_access => false do
get :projects, :id => @repository.id, :format => :json
@ -280,6 +289,7 @@ describe Api::V1::RepositoriesController do
it_should_behave_like 'api repository user with reader rights'
it_should_behave_like 'api repository user with reader rights for hidden platform'
it_should_behave_like 'api repository user with writer rights'
it_should_behave_like 'api repository user without key_pair rights'
end
context 'for platform owner user' do
@ -294,6 +304,7 @@ describe Api::V1::RepositoriesController do
it_should_behave_like 'api repository user with reader rights'
it_should_behave_like 'api repository user with reader rights for hidden platform'
it_should_behave_like 'api repository user with writer rights'
it_should_behave_like 'api repository user without key_pair rights'
end
context 'for user' do
@ -306,5 +317,26 @@ describe Api::V1::RepositoriesController do
it_should_behave_like 'api repository user without reader rights for hidden platform'
it_should_behave_like 'api repository user with show rights'
it_should_behave_like 'api repository user without writer rights'
it_should_behave_like 'api repository user without key_pair rights'
end
context 'for system user' do
before(:each) do
@user = FactoryGirl.create(:user, :role => 'system')
http_login(@user)
end
it 'should be able to perform key_pair action when repository has not keys' do
get :key_pair, :id => @repository.id, :format => :json
response.should be_success
end
it 'should be able to perform key_pair action when repository has keys' do
FactoryGirl.create(:key_pair, :repository => @repository)
get :key_pair, :id => @repository.id, :format => :json
response.should be_success
end
end
end

View File

@ -69,17 +69,18 @@ end
describe Platforms::KeyPairsController do
before(:each) do
stub_symlink_methods
stub_key_pairs_calls
stub_redis
@platform = FactoryGirl.create(:platform)
@repository = FactoryGirl.create(:repository, :platform => @platform)
@user = FactoryGirl.create(:user)
kp = FactoryGirl.build(:key_pair)
@create_params = {
:platform_id => @platform,
:key_pair => {
:repository_id => @repository,
:public => "iampublic",
:secret => "iamsecret"
:public => kp.public,
:secret => kp.secret
}
}
end

View File

@ -3,8 +3,18 @@ FactoryGirl.define do
factory :key_pair do
association :repository
association :user
public FactoryGirl.generate(:string)
secret FactoryGirl.generate(:string)
public {
file = File.open(Rails.root.join('spec', 'support', 'fixtures', 'pubring.gpg'), "rb")
contents = file.read
file.close
contents
}
secret {
file = File.open(Rails.root.join('spec', 'support', 'fixtures', 'secring.gpg'), "rb")
contents = file.read
file.close
contents
}
end
end

View File

@ -0,0 +1,264 @@
require 'spec_helper'
describe AbfWorker::BuildListsPublishTaskManager do
before(:all) do
@publish_workers_count = APP_CONFIG['abf_worker']['publish_workers_count']
APP_CONFIG['abf_worker']['publish_workers_count'] = 2
end
before do
init_test_root
stub_symlink_methods
FactoryGirl.create(:build_list_core, :new_core => true)
end
subject { AbfWorker::BuildListsPublishTaskManager }
let(:build_list) { FactoryGirl.create(:build_list_core, :new_core => true) }
describe 'when no items for publishing' do
before do
stub_redis
subject.new.run
end
%w(RESIGN_REPOSITORIES
PROJECTS_FOR_CLEANUP
LOCKED_PROJECTS_FOR_CLEANUP
LOCKED_REPOSITORIES
LOCKED_REP_AND_PLATFORMS
LOCKED_BUILD_LISTS).each do |kind|
it "ensure that no '#{kind.downcase.gsub('_', ' ')}'" do
@redis_instance.lrange(subject.const_get(kind), 0, -1).should be_empty
end
end
%w(publish_worker_default publish_worker).each do |kind|
it "ensure that no tasks in '#{kind}' queue" do
@redis_instance.lrange(kind, 0, -1).should be_empty
end
end
end
describe 'when one build_list for publishing' do
before do
stub_redis
build_list.update_column(:status, BuildList::BUILD_PUBLISH)
2.times{ subject.new.run }
end
%w(RESIGN_REPOSITORIES
PROJECTS_FOR_CLEANUP
LOCKED_PROJECTS_FOR_CLEANUP
LOCKED_REPOSITORIES).each do |kind|
it "ensure that no '#{kind.downcase.gsub('_', ' ')}'" do
@redis_instance.lrange(subject.const_get(kind), 0, -1).should be_empty
end
end
it "ensure that 'locked rep and platforms' has only one item" do
queue = @redis_instance.lrange(subject::LOCKED_REP_AND_PLATFORMS, 0, -1)
queue.should have(1).item
queue.should include("#{build_list.save_to_repository_id}-#{build_list.build_for_platform_id}")
end
it "ensure that 'locked build lists' has only one item" do
queue = @redis_instance.lrange(subject::LOCKED_BUILD_LISTS, 0, -1)
queue.should have(1).item
queue.should include(build_list.id.to_s)
end
it "ensure that new task for publishing has been created" do
@redis_instance.lrange('queue:publish_worker_default', 0, -1).should have(1).item
end
end
describe 'grouping build lists for publishing into same repository' do
let(:build_list2) { FactoryGirl.create(:build_list_core,
:new_core => true,
:save_to_platform => build_list.save_to_platform,
:save_to_repository => build_list.save_to_repository,
:build_for_platform => build_list.build_for_platform
) }
before do
stub_redis
build_list.update_column(:status, BuildList::BUILD_PUBLISH)
build_list2.update_column(:status, BuildList::BUILD_PUBLISH)
2.times{ subject.new.run }
end
%w(RESIGN_REPOSITORIES
PROJECTS_FOR_CLEANUP
LOCKED_PROJECTS_FOR_CLEANUP
LOCKED_REPOSITORIES).each do |kind|
it "ensure that no '#{kind.downcase.gsub('_', ' ')}'" do
@redis_instance.lrange(subject.const_get(kind), 0, -1).should be_empty
end
end
it "ensure that 'locked rep and platforms' has only one item" do
queue = @redis_instance.lrange(subject::LOCKED_REP_AND_PLATFORMS, 0, -1)
queue.should have(1).item
queue.should include("#{build_list.save_to_repository_id}-#{build_list.build_for_platform_id}")
end
it "ensure that 'locked build lists' has 2 items" do
queue = @redis_instance.lrange(subject::LOCKED_BUILD_LISTS, 0, -1)
queue.should have(2).item
queue.should include(build_list.id.to_s, build_list2.id.to_s)
end
it "ensure that new task for publishing has been created" do
@redis_instance.lrange('queue:publish_worker_default', 0, -1).should have(1).item
end
end
describe 'creates not more than 4 tasks for publishing' do
before do
stub_redis
build_list.update_column(:status, BuildList::BUILD_PUBLISH)
4.times {
bl = FactoryGirl.create(:build_list_core, :new_core => true)
bl.update_column(:status, BuildList::BUILD_PUBLISH)
}
2.times{ subject.new.run }
end
it "ensure that 'locked rep and platforms' has 4 items" do
@redis_instance.lrange(subject::LOCKED_REP_AND_PLATFORMS, 0, -1).should have(4).items
end
it "ensure that 'locked build lists' has 4 items" do
@redis_instance.lrange(subject::LOCKED_BUILD_LISTS, 0, -1).should have(4).items
end
it "ensure that new tasks for publishing has been created" do
@redis_instance.lrange('queue:publish_worker_default', 0, -1).should have(4).items
end
end
describe 'creates task for removing project from repository' do
before do
stub_redis
build_list.update_column(:status, BuildList::BUILD_PUBLISHED)
ProjectToRepository.where(:project_id => build_list.project_id, :repository_id => build_list.save_to_repository_id).destroy_all
2.times{ subject.new.run }
end
%w(RESIGN_REPOSITORIES
PROJECTS_FOR_CLEANUP
LOCKED_REPOSITORIES
LOCKED_BUILD_LISTS).each do |kind|
it "ensure that no '#{kind.downcase.gsub('_', ' ')}'" do
@redis_instance.lrange(subject.const_get(kind), 0, -1).should be_empty
end
end
it "ensure that 'locked rep and platforms' has only one item" do
queue = @redis_instance.lrange(subject::LOCKED_REP_AND_PLATFORMS, 0, -1)
queue.should have(1).item
queue.should include("#{build_list.save_to_repository_id}-#{build_list.build_for_platform_id}")
end
it "ensure that 'locked projects for cleanup' has only one item" do
queue = @redis_instance.lrange(subject::LOCKED_PROJECTS_FOR_CLEANUP, 0, -1)
queue.should have(1).item
queue.should include("#{build_list.project_id}-#{build_list.save_to_repository_id}-#{build_list.build_for_platform_id}")
end
it "ensure that new task for publishing has been created" do
@redis_instance.lrange('queue:publish_worker_default', 0, -1).should have(1).item
end
end
describe 'grouping build lists for publishing and tasks for removing project from repository' do
let(:build_list2) { FactoryGirl.create(:build_list_core,
:new_core => true,
:save_to_platform => build_list.save_to_platform,
:save_to_repository => build_list.save_to_repository,
:build_for_platform => build_list.build_for_platform
) }
before do
stub_redis
build_list.update_column(:status, BuildList::BUILD_PUBLISH)
build_list2.update_column(:status, BuildList::BUILD_PUBLISHED)
ProjectToRepository.where(:project_id => build_list.project_id, :repository_id => build_list.save_to_repository_id).destroy_all
2.times{ subject.new.run }
end
%w(RESIGN_REPOSITORIES
PROJECTS_FOR_CLEANUP
LOCKED_REPOSITORIES).each do |kind|
it "ensure that no '#{kind.downcase.gsub('_', ' ')}'" do
@redis_instance.lrange(subject.const_get(kind), 0, -1).should be_empty
end
end
it "ensure that 'locked rep and platforms' has only one item" do
queue = @redis_instance.lrange(subject::LOCKED_REP_AND_PLATFORMS, 0, -1)
queue.should have(1).item
queue.should include("#{build_list.save_to_repository_id}-#{build_list.build_for_platform_id}")
end
it "ensure that 'locked projects for cleanup' has only one item" do
queue = @redis_instance.lrange(subject::LOCKED_PROJECTS_FOR_CLEANUP, 0, -1)
queue.should have(1).item
queue.should include("#{build_list.project_id}-#{build_list.save_to_repository_id}-#{build_list.build_for_platform_id}")
end
it "ensure that new task for publishing has been created" do
@redis_instance.lrange('queue:publish_worker_default', 0, -1).should have(1).item
end
it "ensure that 'locked build lists' has only one item" do
queue = @redis_instance.lrange(subject::LOCKED_BUILD_LISTS, 0, -1)
queue.should have(1).item
queue.should include(build_list.id.to_s)
end
end
describe 'resign packages in repository' do
before do
stub_redis
build_list.update_column(:status, BuildList::BUILD_PUBLISH)
FactoryGirl.create(:key_pair, :repository => build_list.save_to_repository)
2.times{ subject.new.run }
end
%w(RESIGN_REPOSITORIES
PROJECTS_FOR_CLEANUP
LOCKED_PROJECTS_FOR_CLEANUP
LOCKED_REP_AND_PLATFORMS
LOCKED_BUILD_LISTS).each do |kind|
it "ensure that no '#{kind.downcase.gsub('_', ' ')}'" do
@redis_instance.lrange(subject.const_get(kind), 0, -1).should be_empty
end
end
it "ensure that 'locked repositories' has only one item" do
queue = @redis_instance.lrange(subject::LOCKED_REPOSITORIES, 0, -1)
queue.should have(1).item
queue.should include(build_list.save_to_repository_id.to_s)
end
it "ensure that new task for resign has been created" do
@redis_instance.lrange('queue:publish_worker_default', 0, -1).should have(1).item
end
end
after(:all) do
APP_CONFIG['abf_worker']['publish_workers_count'] = @publish_workers_count
FileUtils.rm_rf(APP_CONFIG['root_path'])
end
end

View File

@ -2,17 +2,71 @@ require 'spec_helper'
describe KeyPair do
before(:all) do
stub_symlink_methods
stub_key_pairs_calls
init_test_root
stub_redis
FactoryGirl.create(:key_pair)
end
it { should belong_to(:repository) }
it { should belong_to(:user)}
it { should ensure_length_of(:public).is_at_most(10000) }
it { should ensure_length_of(:secret).is_at_most(10000) }
it { should_not allow_mass_assignment_of(:user) }
it { should_not allow_mass_assignment_of(:key_id) }
describe 'check_keys validation' do
subject { FactoryGirl.build(:key_pair) }
it { subject.valid?.should be_true }
it 'checks error when wrong public key' do
subject.public = 'test'
subject.valid?
subject.errors[:public].should =~ [I18n.t('activerecord.errors.key_pair.wrong_key')]
end
it 'checks error when wrong secret key' do
subject.secret = 'test'
subject.valid?
subject.errors[:secret].should =~ [I18n.t('activerecord.errors.key_pair.wrong_key')]
end
it 'checks error when public key contains secret key' do
subject.public = subject.secret
subject.valid?
subject.errors[:public].should =~ [I18n.t('activerecord.errors.key_pair.wrong_public_key')]
end
it 'checks error when secret key contains public key' do
subject.secret = subject.public
subject.valid?
subject.errors[:secret].should =~ [I18n.t('activerecord.errors.key_pair.wrong_secret_key')]
end
it 'checks error when different fingerprint of keys' do
file = File.open(Rails.root.join('spec', 'support', 'fixtures', 'pubring.pass.gpg'), "rb")
subject.public = file.read
file.close
subject.valid?
subject.errors[:secret].should =~ [I18n.t('activerecord.errors.key_pair.wrong_keys')]
end
it 'checks error when secret key contains passphrase' do
file = File.open(Rails.root.join('spec', 'support', 'fixtures', 'pubring.pass.gpg'), "rb")
subject.public = file.read
file.close
file = File.open(Rails.root.join('spec', 'support', 'fixtures', 'secring.pass.gpg'), "rb")
subject.secret = file.read
file.close
subject.valid?
subject.errors[:secret].should =~ [I18n.t('activerecord.errors.key_pair.key_has_passphrase')]
end
end
after(:all) do
Platform.delete_all
User.delete_all

View File

@ -6,7 +6,7 @@ describe Platform do
stub_symlink_methods
Platform.delete_all
User.delete_all
FileUtils.rm_rf(APP_CONFIG['root_path'])
init_test_root
# Need for validate_uniqueness_of check
FactoryGirl.create(:platform)
end
@ -44,6 +44,6 @@ describe Platform do
after(:all) do
Platform.delete_all
User.delete_all
FileUtils.rm_rf(APP_CONFIG['root_path'])
clear_test_root
end
end

View File

@ -7,7 +7,7 @@ describe Product do
Platform.delete_all
User.delete_all
Product.delete_all
FileUtils.rm_rf(APP_CONFIG['root_path'])
init_test_root
# Need for validate_uniqueness_of check
FactoryGirl.create(:product)
end
@ -28,7 +28,7 @@ describe Product do
Platform.delete_all
User.delete_all
Product.delete_all
FileUtils.rm_rf(APP_CONFIG['root_path'])
clear_test_root
end
end

View File

@ -16,4 +16,12 @@ describe ProjectToRepository do
p2r = @second_repo.project_to_repositories.build :project_id => @project.id
p2r.should_not be_valid
end
it 'creates task for removing project from repository on destroy' do
stub_redis
@first_repo.project_to_repositories.destroy_all
queue = @redis_instance.lrange(AbfWorker::BuildListsPublishTaskManager::PROJECTS_FOR_CLEANUP, 0, -1)
queue.should have(1).item
queue.should include("#{@project.id}-#{@first_repo.id}-#{@platform.id}")
end
end

View File

@ -102,7 +102,7 @@ describe PullRequest do
FileUtils.rm_rf(APP_CONFIG['root_path'])
end
it { should belong_to(:issue) }
it { should belong_to(:issue).validate(true) }
it { should belong_to(:to_project) }
it { should belong_to(:from_project) }

View File

@ -21,16 +21,16 @@ describe Repository do
Platform.delete_all
User.delete_all
Repository.delete_all
FileUtils.rm_rf(APP_CONFIG['root_path'])
init_test_root
# Need for validate_uniqueness_of check
FactoryGirl.create(:repository)
end
it { should belong_to(:platform) }
it { should have_many(:project_to_repositories)}
it { should have_many(:projects).through(:project_to_repositories)}
it { should have_many(:project_to_repositories).validate(true) }
it { should have_many(:projects).through(:project_to_repositories) }
it { should validate_presence_of(:name)}
it { should validate_presence_of(:name) }
it { should validate_uniqueness_of(:name).case_insensitive.scoped_to(:platform_id) }
it { should validate_format_of(:name).with('basic_repository-name-1234') }
it { should validate_format_of(:name).not_with('.!') }
@ -48,7 +48,7 @@ describe Repository do
Platform.delete_all
User.delete_all
Repository.delete_all
FileUtils.rm_rf(APP_CONFIG['root_path'])
clear_test_root
end
end

View File

@ -46,23 +46,27 @@ def stub_symlink_methods
any_instance_of(Platform, :remove_symlink_directory => true)
end
def stub_key_pairs_calls
stub(BuildServer).import_gpg_key_pair { [0,"1a2b3c"] }
stub(BuildServer).set_repository_key { 0 }
stub(BuildServer).rm_repository_key { 0 }
Resque.inline = true
APP_CONFIG['root_path'] = "#{Rails.root}/tmp/test_root"
APP_CONFIG['git_path'] = "#{Rails.root}/tmp/test_root"
def init_test_root
clear_test_root
%x(mkdir -p #{APP_CONFIG['root_path']}/{platforms,tmp})
end
Resque.inline = true
# Add testing root_path
%x(rm -Rf #{APP_CONFIG['git_path']})
%x(mkdir -p #{APP_CONFIG['git_path']})
def clear_test_root
%x(rm -Rf #{APP_CONFIG['root_path']})
end
def stub_redis
redis_instance = MockRedis.new
stub(Redis).new { redis_instance }
@redis_instance = MockRedis.new
stub(Redis).new { @redis_instance }
stub(Resque).redis { @redis_instance }
end
init_test_root
def fill_project project
%x(mkdir -p #{project.path} && cp -Rf #{Rails.root}/spec/tests.git/* #{project.path}) # maybe FIXME ?
end

View File

@ -0,0 +1,30 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v1.4.12 (Darwin)
mQENBFDS81MBCADYXCU/PiiKKhufSW5OG2Fq3BYHpTxvzGHJjK1vHxx71iZZmxJb
jr1lB2Iac6TXGjZyZCJqXUyi6+d+AFvt1dE4SzCOYDXF30lGJ+sMgR85vA/dOsRm
dQuFeqmgtcCQDlD3ptDe4RXvLhbV7jTKSnhzL0OCOx7nw3NdVfUQ9lLWNxVj2/Wi
O4/AFwSA97V9zpqZoXY9xm+yN5WN0hHufjoTNLb5wCX4boaChJDrMcx041STtgec
HvJKBhLSS3rS4JwUPPPYWYZvlQ/QbCzo0ZVyPJFoP2QQ1hIADgfE2VL9qmbGb3hZ
CfQJqdYBqlJgaXXqU5tCVZygSn3JKrmtCbS/ABEBAAG0MFZva2htaW4gQWxleGV5
IFYgKGF2b2tobWluKSA8YXZva2htaW5AZ21haWwuY29tPokBOAQTAQIAIgUCUNLz
UwIbAwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQasbV9duAtkco+gf+ObSa
PjGNybCtzo91AGQiyuHvckC4vsuakWV3wuPIvymLVBAwAK3NYpQt9HhV09YlgrTU
y2JYALAsAY9dFTVmn0u6AUjxzgcNJG3NJYoUGdxPJJF3Kb7PVMl4qKYvTFooPY/p
yo6fkLxqrLdjODNelqqPl3BOkryhEQAKgIiPkBfu3rkCuN0cHhxhbNu4nH7I3iyv
atsxi1DDRk2GKZkGiiVQWlAr09ZLP/PpVCzpUxMC3hdjA2yZ118fMfvHzFAM4jdL
BGbgFoads+6wbbhrrSXcD2JdW9knwqfX2GMKJQ55N/m0J56/lkD0zjMbEcCk7Tsv
yOjrYsNnRyWw7jGE8LkBDQRQ0vNTAQgAvdVpy2VgNNVAZ6BVPFDaAwrReksCnOBR
Bx5vtJHx4ZUudTpxYyxkkz8cxVap8pJKlBNrZiim2RS3RP/Ya9nTqNYrY597FHgc
KsS9wrdqw8SWse3Pt0QrCpTCcthXvFwho0WW6GP0b5NoEtblHLeCBXG6xBajs80M
7uX7RuOO8SPBCacHR70dmxHjlzQlPSUPrcwxDdlM5FeePSN/ANyrcHaHqNnk+3Dh
PBbmOxyEVf+CmSRlfuaKgCm2g6V7uhU6DBW/6rczOm8QD6cY3RLVfzFLi+gXK2ym
me99WaICe98qQI82HOaCmUk/JGEI8q3m7rMaL2HmYlHOH/6amYoxUQARAQABiQEf
BBgBAgAJBQJQ0vNTAhsMAAoJEGrG1fXbgLZHEB0H/0qmUSltASmyQhe7LAXQcqlJ
KxxSTCg5IfOAuL++4nn/b5SJFfMo1bkVtgo+r5ix33D8ZDFQsditGVf4red6ddSa
SfnNpZkUScOpeOz31N+ev6fspHnMieL9zoHre/FOqWk6h3Wjl4q1NWdRKwpP5zAn
ug17kWzTVxB7CeNbW5N403JoH4e2bWyGO7ZlcM9J26o0g05RU7h5i8y//iUkXt/d
PjBD6YChYl7jpSezAm3Xqv7z+oNtFNRp5kFDn8z7XeHKn4LbNlJq02dcCQkS3Asu
qNgHF0DPEd+lTyvx5ns4Rd5qt4tuwGJGj+VQSMfmLOX88tc/qqwza/0U3Bct+bI=
=ZCfP
-----END PGP PUBLIC KEY BLOCK-----

View File

@ -0,0 +1,30 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v1.4.12 (Darwin)
mQENBFDS98ABCACaw+nwAvake7DUS8p1vQfLcPMGWPACXxW1x0rvnd6OXakFIb97
lcFQFslLr581nYQIIFbNEJRXI3DO5hz21dEJ13uxNYsVjnwX79ToL7JxrEhi/mrH
mOyZqDhkVb4nwQQ231aF4gqmpMaxbylYhvUEwEDil2uxvnFCo1oB3lkD4xkhRkjg
n7LqNU15pbCKQsY/qYd502UMaYofacXpqaWY9nKMgPwIH1oMYrgbeJO7PrhZPHaX
CT0/9K8LELKvK7gVWcScfXShmYaITWS+udFUsEzy4D2zt9jQOie9lRCJHeeDWvoj
AvlWE4VVFnbNK3ShblXhhcr3pX6sKMarAPtRABEBAAG0IHF3ZXJ0eSAocXFxcXEp
IDxhcXdlckBqZmRzai5jb20+iQE4BBMBAgAiBQJQ0vfAAhsDBgsJCAcDAgYVCAIJ
CgsEFgIDAQIeAQIXgAAKCRC1dIaeWBn9lfgQB/9foCBWi/4BqjMfsFVVVEWhTCCo
GWMrY5BtQtgAoY7KX5NFNzCqECYua+HZaMAZgxZxElOq2N8Ig84msWnbuKibzZn9
+6LJCxYYqyn+5xcqeRpTl5zA3fFV7K6rICZF9AzoBFuJgNZ0Do15XEQCJHIlztm8
AtadgUGQDQkU5OuyGlApQ0KfVxlALyNbIb1b9p0wo+D7LEyTAbCXM6RxloDRBaMe
A4uoyzIHOzj3UxU+/38nizlcNAAGy5uyf0o29c9GpRN4ga1ZDspxPxTxrAg4y289
i6l1GNXqLxko/Cg69269rl6ejZ4c9tDra/3l7LyTWC7fPcorD0PVgj816y+guQEN
BFDS98ABCADpObjrAmbkll/9lzJpuKeComRFu3ry6FBtmuhX22zjbwCm8Xb4EmbS
e9HioZVelN2k8FNI2bxEfM6eKk0VwY4VTXkEYlEH74gYrK+iiYwUracBVaT3q1au
TrFB+xir4I8rZdFI39iwsKsaUt5XoNPVDrVwOWQRUjxTKfA0y1tOOmByyfDRzXNn
bo5tHOx/YXz6nJBe4XpQ7uWcVhHe8wgsd/ti9HPgRDM1Q1bxz9xxWLAn6VF3tvey
Jz1i3L5SSHoFHd8OInktSgi3VzhoSaZaBcHcbzm3OrqUofuGbLTKJgpXpCvQlqle
2ow/wpAL+stluRuPhYWjMSzKarTJwM1DABEBAAGJAR8EGAECAAkFAlDS98ACGwwA
CgkQtXSGnlgZ/ZVC0gf6Av6xYN/O3i4zxQ2anql2TqD6WP3zJX46+T7k+9ayIl67
5aH7vHFjmXBt4y8V2ESYIT8LYE3qak7XntNwzjqG4jS4kAw5mOTKaDbGjr+oCGFF
wfBUlK+JyuxbFL28i5bYa94S/rQJ/suKhjWIQu1O/NKLaNJuN5YlhcA53eA/ppPt
TJBNRG2NUD+rAYN5XGb4ctG5h26PpKfr4fxdd90JHf37S/yAcYPnfEfa/WqgnSPL
Bd6AHwmjHxeuRhQhOgIXRmTtWYp3LlPb2yNjL+Vk0ePmzaYp4QrIO1wF1XKTUPB2
S1x2nlZd4zDbyBYG/2rSWRCB8LwD0H1XVQAMBjBVvQ==
=O0re
-----END PGP PUBLIC KEY BLOCK-----

View File

@ -0,0 +1,58 @@
-----BEGIN PGP PRIVATE KEY BLOCK-----
Version: GnuPG v1.4.12 (Darwin)
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
3Bct+bI=
=CNSZ
-----END PGP PRIVATE KEY BLOCK-----

View File

@ -0,0 +1,59 @@
-----BEGIN PGP PRIVATE KEY BLOCK-----
Version: GnuPG v1.4.12 (Darwin)
lQO+BFDS98ABCACaw+nwAvake7DUS8p1vQfLcPMGWPACXxW1x0rvnd6OXakFIb97
lcFQFslLr581nYQIIFbNEJRXI3DO5hz21dEJ13uxNYsVjnwX79ToL7JxrEhi/mrH
mOyZqDhkVb4nwQQ231aF4gqmpMaxbylYhvUEwEDil2uxvnFCo1oB3lkD4xkhRkjg
n7LqNU15pbCKQsY/qYd502UMaYofacXpqaWY9nKMgPwIH1oMYrgbeJO7PrhZPHaX
CT0/9K8LELKvK7gVWcScfXShmYaITWS+udFUsEzy4D2zt9jQOie9lRCJHeeDWvoj
AvlWE4VVFnbNK3ShblXhhcr3pX6sKMarAPtRABEBAAH+AwMC3KsGqCW9NRNg6SQg
yIKTvOc0qXvCICYwO/z6DZLDXDaZMpDCOU/hNPQkuWYKHWQA5/YW9q0epF7/VdEE
Hgh0280Z7+8/zhM566+rVxeWSJkt/diDKAiFPUxqG8aPVxr1tsyLlqxNapv3JmqO
xXfd/iEnJ50PjPest3yCsj6j+qGbqXE1rPInwK0ys8GVski2BmlDnmjxPsr5+4zq
XvlXSjqpJoW7riRIQ0RULUd+DhSaJBPZD3tBzZGS6fhOKlU+eFqJjv/j6xm3q0tk
Jgcvm1f3E9lo1Fzt+ATvVjY35wgf6kh5oPZGXgqMAzJEDscNvSO+8XoZzGBj0A9M
L+3K/PnQKyOAd4s75lC/tW5+u4HUfVrhyEaVNm0JbVvj/UAn8v1BeA9fKQMbd1He
Y7XqjN5/du7p5+OoXLsWcwHTEcRVMVBxWWkQyJgGgrPjAfAhcOp0KCKA/0AwzmtY
61CgCXpCf78EodqbSn1dPSpT16EXxgFhIBdfZc69EKPm8KYIk3iNrTFUTBcREzeo
cHfZse8+CrfOmJTpvMc1ahd9mQPfpzbGizIKasMUl2GAYg1zMKdKUYvf+WBH1dRQ
l9uh8Z96n0MufVSlmS95An8LHKY19QsKW7ArIk+ZFIxqlDu5TU+3OWFNs6qyoJ35
lGu0dR/nWDRAgYXjkMJhTp/GL2QSkJ1sHuzD98JrH8zBhBQyVm//ceo2QiPI/PEK
eAdFmzhKbIYJCBsmRy3+YXAJaK4ibaHDuTOGGT8n/cS+1oyjlcaG7e5gYH8Hy8xx
MSMMI4vByH8Uq1DDFsXZYtI2utJbFxXcce3KIUmOOhMHy0IXIUIP9swHf3j33EWn
4zbi1Kc+qT2I1sGqg396U/3+za34Z6rxwQ8QU3UiEnxGbzIkDT7eao+2NK7VQWOK
u7QgcXdlcnR5IChxcXFxcSkgPGFxd2VyQGpmZHNqLmNvbT6JATgEEwECACIFAlDS
98ACGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJELV0hp5YGf2V+BAH/1+g
IFaL/gGqMx+wVVVURaFMIKgZYytjkG1C2AChjspfk0U3MKoQJi5r4dlowBmDFnES
U6rY3wiDziaxadu4qJvNmf37oskLFhirKf7nFyp5GlOXnMDd8VXsrqsgJkX0DOgE
W4mA1nQOjXlcRAIkciXO2bwC1p2BQZANCRTk67IaUClDQp9XGUAvI1shvVv2nTCj
4PssTJMBsJczpHGWgNEFox4Di6jLMgc7OPdTFT7/fyeLOVw0AAbLm7J/Sjb1z0al
E3iBrVkOynE/FPGsCDjLbz2LqXUY1eovGSj8KDr3br2uXp6Nnhz20Otr/eXsvJNY
Lt89yisPQ9WCPzXrL6CdA74EUNL3wAEIAOk5uOsCZuSWX/2XMmm4p4KiZEW7evLo
UG2a6FfbbONvAKbxdvgSZtJ70eKhlV6U3aTwU0jZvER8zp4qTRXBjhVNeQRiUQfv
iBisr6KJjBStpwFVpPerVq5OsUH7GKvgjytl0Ujf2LCwqxpS3leg09UOtXA5ZBFS
PFMp8DTLW046YHLJ8NHNc2dujm0c7H9hfPqckF7helDu5ZxWEd7zCCx3+2L0c+BE
MzVDVvHP3HFYsCfpUXe297InPWLcvlJIegUd3w4ieS1KCLdXOGhJploFwdxvObc6
upSh+4ZstMomClekK9CWqV7ajD/CkAv6y2W5G4+FhaMxLMpqtMnAzUMAEQEAAf4D
AwLcqwaoJb01E2BS9JteN2Z24WlHeo7XBEODNEk5SrsJCKkJOA+01RtkJyouBRhA
+zCIDPnjUp/A3F8qtGb8YbBzF6geXuB2S7hQgtMPKCKO/2pC6Lo9hVYIZLj4cCE7
myXqfVt7Dno4rtuwwTmOlihBaPXmfUKuUlF9JAhB7A6uE+YxPulT1emM1I/dLKLg
mU/u0W575B33hUDx14gD4fVEulS89BanvKTHHpWcqoJCj9eObRHPfifzXKsp/Bpx
1HuaG7AbHD70PhpF0QTD+wjrWzp2vF/sHgGQPY8dYIwmDW0JV78Wc114SZUANeTN
wJu8Whvebu9X/1KYpzo2EZMLqsFHDwxrURSw6nhtTIkPozKnjNQ8JCJNTqifyKwl
saelfJrWRf2uHSjmsBicC6Kscay81tK8d6xertCoTMYW0bgXtS3hhn0xxlBtq7Ih
5BabuTXeGMeH1xUDWWTlh7WxWXbF+vkLKn/tM/Jmmh3KfVsOwrj2JMIt+ARSbgRf
FdlpsL8CPpW7asNQIHaQVZqDrfZzCLfFcWIXJcB2bHHXegLXK41WtdjT5GzE46bg
duNx71Wfb9bpARExBklqNiCIQkICOAajKnBXVaG1ihra/ze2gGyXRcFS0+q9feJB
cCqK0J52nl6UWFNsJwOypGbA6GgH2WrEdwsq3DA5o3Xeijedfcda/1KhKv8mVMq5
wOllyfnVsiMv8ecE5k6Igs7fQCWVTTNzwVQuVx0C3zFJTZR6Kp9raih6OWoVeCP7
CxXSXLZqxYg2Rm3dxZS4pmYMAbYRbMETMSb3xNn99aHU5ssARmaj+du9Z+xL15tv
ztmYGS4AyHxje0cRoHS9puZTVYgKo7EtGrtQb1F55QVQJgzzvtcjwiwbmut1vGJu
CB60bC74GHupb8uSJYHGiQEfBBgBAgAJBQJQ0vfAAhsMAAoJELV0hp5YGf2VQtIH
+gL+sWDfzt4uM8UNmp6pdk6g+lj98yV+Ovk+5PvWsiJeu+Wh+7xxY5lwbeMvFdhE
mCE/C2BN6mpO157TcM46huI0uJAMOZjkymg2xo6/qAhhRcHwVJSvicrsWxS9vIuW
2GveEv60Cf7LioY1iELtTvzSi2jSbjeWJYXAOd3gP6aT7UyQTURtjVA/qwGDeVxm
+HLRuYduj6Sn6+H8XXfdCR39+0v8gHGD53xH2v1qoJ0jywXegB8Jox8XrkYUIToC
F0Zk7VmKdy5T29sjYy/lZNHj5s2mKeEKyDtcBdVyk1Dwdktcdp5WXeMw28gWBv9q
0lkQgfC8A9B9V1UADAYwVb0=
=hZ8N
-----END PGP PRIVATE KEY BLOCK-----