bits-installer 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,13 @@
1
+ require 'bits/bit_reader'
2
+
3
+ module Bits
4
+ class BitReaderLocal < BitReader
5
+ def initialize(path)
6
+ @path = path
7
+ end
8
+
9
+ def read
10
+ File.new(@path).read
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,7 @@
1
+ module Bits
2
+ class BitReader
3
+ def read
4
+ raise 'not implemented: read'
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,48 @@
1
+ require 'optparse'
2
+
3
+ module Bits
4
+ # Baseclass for commands.
5
+ class Command
6
+ attr_reader :ns
7
+
8
+ def setup(opts)
9
+ raise "not implemented: setup"
10
+ end
11
+
12
+ def initialize(ns)
13
+ @ns = ns
14
+ end
15
+ end
16
+
17
+ class << self
18
+ def commands
19
+ @commands ||= {}
20
+ end
21
+
22
+ def define_command(command_id, params={}, &block)
23
+ if commands[command_id]
24
+ raise "Command already defined: #{command_id}"
25
+ end
26
+
27
+ desc = params[:desc] || "(no description)"
28
+
29
+ klass = Class.new(Command) do
30
+ @command_id = command_id
31
+ @name = command_id.to_s.capitalize
32
+ @desc = desc
33
+
34
+ class << self
35
+ attr_reader :command_id, :name, :desc
36
+
37
+ def to_s
38
+ "Bits::#{@name}"
39
+ end
40
+ end
41
+ end
42
+
43
+ klass.class_eval(&block)
44
+
45
+ commands[command_id] = klass
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,39 @@
1
+ require 'bits/spawn'
2
+ require 'json'
3
+
4
+ module Bits
5
+ # Helper functions for writing providers which mainly interacts with external
6
+ # commands.
7
+ module CommandProvider
8
+ module ClassMethods
9
+ def check_command(command, name)
10
+ begin
11
+ exit_code = Bits.spawn command, :stdout => NULL
12
+ rescue Errno::ENOENT
13
+ log.debug "#{name} command not available"
14
+ return false
15
+ end
16
+
17
+ if exit_code != 0 then
18
+ log.debug "#{name} command could not be invoked"
19
+ return false
20
+ end
21
+
22
+ log.debug "#{name} command available"
23
+ true
24
+ end
25
+ end
26
+
27
+ def check_command(*args)
28
+ self.class.check_command(*args)
29
+ end
30
+
31
+ def run(*args)
32
+ self.class.run(*args)
33
+ end
34
+
35
+ def self.included(base)
36
+ base.extend ClassMethods
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,75 @@
1
+ require 'bits/command'
2
+ require 'bits/logging'
3
+
4
+ require 'highline'
5
+
6
+ module Bits
7
+ define_command :install, :desc => 'Install a package' do
8
+ include Bits::Logging
9
+
10
+ def setup(opts)
11
+ ns[:force] = false
12
+ ns[:compiled] = nil
13
+
14
+ opts.banner = "Usage: bits install <bit>"
15
+ opts.on('--[no-]compiled', "Insist on installing an already compiled variant or not") do |v|
16
+ ns[:compiled] = v
17
+ end
18
+
19
+ opts.on('--force', "Insist on installing even if packages already installed") do |v|
20
+ ns[:force] = v
21
+ end
22
+ end
23
+
24
+ def entry(args)
25
+ if args.empty? then
26
+ puts parser.help
27
+ return 1
28
+ end
29
+
30
+ atom = args[0]
31
+
32
+ repository = ns[:repository]
33
+
34
+ parameters = {}
35
+ parameters[:compiled] = ns[:compiled] if ns.has_key? :compiled
36
+
37
+ p = repository.find_package atom, parameters
38
+
39
+ if p.installed? and not ns[:force]
40
+ log.info "Already installed '#{atom}' using provider(s): #{p.providers_s}"
41
+ return 0
42
+ end
43
+
44
+ matching = p.matching_ppps
45
+
46
+ raise "No matching PPP could be found" if matching.empty?
47
+
48
+ ppp = pick_one atom, matching
49
+
50
+ install_ppp atom, ppp
51
+ return 0
52
+ end
53
+
54
+ def pick_one(atom, matching)
55
+ return matching[0] if matching.size == 1
56
+
57
+ hl = HighLine.new $stdin
58
+
59
+ hl.choose do |menu|
60
+ menu.prompt = "Which provider would you like to install '#{atom}' with?"
61
+ matching.each do |match|
62
+ menu.choice(match.provider.provider_id) { match }
63
+ end
64
+ end
65
+ end
66
+
67
+ def install_ppp(atom, ppp)
68
+ provider = ppp.provider
69
+ package = ppp.package
70
+
71
+ log.info "Installing '#{atom}' using provider: #{provider.provider_id}"
72
+ provider.install package
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,35 @@
1
+ require 'bits/command'
2
+ require 'bits/logging'
3
+
4
+ module Bits
5
+ define_command :remove, :desc => "Remove a package" do
6
+ include Bits::Logging
7
+
8
+ def setup(opts)
9
+ opts.banner = "Usage: bits remove <bit>"
10
+ end
11
+
12
+ def entry(args)
13
+ if args.empty? then
14
+ puts parser.help
15
+ return 1
16
+ end
17
+
18
+ atom = args[0]
19
+
20
+ repository = @ns[:repository]
21
+
22
+ p = repository.find_package atom
23
+
24
+ unless p.installed?
25
+ log.info "Package not installed '#{atom}'"
26
+ return 0
27
+ end
28
+
29
+ log.info "Removing '#{atom}' using provider(s): #{p.providers_s}"
30
+
31
+ p.remove
32
+ return 0
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,49 @@
1
+ require 'bits/command'
2
+ require 'bits/logging'
3
+ require 'bits/exceptions'
4
+
5
+ module Bits
6
+ define_command :show, :desc => "Show a package" do
7
+ include Bits::Logging
8
+
9
+ def setup(opts)
10
+ opts.banner = "Usage: bits show <bit>"
11
+ opts.separator ""
12
+ opts.separator "Show information about the specified bit."
13
+ end
14
+
15
+ def entry(args)
16
+ if args.empty? then
17
+ puts parser.help
18
+ return 1
19
+ end
20
+
21
+ atom = args[0]
22
+
23
+ repository = ns[:repository]
24
+
25
+ criteria = {}
26
+ criteria[:compiled] = ns[:compiled] if ns.has_key? :compiled
27
+
28
+ begin
29
+ p = repository.find_package atom, criteria
30
+ rescue MissingBit
31
+ puts "No such atom '#{atom}'"
32
+ return 1
33
+ end
34
+
35
+ puts "PPPs:"
36
+
37
+ p.ppps.each do |ppp|
38
+ puts " #{ppp.provider.provider_id}:"
39
+ puts " Bit: #{ppp.bit.atom}"
40
+ puts " Package Atom: #{ppp.package.atom}"
41
+ puts " Installed: #{ppp.package.installed_s}"
42
+ puts " Candidate: #{ppp.package.candidate_s}"
43
+ puts " Parameters: #{ppp.parameters}"
44
+ end
45
+
46
+ return 0
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,34 @@
1
+ require 'bits/command'
2
+ require 'bits/logging'
3
+ require 'bits/exceptions'
4
+
5
+ require 'fileutils'
6
+
7
+ module Bits
8
+ define_command :sync, :desc => "sync local repository" do
9
+ include Bits::Logging
10
+
11
+ GIT = 'git'
12
+ CLONE_URL = 'https://github.com/udoprog/bits-repo'
13
+
14
+ def setup(opts)
15
+ opts.banner = "Usage: bits sync"
16
+ opts.separator ""
17
+ opts.separator "Sync local repository"
18
+ end
19
+
20
+ def entry(args)
21
+ dir = ns[:local_repository_dir]
22
+
23
+ setup_original dir unless File.directory? dir
24
+
25
+ Dir.chdir(dir) do
26
+ Bits.spawn [GIT, 'pull', 'origin', 'master']
27
+ end
28
+ end
29
+
30
+ def setup_original(dir)
31
+ Bits.spawn [GIT, 'clone', CLONE_URL, dir]
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,12 @@
1
+ module Bits
2
+ class ProviderException < Exception; end
3
+
4
+ # Is raised when a package being requested does not exist.
5
+ class MissingPackage < ProviderException; end
6
+
7
+ # Is raised when a bit does not exist.
8
+ class MissingBit < ProviderException; end
9
+
10
+ # Is raised when a package being requested does not exist.
11
+ class MissingProvidedPackage < ProviderException; end
12
+ end
@@ -0,0 +1,24 @@
1
+ module Bits
2
+ class ExecuteContext
3
+ SUDO = 'sudo'
4
+
5
+ def initialize(user)
6
+ @user = user
7
+ end
8
+
9
+ def spawn(args, params={})
10
+ superuser = params[:superuser] || false
11
+
12
+ if superuser and not @user.superuser?
13
+ Bits.spawn [SUDO] + args, params
14
+ else
15
+ Bits.spawn args, params
16
+ end
17
+ end
18
+
19
+ def run(args, params={})
20
+ params[:ignore_exitcode] = true
21
+ spawn(args, params) == 0
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,204 @@
1
+ require 'bits/logging'
2
+
3
+ require 'rubygems'
4
+ require 'json'
5
+
6
+ module Bits
7
+ module ExternalInterface
8
+ class Interface
9
+ include Bits::Logging
10
+
11
+ attr_reader :capabilities
12
+
13
+ def initialize(id, args, stdin, stdout, pid)
14
+ @id = id
15
+ @args = args
16
+ @stdin = stdin
17
+ @stdout = stdout
18
+ @pid = pid
19
+ @capabilities = []
20
+ end
21
+
22
+ # end the child process by closing stdin.
23
+ def end
24
+ @stdin.close
25
+ Process.wait @pid
26
+ $?
27
+ end
28
+
29
+ def info(atom)
30
+ type, response = request :info, :atom => atom
31
+ end
32
+
33
+ def ping
34
+ begin
35
+ type, response = request :ping
36
+ rescue
37
+ log.debug "problem while pinging interface '#{@id}': #{$!}"
38
+ return nil
39
+ end
40
+
41
+ raise "Expected pong but got #{type}" unless type == :pong
42
+ @capabilities = (response['capabilities'] || []).map(&:to_sym)
43
+ return true
44
+ end
45
+
46
+ def request(type, command={})
47
+ command[:__type__] = type
48
+
49
+ write JSON.dump(command)
50
+ data = read
51
+
52
+ raise "empty response" if data.nil?
53
+
54
+ response = JSON.load(data)
55
+
56
+ response_type = response['__type__'].to_sym
57
+
58
+ if response_type == :error
59
+ error_text = response['text']
60
+ raise "Error in interface: #{error_text}"
61
+ end
62
+
63
+ [response_type, response]
64
+ end
65
+
66
+ def close
67
+ return if @stdin.nil? and @stdout.nil?
68
+ reap_child
69
+ end
70
+
71
+ private
72
+
73
+ def reap_child
74
+ @stdin.close unless @stdin.nil?
75
+ @stdout.close unless @stdout.nil?
76
+
77
+ @stdin = nil
78
+ @stdout = nil
79
+
80
+ # don't hang since write might have reaped it already.
81
+ Process.wait @pid, Process::WNOHANG
82
+ end
83
+
84
+ def write(data)
85
+ return if @stdin.nil?
86
+ @stdin.puts data
87
+ @stdin.flush
88
+ rescue
89
+ reap_child
90
+ raise
91
+ end
92
+
93
+ # read or no-op if it has been closed.
94
+ def read
95
+ return if @stdout.nil?
96
+ @stdout.gets
97
+ rescue
98
+ reap_child
99
+ raise
100
+ end
101
+ end
102
+
103
+ module ClassMethods
104
+ # access global interface cache for class methods.
105
+ def interfaces
106
+ ExternalInterface.interfaces
107
+ end
108
+
109
+ # Spawn an interface that is shared between all users.
110
+ def setup_interface(id, params = {})
111
+ capabilities = params[:capabilities] || []
112
+
113
+ interface = spawn_interface id
114
+
115
+ if interface.nil?
116
+ log.debug "Interface '#{id}' not available"
117
+ return false
118
+ end
119
+
120
+ missing_capabilities = capabilities - interface.capabilities
121
+
122
+ unless missing_capabilities.empty?
123
+ missing_s = missing_capabilities.join ', '
124
+ log.debug "Interface '#{id}' is available, but is missing capabilities: #{missing_s}"
125
+ return false
126
+ end
127
+
128
+ has_s = capabilities.join ', '
129
+ log.debug "Interface '#{id}' is available with capabilities: #{has_s}"
130
+ return true
131
+ end
132
+
133
+ private
134
+
135
+ def spawn_interface(id)
136
+ unless interfaces[id].nil?
137
+ return interfaces[id]
138
+ end
139
+
140
+ libexec_path = path_to_libexec "bits-#{id}"
141
+
142
+ command = [libexec_path]
143
+
144
+ stdout_r, stdout_w = IO.pipe
145
+ stdin_r, stdin_w = IO.pipe
146
+
147
+ pid = fork do
148
+ stdin_w.close
149
+ stdout_r.close
150
+
151
+ $stdin.reopen stdin_r
152
+ $stdout.reopen stdout_w
153
+
154
+ begin
155
+ exec(*command)
156
+ rescue Errno::ENOENT
157
+ $stdout.close
158
+ $stdin.close
159
+ exit ENOENT
160
+ end
161
+
162
+ exit 1
163
+ end
164
+
165
+ stdin_r.close
166
+ stdout_w.close
167
+
168
+ interface = Interface.new(id, command, stdin_w, stdout_r, pid)
169
+
170
+ if not interface.ping
171
+ interface.close
172
+ return nil
173
+ end
174
+
175
+ interfaces[id] = interface
176
+ end
177
+
178
+ def path_to_libexec(name)
179
+ File.join File.dirname(File.expand_path(__FILE__)), File.join('..', 'libexec', name)
180
+ end
181
+ end
182
+
183
+ # global cache for interfaces.
184
+ def self.interfaces
185
+ @interfaces ||= {}
186
+ end
187
+
188
+ # access global interface cache for instances.
189
+ def interfaces
190
+ self.class.interfaces
191
+ end
192
+
193
+ # close all global interfaces
194
+ def self.close_interfaces
195
+ interfaces.each do |id, interface|
196
+ interface.close
197
+ end
198
+ end
199
+
200
+ def self.included(base)
201
+ base.extend ClassMethods
202
+ end
203
+ end
204
+ end