vines-services 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/README CHANGED
@@ -11,6 +11,8 @@ defined as: platform is 'mac_os_x' and platform_version starts with '10.7'.
11
11
  As machines update to Lion, they will join this service automatically and may
12
12
  be managed as one group.
13
13
 
14
+ Additional documentation can be found at www.getvines.com.
15
+
14
16
  == Usage
15
17
 
16
18
  1. gem install vines-services
@@ -19,11 +21,10 @@ be managed as one group.
19
21
 
20
22
  == Dependencies
21
23
 
22
- * bcrypt-ruby >= 3.0.0
23
- * blather >= 0.5.4
24
+ * bcrypt-ruby >= 3.0.1
25
+ * blather >= 0.5.8
24
26
  * citrus >= 2.4.0
25
27
  * eventmachine >= 0.12.10
26
- * nokogiri >= 1.4.4
27
28
  * sqlite3 >= 1.3.4
28
29
  * ruby >= 1.9.2
29
30
 
@@ -32,8 +33,7 @@ $ sudo apt-get install build-essential ruby1.9.1 ruby1.9.1-dev libxml2-dev libxs
32
33
 
33
34
  == Contact
34
35
 
35
- * David Graham <david@negativecode.com>
36
- * Chris Johnson <chris@negativecode.com>
36
+ * David Graham <david@negativecode.com>
37
37
 
38
38
  == License
39
39
 
data/Rakefile CHANGED
@@ -5,10 +5,11 @@ require 'rubygems/package_task'
5
5
  require 'nokogiri'
6
6
  require_relative 'lib/vines/services/version'
7
7
 
8
+ CLOBBER.include('pkg', 'web/javascripts', 'web/stylesheets/app.css')
9
+
8
10
  spec = Gem::Specification.new do |s|
9
11
  s.name = "vines-services"
10
12
  s.version = Vines::Services::VERSION
11
- s.date = Time.now.strftime("%Y-%m-%d")
12
13
 
13
14
  s.summary = "An XMPP component that broadcasts shell commands to many agents."
14
15
  s.description = "Vines Services are dynamically updated groups of systems based
@@ -16,17 +17,16 @@ on criteria like hostname, installed software, operating system, etc. Send a
16
17
  command to the service and it runs on every system in the group. Services, files
17
18
  and permissions are managed via the bundled web application."
18
19
 
19
- s.authors = ["David Graham", "Chris Johnson"]
20
- s.email = %w[david@negativecode.com chris@negativecode.com]
20
+ s.authors = ["David Graham"]
21
+ s.email = %w[david@negativecode.com]
21
22
  s.homepage = "http://www.getvines.com"
22
23
 
23
- s.files = FileList['[A-Z]*', '{bin,lib,conf,web}/**/*']
24
24
  s.test_files = FileList["test/**/*"]
25
25
  s.executables = %w[vines-services]
26
26
  s.require_path = "lib"
27
27
 
28
- s.add_dependency "bcrypt-ruby", "~> 3.0.0"
29
- s.add_dependency "blather", "~> 0.5.4"
28
+ s.add_dependency "bcrypt-ruby", "~> 3.0.1"
29
+ s.add_dependency "blather", "~> 0.5.8"
30
30
  s.add_dependency "citrus", "~> 2.4.0"
31
31
  s.add_dependency "couchrest_model", "~> 1.1.2"
32
32
  s.add_dependency "em-http-request", "~> 0.3.0"
@@ -39,8 +39,12 @@ and permissions are managed via the bundled web application."
39
39
  s.required_ruby_version = '>= 1.9.2'
40
40
  end
41
41
 
42
- Gem::PackageTask.new(spec) do |pkg|
43
- pkg.need_tar = true
42
+ # Set gem file list after CoffeeScripts have been compiled, so web/javascripts/
43
+ # is included in the gem.
44
+ task :gemprep do
45
+ spec.files = FileList['[A-Z]*', '{bin,lib,conf,web}/**/*']
46
+ Gem::PackageTask.new(spec).define
47
+ Rake::Task['gem'].invoke
44
48
  end
45
49
 
46
50
  Rake::TestTask.new(:test) do |test|
@@ -116,7 +120,6 @@ task :compile do
116
120
 
117
121
  sh %{coffee -c -b -o web/javascripts web/coffeescripts/*.coffee}
118
122
  sh %{cat #{js_files} | uglifyjs -nc > web/javascripts/app.js}
119
-
120
123
  sh %{cat #{css_files} > web/stylesheets/app.css}
121
124
  end
122
125
 
@@ -126,5 +129,4 @@ task :cleanup do
126
129
  File.delete('/tmp/index.html')
127
130
  end
128
131
 
129
-
130
- task :default => [:clobber, :test, :compile, :gem, :cleanup]
132
+ task :default => [:clobber, :test, :compile, :gemprep, :cleanup]
data/bin/vines-services CHANGED
@@ -90,6 +90,5 @@ rescue SystemExit
90
90
  # do nothing
91
91
  rescue Exception => e
92
92
  puts e.message
93
- puts e.backtrace
94
93
  exit(1)
95
94
  end
@@ -5,77 +5,96 @@ module Vines
5
5
  module Command
6
6
  class Init
7
7
  def run(opts)
8
- raise 'vines-services init [domain]' unless opts[:args].size == 1
9
- @domain = opts[:args].first.dup
10
- dir = File.expand_path(@domain)
11
- @component_password = Kit.generate_password
12
- create_directories
13
- initialize_server
14
- initialize_component
15
- initialize_agent
16
- puts "Initialized service, agent, and server directories: #{@domain}"
8
+ raise 'vines-services init <domain>' unless opts[:args].size == 1
9
+ @domain = opts[:args].first.downcase
10
+ base = File.expand_path(@domain)
11
+ raise "Directory already initialized: #{@domain}" if File.exists?(base)
12
+ raise "Agent gem required: gem install vines-agent" unless agent_gem_installed?
13
+
14
+ EM.run do
15
+ Fiber.new do
16
+ @db = find_db
17
+ create_views
18
+ user = save_user(ask_for_jid, ask_for_password)
19
+ create_services(user)
20
+ @token = Kit.auth_token
21
+
22
+ Dir.mkdir(base)
23
+ %w[server services agent].each do |sub|
24
+ dir = File.expand_path(sub, base)
25
+ Dir.mkdir(dir)
26
+ Dir.chdir(dir) { send("init_#{sub}") }
27
+ end
28
+ puts "Initialized server, agent, and services directories: #{@domain}"
29
+ puts "Login at http://localhost:5280/"
30
+ EM.stop
31
+ end.resume
32
+ end
17
33
  end
18
34
 
19
35
  private
20
36
 
21
- def initialize_server
22
- Dir.chdir(File.join(@domain, "server"))
37
+ def init_server
23
38
  `vines init #{@domain}`
24
- configure_database
25
- FileUtils.mv Dir.glob("#{@domain}/*"), "./"
39
+ FileUtils.mv(Dir.glob("#{@domain}/*"), '.')
26
40
  FileUtils.remove_dir(@domain)
41
+ FileUtils.remove_dir('data/users')
42
+ web = File.expand_path("../../../../../web", __FILE__)
43
+ FileUtils.cp_r(Dir.glob("#{web}/*"), 'web')
44
+ update_server_config('conf/config.rb')
27
45
  `vines start -d`
28
- FileUtils.cp_r(File.expand_path("../../../../../web/index.html", __FILE__), File.join("web", "index.html"))
29
- FileUtils.cp_r(File.expand_path("../../../../../web/javascripts", __FILE__), File.join("web", "javascripts"))
30
- FileUtils.cp_r(File.expand_path("../../../../../web/stylesheets", __FILE__), File.join("web", "stylesheets"))
31
- FileUtils.cp_r(File.expand_path("../../../../../web/images", __FILE__), File.join("web", "images"))
32
- FileUtils.cp_r(File.expand_path("../../../../../web/coffeescripts", __FILE__), File.join("web", "coffeescripts"))
46
+ puts "Started vines server: vines start -d"
33
47
  end
34
48
 
35
- def initialize_agent
36
- Dir.chdir("../agent/")
49
+ def init_agent
37
50
  `vines-agent init #{@domain}`
38
- FileUtils.cp("../server/conf/certs/#{@domain}.crt", File.join("#{@domain}", "conf", "certs"))
39
- agent_password = Kit.generate_password
40
- jid = Vines::JID.new(fqdn, @domain).bare
41
- id = "user:#{jid}"
42
- Fiber.new do
43
- user = Vines::Services::CouchModels::User.new(id: id)
44
- user.name = jid
45
- user.system = true
46
- user.password = agent_password
47
- if user.valid?
48
- user.save
49
- log.debug("Saving user: #{user}")
50
- end
51
- end.resume
52
- update_agent_config(File.join("#{@domain}", "conf", "config.rb"), agent_password)
53
- FileUtils.mv Dir.glob("#{@domain}/*"), "./"
51
+ FileUtils.mv(Dir.glob("#{@domain}/*"), '.')
54
52
  FileUtils.remove_dir(@domain)
53
+ FileUtils.cp("../server/conf/certs/#{@domain}.crt", 'conf/certs')
54
+ require File.expand_path('conf/config.rb')
55
+ token = Vines::Agent::Config.instance.domain.password
56
+ save_user(Vines::JID.new(fqdn, @domain), token, true)
55
57
  `vines-agent start -d`
58
+ puts "Started vines agent: vines-agent start -d"
56
59
  end
57
60
 
58
- # Coordinate all the configuration required for the service
59
- def initialize_component
60
- Dir.chdir("../services/")
61
- %w[conf].each do |sub|
62
- FileUtils.cp_r(File.expand_path("../../../../../#{sub}", __FILE__), File.join("."))
63
- end
64
- log, pid = %w[log pid data data/index].map do |sub|
65
- File.join(sub).tap {|subdir| Dir.mkdir(subdir) }
66
- end
67
- update_config(File.join("conf", "config.rb"))
61
+ def init_services
62
+ %w[log pid data data/index data/upload].each {|sub| Dir.mkdir(sub) }
63
+ %w[data data/index data/upload].each {|dir| File.chmod(0700, dir) }
64
+ FileUtils.cp_r(File.expand_path("../../../../../conf", __FILE__), '.')
65
+ File.chmod(0600, 'conf/config.rb')
66
+ update_services_config('conf/config.rb')
68
67
  `vines-services start -d`
68
+ puts "Started vines services component: vines-services start -d"
69
69
  end
70
70
 
71
- #create all the directories for the server, agent, and service
72
- def create_directories
73
- Dir.mkdir(@domain)
74
- %w[server services agent].each do |sub|
75
- Dir.mkdir(File.join(@domain, sub))
71
+ def agent_gem_installed?
72
+ require 'vines/agent'
73
+ rescue LoadError
74
+ false
75
+ end
76
+
77
+ def create_views
78
+ url = "http://#{@db[:host]}:#{@db[:port]}"
79
+ CouchRest::Model::Base.database = CouchRest::Server.new(url).database(@db[:name])
80
+ CouchRest::Model::Base.subclasses.each do |klass|
81
+ klass.save_design_doc! if klass.respond_to?(:save_design_doc!)
76
82
  end
77
83
  end
78
84
 
85
+ def find_db
86
+ host, port = 'localhost', 5984
87
+ db = @domain.downcase.gsub('.', '_')
88
+ until create_db(host, port, db)
89
+ puts "CouchDB connection failed"
90
+ $stdout.write('CouchDB Host: ')
91
+ host = $stdin.gets.chomp
92
+ $stdout.write('CouchDB Port: ')
93
+ port = $stdin.gets.chomp
94
+ end
95
+ {host: host, port: port, name: db}
96
+ end
97
+
79
98
  def create_db(host, port, db)
80
99
  url = "http://#{host}:#{port}/#{db}"
81
100
  begin
@@ -88,122 +107,120 @@ module Vines
88
107
  false
89
108
  end
90
109
 
91
- # This will attempt to create a new couch database. If one exists
92
- # by the name generated, it will not be deleted. This will loop until
93
- # it gets a valid couch db address and port
94
- def configure_database
95
- server, port = "127.0.0.1", 5984
96
- db_name = @domain.gsub(".", "_").downcase()
97
- until create_db(server, port, db_name)
98
- puts "Unable to connect."
99
- $stdout.write('CouchDB Server: ')
100
- server = $stdin.gets.chomp
101
- $stdout.write('CouchDB Port: ')
102
- port = $stdin.gets.chomp
103
- end
104
- @couch_server = server
105
- @couch_port = port
106
- @couch_db = db_name
107
- update_config(File.join(@domain, "conf", "config.rb"))
108
- @storage = Vines::Services::Storage::CouchDB.new do
109
- host server
110
- port port
111
- database db_name
112
- index_dir '.'
113
- end
114
- create_user
110
+ def save_user(jid, password, system=false)
111
+ user = Vines::Services::CouchModels::User.new.tap do |u|
112
+ u.id = "user:#{jid}"
113
+ u.plain_password = password
114
+ u.system = system
115
+ u.admin! unless system
116
+ end.save
117
+ puts "Created user: #{jid}"
118
+ user
115
119
  end
116
120
 
117
- #Create a new vines user object in the couch database
118
- def create_user
119
- jid, password = nil, nil
121
+ def ask_for_jid
122
+ jid = nil
120
123
  until jid
121
124
  $stdout.write('JID: ')
122
125
  if node = $stdin.gets.chomp.split('@').first
123
126
  jid = Vines::JID.new(node, @domain) rescue nil
124
127
  end
125
128
  end
129
+ jid
130
+ end
126
131
 
132
+ def ask_for_password
133
+ password = nil
127
134
  until password
128
135
  $stdout.write('Password: ')
129
136
  `stty -echo`
130
137
  password = $stdin.gets.chomp
131
138
  password = nil if password.empty?
132
139
  `stty echo`
140
+ puts
141
+ end
142
+ password
143
+ end
144
+
145
+ # Return the fully qualified domain name for this machine. This is used
146
+ # to determine the agent's JID.
147
+ def fqdn
148
+ ohai.fqdn.downcase
149
+ end
150
+
151
+ def ohai
152
+ require 'ohai'
153
+ @ohai ||= Ohai::System.new.tap do |system|
154
+ system.require_plugin('os')
155
+ system.require_plugin('hostname')
156
+ system.require_plugin('network')
157
+ system.require_plugin('platform')
133
158
  end
134
- puts "\nCreated #{jid}"
135
-
136
- id = "user:#{jid}"
137
- Fiber.new do
138
- user = Vines::Services::CouchModels::User.new(id: id)
139
- user.name = jid
140
- user.password = password
141
- if user.valid?
142
- user.save
143
- log.debug("\nSaving user: #{user}")
144
- end
145
- end.resume
146
159
  end
147
160
 
148
- # Set the agents password to the one we generated in this initialization
149
- def update_agent_config(config, password)
161
+ # Create some default services based on this system's information. This
162
+ # gives the user some examples from which to build.
163
+ def create_services(user)
164
+ require 'etc'
165
+ ip = ohai.ipaddress.match(/(\d*\.\d*\.\d*)\.\d*/)[1]
166
+ platform = case ohai.platform
167
+ when 'mac_os_x' then 'Mac OS X'
168
+ else ohai.platform.capitalize
169
+ end
170
+ {
171
+ "All Systems" => "uptime_seconds > 0",
172
+ "#{ohai.kernel['name']} Systems" => "kernel.name is '#{ohai.kernel['name']}'",
173
+ "Domain: #{ohai.domain}" => "domain is '#{ohai.domain}'",
174
+ "#{platform} Systems" => "platform is '#{ohai.platform}'",
175
+ "Network: #{ip}.x" => "ipaddress starts with '#{ip}.'"
176
+ }.each do |name, code|
177
+ Vines::Services::CouchModels::Service.new(
178
+ name: name,
179
+ jid: to_jid(name),
180
+ code: code,
181
+ users: [user.jid],
182
+ accounts: [Etc.getlogin]).save
183
+ end
184
+ end
185
+
186
+ def to_jid(name)
187
+ Blather::JID.new(CGI.escape(name), "vines.#{@domain}").to_s.downcase
188
+ end
189
+
190
+ def update_services_config(config)
150
191
  text = File.read(config)
151
192
  File.open(config, 'w') do |f|
152
- f.write(text.gsub(/password.*/, "password '#{password}'"))
193
+ replaced = text
194
+ .gsub('wonderland.lit', @domain.downcase)
195
+ .gsub('secr3t', @token)
196
+ .gsub("host 'localhost'", "host '#{@db[:host]}'")
197
+ .gsub("port 5984", "port #{@db[:port]}")
198
+ .gsub("database 'xmpp'", "database '#{@db[:name]}'")
199
+ f.write(replaced)
153
200
  end
154
201
  end
155
202
 
156
- # Return the fully qualified domain name for this machine. This is used
157
- # to determine the agent's JID.
158
- def fqdn
159
- require 'ohai'
160
- system = Ohai::System.new
161
- system.require_plugin('os')
162
- system.require_plugin('hostname')
163
- system.fqdn.downcase
164
- end
165
-
166
- # Change the config file to contain the values we generated or validated.
167
- def update_config(config)
168
- # FIXME vines.local
169
- orig_config = """
170
- host 'vines.local' do
171
- cross_domain_messages false
172
- private_storage false
173
- storage 'fs' do
174
- dir 'data/users'
175
- end
176
- # components 'tea' => 'secr3t',
177
- # 'cake' => 'passw0rd'
178
- end"""
179
- new_config = """
180
- host '#{@domain}' do
181
- cross_domain_messages false
182
- private_storage true
183
- storage 'couchdb' do
184
- host '#{@couch_server}'
185
- port #{@couch_port}
186
- database '#{@couch_db}'
187
- tls false
188
- username ''
189
- password ''
190
- end
191
- components 'vines' => '#{@component_password}'
192
- end"""
203
+ def update_server_config(config)
204
+ replacement = %Q{
205
+ storage 'couchdb' do
206
+ host '#{@db[:host]}'
207
+ port #{@db[:port]}
208
+ database '#{@db[:name]}'
209
+ tls false
210
+ username ''
211
+ password ''
212
+ end
213
+ components 'vines' => '#{@token}'
214
+ }
193
215
  text = File.read(config)
194
- text = text.gsub(orig_config, new_config)
195
- text = text.gsub("host 'vines.wonderland.lit'", "host 'vines.#{@domain}'")
196
- text = text.gsub("secr3t", "#{@component_password}")
197
- text = text.gsub("host 'localhost'", "host '#{@couch_server}'")
198
- text = text.gsub("port 5984", "port #{@couch_port}")
199
- text = text.gsub("database 'xmpp'", "database '#{@couch_db}'")
200
216
  File.open(config, 'w') do |f|
201
- f.write(text)
217
+ replaced = text
218
+ .gsub('Vines::Config.configure do', "require 'vines/services/roster'\n\nVines::Config.configure do")
219
+ .gsub(/\s{4}storage 'fs' do.*\s{4}end/m, replacement)
220
+ f.write(replaced)
202
221
  end
203
222
  end
204
223
  end
205
224
  end
206
225
  end
207
226
  end
208
-
209
-
@@ -19,18 +19,23 @@ module Vines
19
19
  private
20
20
 
21
21
  # Forward the agent's response message to the user that sent it the
22
- # command.
22
+ # command. Tag the message with a jid element, identifying the agent
23
+ # returning the command's output, like:
24
+ # <jid xmlns="http://getvines.com/protocol">
25
+ # www01.wonderland.lit@wonderland.lit/vines
26
+ # </jid>.
23
27
  def forward_to_user
24
28
  jid = node.xpath('/message/ns:jid', 'ns' => NS).first
25
29
  return unless jid
26
- jid.remove
30
+ agent = node.from
27
31
  node.from = node.to
28
32
  node.to = jid.content
33
+ jid.content = agent
29
34
  stream.write(node)
30
35
  end
31
36
 
32
37
  # Forward the user's message to the members of the service. Tag the
33
- # message with a jid element, identifying the user executing the command
38
+ # message with a jid element, identifying the user executing the command,
34
39
  # like: <jid xmlns="http://getvines.com/protocol">alice@wonderland.lit/tea</jid>
35
40
  # When the agent receives a message from one of its services, it checks
36
41
  # this jid element for the user permissions with which to run the command.
@@ -39,28 +39,29 @@ module Vines
39
39
  raise 'user already exists' if User.find(id)
40
40
  User.new(id: id).tap do |u|
41
41
  u.system = system
42
- u.password = password1
42
+ u.plain_password = password1
43
43
  end
44
44
  else # existing user
45
45
  User.get!("user:%s" % Vines::JID.new(jid)).tap do |u|
46
46
  raise "record found, but is a #{u.system ? 'system' : 'user'}" unless u.system == system
47
47
  if system
48
- u.password = password1 unless password1.empty?
48
+ u.plain_password = password1 unless password1.empty?
49
49
  else # humans
50
- u.change_password(password1, password2) unless password1.empty? || password2.empty?
50
+ if u.jid == current_user.jid
51
+ u.change_password(password1, password2) unless password1.empty? || password2.empty?
52
+ else
53
+ u.plain_password = password1 unless password1.empty?
54
+ end
51
55
  end
52
56
  end
53
57
  end
54
58
 
55
59
  unless system
56
60
  user.name = name
57
- if user.jid == current_user.jid && !current_user.manage_services
58
- if obj['permissions']['systems'] == true
59
- user.valid = false
60
- send_error('not-acceptable')
61
- end
61
+ # users can't set their own permissions
62
+ unless user.jid == current_user.jid
63
+ user.permissions = obj['permissions']
62
64
  end
63
- user.permissions = obj['permissions']
64
65
  end
65
66
 
66
67
  if user.valid?
@@ -85,8 +86,7 @@ module Vines
85
86
  end
86
87
 
87
88
  def save_services(user, services)
88
- return if user.system?
89
- return unless user.permissions['manage_services']
89
+ return if user.system? || !user.manage_services?
90
90
  current = user.services.map {|s| s.id }
91
91
  members = []
92
92
 
@@ -35,7 +35,7 @@ module Vines
35
35
  # index, these tasks may be delayed by query tasks.
36
36
  def <<(doc)
37
37
  @tasks.push({
38
- priority: -Time.now.to_f,
38
+ priority: Time.now.to_f,
39
39
  type: :index,
40
40
  doc: doc
41
41
  })
@@ -6,7 +6,7 @@ module Vines
6
6
  # Behaves just like EM::Queue with the exception that items added to the queue
7
7
  # are sorted and popped from the queue by their priority. The optional comparator
8
8
  # block passed in the constructor determines element priority, with items sorted
9
- # highest to lowest. If no block is provided, the elements' natural ordering,
9
+ # lowest to highest. If no block is provided, the elements' natural ordering,
10
10
  # via <=>, is used.
11
11
  #
12
12
  # @example
@@ -14,15 +14,15 @@ module Vines
14
14
  # q = PriorityQueue.new do |a, b|
15
15
  # a[:priority] <=> b[:priority]
16
16
  # end
17
- # q.push({:priority => 1, :msg => 'one'})
18
- # q.push({:priority => 2, :msg => 'two'})
19
17
  # q.push({:priority => 3, :msg => 'three'})
18
+ # q.push({:priority => 2, :msg => 'two'})
19
+ # q.push({:priority => 1, :msg => 'one'})
20
20
  # 3.times do
21
21
  # q.pop {|item| puts item[:msg] }
22
22
  # end
23
- # #=> "three"
23
+ # #=> "one"
24
24
  # # "two"
25
- # # "one"
25
+ # # "three"
26
26
  #
27
27
  class PriorityQueue < EM::Queue
28
28
  def initialize(&comparator)
@@ -30,7 +30,7 @@ module Vines
30
30
  @items = Heap.new(&comparator)
31
31
  end
32
32
 
33
- # A binary max heap implementation for efficient storage of queue items. This
33
+ # A binary min heap implementation for efficient storage of queue items. This
34
34
  # class implements the Array methods called by EM::Queue so that it may
35
35
  # replace the +@items+ instance variable. Namely, +push+ +shift+, +size+, and
36
36
  # +empty?+ are implemented.
@@ -42,7 +42,7 @@ module Vines
42
42
  end
43
43
 
44
44
  def push(*items)
45
- items.flatten.each do |item|
45
+ items.each do |item|
46
46
  @heap << item
47
47
  move_up(@heap.size - 1)
48
48
  end
@@ -73,17 +73,17 @@ module Vines
73
73
  left = 2 * k + 1
74
74
  right = 2 * k + 2
75
75
  return if left > (@heap.size - 1)
76
- larger = (right < @heap.size && @comp[@heap[right], @heap[left]] > 0) ? right : left
77
- if @comp[@heap[k], @heap[larger]] < 0
78
- @heap[k], @heap[larger] = @heap[larger], @heap[k]
79
- move_down(larger)
76
+ smaller = (right < @heap.size && @comp[@heap[right], @heap[left]] < 0) ? right : left
77
+ if @comp[@heap[k], @heap[smaller]] > 0
78
+ @heap[k], @heap[smaller] = @heap[smaller], @heap[k]
79
+ move_down(smaller)
80
80
  end
81
81
  end
82
82
 
83
83
  def move_up(k)
84
84
  return if k == 0
85
85
  parent = (k - 1) / 2
86
- if @comp[@heap[k], @heap[parent]] > 0
86
+ if @comp[@heap[k], @heap[parent]] < 0
87
87
  @heap[k], @heap[parent] = @heap[parent], @heap[k]
88
88
  move_up(parent)
89
89
  end