| 
									
										
										
										
											2024-07-05 20:01:13 +08:00
										 |  |  | # | 
					
						
							|  |  |  | # Licensed to the Apache Software Foundation (ASF) under one or more | 
					
						
							|  |  |  | # contributor license agreements.  See the NOTICE file distributed with | 
					
						
							|  |  |  | # this work for additional information regarding copyright ownership. | 
					
						
							|  |  |  | # The ASF licenses this file to You under the Apache License, Version 2.0 | 
					
						
							|  |  |  | # (the "License"); you may not use this file except in compliance with | 
					
						
							|  |  |  | # the License.  You may obtain a copy of the License at | 
					
						
							|  |  |  | # | 
					
						
							|  |  |  | #    http://www.apache.org/licenses/LICENSE-2.0 | 
					
						
							|  |  |  | # | 
					
						
							|  |  |  | # Unless required by applicable law or agreed to in writing, software | 
					
						
							|  |  |  | # distributed under the License is distributed on an "AS IS" BASIS, | 
					
						
							|  |  |  | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
					
						
							|  |  |  | # See the License for the specific language governing permissions and | 
					
						
							|  |  |  | # limitations under the License. | 
					
						
							|  |  |  | # | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | """
 | 
					
						
							|  |  |  | Auxiliary functions to manage the release script runtime | 
					
						
							|  |  |  | and launch external utilities. | 
					
						
							|  |  |  | """
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import os | 
					
						
							|  |  |  | import subprocess | 
					
						
							|  |  |  | import sys | 
					
						
							|  |  |  | import tempfile | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import templates | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | this_script_dir = os.path.abspath(os.path.dirname(__file__)) | 
					
						
							|  |  |  | repo_dir = os.environ.get("KAFKA_HOME", os.path.abspath(this_script_dir + "/..")) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | fail_hooks = [] | 
					
						
							|  |  |  | failing = False | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def append_fail_hook(name, hook_fn): | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     Register a fail hook function, to run in case fail() is called. | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     fail_hooks.append((name, hook_fn)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def fail(msg = ""): | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     Terminate execution with the given message, | 
					
						
							|  |  |  |     after running any registered hooks. | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     global failing | 
					
						
							|  |  |  |     if failing: | 
					
						
							|  |  |  |         raise Exception(f"Recursive fail invocation") | 
					
						
							|  |  |  |     failing = True | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for name, func in fail_hooks: | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             func() | 
					
						
							|  |  |  |         except Exception as e: | 
					
						
							|  |  |  |             print(f"Exception caught in fail hook {name}: {e}") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     print(f"FAILURE: {msg}") | 
					
						
							|  |  |  |     sys.exit(1) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def prompt(msg): | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     Prompt user for input with the given message. | 
					
						
							|  |  |  |     This removes leading and trailing spaces. | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     text = input(msg) | 
					
						
							|  |  |  |     return text.strip() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def confirm(msg): | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     Prompt the user to confirm | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     while True: | 
					
						
							|  |  |  |         text = prompt(msg + " (y/n): ").lower() | 
					
						
							|  |  |  |         if text in ['y', 'n']: | 
					
						
							|  |  |  |             return text == 'y' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def confirm_or_fail(msg): | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     Prompt the user to confirm and fail on negative input. | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     if not confirm(msg): | 
					
						
							|  |  |  |         fail("Ok, giving up") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def execute(cmd, *args, **kwargs): | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     Execute an external command and return its output. | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     if "shell" not in kwargs and isinstance(cmd, str): | 
					
						
							|  |  |  |         cmd = cmd.split() | 
					
						
							|  |  |  |     if "input" in kwargs and isinstance(kwargs["input"], str): | 
					
						
							|  |  |  |         kwargs["input"] = kwargs["input"].encode() | 
					
						
							|  |  |  |     kwargs["stderr"] = stderr=subprocess.STDOUT | 
					
						
							|  |  |  |     output = subprocess.check_output(cmd, *args, **kwargs) | 
					
						
							|  |  |  |     return output.decode("utf-8") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def _prefix(prefix_str, value_str): | 
					
						
							|  |  |  |     return prefix_str + value_str.replace("\n", "\n" + prefix_str) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def cmd(action, cmd_arg, *args, **kwargs): | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     Execute an external command. This should be preferered over execute() | 
					
						
							|  |  |  |     when returning the output is not necessary, as the user will be given | 
					
						
							|  |  |  |     the option of retrying in case of a failure. | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     stdin_log = "" | 
					
						
							|  |  |  |     if "stdin" in kwargs and isinstance(kwargs["stdin"], str): | 
					
						
							|  |  |  |         stdin_str = kwargs["stdin"] | 
					
						
							|  |  |  |         stdin_log = "\n" + _prefix("< ", stdin_str) | 
					
						
							|  |  |  |         stdin = tempfile.TemporaryFile() | 
					
						
							|  |  |  |         stdin.write(stdin_str.encode("utf-8")) | 
					
						
							|  |  |  |         stdin.seek(0) | 
					
						
							|  |  |  |         kwargs["stdin"] = stdin | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     print(f"{action}\n$ {cmd_arg}{stdin_log}") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if isinstance(cmd_arg, str) and not kwargs.get("shell", False): | 
					
						
							|  |  |  |         cmd_arg = cmd_arg.split() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     allow_failure = kwargs.pop("allow_failure", False) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     retry = True | 
					
						
							|  |  |  |     while retry: | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             output = execute(cmd_arg, *args, stderr=subprocess.STDOUT, **kwargs) | 
					
						
							|  |  |  |             print(_prefix("> ", output.strip())) | 
					
						
							| 
									
										
										
										
											2024-10-09 06:40:27 +08:00
										 |  |  |             return True | 
					
						
							| 
									
										
										
										
											2024-07-05 20:01:13 +08:00
										 |  |  |         except subprocess.CalledProcessError as e: | 
					
						
							|  |  |  |             print(e.output.decode("utf-8")) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if allow_failure: | 
					
						
							| 
									
										
										
										
											2024-10-09 06:40:27 +08:00
										 |  |  |                 return False | 
					
						
							| 
									
										
										
										
											2024-07-05 20:01:13 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |             retry = confirm("Retry?") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     print(templates.cmd_failed()) | 
					
						
							|  |  |  |     fail("") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 |