bwrap 1.0.0.pre.alpha1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bwrap/version"
4
+ require_relative "execution"
5
+
6
+ # Exit codes for exit status handling.
7
+ #
8
+ # These are not strictly speaking related to execution system,
9
+ # but rather to handle execution status of actual scripts.
10
+ # For example, to set exit code for {Bwrap::Output#error} call.
11
+ #
12
+ # Since this is fully own module, it shouldn’t harm for this to be here.
13
+ module Bwrap::Execution::Labels
14
+ # If we had a successful run, i.e. no error happened.
15
+ NO_ERROR = 0
16
+
17
+ # Do not use.
18
+ INVALID_ERROR = 1
19
+
20
+ # When Output#error has been called with no :label.
21
+ UNSPECIFIED_ERROR = 2
22
+
23
+ # If no better error code has been created.
24
+ GENERIC_ERROR = 3
25
+
26
+ # Converts given label to real exit code.
27
+ #
28
+ # @param label [Symbol] Label to be converted to a exit code
29
+ #
30
+ # Accepted labels:
31
+ # - :success
32
+ # - :unspecified
33
+ # - :generic
34
+ # - :target_host_not_found
35
+ # - :no_package_manager
36
+ # - :debootstrap_failed
37
+ # - :file_not_modified
38
+ #
39
+ # Additional labels can be added with {.add_label}.
40
+ #
41
+ # @return [Integer] Exit code between 0 to 255
42
+ def self.resolve_exit_code label
43
+ return @additional_labels[label] if @additional_labels and @additional_labels[label]
44
+
45
+ case label
46
+ when :success
47
+ NO_ERROR
48
+ when :unspecified
49
+ UNSPECIFIED_ERROR
50
+ when :generic
51
+ GENERIC_ERROR
52
+ else
53
+ warn "Got invalid error label: #{label}"
54
+ INVALID_ERROR
55
+ end
56
+ end
57
+
58
+ # Add a custom label.
59
+ #
60
+ # @param label [Symbol] Name of exit code label
61
+ # @param exit_code [Integer] Number in range 0-255
62
+ def self.add_label label, exit_code
63
+ @additional_labels ||= {}
64
+ @additional_labels[label] = exit_code
65
+ end
66
+ end
@@ -0,0 +1,177 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bwrap/version"
4
+ require "bwrap/output"
5
+ require_relative "execution/execute"
6
+
7
+ # @abstract Module to be included in a class that needs to execute commands.
8
+ #
9
+ # Methods to execute processes.
10
+ module Bwrap::Execution
11
+ include Bwrap::Output
12
+
13
+ # Unspecified execution related error.
14
+ class CommandError < StandardError
15
+ end
16
+
17
+ # Signifies that command execution has failed.
18
+ class ExecutionFailed < CommandError
19
+ end
20
+
21
+ # Actual implementation of execution command. Can be used when static method is needed.
22
+ #
23
+ # @note When an array is given as a command, empty strings are passed as empty arguments.
24
+ #
25
+ # This means that `[ "foo", "bar" ]` passes one argument to "foo" command, when
26
+ # `[ "foo", "", "bar" ]` passes two arguments.
27
+ #
28
+ # This may or may not be what is assumed, so it can’t be fixed here. It is up to the
29
+ # command to decide how to handle empty arguments.
30
+ #
31
+ # Returns pid of executed command if wait is false.
32
+ # Returns command output if wait is true.
33
+ #
34
+ # fail == If true, an error is raised in case the command returns failure code.
35
+ #
36
+ # @param error if :show, warn()s output of the command is shown if execution failed.
37
+ #
38
+ # @see #execute
39
+ def self.do_execute command,
40
+ fail: true,
41
+ wait: true,
42
+ log: true,
43
+ direct_output: false,
44
+ env: {},
45
+ clear_env: false,
46
+ error: nil,
47
+ log_callback: 2,
48
+ rootcmd: nil
49
+ command = Execute.format_command command, rootcmd: rootcmd
50
+ Execute.handle_logging command, log_callback: log_callback, log: log, dry_run: @dry_run
51
+ return if @dry_run || Bwrap::Execution::Execute.dry_run
52
+
53
+ Execute.open_pipes direct_output
54
+
55
+ # If command is an array, there can’t be arrays inside the array.
56
+ # For convenience, the array is flattened here, so callers can construct commands more easily.
57
+ if command.respond_to? :flatten!
58
+ command.flatten!
59
+ end
60
+
61
+ # If command is string, splat operator (the *) does not do anything. If array, it expand the arguments.
62
+ # This causes spawning work correctly, as that’s how spawn() expects to have the argu
63
+ pid = spawn(env, *command, err: [ :child, :out ], out: Execute.w, unsetenv_others: clear_env)
64
+ output = Execute.finish_execution(log: log, wait: wait, direct_output: direct_output)
65
+ return pid unless wait
66
+
67
+ # This is instant return, but allows us to have $?/$CHILD_STATUS set.
68
+ Process.wait pid
69
+ @last_status = $CHILD_STATUS
70
+
71
+ output = Execute.process_output output: output
72
+ Execute.handle_execution_fail fail: fail, error: error, output: output
73
+ output
74
+ ensure
75
+ Execute.clean_variables
76
+ end
77
+
78
+ # Returns Process::Status instance of last execution.
79
+ #
80
+ # @note This only is able to return the status if wait is true, as otherwise caller is assumed to
81
+ # handle execution flow.
82
+ def self.last_status
83
+ @last_status
84
+ end
85
+
86
+ # Check if requested program can be found.
87
+ #
88
+ # Should work cross-platform and in restricted environents pretty well.
89
+ private def command_available? command
90
+ exts = ENV["PATHEXT"] ? ENV["PATHEXT"].split(";") : [ "" ]
91
+ ENV["PATH"].split(File::PATH_SEPARATOR).each do |path|
92
+ exts.each do |ext|
93
+ exe = File.join(path, "#{command}#{ext}")
94
+ return true if File.executable?(exe) && !File.directory?(exe)
95
+ end
96
+ end
97
+ false
98
+ end
99
+
100
+ # Returns path to given executable.
101
+ private def which command, fail: true
102
+ exts = ENV["PATHEXT"] ? ENV["PATHEXT"].split(";") : [ "" ]
103
+ ENV["PATH"].split(File::PATH_SEPARATOR).each do |path|
104
+ exts.each do |ext|
105
+ exe = File.join(path, "#{command}#{ext}")
106
+ return exe if File.executable?(exe) && !File.directory?(exe)
107
+ end
108
+ end
109
+ error "Failed to find #{command} from PATH." if fail
110
+ nil
111
+ end
112
+
113
+ # Execute a command.
114
+ #
115
+ # This method can be used by including Execution module in a class that should be able to
116
+ # execute commands.
117
+ #
118
+ # @see .do_execute .do_execute for documentation of argument syntax
119
+ private def execute *args
120
+ # Mangle proper location to error message.
121
+ if args.last.is_a? Hash
122
+ args.last[:log_callback] = 3
123
+ else
124
+ args << { log_callback: 3 }
125
+ end
126
+ Bwrap::Execution.do_execute(*args)
127
+ end
128
+
129
+ # Same as ::execute, but uses log: false to avoid unnecessary output when we’re just getting a
130
+ # value for internal needs.
131
+ #
132
+ # Defaults to fail: false, since when one just wants to get the value, there is not that much
133
+ # need to unconditionally die if getting bad exit code.
134
+ private def execvalue *args, fail: false, rootcmd: nil, env: {}
135
+ # This logging handling is a bit of duplication from execute(), but to be extra safe, it is duplicated.
136
+ # The debug message contents will always be evaluated, so can just do it like this.
137
+ log_command = args[0].respond_to?(:join) && args[0].join(" ") || args[0]
138
+ log_command =
139
+ if log_command.frozen?
140
+ log_command.dup.force_encoding("UTF-8")
141
+ else
142
+ log_command.force_encoding("UTF-8")
143
+ end
144
+ if @dry_run
145
+ puts "Would execvalue “#{log_command}” at #{caller_locations(1, 1)[0]}"
146
+ return
147
+ end
148
+ trace "Execvaluing “#{log_command}” at #{caller_locations(1, 1)[0]}"
149
+ execute(*args, fail: fail, log: false, rootcmd: rootcmd, env: env)
150
+ end
151
+
152
+ private def exec_success?
153
+ $CHILD_STATUS.success?
154
+ end
155
+
156
+ private def exec_failure?
157
+ !exec_success?
158
+ end
159
+
160
+ # When running through bundler, don’t use whatever it defines as we’re running inside chroot.
161
+ private def clean_execute
162
+ if (Bundler&.bundler_major_version) >= 2
163
+ Bundler.with_unbundled_env do
164
+ yield 2
165
+ end
166
+ elsif Bundler&.bundler_major_version == 1
167
+ Bundler.with_clean_env do
168
+ yield 1
169
+ end
170
+ else
171
+ yield nil
172
+ end
173
+ rescue NameError
174
+ # If NameError is thrown, no Bundler is available.
175
+ yield nil
176
+ end
177
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "output"
4
+
5
+ # Methods to color CLI output.
6
+ module Bwrap::Output::Colors
7
+ # Outputs ANSI true color sequence.
8
+ def self.color red, green, blue, bold: false
9
+ return unless $stdout.tty?
10
+ return if ENV["COLORTERM"] != "truecolor" && ENV["COLORTERM"] != "24bit"
11
+
12
+ out = ""
13
+ out += "\x1b[38;1;1m" if bold
14
+ # https://stackoverflow.com/questions/4842424/list-of-ansi-color-escape-sequences
15
+ out += "\x1b[38;2;#{red};#{green};#{blue}m"
16
+
17
+ out
18
+ end
19
+
20
+ # Outputs ANSI command to stop color output.
21
+ def self.stopcolor
22
+ return unless $stdout.tty?
23
+ return if ENV["COLORTERM"] != "truecolor" && ENV["COLORTERM"] != "24bit"
24
+
25
+ "\x1b[0m"
26
+ end
27
+
28
+ # Calls `Bwrap::Output::Colors#color`.
29
+ private def color red, green, blue, bold: false
30
+ Bwrap::Output::Colors.color red, green, blue, bold
31
+ end
32
+
33
+ # Calls `Bwrap::Output::Colors#stopcolor`.
34
+ private def stopcolor
35
+ Bwrap::Output::Colors.stopcolor
36
+ end
37
+ end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "colors"
4
+
5
+ # Output level functionality.
6
+ class Bwrap::Output::Levels
7
+ include Bwrap::Output::Colors
8
+
9
+ @@_verbose = false
10
+ @@_debug = false
11
+ @@_trace = ENV["BWRAP_TRACE"] && true || false
12
+
13
+ # @see Bwrap::Output#verbose?
14
+ def self.verbose?
15
+ @@_verbose
16
+ end
17
+
18
+ # @see Bwrap::Output##debug?
19
+ def self.debug?
20
+ @@_debug
21
+ end
22
+
23
+ # @see Bwrap::Output##trace?
24
+ def self.trace?
25
+ @@_trace
26
+ end
27
+
28
+ # Takes hash of options received from Optimist and checks output related flags.
29
+ def self.handle_output_options options
30
+ # Set output level flags to true or false, if it was given.
31
+ unless options[:trace].nil?
32
+ @@_verbose = options[:trace]
33
+ @@_debug = options[:trace]
34
+ @@_trace = options[:trace]
35
+ end
36
+ unless options[:debug].nil?
37
+ @@_verbose = options[:debug]
38
+ @@_debug = options[:debug]
39
+ end
40
+ unless options[:verbose].nil?
41
+ @@_verbose = options[:verbose]
42
+ end
43
+ end
44
+
45
+ # Formats given string and outputs it.
46
+ #
47
+ # @return formatted string
48
+ def self.trace_print_formatted str, log_callback: 1
49
+ out = "#{Bwrap::Output::Colors.color(230, 165, 240)}[TRACE]#{Bwrap::Output::Colors.stopcolor} #{str}"
50
+ out = append_caller out, log_callback: (log_callback + 1)
51
+ puts out
52
+
53
+ out
54
+ end
55
+
56
+ # Formats given string and outputs it.
57
+ #
58
+ # @return formatted string
59
+ def self.debug_print_formatted str, log_callback: 1
60
+ out = "#{Bwrap::Output::Colors.color(190, 190, 190)}[DEBUG]#{Bwrap::Output::Colors.stopcolor} #{str}"
61
+ out = append_caller out, log_callback: (log_callback + 1)
62
+ puts out
63
+
64
+ out
65
+ end
66
+
67
+ # Formats given string and outputs it.
68
+ #
69
+ # @return formatted string
70
+ def self.verbose_print_formatted str, log_callback: 1
71
+ out = "#{Bwrap::Output::Colors.color(130, 230, 130, bold: true)}[INFO]#{Bwrap::Output::Colors.stopcolor} #{str}"
72
+ out = append_caller out, log_callback: (log_callback + 1)
73
+ puts out
74
+
75
+ out
76
+ end
77
+
78
+ # Formats given string and outputs it.
79
+ #
80
+ # @return formatted string
81
+ def self.warning_print_formatted str, log_callback: 1
82
+ out = "#{Bwrap::Output::Colors.color(190, 190, 110, bold: true)}[WARN]#{Bwrap::Output::Colors.stopcolor} #{str}"
83
+ out = "#{out} (called at #{caller_locations(log_callback, 1)[0]})" if @@_debug or @@_trace
84
+ $stderr.puts out
85
+
86
+ out
87
+ end
88
+
89
+ # Formats given string and outputs it.
90
+ def self.error_print_formatted str, log_callback: 1
91
+ out = "#{Bwrap::Output::Colors.color(230, 110, 110, bold: true)}[ERROR]#{Bwrap::Output::Colors.stopcolor} #{str}"
92
+ out = "#{out} (called at #{caller_locations(log_callback, 1)[0]})" if @@_debug or @@_trace
93
+ $stderr.puts out
94
+ end
95
+
96
+ # Private class method.
97
+ #
98
+ # Appends caller information to given output.
99
+ #
100
+ # Used by *_print_formatted methods.
101
+ def self.append_caller out, log_callback: 1
102
+ out = "#{out} (called at #{caller_locations(log_callback, 1)[0]})" if @@_trace
103
+ out
104
+ end
105
+ private_class_method :append_caller
106
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: false
2
+
3
+ # force_encoding modifies string, so can’t freeze strings.
4
+
5
+ require_relative "output"
6
+
7
+ # Logging methods.
8
+ class Bwrap::Output::Log
9
+ @@log_file = nil
10
+
11
+ # Get handle of opened log file.
12
+ def self.log_file
13
+ @@log_file
14
+ end
15
+
16
+ # Writes given string to log.
17
+ def self.write_to_log str
18
+ @@log_file&.write str.force_encoding("UTF-8")
19
+ end
20
+
21
+ # Writes given string to log.
22
+ def self.puts_to_log str
23
+ @@log_file&.puts str.force_encoding("UTF-8")
24
+ end
25
+
26
+ # Closes an open log file.
27
+ def self.close_log_file
28
+ @@log_file&.close
29
+ @@log_file = nil
30
+ end
31
+
32
+ # Starts logging to given file.
33
+ def self.log_to_file log_path
34
+ log_file = File.open log_path, "w"
35
+
36
+ # In default mode, log messages disappears as Ruby’s own buffer gets full.
37
+ # This makes only OS buffer being used.
38
+ log_file.sync = false
39
+
40
+ @@log_file = log_file
41
+
42
+ at_exit do
43
+ ::Bwrap::Output::Log.close_log_file
44
+ end
45
+ end
46
+ end
47
+
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bwrap/version"
4
+
5
+ # Outputting utilities.
6
+ module Bwrap::Output
7
+ # Nya.
8
+ end
@@ -0,0 +1,153 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Have variables like $CHILD_STATUS which is alias of $?.
4
+ require "English"
5
+
6
+ require "bwrap/execution/labels"
7
+ require_relative "output/colors"
8
+ require_relative "output/levels"
9
+ require_relative "output/log"
10
+
11
+ # Extends the module with outputting methods.
12
+ module Bwrap::Output
13
+ include Bwrap::Output::Colors
14
+
15
+ # @see #verbose?
16
+ def self.verbose?
17
+ Bwrap::Output::Levels.verbose?
18
+ end
19
+
20
+ # @see #debug?
21
+ def self.debug?
22
+ Bwrap::Output::Levels.debug?
23
+ end
24
+
25
+ # @see #trace?
26
+ def self.trace?
27
+ Bwrap::Output::Levels.trace?
28
+ end
29
+
30
+ # Takes hash of options received from Optimist and checks output related flags.
31
+ def self.handle_output_options options
32
+ Bwrap::Output::Levels.handle_output_options options
33
+ end
34
+
35
+ # Handler used by #trace to output given string.
36
+ def self.trace_output str, raw: false, log_callback: 1
37
+ return unless trace?
38
+
39
+ if raw
40
+ print str
41
+ else
42
+ out = Bwrap::Output::Levels.trace_print_formatted str, log_callback: (log_callback + 1)
43
+ end
44
+ Bwrap::Output::Log.puts_to_log out || str
45
+ end
46
+
47
+ # Handler used by #debug to output given string.
48
+ def self.debug_output str, raw: false, log_callback: 1
49
+ return unless debug?
50
+
51
+ if raw
52
+ print str
53
+ else
54
+ out = Bwrap::Output::Levels.debug_print_formatted str, log_callback: (log_callback + 1)
55
+ end
56
+ Bwrap::Output::Log.puts_to_log out || str
57
+ end
58
+
59
+ # Handler used by #verb to output given string.
60
+ def self.verb_output str, raw: false, log_callback: 1
61
+ return unless verbose?
62
+
63
+ if raw
64
+ print str
65
+ else
66
+ out = Bwrap::Output::Levels.verbose_print_formatted str, log_callback: (log_callback + 1)
67
+ end
68
+ Bwrap::Output::Log.puts_to_log out || str
69
+ end
70
+
71
+ # Handler used by #warn to output given string.
72
+ def self.warn_output str, raw: false, log_callback: 1
73
+ if raw
74
+ print str
75
+ else
76
+ out = Bwrap::Output::Levels.warning_print_formatted str, log_callback: (log_callback + 1)
77
+ end
78
+ Bwrap::Output::Log.puts_to_log out || str
79
+ end
80
+
81
+ # Aborts current process.
82
+ #
83
+ # Use this instead of Ruby’s #exit in order to filter out dummy #exit calls from the code.
84
+ def self.error_output str = nil, label: :unspecified, log_callback: 1
85
+ unless str.nil?
86
+ out = Bwrap::Output::Levels.error_print_formatted str, log_callback: (log_callback + 1)
87
+ Bwrap::Output::Log.puts_to_log out
88
+ end
89
+
90
+ exit Bwrap::Execution::Labels.resolve_exit_code(label)
91
+ end
92
+
93
+ # @return true if --verbose, --debug or --trace has been passed, false if not.
94
+ private def verbose?
95
+ Bwrap::Output::Levels.verbose?
96
+ end
97
+
98
+ # @return true if --debug or --trace has been passed, false if not.
99
+ private def debug?
100
+ Bwrap::Output::Levels.debug?
101
+ end
102
+
103
+ # @return true if --trace has been passed, false if not.
104
+ private def trace?
105
+ Bwrap::Output::Levels.trace?
106
+ end
107
+
108
+ # Outputs given string if trace flag has been set.
109
+ #
110
+ # Output flags can be set with {.handle_output_options}.
111
+ #
112
+ # @param str String to be outputted
113
+ # @param raw [Boolean] If true, disables output formatting
114
+ private def trace str, raw: false
115
+ Bwrap::Output.trace_output(str, raw: raw, log_callback: 2)
116
+ end
117
+
118
+ # Outputs given string if debug flag has been set.
119
+ #
120
+ # Output flags can be set with {.handle_output_options}.
121
+ #
122
+ # @param str String to be outputted
123
+ # @param raw [Boolean] If true, disables output formatting
124
+ private def debug str, raw: false
125
+ Bwrap::Output.debug_output(str, raw: raw, log_callback: 2)
126
+ end
127
+
128
+ # Outputs given string if verbose flag has been set.
129
+ #
130
+ # Output flags can be set with {.handle_output_options}.
131
+ #
132
+ # @param str String to be outputted
133
+ # @param raw [Boolean] If true, disables output formatting
134
+ private def verb str, raw: false
135
+ Bwrap::Output.verb_output(str, raw: raw, log_callback: 2)
136
+ end
137
+
138
+ # Outputs given string to `$stderr`.
139
+ #
140
+ # @param str String to be outputted
141
+ # @param raw [Boolean] If true, disables output formatting
142
+ private def warn str, raw: false
143
+ Bwrap::Output.warn_output(str, raw: raw, log_callback: 2)
144
+ end
145
+
146
+ # Outputs given string to `$stderr` and halts execution.
147
+ #
148
+ # @param str String to be outputted
149
+ # @param label [Symbol] Exit label accepted by {Bwrap::Execution.resolve_exit_code}
150
+ private def error str = nil, label: :unspecified
151
+ Bwrap::Output.error_output(str, label: label, log_callback: 2)
152
+ end
153
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ # bwrap command runner tools.
4
+ module Bwrap
5
+ # Current version of bwrap.
6
+ VERSION = "1.0.0-alpha1"
7
+ end
8
+
9
+ require "deep-cover" if ENV["DEEP_COVER"]
data/lib/bwrap.rb ADDED
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "optimist"
4
+
5
+ #require "deep-cover" if ENV["DEEP_COVER"]
6
+
7
+ require "bwrap/version"
8
+ require "bwrap/args/construct"
9
+ require "bwrap/config"
10
+ require "bwrap/execution"
11
+
12
+ # Executes bwrap command using given configuration.
13
+ class Bwrap::Bwrap
14
+ include Bwrap::Execution
15
+
16
+ def initialize config
17
+ @config = config
18
+
19
+ parse_command_line_arguments
20
+ end
21
+
22
+ # Runs given command inside bwrap.
23
+ #
24
+ # @param command [String, Array] Command, with necessary arguments, to be executed inside bwrap
25
+ def run command
26
+ construct = Bwrap::Args::Construct.new
27
+ construct.config = @config
28
+ bwrap_args = construct.construct_bwrap_args
29
+
30
+ exec_command = [ "bwrap" ]
31
+ exec_command += bwrap_args
32
+ exec_command += %W{ #{command} --new-instance }
33
+ exec_command += ARGV unless ARGV.empty?
34
+
35
+ execute exec_command
36
+
37
+ construct.cleanup
38
+ end
39
+
40
+ # Parses command line arguments given to caller script.
41
+ private def parse_command_line_arguments
42
+ options = optimist_cli_args
43
+
44
+ Bwrap::Output.handle_output_options options
45
+ end
46
+
47
+ # Parses global bwrap flags using Optimist.
48
+ private def optimist_cli_args
49
+ Optimist.options do
50
+ version ::Bwrap::VERSION
51
+
52
+ banner "Usage:"
53
+ banner " #{$PROGRAM_NAME} [global options]\n \n"
54
+ banner "Global options:"
55
+ opt :verbose,
56
+ "Show verbose output",
57
+ short: "v"
58
+ opt :debug,
59
+ "Show debug output (useful when debugging a problem)",
60
+ short: "d"
61
+ opt :trace,
62
+ "Show trace output (noisiest, probably not useful for most of time)",
63
+ short: :none
64
+ opt :version,
65
+ "Print version and exit",
66
+ short: "V"
67
+ opt :help,
68
+ "Show help message",
69
+ short: "h"
70
+
71
+ educate_on_error
72
+ end
73
+ end
74
+ end
data.tar.gz.sig ADDED
Binary file