14
14
# limitations under the License.
15
15
16
16
import os
17
+ import re
17
18
import pwd
18
19
import uuid
19
20
import functools
52
53
53
54
# constants to lookup in runner_parameters.
54
55
RUNNER_SUDO = 'sudo'
56
+ RUNNER_SUDO_PASSWORD = 'sudo_password'
55
57
RUNNER_ON_BEHALF_USER = 'user'
56
58
RUNNER_COMMAND = 'cmd'
57
59
RUNNER_CWD = 'cwd'
@@ -84,6 +86,7 @@ def pre_run(self):
84
86
super (LocalShellRunner , self ).pre_run ()
85
87
86
88
self ._sudo = self .runner_parameters .get (RUNNER_SUDO , False )
89
+ self ._sudo_password = self .runner_parameters .get (RUNNER_SUDO_PASSWORD , None )
87
90
self ._on_behalf_user = self .context .get (RUNNER_ON_BEHALF_USER , LOGGED_USER_USERNAME )
88
91
self ._user = cfg .CONF .system_user .user
89
92
self ._cwd = self .runner_parameters .get (RUNNER_CWD , None )
@@ -105,7 +108,8 @@ def run(self, action_parameters):
105
108
user = self ._user ,
106
109
env_vars = env_vars ,
107
110
sudo = self ._sudo ,
108
- timeout = self ._timeout )
111
+ timeout = self ._timeout ,
112
+ sudo_password = self ._sudo_password )
109
113
else :
110
114
script_action = True
111
115
script_local_path_abs = self .entry_point
@@ -121,13 +125,16 @@ def run(self, action_parameters):
121
125
env_vars = env_vars ,
122
126
sudo = self ._sudo ,
123
127
timeout = self ._timeout ,
124
- cwd = self ._cwd )
128
+ cwd = self ._cwd ,
129
+ sudo_password = self ._sudo_password )
125
130
126
131
args = action .get_full_command_string ()
132
+ sanitized_args = action .get_sanitized_full_command_string ()
127
133
128
134
# For consistency with the old Fabric based runner, make sure the file is executable
129
135
if script_action :
130
136
args = 'chmod +x %s ; %s' % (script_local_path_abs , args )
137
+ sanitized_args = 'chmod +x %s ; %s' % (script_local_path_abs , sanitized_args )
131
138
132
139
env = os .environ .copy ()
133
140
@@ -140,7 +147,7 @@ def run(self, action_parameters):
140
147
141
148
LOG .info ('Executing action via LocalRunner: %s' , self .runner_id )
142
149
LOG .info ('[Action info] name: %s, Id: %s, command: %s, user: %s, sudo: %s' %
143
- (action .name , action .action_exec_id , args , action .user , action .sudo ))
150
+ (action .name , action .action_exec_id , sanitized_args , action .user , action .sudo ))
144
151
145
152
stdout = StringIO ()
146
153
stderr = StringIO ()
@@ -155,6 +162,17 @@ def run(self, action_parameters):
155
162
read_and_store_stderr = make_read_and_store_stream_func (execution_db = self .execution ,
156
163
action_db = self .action , store_data_func = store_execution_stderr_line )
157
164
165
+ # If sudo password is provided, pass it to the subprocess via stdin>
166
+ # Note: We don't need to explicitly escape the argument because we pass command as a list
167
+ # to subprocess.Popen and all the arguments are escaped by the function.
168
+ if self ._sudo_password :
169
+ LOG .debug ('Supplying sudo password via stdin' )
170
+ echo_process = subprocess .Popen (['echo' , self ._sudo_password + '\n ' ],
171
+ stdout = subprocess .PIPE )
172
+ stdin = echo_process .stdout
173
+ else :
174
+ stdin = None
175
+
158
176
# Make sure os.setsid is called on each spawned process so that all processes
159
177
# are in the same group.
160
178
@@ -164,7 +182,8 @@ def run(self, action_parameters):
164
182
# Ideally os.killpg should have done the trick but for some reason that failed.
165
183
# Note: pkill will set the returncode to 143 so we don't need to explicitly set
166
184
# it to some non-zero value.
167
- exit_code , stdout , stderr , timed_out = shell .run_command (cmd = args , stdin = None ,
185
+ exit_code , stdout , stderr , timed_out = shell .run_command (cmd = args ,
186
+ stdin = stdin ,
168
187
stdout = subprocess .PIPE ,
169
188
stderr = subprocess .PIPE ,
170
189
shell = True ,
@@ -184,6 +203,20 @@ def run(self, action_parameters):
184
203
error = 'Action failed to complete in %s seconds' % (self ._timeout )
185
204
exit_code = - 1 * exit_code_constants .SIGKILL_EXIT_CODE
186
205
206
+ # Detect if user provided an invalid sudo password or sudo is not configured for that user
207
+ if self ._sudo_password :
208
+ if re .search ('sudo: \d+ incorrect password attempts' , stderr ):
209
+ match = re .search ('\[sudo\] password for (.+?)\:' , stderr )
210
+
211
+ if match :
212
+ username = match .groups ()[0 ]
213
+ else :
214
+ username = 'unknown'
215
+
216
+ error = ('Invalid sudo password provided or sudo is not configured for this user '
217
+ '(%s)' % (username ))
218
+ exit_code = - 1
219
+
187
220
succeeded = (exit_code == exit_code_constants .SUCCESS_EXIT_CODE )
188
221
189
222
result = {
0 commit comments