1"""
2All salt configuration loading and defaults should be in this module
3"""
4import codecs
5import glob
6import logging
7import os
8import re
9import sys
10import time
11import types
12import urllib.parse
13from copy import deepcopy
14
15import salt.defaults.exitcodes
16import salt.exceptions
17import salt.syspaths
18import salt.utils.data
19import salt.utils.dictupdate
20import salt.utils.files
21import salt.utils.immutabletypes as immutabletypes
22import salt.utils.network
23import salt.utils.path
24import salt.utils.platform
25import salt.utils.stringutils
26import salt.utils.user
27import salt.utils.validate.path
28import salt.utils.xdg
29import salt.utils.yaml
30import salt.utils.zeromq
31
32try:
33    import psutil
34
35    if not hasattr(psutil, "virtual_memory"):
36        raise ImportError("Version of psutil too old.")
37    HAS_PSUTIL = True
38except ImportError:
39    HAS_PSUTIL = False
40
41log = logging.getLogger(__name__)
42
43_DFLT_LOG_DATEFMT = "%H:%M:%S"
44_DFLT_LOG_DATEFMT_LOGFILE = "%Y-%m-%d %H:%M:%S"
45_DFLT_LOG_FMT_CONSOLE = "[%(levelname)-8s] %(message)s"
46_DFLT_LOG_FMT_LOGFILE = (
47    "%(asctime)s,%(msecs)03d [%(name)-17s:%(lineno)-4d][%(levelname)-8s][%(process)d]"
48    " %(message)s"
49)
50_DFLT_LOG_FMT_JID = "[JID: %(jid)s]"
51_DFLT_REFSPECS = ["+refs/heads/*:refs/remotes/origin/*", "+refs/tags/*:refs/tags/*"]
52DEFAULT_INTERVAL = 60
53
54if salt.utils.platform.is_windows():
55    # Since an 'ipc_mode' of 'ipc' will never work on Windows due to lack of
56    # support in ZeroMQ, we want the default to be something that has a
57    # chance of working.
58    _DFLT_IPC_MODE = "tcp"
59    _DFLT_FQDNS_GRAINS = False
60    _MASTER_TRIES = -1
61    # This needs to be SYSTEM in order for salt-master to run as a Service
62    # Otherwise, it will not respond to CLI calls
63    _MASTER_USER = "SYSTEM"
64elif salt.utils.platform.is_proxy():
65    _DFLT_IPC_MODE = "ipc"
66    _DFLT_FQDNS_GRAINS = False
67    _MASTER_TRIES = 1
68    _MASTER_USER = salt.utils.user.get_user()
69else:
70    _DFLT_IPC_MODE = "ipc"
71    _DFLT_FQDNS_GRAINS = True
72    _MASTER_TRIES = 1
73    _MASTER_USER = salt.utils.user.get_user()
74
75
76def _gather_buffer_space():
77    """
78    Gather some system data and then calculate
79    buffer space.
80
81    Result is in bytes.
82    """
83    if HAS_PSUTIL and psutil.version_info >= (0, 6, 0):
84        # Oh good, we have psutil. This will be quick.
85        total_mem = psutil.virtual_memory().total
86    else:
87        # Avoid loading core grains unless absolutely required
88        import platform
89        import salt.grains.core
90
91        # We need to load up ``mem_total`` grain. Let's mimic required OS data.
92        os_data = {"kernel": platform.system()}
93        grains = salt.grains.core._memdata(os_data)
94        total_mem = grains["mem_total"]
95    # Return the higher number between 5% of the system memory and 10MiB
96    return max([total_mem * 0.05, 10 << 20])
97
98
99# For the time being this will be a fixed calculation
100# TODO: Allow user configuration
101_DFLT_IPC_WBUFFER = _gather_buffer_space() * 0.5
102# TODO: Reserved for future use
103_DFLT_IPC_RBUFFER = _gather_buffer_space() * 0.5
104
105VALID_OPTS = immutabletypes.freeze(
106    {
107        # The address of the salt master. May be specified as IP address or hostname
108        "master": (str, list),
109        # The TCP/UDP port of the master to connect to in order to listen to publications
110        "master_port": (str, int),
111        # The behaviour of the minion when connecting to a master. Can specify 'failover',
112        # 'disable', 'distributed', or 'func'. If 'func' is specified, the 'master' option should be
113        # set to an exec module function to run to determine the master hostname. If 'disable' is
114        # specified the minion will run, but will not try to connect to a master. If 'distributed'
115        # is specified the minion will try to deterministically pick a master based on its' id.
116        "master_type": str,
117        # Specify the format in which the master address will be specified. Can
118        # specify 'default' or 'ip_only'. If 'ip_only' is specified, then the
119        # master address will not be split into IP and PORT.
120        "master_uri_format": str,
121        # The following optiosn refer to the Minion only, and they specify
122        # the details of the source address / port to be used when connecting to
123        # the Master. This is useful when dealing withmachines where due to firewall
124        # rules you are restricted to use a certain IP/port combination only.
125        "source_interface_name": str,
126        "source_address": str,
127        "source_ret_port": (str, int),
128        "source_publish_port": (str, int),
129        # The fingerprint of the master key may be specified to increase security. Generate
130        # a master fingerprint with `salt-key -F master`
131        "master_finger": str,
132        # Deprecated in 2019.2.0. Use 'random_master' instead.
133        # Do not remove! Keep as an alias for usability.
134        "master_shuffle": bool,
135        # When in multi-master mode, temporarily remove a master from the list if a conenction
136        # is interrupted and try another master in the list.
137        "master_alive_interval": int,
138        # When in multi-master failover mode, fail back to the first master in the list if it's back
139        # online.
140        "master_failback": bool,
141        # When in multi-master mode, and master_failback is enabled ping the top master with this
142        # interval.
143        "master_failback_interval": int,
144        # The name of the signing key-pair
145        "master_sign_key_name": str,
146        # Sign the master auth-replies with a cryptographic signature of the masters public key.
147        "master_sign_pubkey": bool,
148        # Enables verification of the master-public-signature returned by the master in auth-replies.
149        # Must also set master_sign_pubkey for this to work
150        "verify_master_pubkey_sign": bool,
151        # If verify_master_pubkey_sign is enabled, the signature is only verified, if the public-key of
152        # the master changes. If the signature should always be verified, this can be set to True.
153        "always_verify_signature": bool,
154        # The name of the file in the masters pki-directory that holds the pre-calculated signature of
155        # the masters public-key
156        "master_pubkey_signature": str,
157        # Instead of computing the signature for each auth-reply, use a pre-calculated signature.
158        # The master_pubkey_signature must also be set for this.
159        "master_use_pubkey_signature": bool,
160        # Enable master stats eveents to be fired, these events will contain information about
161        # what commands the master is processing and what the rates are of the executions
162        "master_stats": bool,
163        "master_stats_event_iter": int,
164        # The key fingerprint of the higher-level master for the syndic to verify it is talking to the
165        # intended master
166        "syndic_finger": str,
167        # The caching mechanism to use for the PKI key store. Can substantially decrease master publish
168        # times. Available types:
169        # 'maint': Runs on a schedule as a part of the maintanence process.
170        # '': Disable the key cache [default]
171        "key_cache": str,
172        # The user under which the daemon should run
173        "user": str,
174        # The root directory prepended to these options: pki_dir, cachedir,
175        # sock_dir, log_file, autosign_file, autoreject_file, extension_modules,
176        # key_logfile, pidfile:
177        "root_dir": str,
178        # The directory used to store public key data
179        "pki_dir": str,
180        # A unique identifier for this daemon
181        "id": str,
182        # Use a module function to determine the unique identifier. If this is
183        # set and 'id' is not set, it will allow invocation of a module function
184        # to determine the value of 'id'. For simple invocations without function
185        # arguments, this may be a string that is the function name. For
186        # invocations with function arguments, this may be a dictionary with the
187        # key being the function name, and the value being an embedded dictionary
188        # where each key is a function argument name and each value is the
189        # corresponding argument value.
190        "id_function": (dict, str),
191        # The directory to store all cache files.
192        "cachedir": str,
193        # Append minion_id to these directories.  Helps with
194        # multiple proxies and minions running on the same machine.
195        # Allowed elements in the list: pki_dir, cachedir, extension_modules, pidfile
196        "append_minionid_config_dirs": list,
197        # Flag to cache jobs locally.
198        "cache_jobs": bool,
199        # The path to the salt configuration file
200        "conf_file": str,
201        # The directory containing unix sockets for things like the event bus
202        "sock_dir": str,
203        # The pool size of unix sockets, it is necessary to avoid blocking waiting for zeromq and tcp communications.
204        "sock_pool_size": int,
205        # Specifies how the file server should backup files, if enabled. The backups
206        # live in the cache dir.
207        "backup_mode": str,
208        # A default renderer for all operations on this host
209        "renderer": str,
210        # Renderer whitelist. The only renderers from this list are allowed.
211        "renderer_whitelist": list,
212        # Rendrerer blacklist. Renderers from this list are disalloed even if specified in whitelist.
213        "renderer_blacklist": list,
214        # A flag indicating that a highstate run should immediately cease if a failure occurs.
215        "failhard": bool,
216        # A flag to indicate that highstate runs should force refresh the modules prior to execution
217        "autoload_dynamic_modules": bool,
218        # Force the minion into a single environment when it fetches files from the master
219        "saltenv": (type(None), str),
220        # Prevent saltenv from being overridden on the command line
221        "lock_saltenv": bool,
222        # Force the minion into a single pillar root when it fetches pillar data from the master
223        "pillarenv": (type(None), str),
224        # Make the pillarenv always match the effective saltenv
225        "pillarenv_from_saltenv": bool,
226        # Allows a user to provide an alternate name for top.sls
227        "state_top": str,
228        "state_top_saltenv": (type(None), str),
229        # States to run when a minion starts up
230        "startup_states": str,
231        # List of startup states
232        "sls_list": list,
233        # Configuration for snapper in the state system
234        "snapper_states": bool,
235        "snapper_states_config": str,
236        # A top file to execute if startup_states == 'top'
237        "top_file": str,
238        # Location of the files a minion should look for. Set to 'local' to never ask the master.
239        "file_client": str,
240        "local": bool,
241        # When using a local file_client, this parameter is used to allow the client to connect to
242        # a master for remote execution.
243        "use_master_when_local": bool,
244        # A map of saltenvs and fileserver backend locations
245        "file_roots": dict,
246        # A map of saltenvs and fileserver backend locations
247        "pillar_roots": dict,
248        # The external pillars permitted to be used on-demand using pillar.ext
249        "on_demand_ext_pillar": list,
250        # A map of glob paths to be used
251        "decrypt_pillar": list,
252        # Delimiter to use in path expressions for decrypt_pillar
253        "decrypt_pillar_delimiter": str,
254        # Default renderer for decrypt_pillar
255        "decrypt_pillar_default": str,
256        # List of renderers available for decrypt_pillar
257        "decrypt_pillar_renderers": list,
258        # The type of hashing algorithm to use when doing file comparisons
259        "hash_type": str,
260        # Order of preference for optimized .pyc files (PY3 only)
261        "optimization_order": list,
262        # Refuse to load these modules
263        "disable_modules": list,
264        # Refuse to load these returners
265        "disable_returners": list,
266        # Tell the loader to only load modules in this list
267        "whitelist_modules": list,
268        # A list of additional directories to search for salt modules in
269        "module_dirs": list,
270        # A list of additional directories to search for salt returners in
271        "returner_dirs": list,
272        # A list of additional directories to search for salt states in
273        "states_dirs": list,
274        # A list of additional directories to search for salt grains in
275        "grains_dirs": list,
276        # A list of additional directories to search for salt renderers in
277        "render_dirs": list,
278        # A list of additional directories to search for salt outputters in
279        "outputter_dirs": list,
280        # A list of additional directories to search for salt utilities in. (Used by the loader
281        # to populate __utils__)
282        "utils_dirs": list,
283        # salt cloud providers
284        "providers": dict,
285        # First remove all modules during any sync operation
286        "clean_dynamic_modules": bool,
287        # A flag indicating that a master should accept any minion connection without any authentication
288        "open_mode": bool,
289        # Whether or not processes should be forked when needed. The alternative is to use threading.
290        "multiprocessing": bool,
291        # Maximum number of concurrently active processes at any given point in time
292        "process_count_max": int,
293        # Whether or not the salt minion should run scheduled mine updates
294        "mine_enabled": bool,
295        # Whether or not scheduled mine updates should be accompanied by a job return for the job cache
296        "mine_return_job": bool,
297        # The number of minutes between mine updates.
298        "mine_interval": int,
299        # The ipc strategy. (i.e., sockets versus tcp, etc)
300        "ipc_mode": str,
301        # Enable ipv6 support for daemons
302        "ipv6": (type(None), bool),
303        # The chunk size to use when streaming files with the file server
304        "file_buffer_size": int,
305        # The TCP port on which minion events should be published if ipc_mode is TCP
306        "tcp_pub_port": int,
307        # The TCP port on which minion events should be pulled if ipc_mode is TCP
308        "tcp_pull_port": int,
309        # The TCP port on which events for the master should be published if ipc_mode is TCP
310        "tcp_master_pub_port": int,
311        # The TCP port on which events for the master should be pulled if ipc_mode is TCP
312        "tcp_master_pull_port": int,
313        # The TCP port on which events for the master should pulled and then republished onto
314        # the event bus on the master
315        "tcp_master_publish_pull": int,
316        # The TCP port for mworkers to connect to on the master
317        "tcp_master_workers": int,
318        # The file to send logging data to
319        "log_file": str,
320        # The level of verbosity at which to log
321        "log_level": str,
322        # The log level to log to a given file
323        "log_level_logfile": (type(None), str),
324        # The format to construct dates in log files
325        "log_datefmt": str,
326        # The dateformat for a given logfile
327        "log_datefmt_logfile": str,
328        # The format for console logs
329        "log_fmt_console": str,
330        # The format for a given log file
331        "log_fmt_logfile": (tuple, str),
332        # A dictionary of logging levels
333        "log_granular_levels": dict,
334        # The maximum number of bytes a single log file may contain before
335        # it is rotated. A value of 0 disables this feature.
336        # Currently only supported on Windows. On other platforms, use an
337        # external tool such as 'logrotate' to manage log files.
338        "log_rotate_max_bytes": int,
339        # The number of backup files to keep when rotating log files. Only
340        # used if log_rotate_max_bytes is greater than 0.
341        # Currently only supported on Windows. On other platforms, use an
342        # external tool such as 'logrotate' to manage log files.
343        "log_rotate_backup_count": int,
344        # If an event is above this size, it will be trimmed before putting it on the event bus
345        "max_event_size": int,
346        # Enable old style events to be sent on minion_startup. Change default to False in 3001 release
347        "enable_legacy_startup_events": bool,
348        # Always execute states with test=True if this flag is set
349        "test": bool,
350        # Tell the loader to attempt to import *.pyx cython files if cython is available
351        "cython_enable": bool,
352        # Whether or not to load grains for FQDNs
353        "enable_fqdns_grains": bool,
354        # Whether or not to load grains for the GPU
355        "enable_gpu_grains": bool,
356        # Tell the loader to attempt to import *.zip archives
357        "enable_zip_modules": bool,
358        # Tell the client to show minions that have timed out
359        "show_timeout": bool,
360        # Tell the client to display the jid when a job is published
361        "show_jid": bool,
362        # Ensure that a generated jid is always unique. If this is set, the jid
363        # format is different due to an underscore and process id being appended
364        # to the jid. WARNING: A change to the jid format may break external
365        # applications that depend on the original format.
366        "unique_jid": bool,
367        # Tells the highstate outputter to show successful states. False will omit successes.
368        "state_verbose": bool,
369        # Specify the format for state outputs. See highstate outputter for additional details.
370        "state_output": str,
371        # Tells the highstate outputter to only report diffs of states that changed
372        "state_output_diff": bool,
373        # Tells the highstate outputter whether profile information will be shown for each state run
374        "state_output_profile": bool,
375        # When true, states run in the order defined in an SLS file, unless requisites re-order them
376        "state_auto_order": bool,
377        # Fire events as state chunks are processed by the state compiler
378        "state_events": bool,
379        # The number of seconds a minion should wait before retry when attempting authentication
380        "acceptance_wait_time": float,
381        # The number of seconds a minion should wait before giving up during authentication
382        "acceptance_wait_time_max": float,
383        # Retry a connection attempt if the master rejects a minion's public key
384        "rejected_retry": bool,
385        # The interval in which a daemon's main loop should attempt to perform all necessary tasks
386        # for normal operation
387        "loop_interval": float,
388        # Perform pre-flight verification steps before daemon startup, such as checking configuration
389        # files and certain directories.
390        "verify_env": bool,
391        # The grains dictionary for a minion, containing specific "facts" about the minion
392        "grains": dict,
393        # Allow a daemon to function even if the key directories are not secured
394        "permissive_pki_access": bool,
395        # The passphrase of the master's private key
396        "key_pass": (type(None), str),
397        # The passphrase of the master's private signing key
398        "signing_key_pass": (type(None), str),
399        # The path to a directory to pull in configuration file includes
400        "default_include": str,
401        # If a minion is running an esky build of salt, upgrades can be performed using the url
402        # defined here. See saltutil.update() for additional information
403        "update_url": (bool, str),
404        # If using update_url with saltutil.update(), provide a list of services to be restarted
405        # post-install
406        "update_restart_services": list,
407        # The number of seconds to sleep between retrying an attempt to resolve the hostname of a
408        # salt master
409        "retry_dns": float,
410        "retry_dns_count": (type(None), int),
411        # In the case when the resolve of the salt master hostname fails, fall back to localhost
412        "resolve_dns_fallback": bool,
413        # set the zeromq_reconnect_ivl option on the minion.
414        # http://lists.zeromq.org/pipermail/zeromq-dev/2011-January/008845.html
415        "recon_max": float,
416        # If recon_randomize is set, this specifies the lower bound for the randomized period
417        "recon_default": float,
418        # Tells the minion to choose a bounded, random interval to have zeromq attempt to reconnect
419        # in the event of a disconnect event
420        "recon_randomize": bool,
421        # Configures retry interval, randomized between timer and timer_max if timer_max > 0
422        "return_retry_timer": int,
423        "return_retry_timer_max": int,
424        # Configures amount of return retries
425        "return_retry_tries": int,
426        # Specify one or more returners in which all events will be sent to. Requires that the returners
427        # in question have an event_return(event) function!
428        "event_return": (list, str),
429        # The number of events to queue up in memory before pushing them down the pipe to an event
430        # returner specified by 'event_return'
431        "event_return_queue": int,
432        # The number of seconds that events can languish in the queue before we flush them.
433        # The goal here is to ensure that if the bus is not busy enough to reach a total
434        # `event_return_queue` events won't get stale.
435        "event_return_queue_max_seconds": int,
436        # Only forward events to an event returner if it matches one of the tags in this list
437        "event_return_whitelist": list,
438        # Events matching a tag in this list should never be sent to an event returner.
439        "event_return_blacklist": list,
440        # default match type for filtering events tags: startswith, endswith, find, regex, fnmatch
441        "event_match_type": str,
442        # This pidfile to write out to when a daemon starts
443        "pidfile": str,
444        # Used with the SECO range master tops system
445        "range_server": str,
446        # The tcp keepalive interval to set on TCP ports. This setting can be used to tune Salt
447        # connectivity issues in messy network environments with misbehaving firewalls
448        "tcp_keepalive": bool,
449        # Sets zeromq TCP keepalive idle. May be used to tune issues with minion disconnects
450        "tcp_keepalive_idle": float,
451        # Sets zeromq TCP keepalive count. May be used to tune issues with minion disconnects
452        "tcp_keepalive_cnt": float,
453        # Sets zeromq TCP keepalive interval. May be used to tune issues with minion disconnects.
454        "tcp_keepalive_intvl": float,
455        # The network interface for a daemon to bind to
456        "interface": str,
457        # The port for a salt master to broadcast publications on. This will also be the port minions
458        # connect to to listen for publications.
459        "publish_port": int,
460        # TODO unknown option!
461        "auth_mode": int,
462        # listen queue size / backlog
463        "zmq_backlog": int,
464        # Set the zeromq high water mark on the publisher interface.
465        # http://api.zeromq.org/3-2:zmq-setsockopt
466        "pub_hwm": int,
467        # IPC buffer size
468        # Refs https://github.com/saltstack/salt/issues/34215
469        "ipc_write_buffer": int,
470        # various subprocess niceness levels
471        "req_server_niceness": (type(None), int),
472        "pub_server_niceness": (type(None), int),
473        "fileserver_update_niceness": (type(None), int),
474        "maintenance_niceness": (type(None), int),
475        "mworker_niceness": (type(None), int),
476        "mworker_queue_niceness": (type(None), int),
477        "event_return_niceness": (type(None), int),
478        "event_publisher_niceness": (type(None), int),
479        "reactor_niceness": (type(None), int),
480        # The number of MWorker processes for a master to startup. This number needs to scale up as
481        # the number of connected minions increases.
482        "worker_threads": int,
483        # The port for the master to listen to returns on. The minion needs to connect to this port
484        # to send returns.
485        "ret_port": int,
486        # The number of hours to keep jobs around in the job cache on the master
487        "keep_jobs": int,
488        # If the returner supports `clean_old_jobs`, then at cleanup time,
489        # archive the job data before deleting it.
490        "archive_jobs": bool,
491        # A master-only copy of the file_roots dictionary, used by the state compiler
492        "master_roots": dict,
493        # Add the proxymodule LazyLoader object to opts.  This breaks many things
494        # but this was the default pre 2015.8.2.  This should default to
495        # False in 2016.3.0
496        "add_proxymodule_to_opts": bool,
497        # Merge pillar data into configuration opts.
498        # As multiple proxies can run on the same server, we may need different
499        # configuration options for each, while there's one single configuration file.
500        # The solution is merging the pillar data of each proxy minion into the opts.
501        "proxy_merge_pillar_in_opts": bool,
502        # Deep merge of pillar data into configuration opts.
503        # Evaluated only when `proxy_merge_pillar_in_opts` is True.
504        "proxy_deep_merge_pillar_in_opts": bool,
505        # The strategy used when merging pillar into opts.
506        # Considered only when `proxy_merge_pillar_in_opts` is True.
507        "proxy_merge_pillar_in_opts_strategy": str,
508        # Allow enabling mine details using pillar data.
509        "proxy_mines_pillar": bool,
510        # In some particular cases, always alive proxies are not beneficial.
511        # This option can be used in those less dynamic environments:
512        # the user can request the connection
513        # always alive, or init-shutdown per command.
514        "proxy_always_alive": bool,
515        # Poll the connection state with the proxy minion
516        # If enabled, this option requires the function `alive`
517        # to be implemented in the proxy module
518        "proxy_keep_alive": bool,
519        # Frequency of the proxy_keep_alive, in minutes
520        "proxy_keep_alive_interval": int,
521        # Update intervals
522        "roots_update_interval": int,
523        "azurefs_update_interval": int,
524        "gitfs_update_interval": int,
525        "git_pillar_update_interval": int,
526        "hgfs_update_interval": int,
527        "minionfs_update_interval": int,
528        "s3fs_update_interval": int,
529        "svnfs_update_interval": int,
530        # NOTE: git_pillar_base, git_pillar_fallback, git_pillar_branch,
531        # git_pillar_env, and git_pillar_root omitted here because their values
532        # could conceivably be loaded as non-string types, which is OK because
533        # git_pillar will normalize them to strings. But rather than include all the
534        # possible types they could be, we'll just skip type-checking.
535        "git_pillar_ssl_verify": bool,
536        "git_pillar_global_lock": bool,
537        "git_pillar_user": str,
538        "git_pillar_password": str,
539        "git_pillar_insecure_auth": bool,
540        "git_pillar_privkey": str,
541        "git_pillar_pubkey": str,
542        "git_pillar_passphrase": str,
543        "git_pillar_refspecs": list,
544        "git_pillar_includes": bool,
545        "git_pillar_verify_config": bool,
546        # NOTE: gitfs_base, gitfs_fallback, gitfs_mountpoint, and gitfs_root omitted
547        # here because their values could conceivably be loaded as non-string types,
548        # which is OK because gitfs will normalize them to strings. But rather than
549        # include all the possible types they could be, we'll just skip type-checking.
550        "gitfs_remotes": list,
551        "gitfs_insecure_auth": bool,
552        "gitfs_privkey": str,
553        "gitfs_pubkey": str,
554        "gitfs_passphrase": str,
555        "gitfs_saltenv_whitelist": list,
556        "gitfs_saltenv_blacklist": list,
557        "gitfs_ssl_verify": bool,
558        "gitfs_global_lock": bool,
559        "gitfs_saltenv": list,
560        "gitfs_ref_types": list,
561        "gitfs_refspecs": list,
562        "gitfs_disable_saltenv_mapping": bool,
563        "hgfs_remotes": list,
564        "hgfs_mountpoint": str,
565        "hgfs_root": str,
566        "hgfs_base": str,
567        "hgfs_branch_method": str,
568        "hgfs_saltenv_whitelist": list,
569        "hgfs_saltenv_blacklist": list,
570        "svnfs_remotes": list,
571        "svnfs_mountpoint": str,
572        "svnfs_root": str,
573        "svnfs_trunk": str,
574        "svnfs_branches": str,
575        "svnfs_tags": str,
576        "svnfs_saltenv_whitelist": list,
577        "svnfs_saltenv_blacklist": list,
578        "minionfs_env": str,
579        "minionfs_mountpoint": str,
580        "minionfs_whitelist": list,
581        "minionfs_blacklist": list,
582        # Specify a list of external pillar systems to use
583        "ext_pillar": list,
584        # Reserved for future use to version the pillar structure
585        "pillar_version": int,
586        # Whether or not a copy of the master opts dict should be rendered into minion pillars
587        "pillar_opts": bool,
588        # Cache the master pillar to disk to avoid having to pass through the rendering system
589        "pillar_cache": bool,
590        # Pillar cache TTL, in seconds. Has no effect unless `pillar_cache` is True
591        "pillar_cache_ttl": int,
592        # Pillar cache backend. Defaults to `disk` which stores caches in the master cache
593        "pillar_cache_backend": str,
594        # Cache the GPG data to avoid having to pass through the gpg renderer
595        "gpg_cache": bool,
596        # GPG data cache TTL, in seconds. Has no effect unless `gpg_cache` is True
597        "gpg_cache_ttl": int,
598        # GPG data cache backend. Defaults to `disk` which stores caches in the master cache
599        "gpg_cache_backend": str,
600        "pillar_safe_render_error": bool,
601        # When creating a pillar, there are several strategies to choose from when
602        # encountering duplicate values
603        "pillar_source_merging_strategy": str,
604        # Recursively merge lists by aggregating them instead of replacing them.
605        "pillar_merge_lists": bool,
606        # If True, values from included pillar SLS targets will override
607        "pillar_includes_override_sls": bool,
608        # How to merge multiple top files from multiple salt environments
609        # (saltenvs); can be 'merge' or 'same'
610        "top_file_merging_strategy": str,
611        # The ordering for salt environment merging, when top_file_merging_strategy
612        # is set to 'same'
613        "env_order": list,
614        # The salt environment which provides the default top file when
615        # top_file_merging_strategy is set to 'same'; defaults to 'base'
616        "default_top": str,
617        "ping_on_rotate": bool,
618        "peer": dict,
619        "preserve_minion_cache": bool,
620        "syndic_master": (str, list),
621        # The behaviour of the multimaster syndic when connection to a master of masters failed. Can
622        # specify 'random' (default) or 'ordered'. If set to 'random' masters will be iterated in random
623        # order if 'ordered' the configured order will be used.
624        "syndic_failover": str,
625        "syndic_forward_all_events": bool,
626        "runner_dirs": list,
627        "client_acl_verify": bool,
628        "publisher_acl": dict,
629        "publisher_acl_blacklist": dict,
630        "sudo_acl": bool,
631        "external_auth": dict,
632        "token_expire": int,
633        "token_expire_user_override": (bool, dict),
634        "file_recv": bool,
635        "file_recv_max_size": int,
636        "file_ignore_regex": (list, str),
637        "file_ignore_glob": (list, str),
638        "fileserver_backend": list,
639        "fileserver_followsymlinks": bool,
640        "fileserver_ignoresymlinks": bool,
641        "fileserver_limit_traversal": bool,
642        "fileserver_verify_config": bool,
643        # Optionally apply '*' permissioins to any user. By default '*' is a fallback case that is
644        # applied only if the user didn't matched by other matchers.
645        "permissive_acl": bool,
646        # Optionally enables keeping the calculated user's auth list in the token file.
647        "keep_acl_in_token": bool,
648        # Auth subsystem module to use to get authorized access list for a user. By default it's the
649        # same module used for external authentication.
650        "eauth_acl_module": str,
651        # Subsystem to use to maintain eauth tokens. By default, tokens are stored on the local
652        # filesystem
653        "eauth_tokens": str,
654        # The number of open files a daemon is allowed to have open. Frequently needs to be increased
655        # higher than the system default in order to account for the way zeromq consumes file handles.
656        "max_open_files": int,
657        # Automatically accept any key provided to the master. Implies that the key will be preserved
658        # so that subsequent connections will be authenticated even if this option has later been
659        # turned off.
660        "auto_accept": bool,
661        "autosign_timeout": int,
662        # A mapping of external systems that can be used to generate topfile data.
663        "master_tops": dict,
664        # Whether or not matches from master_tops should be executed before or
665        # after those from the top file(s).
666        "master_tops_first": bool,
667        # A flag that should be set on a top-level master when it is ordering around subordinate masters
668        # via the use of a salt syndic
669        "order_masters": bool,
670        # Whether or not to cache jobs so that they can be examined later on
671        "job_cache": bool,
672        # Define a returner to be used as an external job caching storage backend
673        "ext_job_cache": str,
674        # Specify a returner for the master to use as a backend storage system to cache jobs returns
675        # that it receives
676        "master_job_cache": str,
677        # Specify whether the master should store end times for jobs as returns come in
678        "job_cache_store_endtime": bool,
679        # The minion data cache is a cache of information about the minions stored on the master.
680        # This information is primarily the pillar and grains data. The data is cached in the master
681        # cachedir under the name of the minion and used to predetermine what minions are expected to
682        # reply from executions.
683        "minion_data_cache": bool,
684        # The number of seconds between AES key rotations on the master
685        "publish_session": int,
686        # Defines a salt reactor. See https://docs.saltproject.io/en/latest/topics/reactor/
687        "reactor": list,
688        # The TTL for the cache of the reactor configuration
689        "reactor_refresh_interval": int,
690        # The number of workers for the runner/wheel in the reactor
691        "reactor_worker_threads": int,
692        # The queue size for workers in the reactor
693        "reactor_worker_hwm": int,
694        # Defines engines. See https://docs.saltproject.io/en/latest/topics/engines/
695        "engines": list,
696        # Whether or not to store runner returns in the job cache
697        "runner_returns": bool,
698        "serial": str,
699        "search": str,
700        # A compound target definition.
701        # See: https://docs.saltproject.io/en/latest/topics/targeting/nodegroups.html
702        "nodegroups": (dict, list),
703        # List-only nodegroups for salt-ssh. Each group must be formed as either a
704        # comma-separated list, or a YAML list.
705        "ssh_list_nodegroups": dict,
706        # By default, salt-ssh uses its own specially-generated RSA key to auth
707        # against minions. If this is set to True, salt-ssh will look in
708        # for a key at ~/.ssh/id_rsa, and fall back to using its own specially-
709        # generated RSA key if that file doesn't exist.
710        "ssh_use_home_key": bool,
711        # The logfile location for salt-key
712        "key_logfile": str,
713        # The upper bound for the random number of seconds that a minion should
714        # delay when starting in up before it connects to a master. This can be
715        # used to mitigate a thundering-herd scenario when many minions start up
716        # at once and attempt to all connect immediately to the master
717        "random_startup_delay": int,
718        # The source location for the winrepo sls files
719        # (used by win_pkg.py, minion only)
720        "winrepo_source_dir": str,
721        "winrepo_dir": str,
722        "winrepo_dir_ng": str,
723        "winrepo_cachefile": str,
724        # NOTE: winrepo_branch omitted here because its value could conceivably be
725        # loaded as a non-string type, which is OK because winrepo will normalize
726        # them to strings. But rather than include all the possible types it could
727        # be, we'll just skip type-checking.
728        "winrepo_cache_expire_max": int,
729        "winrepo_cache_expire_min": int,
730        "winrepo_remotes": list,
731        "winrepo_remotes_ng": list,
732        "winrepo_ssl_verify": bool,
733        "winrepo_user": str,
734        "winrepo_password": str,
735        "winrepo_insecure_auth": bool,
736        "winrepo_privkey": str,
737        "winrepo_pubkey": str,
738        "winrepo_passphrase": str,
739        "winrepo_refspecs": list,
740        # Set a hard limit for the amount of memory modules can consume on a minion.
741        "modules_max_memory": int,
742        # Blacklist specific core grains to be filtered
743        "grains_blacklist": list,
744        # The number of minutes between the minion refreshing its cache of grains
745        "grains_refresh_every": int,
746        # Use lspci to gather system data for grains on a minion
747        "enable_lspci": bool,
748        # The number of seconds for the salt client to wait for additional syndics to
749        # check in with their lists of expected minions before giving up
750        "syndic_wait": int,
751        # Override Jinja environment option defaults for all templates except sls templates
752        "jinja_env": dict,
753        # Set Jinja environment options for sls templates
754        "jinja_sls_env": dict,
755        # If this is set to True leading spaces and tabs are stripped from the start
756        # of a line to a block.
757        "jinja_lstrip_blocks": bool,
758        # If this is set to True the first newline after a Jinja block is removed
759        "jinja_trim_blocks": bool,
760        # Cache minion ID to file
761        "minion_id_caching": bool,
762        # Always generate minion id in lowercase.
763        "minion_id_lowercase": bool,
764        # Remove either a single domain (foo.org), or all (True) from a generated minion id.
765        "minion_id_remove_domain": (str, bool),
766        # If set, the master will sign all publications before they are sent out
767        "sign_pub_messages": bool,
768        # The size of key that should be generated when creating new keys
769        "keysize": int,
770        # The transport system for this daemon. (i.e. zeromq, tcp, detect, etc)
771        "transport": str,
772        # The number of seconds to wait when the client is requesting information about running jobs
773        "gather_job_timeout": int,
774        # The number of seconds to wait before timing out an authentication request
775        "auth_timeout": int,
776        # The number of attempts to authenticate to a master before giving up
777        "auth_tries": int,
778        # The number of attempts to connect to a master before giving up.
779        # Set this to -1 for unlimited attempts. This allows for a master to have
780        # downtime and the minion to reconnect to it later when it comes back up.
781        # In 'failover' mode, it is the number of attempts for each set of masters.
782        # In this mode, it will cycle through the list of masters for each attempt.
783        "master_tries": int,
784        # Never give up when trying to authenticate to a master
785        "auth_safemode": bool,
786        # Selects a random master when starting a minion up in multi-master mode or
787        # when starting a minion with salt-call. ``master`` must be a list.
788        "random_master": bool,
789        # An upper bound for the amount of time for a minion to sleep before attempting to
790        # reauth after a restart.
791        "random_reauth_delay": int,
792        # The number of seconds for a syndic to poll for new messages that need to be forwarded
793        "syndic_event_forward_timeout": float,
794        # The length that the syndic event queue must hit before events are popped off and forwarded
795        "syndic_jid_forward_cache_hwm": int,
796        # Salt SSH configuration
797        "ssh_passwd": str,
798        "ssh_port": str,
799        "ssh_sudo": bool,
800        "ssh_sudo_user": str,
801        "ssh_timeout": float,
802        "ssh_user": str,
803        "ssh_scan_ports": str,
804        "ssh_scan_timeout": float,
805        "ssh_identities_only": bool,
806        "ssh_log_file": str,
807        "ssh_config_file": str,
808        "ssh_merge_pillar": bool,
809        "ssh_run_pre_flight": bool,
810        "cluster_mode": bool,
811        "sqlite_queue_dir": str,
812        "queue_dirs": list,
813        # Instructs the minion to ping its master(s) every n number of minutes. Used
814        # primarily as a mitigation technique against minion disconnects.
815        "ping_interval": int,
816        # Instructs the salt CLI to print a summary of a minion responses before returning
817        "cli_summary": bool,
818        # The maximum number of minion connections allowed by the master. Can have performance
819        # implications in large setups.
820        "max_minions": int,
821        "username": (type(None), str),
822        "password": (type(None), str),
823        # Use zmq.SUSCRIBE to limit listening sockets to only process messages bound for them
824        "zmq_filtering": bool,
825        # Connection caching. Can greatly speed up salt performance.
826        "con_cache": bool,
827        "rotate_aes_key": bool,
828        # Cache ZeroMQ connections. Can greatly improve salt performance.
829        "cache_sreqs": bool,
830        # Can be set to override the python_shell=False default in the cmd module
831        "cmd_safe": bool,
832        # Used by salt-api for master requests timeout
833        "rest_timeout": int,
834        # If set, all minion exec module actions will be rerouted through sudo as this user
835        "sudo_user": str,
836        # HTTP connection timeout in seconds. Applied for tornado http fetch functions like cp.get_url
837        # should be greater than overall download time
838        "http_connect_timeout": float,
839        # HTTP request timeout in seconds. Applied for tornado http fetch functions like cp.get_url
840        # should be greater than overall download time
841        "http_request_timeout": float,
842        # HTTP request max file content size.
843        "http_max_body": int,
844        # Delay in seconds before executing bootstrap (Salt Cloud)
845        "bootstrap_delay": int,
846        # If a proxymodule has a function called 'grains', then call it during
847        # regular grains loading and merge the results with the proxy's grains
848        # dictionary.  Otherwise it is assumed that the module calls the grains
849        # function in a custom way and returns the data elsewhere
850        #
851        # Default to False for 2016.3 and 2016.11. Switch to True for 2017.7.0
852        "proxy_merge_grains_in_module": bool,
853        # Command to use to restart salt-minion
854        "minion_restart_command": list,
855        # Whether or not a minion should send the results of a command back to the master
856        # Useful when a returner is the source of truth for a job result
857        "pub_ret": bool,
858        # HTTP proxy settings. Used in tornado fetch functions, apt-key etc
859        "proxy_host": str,
860        "proxy_username": str,
861        "proxy_password": str,
862        "proxy_port": int,
863        # Exclude list of hostnames from proxy
864        "no_proxy": list,
865        # Minion de-dup jid cache max size
866        "minion_jid_queue_hwm": int,
867        # Minion data cache driver (one of satl.cache.* modules)
868        "cache": str,
869        # Enables a fast in-memory cache booster and sets the expiration time.
870        "memcache_expire_seconds": int,
871        # Set a memcache limit in items (bank + key) per cache storage (driver + driver_opts).
872        "memcache_max_items": int,
873        # Each time a cache storage got full cleanup all the expired items not just the oldest one.
874        "memcache_full_cleanup": bool,
875        # Enable collecting the memcache stats and log it on `debug` log level.
876        "memcache_debug": bool,
877        # Thin and minimal Salt extra modules
878        "thin_extra_mods": str,
879        "min_extra_mods": str,
880        # Default returners minion should use. List or comma-delimited string
881        "return": (str, list),
882        # TLS/SSL connection options. This could be set to a dictionary containing arguments
883        # corresponding to python ssl.wrap_socket method. For details see:
884        # http://www.tornadoweb.org/en/stable/tcpserver.html#tornado.tcpserver.TCPServer
885        # http://docs.python.org/2/library/ssl.html#ssl.wrap_socket
886        # Note: to set enum arguments values like `cert_reqs` and `ssl_version` use constant names
887        # without ssl module prefix: `CERT_REQUIRED` or `PROTOCOL_SSLv23`.
888        "ssl": (dict, bool, type(None)),
889        # Controls how a multi-function job returns its data. If this is False,
890        # it will return its data using a dictionary with the function name as
891        # the key. This is compatible with legacy systems. If this is True, it
892        # will return its data using an array in the same order as the input
893        # array of functions to execute. This allows for calling the same
894        # function multiple times in the same multi-function job.
895        "multifunc_ordered": bool,
896        # Controls whether beacons are set up before a connection
897        # to the master is attempted.
898        "beacons_before_connect": bool,
899        # Controls whether the scheduler is set up before a connection
900        # to the master is attempted.
901        "scheduler_before_connect": bool,
902        # Whitelist/blacklist specific modules to be synced
903        "extmod_whitelist": dict,
904        "extmod_blacklist": dict,
905        # django auth
906        "django_auth_path": str,
907        "django_auth_settings": str,
908        # Number of times to try to auth with the master on a reconnect with the
909        # tcp transport
910        "tcp_authentication_retries": int,
911        # Backoff interval in seconds for minion reconnect with tcp transport
912        "tcp_reconnect_backoff": float,
913        # Permit or deny allowing minions to request revoke of its own key
914        "allow_minion_key_revoke": bool,
915        # File chunk size for salt-cp
916        "salt_cp_chunk_size": int,
917        # Require that the minion sign messages it posts to the master on the event
918        # bus
919        "minion_sign_messages": bool,
920        # Have master drop messages from minions for which their signatures do
921        # not verify
922        "drop_messages_signature_fail": bool,
923        # Require that payloads from minions have a 'sig' entry
924        # (in other words, require that minions have 'minion_sign_messages'
925        # turned on)
926        "require_minion_sign_messages": bool,
927        # The list of config entries to be passed to external pillar function as
928        # part of the extra_minion_data param
929        # Subconfig entries can be specified by using the ':' notation (e.g. key:subkey)
930        "pass_to_ext_pillars": (str, list),
931        # SSDP discovery publisher description.
932        # Contains publisher configuration and minion mapping.
933        # Setting it to False disables discovery
934        "discovery": (dict, bool),
935        # Scheduler should be a dictionary
936        "schedule": dict,
937        # Whether to fire auth events
938        "auth_events": bool,
939        # Whether to fire Minion data cache refresh events
940        "minion_data_cache_events": bool,
941        # Enable calling ssh minions from the salt master
942        "enable_ssh_minions": bool,
943        # Thorium saltenv
944        "thoriumenv": (type(None), str),
945        # Thorium top file location
946        "thorium_top": str,
947        # Allow raw_shell option when using the ssh
948        # client via the Salt API
949        "netapi_allow_raw_shell": bool,
950        "disabled_requisites": (str, list),
951        # Feature flag config
952        "features": dict,
953        "fips_mode": bool,
954        # Feature flag to enable checking if master is connected to a host
955        # on a given port
956        "detect_remote_minions": bool,
957        # The port to be used when checking if a master is connected to a
958        # minion
959        "remote_minions_port": int,
960    }
961)
962
963# default configurations
964DEFAULT_MINION_OPTS = immutabletypes.freeze(
965    {
966        "interface": "0.0.0.0",
967        "master": "salt",
968        "master_type": "str",
969        "master_uri_format": "default",
970        "source_interface_name": "",
971        "source_address": "",
972        "source_ret_port": 0,
973        "source_publish_port": 0,
974        "master_port": 4506,
975        "master_finger": "",
976        "master_shuffle": False,
977        "master_alive_interval": 0,
978        "master_failback": False,
979        "master_failback_interval": 0,
980        "verify_master_pubkey_sign": False,
981        "sign_pub_messages": False,
982        "always_verify_signature": False,
983        "master_sign_key_name": "master_sign",
984        "syndic_finger": "",
985        "user": salt.utils.user.get_user(),
986        "root_dir": salt.syspaths.ROOT_DIR,
987        "pki_dir": os.path.join(salt.syspaths.CONFIG_DIR, "pki", "minion"),
988        "id": "",
989        "id_function": {},
990        "cachedir": os.path.join(salt.syspaths.CACHE_DIR, "minion"),
991        "append_minionid_config_dirs": [],
992        "cache_jobs": False,
993        "grains_blacklist": [],
994        "grains_cache": False,
995        "grains_cache_expiration": 300,
996        "grains_deep_merge": False,
997        "conf_file": os.path.join(salt.syspaths.CONFIG_DIR, "minion"),
998        "sock_dir": os.path.join(salt.syspaths.SOCK_DIR, "minion"),
999        "sock_pool_size": 1,
1000        "backup_mode": "",
1001        "renderer": "jinja|yaml",
1002        "renderer_whitelist": [],
1003        "renderer_blacklist": [],
1004        "random_startup_delay": 0,
1005        "failhard": False,
1006        "autoload_dynamic_modules": True,
1007        "saltenv": None,
1008        "lock_saltenv": False,
1009        "pillarenv": None,
1010        "pillarenv_from_saltenv": False,
1011        "pillar_opts": False,
1012        "pillar_source_merging_strategy": "smart",
1013        "pillar_merge_lists": False,
1014        "pillar_includes_override_sls": False,
1015        # ``pillar_cache``, ``pillar_cache_ttl``, ``pillar_cache_backend``,
1016        # ``gpg_cache``, ``gpg_cache_ttl`` and ``gpg_cache_backend``
1017        # are not used on the minion but are unavoidably in the code path
1018        "pillar_cache": False,
1019        "pillar_cache_ttl": 3600,
1020        "pillar_cache_backend": "disk",
1021        "gpg_cache": False,
1022        "gpg_cache_ttl": 86400,
1023        "gpg_cache_backend": "disk",
1024        "extension_modules": os.path.join(salt.syspaths.CACHE_DIR, "minion", "extmods"),
1025        "state_top": "top.sls",
1026        "state_top_saltenv": None,
1027        "startup_states": "",
1028        "sls_list": [],
1029        "start_event_grains": [],
1030        "top_file": "",
1031        "thoriumenv": None,
1032        "thorium_top": "top.sls",
1033        "thorium_interval": 0.5,
1034        "thorium_roots": {"base": [salt.syspaths.BASE_THORIUM_ROOTS_DIR]},
1035        "file_client": "remote",
1036        "local": False,
1037        "use_master_when_local": False,
1038        "file_roots": {
1039            "base": [salt.syspaths.BASE_FILE_ROOTS_DIR, salt.syspaths.SPM_FORMULA_PATH]
1040        },
1041        "top_file_merging_strategy": "merge",
1042        "env_order": [],
1043        "default_top": "base",
1044        "fileserver_limit_traversal": False,
1045        "file_recv": False,
1046        "file_recv_max_size": 100,
1047        "file_ignore_regex": [],
1048        "file_ignore_glob": [],
1049        "fileserver_backend": ["roots"],
1050        "fileserver_followsymlinks": True,
1051        "fileserver_ignoresymlinks": False,
1052        "pillar_roots": {
1053            "base": [salt.syspaths.BASE_PILLAR_ROOTS_DIR, salt.syspaths.SPM_PILLAR_PATH]
1054        },
1055        "on_demand_ext_pillar": ["libvirt", "virtkey"],
1056        "decrypt_pillar": [],
1057        "decrypt_pillar_delimiter": ":",
1058        "decrypt_pillar_default": "gpg",
1059        "decrypt_pillar_renderers": ["gpg"],
1060        # Update intervals
1061        "roots_update_interval": DEFAULT_INTERVAL,
1062        "azurefs_update_interval": DEFAULT_INTERVAL,
1063        "gitfs_update_interval": DEFAULT_INTERVAL,
1064        "git_pillar_update_interval": DEFAULT_INTERVAL,
1065        "hgfs_update_interval": DEFAULT_INTERVAL,
1066        "minionfs_update_interval": DEFAULT_INTERVAL,
1067        "s3fs_update_interval": DEFAULT_INTERVAL,
1068        "svnfs_update_interval": DEFAULT_INTERVAL,
1069        "git_pillar_base": "master",
1070        "git_pillar_branch": "master",
1071        "git_pillar_env": "",
1072        "git_pillar_fallback": "",
1073        "git_pillar_root": "",
1074        "git_pillar_ssl_verify": True,
1075        "git_pillar_global_lock": True,
1076        "git_pillar_user": "",
1077        "git_pillar_password": "",
1078        "git_pillar_insecure_auth": False,
1079        "git_pillar_privkey": "",
1080        "git_pillar_pubkey": "",
1081        "git_pillar_passphrase": "",
1082        "git_pillar_refspecs": _DFLT_REFSPECS,
1083        "git_pillar_includes": True,
1084        "gitfs_remotes": [],
1085        "gitfs_mountpoint": "",
1086        "gitfs_root": "",
1087        "gitfs_base": "master",
1088        "gitfs_fallback": "",
1089        "gitfs_user": "",
1090        "gitfs_password": "",
1091        "gitfs_insecure_auth": False,
1092        "gitfs_privkey": "",
1093        "gitfs_pubkey": "",
1094        "gitfs_passphrase": "",
1095        "gitfs_saltenv_whitelist": [],
1096        "gitfs_saltenv_blacklist": [],
1097        "gitfs_global_lock": True,
1098        "gitfs_ssl_verify": True,
1099        "gitfs_saltenv": [],
1100        "gitfs_ref_types": ["branch", "tag", "sha"],
1101        "gitfs_refspecs": _DFLT_REFSPECS,
1102        "gitfs_disable_saltenv_mapping": False,
1103        "unique_jid": False,
1104        "hash_type": "sha256",
1105        "optimization_order": [0, 1, 2],
1106        "disable_modules": [],
1107        "disable_returners": [],
1108        "whitelist_modules": [],
1109        "module_dirs": [],
1110        "returner_dirs": [],
1111        "grains_dirs": [],
1112        "states_dirs": [],
1113        "render_dirs": [],
1114        "outputter_dirs": [],
1115        "utils_dirs": [],
1116        "publisher_acl": {},
1117        "publisher_acl_blacklist": {},
1118        "providers": {},
1119        "clean_dynamic_modules": True,
1120        "open_mode": False,
1121        "auto_accept": True,
1122        "autosign_timeout": 120,
1123        "multiprocessing": True,
1124        "process_count_max": -1,
1125        "mine_enabled": True,
1126        "mine_return_job": False,
1127        "mine_interval": 60,
1128        "ipc_mode": _DFLT_IPC_MODE,
1129        "ipc_write_buffer": _DFLT_IPC_WBUFFER,
1130        "ipv6": None,
1131        "file_buffer_size": 262144,
1132        "tcp_pub_port": 4510,
1133        "tcp_pull_port": 4511,
1134        "tcp_authentication_retries": 5,
1135        "tcp_reconnect_backoff": 1,
1136        "log_file": os.path.join(salt.syspaths.LOGS_DIR, "minion"),
1137        "log_level": "warning",
1138        "log_level_logfile": None,
1139        "log_datefmt": _DFLT_LOG_DATEFMT,
1140        "log_datefmt_logfile": _DFLT_LOG_DATEFMT_LOGFILE,
1141        "log_fmt_console": _DFLT_LOG_FMT_CONSOLE,
1142        "log_fmt_logfile": _DFLT_LOG_FMT_LOGFILE,
1143        "log_fmt_jid": _DFLT_LOG_FMT_JID,
1144        "log_granular_levels": {},
1145        "log_rotate_max_bytes": 0,
1146        "log_rotate_backup_count": 0,
1147        "max_event_size": 1048576,
1148        "enable_legacy_startup_events": True,
1149        "test": False,
1150        "ext_job_cache": "",
1151        "cython_enable": False,
1152        "enable_fqdns_grains": _DFLT_FQDNS_GRAINS,
1153        "enable_gpu_grains": True,
1154        "enable_zip_modules": False,
1155        "state_verbose": True,
1156        "state_output": "full",
1157        "state_output_diff": False,
1158        "state_output_profile": True,
1159        "state_auto_order": True,
1160        "state_events": False,
1161        "state_aggregate": False,
1162        "snapper_states": False,
1163        "snapper_states_config": "root",
1164        "acceptance_wait_time": 10,
1165        "acceptance_wait_time_max": 0,
1166        "rejected_retry": False,
1167        "loop_interval": 1,
1168        "verify_env": True,
1169        "grains": {},
1170        "permissive_pki_access": False,
1171        "default_include": "minion.d/*.conf",
1172        "update_url": False,
1173        "update_restart_services": [],
1174        "retry_dns": 30,
1175        "retry_dns_count": None,
1176        "resolve_dns_fallback": True,
1177        "recon_max": 10000,
1178        "recon_default": 1000,
1179        "recon_randomize": True,
1180        "return_retry_timer": 5,
1181        "return_retry_timer_max": 10,
1182        "return_retry_tries": 3,
1183        "random_reauth_delay": 10,
1184        "winrepo_source_dir": "salt://win/repo-ng/",
1185        "winrepo_dir": os.path.join(salt.syspaths.BASE_FILE_ROOTS_DIR, "win", "repo"),
1186        "winrepo_dir_ng": os.path.join(
1187            salt.syspaths.BASE_FILE_ROOTS_DIR, "win", "repo-ng"
1188        ),
1189        "winrepo_cachefile": "winrepo.p",
1190        "winrepo_cache_expire_max": 604800,
1191        "winrepo_cache_expire_min": 1800,
1192        "winrepo_remotes": ["https://github.com/saltstack/salt-winrepo.git"],
1193        "winrepo_remotes_ng": ["https://github.com/saltstack/salt-winrepo-ng.git"],
1194        "winrepo_branch": "master",
1195        "winrepo_fallback": "",
1196        "winrepo_ssl_verify": True,
1197        "winrepo_user": "",
1198        "winrepo_password": "",
1199        "winrepo_insecure_auth": False,
1200        "winrepo_privkey": "",
1201        "winrepo_pubkey": "",
1202        "winrepo_passphrase": "",
1203        "winrepo_refspecs": _DFLT_REFSPECS,
1204        "pidfile": os.path.join(salt.syspaths.PIDFILE_DIR, "salt-minion.pid"),
1205        "range_server": "range:80",
1206        "reactor_refresh_interval": 60,
1207        "reactor_worker_threads": 10,
1208        "reactor_worker_hwm": 10000,
1209        "engines": [],
1210        "tcp_keepalive": True,
1211        "tcp_keepalive_idle": 300,
1212        "tcp_keepalive_cnt": -1,
1213        "tcp_keepalive_intvl": -1,
1214        "modules_max_memory": -1,
1215        "grains_refresh_every": 0,
1216        "minion_id_caching": True,
1217        "minion_id_lowercase": False,
1218        "minion_id_remove_domain": False,
1219        "keysize": 2048,
1220        "transport": "zeromq",
1221        "auth_timeout": 5,
1222        "auth_tries": 7,
1223        "master_tries": _MASTER_TRIES,
1224        "master_tops_first": False,
1225        "auth_safemode": False,
1226        "random_master": False,
1227        "cluster_mode": False,
1228        "restart_on_error": False,
1229        "ping_interval": 0,
1230        "username": None,
1231        "password": None,
1232        "zmq_filtering": False,
1233        "zmq_monitor": False,
1234        "cache_sreqs": True,
1235        "cmd_safe": True,
1236        "sudo_user": "",
1237        "http_connect_timeout": 20.0,  # tornado default - 20 seconds
1238        "http_request_timeout": 1 * 60 * 60.0,  # 1 hour
1239        "http_max_body": 100 * 1024 * 1024 * 1024,  # 100GB
1240        "event_match_type": "startswith",
1241        "minion_restart_command": [],
1242        "pub_ret": True,
1243        "proxy_host": "",
1244        "proxy_username": "",
1245        "proxy_password": "",
1246        "proxy_port": 0,
1247        "minion_jid_queue_hwm": 100,
1248        "ssl": None,
1249        "multifunc_ordered": False,
1250        "beacons_before_connect": False,
1251        "scheduler_before_connect": False,
1252        "cache": "localfs",
1253        "salt_cp_chunk_size": 65536,
1254        "extmod_whitelist": {},
1255        "extmod_blacklist": {},
1256        "minion_sign_messages": False,
1257        "discovery": False,
1258        "schedule": {},
1259        "ssh_merge_pillar": True,
1260        "disabled_requisites": [],
1261        "reactor_niceness": None,
1262        "fips_mode": False,
1263    }
1264)
1265
1266DEFAULT_MASTER_OPTS = immutabletypes.freeze(
1267    {
1268        "interface": "0.0.0.0",
1269        "publish_port": 4505,
1270        "zmq_backlog": 1000,
1271        "pub_hwm": 1000,
1272        "auth_mode": 1,
1273        "user": _MASTER_USER,
1274        "worker_threads": 5,
1275        "sock_dir": os.path.join(salt.syspaths.SOCK_DIR, "master"),
1276        "sock_pool_size": 1,
1277        "ret_port": 4506,
1278        "timeout": 5,
1279        "keep_jobs": 24,
1280        "archive_jobs": False,
1281        "root_dir": salt.syspaths.ROOT_DIR,
1282        "pki_dir": os.path.join(salt.syspaths.CONFIG_DIR, "pki", "master"),
1283        "key_cache": "",
1284        "cachedir": os.path.join(salt.syspaths.CACHE_DIR, "master"),
1285        "file_roots": {
1286            "base": [salt.syspaths.BASE_FILE_ROOTS_DIR, salt.syspaths.SPM_FORMULA_PATH]
1287        },
1288        "master_roots": {"base": [salt.syspaths.BASE_MASTER_ROOTS_DIR]},
1289        "pillar_roots": {
1290            "base": [salt.syspaths.BASE_PILLAR_ROOTS_DIR, salt.syspaths.SPM_PILLAR_PATH]
1291        },
1292        "on_demand_ext_pillar": ["libvirt", "virtkey"],
1293        "decrypt_pillar": [],
1294        "decrypt_pillar_delimiter": ":",
1295        "decrypt_pillar_default": "gpg",
1296        "decrypt_pillar_renderers": ["gpg"],
1297        "thoriumenv": None,
1298        "thorium_top": "top.sls",
1299        "thorium_interval": 0.5,
1300        "thorium_roots": {"base": [salt.syspaths.BASE_THORIUM_ROOTS_DIR]},
1301        "top_file_merging_strategy": "merge",
1302        "env_order": [],
1303        "saltenv": None,
1304        "lock_saltenv": False,
1305        "pillarenv": None,
1306        "default_top": "base",
1307        "file_client": "local",
1308        "local": True,
1309        # Update intervals
1310        "roots_update_interval": DEFAULT_INTERVAL,
1311        "azurefs_update_interval": DEFAULT_INTERVAL,
1312        "gitfs_update_interval": DEFAULT_INTERVAL,
1313        "git_pillar_update_interval": DEFAULT_INTERVAL,
1314        "hgfs_update_interval": DEFAULT_INTERVAL,
1315        "minionfs_update_interval": DEFAULT_INTERVAL,
1316        "s3fs_update_interval": DEFAULT_INTERVAL,
1317        "svnfs_update_interval": DEFAULT_INTERVAL,
1318        "git_pillar_base": "master",
1319        "git_pillar_branch": "master",
1320        "git_pillar_env": "",
1321        "git_pillar_fallback": "",
1322        "git_pillar_root": "",
1323        "git_pillar_ssl_verify": True,
1324        "git_pillar_global_lock": True,
1325        "git_pillar_user": "",
1326        "git_pillar_password": "",
1327        "git_pillar_insecure_auth": False,
1328        "git_pillar_privkey": "",
1329        "git_pillar_pubkey": "",
1330        "git_pillar_passphrase": "",
1331        "git_pillar_refspecs": _DFLT_REFSPECS,
1332        "git_pillar_includes": True,
1333        "git_pillar_verify_config": True,
1334        "gitfs_remotes": [],
1335        "gitfs_mountpoint": "",
1336        "gitfs_root": "",
1337        "gitfs_base": "master",
1338        "gitfs_fallback": "",
1339        "gitfs_user": "",
1340        "gitfs_password": "",
1341        "gitfs_insecure_auth": False,
1342        "gitfs_privkey": "",
1343        "gitfs_pubkey": "",
1344        "gitfs_passphrase": "",
1345        "gitfs_saltenv_whitelist": [],
1346        "gitfs_saltenv_blacklist": [],
1347        "gitfs_global_lock": True,
1348        "gitfs_ssl_verify": True,
1349        "gitfs_saltenv": [],
1350        "gitfs_ref_types": ["branch", "tag", "sha"],
1351        "gitfs_refspecs": _DFLT_REFSPECS,
1352        "gitfs_disable_saltenv_mapping": False,
1353        "hgfs_remotes": [],
1354        "hgfs_mountpoint": "",
1355        "hgfs_root": "",
1356        "hgfs_base": "default",
1357        "hgfs_branch_method": "branches",
1358        "hgfs_saltenv_whitelist": [],
1359        "hgfs_saltenv_blacklist": [],
1360        "show_timeout": True,
1361        "show_jid": False,
1362        "unique_jid": False,
1363        "svnfs_remotes": [],
1364        "svnfs_mountpoint": "",
1365        "svnfs_root": "",
1366        "svnfs_trunk": "trunk",
1367        "svnfs_branches": "branches",
1368        "svnfs_tags": "tags",
1369        "svnfs_saltenv_whitelist": [],
1370        "svnfs_saltenv_blacklist": [],
1371        "max_event_size": 1048576,
1372        "master_stats": False,
1373        "master_stats_event_iter": 60,
1374        "minionfs_env": "base",
1375        "minionfs_mountpoint": "",
1376        "minionfs_whitelist": [],
1377        "minionfs_blacklist": [],
1378        "ext_pillar": [],
1379        "pillar_version": 2,
1380        "pillar_opts": False,
1381        "pillar_safe_render_error": True,
1382        "pillar_source_merging_strategy": "smart",
1383        "pillar_merge_lists": False,
1384        "pillar_includes_override_sls": False,
1385        "pillar_cache": False,
1386        "pillar_cache_ttl": 3600,
1387        "pillar_cache_backend": "disk",
1388        "gpg_cache": False,
1389        "gpg_cache_ttl": 86400,
1390        "gpg_cache_backend": "disk",
1391        "ping_on_rotate": False,
1392        "peer": {},
1393        "preserve_minion_cache": False,
1394        "syndic_master": "masterofmasters",
1395        "syndic_failover": "random",
1396        "syndic_forward_all_events": False,
1397        "syndic_log_file": os.path.join(salt.syspaths.LOGS_DIR, "syndic"),
1398        "syndic_pidfile": os.path.join(salt.syspaths.PIDFILE_DIR, "salt-syndic.pid"),
1399        "outputter_dirs": [],
1400        "runner_dirs": [],
1401        "utils_dirs": [],
1402        "client_acl_verify": True,
1403        "publisher_acl": {},
1404        "publisher_acl_blacklist": {},
1405        "sudo_acl": False,
1406        "external_auth": {},
1407        "token_expire": 43200,
1408        "token_expire_user_override": False,
1409        "permissive_acl": False,
1410        "keep_acl_in_token": False,
1411        "eauth_acl_module": "",
1412        "eauth_tokens": "localfs",
1413        "extension_modules": os.path.join(salt.syspaths.CACHE_DIR, "master", "extmods"),
1414        "module_dirs": [],
1415        "file_recv": False,
1416        "file_recv_max_size": 100,
1417        "file_buffer_size": 1048576,
1418        "file_ignore_regex": [],
1419        "file_ignore_glob": [],
1420        "fileserver_backend": ["roots"],
1421        "fileserver_followsymlinks": True,
1422        "fileserver_ignoresymlinks": False,
1423        "fileserver_limit_traversal": False,
1424        "fileserver_verify_config": True,
1425        "max_open_files": 100000,
1426        "hash_type": "sha256",
1427        "optimization_order": [0, 1, 2],
1428        "conf_file": os.path.join(salt.syspaths.CONFIG_DIR, "master"),
1429        "open_mode": False,
1430        "auto_accept": False,
1431        "renderer": "jinja|yaml",
1432        "renderer_whitelist": [],
1433        "renderer_blacklist": [],
1434        "failhard": False,
1435        "state_top": "top.sls",
1436        "state_top_saltenv": None,
1437        "master_tops": {},
1438        "master_tops_first": False,
1439        "order_masters": False,
1440        "job_cache": True,
1441        "ext_job_cache": "",
1442        "master_job_cache": "local_cache",
1443        "job_cache_store_endtime": False,
1444        "minion_data_cache": True,
1445        "enforce_mine_cache": False,
1446        "ipc_mode": _DFLT_IPC_MODE,
1447        "ipc_write_buffer": _DFLT_IPC_WBUFFER,
1448        # various subprocess niceness levels
1449        "req_server_niceness": None,
1450        "pub_server_niceness": None,
1451        "fileserver_update_niceness": None,
1452        "mworker_niceness": None,
1453        "mworker_queue_niceness": None,
1454        "maintenance_niceness": None,
1455        "event_return_niceness": None,
1456        "event_publisher_niceness": None,
1457        "reactor_niceness": None,
1458        "ipv6": None,
1459        "tcp_master_pub_port": 4512,
1460        "tcp_master_pull_port": 4513,
1461        "tcp_master_publish_pull": 4514,
1462        "tcp_master_workers": 4515,
1463        "log_file": os.path.join(salt.syspaths.LOGS_DIR, "master"),
1464        "log_level": "warning",
1465        "log_level_logfile": None,
1466        "log_datefmt": _DFLT_LOG_DATEFMT,
1467        "log_datefmt_logfile": _DFLT_LOG_DATEFMT_LOGFILE,
1468        "log_fmt_console": _DFLT_LOG_FMT_CONSOLE,
1469        "log_fmt_logfile": _DFLT_LOG_FMT_LOGFILE,
1470        "log_fmt_jid": _DFLT_LOG_FMT_JID,
1471        "log_granular_levels": {},
1472        "log_rotate_max_bytes": 0,
1473        "log_rotate_backup_count": 0,
1474        "pidfile": os.path.join(salt.syspaths.PIDFILE_DIR, "salt-master.pid"),
1475        "publish_session": 86400,
1476        "range_server": "range:80",
1477        "reactor": [],
1478        "reactor_refresh_interval": 60,
1479        "reactor_worker_threads": 10,
1480        "reactor_worker_hwm": 10000,
1481        "engines": [],
1482        "event_return": "",
1483        "event_return_queue": 0,
1484        "event_return_whitelist": [],
1485        "event_return_blacklist": [],
1486        "event_match_type": "startswith",
1487        "runner_returns": True,
1488        "serial": "msgpack",
1489        "test": False,
1490        "state_verbose": True,
1491        "state_output": "full",
1492        "state_output_diff": False,
1493        "state_output_profile": True,
1494        "state_auto_order": True,
1495        "state_events": False,
1496        "state_aggregate": False,
1497        "search": "",
1498        "loop_interval": 60,
1499        "nodegroups": {},
1500        "ssh_list_nodegroups": {},
1501        "ssh_use_home_key": False,
1502        "cython_enable": False,
1503        "enable_gpu_grains": False,
1504        # XXX: Remove 'key_logfile' support in 2014.1.0
1505        "key_logfile": os.path.join(salt.syspaths.LOGS_DIR, "key"),
1506        "verify_env": True,
1507        "permissive_pki_access": False,
1508        "key_pass": None,
1509        "signing_key_pass": None,
1510        "default_include": "master.d/*.conf",
1511        "winrepo_dir": os.path.join(salt.syspaths.BASE_FILE_ROOTS_DIR, "win", "repo"),
1512        "winrepo_dir_ng": os.path.join(
1513            salt.syspaths.BASE_FILE_ROOTS_DIR, "win", "repo-ng"
1514        ),
1515        "winrepo_cachefile": "winrepo.p",
1516        "winrepo_remotes": ["https://github.com/saltstack/salt-winrepo.git"],
1517        "winrepo_remotes_ng": ["https://github.com/saltstack/salt-winrepo-ng.git"],
1518        "winrepo_branch": "master",
1519        "winrepo_fallback": "",
1520        "winrepo_ssl_verify": True,
1521        "winrepo_user": "",
1522        "winrepo_password": "",
1523        "winrepo_insecure_auth": False,
1524        "winrepo_privkey": "",
1525        "winrepo_pubkey": "",
1526        "winrepo_passphrase": "",
1527        "winrepo_refspecs": _DFLT_REFSPECS,
1528        "syndic_wait": 5,
1529        "jinja_env": {},
1530        "jinja_sls_env": {},
1531        "jinja_lstrip_blocks": False,
1532        "jinja_trim_blocks": False,
1533        "tcp_keepalive": True,
1534        "tcp_keepalive_idle": 300,
1535        "tcp_keepalive_cnt": -1,
1536        "tcp_keepalive_intvl": -1,
1537        "sign_pub_messages": True,
1538        "keysize": 2048,
1539        "transport": "zeromq",
1540        "gather_job_timeout": 10,
1541        "syndic_event_forward_timeout": 0.5,
1542        "syndic_jid_forward_cache_hwm": 100,
1543        "regen_thin": False,
1544        "ssh_passwd": "",
1545        "ssh_priv_passwd": "",
1546        "ssh_port": "22",
1547        "ssh_sudo": False,
1548        "ssh_sudo_user": "",
1549        "ssh_timeout": 60,
1550        "ssh_user": "root",
1551        "ssh_scan_ports": "22",
1552        "ssh_scan_timeout": 0.01,
1553        "ssh_identities_only": False,
1554        "ssh_log_file": os.path.join(salt.syspaths.LOGS_DIR, "ssh"),
1555        "ssh_config_file": os.path.join(salt.syspaths.HOME_DIR, ".ssh", "config"),
1556        "cluster_mode": False,
1557        "sqlite_queue_dir": os.path.join(salt.syspaths.CACHE_DIR, "master", "queues"),
1558        "queue_dirs": [],
1559        "cli_summary": False,
1560        "max_minions": 0,
1561        "master_sign_key_name": "master_sign",
1562        "master_sign_pubkey": False,
1563        "master_pubkey_signature": "master_pubkey_signature",
1564        "master_use_pubkey_signature": False,
1565        "zmq_filtering": False,
1566        "zmq_monitor": False,
1567        "con_cache": False,
1568        "rotate_aes_key": True,
1569        "cache_sreqs": True,
1570        "dummy_pub": False,
1571        "http_connect_timeout": 20.0,  # tornado default - 20 seconds
1572        "http_request_timeout": 1 * 60 * 60.0,  # 1 hour
1573        "http_max_body": 100 * 1024 * 1024 * 1024,  # 100GB
1574        "cache": "localfs",
1575        "memcache_expire_seconds": 0,
1576        "memcache_max_items": 1024,
1577        "memcache_full_cleanup": False,
1578        "memcache_debug": False,
1579        "thin_extra_mods": "",
1580        "min_extra_mods": "",
1581        "ssl": None,
1582        "extmod_whitelist": {},
1583        "extmod_blacklist": {},
1584        "clean_dynamic_modules": True,
1585        "django_auth_path": "",
1586        "django_auth_settings": "",
1587        "allow_minion_key_revoke": True,
1588        "salt_cp_chunk_size": 98304,
1589        "require_minion_sign_messages": False,
1590        "drop_messages_signature_fail": False,
1591        "discovery": False,
1592        "schedule": {},
1593        "auth_events": True,
1594        "minion_data_cache_events": True,
1595        "enable_ssh_minions": False,
1596        "netapi_allow_raw_shell": False,
1597        "fips_mode": False,
1598        "detect_remote_minions": False,
1599        "remote_minions_port": 22,
1600    }
1601)
1602
1603
1604# ----- Salt Proxy Minion Configuration Defaults ----------------------------------->
1605# These are merged with DEFAULT_MINION_OPTS since many of them also apply here.
1606DEFAULT_PROXY_MINION_OPTS = immutabletypes.freeze(
1607    {
1608        "conf_file": os.path.join(salt.syspaths.CONFIG_DIR, "proxy"),
1609        "log_file": os.path.join(salt.syspaths.LOGS_DIR, "proxy"),
1610        "add_proxymodule_to_opts": False,
1611        "proxy_merge_grains_in_module": True,
1612        "extension_modules": os.path.join(salt.syspaths.CACHE_DIR, "proxy", "extmods"),
1613        "append_minionid_config_dirs": [
1614            "cachedir",
1615            "pidfile",
1616            "default_include",
1617            "extension_modules",
1618        ],
1619        "default_include": "proxy.d/*.conf",
1620        "proxy_merge_pillar_in_opts": False,
1621        "proxy_deep_merge_pillar_in_opts": False,
1622        "proxy_merge_pillar_in_opts_strategy": "smart",
1623        "proxy_mines_pillar": True,
1624        # By default, proxies will preserve the connection.
1625        # If this option is set to False,
1626        # the connection with the remote dumb device
1627        # is closed after each command request.
1628        "proxy_always_alive": True,
1629        "proxy_keep_alive": True,  # by default will try to keep alive the connection
1630        "proxy_keep_alive_interval": 1,  # frequency of the proxy keepalive in minutes
1631        "pki_dir": os.path.join(salt.syspaths.CONFIG_DIR, "pki", "proxy"),
1632        "cachedir": os.path.join(salt.syspaths.CACHE_DIR, "proxy"),
1633        "sock_dir": os.path.join(salt.syspaths.SOCK_DIR, "proxy"),
1634    }
1635)
1636
1637# ----- Salt Cloud Configuration Defaults ----------------------------------->
1638DEFAULT_CLOUD_OPTS = immutabletypes.freeze(
1639    {
1640        "verify_env": True,
1641        "default_include": "cloud.conf.d/*.conf",
1642        # Global defaults
1643        "ssh_auth": "",
1644        "cachedir": os.path.join(salt.syspaths.CACHE_DIR, "cloud"),
1645        "keysize": 4096,
1646        "os": "",
1647        "script": "bootstrap-salt",
1648        "start_action": None,
1649        "enable_hard_maps": False,
1650        "delete_sshkeys": False,
1651        # Custom deploy scripts
1652        "deploy_scripts_search_path": "cloud.deploy.d",
1653        # Logging defaults
1654        "log_file": os.path.join(salt.syspaths.LOGS_DIR, "cloud"),
1655        "log_level": "warning",
1656        "log_level_logfile": None,
1657        "log_datefmt": _DFLT_LOG_DATEFMT,
1658        "log_datefmt_logfile": _DFLT_LOG_DATEFMT_LOGFILE,
1659        "log_fmt_console": _DFLT_LOG_FMT_CONSOLE,
1660        "log_fmt_logfile": _DFLT_LOG_FMT_LOGFILE,
1661        "log_fmt_jid": _DFLT_LOG_FMT_JID,
1662        "log_granular_levels": {},
1663        "log_rotate_max_bytes": 0,
1664        "log_rotate_backup_count": 0,
1665        "bootstrap_delay": 0,
1666        "cache": "localfs",
1667    }
1668)
1669
1670DEFAULT_API_OPTS = immutabletypes.freeze(
1671    {
1672        # ----- Salt master settings overridden by Salt-API --------------------->
1673        "api_pidfile": os.path.join(salt.syspaths.PIDFILE_DIR, "salt-api.pid"),
1674        "api_logfile": os.path.join(salt.syspaths.LOGS_DIR, "api"),
1675        "rest_timeout": 300,
1676        # <---- Salt master settings overridden by Salt-API ----------------------
1677    }
1678)
1679
1680DEFAULT_SPM_OPTS = immutabletypes.freeze(
1681    {
1682        # ----- Salt master settings overridden by SPM --------------------->
1683        "spm_conf_file": os.path.join(salt.syspaths.CONFIG_DIR, "spm"),
1684        "formula_path": salt.syspaths.SPM_FORMULA_PATH,
1685        "pillar_path": salt.syspaths.SPM_PILLAR_PATH,
1686        "reactor_path": salt.syspaths.SPM_REACTOR_PATH,
1687        "spm_logfile": os.path.join(salt.syspaths.LOGS_DIR, "spm"),
1688        "spm_default_include": "spm.d/*.conf",
1689        # spm_repos_config also includes a .d/ directory
1690        "spm_repos_config": "/etc/salt/spm.repos",
1691        "spm_cache_dir": os.path.join(salt.syspaths.CACHE_DIR, "spm"),
1692        "spm_build_dir": os.path.join(salt.syspaths.SRV_ROOT_DIR, "spm_build"),
1693        "spm_build_exclude": ["CVS", ".hg", ".git", ".svn"],
1694        "spm_db": os.path.join(salt.syspaths.CACHE_DIR, "spm", "packages.db"),
1695        "cache": "localfs",
1696        "spm_repo_dups": "ignore",
1697        # If set, spm_node_type will be either master or minion, but they should
1698        # NOT be a default
1699        "spm_node_type": "",
1700        "spm_share_dir": os.path.join(salt.syspaths.SHARE_DIR, "spm"),
1701        # <---- Salt master settings overridden by SPM ----------------------
1702    }
1703)
1704
1705VM_CONFIG_DEFAULTS = immutabletypes.freeze(
1706    {"default_include": "cloud.profiles.d/*.conf"}
1707)
1708
1709PROVIDER_CONFIG_DEFAULTS = immutabletypes.freeze(
1710    {"default_include": "cloud.providers.d/*.conf"}
1711)
1712# <---- Salt Cloud Configuration Defaults ------------------------------------
1713
1714
1715def _normalize_roots(file_roots):
1716    """
1717    Normalize file or pillar roots.
1718    """
1719    for saltenv, dirs in file_roots.items():
1720        normalized_saltenv = str(saltenv)
1721        if normalized_saltenv != saltenv:
1722            file_roots[normalized_saltenv] = file_roots.pop(saltenv)
1723        if not isinstance(dirs, (list, tuple)):
1724            file_roots[normalized_saltenv] = []
1725        file_roots[normalized_saltenv] = _expand_glob_path(
1726            file_roots[normalized_saltenv]
1727        )
1728    return file_roots
1729
1730
1731def _validate_pillar_roots(pillar_roots):
1732    """
1733    If the pillar_roots option has a key that is None then we will error out,
1734    just replace it with an empty list
1735    """
1736    if not isinstance(pillar_roots, dict):
1737        log.warning(
1738            "The pillar_roots parameter is not properly formatted, using defaults"
1739        )
1740        return {"base": _expand_glob_path([salt.syspaths.BASE_PILLAR_ROOTS_DIR])}
1741    return _normalize_roots(pillar_roots)
1742
1743
1744def _validate_file_roots(file_roots):
1745    """
1746    If the file_roots option has a key that is None then we will error out,
1747    just replace it with an empty list
1748    """
1749    if not isinstance(file_roots, dict):
1750        log.warning(
1751            "The file_roots parameter is not properly formatted, using defaults"
1752        )
1753        return {"base": _expand_glob_path([salt.syspaths.BASE_FILE_ROOTS_DIR])}
1754    return _normalize_roots(file_roots)
1755
1756
1757def _expand_glob_path(file_roots):
1758    """
1759    Applies shell globbing to a set of directories and returns
1760    the expanded paths
1761    """
1762    unglobbed_path = []
1763    for path in file_roots:
1764        try:
1765            if glob.has_magic(path):
1766                unglobbed_path.extend(glob.glob(path))
1767            else:
1768                unglobbed_path.append(path)
1769        except Exception:  # pylint: disable=broad-except
1770            unglobbed_path.append(path)
1771    return unglobbed_path
1772
1773
1774def _validate_opts(opts):
1775    """
1776    Check that all of the types of values passed into the config are
1777    of the right types
1778    """
1779
1780    def format_multi_opt(valid_type):
1781        try:
1782            num_types = len(valid_type)
1783        except TypeError:
1784            # Bare type name won't have a length, return the name of the type
1785            # passed.
1786            return valid_type.__name__
1787        else:
1788
1789            def get_types(types, type_tuple):
1790                for item in type_tuple:
1791                    if isinstance(item, tuple):
1792                        get_types(types, item)
1793                    else:
1794                        try:
1795                            types.append(item.__name__)
1796                        except AttributeError:
1797                            log.warning(
1798                                "Unable to interpret type %s while validating "
1799                                "configuration",
1800                                item,
1801                            )
1802
1803            types = []
1804            get_types(types, valid_type)
1805
1806            ret = ", ".join(types[:-1])
1807            ret += " or " + types[-1]
1808            return ret
1809
1810    errors = []
1811
1812    err = (
1813        "Config option '{}' with value {} has an invalid type of {}, a "
1814        "{} is required for this option"
1815    )
1816    for key, val in opts.items():
1817        if key in VALID_OPTS:
1818            if val is None:
1819                if VALID_OPTS[key] is None:
1820                    continue
1821                else:
1822                    try:
1823                        if None in VALID_OPTS[key]:
1824                            continue
1825                    except TypeError:
1826                        # VALID_OPTS[key] is not iterable and not None
1827                        pass
1828
1829            if isinstance(val, VALID_OPTS[key]):
1830                continue
1831
1832            # We don't know what data type sdb will return at run-time so we
1833            # simply cannot check it for correctness here at start-time.
1834            if isinstance(val, str) and val.startswith("sdb://"):
1835                continue
1836
1837            if hasattr(VALID_OPTS[key], "__call__"):
1838                try:
1839                    VALID_OPTS[key](val)
1840                    if isinstance(val, (list, dict)):
1841                        # We'll only get here if VALID_OPTS[key] is str or
1842                        # bool, and the passed value is a list/dict. Attempting
1843                        # to run int() or float() on a list/dict will raise an
1844                        # exception, but running str() or bool() on it will
1845                        # pass despite not being the correct type.
1846                        errors.append(
1847                            err.format(
1848                                key, val, type(val).__name__, VALID_OPTS[key].__name__
1849                            )
1850                        )
1851                except (TypeError, ValueError):
1852                    errors.append(
1853                        err.format(
1854                            key, val, type(val).__name__, VALID_OPTS[key].__name__
1855                        )
1856                    )
1857                continue
1858
1859            errors.append(
1860                err.format(
1861                    key, val, type(val).__name__, format_multi_opt(VALID_OPTS[key])
1862                )
1863            )
1864
1865    # Convert list to comma-delimited string for 'return' config option
1866    if isinstance(opts.get("return"), list):
1867        opts["return"] = ",".join(opts["return"])
1868
1869    for error in errors:
1870        log.warning(error)
1871    if errors:
1872        return False
1873    return True
1874
1875
1876def _validate_ssh_minion_opts(opts):
1877    """
1878    Ensure we're not using any invalid ssh_minion_opts. We want to make sure
1879    that the ssh_minion_opts does not override any pillar or fileserver options
1880    inherited from the master config. To add other items, modify the if
1881    statement in the for loop below.
1882    """
1883    ssh_minion_opts = opts.get("ssh_minion_opts", {})
1884    if not isinstance(ssh_minion_opts, dict):
1885        log.error("Invalidly-formatted ssh_minion_opts")
1886        opts.pop("ssh_minion_opts")
1887
1888    for opt_name in list(ssh_minion_opts):
1889        if (
1890            re.match("^[a-z0-9]+fs_", opt_name, flags=re.IGNORECASE)
1891            or ("pillar" in opt_name and not "ssh_merge_pillar" == opt_name)
1892            or opt_name in ("fileserver_backend",)
1893        ):
1894            log.warning(
1895                "'%s' is not a valid ssh_minion_opts parameter, ignoring", opt_name
1896            )
1897            ssh_minion_opts.pop(opt_name)
1898
1899
1900def _append_domain(opts):
1901    """
1902    Append a domain to the existing id if it doesn't already exist
1903    """
1904    # Domain already exists
1905    if opts["id"].endswith(opts["append_domain"]):
1906        return opts["id"]
1907    # Trailing dot should mean an FQDN that is terminated, leave it alone.
1908    if opts["id"].endswith("."):
1909        return opts["id"]
1910    return "{0[id]}.{0[append_domain]}".format(opts)
1911
1912
1913def _read_conf_file(path):
1914    """
1915    Read in a config file from a given path and process it into a dictionary
1916    """
1917    log.debug("Reading configuration from %s", path)
1918    append_file_suffix_YAMLError = False
1919    with salt.utils.files.fopen(path, "r") as conf_file:
1920        try:
1921            conf_opts = salt.utils.yaml.safe_load(conf_file) or {}
1922        except salt.utils.yaml.YAMLError as err:
1923            message = "Error parsing configuration file: {} - {}".format(path, err)
1924            log.error(message)
1925            if path.endswith("_schedule.conf"):
1926                # Create empty dictionary of config options
1927                conf_opts = {}
1928                # Rename this file, once closed
1929                append_file_suffix_YAMLError = True
1930            else:
1931                raise salt.exceptions.SaltConfigurationError(message)
1932
1933    if append_file_suffix_YAMLError:
1934        message = "Renaming to {}".format(path + "YAMLError")
1935        log.error(message)
1936        os.replace(path, path + "YAMLError")
1937
1938    # only interpret documents as a valid conf, not things like strings,
1939    # which might have been caused by invalid yaml syntax
1940    if not isinstance(conf_opts, dict):
1941        message = (
1942            "Error parsing configuration file: {} - conf "
1943            "should be a document, not {}.".format(path, type(conf_opts))
1944        )
1945        log.error(message)
1946        raise salt.exceptions.SaltConfigurationError(message)
1947
1948    # allow using numeric ids: convert int to string
1949    if "id" in conf_opts:
1950        if not isinstance(conf_opts["id"], str):
1951            conf_opts["id"] = str(conf_opts["id"])
1952        else:
1953            conf_opts["id"] = salt.utils.data.decode(conf_opts["id"])
1954    return conf_opts
1955
1956
1957def _absolute_path(path, relative_to=None):
1958    """
1959    Return an absolute path. In case ``relative_to`` is passed and ``path`` is
1960    not an absolute path, we try to prepend ``relative_to`` to ``path``and if
1961    that path exists, return that one
1962    """
1963
1964    if path and os.path.isabs(path):
1965        return path
1966    if path and relative_to is not None:
1967        _abspath = os.path.join(relative_to, path)
1968        if os.path.isfile(_abspath):
1969            log.debug(
1970                "Relative path '%s' converted to existing absolute path '%s'",
1971                path,
1972                _abspath,
1973            )
1974            return _abspath
1975    return path
1976
1977
1978def load_config(path, env_var, default_path=None, exit_on_config_errors=True):
1979    """
1980    Returns configuration dict from parsing either the file described by
1981    ``path`` or the environment variable described by ``env_var`` as YAML.
1982    """
1983    if path is None:
1984        # When the passed path is None, we just want the configuration
1985        # defaults, not actually loading the whole configuration.
1986        return {}
1987
1988    if default_path is None:
1989        # This is most likely not being used from salt, i.e., could be salt-cloud
1990        # or salt-api which have not yet migrated to the new default_path
1991        # argument. Let's issue a warning message that the environ vars won't
1992        # work.
1993        import inspect
1994
1995        previous_frame = inspect.getframeinfo(inspect.currentframe().f_back)
1996        log.warning(
1997            "The function '%s()' defined in '%s' is not yet using the "
1998            "new 'default_path' argument to `salt.config.load_config()`. "
1999            "As such, the '%s' environment variable will be ignored",
2000            previous_frame.function,
2001            previous_frame.filename,
2002            env_var,
2003        )
2004        # In this case, maintain old behavior
2005        default_path = DEFAULT_MASTER_OPTS["conf_file"]
2006
2007    # Default to the environment variable path, if it exists
2008    env_path = os.environ.get(env_var, path)
2009    if not env_path or not os.path.isfile(env_path):
2010        env_path = path
2011    # If non-default path from `-c`, use that over the env variable
2012    if path != default_path:
2013        env_path = path
2014
2015    path = env_path
2016
2017    # If the configuration file is missing, attempt to copy the template,
2018    # after removing the first header line.
2019    if not os.path.isfile(path):
2020        template = "{}.template".format(path)
2021        if os.path.isfile(template):
2022            log.debug("Writing %s based on %s", path, template)
2023            with salt.utils.files.fopen(path, "w") as out:
2024                with salt.utils.files.fopen(template, "r") as ifile:
2025                    ifile.readline()  # skip first line
2026                    out.write(ifile.read())
2027
2028    opts = {}
2029
2030    if salt.utils.validate.path.is_readable(path):
2031        try:
2032            opts = _read_conf_file(path)
2033            opts["conf_file"] = path
2034        except salt.exceptions.SaltConfigurationError as error:
2035            log.error(error)
2036            if exit_on_config_errors:
2037                sys.exit(salt.defaults.exitcodes.EX_GENERIC)
2038    else:
2039        log.debug("Missing configuration file: %s", path)
2040
2041    return opts
2042
2043
2044def include_config(include, orig_path, verbose, exit_on_config_errors=False):
2045    """
2046    Parses extra configuration file(s) specified in an include list in the
2047    main config file.
2048    """
2049    # Protect against empty option
2050    if not include:
2051        return {}
2052
2053    if orig_path is None:
2054        # When the passed path is None, we just want the configuration
2055        # defaults, not actually loading the whole configuration.
2056        return {}
2057
2058    if isinstance(include, str):
2059        include = [include]
2060
2061    configuration = {}
2062    for path in include:
2063        # Allow for includes like ~/foo
2064        path = os.path.expanduser(path)
2065        if not os.path.isabs(path):
2066            path = os.path.join(os.path.dirname(orig_path), path)
2067
2068        # Catch situation where user typos path in configuration; also warns
2069        # for empty include directory (which might be by design)
2070        glob_matches = glob.glob(path)
2071        if not glob_matches:
2072            if verbose:
2073                log.warning(
2074                    'Warning parsing configuration file: "include" path/glob '
2075                    "'%s' matches no files",
2076                    path,
2077                )
2078
2079        for fn_ in sorted(glob_matches):
2080            log.debug("Including configuration from '%s'", fn_)
2081            try:
2082                opts = _read_conf_file(fn_)
2083            except salt.exceptions.SaltConfigurationError as error:
2084                log.error(error)
2085                if exit_on_config_errors:
2086                    sys.exit(salt.defaults.exitcodes.EX_GENERIC)
2087                else:
2088                    # Initialize default config if we wish to skip config errors
2089                    opts = {}
2090            schedule = opts.get("schedule", {})
2091            if schedule and "schedule" in configuration:
2092                configuration["schedule"].update(schedule)
2093            include = opts.get("include", [])
2094            if include:
2095                opts.update(include_config(include, fn_, verbose))
2096
2097            salt.utils.dictupdate.update(configuration, opts, True, True)
2098
2099    return configuration
2100
2101
2102def prepend_root_dir(opts, path_options):
2103    """
2104    Prepends the options that represent filesystem paths with value of the
2105    'root_dir' option.
2106    """
2107    root_dir = os.path.abspath(opts["root_dir"])
2108    def_root_dir = salt.syspaths.ROOT_DIR.rstrip(os.sep)
2109    for path_option in path_options:
2110        if path_option in opts:
2111            path = opts[path_option]
2112            tmp_path_def_root_dir = None
2113            tmp_path_root_dir = None
2114            # When running testsuite, salt.syspaths.ROOT_DIR is often empty
2115            if path == def_root_dir or path.startswith(def_root_dir + os.sep):
2116                # Remove the default root dir prefix
2117                tmp_path_def_root_dir = path[len(def_root_dir) :]
2118            if root_dir and (path == root_dir or path.startswith(root_dir + os.sep)):
2119                # Remove the root dir prefix
2120                tmp_path_root_dir = path[len(root_dir) :]
2121            if tmp_path_def_root_dir and not tmp_path_root_dir:
2122                # Just the default root dir matched
2123                path = tmp_path_def_root_dir
2124            elif tmp_path_root_dir and not tmp_path_def_root_dir:
2125                # Just the root dir matched
2126                path = tmp_path_root_dir
2127            elif tmp_path_def_root_dir and tmp_path_root_dir:
2128                # In this case both the default root dir and the override root
2129                # dir matched; this means that either
2130                # def_root_dir is a substring of root_dir or vice versa
2131                # We must choose the most specific path
2132                if def_root_dir in root_dir:
2133                    path = tmp_path_root_dir
2134                else:
2135                    path = tmp_path_def_root_dir
2136            elif salt.utils.platform.is_windows() and not os.path.splitdrive(path)[0]:
2137                # In windows, os.path.isabs resolves '/' to 'C:\\' or whatever
2138                # the root drive is.  This elif prevents the next from being
2139                # hit, so that the root_dir is prefixed in cases where the
2140                # drive is not prefixed on a config option
2141                pass
2142            elif os.path.isabs(path):
2143                # Absolute path (not default or overridden root_dir)
2144                # No prepending required
2145                continue
2146            # Prepending the root dir
2147            opts[path_option] = salt.utils.path.join(root_dir, path)
2148
2149
2150def insert_system_path(opts, paths):
2151    """
2152    Inserts path into python path taking into consideration 'root_dir' option.
2153    """
2154    if isinstance(paths, str):
2155        paths = [paths]
2156    for path in paths:
2157        path_options = {"path": path, "root_dir": opts["root_dir"]}
2158        prepend_root_dir(path_options, path_options)
2159        if os.path.isdir(path_options["path"]) and path_options["path"] not in sys.path:
2160            sys.path.insert(0, path_options["path"])
2161
2162
2163def minion_config(
2164    path,
2165    env_var="SALT_MINION_CONFIG",
2166    defaults=None,
2167    cache_minion_id=False,
2168    ignore_config_errors=True,
2169    minion_id=None,
2170    role="minion",
2171):
2172    """
2173    Reads in the minion configuration file and sets up special options
2174
2175    This is useful for Minion-side operations, such as the
2176    :py:class:`~salt.client.Caller` class, and manually running the loader
2177    interface.
2178
2179    .. code-block:: python
2180
2181        import salt.config
2182        minion_opts = salt.config.minion_config('/etc/salt/minion')
2183    """
2184    if defaults is None:
2185        defaults = DEFAULT_MINION_OPTS.copy()
2186
2187    if not os.environ.get(env_var, None):
2188        # No valid setting was given using the configuration variable.
2189        # Lets see is SALT_CONFIG_DIR is of any use
2190        salt_config_dir = os.environ.get("SALT_CONFIG_DIR", None)
2191        if salt_config_dir:
2192            env_config_file_path = os.path.join(salt_config_dir, "minion")
2193            if salt_config_dir and os.path.isfile(env_config_file_path):
2194                # We can get a configuration file using SALT_CONFIG_DIR, let's
2195                # update the environment with this information
2196                os.environ[env_var] = env_config_file_path
2197
2198    overrides = load_config(path, env_var, DEFAULT_MINION_OPTS["conf_file"])
2199    default_include = overrides.get("default_include", defaults["default_include"])
2200    include = overrides.get("include", [])
2201
2202    overrides.update(
2203        include_config(
2204            default_include,
2205            path,
2206            verbose=False,
2207            exit_on_config_errors=not ignore_config_errors,
2208        )
2209    )
2210    overrides.update(
2211        include_config(
2212            include, path, verbose=True, exit_on_config_errors=not ignore_config_errors
2213        )
2214    )
2215
2216    opts = apply_minion_config(
2217        overrides, defaults, cache_minion_id=cache_minion_id, minion_id=minion_id
2218    )
2219    opts["__role"] = role
2220    if role != "master":
2221        apply_sdb(opts)
2222        _validate_opts(opts)
2223    return opts
2224
2225
2226def mminion_config(path, overrides, ignore_config_errors=True):
2227    opts = minion_config(path, ignore_config_errors=ignore_config_errors, role="master")
2228    opts.update(overrides)
2229    apply_sdb(opts)
2230
2231    _validate_opts(opts)
2232    opts["grains"] = salt.loader.grains(opts)
2233    opts["pillar"] = {}
2234    return opts
2235
2236
2237def proxy_config(
2238    path,
2239    env_var="SALT_PROXY_CONFIG",
2240    defaults=None,
2241    cache_minion_id=False,
2242    ignore_config_errors=True,
2243    minion_id=None,
2244):
2245    """
2246    Reads in the proxy minion configuration file and sets up special options
2247
2248    This is useful for Minion-side operations, such as the
2249    :py:class:`~salt.client.Caller` class, and manually running the loader
2250    interface.
2251
2252    .. code-block:: python
2253
2254        import salt.config
2255        proxy_opts = salt.config.proxy_config('/etc/salt/proxy')
2256    """
2257    if defaults is None:
2258        defaults = DEFAULT_MINION_OPTS.copy()
2259
2260    defaults.update(DEFAULT_PROXY_MINION_OPTS)
2261
2262    if not os.environ.get(env_var, None):
2263        # No valid setting was given using the configuration variable.
2264        # Lets see is SALT_CONFIG_DIR is of any use
2265        salt_config_dir = os.environ.get("SALT_CONFIG_DIR", None)
2266        if salt_config_dir:
2267            env_config_file_path = os.path.join(salt_config_dir, "proxy")
2268            if salt_config_dir and os.path.isfile(env_config_file_path):
2269                # We can get a configuration file using SALT_CONFIG_DIR, let's
2270                # update the environment with this information
2271                os.environ[env_var] = env_config_file_path
2272
2273    overrides = load_config(path, env_var, DEFAULT_PROXY_MINION_OPTS["conf_file"])
2274    default_include = overrides.get("default_include", defaults["default_include"])
2275    include = overrides.get("include", [])
2276
2277    overrides.update(
2278        include_config(
2279            default_include,
2280            path,
2281            verbose=False,
2282            exit_on_config_errors=not ignore_config_errors,
2283        )
2284    )
2285    overrides.update(
2286        include_config(
2287            include, path, verbose=True, exit_on_config_errors=not ignore_config_errors
2288        )
2289    )
2290
2291    opts = apply_minion_config(
2292        overrides, defaults, cache_minion_id=cache_minion_id, minion_id=minion_id
2293    )
2294
2295    # Update opts with proxy specific configuration
2296    # with the updated default_include.
2297    default_include = opts.get("default_include", defaults["default_include"])
2298    include = opts.get("include", [])
2299
2300    overrides.update(
2301        include_config(
2302            default_include,
2303            path,
2304            verbose=False,
2305            exit_on_config_errors=not ignore_config_errors,
2306        )
2307    )
2308    overrides.update(
2309        include_config(
2310            include, path, verbose=True, exit_on_config_errors=not ignore_config_errors
2311        )
2312    )
2313
2314    opts = apply_minion_config(
2315        overrides, defaults, cache_minion_id=cache_minion_id, minion_id=minion_id
2316    )
2317
2318    apply_sdb(opts)
2319    _validate_opts(opts)
2320    return opts
2321
2322
2323def syndic_config(
2324    master_config_path,
2325    minion_config_path,
2326    master_env_var="SALT_MASTER_CONFIG",
2327    minion_env_var="SALT_MINION_CONFIG",
2328    minion_defaults=None,
2329    master_defaults=None,
2330):
2331
2332    if minion_defaults is None:
2333        minion_defaults = DEFAULT_MINION_OPTS.copy()
2334
2335    if master_defaults is None:
2336        master_defaults = DEFAULT_MASTER_OPTS.copy()
2337
2338    opts = {}
2339    master_opts = master_config(master_config_path, master_env_var, master_defaults)
2340    minion_opts = minion_config(minion_config_path, minion_env_var, minion_defaults)
2341    opts["_minion_conf_file"] = master_opts["conf_file"]
2342    opts["_master_conf_file"] = minion_opts["conf_file"]
2343    opts.update(master_opts)
2344    opts.update(minion_opts)
2345    syndic_opts = {
2346        "__role": "syndic",
2347        "root_dir": opts.get("root_dir", salt.syspaths.ROOT_DIR),
2348        "pidfile": opts.get("syndic_pidfile", "salt-syndic.pid"),
2349        "log_file": opts.get("syndic_log_file", "salt-syndic.log"),
2350        "log_level": master_opts["log_level"],
2351        "id": minion_opts["id"],
2352        "pki_dir": minion_opts["pki_dir"],
2353        "master": opts["syndic_master"],
2354        "interface": master_opts["interface"],
2355        "master_port": int(
2356            opts.get(
2357                # The user has explicitly defined the syndic master port
2358                "syndic_master_port",
2359                opts.get(
2360                    # No syndic_master_port, grab master_port from opts
2361                    "master_port",
2362                    # No master_opts, grab from the provided minion defaults
2363                    minion_defaults.get(
2364                        "master_port",
2365                        # Not on the provided minion defaults, load from the
2366                        # static minion defaults
2367                        DEFAULT_MINION_OPTS["master_port"],
2368                    ),
2369                ),
2370            )
2371        ),
2372        "user": opts.get("syndic_user", opts["user"]),
2373        "sock_dir": os.path.join(
2374            opts["cachedir"], opts.get("syndic_sock_dir", opts["sock_dir"])
2375        ),
2376        "sock_pool_size": master_opts["sock_pool_size"],
2377        "cachedir": master_opts["cachedir"],
2378    }
2379    opts.update(syndic_opts)
2380    # Prepend root_dir to other paths
2381    prepend_root_dirs = [
2382        "pki_dir",
2383        "cachedir",
2384        "pidfile",
2385        "sock_dir",
2386        "extension_modules",
2387        "autosign_file",
2388        "autoreject_file",
2389        "token_dir",
2390        "autosign_grains_dir",
2391    ]
2392    for config_key in ("log_file", "key_logfile", "syndic_log_file"):
2393        # If this is not a URI and instead a local path
2394        if urllib.parse.urlparse(opts.get(config_key, "")).scheme == "":
2395            prepend_root_dirs.append(config_key)
2396    prepend_root_dir(opts, prepend_root_dirs)
2397    return opts
2398
2399
2400def apply_sdb(opts, sdb_opts=None):
2401    """
2402    Recurse for sdb:// links for opts
2403    """
2404    # Late load of SDB to keep CLI light
2405    import salt.utils.sdb
2406
2407    if sdb_opts is None:
2408        sdb_opts = opts
2409    if isinstance(sdb_opts, str) and sdb_opts.startswith("sdb://"):
2410        return salt.utils.sdb.sdb_get(sdb_opts, opts)
2411    elif isinstance(sdb_opts, dict):
2412        for key, value in sdb_opts.items():
2413            if value is None:
2414                continue
2415            sdb_opts[key] = apply_sdb(opts, value)
2416    elif isinstance(sdb_opts, list):
2417        for key, value in enumerate(sdb_opts):
2418            if value is None:
2419                continue
2420            sdb_opts[key] = apply_sdb(opts, value)
2421
2422    return sdb_opts
2423
2424
2425# ----- Salt Cloud Configuration Functions ---------------------------------->
2426def cloud_config(
2427    path,
2428    env_var="SALT_CLOUD_CONFIG",
2429    defaults=None,
2430    master_config_path=None,
2431    master_config=None,
2432    providers_config_path=None,
2433    providers_config=None,
2434    profiles_config_path=None,
2435    profiles_config=None,
2436):
2437    """
2438    Read in the Salt Cloud config and return the dict
2439    """
2440    if path:
2441        config_dir = os.path.dirname(path)
2442    else:
2443        config_dir = salt.syspaths.CONFIG_DIR
2444
2445    # Load the cloud configuration
2446    overrides = load_config(path, env_var, os.path.join(config_dir, "cloud"))
2447
2448    if defaults is None:
2449        defaults = DEFAULT_CLOUD_OPTS.copy()
2450
2451    # Set defaults early to override Salt Master's default config values later
2452    defaults.update(overrides)
2453    overrides = defaults
2454
2455    # Load cloud configuration from any default or provided includes
2456    overrides.update(
2457        salt.config.include_config(overrides["default_include"], path, verbose=False)
2458    )
2459    include = overrides.get("include", [])
2460    overrides.update(salt.config.include_config(include, path, verbose=True))
2461
2462    # The includes have been evaluated, let's see if master, providers and
2463    # profiles configuration settings have been included and if not, set the
2464    # default value
2465    if "master_config" in overrides and master_config_path is None:
2466        # The configuration setting is being specified in the main cloud
2467        # configuration file
2468        master_config_path = overrides["master_config"]
2469    elif (
2470        "master_config" not in overrides
2471        and not master_config
2472        and not master_config_path
2473    ):
2474        # The configuration setting is not being provided in the main cloud
2475        # configuration file, and
2476        master_config_path = os.path.join(config_dir, "master")
2477
2478    # Convert relative to absolute paths if necessary
2479    master_config_path = _absolute_path(master_config_path, config_dir)
2480
2481    if "providers_config" in overrides and providers_config_path is None:
2482        # The configuration setting is being specified in the main cloud
2483        # configuration file
2484        providers_config_path = overrides["providers_config"]
2485    elif (
2486        "providers_config" not in overrides
2487        and not providers_config
2488        and not providers_config_path
2489    ):
2490        providers_config_path = os.path.join(config_dir, "cloud.providers")
2491
2492    # Convert relative to absolute paths if necessary
2493    providers_config_path = _absolute_path(providers_config_path, config_dir)
2494
2495    if "profiles_config" in overrides and profiles_config_path is None:
2496        # The configuration setting is being specified in the main cloud
2497        # configuration file
2498        profiles_config_path = overrides["profiles_config"]
2499    elif (
2500        "profiles_config" not in overrides
2501        and not profiles_config
2502        and not profiles_config_path
2503    ):
2504        profiles_config_path = os.path.join(config_dir, "cloud.profiles")
2505
2506    # Convert relative to absolute paths if necessary
2507    profiles_config_path = _absolute_path(profiles_config_path, config_dir)
2508
2509    # Prepare the deploy scripts search path
2510    deploy_scripts_search_path = overrides.get(
2511        "deploy_scripts_search_path",
2512        defaults.get("deploy_scripts_search_path", "cloud.deploy.d"),
2513    )
2514    if isinstance(deploy_scripts_search_path, str):
2515        deploy_scripts_search_path = [deploy_scripts_search_path]
2516
2517    # Check the provided deploy scripts search path removing any non existing
2518    # entries.
2519    for idx, entry in enumerate(deploy_scripts_search_path[:]):
2520        if not os.path.isabs(entry):
2521            # Let's try adding the provided path's directory name turns the
2522            # entry into a proper directory
2523            entry = os.path.join(os.path.dirname(path), entry)
2524
2525        if os.path.isdir(entry):
2526            # Path exists, let's update the entry (its path might have been
2527            # made absolute)
2528            deploy_scripts_search_path[idx] = entry
2529            continue
2530
2531        # It's not a directory? Remove it from the search path
2532        deploy_scripts_search_path.pop(idx)
2533
2534    # Add the built-in scripts directory to the search path (last resort)
2535    deploy_scripts_search_path.append(
2536        os.path.abspath(
2537            os.path.join(os.path.dirname(__file__), "..", "cloud", "deploy")
2538        )
2539    )
2540
2541    # Let's make the search path a tuple and add it to the overrides.
2542    overrides.update(deploy_scripts_search_path=tuple(deploy_scripts_search_path))
2543
2544    # Grab data from the 4 sources
2545    # 1st - Master config
2546    if master_config_path is not None and master_config is not None:
2547        raise salt.exceptions.SaltCloudConfigError(
2548            "Only pass `master_config` or `master_config_path`, not both."
2549        )
2550    elif master_config_path is None and master_config is None:
2551        master_config = salt.config.master_config(
2552            overrides.get(
2553                # use the value from the cloud config file
2554                "master_config",
2555                # if not found, use the default path
2556                os.path.join(salt.syspaths.CONFIG_DIR, "master"),
2557            )
2558        )
2559    elif master_config_path is not None and master_config is None:
2560        master_config = salt.config.master_config(master_config_path)
2561
2562    # cloud config has a separate cachedir
2563    del master_config["cachedir"]
2564
2565    # 2nd - salt-cloud configuration which was loaded before so we could
2566    # extract the master configuration file if needed.
2567
2568    # Override master configuration with the salt cloud(current overrides)
2569    master_config.update(overrides)
2570    # We now set the overridden master_config as the overrides
2571    overrides = master_config
2572
2573    if providers_config_path is not None and providers_config is not None:
2574        raise salt.exceptions.SaltCloudConfigError(
2575            "Only pass `providers_config` or `providers_config_path`, not both."
2576        )
2577    elif providers_config_path is None and providers_config is None:
2578        providers_config_path = overrides.get(
2579            # use the value from the cloud config file
2580            "providers_config",
2581            # if not found, use the default path
2582            os.path.join(salt.syspaths.CONFIG_DIR, "cloud.providers"),
2583        )
2584
2585    if profiles_config_path is not None and profiles_config is not None:
2586        raise salt.exceptions.SaltCloudConfigError(
2587            "Only pass `profiles_config` or `profiles_config_path`, not both."
2588        )
2589    elif profiles_config_path is None and profiles_config is None:
2590        profiles_config_path = overrides.get(
2591            # use the value from the cloud config file
2592            "profiles_config",
2593            # if not found, use the default path
2594            os.path.join(salt.syspaths.CONFIG_DIR, "cloud.profiles"),
2595        )
2596
2597    # Apply the salt-cloud configuration
2598    opts = apply_cloud_config(overrides, defaults)
2599
2600    # 3rd - Include Cloud Providers
2601    if "providers" in opts:
2602        if providers_config is not None:
2603            raise salt.exceptions.SaltCloudConfigError(
2604                "Do not mix the old cloud providers configuration with "
2605                "the passing a pre-configured providers configuration "
2606                "dictionary."
2607            )
2608
2609        if providers_config_path is not None:
2610            providers_confd = os.path.join(
2611                os.path.dirname(providers_config_path), "cloud.providers.d", "*"
2612            )
2613
2614            if os.path.isfile(providers_config_path) or glob.glob(providers_confd):
2615                raise salt.exceptions.SaltCloudConfigError(
2616                    "Do not mix the old cloud providers configuration with "
2617                    "the new one. The providers configuration should now go "
2618                    "in the file `{0}` or a separate `*.conf` file within "
2619                    "`cloud.providers.d/` which is relative to `{0}`.".format(
2620                        os.path.join(salt.syspaths.CONFIG_DIR, "cloud.providers")
2621                    )
2622                )
2623        # No exception was raised? It's the old configuration alone
2624        providers_config = opts["providers"]
2625
2626    elif providers_config_path is not None:
2627        # Load from configuration file, even if that files does not exist since
2628        # it will be populated with defaults.
2629        providers_config = cloud_providers_config(providers_config_path)
2630
2631    # Let's assign back the computed providers configuration
2632    opts["providers"] = providers_config
2633
2634    # 4th - Include VM profiles config
2635    if profiles_config is None:
2636        # Load profiles configuration from the provided file
2637        profiles_config = vm_profiles_config(profiles_config_path, providers_config)
2638    opts["profiles"] = profiles_config
2639
2640    # recurse opts for sdb configs
2641    apply_sdb(opts)
2642
2643    # prepend root_dir
2644    prepend_root_dirs = ["cachedir"]
2645    if "log_file" in opts and urllib.parse.urlparse(opts["log_file"]).scheme == "":
2646        prepend_root_dirs.append(opts["log_file"])
2647    prepend_root_dir(opts, prepend_root_dirs)
2648
2649    # Return the final options
2650    return opts
2651
2652
2653def apply_cloud_config(overrides, defaults=None):
2654    """
2655    Return a cloud config
2656    """
2657    if defaults is None:
2658        defaults = DEFAULT_CLOUD_OPTS.copy()
2659
2660    config = defaults.copy()
2661    if overrides:
2662        config.update(overrides)
2663
2664    # If the user defined providers in salt cloud's main configuration file, we
2665    # need to take care for proper and expected format.
2666    if "providers" in config:
2667        # Keep a copy of the defined providers
2668        providers = config["providers"].copy()
2669        # Reset the providers dictionary
2670        config["providers"] = {}
2671        # Populate the providers dictionary
2672        for alias, details in providers.items():
2673            if isinstance(details, list):
2674                for detail in details:
2675                    if "driver" not in detail:
2676                        raise salt.exceptions.SaltCloudConfigError(
2677                            "The cloud provider alias '{}' has an entry "
2678                            "missing the required setting of 'driver'.".format(alias)
2679                        )
2680
2681                    driver = detail["driver"]
2682
2683                    if ":" in driver:
2684                        # Weird, but...
2685                        alias, driver = driver.split(":")
2686
2687                    if alias not in config["providers"]:
2688                        config["providers"][alias] = {}
2689
2690                    detail["provider"] = "{}:{}".format(alias, driver)
2691                    config["providers"][alias][driver] = detail
2692            elif isinstance(details, dict):
2693                if "driver" not in details:
2694                    raise salt.exceptions.SaltCloudConfigError(
2695                        "The cloud provider alias '{}' has an entry "
2696                        "missing the required setting of 'driver'".format(alias)
2697                    )
2698
2699                driver = details["driver"]
2700
2701                if ":" in driver:
2702                    # Weird, but...
2703                    alias, driver = driver.split(":")
2704                if alias not in config["providers"]:
2705                    config["providers"][alias] = {}
2706
2707                details["provider"] = "{}:{}".format(alias, driver)
2708                config["providers"][alias][driver] = details
2709
2710    # Migrate old configuration
2711    config = old_to_new(config)
2712
2713    return config
2714
2715
2716def old_to_new(opts):
2717    providers = (
2718        "AWS",
2719        "CLOUDSTACK",
2720        "DIGITALOCEAN",
2721        "EC2",
2722        "GOGRID",
2723        "IBMSCE",
2724        "JOYENT",
2725        "LINODE",
2726        "OPENSTACK",
2727        "PARALLELS",
2728        "RACKSPACE",
2729        "SALTIFY",
2730    )
2731
2732    for provider in providers:
2733
2734        provider_config = {}
2735        for opt, val in opts.items():
2736            if provider in opt:
2737                value = val
2738                name = opt.split(".", 1)[1]
2739                provider_config[name] = value
2740
2741        lprovider = provider.lower()
2742        if provider_config:
2743            provider_config["provider"] = lprovider
2744            opts.setdefault("providers", {})
2745            # provider alias
2746            opts["providers"][lprovider] = {}
2747            # provider alias, provider driver
2748            opts["providers"][lprovider][lprovider] = provider_config
2749    return opts
2750
2751
2752def vm_profiles_config(path, providers, env_var="SALT_CLOUDVM_CONFIG", defaults=None):
2753    """
2754    Read in the salt cloud VM config file
2755    """
2756    if defaults is None:
2757        defaults = VM_CONFIG_DEFAULTS
2758
2759    overrides = salt.config.load_config(
2760        path, env_var, os.path.join(salt.syspaths.CONFIG_DIR, "cloud.profiles")
2761    )
2762
2763    default_include = overrides.get("default_include", defaults["default_include"])
2764    include = overrides.get("include", [])
2765
2766    overrides.update(salt.config.include_config(default_include, path, verbose=False))
2767    overrides.update(salt.config.include_config(include, path, verbose=True))
2768    return apply_vm_profiles_config(providers, overrides, defaults)
2769
2770
2771def apply_vm_profiles_config(providers, overrides, defaults=None):
2772    if defaults is None:
2773        defaults = VM_CONFIG_DEFAULTS
2774
2775    config = defaults.copy()
2776    if overrides:
2777        config.update(overrides)
2778
2779    vms = {}
2780
2781    for key, val in config.items():
2782        if key in ("conf_file", "include", "default_include", "user"):
2783            continue
2784        if not isinstance(val, dict):
2785            raise salt.exceptions.SaltCloudConfigError(
2786                "The VM profiles configuration found in '{0[conf_file]}' is "
2787                "not in the proper format".format(config)
2788            )
2789        val["profile"] = key
2790        vms[key] = val
2791
2792    # Is any VM profile extending data!?
2793    for profile, details in vms.copy().items():
2794        if "extends" not in details:
2795            if ":" in details["provider"]:
2796                alias, driver = details["provider"].split(":")
2797                if alias not in providers or driver not in providers[alias]:
2798                    log.trace(
2799                        "The profile '%s' is defining '%s' "
2800                        "as the provider. Since there is no valid "
2801                        "configuration for that provider, the profile will be "
2802                        "removed from the available listing",
2803                        profile,
2804                        details["provider"],
2805                    )
2806                    vms.pop(profile)
2807                    continue
2808
2809                if "profiles" not in providers[alias][driver]:
2810                    providers[alias][driver]["profiles"] = {}
2811                providers[alias][driver]["profiles"][profile] = details
2812
2813            if details["provider"] not in providers:
2814                log.trace(
2815                    "The profile '%s' is defining '%s' as the "
2816                    "provider. Since there is no valid configuration for "
2817                    "that provider, the profile will be removed from the "
2818                    "available listing",
2819                    profile,
2820                    details["provider"],
2821                )
2822                vms.pop(profile)
2823                continue
2824
2825            driver = next(iter(list(providers[details["provider"]].keys())))
2826            providers[details["provider"]][driver].setdefault("profiles", {}).update(
2827                {profile: details}
2828            )
2829            details["provider"] = "{0[provider]}:{1}".format(details, driver)
2830            vms[profile] = details
2831
2832            continue
2833
2834        extends = details.pop("extends")
2835        if extends not in vms:
2836            log.error(
2837                "The '%s' profile is trying to extend data from '%s' "
2838                "though '%s' is not defined in the salt profiles loaded "
2839                "data. Not extending and removing from listing!",
2840                profile,
2841                extends,
2842                extends,
2843            )
2844            vms.pop(profile)
2845            continue
2846
2847        extended = deepcopy(vms.get(extends))
2848        extended.pop("profile")
2849        # Merge extended configuration with base profile
2850        extended = salt.utils.dictupdate.update(extended, details)
2851
2852        if ":" not in extended["provider"]:
2853            if extended["provider"] not in providers:
2854                log.trace(
2855                    "The profile '%s' is defining '%s' as the "
2856                    "provider. Since there is no valid configuration for "
2857                    "that provider, the profile will be removed from the "
2858                    "available listing",
2859                    profile,
2860                    extended["provider"],
2861                )
2862                vms.pop(profile)
2863                continue
2864
2865            driver = next(iter(list(providers[extended["provider"]].keys())))
2866            providers[extended["provider"]][driver].setdefault("profiles", {}).update(
2867                {profile: extended}
2868            )
2869
2870            extended["provider"] = "{0[provider]}:{1}".format(extended, driver)
2871        else:
2872            alias, driver = extended["provider"].split(":")
2873            if alias not in providers or driver not in providers[alias]:
2874                log.trace(
2875                    "The profile '%s' is defining '%s' as "
2876                    "the provider. Since there is no valid configuration "
2877                    "for that provider, the profile will be removed from "
2878                    "the available listing",
2879                    profile,
2880                    extended["provider"],
2881                )
2882                vms.pop(profile)
2883                continue
2884
2885            providers[alias][driver].setdefault("profiles", {}).update(
2886                {profile: extended}
2887            )
2888
2889        # Update the profile's entry with the extended data
2890        vms[profile] = extended
2891
2892    return vms
2893
2894
2895def cloud_providers_config(path, env_var="SALT_CLOUD_PROVIDERS_CONFIG", defaults=None):
2896    """
2897    Read in the salt cloud providers configuration file
2898    """
2899    if defaults is None:
2900        defaults = PROVIDER_CONFIG_DEFAULTS
2901
2902    overrides = salt.config.load_config(
2903        path, env_var, os.path.join(salt.syspaths.CONFIG_DIR, "cloud.providers")
2904    )
2905
2906    default_include = overrides.get("default_include", defaults["default_include"])
2907    include = overrides.get("include", [])
2908
2909    overrides.update(salt.config.include_config(default_include, path, verbose=False))
2910    overrides.update(salt.config.include_config(include, path, verbose=True))
2911    return apply_cloud_providers_config(overrides, defaults)
2912
2913
2914def apply_cloud_providers_config(overrides, defaults=None):
2915    """
2916    Apply the loaded cloud providers configuration.
2917    """
2918    if defaults is None:
2919        defaults = PROVIDER_CONFIG_DEFAULTS
2920
2921    config = defaults.copy()
2922    if overrides:
2923        config.update(overrides)
2924
2925    # Is the user still using the old format in the new configuration file?!
2926    for name, settings in config.copy().items():
2927        if "." in name:
2928            log.warning("Please switch to the new providers configuration syntax")
2929
2930            # Let's help out and migrate the data
2931            config = old_to_new(config)
2932
2933            # old_to_new will migrate the old data into the 'providers' key of
2934            # the config dictionary. Let's map it correctly
2935            for prov_name, prov_settings in config.pop("providers").items():
2936                config[prov_name] = prov_settings
2937            break
2938
2939    providers = {}
2940    ext_count = 0
2941    for key, val in config.items():
2942        if key in ("conf_file", "include", "default_include", "user"):
2943            continue
2944
2945        if not isinstance(val, (list, tuple)):
2946            val = [val]
2947        else:
2948            # Need to check for duplicate cloud provider entries per "alias" or
2949            # we won't be able to properly reference it.
2950            handled_providers = set()
2951            for details in val:
2952                if "driver" not in details:
2953                    if "extends" not in details:
2954                        log.error(
2955                            "Please check your cloud providers configuration. "
2956                            "There's no 'driver' nor 'extends' definition "
2957                            "referenced."
2958                        )
2959                    continue
2960
2961                if details["driver"] in handled_providers:
2962                    log.error(
2963                        "You can only have one entry per cloud provider. For "
2964                        "example, if you have a cloud provider configuration "
2965                        "section named, 'production', you can only have a "
2966                        "single entry for EC2, Joyent, Openstack, and so "
2967                        "forth."
2968                    )
2969                    raise salt.exceptions.SaltCloudConfigError(
2970                        "The cloud provider alias '{0}' has multiple entries "
2971                        "for the '{1[driver]}' driver.".format(key, details)
2972                    )
2973                handled_providers.add(details["driver"])
2974
2975        for entry in val:
2976
2977            if "driver" not in entry:
2978                entry["driver"] = "-only-extendable-{}".format(ext_count)
2979                ext_count += 1
2980
2981            if key not in providers:
2982                providers[key] = {}
2983
2984            provider = entry["driver"]
2985            if provider not in providers[key]:
2986                providers[key][provider] = entry
2987
2988    # Is any provider extending data!?
2989    while True:
2990        keep_looping = False
2991        for provider_alias, entries in providers.copy().items():
2992            for driver, details in entries.items():
2993                # Set a holder for the defined profiles
2994                providers[provider_alias][driver]["profiles"] = {}
2995
2996                if "extends" not in details:
2997                    continue
2998
2999                extends = details.pop("extends")
3000
3001                if ":" in extends:
3002                    alias, provider = extends.split(":")
3003                    if alias not in providers:
3004                        raise salt.exceptions.SaltCloudConfigError(
3005                            "The '{0}' cloud provider entry in '{1}' is "
3006                            "trying to extend data from '{2}' though "
3007                            "'{2}' is not defined in the salt cloud "
3008                            "providers loaded data.".format(
3009                                details["driver"], provider_alias, alias
3010                            )
3011                        )
3012
3013                    if provider not in providers.get(alias):
3014                        raise salt.exceptions.SaltCloudConfigError(
3015                            "The '{0}' cloud provider entry in '{1}' is "
3016                            "trying to extend data from '{2}:{3}' though "
3017                            "'{3}' is not defined in '{1}'".format(
3018                                details["driver"], provider_alias, alias, provider
3019                            )
3020                        )
3021                    details["extends"] = "{}:{}".format(alias, provider)
3022                    # change provider details '-only-extendable-' to extended
3023                    # provider name
3024                    details["driver"] = provider
3025                elif providers.get(extends):
3026                    raise salt.exceptions.SaltCloudConfigError(
3027                        "The '{}' cloud provider entry in '{}' is "
3028                        "trying to extend from '{}' and no provider was "
3029                        "specified. Not extending!".format(
3030                            details["driver"], provider_alias, extends
3031                        )
3032                    )
3033                elif extends not in providers:
3034                    raise salt.exceptions.SaltCloudConfigError(
3035                        "The '{0}' cloud provider entry in '{1}' is "
3036                        "trying to extend data from '{2}' though '{2}' "
3037                        "is not defined in the salt cloud providers loaded "
3038                        "data.".format(details["driver"], provider_alias, extends)
3039                    )
3040                else:
3041                    if driver in providers.get(extends):
3042                        details["extends"] = "{}:{}".format(extends, driver)
3043                    elif "-only-extendable-" in providers.get(extends):
3044                        details["extends"] = "{}:{}".format(
3045                            extends, "-only-extendable-{}".format(ext_count)
3046                        )
3047                    else:
3048                        # We're still not aware of what we're trying to extend
3049                        # from. Let's try on next iteration
3050                        details["extends"] = extends
3051                        keep_looping = True
3052        if not keep_looping:
3053            break
3054
3055    while True:
3056        # Merge provided extends
3057        keep_looping = False
3058        for alias, entries in providers.copy().items():
3059            for driver in list(entries.keys()):
3060                # Don't use iteritems, because the values of the dictionary will be changed
3061                details = entries[driver]
3062
3063                if "extends" not in details:
3064                    # Extends resolved or non existing, continue!
3065                    continue
3066
3067                if "extends" in details["extends"]:
3068                    # Since there's a nested extends, resolve this one in the
3069                    # next iteration
3070                    keep_looping = True
3071                    continue
3072
3073                # Let's get a reference to what we're supposed to extend
3074                extends = details.pop("extends")
3075                # Split the setting in (alias, driver)
3076                ext_alias, ext_driver = extends.split(":")
3077                # Grab a copy of what should be extended
3078                extended = providers.get(ext_alias).get(ext_driver).copy()
3079                # Merge the data to extend with the details
3080                extended = salt.utils.dictupdate.update(extended, details)
3081                # Update the providers dictionary with the merged data
3082                providers[alias][driver] = extended
3083                # Update name of the driver, now that it's populated with extended information
3084                if driver.startswith("-only-extendable-"):
3085                    providers[alias][ext_driver] = providers[alias][driver]
3086                    # Delete driver with old name to maintain dictionary size
3087                    del providers[alias][driver]
3088
3089        if not keep_looping:
3090            break
3091
3092    # Now clean up any providers entry that was just used to be a data tree to
3093    # extend from
3094    for provider_alias, entries in providers.copy().items():
3095        for driver, details in entries.copy().items():
3096            if not driver.startswith("-only-extendable-"):
3097                continue
3098
3099            log.info(
3100                "There's at least one cloud driver under the '%s' "
3101                "cloud provider alias which does not have the required "
3102                "'driver' setting. Removing it from the available "
3103                "providers listing.",
3104                provider_alias,
3105            )
3106            providers[provider_alias].pop(driver)
3107
3108        if not providers[provider_alias]:
3109            providers.pop(provider_alias)
3110
3111    return providers
3112
3113
3114def get_cloud_config_value(name, vm_, opts, default=None, search_global=True):
3115    """
3116    Search and return a setting in a known order:
3117
3118        1. In the virtual machine's configuration
3119        2. In the virtual machine's profile configuration
3120        3. In the virtual machine's provider configuration
3121        4. In the salt cloud configuration if global searching is enabled
3122        5. Return the provided default
3123    """
3124
3125    # As a last resort, return the default
3126    value = default
3127
3128    if search_global is True and opts.get(name, None) is not None:
3129        # The setting name exists in the cloud(global) configuration
3130        value = deepcopy(opts[name])
3131
3132    if vm_ and name:
3133        # Let's get the value from the profile, if present
3134        if "profile" in vm_ and vm_["profile"] is not None:
3135            if name in opts["profiles"][vm_["profile"]]:
3136                if isinstance(value, dict):
3137                    value.update(opts["profiles"][vm_["profile"]][name].copy())
3138                else:
3139                    value = deepcopy(opts["profiles"][vm_["profile"]][name])
3140
3141        # Let's get the value from the provider, if present.
3142        if ":" in vm_["driver"]:
3143            # The provider is defined as <provider-alias>:<driver-name>
3144            alias, driver = vm_["driver"].split(":")
3145            if alias in opts["providers"] and driver in opts["providers"][alias]:
3146                details = opts["providers"][alias][driver]
3147                if name in details:
3148                    if isinstance(value, dict):
3149                        value.update(details[name].copy())
3150                    else:
3151                        value = deepcopy(details[name])
3152        elif len(opts["providers"].get(vm_["driver"], ())) > 1:
3153            # The provider is NOT defined as <provider-alias>:<driver-name>
3154            # and there's more than one entry under the alias.
3155            # WARN the user!!!!
3156            log.error(
3157                "The '%s' cloud provider definition has more than one "
3158                "entry. Your VM configuration should be specifying the "
3159                "provider as 'driver: %s:<driver-engine>'. Since "
3160                "it's not, we're returning the first definition which "
3161                "might not be what you intended.",
3162                vm_["driver"],
3163                vm_["driver"],
3164            )
3165
3166        if vm_["driver"] in opts["providers"]:
3167            # There's only one driver defined for this provider. This is safe.
3168            alias_defs = opts["providers"].get(vm_["driver"])
3169            provider_driver_defs = alias_defs[next(iter(list(alias_defs.keys())))]
3170            if name in provider_driver_defs:
3171                # The setting name exists in the VM's provider configuration.
3172                # Return it!
3173                if isinstance(value, dict):
3174                    value.update(provider_driver_defs[name].copy())
3175                else:
3176                    value = deepcopy(provider_driver_defs[name])
3177
3178    if name and vm_ and name in vm_:
3179        # The setting name exists in VM configuration.
3180        if isinstance(vm_[name], types.GeneratorType):
3181            value = next(vm_[name], "")
3182        else:
3183            if isinstance(value, dict) and isinstance(vm_[name], dict):
3184                value.update(vm_[name].copy())
3185            else:
3186                value = deepcopy(vm_[name])
3187
3188    return value
3189
3190
3191def is_provider_configured(
3192    opts, provider, required_keys=(), log_message=True, aliases=()
3193):
3194    """
3195    Check and return the first matching and fully configured cloud provider
3196    configuration.
3197    """
3198    if ":" in provider:
3199        alias, driver = provider.split(":")
3200        if alias not in opts["providers"]:
3201            return False
3202        if driver not in opts["providers"][alias]:
3203            return False
3204        for key in required_keys:
3205            if opts["providers"][alias][driver].get(key, None) is None:
3206                if log_message is True:
3207                    # There's at least one require configuration key which is not
3208                    # set.
3209                    log.warning(
3210                        "The required '%s' configuration setting is missing "
3211                        "from the '%s' driver, which is configured under the "
3212                        "'%s' alias.",
3213                        key,
3214                        provider,
3215                        alias,
3216                    )
3217                return False
3218        # If we reached this far, there's a properly configured provider.
3219        # Return it!
3220        return opts["providers"][alias][driver]
3221
3222    for alias, drivers in opts["providers"].items():
3223        for driver, provider_details in drivers.items():
3224            if driver != provider and driver not in aliases:
3225                continue
3226
3227            # If we reached this far, we have a matching provider, let's see if
3228            # all required configuration keys are present and not None.
3229            skip_provider = False
3230            for key in required_keys:
3231                if provider_details.get(key, None) is None:
3232                    if log_message is True:
3233                        # This provider does not include all necessary keys,
3234                        # continue to next one.
3235                        log.warning(
3236                            "The required '%s' configuration setting is "
3237                            "missing from the '%s' driver, which is configured "
3238                            "under the '%s' alias.",
3239                            key,
3240                            provider,
3241                            alias,
3242                        )
3243                    skip_provider = True
3244                    break
3245
3246            if skip_provider:
3247                continue
3248
3249            # If we reached this far, the provider included all required keys
3250            return provider_details
3251
3252    # If we reached this point, the provider is not configured.
3253    return False
3254
3255
3256def is_profile_configured(opts, provider, profile_name, vm_=None):
3257    """
3258    Check if the requested profile contains the minimum required parameters for
3259    a profile.
3260
3261    Required parameters include image and provider for all drivers, while some
3262    drivers also require size keys.
3263
3264    .. versionadded:: 2015.8.0
3265    """
3266    # Standard dict keys required by all drivers.
3267    required_keys = ["provider"]
3268    alias, driver = provider.split(":")
3269
3270    # Most drivers need an image to be specified, but some do not.
3271    non_image_drivers = [
3272        "nova",
3273        "virtualbox",
3274        "libvirt",
3275        "softlayer",
3276        "oneandone",
3277        "profitbricks",
3278    ]
3279
3280    # Most drivers need a size, but some do not.
3281    non_size_drivers = [
3282        "opennebula",
3283        "parallels",
3284        "proxmox",
3285        "scaleway",
3286        "softlayer",
3287        "softlayer_hw",
3288        "vmware",
3289        "vsphere",
3290        "virtualbox",
3291        "libvirt",
3292        "oneandone",
3293        "profitbricks",
3294    ]
3295
3296    provider_key = opts["providers"][alias][driver]
3297    profile_key = opts["providers"][alias][driver]["profiles"][profile_name]
3298
3299    # If cloning on Linode, size and image are not necessary.
3300    # They are obtained from the to-be-cloned VM.
3301    if driver == "linode" and profile_key.get("clonefrom", False):
3302        non_image_drivers.append("linode")
3303        non_size_drivers.append("linode")
3304    elif driver == "gce" and "sourceImage" in str(vm_.get("ex_disks_gce_struct")):
3305        non_image_drivers.append("gce")
3306
3307    # If cloning on VMware, specifying image is not necessary.
3308    if driver == "vmware" and "image" not in list(profile_key.keys()):
3309        non_image_drivers.append("vmware")
3310
3311    if driver not in non_image_drivers:
3312        required_keys.append("image")
3313        if driver == "vmware":
3314            required_keys.append("datastore")
3315    elif driver in ["linode", "virtualbox"]:
3316        required_keys.append("clonefrom")
3317    elif driver == "nova":
3318        nova_image_keys = [
3319            "image",
3320            "block_device_mapping",
3321            "block_device",
3322            "boot_volume",
3323        ]
3324        if not any([key in provider_key for key in nova_image_keys]) and not any(
3325            [key in profile_key for key in nova_image_keys]
3326        ):
3327            required_keys.extend(nova_image_keys)
3328
3329    if driver not in non_size_drivers:
3330        required_keys.append("size")
3331
3332    # Check if required fields are supplied in the provider config. If they
3333    # are present, remove it from the required_keys list.
3334    for item in list(required_keys):
3335        if item in provider_key:
3336            required_keys.remove(item)
3337
3338    # If a vm_ dict was passed in, use that information to get any other configs
3339    # that we might have missed thus far, such as a option provided in a map file.
3340    if vm_:
3341        for item in list(required_keys):
3342            if item in vm_:
3343                required_keys.remove(item)
3344
3345    # Check for remaining required parameters in the profile config.
3346    for item in required_keys:
3347        if profile_key.get(item, None) is None:
3348            # There's at least one required configuration item which is not set.
3349            log.error(
3350                "The required '%s' configuration setting is missing from "
3351                "the '%s' profile, which is configured under the '%s' alias.",
3352                item,
3353                profile_name,
3354                alias,
3355            )
3356            return False
3357
3358    return True
3359
3360
3361def check_driver_dependencies(driver, dependencies):
3362    """
3363    Check if the driver's dependencies are available.
3364
3365    .. versionadded:: 2015.8.0
3366
3367    driver
3368        The name of the driver.
3369
3370    dependencies
3371        The dictionary of dependencies to check.
3372    """
3373    ret = True
3374    for key, value in dependencies.items():
3375        if value is False:
3376            log.warning(
3377                "Missing dependency: '%s'. The %s driver requires "
3378                "'%s' to be installed.",
3379                key,
3380                driver,
3381                key,
3382            )
3383            ret = False
3384
3385    return ret
3386
3387
3388# <---- Salt Cloud Configuration Functions -----------------------------------
3389
3390
3391def _cache_id(minion_id, cache_file):
3392    """
3393    Helper function, writes minion id to a cache file.
3394    """
3395    path = os.path.dirname(cache_file)
3396    try:
3397        if not os.path.isdir(path):
3398            os.makedirs(path)
3399    except OSError as exc:
3400        # Handle race condition where dir is created after os.path.isdir check
3401        if os.path.isdir(path):
3402            pass
3403        else:
3404            log.error("Failed to create dirs to minion_id file: %s", exc)
3405
3406    try:
3407        with salt.utils.files.fopen(cache_file, "w") as idf:
3408            idf.write(minion_id)
3409    except OSError as exc:
3410        log.error("Could not cache minion ID: %s", exc)
3411
3412
3413def call_id_function(opts):
3414    """
3415    Evaluate the function that determines the ID if the 'id_function'
3416    option is set and return the result
3417    """
3418    if opts.get("id"):
3419        return opts["id"]
3420
3421    # Import 'salt.loader' here to avoid a circular dependency
3422    import salt.loader as loader
3423
3424    if isinstance(opts["id_function"], str):
3425        mod_fun = opts["id_function"]
3426        fun_kwargs = {}
3427    elif isinstance(opts["id_function"], dict):
3428        mod_fun, fun_kwargs = next(iter(opts["id_function"].items()))
3429        if fun_kwargs is None:
3430            fun_kwargs = {}
3431    else:
3432        log.error("'id_function' option is neither a string nor a dictionary")
3433        sys.exit(salt.defaults.exitcodes.EX_GENERIC)
3434
3435    # split module and function and try loading the module
3436    mod, fun = mod_fun.split(".")
3437    if not opts.get("grains"):
3438        # Get grains for use by the module
3439        opts["grains"] = loader.grains(opts)
3440
3441    try:
3442        id_mod = loader.raw_mod(opts, mod, fun)
3443        if not id_mod:
3444            raise KeyError
3445        # we take whatever the module returns as the minion ID
3446        newid = id_mod[mod_fun](**fun_kwargs)
3447        if not isinstance(newid, str) or not newid:
3448            log.error(
3449                'Function %s returned value "%s" of type %s instead of string',
3450                mod_fun,
3451                newid,
3452                type(newid),
3453            )
3454            sys.exit(salt.defaults.exitcodes.EX_GENERIC)
3455        log.info("Evaluated minion ID from module: %s %s", mod_fun, newid)
3456        return newid
3457    except TypeError:
3458        log.error(
3459            "Function arguments %s are incorrect for function %s", fun_kwargs, mod_fun
3460        )
3461        sys.exit(salt.defaults.exitcodes.EX_GENERIC)
3462    except KeyError:
3463        log.error("Failed to load module %s", mod_fun)
3464        sys.exit(salt.defaults.exitcodes.EX_GENERIC)
3465
3466
3467def remove_domain_from_fqdn(opts, newid):
3468    """
3469    Depending on the values of `minion_id_remove_domain`,
3470    remove all domains or a single domain from a FQDN, effectivly generating a hostname.
3471    """
3472    opt_domain = opts.get("minion_id_remove_domain")
3473    if opt_domain is True:
3474        if "." in newid:
3475            # Remove any domain
3476            newid, xdomain = newid.split(".", 1)
3477            log.debug("Removed any domain (%s) from minion id.", xdomain)
3478    else:
3479        # Must be string type
3480        if newid.upper().endswith("." + opt_domain.upper()):
3481            # Remove single domain
3482            newid = newid[: -len("." + opt_domain)]
3483            log.debug("Removed single domain %s from minion id.", opt_domain)
3484    return newid
3485
3486
3487def get_id(opts, cache_minion_id=False):
3488    """
3489    Guess the id of the minion.
3490
3491    If CONFIG_DIR/minion_id exists, use the cached minion ID from that file.
3492    If no minion id is configured, use multiple sources to find a FQDN.
3493    If no FQDN is found you may get an ip address.
3494
3495    Returns two values: the detected ID, and a boolean value noting whether or
3496    not an IP address is being used for the ID.
3497    """
3498    if opts["root_dir"] is None:
3499        root_dir = salt.syspaths.ROOT_DIR
3500    else:
3501        root_dir = opts["root_dir"]
3502
3503    config_dir = salt.syspaths.CONFIG_DIR
3504    if config_dir.startswith(salt.syspaths.ROOT_DIR):
3505        config_dir = config_dir.split(salt.syspaths.ROOT_DIR, 1)[-1]
3506
3507    # Check for cached minion ID
3508    id_cache = os.path.join(root_dir, config_dir.lstrip(os.path.sep), "minion_id")
3509
3510    if opts.get("minion_id_caching", True):
3511        try:
3512            with salt.utils.files.fopen(id_cache) as idf:
3513                name = salt.utils.stringutils.to_unicode(idf.readline().strip())
3514                bname = salt.utils.stringutils.to_bytes(name)
3515                if bname.startswith(codecs.BOM):  # Remove BOM if exists
3516                    name = salt.utils.stringutils.to_str(
3517                        bname.replace(codecs.BOM, "", 1)
3518                    )
3519            if name and name != "localhost":
3520                log.debug("Using cached minion ID from %s: %s", id_cache, name)
3521                return name, False
3522        except OSError:
3523            pass
3524    if "__role" in opts and opts.get("__role") == "minion":
3525        log.debug(
3526            "Guessing ID. The id can be explicitly set in %s",
3527            os.path.join(salt.syspaths.CONFIG_DIR, "minion"),
3528        )
3529
3530    if opts.get("id_function"):
3531        newid = call_id_function(opts)
3532    else:
3533        newid = salt.utils.network.generate_minion_id()
3534
3535    if opts.get("minion_id_lowercase"):
3536        newid = newid.lower()
3537        log.debug("Changed minion id %s to lowercase.", newid)
3538
3539    # Optionally remove one or many domains in a generated minion id
3540    if opts.get("minion_id_remove_domain"):
3541        newid = remove_domain_from_fqdn(opts, newid)
3542
3543    if "__role" in opts and opts.get("__role") == "minion":
3544        if opts.get("id_function"):
3545            log.debug(
3546                "Found minion id from external function %s: %s",
3547                opts["id_function"],
3548                newid,
3549            )
3550        else:
3551            log.debug("Found minion id from generate_minion_id(): %s", newid)
3552    if cache_minion_id and opts.get("minion_id_caching", True):
3553        _cache_id(newid, id_cache)
3554    is_ipv4 = salt.utils.network.is_ipv4(newid)
3555    return newid, is_ipv4
3556
3557
3558def _update_ssl_config(opts):
3559    """
3560    Resolves string names to integer constant in ssl configuration.
3561    """
3562    if opts["ssl"] in (None, False):
3563        opts["ssl"] = None
3564        return
3565    if opts["ssl"] is True:
3566        opts["ssl"] = {}
3567        return
3568    import ssl
3569
3570    for key, prefix in (("cert_reqs", "CERT_"), ("ssl_version", "PROTOCOL_")):
3571        val = opts["ssl"].get(key)
3572        if val is None:
3573            continue
3574        if (
3575            not isinstance(val, str)
3576            or not val.startswith(prefix)
3577            or not hasattr(ssl, val)
3578        ):
3579            message = "SSL option '{}' must be set to one of the following values: '{}'.".format(
3580                key,
3581                "', '".join([val for val in dir(ssl) if val.startswith(prefix)]),
3582            )
3583            log.error(message)
3584            raise salt.exceptions.SaltConfigurationError(message)
3585        opts["ssl"][key] = getattr(ssl, val)
3586
3587
3588def _adjust_log_file_override(overrides, default_log_file):
3589    """
3590    Adjusts the log_file based on the log_dir override
3591    """
3592    if overrides.get("log_dir"):
3593        # Adjust log_file if a log_dir override is introduced
3594        if overrides.get("log_file"):
3595            if not os.path.isabs(overrides["log_file"]):
3596                # Prepend log_dir if log_file is relative
3597                overrides["log_file"] = os.path.join(
3598                    overrides["log_dir"], overrides["log_file"]
3599                )
3600        else:
3601            # Create the log_file override
3602            overrides["log_file"] = os.path.join(
3603                overrides["log_dir"], os.path.basename(default_log_file)
3604            )
3605
3606
3607def apply_minion_config(
3608    overrides=None, defaults=None, cache_minion_id=False, minion_id=None
3609):
3610    """
3611    Returns minion configurations dict.
3612    """
3613    if defaults is None:
3614        defaults = DEFAULT_MINION_OPTS.copy()
3615    if overrides is None:
3616        overrides = {}
3617
3618    opts = defaults.copy()
3619    opts["__role"] = "minion"
3620    _adjust_log_file_override(overrides, defaults["log_file"])
3621    if overrides:
3622        opts.update(overrides)
3623
3624    if "environment" in opts:
3625        if opts["saltenv"] is not None:
3626            log.warning(
3627                "The 'saltenv' and 'environment' minion config options "
3628                "cannot both be used. Ignoring 'environment' in favor of "
3629                "'saltenv'."
3630            )
3631            # Set environment to saltenv in case someone's custom module is
3632            # refrencing __opts__['environment']
3633            opts["environment"] = opts["saltenv"]
3634        else:
3635            log.warning(
3636                "The 'environment' minion config option has been renamed "
3637                "to 'saltenv'. Using %s as the 'saltenv' config value.",
3638                opts["environment"],
3639            )
3640            opts["saltenv"] = opts["environment"]
3641
3642    for idx, val in enumerate(opts["fileserver_backend"]):
3643        if val in ("git", "hg", "svn", "minion"):
3644            new_val = val + "fs"
3645            log.debug(
3646                "Changed %s to %s in minion opts' fileserver_backend list", val, new_val
3647            )
3648            opts["fileserver_backend"][idx] = new_val
3649
3650    opts["__cli"] = salt.utils.stringutils.to_unicode(os.path.basename(sys.argv[0]))
3651
3652    # No ID provided. Will getfqdn save us?
3653    using_ip_for_id = False
3654    if not opts.get("id"):
3655        if minion_id:
3656            opts["id"] = minion_id
3657        else:
3658            opts["id"], using_ip_for_id = get_id(opts, cache_minion_id=cache_minion_id)
3659
3660    # it does not make sense to append a domain to an IP based id
3661    if not using_ip_for_id and "append_domain" in opts:
3662        opts["id"] = _append_domain(opts)
3663
3664    for directory in opts.get("append_minionid_config_dirs", []):
3665        if directory in ("pki_dir", "cachedir", "extension_modules"):
3666            newdirectory = os.path.join(opts[directory], opts["id"])
3667            opts[directory] = newdirectory
3668        elif directory == "default_include" and directory in opts:
3669            include_dir = os.path.dirname(opts[directory])
3670            new_include_dir = os.path.join(
3671                include_dir, opts["id"], os.path.basename(opts[directory])
3672            )
3673            opts[directory] = new_include_dir
3674
3675    # pidfile can be in the list of append_minionid_config_dirs, but pidfile
3676    # is the actual path with the filename, not a directory.
3677    if "pidfile" in opts.get("append_minionid_config_dirs", []):
3678        newpath_list = os.path.split(opts["pidfile"])
3679        opts["pidfile"] = os.path.join(
3680            newpath_list[0], "salt", opts["id"], newpath_list[1]
3681        )
3682
3683    if len(opts["sock_dir"]) > len(opts["cachedir"]) + 10:
3684        opts["sock_dir"] = os.path.join(opts["cachedir"], ".salt-unix")
3685
3686    # Enabling open mode requires that the value be set to True, and
3687    # nothing else!
3688    opts["open_mode"] = opts["open_mode"] is True
3689    opts["file_roots"] = _validate_file_roots(opts["file_roots"])
3690    opts["pillar_roots"] = _validate_pillar_roots(opts["pillar_roots"])
3691    # Make sure ext_mods gets set if it is an untrue value
3692    # (here to catch older bad configs)
3693    opts["extension_modules"] = opts.get("extension_modules") or os.path.join(
3694        opts["cachedir"], "extmods"
3695    )
3696    # Set up the utils_dirs location from the extension_modules location
3697    opts["utils_dirs"] = opts.get("utils_dirs") or [
3698        os.path.join(opts["extension_modules"], "utils")
3699    ]
3700
3701    # Insert all 'utils_dirs' directories to the system path
3702    insert_system_path(opts, opts["utils_dirs"])
3703
3704    # Prepend root_dir to other paths
3705    prepend_root_dirs = [
3706        "pki_dir",
3707        "cachedir",
3708        "sock_dir",
3709        "extension_modules",
3710        "pidfile",
3711    ]
3712
3713    # These can be set to syslog, so, not actual paths on the system
3714    for config_key in ("log_file", "key_logfile"):
3715        if urllib.parse.urlparse(opts.get(config_key, "")).scheme == "":
3716            prepend_root_dirs.append(config_key)
3717
3718    prepend_root_dir(opts, prepend_root_dirs)
3719
3720    # if there is no beacons option yet, add an empty beacons dict
3721    if "beacons" not in opts:
3722        opts["beacons"] = {}
3723
3724    if overrides.get("ipc_write_buffer", "") == "dynamic":
3725        opts["ipc_write_buffer"] = _DFLT_IPC_WBUFFER
3726    if "ipc_write_buffer" not in overrides:
3727        opts["ipc_write_buffer"] = 0
3728
3729    # Make sure hash_type is lowercase
3730    opts["hash_type"] = opts["hash_type"].lower()
3731
3732    # Check and update TLS/SSL configuration
3733    _update_ssl_config(opts)
3734    _update_discovery_config(opts)
3735
3736    return opts
3737
3738
3739def _update_discovery_config(opts):
3740    """
3741    Update discovery config for all instances.
3742
3743    :param opts:
3744    :return:
3745    """
3746    if opts.get("discovery") not in (None, False):
3747        if opts["discovery"] is True:
3748            opts["discovery"] = {}
3749        discovery_config = {
3750            "attempts": 3,
3751            "pause": 5,
3752            "port": 4520,
3753            "match": "any",
3754            "mapping": {},
3755        }
3756        for key in opts["discovery"]:
3757            if key not in discovery_config:
3758                raise salt.exceptions.SaltConfigurationError(
3759                    "Unknown discovery option: {}".format(key)
3760                )
3761        if opts.get("__role") != "minion":
3762            for key in ["attempts", "pause", "match"]:
3763                del discovery_config[key]
3764        opts["discovery"] = salt.utils.dictupdate.update(
3765            discovery_config, opts["discovery"], True, True
3766        )
3767
3768
3769def master_config(
3770    path, env_var="SALT_MASTER_CONFIG", defaults=None, exit_on_config_errors=False
3771):
3772    """
3773    Reads in the master configuration file and sets up default options
3774
3775    This is useful for running the actual master daemon. For running
3776    Master-side client interfaces that need the master opts see
3777    :py:func:`salt.client.client_config`.
3778    """
3779    if defaults is None:
3780        defaults = DEFAULT_MASTER_OPTS.copy()
3781
3782    if not os.environ.get(env_var, None):
3783        # No valid setting was given using the configuration variable.
3784        # Lets see is SALT_CONFIG_DIR is of any use
3785        salt_config_dir = os.environ.get("SALT_CONFIG_DIR", None)
3786        if salt_config_dir:
3787            env_config_file_path = os.path.join(salt_config_dir, "master")
3788            if salt_config_dir and os.path.isfile(env_config_file_path):
3789                # We can get a configuration file using SALT_CONFIG_DIR, let's
3790                # update the environment with this information
3791                os.environ[env_var] = env_config_file_path
3792
3793    overrides = load_config(path, env_var, DEFAULT_MASTER_OPTS["conf_file"])
3794    default_include = overrides.get("default_include", defaults["default_include"])
3795    include = overrides.get("include", [])
3796
3797    overrides.update(
3798        include_config(
3799            default_include,
3800            path,
3801            verbose=False,
3802            exit_on_config_errors=exit_on_config_errors,
3803        )
3804    )
3805    overrides.update(
3806        include_config(
3807            include, path, verbose=True, exit_on_config_errors=exit_on_config_errors
3808        )
3809    )
3810    opts = apply_master_config(overrides, defaults)
3811    _validate_ssh_minion_opts(opts)
3812    _validate_opts(opts)
3813    # If 'nodegroups:' is uncommented in the master config file, and there are
3814    # no nodegroups defined, opts['nodegroups'] will be None. Fix this by
3815    # reverting this value to the default, as if 'nodegroups:' was commented
3816    # out or not present.
3817    if opts.get("nodegroups") is None:
3818        opts["nodegroups"] = DEFAULT_MASTER_OPTS.get("nodegroups", {})
3819    if salt.utils.data.is_dictlist(opts["nodegroups"]):
3820        opts["nodegroups"] = salt.utils.data.repack_dictlist(opts["nodegroups"])
3821    apply_sdb(opts)
3822    return opts
3823
3824
3825def apply_master_config(overrides=None, defaults=None):
3826    """
3827    Returns master configurations dict.
3828    """
3829    if defaults is None:
3830        defaults = DEFAULT_MASTER_OPTS.copy()
3831    if overrides is None:
3832        overrides = {}
3833
3834    opts = defaults.copy()
3835    opts["__role"] = "master"
3836    _adjust_log_file_override(overrides, defaults["log_file"])
3837    if overrides:
3838        opts.update(overrides)
3839
3840    opts["__cli"] = salt.utils.stringutils.to_unicode(os.path.basename(sys.argv[0]))
3841
3842    if "environment" in opts:
3843        if opts["saltenv"] is not None:
3844            log.warning(
3845                "The 'saltenv' and 'environment' master config options "
3846                "cannot both be used. Ignoring 'environment' in favor of "
3847                "'saltenv'."
3848            )
3849            # Set environment to saltenv in case someone's custom runner is
3850            # refrencing __opts__['environment']
3851            opts["environment"] = opts["saltenv"]
3852        else:
3853            log.warning(
3854                "The 'environment' master config option has been renamed "
3855                "to 'saltenv'. Using %s as the 'saltenv' config value.",
3856                opts["environment"],
3857            )
3858            opts["saltenv"] = opts["environment"]
3859
3860    for idx, val in enumerate(opts["fileserver_backend"]):
3861        if val in ("git", "hg", "svn", "minion"):
3862            new_val = val + "fs"
3863            log.debug(
3864                "Changed %s to %s in master opts' fileserver_backend list", val, new_val
3865            )
3866            opts["fileserver_backend"][idx] = new_val
3867
3868    if len(opts["sock_dir"]) > len(opts["cachedir"]) + 10:
3869        opts["sock_dir"] = os.path.join(opts["cachedir"], ".salt-unix")
3870
3871    opts["token_dir"] = os.path.join(opts["cachedir"], "tokens")
3872    opts["syndic_dir"] = os.path.join(opts["cachedir"], "syndics")
3873    # Make sure ext_mods gets set if it is an untrue value
3874    # (here to catch older bad configs)
3875    opts["extension_modules"] = opts.get("extension_modules") or os.path.join(
3876        opts["cachedir"], "extmods"
3877    )
3878    # Set up the utils_dirs location from the extension_modules location
3879    opts["utils_dirs"] = opts.get("utils_dirs") or [
3880        os.path.join(opts["extension_modules"], "utils")
3881    ]
3882
3883    # Insert all 'utils_dirs' directories to the system path
3884    insert_system_path(opts, opts["utils_dirs"])
3885
3886    if overrides.get("ipc_write_buffer", "") == "dynamic":
3887        opts["ipc_write_buffer"] = _DFLT_IPC_WBUFFER
3888    if "ipc_write_buffer" not in overrides:
3889        opts["ipc_write_buffer"] = 0
3890    using_ip_for_id = False
3891    append_master = False
3892    if not opts.get("id"):
3893        opts["id"], using_ip_for_id = get_id(opts, cache_minion_id=None)
3894        append_master = True
3895
3896    # it does not make sense to append a domain to an IP based id
3897    if not using_ip_for_id and "append_domain" in opts:
3898        opts["id"] = _append_domain(opts)
3899    if append_master:
3900        opts["id"] += "_master"
3901
3902    # Prepend root_dir to other paths
3903    prepend_root_dirs = [
3904        "pki_dir",
3905        "cachedir",
3906        "pidfile",
3907        "sock_dir",
3908        "extension_modules",
3909        "autosign_file",
3910        "autoreject_file",
3911        "token_dir",
3912        "syndic_dir",
3913        "sqlite_queue_dir",
3914        "autosign_grains_dir",
3915    ]
3916
3917    # These can be set to syslog, so, not actual paths on the system
3918    for config_key in ("log_file", "key_logfile", "ssh_log_file"):
3919        log_setting = opts.get(config_key, "")
3920        if log_setting is None:
3921            continue
3922
3923        if urllib.parse.urlparse(log_setting).scheme == "":
3924            prepend_root_dirs.append(config_key)
3925
3926    prepend_root_dir(opts, prepend_root_dirs)
3927
3928    # Enabling open mode requires that the value be set to True, and
3929    # nothing else!
3930    opts["open_mode"] = opts["open_mode"] is True
3931    opts["auto_accept"] = opts["auto_accept"] is True
3932    opts["file_roots"] = _validate_file_roots(opts["file_roots"])
3933    opts["pillar_roots"] = _validate_file_roots(opts["pillar_roots"])
3934
3935    if opts["file_ignore_regex"]:
3936        # If file_ignore_regex was given, make sure it's wrapped in a list.
3937        # Only keep valid regex entries for improved performance later on.
3938        if isinstance(opts["file_ignore_regex"], str):
3939            ignore_regex = [opts["file_ignore_regex"]]
3940        elif isinstance(opts["file_ignore_regex"], list):
3941            ignore_regex = opts["file_ignore_regex"]
3942
3943        opts["file_ignore_regex"] = []
3944        for regex in ignore_regex:
3945            try:
3946                # Can't store compiled regex itself in opts (breaks
3947                # serialization)
3948                re.compile(regex)
3949                opts["file_ignore_regex"].append(regex)
3950            except Exception:  # pylint: disable=broad-except
3951                log.warning("Unable to parse file_ignore_regex. Skipping: %s", regex)
3952
3953    if opts["file_ignore_glob"]:
3954        # If file_ignore_glob was given, make sure it's wrapped in a list.
3955        if isinstance(opts["file_ignore_glob"], str):
3956            opts["file_ignore_glob"] = [opts["file_ignore_glob"]]
3957
3958    # Let's make sure `worker_threads` does not drop below 3 which has proven
3959    # to make `salt.modules.publish` not work under the test-suite.
3960    if opts["worker_threads"] < 3 and opts.get("peer", None):
3961        log.warning(
3962            "The 'worker_threads' setting in '%s' cannot be lower than "
3963            "3. Resetting it to the default value of 3.",
3964            opts["conf_file"],
3965        )
3966        opts["worker_threads"] = 3
3967
3968    opts.setdefault("pillar_source_merging_strategy", "smart")
3969
3970    # Make sure hash_type is lowercase
3971    opts["hash_type"] = opts["hash_type"].lower()
3972
3973    # Check and update TLS/SSL configuration
3974    _update_ssl_config(opts)
3975    _update_discovery_config(opts)
3976
3977    return opts
3978
3979
3980def client_config(path, env_var="SALT_CLIENT_CONFIG", defaults=None):
3981    """
3982    Load Master configuration data
3983
3984    Usage:
3985
3986    .. code-block:: python
3987
3988        import salt.config
3989        master_opts = salt.config.client_config('/etc/salt/master')
3990
3991    Returns a dictionary of the Salt Master configuration file with necessary
3992    options needed to communicate with a locally-running Salt Master daemon.
3993    This function searches for client specific configurations and adds them to
3994    the data from the master configuration.
3995
3996    This is useful for master-side operations like
3997    :py:class:`~salt.client.LocalClient`.
3998    """
3999    if defaults is None:
4000        defaults = DEFAULT_MASTER_OPTS.copy()
4001
4002    xdg_dir = salt.utils.xdg.xdg_config_dir()
4003    if os.path.isdir(xdg_dir):
4004        client_config_dir = xdg_dir
4005        saltrc_config_file = "saltrc"
4006    else:
4007        client_config_dir = os.path.expanduser("~")
4008        saltrc_config_file = ".saltrc"
4009
4010    # Get the token file path from the provided defaults. If not found, specify
4011    # our own, sane, default
4012    opts = {
4013        "token_file": defaults.get(
4014            "token_file", os.path.join(client_config_dir, "salt_token")
4015        )
4016    }
4017    # Update options with the master configuration, either from the provided
4018    # path, salt's defaults or provided defaults
4019    opts.update(master_config(path, defaults=defaults))
4020    # Update with the users salt dot file or with the environment variable
4021    saltrc_config = os.path.join(client_config_dir, saltrc_config_file)
4022    opts.update(load_config(saltrc_config, env_var, saltrc_config))
4023    # Make sure we have a proper and absolute path to the token file
4024    if "token_file" in opts:
4025        opts["token_file"] = os.path.abspath(os.path.expanduser(opts["token_file"]))
4026    # If the token file exists, read and store the contained token
4027    if os.path.isfile(opts["token_file"]):
4028        # Make sure token is still valid
4029        expire = opts.get("token_expire", 43200)
4030        if os.stat(opts["token_file"]).st_mtime + expire > time.mktime(
4031            time.localtime()
4032        ):
4033            with salt.utils.files.fopen(opts["token_file"]) as fp_:
4034                opts["token"] = fp_.read().strip()
4035    # On some platforms, like OpenBSD, 0.0.0.0 won't catch a master running on localhost
4036    if opts["interface"] == "0.0.0.0":
4037        opts["interface"] = "127.0.0.1"
4038
4039    # Make sure the master_uri is set
4040    if "master_uri" not in opts:
4041        opts["master_uri"] = "tcp://{ip}:{port}".format(
4042            ip=salt.utils.zeromq.ip_bracket(opts["interface"]), port=opts["ret_port"]
4043        )
4044
4045    # Return the client options
4046    _validate_opts(opts)
4047    return opts
4048
4049
4050def api_config(path):
4051    """
4052    Read in the Salt Master config file and add additional configs that
4053    need to be stubbed out for salt-api
4054    """
4055    # Let's grab a copy of salt-api's required defaults
4056    opts = DEFAULT_API_OPTS.copy()
4057
4058    # Let's override them with salt's master opts
4059    opts.update(client_config(path, defaults=DEFAULT_MASTER_OPTS.copy()))
4060
4061    # Let's set the pidfile and log_file values in opts to api settings
4062    opts.update(
4063        {
4064            "pidfile": opts.get("api_pidfile", DEFAULT_API_OPTS["api_pidfile"]),
4065            "log_file": opts.get("api_logfile", DEFAULT_API_OPTS["api_logfile"]),
4066        }
4067    )
4068
4069    prepend_root_dir(opts, ["api_pidfile", "api_logfile", "log_file", "pidfile"])
4070    return opts
4071
4072
4073def spm_config(path):
4074    """
4075    Read in the salt master config file and add additional configs that
4076    need to be stubbed out for spm
4077
4078    .. versionadded:: 2015.8.0
4079    """
4080    # Let's grab a copy of salt's master default opts
4081    defaults = DEFAULT_MASTER_OPTS.copy()
4082    # Let's override them with spm's required defaults
4083    defaults.update(DEFAULT_SPM_OPTS)
4084
4085    overrides = load_config(path, "SPM_CONFIG", DEFAULT_SPM_OPTS["spm_conf_file"])
4086    default_include = overrides.get(
4087        "spm_default_include", defaults["spm_default_include"]
4088    )
4089    include = overrides.get("include", [])
4090
4091    overrides.update(include_config(default_include, path, verbose=False))
4092    overrides.update(include_config(include, path, verbose=True))
4093    defaults = apply_master_config(overrides, defaults)
4094    defaults = apply_spm_config(overrides, defaults)
4095    return client_config(path, env_var="SPM_CONFIG", defaults=defaults)
4096
4097
4098def apply_spm_config(overrides, defaults):
4099    """
4100    Returns the spm configurations dict.
4101
4102    .. versionadded:: 2015.8.1
4103    """
4104    opts = defaults.copy()
4105    _adjust_log_file_override(overrides, defaults["log_file"])
4106    if overrides:
4107        opts.update(overrides)
4108
4109    # Prepend root_dir to other paths
4110    prepend_root_dirs = [
4111        "formula_path",
4112        "pillar_path",
4113        "reactor_path",
4114        "spm_cache_dir",
4115        "spm_build_dir",
4116    ]
4117
4118    # These can be set to syslog, so, not actual paths on the system
4119    for config_key in ("spm_logfile",):
4120        log_setting = opts.get(config_key, "")
4121        if log_setting is None:
4122            continue
4123
4124        if urllib.parse.urlparse(log_setting).scheme == "":
4125            prepend_root_dirs.append(config_key)
4126
4127    prepend_root_dir(opts, prepend_root_dirs)
4128    return opts
4129