foreman_remote_execution_core 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dd9eb5801c41deb219aceba0cc4872be707558115fbb4338b925e46a68fc9cb4
4
- data.tar.gz: f50b8d84f62ec3a2dc3773c7ca32d8603d6289fad10db932d624f4497e401de5
3
+ metadata.gz: 80ae26d6b169ea807ed8b6eea817a6797dde93a11fff3ee5a29cf6794b5ea378
4
+ data.tar.gz: ccb07c483e5a2973c11641cb1bc6e6385fc2b33088a4fadbeb41fff60ef2fdff
5
5
  SHA512:
6
- metadata.gz: ed270e5b367116ee7549a7913869ade1c21c15041c87356fb6600adf1e4562d68053a32787dcdeaa832b89d8f6200a5d67a50ce77d8c9dca88878ef2d52a8a10
7
- data.tar.gz: 1f313fbf2dc29a928d56442ea98994810b0c02f890d298904a20ae44994825b6dc2848dcd0f0cf275ed6d371ed1338057b3b256619221e3ad97214f48bdc7c5a
6
+ metadata.gz: 9f9b4d32165279cc36b5c59a7fa901a239c7a67643d4542b6713d1569624107f5e79091d16916a2a9695e20d78b405d1255c6f363e6130b6e0b73903e55deae3
7
+ data.tar.gz: 651c3382363f5684cb2c8f62ce16582cfe8b51dbb9479addb3cd8282c2d90d2e658c2e831a563ffce1cf3f67d5ac2884e2bb1c2eca0f836b2fce1ffe85edd1f3
@@ -0,0 +1,110 @@
1
+ #!/bin/sh
2
+ #
3
+ # Control script for the remote execution jobs.
4
+ #
5
+ # The initial script calls `$CONTROL_SCRIPT init-script-finish` once the original script exits.
6
+ # In automatic mode, the exit code is sent back to the proxy on `init-script-finish`.
7
+ #
8
+ # What the script provides is also a manual mode, where the author of the rex script can take
9
+ # full control of the job lifecycle. This allows keeping the marked as running even when
10
+ # the initial script finishes.
11
+ #
12
+ # The manual mode is turned on by calling `$CONTROL_SCRIPT manual-control`. After calling this,
13
+ # one can call `echo message | $CONTROL_SCRIPT update` to send output to the remote execution jobs
14
+ # and `$CONTROL_SCRIPT finish 0` once finished (with 0 as exit code) to send output to the remote execution jobs
15
+ # and `$CONTROL_SCRIPT finish 0` once finished (with 0 as exit code)
16
+ BASE_DIR="$(dirname "$(readlink -f "$0")")"
17
+
18
+ if ! command -v curl >/dev/null; then
19
+ echo 'curl is required' >&2
20
+ exit 1
21
+ fi
22
+
23
+ # send the callback data to proxy
24
+ update() {
25
+ "$BASE_DIR/retrieve.sh" push_update
26
+ }
27
+
28
+ # wait for named pipe $1 to retrieve data. If $2 is provided, it serves as timeout
29
+ # in seconds on how long to wait when reading.
30
+ wait_for_pipe() {
31
+ pipe_path=$1
32
+ if [ -n "$2" ]; then
33
+ timeout="-t $2"
34
+ fi
35
+ if read $timeout <>"$pipe_path"; then
36
+ rm "$pipe_path"
37
+ return 0
38
+ else
39
+ return 1
40
+ fi
41
+ }
42
+
43
+ # function run in background, when receiving update data via STDIN.
44
+ periodic_update() {
45
+ interval=1
46
+ # reading some data from periodic_update_control signals we're done
47
+ while ! wait_for_pipe "$BASE_DIR/periodic_update_control" "$interval"; do
48
+ update
49
+ done
50
+ # one more update before we finish
51
+ update
52
+ # signal the main process that we are finished
53
+ echo > "$BASE_DIR/periodic_update_finished"
54
+ }
55
+
56
+ # signal the periodic_update process that the main process is finishing
57
+ periodic_update_finish() {
58
+ if [ -e "$BASE_DIR/periodic_update_control" ]; then
59
+ echo > "$BASE_DIR/periodic_update_control"
60
+ fi
61
+ }
62
+
63
+ ACTION=${1:-finish}
64
+
65
+ case "$ACTION" in
66
+ init-script-finish)
67
+ if ! [ -e "$BASE_DIR/manual_mode" ]; then
68
+ # make the exit code of initialization script the exit code of the whole job
69
+ cp init_exit_code exit_code
70
+ update
71
+ fi
72
+ ;;
73
+ finish)
74
+ # take exit code passed via the command line, with fallback
75
+ # to the exit code of the initialization script
76
+ exit_code=${2:-$(cat "$BASE_DIR/init_exit_code")}
77
+ echo $exit_code > "$BASE_DIR/exit_code"
78
+ update
79
+ if [ -e "$BASE_DIR/manual_mode" ]; then
80
+ rm "$BASE_DIR/manual_mode"
81
+ fi
82
+ ;;
83
+ update)
84
+ # read data from input when redirected though a pipe
85
+ if ! [ -t 0 ]; then
86
+ # couple of named pipes to coordinate the main process with the periodic_update
87
+ mkfifo "$BASE_DIR/periodic_update_control"
88
+ mkfifo "$BASE_DIR/periodic_update_finished"
89
+ trap "periodic_update_finish" EXIT
90
+ # run periodic update as separate process to keep sending updates in output to server
91
+ periodic_update &
92
+ # redirect the input into output
93
+ tee -a "$BASE_DIR/output"
94
+ periodic_update_finish
95
+ # ensure the periodic update finished before we return
96
+ wait_for_pipe "$BASE_DIR/periodic_update_finished"
97
+ else
98
+ update
99
+ fi
100
+ ;;
101
+ # mark the script to be in manual mode: this means the script author needs to use `update` and `finish`
102
+ # commands to send output to the remote execution job or mark it as finished.
103
+ manual-mode)
104
+ touch "$BASE_DIR/manual_mode"
105
+ ;;
106
+ *)
107
+ echo "Unknown action $ACTION"
108
+ exit 1
109
+ ;;
110
+ esac
@@ -0,0 +1,151 @@
1
+ #!/bin/sh
2
+
3
+ if ! pgrep --help 2>/dev/null >/dev/null; then
4
+ echo DONE 1
5
+ echo "pgrep is required" >&2
6
+ exit 1
7
+ fi
8
+
9
+ BASE_DIR="$(dirname "$(readlink -f "$0")")"
10
+
11
+ # load the data required for generating the callback
12
+ . "$BASE_DIR/env.sh"
13
+ URL_PREFIX="$CALLBACK_HOST/dynflow/tasks/$TASK_ID"
14
+ AUTH="$TASK_ID:$OTP"
15
+ CURL="curl --silent --show-error --fail --max-time 10"
16
+
17
+ MY_LOCK_FILE="$BASE_DIR/retrieve_lock.$$"
18
+ MY_PID=$$
19
+ echo $MY_PID >"$MY_LOCK_FILE"
20
+ LOCK_FILE="$BASE_DIR/retrieve_lock"
21
+ TMP_OUTPUT_FILE="$BASE_DIR/tmp_output"
22
+
23
+ RUN_TIMEOUT=30 # for how long can the script hold the lock
24
+ WAIT_TIMEOUT=60 # for how long the script is trying to acquire the lock
25
+ START_TIME=$(date +%s)
26
+
27
+ fail() {
28
+ echo RUNNING
29
+ echo "$1"
30
+ exit 1
31
+ }
32
+
33
+ acquire_lock() {
34
+ # try to acquire lock by creating the file (ln should be atomic an fail in case
35
+ # another process succeeded first). We also check the content of the lock file,
36
+ # in case our process won when competing over the lock while invalidating
37
+ # the lock on timeout.
38
+ ln "$MY_LOCK_FILE" "$LOCK_FILE" 2>/dev/null || [ "$(head -n1 "$LOCK_FILE")" = "$MY_PID" ]
39
+ return $?
40
+ }
41
+
42
+ # acquiring the lock before proceeding, to ensure only one instance of the script is running
43
+ while ! acquire_lock; do
44
+ # we failed to create retrieve_lock - assuming there is already another retrieve script running
45
+ current_pid=$(head -n1 "$LOCK_FILE")
46
+ if [ -z "$current_pid" ]; then
47
+ continue
48
+ fi
49
+ # check whether the lock is not too old (compared to $RUN_TIMEOUT) and try to kill
50
+ # if it is, so that we don't have a stalled processes here
51
+ lock_lines_count=$(wc -l < "$LOCK_FILE")
52
+ current_lock_time=$(stat --format "%Y" "$LOCK_FILE")
53
+ current_time=$(date +%s)
54
+
55
+ if [ "$(( current_time - START_TIME ))" -gt "$WAIT_TIMEOUT" ]; then
56
+ # We were waiting for the lock for too long - just give up
57
+ fail "Wait time exceeded $WAIT_TIMEOUT"
58
+ elif [ "$(( current_time - current_lock_time ))" -gt "$RUN_TIMEOUT" ]; then
59
+ # The previous lock it hold for too long - re-acquiring procedure
60
+ if [ "$lock_lines_count" -gt 1 ]; then
61
+ # there were multiple processes waiting for lock without resolution
62
+ # longer than the $RUN_TIMEOUT - we reset the lock file and let processes
63
+ # to compete
64
+ echo "RETRY" > "$LOCK_FILE"
65
+ fi
66
+ if [ "$current_pid" != "RETRY" ]; then
67
+ # try to kill the currently stalled process
68
+ kill -9 "$current_pid" 2>/dev/null
69
+ fi
70
+ # try to add our process as one candidate
71
+ echo $MY_PID >> "$LOCK_FILE"
72
+ if [ "$( head -n2 "$LOCK_FILE" | tail -n1 )" = "$MY_PID" ]; then
73
+ # our process won the competition for the new lock: it is the first pid
74
+ # after the original one in the lock file - take ownership of the lock
75
+ # next iteration only this process will get through
76
+ echo $MY_PID >"$LOCK_FILE"
77
+ fi
78
+ else
79
+ # still waiting for the original owner to finish
80
+ sleep 1
81
+ fi
82
+ done
83
+
84
+ release_lock() {
85
+ rm "$MY_LOCK_FILE"
86
+ rm "$LOCK_FILE"
87
+ }
88
+ # ensure the release the lock at exit
89
+ trap "release_lock" EXIT
90
+
91
+ # make sure we clear previous tmp output file
92
+ if [ -e "$TMP_OUTPUT_FILE" ]; then
93
+ rm "$TMP_OUTPUT_FILE"
94
+ fi
95
+
96
+ pid=$(cat "$BASE_DIR/pid")
97
+ [ -f "$BASE_DIR/position" ] || echo 1 > "$BASE_DIR/position"
98
+ position=$(cat "$BASE_DIR/position")
99
+
100
+ prepare_output() {
101
+ if [ -e "$BASE_DIR/manual_mode" ] || ([ -n "$pid" ] && pgrep -P "$pid" >/dev/null 2>&1); then
102
+ echo RUNNING
103
+ else
104
+ echo "DONE $(cat "$BASE_DIR/exit_code" 2>/dev/null)"
105
+ fi
106
+ [ -f "$BASE_DIR/output" ] || exit 0
107
+ tail --bytes "+${position}" "$BASE_DIR/output" > "$TMP_OUTPUT_FILE"
108
+ cat "$TMP_OUTPUT_FILE"
109
+ }
110
+
111
+ # prepare the callback payload
112
+ payload() {
113
+ if [ -n "$1" ]; then
114
+ exit_code="$1"
115
+ else
116
+ exit_code=null
117
+ fi
118
+
119
+ if [ -e "$BASE_DIR/manual_mode" ]; then
120
+ manual_mode=true
121
+ output=$(prepare_output | base64 -w0)
122
+ else
123
+ manual_mode=false
124
+ fi
125
+
126
+ echo "{ \"exit_code\": $exit_code,"\
127
+ " \"step_id\": \"$STEP_ID\","\
128
+ " \"manual_mode\": $manual_mode,"\
129
+ " \"output\": \"$output\" }"
130
+ }
131
+
132
+ if [ "$1" = "push_update" ]; then
133
+ if [ -e "$BASE_DIR/exit_code" ]; then
134
+ exit_code="$(cat "$BASE_DIR/exit_code")"
135
+ action="done"
136
+ else
137
+ exit_code=""
138
+ action="update"
139
+ fi
140
+ $CURL -X POST -d "$(payload $exit_code)" -u "$AUTH" "$URL_PREFIX"/$action 2>>"$BASE_DIR/curl_stderr"
141
+ success=$?
142
+ else
143
+ prepare_output
144
+ success=$?
145
+ fi
146
+
147
+ if [ "$success" = 0 ] && [ -e "$TMP_OUTPUT_FILE" ]; then
148
+ # in case the retrieval was successful, move the position of the cursor to be read next time
149
+ bytes=$(wc --bytes < "$TMP_OUTPUT_FILE")
150
+ expr "${position}" + "${bytes}" > "$BASE_DIR/position"
151
+ fi
@@ -1,8 +1,25 @@
1
+ require 'base64'
2
+
1
3
  module ForemanRemoteExecutionCore
2
4
  class PollingScriptRunner < ScriptRunner
3
5
 
4
6
  DEFAULT_REFRESH_INTERVAL = 60
5
7
 
8
+ def self.load_script(name)
9
+ script_dir = File.expand_path('../async_scripts', __FILE__)
10
+ File.read(File.join(script_dir, name))
11
+ end
12
+
13
+ # The script that controls the flow of the job, able to initiate update or
14
+ # finish on the task, or take over the control over script lifecycle
15
+ CONTROL_SCRIPT = load_script('control.sh')
16
+
17
+ # The script always outputs at least one line
18
+ # First line of the output either has to begin with
19
+ # "RUNNING" or "DONE $EXITCODE"
20
+ # The following lines are treated as regular output
21
+ RETRIEVE_SCRIPT = load_script('retrieve.sh')
22
+
6
23
  def initialize(options, user_method, suspended_action: nil)
7
24
  super(options, user_method, suspended_action: suspended_action)
8
25
  @callback_host = options[:callback_host]
@@ -13,19 +30,19 @@ module ForemanRemoteExecutionCore
13
30
 
14
31
  def prepare_start
15
32
  super
16
- basedir = File.dirname @remote_script
17
- @pid_path = File.join(basedir, 'pid')
18
- @retrieval_script ||= File.join(basedir, 'retrieve')
19
- prepare_retrieval unless @prepared
33
+ @base_dir = File.dirname @remote_script
34
+ upload_control_scripts
20
35
  end
21
36
 
22
- def control_script
37
+ def initialization_script
23
38
  close_stdin = '</dev/null'
24
39
  close_fds = close_stdin + ' >/dev/null 2>/dev/null'
25
- # pipe the output to tee while capturing the exit code in a file, don't wait for it to finish, output PID of the main command
26
- <<-SCRIPT.gsub(/^\s+\| /, '')
27
- | sh -c '(#{@user_method.cli_command_prefix}#{@remote_script} #{close_stdin}; echo $?>#{@exit_code_path}) | /usr/bin/tee #{@output_path} >/dev/null; #{callback_scriptlet}' #{close_fds} &
28
- | echo $! > '#{@pid_path}'
40
+ main_script = "(#{@remote_script} #{close_stdin} 2>&1; echo $?>#{@base_dir}/init_exit_code) >#{@base_dir}/output"
41
+ control_script_finish = "#{@control_script_path} init-script-finish"
42
+ <<-SCRIPT.gsub(/^ +\| /, '')
43
+ | export CONTROL_SCRIPT="#{@control_script_path}"
44
+ | sh -c '#{main_script}; #{control_script_finish}' #{close_fds} &
45
+ | echo $! > '#{@base_dir}/pid'
29
46
  SCRIPT
30
47
  end
31
48
 
@@ -35,9 +52,12 @@ module ForemanRemoteExecutionCore
35
52
 
36
53
  def refresh
37
54
  err = output = nil
38
- with_retries do
55
+ begin
39
56
  _, output, err = run_sync("#{@user_method.cli_command_prefix} #{@retrieval_script}")
57
+ rescue => e
58
+ @logger.info("Error while connecting to the remote host on refresh: #{e.message}")
40
59
  end
60
+ return if output.nil? || output.empty?
41
61
  lines = output.lines
42
62
  result = lines.shift.match(/^DONE (\d+)?/)
43
63
  publish_data(lines.join, 'stdout') unless lines.empty?
@@ -45,8 +65,21 @@ module ForemanRemoteExecutionCore
45
65
  if result
46
66
  exitcode = result[1] || 0
47
67
  publish_exit_status(exitcode.to_i)
48
- run_sync("rm -rf \"#{remote_command_dir}\"") if @cleanup_working_dirs
68
+ cleanup
69
+ end
70
+ ensure
71
+ destroy_session
72
+ end
73
+
74
+ def external_event(event)
75
+ data = event.data
76
+ if data['manual_mode']
77
+ load_event_updates(data)
78
+ else
79
+ # getting the update from automatic mode - reaching to the host to get the latest update
80
+ return run_refresh
49
81
  end
82
+ ensure
50
83
  destroy_session
51
84
  end
52
85
 
@@ -55,66 +88,41 @@ module ForemanRemoteExecutionCore
55
88
  ForemanTasksCore::OtpManager.drop_otp(@task_id, @otp) if @otp
56
89
  end
57
90
 
58
- def prepare_retrieval
59
- # The script always outputs at least one line
60
- # First line of the output either has to begin with
61
- # "RUNNING" or "DONE $EXITCODE"
62
- # The following lines are treated as regular output
63
- base = File.dirname(@output_path)
64
- posfile = File.join(base, 'position')
65
- tmpfile = File.join(base, 'tmp')
66
- script = <<-SCRIPT.gsub(/^ +\| /, '')
67
- | #!/bin/sh
68
- | pid=$(cat "#{@pid_path}")
69
- | if ! pgrep --help 2>/dev/null >/dev/null; then
70
- | echo DONE 1
71
- | echo "pgrep is required" >&2
72
- | exit 1
73
- | fi
74
- | if pgrep -P "$pid" >/dev/null 2>/dev/null; then
75
- | echo RUNNING
76
- | else
77
- | echo "DONE $(cat "#{@exit_code_path}" 2>/dev/null)"
78
- | fi
79
- | [ -f "#{@output_path}" ] || exit 0
80
- | [ -f "#{posfile}" ] || echo 1 > "#{posfile}"
81
- | position=$(cat "#{posfile}")
82
- | tail --bytes "+${position}" "#{@output_path}" > "#{tmpfile}"
83
- | bytes=$(cat "#{tmpfile}" | wc --bytes)
84
- | expr "${position}" + "${bytes}" > "#{posfile}"
85
- | cat "#{tmpfile}"
86
- SCRIPT
87
- @logger.debug("copying script:\n#{script.lines.map { |line| " | #{line}" }.join}")
88
- cp_script_to_remote(script, 'retrieve')
89
- @prepared = true
90
- end
91
-
92
- def callback_scriptlet(callback_script_path = nil)
93
- if @otp
94
- callback_script_path = cp_script_to_remote(callback_script, 'callback') if callback_script_path.nil?
95
- "#{@user_method.cli_command_prefix}#{callback_script_path}"
96
- else
97
- ':' # Shell synonym for "do nothing"
98
- end
91
+ def upload_control_scripts
92
+ return if @control_scripts_uploaded
93
+ cp_script_to_remote(env_script, 'env.sh')
94
+ @control_script_path = cp_script_to_remote(CONTROL_SCRIPT, 'control.sh')
95
+ @retrieval_script = cp_script_to_remote(RETRIEVE_SCRIPT, 'retrieve.sh')
96
+ @control_scripts_uploaded = true
99
97
  end
100
98
 
101
- def callback_script
99
+ # Script setting the dynamic values to env variables: it's sourced from other control scripts
100
+ def env_script
102
101
  <<-SCRIPT.gsub(/^ +\| /, '')
103
- | #!/bin/sh
104
- | exit_code=$(cat "#{@exit_code_path}")
105
- | url="#{@callback_host}/dynflow/tasks/#{@task_id}/done"
106
- | json="{ \\\"step_id\\\": #{@step_id} }"
107
- | if which curl >/dev/null; then
108
- | curl -X POST -d "$json" -u "#{@task_id}:#{@otp}" "$url"
109
- | else
110
- | echo 'curl is required' >&2
111
- | exit 1
112
- | fi
102
+ | CALLBACK_HOST="#{@callback_host}"
103
+ | TASK_ID="#{@task_id}"
104
+ | STEP_ID="#{@step_id}"
105
+ | OTP="#{@otp}"
113
106
  SCRIPT
114
107
  end
115
108
 
116
109
  private
117
110
 
111
+ # Generates updates based on the callback data from the manual mode
112
+ def load_event_updates(event_data)
113
+ continuous_output = ForemanTasksCore::ContinuousOutput.new
114
+ if event_data.key?('output')
115
+ lines = Base64.decode64(event_data['output']).sub(/\A(RUNNING|DONE).*\n/, '')
116
+ continuous_output.add_output(lines, 'stdout')
117
+ end
118
+ cleanup if event_data['exit_code']
119
+ new_update(continuous_output, event_data['exit_code'])
120
+ end
121
+
122
+ def cleanup
123
+ run_sync("rm -rf \"#{remote_command_dir}\"") if @cleanup_working_dirs
124
+ end
125
+
118
126
  def destroy_session
119
127
  if @session
120
128
  @logger.debug("Closing session with #{@ssh_user}@#{@host}")
@@ -151,8 +151,8 @@ module ForemanRemoteExecutionCore
151
151
 
152
152
  def start
153
153
  prepare_start
154
- script = control_script
155
- logger.debug("executing script:\n#{script.lines.map { |line| " | #{line}" }.join}")
154
+ script = initialization_script
155
+ logger.debug("executing script:\n#{indent_multiline(script)}")
156
156
  trigger(script)
157
157
  rescue => e
158
158
  logger.error("error while initalizing command #{e.class} #{e.message}:\n #{e.backtrace.join("\n")}")
@@ -169,7 +169,8 @@ module ForemanRemoteExecutionCore
169
169
  @exit_code_path = File.join(File.dirname(@remote_script), 'exit_code')
170
170
  end
171
171
 
172
- def control_script
172
+ # the script that initiates the execution
173
+ def initialization_script
173
174
  # pipe the output to tee while capturing the exit code in a file
174
175
  <<-SCRIPT.gsub(/^\s+\| /, '')
175
176
  | sh <<WRAPPER
@@ -252,6 +253,10 @@ module ForemanRemoteExecutionCore
252
253
 
253
254
  private
254
255
 
256
+ def indent_multiline(string)
257
+ string.lines.map { |line| " | #{line}" }.join
258
+ end
259
+
255
260
  def should_cleanup?
256
261
  @session && !@session.closed? && @cleanup_working_dirs
257
262
  end
@@ -389,7 +394,9 @@ module ForemanRemoteExecutionCore
389
394
  end
390
395
 
391
396
  def cp_script_to_remote(script = @script, name = 'script')
392
- upload_data(sanitize_script(script), remote_command_file(name), 555)
397
+ path = remote_command_file(name)
398
+ @logger.debug("copying script to #{path}:\n#{indent_multiline(script)}")
399
+ upload_data(sanitize_script(script), path, 555)
393
400
  end
394
401
 
395
402
  def upload_data(data, path, permissions = 555)
@@ -1,3 +1,3 @@
1
1
  module ForemanRemoteExecutionCore
2
- VERSION = '1.2.0'.freeze
2
+ VERSION = '1.3.0'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foreman_remote_execution_core
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ivan Nečas
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-05-21 00:00:00.000000000 Z
11
+ date: 2019-10-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: foreman-tasks-core
@@ -48,6 +48,8 @@ files:
48
48
  - LICENSE
49
49
  - lib/foreman_remote_execution_core.rb
50
50
  - lib/foreman_remote_execution_core/actions.rb
51
+ - lib/foreman_remote_execution_core/async_scripts/control.sh
52
+ - lib/foreman_remote_execution_core/async_scripts/retrieve.sh
51
53
  - lib/foreman_remote_execution_core/dispatcher.rb
52
54
  - lib/foreman_remote_execution_core/fake_script_runner.rb
53
55
  - lib/foreman_remote_execution_core/log_filter.rb
@@ -73,8 +75,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
73
75
  - !ruby/object:Gem::Version
74
76
  version: '0'
75
77
  requirements: []
76
- rubyforge_project:
77
- rubygems_version: 2.7.6
78
+ rubygems_version: 3.0.3
78
79
  signing_key:
79
80
  specification_version: 4
80
81
  summary: Foreman remote execution - core bits