mirror of
				https://github.com/zulip/zulip.git
				synced 2025-10-30 03:23:50 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			114 lines
		
	
	
		
			3.5 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			114 lines
		
	
	
		
			3.5 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
| #!/usr/bin/env python3
 | |
| import argparse
 | |
| import configparser
 | |
| import os
 | |
| import re
 | |
| import subprocess
 | |
| import sys
 | |
| import tempfile
 | |
| 
 | |
| import yaml
 | |
| 
 | |
| BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 | |
| sys.path.insert(0, BASE_DIR)
 | |
| 
 | |
| from scripts.lib.puppet_cache import setup_puppet_modules
 | |
| from scripts.lib.zulip_tools import assert_running_as_root, parse_os_release
 | |
| 
 | |
| assert_running_as_root()
 | |
| 
 | |
| parser = argparse.ArgumentParser(description="Run Puppet")
 | |
| parser.add_argument(
 | |
|     "--force", "-f", action="store_true", help="Do not prompt with proposed changes"
 | |
| )
 | |
| parser.add_argument("--noop", action="store_true", help="Do not apply the changes")
 | |
| parser.add_argument("--config", default="/etc/zulip/zulip.conf", help="Alternate zulip.conf path")
 | |
| args, extra_args = parser.parse_known_args()
 | |
| 
 | |
| config = configparser.RawConfigParser()
 | |
| config.read(args.config)
 | |
| 
 | |
| setup_puppet_modules()
 | |
| 
 | |
| distro_info = parse_os_release()
 | |
| puppet_config = """
 | |
| Exec { path => "/usr/sbin:/usr/bin:/sbin:/bin" }
 | |
| """
 | |
| 
 | |
| for pclass in re.split(r"\s*,\s*", config.get("machine", "puppet_classes")):
 | |
|     if " " in pclass:
 | |
|         print(
 | |
|             f"The `machine.puppet_classes` setting in {args.config} must be comma-separated, not space-separated!"
 | |
|         )
 | |
|         sys.exit(1)
 | |
|     puppet_config += f"include {pclass}\n"
 | |
| 
 | |
| # We use the Puppet configuration from the same Zulip checkout as this script
 | |
| scripts_path = os.path.join(BASE_DIR, "scripts")
 | |
| puppet_module_path = os.path.join(BASE_DIR, "puppet")
 | |
| puppet_cmd = [
 | |
|     "puppet",
 | |
|     "apply",
 | |
|     f"--modulepath={puppet_module_path}:/srv/zulip-puppet-cache/current",
 | |
|     "-e",
 | |
|     puppet_config,
 | |
| ]
 | |
| if args.noop:
 | |
|     puppet_cmd += ["--noop"]
 | |
| puppet_cmd += extra_args
 | |
| 
 | |
| # Set the scripts path to be a factor so it can be used by Puppet code
 | |
| puppet_env = os.environ.copy()
 | |
| puppet_env["FACTER_zulip_conf_path"] = args.config
 | |
| puppet_env["FACTER_zulip_scripts_path"] = scripts_path
 | |
| 
 | |
| 
 | |
| def noop_would_change(puppet_cmd: list[str]) -> bool:
 | |
|     # --noop does not work with --detailed-exitcodes; see
 | |
|     # https://tickets.puppetlabs.com/browse/PUP-686
 | |
|     with tempfile.NamedTemporaryFile() as lastrun_file:
 | |
|         try:
 | |
|             subprocess.check_call(
 | |
|                 # puppet_cmd may already contain --noop, but it is safe to
 | |
|                 # supply twice
 | |
|                 [*puppet_cmd, "--noop", "--lastrunfile", lastrun_file.name],
 | |
|                 env=puppet_env,
 | |
|             )
 | |
|         except subprocess.CalledProcessError:
 | |
|             sys.exit(2)
 | |
| 
 | |
|         # Reopen the file since Puppet rewrote it with a new inode
 | |
|         with open(lastrun_file.name) as lastrun:
 | |
|             lastrun_data = yaml.safe_load(lastrun)
 | |
|             resources = lastrun_data.get("resources", {})
 | |
|             if resources.get("failed", 0) != 0:
 | |
|                 sys.exit(2)
 | |
|             return resources.get("out_of_sync", 0) != 0
 | |
| 
 | |
| 
 | |
| if not args.noop and not args.force:
 | |
|     if not noop_would_change([*puppet_cmd, "--show_diff"]):
 | |
|         sys.exit(0)
 | |
| 
 | |
|     do_apply = None
 | |
|     while do_apply != "y":
 | |
|         sys.stdout.write("Apply changes? [y/N] ")
 | |
|         sys.stdout.flush()
 | |
|         do_apply = sys.stdin.readline().strip().lower()
 | |
|         if do_apply in ("", "n"):
 | |
|             sys.exit(0)
 | |
| 
 | |
| if args.noop and args.force:
 | |
|     if noop_would_change(puppet_cmd):
 | |
|         sys.exit(1)
 | |
|     else:
 | |
|         sys.exit(0)
 | |
| 
 | |
| ret = subprocess.call([*puppet_cmd, "--detailed-exitcodes"], env=puppet_env)
 | |
| # ret = 0 => no changes, no errors
 | |
| # ret = 2 => changes, no errors
 | |
| # ret = 4 => no changes, yes errors
 | |
| # ret = 6 => changes, yes errors
 | |
| if ret not in (0, 2):
 | |
|     sys.exit(2)
 |