vines-services 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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