<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Max Arnold</title>
    <description>The latest articles on DEV Community by Max Arnold (@marnold).</description>
    <link>https://dev.to/marnold</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F81336%2Fb0a4edf9-6270-4e16-831b-896eb778fc6e.jpeg</url>
      <title>DEV Community: Max Arnold</title>
      <link>https://dev.to/marnold</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/marnold"/>
    <language>en</language>
    <item>
      <title>Upgrading Salt to Python 3</title>
      <dc:creator>Max Arnold</dc:creator>
      <pubDate>Wed, 19 Feb 2020 18:36:22 +0000</pubDate>
      <link>https://dev.to/marnold/upgrading-salt-to-python-3-3fe6</link>
      <guid>https://dev.to/marnold/upgrading-salt-to-python-3-3fe6</guid>
      <description>&lt;p&gt;As of January 1st, 2020, Python 2 has officially reached the end of life and will no longer receive any updates (except the last one in April). Various Linux distributions will support it for a while; however, many organizations already switched to Python 3 or plan to do so in the near future.&lt;/p&gt;

&lt;p&gt;It is time to switch your Salt install to Python 3:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.saltstack.com/en/latest/topics/releases/2017.7.0.html"&gt;Salt Nitrogen&lt;/a&gt; (2017.7.0) is the first release that supported Py3&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.saltstack.com/en/latest/topics/releases/2018.3.0.html#unicode-python-3-compatibility-improvements"&gt;Salt Oxygen&lt;/a&gt; (2018.3.0) gained significant Py3 improvements&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.saltstack.com/en/latest/topics/releases/2019.2.0.html#python-2-7-deprecation"&gt;Salt Fluorine&lt;/a&gt; (2019.2.0) can be considered more-or-less mature&lt;/li&gt;
&lt;li&gt;Salt 3001 Sodium (the one after &lt;a href="https://docs.saltstack.com/en/master/topics/releases/3000.html"&gt;Salt 3000 Neon&lt;/a&gt;) is going to deprecate Python 2.7&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This guide describes how to switch the repo from Py2 to Py3, identify and uninstall the old packages, then install the Py3 version. It contains detailed checklists that will help you to plan and perform the migration. It can be also applied to regular upgrades. However, it is not a 100% automatic foolproof recipe you can apply blindly.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;10 key principles&lt;/li&gt;
&lt;li&gt;Plan the upgrade process&lt;/li&gt;
&lt;li&gt;Survey your master and minion fleet&lt;/li&gt;
&lt;li&gt;Upgrade the Salt Master&lt;/li&gt;
&lt;li&gt;Upgrade Salt on Windows&lt;/li&gt;
&lt;li&gt;Upgrade Salt on Ubuntu and Debian&lt;/li&gt;
&lt;li&gt;Upgrade Salt on CentOS and Amazon Linux&lt;/li&gt;
&lt;li&gt;Upgrade Salt on other operating systems&lt;/li&gt;
&lt;li&gt;Monitor the upgrade process&lt;/li&gt;
&lt;li&gt;Upgrade automation&lt;/li&gt;
&lt;li&gt;Future developments&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  10 key principles
&lt;/h2&gt;

&lt;p&gt;Over the years of using Salt, I developed a set of ten opinionated guiding principles that will help you maintain and upgrade it. You do not have to follow these guidelines, and I often break them too. However, they are implied throughout the article.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Have a QA environment
&lt;/h4&gt;

&lt;p&gt;QA environment can be manual or automated. The goal is to be able to test the required combinations of Operating Systems, Salt versions (master &amp;amp; minion), and possible highstates. Always do staged/gradual rollouts. You’ll save a lot of time if your QA environment supports snapshots.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Run the same Salt version everywhere
&lt;/h4&gt;

&lt;p&gt;Master and minion versions should be the same (the only exceptions are [1] and [5]). This is the most traveled path. You won’t need to adapt your states to different feature sets. Also, the upgrade process is much easier.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Avoid rare operating modes
&lt;/h4&gt;

&lt;p&gt;Less traveled paths and settings that significantly change how Salt operates (transport, multiprocessing, zmq filtering, caching, undocumented things), tend to result in nasty surprises. If you want to change a setting, give it a try in your QA environment for a while.&lt;/p&gt;

&lt;h4&gt;
  
  
  4. Control the bootstrap/upgrade process
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Do not use the “latest” repo prefix (except in a QA environment), and do not rush to upgrade to a latest major version&lt;/li&gt;
&lt;li&gt;Use the “archive” repo prefix, or pin Salt packages down to a minor version (you never know when another 2019.2.1 will happen)&lt;/li&gt;
&lt;li&gt;Always install specific version when you bootstrap new minions (e.g., through the &lt;a href="https://github.com/saltstack/salt/issues/54857#issuecomment-537731285"&gt;&lt;code&gt;script_args&lt;/code&gt;&lt;/a&gt; Salt Cloud option)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Yes, a minor upgrade will require more work. But it is work you can control and decide when to do. You can’t control work that appears out of the blue when minions automatically upgrade and something breaks.&lt;/p&gt;

&lt;h4&gt;
  
  
  5. Upgrade the master first
&lt;/h4&gt;

&lt;p&gt;When you upgrade Salt, upgrade the master first. Older master can (and eventually will) &lt;a href="https://github.com/saltstack/salt/issues/56118"&gt;break things&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  6. Back up the keys
&lt;/h4&gt;

&lt;p&gt;Set up periodic backups of the &lt;code&gt;/etc/salt/pki&lt;/code&gt; master directory. Other than that, your Salt master should be disposable, and states/pillars should be kept in a version control system (or pulled from external data sources).&lt;/p&gt;

&lt;h4&gt;
  
  
  7. Have a backup access path
&lt;/h4&gt;

&lt;p&gt;Have a plan B to access your servers when Salt goes down. Use &lt;code&gt;salt-ssh&lt;/code&gt;, &lt;code&gt;fabric&lt;/code&gt;, &lt;code&gt;ansible&lt;/code&gt;, &lt;code&gt;ssh&lt;/code&gt;, &lt;code&gt;winrm&lt;/code&gt;, or any other tool. It is also helpful to keep a list of minion IPs (you can periodically extract them from the master grain cache).&lt;/p&gt;

&lt;h4&gt;
  
  
  8. Use Salt as an O&amp;amp;M control plane
&lt;/h4&gt;

&lt;p&gt;Avoid making your production workload depend on Salt. If you uninstall Salt, your production environment should remain operational (at least for a while).&lt;/p&gt;

&lt;p&gt;If you have the resources, you may also want to consider the last two principles:&lt;/p&gt;

&lt;h4&gt;
  
  
  9. Mirror the Salt repo
&lt;/h4&gt;

&lt;p&gt;I never had to do &lt;a href="https://repo.saltstack.com/#mirror"&gt;this&lt;/a&gt;, but I’ve seen rare connectivity issues with the official repo. Also, for some folks, the minion bootstrap process was broken when SaltStack removed (and then quickly returned back) the buggy 2019.2.1 release. This is less of an issue if you follow the principle [4].&lt;/p&gt;

&lt;h4&gt;
  
  
  10. Build your own packages
&lt;/h4&gt;

&lt;p&gt;Bugs will happen, and you need to fix them quickly without waiting for the next release. Also, you may want to integrate new features before they are shipped. Custom packages are a way to achieve that. They give you full control over the Salt code you run.&lt;/p&gt;

&lt;p&gt;I ran a patched salt-master from a virtualenv for some time in 2012 or 2013, but I no longer do that. Instead, I prefer the &lt;a href="https://dev.to/marnold/patching-salt-modules-1fa4"&gt;more lightweight ways to patch Salt&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Plan the upgrade process
&lt;/h2&gt;

&lt;h4&gt;
  
  
  1. Survey your master and minion fleet
&lt;/h4&gt;

&lt;p&gt;A list of useful commands is provided below. There are many dimensions you should inspect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Operating systems, their architectures, and versions&lt;/li&gt;
&lt;li&gt;Available repositories&lt;/li&gt;
&lt;li&gt;Python versions&lt;/li&gt;
&lt;li&gt;Master and minion versions&lt;/li&gt;
&lt;li&gt;Salt dependencies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The main goal is to find all the existing combinations, so you can exhaustively test the upgrade process. Also check out the &lt;a href="https://github.com/saltstack/salt-bootstrap#python-3-support"&gt;Py3 support list&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Prepare the QA environment
&lt;/h4&gt;

&lt;p&gt;Ideally, you want to be able:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Test the same OS/Salt versions&lt;/li&gt;
&lt;li&gt;Test the existing highstates&lt;/li&gt;
&lt;li&gt;Test the actual workload that runs on top of your infra&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You’ll need to test the upgrade process multiple times, so a fast feedback cycle is necessary (e.g., it would be nice to quickly snapshot/restore your QA environment).&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Update the states
&lt;/h4&gt;

&lt;p&gt;Make sure your states are compatible with the target Salt version. Read the official release notes and search for any open issues.&lt;/p&gt;

&lt;h4&gt;
  
  
  4. Decide on the upgrade process
&lt;/h4&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Perform the backups (master/minion keys, alternate connection paths, etc.)&lt;/li&gt;
&lt;li&gt;Upgrade OS packages, run autoclean/autoremove. Optionally reboot if you have new kernels.&lt;/li&gt;
&lt;li&gt;Disable any periodic Salt jobs and other stuff that can interfere with the upgrade process. Try &lt;a href="https://docs.saltstack.com/en/latest/topics/blackout/"&gt;minion blackouts&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Upgrade master to the target version, while staying on Py2&lt;/li&gt;
&lt;li&gt;Ensure that the existing process to bootstrap new minions uses the target Salt version (Py3)&lt;/li&gt;
&lt;li&gt;Upgrade minions to the target version, while staying on Py2. If that is not possible, upgrade to the latest available for each distro.&lt;/li&gt;
&lt;li&gt;Upgrade Salt master to Py3&lt;/li&gt;
&lt;li&gt;Upgrade Salt minions to Py3 for each OS separately&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Consider the minion upgrade order. Your environment may require some specific upgrade sequence.&lt;/p&gt;

&lt;p&gt;The goal is to remove the old Salt version and its dependencies (to avoid having packages from different repos), then bootstrap the Py3 one from scratch.&lt;/p&gt;

&lt;p&gt;If your minions are disposable, you can avoid the in-place upgrade process and instead rebuild them from scratch. Another option is blue/green deployment.&lt;/p&gt;

&lt;h4&gt;
  
  
  5. Test the upgrade process multiple times
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Manually upgrade your masters&lt;/li&gt;
&lt;li&gt;Make sure their minions are reachable after the upgrade&lt;/li&gt;
&lt;li&gt;Manually upgrade a single minion for each OS&lt;/li&gt;
&lt;li&gt;Make sure the upgraded minions are reachable, the highstates can be applied, and the business apps are functional&lt;/li&gt;
&lt;li&gt;Automate the above steps&lt;/li&gt;
&lt;li&gt;Test the automation multiple times&lt;/li&gt;
&lt;li&gt;Test the backup options (master/minion keys, alternate connection paths, etc.)&lt;/li&gt;
&lt;li&gt;Test the bootstrap process for new minions&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  6. Do the upgrade
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Decide how many upgrade stages you want&lt;/li&gt;
&lt;li&gt;Decide on the appropriate schedule&lt;/li&gt;
&lt;li&gt;Inform the team(s)&lt;/li&gt;
&lt;li&gt;Perform and verify the backups (master/minion keys, alternate connection paths, etc.)&lt;/li&gt;
&lt;li&gt;Switch the bootstrap for new minions to the target version&lt;/li&gt;
&lt;li&gt;Upgrade the master&lt;/li&gt;
&lt;li&gt;Upgrade the minions&lt;/li&gt;
&lt;li&gt;Monitor the result&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Survey your master and minion fleet
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Listing versions
&lt;/h3&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;% &lt;span class="nb"&gt;sudo &lt;/span&gt;salt &lt;span class="s1"&gt;'*'&lt;/span&gt; test.version

% &lt;span class="nb"&gt;sudo &lt;/span&gt;salt &lt;span class="s1"&gt;'*'&lt;/span&gt; pkg.version salt-minion

% &lt;span class="nb"&gt;sudo &lt;/span&gt;salt &lt;span class="s1"&gt;'*'&lt;/span&gt; test.versions_report
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;(you may find a lot of variation in the installed dependencies!)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;% sudo salt '*' grains.item pythonversion

% sudo salt-run manage.versions

Master:
    2019.2.3
Minion requires update:
    ----------
    minion1:
        2018.3.5
    minion2:
        2018.3.5
    minion3:
        2018.3.5
    win10:
        2018.3.5

sudo salt-run survey.hash '*' test.version
|_
  ----------
  pool:
      - minion1
      - minion2
      - minion3
      - win10
  result:
      2018.3.5
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Listing repos and keys
&lt;/h3&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;% &lt;span class="nb"&gt;sudo &lt;/span&gt;salt &lt;span class="s1"&gt;'*'&lt;/span&gt; pkg.list_repos
% &lt;span class="nb"&gt;sudo &lt;/span&gt;salt &lt;span class="s1"&gt;'*'&lt;/span&gt; pkg.get_repo_keys
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Combine with &lt;code&gt;jq&lt;/code&gt; to get more compact output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;% &lt;span class="nb"&gt;sudo &lt;/span&gt;salt &lt;span class="nt"&gt;-G&lt;/span&gt; &lt;span class="s2"&gt;"os:Ubuntu"&lt;/span&gt; pkg.list_repos &lt;span class="nt"&gt;--static&lt;/span&gt; &lt;span class="nt"&gt;--out&lt;/span&gt; json | jq &lt;span class="s1"&gt;'.[] | with_entries(select(.key|contains("saltstack"))) | .[][].line'&lt;/span&gt;

&lt;span class="s2"&gt;"deb https://repo.saltstack.com/apt/ubuntu/18.04/amd64/archive/2018.3.5 bionic main"&lt;/span&gt;
&lt;span class="s2"&gt;"deb https://repo.saltstack.com/apt/ubuntu/18.04/amd64/archive/2019.2.3 bionic main"&lt;/span&gt;

% &lt;span class="nb"&gt;sudo &lt;/span&gt;salt &lt;span class="nt"&gt;-G&lt;/span&gt; &lt;span class="s2"&gt;"os:Debian"&lt;/span&gt; pkg.list_repos &lt;span class="nt"&gt;--static&lt;/span&gt; &lt;span class="nt"&gt;--out&lt;/span&gt; json | jq &lt;span class="s1"&gt;'.[] | with_entries(select(.key|contains("saltstack"))) | .[][].line'&lt;/span&gt;

&lt;span class="s2"&gt;"deb https://repo.saltstack.com/apt/debian/9/amd64/archive/2018.3.5 stretch main"&lt;/span&gt;
&lt;span class="s2"&gt;"deb https://repo.saltstack.com/apt/debian/9/amd64/archive/2019.2.3 stretch main"&lt;/span&gt;

% &lt;span class="nb"&gt;sudo &lt;/span&gt;salt &lt;span class="nt"&gt;-G&lt;/span&gt; &lt;span class="s2"&gt;"os:CentOS"&lt;/span&gt; pkg.list_repos &lt;span class="nt"&gt;--static&lt;/span&gt; &lt;span class="nt"&gt;--out&lt;/span&gt; json | jq &lt;span class="s1"&gt;'.[] | with_entries(select(.key|contains("saltstack"))) | .[].baseurl'&lt;/span&gt;

&lt;span class="s2"&gt;"https://repo.saltstack.com/yum/redhat/7/&lt;/span&gt;&lt;span class="nv"&gt;$basearch&lt;/span&gt;&lt;span class="s2"&gt;/archive/2018.3.5/"&lt;/span&gt;
&lt;span class="s2"&gt;"https://repo.saltstack.com/yum/redhat/7/&lt;/span&gt;&lt;span class="nv"&gt;$basearch&lt;/span&gt;&lt;span class="s2"&gt;/archive/2019.2.3/"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Targeting py2 minions
&lt;/h3&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;% &lt;span class="nb"&gt;sudo &lt;/span&gt;salt &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"G@os:Ubuntu and G@pythonversion:0:2"&lt;/span&gt; grains.item pythonversion saltversion
% &lt;span class="nb"&gt;sudo &lt;/span&gt;salt &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"G@os:Debian and G@pythonversion:0:2"&lt;/span&gt; grains.item pythonversion saltversion
% &lt;span class="nb"&gt;sudo &lt;/span&gt;salt &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"G@os:CentOS and G@pythonversion:0:2"&lt;/span&gt; grains.item pythonversion saltversion
% &lt;span class="nb"&gt;sudo &lt;/span&gt;salt &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"G@os:Windows and G@pythonversion:0:2"&lt;/span&gt; grains.item pythonversion saltversion
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Other useful grains for targeting
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;cpuarch&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lsb_distrib_codename&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lsb_distrib_id&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lsb_distrib_release&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;os&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;osarch&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;oscodename&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;osfinger&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;osmajorrelease&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;osrelease&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;osversion&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pythonexecutable&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pythonversion&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;saltpath&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;saltversion&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Surveys
&lt;/h3&gt;

&lt;p&gt;Any module calls (&lt;code&gt;test.version&lt;/code&gt;, &lt;code&gt;cmd.run&lt;/code&gt;, &lt;code&gt;grains.item&lt;/code&gt;, etc.) can be combined with the &lt;code&gt;survey&lt;/code&gt; runner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo salt-run survey.hash '*' grains.item os osfinger pythonversion saltversion
|_
  ----------
  pool:
      - minion3k
  result:
      {u'osfinger': u'Ubuntu-18.04', u'saltversion': u'2018.3.5', u'os': u'Ubuntu', u'pythonversion': [2, 7, 17, u'final', 0]}
|_
  ----------
  pool:
      - minion2
  result:
      {u'osfinger': u'CentOS Linux-7', u'saltversion': u'2018.3.5', u'os': u'CentOS', u'pythonversion': [2, 7, 5, u'final', 0]}
|_
  ----------
  pool:
      - minion1
  result:
      {u'osfinger': u'Debian-9', u'saltversion': u'2018.3.5', u'os': u'Debian', u'pythonversion': [2, 7, 13, u'final', 0]}
|_
  ----------
  pool:
      - win10
  result:
      {u'osfinger': u'Windows-10', u'saltversion': u'2018.3.5', u'os': u'Windows', u'pythonversion': [2, 7, 14, u'final', 0]}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Listing package origins
&lt;/h3&gt;

&lt;p&gt;The goal is to find packages that were installed from a specific origin, so that you can remove them during the upgrade process.&lt;/p&gt;

&lt;h4&gt;
  
  
  Ubuntu/Debian
&lt;/h4&gt;

&lt;p&gt;I found the following snippet on &lt;a href="https://askubuntu.com/a/43583"&gt;Ask Ubuntu&lt;/a&gt;. If you know a more straightforward way to do the same without installing aptitude or other packages, I’m all ears.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;% apt-cache policy &lt;span class="si"&gt;$(&lt;/span&gt;dpkg &lt;span class="nt"&gt;-l&lt;/span&gt; | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'NR &amp;gt;= 6 { print $2 }'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; |
  &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'/^[^]/ { split($1, a, ":"); pkg = a[1] }
    nextline == 1 { nextline = 0; printf("%-40s %-50s %s\n", pkg, $2, $3) }
    /\*\*\*/ { nextline = 1 }'&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;saltstack
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The output can vary depending on the OS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;salt-common https://repo.saltstack.com/apt/ubuntu/18.04/amd64/archive/2018.3.5 bionic/main
salt-minion https://repo.saltstack.com/apt/ubuntu/18.04/amd64/archive/2018.3.5 bionic/main

python-jinja2 https://repo.saltstack.com/apt/debian/9/amd64/archive/2018.3.5 stretch/main
salt-common https://repo.saltstack.com/apt/debian/9/amd64/archive/2018.3.5 stretch/main
salt-minion https://repo.saltstack.com/apt/debian/9/amd64/archive/2018.3.5 stretch/main
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Additionally, you can inspect how a Salt minion was installed in the first place. You need to figure out which packages are installed explicitly (the rest of the dependencies can be auto removed). Check out the &lt;code&gt;/var/log/apt/history.log&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;Debian:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Start-Date: 2020-02-18 05:15:02
Commandline: apt-get install -y -o DPkg::Options::=--force-confold procps pciutils python-yaml
Requested-By: vagrant (1000)
Install: python-yaml:amd64 (3.12-1), libyaml-0-2:amd64 (0.1.7-2, automatic)
End-Date: 2020-02-18 05:15:02

Start-Date: 2020-02-18 05:15:05
Commandline: apt-get install -y -o DPkg::Options::=--force-confold wget gnupg2 apt-transport-https ca-certificates
Requested-By: vagrant (1000)
Install: gnupg2:amd64 (2.1.18-8~deb9u4), apt-transport-https:amd64 (1.4.9)
End-Date: 2020-02-18 05:15:06

Start-Date: 2020-02-18 05:15:35
Commandline: apt-get install -y -o DPkg::Options::=--force-confold salt-minion
Requested-By: vagrant (1000)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Ubuntu:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Start-Date: 2020-02-18 05:15:08
Commandline: apt-get install -y -o DPkg::Options::=--force-confold wget gnupg apt-transport-https ca-certificates
Requested-By: vagrant (1000)
Install: apt-transport-https:amd64 (1.6.12)
End-Date: 2020-02-18 05:15:08

Start-Date: 2020-02-18 05:15:49
Commandline: apt-get install -y -o DPkg::Options::=--force-confold python2.7 python-apt python-requests python-yaml procps pciutils
Requested-By: vagrant (1000)
Install: python-six:amd64 (1.11.0-2, automatic), python-openssl:amd64 (17.5.0-1ubuntu1, automatic), python-yaml:amd64 (3.12-1build2), python-requests:amd64 (2.18.4-2ubuntu0.1), python-certifi:amd64 (2018.1.18-2, automatic), python-chardet:amd64 (3.0.4-1, automatic), python-enum34:amd64 (1.1.6-2, automatic), python-cryptography:amd64 (2.1.4-1ubuntu1.3, automatic), python-cffi-backend:amd64 (1.11.5-1, automatic), python-ipaddress:amd64 (1.0.17-1, automatic), python-pkg-resources:amd64 (39.0.1-2, automatic), python-apt:amd64 (1.6.5ubuntu0.2), python-urllib3:amd64 (1.22-1ubuntu0.18.04.1, automatic), python-idna:amd64 (2.6-1, automatic), python-asn1crypto:amd64 (0.24.0-1, automatic)
Upgrade: python2.7-minimal:amd64 (2.7.15-4ubuntu4~18.04.2, 2.7.17-1~18.04), libpython2.7:amd64 (2.7.15-4ubuntu4~18.04.2, 2.7.17-1~18.04), python2.7:amd64 (2.7.15-4ubuntu4~18.04.2, 2.7.17-1~18.04), libpython2.7-minimal:amd64 (2.7.15-4ubuntu4~18.04.2, 2.7.17-1~18.04), libpython2.7-stdlib:amd64 (2.7.15-4ubuntu4~18.04.2, 2.7.17-1~18.04)
End-Date: 2020-02-18 05:15:56

Start-Date: 2020-02-18 05:16:11
Commandline: apt-get install -y -o DPkg::Options::=--force-confold salt-minion
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;bootstrap-salt.sh&lt;/code&gt; script also logs everything to &lt;code&gt;/tmp/bootstrap-salt.log&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pciutils is already the newest version (1:3.5.2-1).
procps is already the newest version (2:3.3.12-3+deb9u1).
...
Selecting previously unselected package python-yaml.
Preparing to unpack .../python-yaml_3.12-1_amd64.deb ...
Unpacking python-yaml (3.12-1) ...
Setting up libyaml-0-2:amd64 (0.1.7-2) ...
Processing triggers for libc-bin (2.24-11+deb9u4) ...
Setting up python-yaml (3.12-1) ...
Reading package lists...
Building dependency tree...
Reading state information...
ca-certificates is already the newest version (20161130+nmu1+deb9u1).
ca-certificates set to manually installed.
wget is already the newest version (1.18-5+deb9u3).
The following NEW packages will be installed:
  apt-transport-https gnupg2
...
The following additional packages will be installed:
  dctrl-tools debconf-utils dirmngr javascript-common libjs-jquery
  libjs-sphinxdoc libjs-underscore libpgm-5.2-0 libsodium18 libzmq5 python-apt
  python-backports-abc python-cffi-backend python-chardet
  python-concurrent.futures python-croniter python-cryptography
  python-dateutil python-enum34 python-idna python-ipaddress python-jinja2
  python-markupsafe python-msgpack python-openssl python-pkg-resources
  python-psutil python-pyasn1 python-requests python-setuptools
  python-singledispatch python-six python-systemd python-tornado python-tz
  python-urllib3 python-zmq salt-common
Suggested packages:
  debtags dbus-user-session pinentry-gnome3 tor apache2 | lighttpd | httpd
  python-apt-dbg python-apt-doc python-cryptography-doc
  python-cryptography-vectors python-enum34-doc python-jinja2-doc
  python-openssl-doc python-openssl-dbg python-psutil-doc doc-base
  python-socks python-setuptools-doc python-mysqldb python-pycurl
  python-tornado-doc python-twisted python-ntlm python-augeas
Recommended packages:
  sfdisk e2fprogs
The following NEW packages will be installed:
  dctrl-tools debconf-utils dirmngr javascript-common libjs-jquery
  libjs-sphinxdoc libjs-underscore libpgm-5.2-0 libsodium18 libzmq5 python-apt
  python-backports-abc python-cffi-backend python-chardet
  python-concurrent.futures python-croniter python-cryptography
  python-dateutil python-enum34 python-idna python-ipaddress python-jinja2
  python-markupsafe python-msgpack python-openssl python-pkg-resources
  python-psutil python-pyasn1 python-requests python-setuptools
  python-singledispatch python-six python-systemd python-tornado python-tz
  python-urllib3 python-zmq salt-common salt-minion
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;So, for Debian/Ubuntu you may want to remove the &lt;code&gt;salt-minion&lt;/code&gt;, &lt;code&gt;python-requests&lt;/code&gt;, &lt;code&gt;python-yaml&lt;/code&gt;, and &lt;code&gt;python-apt&lt;/code&gt; explicitly, then auto remove the dependencies.&lt;/p&gt;

&lt;h4&gt;
  
  
  CentOS/Amazon
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;% yum list installed | grep saltstack

PyYAML.x86_64 3.11-1.el7 @saltstack
python-zmq.x86_64 15.3.0-3.el7 @saltstack
salt.noarch 2018.3.5-1.el7 @saltstack
salt-minion.noarch 2018.3.5-1.el7 @saltstack
zeromq.x86_64 4.1.4-7.el7 @saltstack
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;/var/log/yum.log&lt;/code&gt; is not very informative:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Feb 18 08:15:37 Updated: rpm-libs-4.11.3-40.el7.x86_64
Feb 18 08:15:37 Updated: rpm-4.11.3-40.el7.x86_64
Feb 18 08:15:38 Updated: rpm-build-libs-4.11.3-40.el7.x86_64
Feb 18 08:15:38 Updated: rpm-python-4.11.3-40.el7.x86_64
Feb 18 08:15:38 Installed: python-chardet-2.2.1-3.el7.noarch
Feb 18 08:15:38 Installed: python-kitchen-1.1.1-5.el7.noarch
Feb 18 08:15:38 Installed: libyaml-0.1.4-11.el7_0.x86_64
Feb 18 08:15:38 Updated: libxml2-2.9.1-6.el7_2.3.x86_64
Feb 18 08:15:38 Installed: libxml2-python-2.9.1-6.el7_2.3.x86_64
Feb 18 08:15:38 Updated: python-urlgrabber-3.10-9.el7.noarch
Feb 18 08:15:39 Updated: yum-3.4.3-163.el7.centos.noarch
Feb 18 08:15:39 Installed: yum-utils-1.1.31-52.el7.noarch
Feb 18 08:15:39 Installed: PyYAML-3.11-1.el7.x86_64
Feb 18 08:15:39 Updated: chkconfig-1.7.4-1.el7.x86_64
Feb 18 08:16:15 Installed: lz4-1.7.5-3.el7.x86_64
Feb 18 08:16:15 Updated: systemd-libs-219-67.el7_7.3.x86_64
Feb 18 08:16:15 Installed: python-ipaddress-1.0.16-2.el7.noarch
Feb 18 08:16:15 Installed: python-markupsafe-0.11-10.el7.x86_64
Feb 18 08:16:15 Installed: python-six-1.9.0-2.el7.noarch
Feb 18 08:16:15 Installed: libtommath-0.42.0-6.el7.x86_64
Feb 18 08:16:15 Installed: libtomcrypt-1.17-26.el7.x86_64
Feb 18 08:16:16 Installed: python2-crypto-2.6.1-16.el7.x86_64
Feb 18 08:16:16 Installed: python-backports-1.0-8.el7.x86_64
Feb 18 08:16:16 Installed: python-backports-ssl_match_hostname-3.5.0.1-1.el7.noarch
Feb 18 08:16:16 Installed: python-tornado-4.2.1-5.el7.x86_64
Feb 18 08:16:16 Installed: python-urllib3-1.10.2-7.el7.noarch
Feb 18 08:16:16 Installed: python-requests-2.6.0-8.el7_7.noarch
Feb 18 08:16:17 Installed: python-babel-0.9.6-8.el7.noarch
Feb 18 08:16:18 Installed: python-jinja2-2.7.2-4.el7.noarch
Feb 18 08:16:18 Installed: libsodium-1.0.18-1.el7.x86_64
Feb 18 08:16:18 Installed: python2-msgpack-0.5.6-5.el7.x86_64
Feb 18 08:16:18 Updated: cryptsetup-libs-2.0.3-5.el7.x86_64
Feb 18 08:16:20 Updated: systemd-219-67.el7_7.3.x86_64
Feb 18 08:16:20 Installed: systemd-python-219-67.el7_7.3.x86_64
Feb 18 08:16:20 Installed: openpgm-5.2.122-2.el7.x86_64
Feb 18 08:16:20 Installed: zeromq-4.1.4-7.el7.x86_64
Feb 18 08:16:21 Installed: python-zmq-15.3.0-3.el7.x86_64
Feb 18 08:16:21 Installed: python2-futures-3.1.1-5.el7.noarch
Feb 18 08:16:21 Updated: pciutils-libs-3.5.1-3.el7.x86_64
Feb 18 08:16:21 Installed: pciutils-3.5.1-3.el7.x86_64
Feb 18 08:16:21 Installed: python2-psutil-2.2.1-5.el7.x86_64
Feb 18 08:16:26 Installed: salt-2018.3.5-1.el7.noarch
Feb 18 08:16:26 Installed: salt-minion-2018.3.5-1.el7.noarch
Feb 18 08:16:26 Updated: systemd-sysv-219-67.el7_7.3.x86_64
Feb 18 08:16:26 Updated: libgudev1-219-67.el7_7.3.x86_64
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;However, the &lt;code&gt;/tmp/bootstrap-salt.log&lt;/code&gt; shows precisely which packages were requested and which are merely dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;================================================================================
 Package Arch Version Repository Size
================================================================================
Installing:
 PyYAML x86_64 3.11-1.el7 saltstack 160 k
 yum-utils noarch 1.1.31-52.el7 base 121 k
Updating:
 chkconfig x86_64 1.7.4-1.el7 base 181 k
Installing for dependencies:
 libxml2-python x86_64 2.9.1-6.el7_2.3 base 247 k
 libyaml x86_64 0.1.4-11.el7_0 base 55 k
 python-chardet noarch 2.2.1-3.el7 base 227 k
 python-kitchen noarch 1.1.1-5.el7 base 267 k
Updating for dependencies:
 libxml2 x86_64 2.9.1-6.el7_2.3 base 668 k
 python-urlgrabber noarch 3.10-9.el7 base 108 k
 rpm x86_64 4.11.3-40.el7 base 1.2 M
 rpm-build-libs x86_64 4.11.3-40.el7 base 107 k
 rpm-libs x86_64 4.11.3-40.el7 base 278 k
 rpm-python x86_64 4.11.3-40.el7 base 83 k
 yum noarch 3.4.3-163.el7.centos base 1.2 M

Transaction Summary
================================================================================
Install 2 Packages (+4 Dependent packages)
Upgrade 1 Package (+7 Dependent packages)

================================================================================
 Package Arch Version Repository Size
================================================================================
Installing:
 salt-minion noarch 2018.3.5-1.el7 saltstack 37 k
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Below is a similar log for Amazon Linux:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Installing:
 m2crypto x86_64 0.31.0-4.el7 saltstack-repo 287 k
 python-crypto x86_64 2.6.1-2.el7 saltstack-repo 470 k
 python-msgpack x86_64 0.4.6-1.el7 saltstack-repo 73 k
 python-zmq x86_64 15.3.0-3.el7 saltstack-repo 520 k
Updating for dependencies:
 PyYAML x86_64 3.11-1.el7 saltstack-repo 160 k
 procps-ng x86_64 3.3.10-26.amzn2 amzn2-core 292 k
 python-requests noarch 2.6.0-7.amzn2 amzn2-core 95 k
Installing for dependencies:
 libsodium x86_64 1.0.16-1.el7 saltstack-repo 140 k
 openpgm x86_64 5.2.122-2.el7 saltstack-repo 172 k
 python2-typing noarch 3.5.2.2-4.el7 saltstack-repo 39 k
 zeromq x86_64 4.1.4-7.el7 saltstack-repo 555 k
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;So, for CentOS/Amazon you may want to remove the &lt;code&gt;salt-minion&lt;/code&gt;, &lt;code&gt;PyYAML&lt;/code&gt;, &lt;code&gt;m2crypto&lt;/code&gt;, &lt;code&gt;python-zmq&lt;/code&gt;, &lt;code&gt;python-crypto&lt;/code&gt;, and &lt;code&gt;python-msgpack&lt;/code&gt; explicitly, then auto remove the dependencies.&lt;/p&gt;

&lt;h3&gt;
  
  
  Listing dependencies using &lt;code&gt;pip&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;If you installed some Salt dependencies via &lt;code&gt;pip&lt;/code&gt;, it is worth inspecting them too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;% &lt;span class="nb"&gt;sudo &lt;/span&gt;salt &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"G@kernel:Linux"&lt;/span&gt; cmd.run &lt;span class="s1"&gt;'pip freeze --local'&lt;/span&gt;

% &lt;span class="nb"&gt;sudo &lt;/span&gt;salt &lt;span class="nt"&gt;-t&lt;/span&gt; 30 &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"G@os:Windows"&lt;/span&gt; cmd.run &lt;span class="s1"&gt;'pip.exe freeze --local'&lt;/span&gt; &lt;span class="nv"&gt;cwd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'C:\salt\bin\scripts\'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;On CentOS, Amazon, and Windows, the command shows a full list of python packages, so you’ll need to compare it with a default one to find what is different.&lt;/p&gt;

&lt;h2&gt;
  
  
  Upgrade the Salt Master
&lt;/h2&gt;

&lt;p&gt;From the &lt;a href="https://docs.saltstack.com/en/latest/faq.html#can-i-run-different-versions-of-salt-on-my-master-and-minion"&gt;FAQ&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Q: Can I run different versions of Salt on my Master and Minion?&lt;/p&gt;

&lt;p&gt;A: This depends on the versions. In general, it is recommended that Master and Minion versions match.When upgrading Salt, the master(s) should always be upgraded first. Backwards compatibility for minions running newer versions of Salt than their masters is not guaranteed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There are two common ways to upgrade a Salt master:&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 1
&lt;/h3&gt;

&lt;p&gt;The in-place upgrade process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Backup the &lt;code&gt;/etc/salt/pki&lt;/code&gt; directory (just in case)&lt;/li&gt;
&lt;li&gt;Remove the old Salt repo&lt;/li&gt;
&lt;li&gt;Uninstall the old salt-master package and all of its dependencies&lt;/li&gt;
&lt;li&gt;Add the new repo (py3)&lt;/li&gt;
&lt;li&gt;Install the new salt-master&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Option 2
&lt;/h3&gt;

&lt;p&gt;If you can’t disconnect all minions at once during the upgrade process, it is possible to bring up an additional master server and migrate minions one-by-one:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Set up new salt-master (py3)&lt;/li&gt;
&lt;li&gt;Copy the &lt;code&gt;/etc/salt/pki&lt;/code&gt; directory from old master to the new one, to ensure that master and minion keys are the same&lt;/li&gt;
&lt;li&gt;Migrate the minions:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# migrate_minion.sls&lt;/span&gt;
&lt;span class="na"&gt;migrate_to_new_master&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;file.replace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;salt['config.get']('conf_file')&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
    &lt;span class="c1"&gt;# Alternative variant if you keep master address in a separate config file&lt;/span&gt;
    &lt;span class="c1"&gt;# - name: "{{ salt['file.join'](salt['config.get']('config_dir'), 'minion.d', 'master.conf') }}"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;^master:\s+{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;salt["config.get"]("master")&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;regex_escape&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}$'&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;repl&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;master:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;new-master.example.com'&lt;/span&gt;

&lt;span class="c1"&gt;# Restart salt minion after the the config option is set&lt;/span&gt;
&lt;span class="c1"&gt;# A naive module.run with service.restart stopped working in 2019.2.1&lt;/span&gt;
&lt;span class="c1"&gt;# (https://github.com/saltstack/salt/issues/55201), so we have to use salt-call&lt;/span&gt;
&lt;span class="c1"&gt;# https://docs.saltstack.com/en/latest/faq.html#restart-using-states&lt;/span&gt;
&lt;span class="na"&gt;restart_salt_minion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;cmd.run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;salt-call&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;service.restart&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;salt-minion'&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;bg&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;onchanges&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;migrate_to_new_master&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;To be safe, you can migrate minions in batches:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;% &lt;span class="nb"&gt;sudo &lt;/span&gt;salt &lt;span class="nt"&gt;-G&lt;/span&gt; &lt;span class="s1"&gt;'master:old-master.example.com'&lt;/span&gt; &lt;span class="nt"&gt;--batch-size&lt;/span&gt; 10% state.apply migrate_minion
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Upgrade Salt on Windows
&lt;/h2&gt;

&lt;h3&gt;
  
  
  With winrepo
&lt;/h3&gt;

&lt;p&gt;Things are very easy in Windows land if you use &lt;a href="https://github.com/saltstack/salt-winrepo-ng"&gt;&lt;code&gt;winrepo-ng&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Update your winrepo definitions
&lt;/h4&gt;

&lt;p&gt;If you do not want to bring the whole winrepo-ng, just put the following two files into &lt;code&gt;salt://win/repo-ng&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/saltstack/salt-winrepo-ng/blob/master/salt-minion-py2.sls"&gt;salt-minion-py2.sls&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/saltstack/salt-winrepo-ng/blob/master/salt-minion-py3.sls"&gt;salt-minion-py3.sls&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  2. Rebuild the package database
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;% &lt;span class="nb"&gt;sudo &lt;/span&gt;salt &lt;span class="nt"&gt;-t&lt;/span&gt; 30 &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"G@os:Windows and G@pythonversion:0:2"&lt;/span&gt; pkg.refresh_db
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  3. List the currently installed version
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;% &lt;span class="nb"&gt;sudo &lt;/span&gt;salt &lt;span class="nt"&gt;-t&lt;/span&gt; 30 &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"G@os:Windows and G@pythonversion:0:2"&lt;/span&gt; pkg.version salt-minion
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  3. Make sure the target version is available
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;% &lt;span class="nb"&gt;sudo &lt;/span&gt;salt &lt;span class="nt"&gt;-t&lt;/span&gt; 30 &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"G@os:Windows and G@pythonversion:0:2"&lt;/span&gt; pkg.list_available salt-minion-py3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  4. Run the upgrade command
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;% &lt;span class="nb"&gt;sudo &lt;/span&gt;salt &lt;span class="nt"&gt;-t&lt;/span&gt; 30 &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"G@os:Windows and G@pythonversion:0:2"&lt;/span&gt; pkg.install salt-minion-py3 &lt;span class="nv"&gt;version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2019.2.3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;You might want to add the &lt;a href="https://docs.saltstack.com/en/latest/topics/targeting/batch.html"&gt;&lt;code&gt;--batch-size X&lt;/code&gt;&lt;/a&gt; argument for a staged rollout.&lt;/p&gt;

&lt;h3&gt;
  
  
  Without winrepo
&lt;/h3&gt;

&lt;p&gt;Do not want to use winrepo at all? Below is an alternative way to upgrade Windows minions.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Fetch the installer
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;% &lt;span class="nb"&gt;sudo &lt;/span&gt;salt &lt;span class="nt"&gt;-t&lt;/span&gt; 30 &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"G@os:Windows and G@pythonversion:0:2 and G@cpuarch:AMD64"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  cp.get_url http://repo.saltstack.com/windows/Salt-Minion-2019.2.3-Py3-AMD64-Setup.exe

win10:
    c:&lt;span class="se"&gt;\s&lt;/span&gt;alt&lt;span class="se"&gt;\v&lt;/span&gt;ar&lt;span class="se"&gt;\c&lt;/span&gt;ache&lt;span class="se"&gt;\s&lt;/span&gt;alt&lt;span class="se"&gt;\m&lt;/span&gt;inion&lt;span class="se"&gt;\e&lt;/span&gt;xtrn_files&lt;span class="se"&gt;\b&lt;/span&gt;ase&lt;span class="se"&gt;\r&lt;/span&gt;epo.saltstack.com&lt;span class="se"&gt;\w&lt;/span&gt;indows&lt;span class="se"&gt;\S&lt;/span&gt;alt-Minion-2019.2.3-Py3-AMD64-Setup.exe
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;You can also include a checksum to prevent redownloads (works in Salt 2018.3.0 or greater):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;% &lt;span class="nb"&gt;sudo &lt;/span&gt;salt &lt;span class="nt"&gt;-t&lt;/span&gt; 30 &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"G@os:Windows and G@pythonversion:0:2 and G@cpuarch:AMD64"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  cp.get_url http://repo.saltstack.com/windows/Salt-Minion-2019.2.3-Py3-AMD64-Setup.exe &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nv"&gt;source_hash&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://repo.saltstack.com/windows/Salt-Minion-2019.2.3-Py3-AMD64-Setup.exe.sha256 |&lt;span class="se"&gt;\&lt;/span&gt;
  iconv &lt;span class="nt"&gt;-f&lt;/span&gt; utf-16 &lt;span class="nt"&gt;-t&lt;/span&gt; utf-8 | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $1;}'&lt;/span&gt; | &lt;span class="nb"&gt;tr &lt;/span&gt;A-Z a-z&lt;span class="si"&gt;)&lt;/span&gt;

win10:
    c:&lt;span class="se"&gt;\s&lt;/span&gt;alt&lt;span class="se"&gt;\v&lt;/span&gt;ar&lt;span class="se"&gt;\c&lt;/span&gt;ache&lt;span class="se"&gt;\s&lt;/span&gt;alt&lt;span class="se"&gt;\m&lt;/span&gt;inion&lt;span class="se"&gt;\e&lt;/span&gt;xtrn_files&lt;span class="se"&gt;\b&lt;/span&gt;ase&lt;span class="se"&gt;\r&lt;/span&gt;epo.saltstack.com&lt;span class="se"&gt;\w&lt;/span&gt;indows&lt;span class="se"&gt;\S&lt;/span&gt;alt-Minion-2019.2.3-Py3-AMD64-Setup.exe
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The default download location is fine, because the Salt installer &lt;a href="https://github.com/saltstack/salt/pull/42913"&gt;doesn’t remove&lt;/a&gt; the cache dir since 2017.7.2.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Run the upgrade command
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;% &lt;span class="nb"&gt;sudo &lt;/span&gt;salt &lt;span class="nt"&gt;-t&lt;/span&gt; 30 &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"G@os:Windows and G@pythonversion:0:2 and G@cpuarch:AMD64"&lt;/span&gt; cmd.run &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'START /B c:\salt\var\cache\salt\minion\extrn_files\base\repo.saltstack.com\windows\Salt-Minion-2019.2.3-Py3-AMD64-Setup.exe /S'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Upgrade Salt on Ubuntu and Debian
&lt;/h2&gt;

&lt;p&gt;There are a few key pieces:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Remove the old repo and key
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;remove_salt_repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;pkgrepo.absent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deb https://repo.saltstack.com/apt/ubuntu/18.04/amd64/archive/2018.3.5 bionic main&lt;/span&gt;
&lt;span class="c1"&gt;# - name: deb https://repo.saltstack.com/apt/debian/9/amd64/archive/2018.3.5 stretch main&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;keyid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0E08A149DE57BFBE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;It is also possible to delete all existing Salt repos and keys on both Ubuntu and Debian:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight jinja"&gt;&lt;code&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;repo_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;repo_data&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'pkg.list_repos'&lt;/span&gt;&lt;span class="p"&gt;]()&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;repo&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;repo_data&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nv"&gt;repo.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'disabled'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="s1"&gt;'saltstack'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;repo.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'line'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="cp"&gt;%}&lt;/span&gt;
Remove repo &lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'line'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;:
  pkgrepo.absent:
    - name: &lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'line'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endif&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endfor&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;endfor&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;

&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;key_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;key_data&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'pkg.get_repo_keys'&lt;/span&gt;&lt;span class="p"&gt;]()&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s1"&gt;'saltstack'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;key_data.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'uid'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="cp"&gt;%}&lt;/span&gt;
Remove key &lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;key_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'keyid'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;:
  module.run:
&lt;span class="cp"&gt;{%&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s1"&gt;'module.run'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'config.get'&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="s1"&gt;'use_superseded'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
    - pkg.del_repo_key:
        - keyid: &lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;key_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'keyid'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
    - name: pkg.del_repo_key
    - kwargs:
        keyid: &lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;key_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'keyid'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endif&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endif&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;endfor&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  2. Stop the salt-minion service
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl stop salt-minion.service
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  3. Remove the packages
&lt;/h4&gt;

&lt;p&gt;The snippet below should be adapted to your specific environment (i.e., remove all the dependencies, but do not touch any packages that are critical for production workload):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;apt-get &lt;span class="nt"&gt;-y&lt;/span&gt; remove &lt;span class="nt"&gt;--auto-remove&lt;/span&gt; salt-minion
apt-get &lt;span class="nt"&gt;-y&lt;/span&gt; remove &lt;span class="nt"&gt;--auto-remove&lt;/span&gt; python-requests python-yaml python-apt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  4. Install the minion
&lt;/h4&gt;

&lt;p&gt;While it is possible to just &lt;code&gt;apt-get install&lt;/code&gt; the necessary packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;apt-get &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; DPkg::Options::&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nt"&gt;--force-confold&lt;/span&gt; &lt;span class="nb"&gt;install &lt;/span&gt;python3-requests python3-yaml python3-apt
apt-get &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; DPkg::Options::&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nt"&gt;--force-confold&lt;/span&gt; &lt;span class="nb"&gt;install &lt;/span&gt;salt-minion
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;I feel that using &lt;code&gt;salt-bootstrap.sh&lt;/code&gt; is simpler and supports more platforms:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/cache/salt/minion
curl &lt;span class="nt"&gt;-Ls&lt;/span&gt; https://bootstrap.saltstack.com | sh &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;-x&lt;/span&gt; python3 stable 2019.2.3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Removing the cache dir is optional, however it was &lt;a href="https://github.com/saltstack/salt/pull/8012"&gt;recommended&lt;/a&gt; some time ago on Windows (no longer so since &lt;a href="https://github.com/saltstack/salt/pull/42913"&gt;2017.7.2&lt;/a&gt;). Also, &lt;a href="https://github.com/saltstack/salt/blob/81eb15264380d82267ffc3c1930410baf1f3fbf1/salt/payload.py#L80-L91"&gt;this comment&lt;/a&gt; hints on possible incompatibilities between cache serialization formats. And finally, it could be important if you use &lt;a href="https://salt.tips/patching-salt-modules/#two-caveats"&gt;version-specific custom modules&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  5. The resulting state
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight jinja"&gt;&lt;code&gt;# upgrade/ubuntu_debian.sls
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;repo_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;repo_data&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'pkg.list_repos'&lt;/span&gt;&lt;span class="p"&gt;]()&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;repo&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;repo_data&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nv"&gt;repo.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'disabled'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="s1"&gt;'saltstack'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;repo.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'line'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="cp"&gt;%}&lt;/span&gt;
Remove repo &lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'line'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;:
  pkgrepo.absent:
    - name: &lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'line'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
    # - keyid: 0E08A149DE57BFBE
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endif&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endfor&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;endfor&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;

&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;key_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;key_data&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'pkg.get_repo_keys'&lt;/span&gt;&lt;span class="p"&gt;]()&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s1"&gt;'saltstack'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;key_data.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'uid'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="cp"&gt;%}&lt;/span&gt;
Remove key &lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;key_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'keyid'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;:
  module.run:
&lt;span class="cp"&gt;{%&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s1"&gt;'module.run'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'config.get'&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="s1"&gt;'use_superseded'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
    - pkg.del_repo_key:
        - keyid: &lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;key_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'keyid'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
    - name: pkg.del_repo_key
    - kwargs:
        keyid: &lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;key_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'keyid'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endif&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endif&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;endfor&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;

install_at:
  pkg.installed:
    - name: at

upgrade_salt_minion:
  cmd.run:
    - name: |
        echo "systemctl stop salt-minion.service
        apt-get -y remove --auto-remove salt-minion
        apt-get -y remove --auto-remove python-requests python-yaml python-apt
        rm -rf /var/cache/salt/minion
        curl -Ls https://bootstrap.saltstack.com | sh -s -- -x python3 stable 2019.2.3" | at now
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;at&lt;/code&gt; trick was once recommended in the official docs. See the following issues if you like some archaeology: &lt;a href="https://github.com/saltstack/salt/issues/5721"&gt;#5721&lt;/a&gt;, &lt;a href="https://github.com/saltstack/salt/issues/7997"&gt;#7997&lt;/a&gt;, &lt;a href="https://github.com/saltstack/salt/issues/22993"&gt;#22993&lt;/a&gt;, &lt;a href="https://github.com/saltstack/salt/pull/32593"&gt;#32593&lt;/a&gt;, &lt;a href="https://github.com/saltstack/salt/pull/39952"&gt;#39952&lt;/a&gt;, &lt;a href="https://github.com/saltstack/salt/issues/43340"&gt;#43340&lt;/a&gt;, &lt;a href="https://github.com/saltstack/salt/pull/46709"&gt;#46709&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can also try the &lt;a href="https://docs.saltstack.com/en/master/ref/modules/all/salt.modules.cmdmod.html#salt.modules.cmdmod.run_bg"&gt;&lt;code&gt;systemd-run --scope&lt;/code&gt;&lt;/a&gt; method.&lt;/p&gt;

&lt;h4&gt;
  
  
  6. Run the upgrade command
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;% &lt;span class="nb"&gt;sudo &lt;/span&gt;salt &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"( G@os:Ubuntu or G@os:Debian ) and G@pythonversion:0:2"&lt;/span&gt; state.apply upgrade.ubuntu_debian
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;You might want to add the &lt;a href="https://docs.saltstack.com/en/latest/topics/targeting/batch.html"&gt;&lt;code&gt;--batch-size X&lt;/code&gt;&lt;/a&gt; argument for a staged rollout.&lt;/p&gt;

&lt;h2&gt;
  
  
  Upgrade Salt on CentOS and Amazon Linux
&lt;/h2&gt;

&lt;p&gt;There are a few key pieces:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Remove the old repo
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;remove_salt_repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;pkgrepo.absent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;saltstack&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;It is also possible to delete all existing Salt repos on any rpm-based distro:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight jinja"&gt;&lt;code&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;repo&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'pkg.list_repos'&lt;/span&gt;&lt;span class="p"&gt;]()&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;repo.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'enabled'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'1'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'1'&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="s1"&gt;'saltstack'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;key&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="cp"&gt;%}&lt;/span&gt;
Remove repo &lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;key&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;:
  pkgrepo.absent:
    - name: &lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;key&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endif&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endfor&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  2. Stop the salt-minion service
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl stop salt-minion.service
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  3. Remove the packages
&lt;/h4&gt;

&lt;p&gt;The snippet below should be adapted to your specific environment (i.e., remove all the dependencies, but do not touch any packages that are critical for production workload):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;yum &lt;span class="nt"&gt;-y&lt;/span&gt; remove salt-minion
yum &lt;span class="nt"&gt;-y&lt;/span&gt; remove PyYAML m2crypto python-zmq zeromq python-crypto python-msgpack
yum &lt;span class="nt"&gt;-y&lt;/span&gt; autoremove
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  4. Install the minion without starting it
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/cache/salt/minion
curl &lt;span class="nt"&gt;-Ls&lt;/span&gt; https://bootstrap.saltstack.com | sh &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; &lt;span class="nt"&gt;-x&lt;/span&gt; python3 stable 2019.2.3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Removing the cache dir is optional, however it was &lt;a href="https://github.com/saltstack/salt/pull/8012"&gt;recommended&lt;/a&gt; some time ago on Windows (no longer so since &lt;a href="https://github.com/saltstack/salt/pull/42913"&gt;2017.7.2&lt;/a&gt;). Also, &lt;a href="https://github.com/saltstack/salt/blob/81eb15264380d82267ffc3c1930410baf1f3fbf1/salt/payload.py#L80-L91"&gt;this comment&lt;/a&gt; hints on possible incompatibilities between cache serialization formats. And finally, it could be important if you use &lt;a href="https://salt.tips/patching-salt-modules/#two-caveats"&gt;version-specific custom modules&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  5. Reuse the old config
&lt;/h4&gt;

&lt;p&gt;The snippet below should be adapted to your specific environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;-f&lt;/span&gt; /etc/salt/minion.rpmsave] &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="o"&gt;(&lt;/span&gt; &lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /etc/salt/minion /etc/salt/minion.bak&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nb"&gt;mv&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /etc/salt/minion.rpmsave /etc/salt/minion &lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  6. Start the salt-minion service
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl is-enabled salt-minion.service &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="o"&gt;(&lt;/span&gt;systemctl preset salt-minion.service &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;salt-minion.service&lt;span class="o"&gt;)&lt;/span&gt;
systemctl start salt-minion.service
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  7. The resulting state
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# upgrade/centos_amazon.sls&lt;/span&gt;
&lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt; &lt;span class="nv"&gt;for key&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;repo in salt&lt;/span&gt;&lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pkg.list_repos'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;().items() -%&lt;/span&gt;&lt;span class="pi"&gt;}&lt;/span&gt;
&lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt; &lt;span class="nv"&gt;if repo.get('enabled'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1'&lt;/span&gt;&lt;span class="nv"&gt;) == '1' and 'saltstack' in key -%&lt;/span&gt;&lt;span class="pi"&gt;}&lt;/span&gt;
&lt;span class="s"&gt;Remove repo {{ key }}&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;pkgrepo.absent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;key&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
&lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt; &lt;span class="nv"&gt;endif -%&lt;/span&gt;&lt;span class="pi"&gt;}&lt;/span&gt;
&lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt; &lt;span class="nv"&gt;endfor %&lt;/span&gt;&lt;span class="pi"&gt;}&lt;/span&gt;

&lt;span class="na"&gt;install_at&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;pkg.installed&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;at&lt;/span&gt;

&lt;span class="na"&gt;atd_service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;service.running&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;atd&lt;/span&gt;
    &lt;span class="c1"&gt;# - enable: True&lt;/span&gt;

&lt;span class="na"&gt;upgrade_salt_minion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;cmd.run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;echo "systemctl stop salt-minion.service&lt;/span&gt;
        &lt;span class="s"&gt;yum -y remove salt-minion&lt;/span&gt;
        &lt;span class="s"&gt;yum -y remove PyYAML m2crypto python-zmq zeromq python-crypto python-msgpack&lt;/span&gt;
        &lt;span class="s"&gt;yum -y autoremove&lt;/span&gt;
        &lt;span class="s"&gt;rm -rf /var/cache/salt/minion&lt;/span&gt;
        &lt;span class="s"&gt;curl -Ls https://bootstrap.saltstack.com | sh -s -- -X -x python3 stable 2019.2.3&lt;/span&gt;
        &lt;span class="s"&gt;[-f /etc/salt/minion.rpmsave] &amp;amp;&amp;amp; \&lt;/span&gt;
        &lt;span class="s"&gt;( cp -f /etc/salt/minion /etc/salt/minion.bak; \&lt;/span&gt;
        &lt;span class="s"&gt;mv -f /etc/salt/minion.rpmsave /etc/salt/minion )&lt;/span&gt;
        &lt;span class="s"&gt;systemctl is-enabled salt-minion.service || \&lt;/span&gt;
        &lt;span class="s"&gt;(systemctl preset salt-minion.service &amp;amp;&amp;amp; \&lt;/span&gt;
        &lt;span class="s"&gt;systemctl enable salt-minion.service)&lt;/span&gt;
        &lt;span class="s"&gt;systemctl start salt-minion.service" | at now&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;at&lt;/code&gt; trick was once recommended in the official docs. See the following issues if you like some archaeology: &lt;a href="https://github.com/saltstack/salt/issues/5721"&gt;#5721&lt;/a&gt;, &lt;a href="https://github.com/saltstack/salt/issues/7997"&gt;#7997&lt;/a&gt;, &lt;a href="https://github.com/saltstack/salt/issues/22993"&gt;#22993&lt;/a&gt;, &lt;a href="https://github.com/saltstack/salt/pull/32593"&gt;#32593&lt;/a&gt;, &lt;a href="https://github.com/saltstack/salt/pull/39952"&gt;#39952&lt;/a&gt;, &lt;a href="https://github.com/saltstack/salt/issues/43340"&gt;#43340&lt;/a&gt;, &lt;a href="https://github.com/saltstack/salt/pull/46709"&gt;#46709&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can also try the &lt;a href="https://docs.saltstack.com/en/master/ref/modules/all/salt.modules.cmdmod.html#salt.modules.cmdmod.run_bg"&gt;&lt;code&gt;systemd-run --scope&lt;/code&gt;&lt;/a&gt; method.&lt;/p&gt;

&lt;h4&gt;
  
  
  8. Run the upgrade command
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;% &lt;span class="nb"&gt;sudo &lt;/span&gt;salt &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"( G@os:CentOS or G@os:Amazon ) and G@pythonversion:0:2"&lt;/span&gt; state.apply upgrade.centos_amazon
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;You might want to add the &lt;a href="https://docs.saltstack.com/en/latest/topics/targeting/batch.html"&gt;&lt;code&gt;--batch-size X&lt;/code&gt;&lt;/a&gt; argument for a staged rollout.&lt;/p&gt;

&lt;h2&gt;
  
  
  Upgrade Salt on other operating systems
&lt;/h2&gt;

&lt;p&gt;If you have done this on any other operating system (Red Hat, Fedora, SUSE, Gentoo, Arch, Raspbian, macOS, Solaris, FreeBSD, OpenBSD, etc.), feel free to share your experience and possible gotchas. I’ll be happy to update the article with any specific recipes!&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitor the upgrade process
&lt;/h2&gt;

&lt;p&gt;The upgrade process can take some time. There are three ways to monitor minions as they reconnect:&lt;/p&gt;

&lt;h4&gt;
  
  
  Watch the master log
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;% &lt;span class="nb"&gt;sudo tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /var/log/salt/master
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  Watch the master event bus
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;% &lt;span class="nb"&gt;sudo &lt;/span&gt;salt-run state.event &lt;span class="nv"&gt;tagmatch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'salt/minion/*/start'&lt;/span&gt; &lt;span class="nv"&gt;pretty&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  List the Py2/Py3 minion count
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;% &lt;span class="nb"&gt;sudo &lt;/span&gt;salt &lt;span class="s2"&gt;"*"&lt;/span&gt; grains.get pythonversion &lt;span class="nt"&gt;--out&lt;/span&gt; json &lt;span class="nt"&gt;--static&lt;/span&gt; | jq &lt;span class="s1"&gt;'.[] | "\(.[0]).\(.[1]).\(.[2])"'&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt;

      1 &lt;span class="s2"&gt;"2.7.13"&lt;/span&gt;
      1 &lt;span class="s2"&gt;"2.7.17"&lt;/span&gt;
      1 &lt;span class="s2"&gt;"2.7.5"&lt;/span&gt;
      1 &lt;span class="s2"&gt;"3.5.3"&lt;/span&gt;
      1 &lt;span class="s2"&gt;"3.6.8"&lt;/span&gt;
      1 &lt;span class="s2"&gt;"3.6.9"&lt;/span&gt;

% &lt;span class="nb"&gt;sudo &lt;/span&gt;salt &lt;span class="s2"&gt;"*"&lt;/span&gt; grains.get pythonversion &lt;span class="nt"&gt;--out&lt;/span&gt; json &lt;span class="nt"&gt;--static&lt;/span&gt; | jq &lt;span class="s1"&gt;'.[] | "\(.[0])"'&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt;

      3 &lt;span class="s2"&gt;"2"&lt;/span&gt;
      3 &lt;span class="s2"&gt;"3"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Upgrade automation
&lt;/h2&gt;

&lt;p&gt;If you need more control over the upgrade process (handle retries and upgrade failures, define specific orchestration sequence, etc.) you can use Salt orchestration or preferably write a &lt;a href="https://vimeo.com/292042849"&gt;custom runner&lt;/a&gt;. Another variant is to use &lt;code&gt;salt-api&lt;/code&gt; and &lt;a href="https://github.com/saltstack/pepper"&gt;&lt;code&gt;pepper&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;An out-of-band upgrade using &lt;code&gt;fabric&lt;/code&gt; or &lt;code&gt;salt-ssh&lt;/code&gt; is also an option.&lt;/p&gt;

&lt;h2&gt;
  
  
  Future developments
&lt;/h2&gt;

&lt;p&gt;In 2019 SaltStack introduced &lt;a href="https://github.com/saltstack/salt-bin/"&gt;salt-bin&lt;/a&gt; and &lt;a href="https://gitlab.com/saltstack/pop/pop-build"&gt;pop-build&lt;/a&gt; projects to make self-contained Salt binaries. Hopefully, they will simplify the dependency handling and make the upgrade process easier.&lt;/p&gt;

&lt;h3&gt;
  
  
  Need to run different Salt versions simultaneously?
&lt;/h3&gt;

&lt;p&gt;The next post will explain how to write backward-compatible Salt states (e.g., to deal with module.run syntax differences, or for public formulas). &lt;a href="https://tips.us8.list-manage.com/subscribe?u=79863cb6907503346da4ed704&amp;amp;id=9de0347807"&gt;Subscribe&lt;/a&gt; to read it as soon as the draft is done.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>saltstack</category>
      <category>salt</category>
      <category>python</category>
    </item>
    <item>
      <title>Patching Salt Modules</title>
      <dc:creator>Max Arnold</dc:creator>
      <pubDate>Mon, 27 Jan 2020 13:26:36 +0000</pubDate>
      <link>https://dev.to/marnold/patching-salt-modules-1fa4</link>
      <guid>https://dev.to/marnold/patching-salt-modules-1fa4</guid>
      <description>&lt;p&gt;Let’s say you were bitten by a critical bug in Salt (2019.2.0 &amp;amp; 2019.2.1 were &lt;a href="https://github.com/saltstack/salt/issues/54755"&gt;painful for many of us&lt;/a&gt;) that had an existing but unreleased fix. Or a cool feature that you badly need has landed in the old develop branch and is waiting forever to be &lt;a href="https://github.com/saltstack/salt-enhancement-proposals/blob/master/accepted/0014-dev-overhaul.md#pr-migration-plan"&gt;migrated to the new master&lt;/a&gt;. Even if it was released, maybe you can’t yet upgrade Salt across the whole minion fleet. Or maybe you were wrongly expecting that your PR against the master branch would be included in the nearest release but were told that the upcoming release is feature frozen, and you have to wait another 4 months for the next one.&lt;/p&gt;

&lt;p&gt;As a DevOps engineer, you need to get things done quickly. Fortunately, SaltStack has a built-in way to patch itself! After reading this guide, you’ll be able to patch almost any part of Salt code, even if you run multiple versions simultaneously. Some of the ways are hacky, but bugs are bugs, and you need to fix them in the most efficient way.&lt;/p&gt;

&lt;p&gt;The methods described are mostly suitable for applying a couple of simple fixes. If you have a large or constantly evolving patchset, you’ll keep yourself sane by making your own fork and installing it via pip or custom packages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Determining affected minions&lt;/li&gt;
&lt;li&gt;How to sync Salt modules&lt;/li&gt;
&lt;li&gt;Extracting upstream patches&lt;/li&gt;
&lt;li&gt;Overriding a whole module&lt;/li&gt;
&lt;li&gt;Adding a new function&lt;/li&gt;
&lt;li&gt;Version-aware guard&lt;/li&gt;
&lt;li&gt;Version-aware override&lt;/li&gt;
&lt;li&gt;Non-syncable modules&lt;/li&gt;
&lt;li&gt;The dreaded utils namespace&lt;/li&gt;
&lt;li&gt;Self-patching&lt;/li&gt;
&lt;li&gt;Bootstrapping patched minions&lt;/li&gt;
&lt;li&gt;Useful commands&lt;/li&gt;
&lt;li&gt;Future developments&lt;/li&gt;
&lt;li&gt;If you need more help&lt;/li&gt;
&lt;li&gt;Unexplored areas&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Determining affected minions
&lt;/h2&gt;

&lt;p&gt;The first thing to do is understand what minion versions you have (you can discover that there are older minions you forgot to upgrade):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;% sudo salt '*' test.version

minion3:
    2019.2.1
minion2:
    2019.2.1
minion1:
    2019.2.3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Another way to do it is the &lt;code&gt;manage.versions&lt;/code&gt; runner (it includes the master version too):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;% sudo salt-run manage.versions

Master:
    2019.2.3
Minion requires update:
    ----------
    minion2:
        2019.2.1
    minion3:
        2019.2.1
Up to date:
    ----------
    minion1:
        2019.2.3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;If that is too verbose, use the &lt;code&gt;survey&lt;/code&gt; runner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;% sudo salt-run survey.hash '*' test.version
|_
  ----------
  pool:
      - minion2
      - minion3
  result:
      2019.2.1
|_
  ----------
  pool:
      - minion1
  result:
      2019.2.3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;And if all you want is just version counters, use the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;% sudo salt '*' test.version --out json --static | jq '.[]' | sort | uniq -c
      2 "2019.2.1"
      1 "2019.2.3"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now you need to understand which of these versions you need to target:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you found a bug, chances are you already know that&lt;/li&gt;
&lt;li&gt;Otherwise, search the &lt;a href="https://github.com/saltstack/salt/issues"&gt;list of issues&lt;/a&gt; and the &lt;a href="https://github.com/saltstack/salt/pulls"&gt;list of pull requests&lt;/a&gt; (including closed ones)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The general advice is to look at the PR contents and see if the changes are applicable to your installed Salt versions.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to sync Salt modules
&lt;/h2&gt;

&lt;p&gt;In the official docs, you can find the &lt;a href="https://docs.saltstack.com/en/master/topics/development/modules/#module-types"&gt;up-to-date list of syncable modules&lt;/a&gt; (contributed by &lt;a href="https://github.com/astronouth7303"&gt;Jamie Bliss&lt;/a&gt; in &lt;a href="https://github.com/saltstack/salt/pull/50633/"&gt;#50633&lt;/a&gt;). The majority of module types could be loaded (synced) from a fileserver, except the ones with a footnote.&lt;/p&gt;

&lt;p&gt;The simplest way to sync them all across a minion fleet is to use the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;% sudo salt '*' saltutil.sync_all
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Alternatively, you can use the individual &lt;a href="https://docs.saltstack.com/en/master/ref/modules/all/salt.modules.saltutil.html#salt.modules.saltutil.sync_beacons"&gt;&lt;code&gt;saltutil.sync_*&lt;/code&gt;&lt;/a&gt; commands to sync different kinds of modules.&lt;/p&gt;

&lt;p&gt;In Salt Neon, you can also use the new &lt;a href="https://github.com/saltstack/salt/pull/50197"&gt;&lt;code&gt;saltutil.sync_*&lt;/code&gt;&lt;/a&gt; states to do the same.&lt;/p&gt;

&lt;p&gt;Some module types could be synced to a master (unfortunately, &lt;a href="https://github.com/saltstack/salt/issues/53914"&gt;excluding rosters&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;% sudo salt-run saltutil.sync_all
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Or individually via the &lt;a href="https://docs.saltstack.com/en/master/ref/runners/all/salt.runners.saltutil.html"&gt;&lt;code&gt;saltutil.sync_*&lt;/code&gt;&lt;/a&gt; runner commands.&lt;/p&gt;

&lt;p&gt;Also, custom modules are automatically synced whenever a highstate is performed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Extracting upstream patches
&lt;/h2&gt;

&lt;p&gt;The general advice is to avoid grabbing the latest modules from the upstream Git repo and putting them into your &lt;code&gt;salt://&lt;/code&gt; file tree. The only scenario when it’ll likely end up well is when a module doesn’t exist yet in your Salt version and doesn’t depend on any other modules. In all other cases, it is much safer to extract a specific patch and adapt it to apply cleanly to your specific Salt version.&lt;/p&gt;

&lt;p&gt;Let’s say you want to backport the ability to pass arbitrary &lt;code&gt;pip&lt;/code&gt; arguments through &lt;code&gt;pip.installed&lt;/code&gt; from Neon to 2019.2.3 (PR &lt;a href="https://github.com/saltstack/salt/pull/52327"&gt;#52327&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;A simple way to extract the feature is to open the &lt;a href="https://github.com/saltstack/salt/pull/52327.diff"&gt;https://github.com/saltstack/salt/pull/52327.diff&lt;/a&gt; link, download the patch and remove unnecessary hunks/files that are related to tests and docs. You can see the resulting patch here: &lt;a href="https://salt.tips/patching-salt-modules/files/pip-extra-args-orig.diff"&gt;pip-extra-args-orig.diff&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The patch won’t apply cleanly; however, if you inspect the &lt;code&gt;salt/modules/pip.py.rej&lt;/code&gt; and &lt;code&gt;salt/states/pip_state.py.rej&lt;/code&gt;, you’ll see that they contain non-functional changes and could be ignored. For more complex conflicts, you’ll need to spend some time to port the patch.&lt;/p&gt;

&lt;p&gt;By comparing the original &lt;code&gt;salt-2019.2.3&lt;/code&gt; tree with the patched one (using the &lt;code&gt;diff -Naur -x '*.orig' -x '*.rej' salt-2019.2.3{.orig,}&lt;/code&gt; command), you can get the patch that applies cleanly to 2019.2.3: &lt;a href="https://salt.tips/patching-salt-modules/files/pip-extra-args-2019-2-3.diff"&gt;pip-extra-args-2019-2-3.diff&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Overriding a whole module
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Grab the patched modules. For the example above, that would be &lt;code&gt;salt/modules/pip.py&lt;/code&gt; and &lt;code&gt;salt/states/pip_state.py&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Put them into the &lt;code&gt;salt://_modules/&lt;/code&gt; and &lt;code&gt;salt://_states/&lt;/code&gt; folders respectively.&lt;/li&gt;
&lt;li&gt;Sync the modules:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;% sudo salt minion1 saltutil.sync_modules,saltutil.sync_states ,

minion1:
    ----------
    saltutil.sync_modules:
        - modules.pip
    saltutil.sync_states:
        - states.pip_state
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Viola! Now you can use the &lt;code&gt;extra_args&lt;/code&gt; option:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ sudo salt minion1 sys.state_doc pip.installed | grep -A 8 extra_args

            extra_args
                pip keyword and positional arguments not yet implemented in salt

                    pandas:
                      pip.installed:
                        - name: pandas
                        - extra_args:
                          - --latest-pip-kwarg: param
                          - --latest-pip-arg

                Warning:

                    If unsupported options are passed here that are not supported in a
                    minion's version of pip, a `No such option error` will be thrown.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Below is an example of how to use this new option to install a package into the &lt;code&gt;/root/.local&lt;/code&gt; directory (instead of &lt;code&gt;/usr/local&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;install_poetry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;pip.installed&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;poetry&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;extra_args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--user&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Adding a new function
&lt;/h2&gt;

&lt;p&gt;Sometimes you want to extend a module with just one new function, but do not want to override the whole module because you are afraid of possible future version conflicts. Let’s say you want to add the &lt;code&gt;delete&lt;/code&gt; function to &lt;code&gt;sdb.sqlite3&lt;/code&gt; module (PR &lt;a href="https://github.com/saltstack/salt/pull/51479"&gt;#51479&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The original module is named &lt;code&gt;sqlite3.py&lt;/code&gt;. Custom one should have a unique file name that contains the original one (see &lt;a href="https://github.com/saltstack/salt/pull/50812#issue-237715602"&gt;#50812&lt;/a&gt; and &lt;a href="https://github.com/saltstack/salt/issues/52521"&gt;#52521&lt;/a&gt; to understand why). Let’s name it as &lt;code&gt;sqlite3_custom.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# The imports below are copied from the original module
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;__future__&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;absolute_import&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;print_function&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unicode_literals&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sqlite3&lt;/span&gt;
    &lt;span class="n"&gt;HAS_SQLITE3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ImportError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;HAS_SQLITE3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

&lt;span class="c1"&gt;# The original module doesn't define a virtualname. However, it is absolutely
# critical to define it in a custom module, otherwise the loader
# won't be able to find our delete function in the same sqlite3 namespace
# (this will happen when the __virtual__ () return value is not a string)
&lt;/span&gt;&lt;span class="n"&gt;__virtualname__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'sqlite3'&lt;/span&gt;

&lt;span class="c1"&gt;# This function is also copied from the original module
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__virtual__&lt;/span&gt; &lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="s"&gt;'''
    Only load if sqlite3 is available.
    '''&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;HAS_SQLITE3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

&lt;span class="c1"&gt;# This is the new function that we want to add
# See https://github.com/saltstack/salt/pull/51479
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="s"&gt;'''
    Delete a key/value pair from sqlite3
    '''&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'delete_query'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'DELETE FROM {0} WHERE '&lt;/span&gt;
                                     &lt;span class="s"&gt;'key=:key'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;'key'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rowcount&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Put the following configuration snippet to &lt;code&gt;/etc/salt/minion.d/mysqlite.conf&lt;/code&gt; on a minion and restart it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;mysqlite&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sqlite3&lt;/span&gt;
  &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/tmp/sdb.sqlite&lt;/span&gt;
  &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sdb&lt;/span&gt;
  &lt;span class="na"&gt;create_table&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Then put the above &lt;code&gt;sqlite3_custom.py&lt;/code&gt; module into the &lt;code&gt;salt://_sdb/&lt;/code&gt; directory and sync it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;% sudo salt minion1 saltutil.sync_sdb

minion1:
    - sdb.sqlite3_custom
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Let’s try it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;% sudo salt minion1 sdb.set sdb://mysqlite/password 12345678

minion1:
    True

% sudo salt minion1 sdb.get sdb://mysqlite/password

minion1:
    12345678

% sudo salt minion1 sdb.delete sdb://mysqlite/password

minion1:
    The minion function caused an exception: Traceback (most recent call last):
      File "/usr/lib/python3/dist-packages/salt/minion.py", line 1664, in _thread_return
        return_data = minion_instance.executors[fname](opts, data, func, args, kwargs)
      File "/usr/lib/python3/dist-packages/salt/executors/direct_call.py", line 12, in execute
        return func(*args, **kwargs)
      File "/usr/lib/python3/dist-packages/salt/modules/sdb.py", line 58, in delete
        return salt.utils.sdb.sdb_delete(uri, __opts__ , __utils__ )
      File "/usr/lib/python3/dist-packages/salt/utils/sdb.py", line 108, in sdb_delete
        return loaded_db[fun](query, profile=profile)
      File "/var/cache/salt/minion/extmods/sdb/sqlite3_custom.py", line 28, in delete
        conn, cur, table = _connect(profile)
    NameError: name '_connect' is not defined
ERROR: Minions returned with non-zero exit code
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Oops! Our custom function needs to have access to &lt;code&gt;_connect&lt;/code&gt;, any related imports, private functions, and module-level variables. Salt loader only exposes public functions (without leading underscores), so any public function calls from the same module should be changed to &lt;code&gt;__salt__ ['sqlite3.function']()&lt;/code&gt; syntax.&lt;/p&gt;

&lt;p&gt;Let’s fix this specific example to make it work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# The import below is copied from the original module
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;__future__&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;absolute_import&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;print_function&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unicode_literals&lt;/span&gt;

&lt;span class="c1"&gt;# This private function is imported from the original module
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;salt.sdb.sqlite3&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;_connect&lt;/span&gt;

&lt;span class="c1"&gt;# The import below is copied from the original module
&lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sqlite3&lt;/span&gt;
    &lt;span class="n"&gt;HAS_SQLITE3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ImportError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;HAS_SQLITE3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

&lt;span class="c1"&gt;# The original module doesn't define a virtualname. However, it is absolutely
# critical to define it in a custom module, otherwise the loader
# won't be able to find our delete function in the same sqlite3 namespace
# (this will happen when the __virtual__ () return value is not a string)
&lt;/span&gt;&lt;span class="n"&gt;__virtualname__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'sqlite3'&lt;/span&gt;

&lt;span class="c1"&gt;# This function is also copied from the original module
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__virtual__&lt;/span&gt; &lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="s"&gt;'''
    Only load if sqlite3 is available.
    '''&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;HAS_SQLITE3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

&lt;span class="c1"&gt;# This is the new function that we want to add
# See https://github.com/saltstack/salt/pull/51479
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="s"&gt;'''
    Delete a key/value pair from sqlite3
    '''&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'delete_query'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'DELETE FROM {0} WHERE '&lt;/span&gt;
                                     &lt;span class="s"&gt;'key=:key'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;'key'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rowcount&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Let’s try it again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;% sudo salt minion1 saltutil.sync_sdb

minion1:
    - sdb.sqlite3_custom

% sudo salt minion1 sdb.delete sdb://mysqlite/password

minion1:
    1

% sudo salt minion1 sdb.get sdb://mysqlite/password

minion1:
    None
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;It works! Please note, that because the overlay module is named differently than the original one, all that initialization code in the upstream module is still triggered. Also, direct imports could potentially have unintended side effects.&lt;/p&gt;

&lt;p&gt;CAVEAT: your custom &lt;code&gt;delete&lt;/code&gt; function will be silently ignored (even with a version-aware guard) if the upstream &lt;code&gt;sqlite3.py&lt;/code&gt; module gets its own &lt;code&gt;delete&lt;/code&gt; function (i.e., after a Salt upgrade).&lt;/p&gt;

&lt;h2&gt;
  
  
  Version-aware guard
&lt;/h2&gt;

&lt;p&gt;It would be nice if a patched module just stopped working with any Salt version that doesn’t match the target one. Then any upgrades would lead to a loud failure, and you won’t forget to remove or update the patched module. Let’s implement exactly that!&lt;/p&gt;

&lt;p&gt;For example, there is an issue &lt;a href="https://github.com/saltstack/salt/issues/54755"&gt;#54755&lt;/a&gt; (&lt;code&gt;pip failures even when not using pip&lt;/code&gt;) that badly affected Salt 2019.2.1. The fix is located in &lt;a href="https://github.com/saltstack/salt/pull/54826"&gt;#54826&lt;/a&gt;; however, it can’t be applied cleanly on top of 2019.2.1 because the module was changed multiple times between the releases. Fortunately, all these changes are relevant, so we can just grab the &lt;a href="https://raw.githubusercontent.com/saltstack/salt/7780a3ed289770a1c02e1ac21a6809563d70aa75/salt/states/pip_state.py"&gt;&lt;code&gt;state/pip_state.py&lt;/code&gt;&lt;/a&gt; module from 2019.2.2 or 2019.2.3. You can inspect the changes using the following command on two extracted release tarballs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;% diff -u salt-2019.2.{1,3}/salt/states/pip_state.py

...
DIFF HERE
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Put the &lt;code&gt;pip_state.py&lt;/code&gt; module from 2019.2.3 into the &lt;code&gt;salt://_states/&lt;/code&gt; directory and sync it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;% sudo salt minion1 saltutil.sync_states

minion1:
    - states.pip_state
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;After that, the state that triggered the error will run just fine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# pip_test_state.sls
/tmp/file.txt:
  file.managed:
    - contents: |
        Content
    - unless:
      - 'grep "Content" /tmp/file.txt'

% sudo salt minion2 state.apply pip_test_state

minion1:
----------
          ID: /tmp/file.txt
    Function: file.managed
      Result: True
     Comment: unless condition is true
     Started: 06:47:19.338294
    Duration: 4058.666 ms
     Changes:

Summary for minion1
------------
Succeeded: 1
Failed: 0
------------
Total states run: 1
Total run time: 4.059 s
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now comes the most important part. We need to add a version-aware guard to the module, so it will only work on 2019.2.1. There are multiple ways to check Salt version:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Compare &lt;code&gt;__grains__ ['saltversion']&lt;/code&gt; with a string like &lt;code&gt;'2019.2.1'&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Compare &lt;code&gt;__grains__ ['saltversioninfo']&lt;/code&gt; with a list like &lt;code&gt;[2019, 2, 1, 0]&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Compare the &lt;code&gt;salt.utils.versions.LooseVersion&lt;/code&gt; instances&lt;/li&gt;
&lt;li&gt;Use the &lt;code&gt;salt.utils.versions.warn_until&lt;/code&gt; helper&lt;/li&gt;
&lt;li&gt;Compare &lt;code&gt;salt.version. __saltstack_version__&lt;/code&gt; with an instance of &lt;code&gt;salt.version.SaltStackVersion&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Compare release codenames using the new Compare release codenames using the new &lt;a href="https://salt.tips/whats-new-in-salt-neon/#salt-version-execution-module"&gt;&lt;code&gt;salt_version&lt;/code&gt;&lt;/a&gt; execution module in Salt Neon&lt;/li&gt;
&lt;li&gt;Use the new &lt;a href="https://salt.tips/whats-new-in-salt-neon/#date-based-deprecation-warning"&gt;&lt;code&gt;salt.utils.versions.warn_until_date&lt;/code&gt;&lt;/a&gt; helper in Salt Neon&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Option (5) seems to be the most robust (it should work with release candidates and git snapshots too, and also support the &lt;a href="https://github.com/saltstack/salt/issues/55896"&gt;new versioning scheme&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;We need to place the version comparison in a module function, which is triggered as early as possible. For the majority of modules, that would be &lt;code&gt;__virtual__ ()&lt;/code&gt; or &lt;code&gt;__init__ (opts)&lt;/code&gt;. The added lines are marked with &lt;code&gt;--- VERSION CHECK ---&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__virtual__&lt;/span&gt; &lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="s"&gt;'''
    Only load if the pip module is available in __salt__
    '''&lt;/span&gt;
    &lt;span class="c1"&gt;# --- VERSION CHECK ---
&lt;/span&gt;    &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;salt.version&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SaltStackVersion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;__saltstack_version__&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;sv&lt;/span&gt;
    &lt;span class="n"&gt;ver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SaltStackVersion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2019&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;u''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sv&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;ver&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'The %s module is not compatible with %s'&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ver&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;critical&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# --- VERSION CHECK END ---
&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;HAS_PKG_RESOURCES&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'The pkg_resources python library is not installed'&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s"&gt;'pip.list'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;__salt__&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;__virtualname__&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now, if you apply the &lt;code&gt;pip.installed&lt;/code&gt; state to 2019.2.3 minion, it will fail because the module is not loaded:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;% sudo salt minion1 state.single pip.installed ipython

minion1:
    Data failed to compile:
----------
    Specified state 'pip.installed' was not found
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Another option is to place this check into the &lt;code&gt;installed(...)&lt;/code&gt; function (right after the docstring and before the function body). This is more risky, because the module will be loaded and some parts could be executed before the check. On the positive side, the failure is more visible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;% sudo salt minion1 state.single pip.installed ipython

minion1:
----------
          ID: ipython
    Function: pip.installed
      Result: False
     Comment: An exception occurred in this state: Traceback (most recent call last):
                File "/usr/lib/python3/dist-packages/salt/state.py", line 1933, in call
                  **cdata['kwargs'])
                File "/usr/lib/python3/dist-packages/salt/loader.py", line 1951, in wrapper
                  return f(*args, **kwargs)
                File "/var/cache/salt/minion/extmods/states/pip_state.py", line 675, in installed
                  raise Exception(msg)
              Exception: The salt.loaded.ext.states.pip_state module is not compatible with 2019.2.1
     Started: 07:49:24.007851
    Duration: 4.435 ms
     Changes:

Summary for minion1
------------
Succeeded: 0
Failed: 1
------------
Total states run: 1
Total run time: 4.435 ms
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;A couple of essential details to summarize:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An overlay module needs to be named exactly as the original one (see the Overriding a whole module section)&lt;/li&gt;
&lt;li&gt;If an overlay module fails with an exception, the original one &lt;a href="https://github.com/saltstack/salt/issues/16439"&gt;won’t be loaded&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;A module that extends the original one needs to contain its name in the filename (see the Adding a new function section)&lt;/li&gt;
&lt;li&gt;If an extension module fails with an exception, the original one will be still loaded&lt;/li&gt;
&lt;li&gt;Because the loader is lazy, the warning would be only triggered when the function is used&lt;/li&gt;
&lt;li&gt;If a module is used implicitly (i.e., not by your states), avoid raising exceptions and instead find other ways to inject the warning&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Version-aware override
&lt;/h2&gt;

&lt;p&gt;So, we got a little safety guard that helps us to detect when our custom modules are outdated. But the rabbit hole is much deeper because we can run multiple Salt versions at the same time and may need to have different versions of custom modules.&lt;/p&gt;

&lt;p&gt;The solution is to use Salt &lt;a href="https://docs.saltstack.com/en/latest/ref/states/top.html#multiple-environments"&gt;environments&lt;/a&gt;. First, set up the &lt;a href="https://docs.saltstack.com/en/latest/ref/file_server/file_roots.html#directory-overlay"&gt;&lt;code&gt;file_roots&lt;/code&gt;&lt;/a&gt; master setting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;file_roots&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;base&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/srv/salt&lt;/span&gt;
  &lt;span class="na"&gt;v20190201&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/srv/salt_20190201&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Second, make sure that any custom modules for 2019.2.1 are placed into the corresponding &lt;code&gt;/srv/salt_20190201/&lt;/code&gt; subdirs (&lt;code&gt;_modules&lt;/code&gt;, &lt;code&gt;_states&lt;/code&gt;, &lt;code&gt;_grains&lt;/code&gt;, etc.) and not into the base environment &lt;code&gt;/srv/salt&lt;/code&gt; dir:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;% tree /srv
├── pillar
│   └── top.sls
├── salt
│   ├── top.sls
├── salt_20190201
│   └── _modules
│   └── test.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;There are two ways to pin minions to specific saltenvs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Using the &lt;a href="https://docs.saltstack.com/en/latest/ref/states/top.html#multiple-environments"&gt;top file&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Using the &lt;a href="https://docs.saltstack.com/en/latest/ref/configuration/minion.html#saltenv"&gt;&lt;code&gt;saltenv&lt;/code&gt;&lt;/a&gt; minion setting&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Unfortunately, the second approach doesn’t affect how the &lt;code&gt;saltutil.sync_*&lt;/code&gt; functions work (no modules are synced). And the explicit &lt;code&gt;saltutil.sync_all saltenv=v20190201&lt;/code&gt; will sync the modules to all minions, ignoring the &lt;code&gt;saltenv&lt;/code&gt; setting. So, the only option left is to specify saltenvs in the top file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;base&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;*'&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[]&lt;/span&gt;
&lt;span class="na"&gt;v20190201&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;saltversion:2019.2.1'&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;grain&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now the &lt;code&gt;saltutil.sync_modules&lt;/code&gt; function will work as intended and sync our custom &lt;code&gt;test.py&lt;/code&gt; only to &lt;code&gt;minion2&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;% salt '*' test.version
minion1:
    2019.2.3
minion2:
    2019.2.1

% sudo salt '*' saltutil.sync_modules
minion1:
minion2:
    - modules.test
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;By adding more environments, you can distribute different custom modules to different minions using any targeting criteria (including the &lt;a href="https://docs.saltstack.com/en/latest/topics/targeting/compound.html"&gt;compound matchers&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;CAVEAT 1: custom modules are cached and won’t be automatically deleted when you update Salt (at least for Ubuntu packages; other distros may contain uninstaller scripts that clear these caches). To mitigate that, run &lt;code&gt;saltutil.clear_cache&lt;/code&gt; before the update (and maybe &lt;code&gt;saltutil.refresh_*&lt;/code&gt; if necessary).&lt;/p&gt;

&lt;p&gt;CAVEAT 2: Fileserver environments won’t be usable for proxymodules until the &lt;a href="https://github.com/saltstack/salt/pull/56222"&gt;#56222&lt;/a&gt; is merged. However, you can fix this problem by using the self-patching method and then proceed as usual.&lt;/p&gt;

&lt;p&gt;If you are paranoid, you can use environments in combination with version-aware guards to prevent modules from being run on updated minions or if you accidentally mess up Salt environments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Non-syncable modules
&lt;/h2&gt;

&lt;p&gt;Some module types do not have a corresponding &lt;code&gt;saltutil.sync_*&lt;/code&gt; command. For example, before Salt Neon, it was impossible to &lt;a href="https://github.com/saltstack/salt/pull/55190"&gt;sync custom executors&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Below is a customized &lt;code&gt;direct_call.py&lt;/code&gt; executor that logs the function name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# -*- coding: utf-8 -*-
# direct_call.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;__future__&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;absolute_import&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;print_function&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unicode_literals&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;logging&lt;/span&gt;

&lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Executing the %s function"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;There are three ways to distribute this custom executor module.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Place the module into a cache dir
&lt;/h3&gt;

&lt;p&gt;The cache dir is defined by the &lt;code&gt;extension_modules&lt;/code&gt; config option (&lt;code&gt;/var/cache/salt/minion/extmods&lt;/code&gt;, there is no need to change it).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# override_executor.sls&lt;/span&gt;
&lt;span class="s"&gt;/var/cache/salt/minion/extmods/executors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;file.directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;makedirs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;True&lt;/span&gt;

&lt;span class="s"&gt;/var/cache/salt/minion/extmods/executors/direct_call.py&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;file.managed&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;salt://_executors/direct_call.py&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;require&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/var/cache/salt/minion/extmods/executors&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;sudo salt minion1 state.apply override_executor&lt;/code&gt; to apply the state.&lt;/p&gt;

&lt;p&gt;The biggest downside is that the cache directory is volatile and may be cleaned (e.g., if you run &lt;code&gt;saltutil.clear_cache&lt;/code&gt;, &lt;code&gt;saltutil.sync_all&lt;/code&gt;, &lt;code&gt;state.highstate&lt;/code&gt;, or &lt;code&gt;state.apply&lt;/code&gt; commands).&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Module search path
&lt;/h3&gt;

&lt;p&gt;Salt has multiple settings to control the search path for each module type (&lt;a href="https://docs.saltstack.com/en/latest/ref/configuration/master.html#extension-modules"&gt;&lt;code&gt;extension_modules&lt;/code&gt;&lt;/a&gt; and various &lt;a href="https://docs.saltstack.com/en/latest/ref/configuration/minion.html#module-dirs"&gt;&lt;code&gt;*_dirs&lt;/code&gt;&lt;/a&gt; options). First, you need to configure the minion. Place the following snippet into &lt;code&gt;/etc/salt/minion.d/extmods.conf&lt;/code&gt; and restart the minion (unfortunately, &lt;a href="https://docs.saltstack.com/en/latest/topics/tutorials/pillar.html#minion-config-in-pillar"&gt;pillar-based minion configuration&lt;/a&gt; doesn’t work in this case):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# /etc/salt/minion.d/extmods.conf&lt;/span&gt;
&lt;span class="na"&gt;executor_dirs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/srv/extmods/executors&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Then run &lt;code&gt;sudo salt minion1 state.apply override_executor&lt;/code&gt; to apply the following state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# override_executor.sls&lt;/span&gt;
&lt;span class="s"&gt;/srv/extmods/executors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;file.directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;makedirs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;True&lt;/span&gt;

&lt;span class="s"&gt;/srv/extmods/executors/direct_call.py&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;file.managed&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;salt://_executors/direct_call.py&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;require&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/srv/extmods/executors&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Setuptools entry points
&lt;/h3&gt;

&lt;p&gt;This is the most complex method; however, it allows installing arbitrary sets of custom modules along with their dependencies. I started writing some examples for this blog post but then abandoned the idea for the sake of brevity. If you are interested in how to do that, visit the &lt;a href="https://docs.saltstack.com/en/latest/topics/tutorials/packaging_modules.html"&gt;Packaging Modules&lt;/a&gt; documentation page.&lt;/p&gt;

&lt;p&gt;Do not forget to add a version-aware guard whenever you override a built-in module.&lt;/p&gt;

&lt;h2&gt;
  
  
  The dreaded utils namespace
&lt;/h2&gt;

&lt;p&gt;Utils are a special breed of modules (despite being syncable through &lt;code&gt;saltutil.sync_utils&lt;/code&gt;). Sometimes they are used through the loader (the &lt;a href="https://docs.saltstack.com/en/latest/topics/development/modules/developing.html#utils"&gt;&lt;code&gt;__utils__&lt;/code&gt;&lt;/a&gt; dunder), but most of the time they are imported directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;% find salt/ -name '*.py' -exec grep -l ' __utils__ \[' \{\} \; | sort -u | wc -l
     159

% find salt/ -name '*.py' -exec grep -l ' salt\.utils' \{\} \; | sort -u | wc -l
    1101
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;There are a couple of problems with &lt;code&gt;utils&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;They are nested, and Salt loader can’t override nested modules (e.g., &lt;code&gt;salt.utils.docker.translate&lt;/code&gt;) because it only supports the &lt;code&gt;module.function&lt;/code&gt; notation&lt;/li&gt;
&lt;li&gt;Although custom utils are added to &lt;code&gt;sys.path&lt;/code&gt; if the &lt;code&gt;utils&lt;/code&gt; cache directory is present, they aren’t importable as &lt;code&gt;salt.utils.*&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Even if they were importable, the minion process wouldn’t reload already imported modules unless restarted&lt;/li&gt;
&lt;li&gt;When utils aren’t called via the &lt;code&gt;__utils__&lt;/code&gt; dunder, &lt;code&gt;__opts__&lt;/code&gt; and other dunders won’t be injected by the loader&lt;/li&gt;
&lt;li&gt;Custom Jinja filters do not work because they are added at import time via the &lt;code&gt;@jinja_filter&lt;/code&gt; decorator, but the loader is lazy and won’t import anything unless it is used somewhere&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This explains the long list of issues that people have when they try to override utils: &lt;a href="https://github.com/saltstack/salt/issues/32500"&gt;#32500&lt;/a&gt;, &lt;a href="https://github.com/saltstack/salt/pull/46841"&gt;#46841&lt;/a&gt;, &lt;a href="https://github.com/saltstack/salt/issues/46911"&gt;#46911&lt;/a&gt;, &lt;a href="https://github.com/saltstack/salt/issues/51719"&gt;#51719&lt;/a&gt;, &lt;a href="https://github.com/saltstack/salt/issues/51958"&gt;#51958&lt;/a&gt;, &lt;a href="https://github.com/saltstack/salt/issues/52136"&gt;#52136&lt;/a&gt;, &lt;a href="https://github.com/saltstack/salt/issues/52222"&gt;#52222&lt;/a&gt;, &lt;a href="https://github.com/saltstack/salt/issues/53666"&gt;#53666&lt;/a&gt;, &lt;a href="https://github.com/saltstack/salt/issues/55232"&gt;#55232&lt;/a&gt;…&lt;/p&gt;

&lt;p&gt;There are two attempts to partially solve this use-case (&lt;a href="https://github.com/saltstack/salt/pull/52001"&gt;#52001&lt;/a&gt; and &lt;a href="https://github.com/saltstack/salt/pull/53167"&gt;#53167&lt;/a&gt; by &lt;a href="https://github.com/aplanas"&gt;Alberto Planas&lt;/a&gt;), but so far none of them has been released.&lt;/p&gt;

&lt;p&gt;I was able to find two ways to deal with this mess when a patch touches both regular modules and utils:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Copy all the changed functions (and functions that use these functions!) from the utils patch into the modules that use it, and change all the calls accordingly&lt;/li&gt;
&lt;li&gt;Alternatively, put the patched utils module into the &lt;code&gt;_utils&lt;/code&gt; dir, then change all the calls to use the &lt;code&gt;__utils__&lt;/code&gt; notation&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With both methods, you’ll end up running two versions of functions from the utils module (the old one is imported as usual, and the newer one is inlined or called from the overlay module).&lt;/p&gt;

&lt;h2&gt;
  
  
  Self-patching
&lt;/h2&gt;

&lt;p&gt;The trick is described by &lt;a href="https://github.com/psy0rz"&gt;DatuX&lt;/a&gt; in &lt;a href="https://github.com/saltstack/salt/issues/51932#issuecomment-493394790"&gt;#51932&lt;/a&gt;. For example, let’s fix the horrible typo that prevented Salt 2019.2.1 from starting when the &lt;code&gt;ping_interval&lt;/code&gt; option was set (see &lt;a href="https://github.com/saltstack/salt/pull/54777"&gt;#54777&lt;/a&gt;). The patch is simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/salt/minion.py b/salt/minion.py
index d1c0c00751..28b4956a49 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/salt/minion.py
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/salt/minion.py
&lt;/span&gt;&lt;span class="p"&gt;@@ -2730,7 +2730,7 @@&lt;/span&gt; class Minion(MinionBase):
                     self._fire_master('ping', 'minion_ping', sync=False, timeout_handler=ping_timeout_handler)
                 except Exception:
                     log.warning('Attempt to ping master failed.', exc_on_loglevel=logging.DEBUG)
&lt;span class="gd"&gt;- self.remove_periodic_callbback('ping', ping_master)
&lt;/span&gt;&lt;span class="gi"&gt;+ self.remove_periodic_callback('ping')
&lt;/span&gt;             self.add_periodic_callback('ping', ping_master, ping_interval)

         # add handler to subscriber

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Put the patch into the &lt;code&gt;salt://patches/files/2019.2.1-periodic-callback.diff&lt;/code&gt; file, then create the following state and put it into &lt;code&gt;salt://patches/periodic_callback.sls&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt; &lt;span class="nv"&gt;if grains.saltversion == "2019.2.1" %&lt;/span&gt;&lt;span class="pi"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Just in case there is no patch command installed&lt;/span&gt;
&lt;span class="na"&gt;patch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;pkg.installed&lt;/span&gt;

&lt;span class="c1"&gt;# We use directory name here (saltpath) to apply patches that touch multiple files&lt;/span&gt;
&lt;span class="na"&gt;periodic_callback_patch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;file.patch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;grains.saltpath&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}'&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;salt://patches/files/2019.2.1-periodic-callback.diff&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;strip&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;require&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;pkg&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;patch&lt;/span&gt;

&lt;span class="c1"&gt;# Set the ping_interval option after the patch is applied&lt;/span&gt;
&lt;span class="na"&gt;set_ping_interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;file.managed&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;salt["file.join"](salt["config.get"]("config_dir"),&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"minion.d",&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"ping_interval.conf")&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}'&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;ping_interval: 60&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;onchanges&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;periodic_callback_patch&lt;/span&gt;

&lt;span class="c1"&gt;# Restart salt minion after the patch is applied and the config option is set&lt;/span&gt;
&lt;span class="c1"&gt;# A naive module.run with service.restart stopped working in 2019.2.1&lt;/span&gt;
&lt;span class="c1"&gt;# (https://github.com/saltstack/salt/issues/55201), so we have to use salt-call&lt;/span&gt;
&lt;span class="c1"&gt;# https://docs.saltstack.com/en/latest/faq.html#restart-using-states&lt;/span&gt;
&lt;span class="na"&gt;restart_salt_minion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;cmd.run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;salt-call&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;service.restart&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;salt-minion'&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;bg&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;onchanges&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;set_ping_interval&lt;/span&gt;

&lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nv"&gt;- endif %&lt;/span&gt;&lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The state will apply the patch, set the required config option, and restart the minion. However, there are a couple of things that could be improved:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The state is idempotent, but to improve the overall performance, we can prevent it from running the second time. You can use &lt;a href="https://github.com/saltstack/salt/issues/51932#issuecomment-493394790"&gt;grain&lt;/a&gt; to mark the patch as applied.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;file.patch&lt;/code&gt; state is not implemented for Windows (see &lt;a href="https://github.com/saltstack/salt/issues/44783"&gt;#44783&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;There are other Windows-specific tweaks that are missing&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Bootstrapping patched minions
&lt;/h2&gt;

&lt;p&gt;All these patching methods are super useful, but it is also essential to know how to apply them to new minions. Earlier I described a few commands that you can use to sync the modules manually. Now it is time to automate that.&lt;/p&gt;

&lt;p&gt;There are many ways to bootstrap Salt minions: salt-bootstrap, salt-ssh, different cloud drivers (including the saltify one), &lt;code&gt;salt-run manage.bootstrap&lt;/code&gt;, and countless custom scripts. The most important thing is to apply any overrides/patches to minions as early as possible (before any other states are run). There are multiple ways to do so:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You do not need to do anything when you apply a highstate. With the &lt;a href="https://docs.saltstack.com/en/latest/ref/configuration/minion.html#autoload-dynamic-modules"&gt;&lt;code&gt;autoload_dynamic_modules&lt;/code&gt;&lt;/a&gt; minion config option (&lt;code&gt;True&lt;/code&gt; by default), Salt runs &lt;code&gt;saltutil.sync_all&lt;/code&gt; automatically before applying the highstate.&lt;/li&gt;
&lt;li&gt;If for some reason, you disabled the above option, you can place a state that syncs modules at the beginning of the &lt;code&gt;top.sls&lt;/code&gt; file, so it will be applied first&lt;/li&gt;
&lt;li&gt;Configure the &lt;a href="https://docs.saltstack.com/en/latest/ref/states/startup.html"&gt;&lt;code&gt;startup_states&lt;/code&gt;&lt;/a&gt; minion setting&lt;/li&gt;
&lt;li&gt;Add a master &lt;a href="https://docs.saltstack.com/en/latest/topics/reactor/#mapping-events-to-reactor-sls-files"&gt;reactor config&lt;/a&gt; for the &lt;code&gt;salt/minion/*/start&lt;/code&gt; event&lt;/li&gt;
&lt;li&gt;Hook into the salt-cloud bootstrap process&lt;/li&gt;
&lt;li&gt;Write a custom bootstrap script&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you need to restart a minion once for the patch to take effect, you can combine methods (3) or (4) with the restart technique. Methods (5) and (6) allow you to patch minions before they are started.&lt;/p&gt;

&lt;h2&gt;
  
  
  Useful commands
&lt;/h2&gt;

&lt;p&gt;A couple of commands to troubleshoot custom modules:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;salt-call&lt;/code&gt; together with print or logging statements (or with any interactive Python debugger like &lt;code&gt;ipdb&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.saltstack.com/en/latest/ref/modules/all/salt.modules.saltutil.html#salt.modules.saltutil.clear_cache"&gt;&lt;code&gt;saltutil.clear_cache&lt;/code&gt;&lt;/a&gt; to clear the &lt;code&gt;extmods&lt;/code&gt; directory&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.saltstack.com/en/master/ref/modules/all/salt.modules.cp.html#salt.modules.cp.list_master"&gt;&lt;code&gt;cp.list_master prefix=_modules/&lt;/code&gt;&lt;/a&gt; (or any other directory) to see which custom modules are available on the fileserver&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.saltstack.com/en/latest/ref/modules/all/salt.modules.saltutil.html#salt.modules.saltutil.list_extmods"&gt;&lt;code&gt;saltutil.list_extmods&lt;/code&gt;&lt;/a&gt; to see the list of synced modules&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.saltstack.com/en/master/ref/modules/all/salt.modules.sysmod.html#salt.modules.sysmod.list_functions"&gt;&lt;code&gt;sys.list_*&lt;/code&gt;&lt;/a&gt; to list different kinds of modules&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Future developments
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;We can expect &lt;a href="https://youtu.be/q-DoWnkte5Y?t=378"&gt;Salt loader refactoring&lt;/a&gt; to happen in 2020&lt;/li&gt;
&lt;li&gt;In 2019 SaltStack introduced &lt;a href="https://www.brighttalk.com/webcast/16869/382336/saltstack-open-source-innovation-a-first-look-at-pop-heist-and-umbra"&gt;POP&lt;/a&gt; (warn: gated content) as a standalone plugin system that is better than Salt loader. So far, no integration plans have been announced.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  If you need more help
&lt;/h2&gt;

&lt;p&gt;Hopefully, this guide has enough details to help you apply any patches you want. However, if you need more help to do that, I’m available for part-time consulting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I can port any existing upstream patch (that is not overly invasive) to an older Salt version. Examples: &lt;a href="https://github.com/saltstack/salt/issues/56118#issuecomment-585081144"&gt;1&lt;/a&gt;, &lt;a href="https://github.com/saltstack/salt/issues/56119#issuecomment-585055831"&gt;2&lt;/a&gt;, &lt;a href="https://github.com/saltstack/salt/issues/56121#issuecomment-585063805"&gt;3&lt;/a&gt;, &lt;a href="https://github.com/saltstack/salt/issues/56131#issuecomment-585086113"&gt;4&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;I can help integrate the patch into your Salt bootstrapping process&lt;/li&gt;
&lt;li&gt;My availability is about ~8-12 hours per week&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;See the &lt;a href="https://salt.tips"&gt;home page footer&lt;/a&gt; for my contact email.&lt;/p&gt;

&lt;h2&gt;
  
  
  Unexplored areas
&lt;/h2&gt;

&lt;p&gt;There are a couple of rabbit holes I wanted to explore but had no time to do so:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to override just one function without copying a whole module?&lt;/li&gt;
&lt;li&gt;Overriding modules using providers&lt;/li&gt;
&lt;li&gt;Why raising exceptions from patched core grains is bad (and what to do instead)?&lt;/li&gt;
&lt;li&gt;Different salt-cloud hooks that could be used to bootstrap patched minions&lt;/li&gt;
&lt;li&gt;How to prevent salt-minion from autostarting (even on Debian/Ubuntu systems) so you can patch it?&lt;/li&gt;
&lt;li&gt;Writing custom bootstrap scripts&lt;/li&gt;
&lt;li&gt;Distributing modules via setuptools entry points (example)&lt;/li&gt;
&lt;li&gt;Patching Salt minions on Windows&lt;/li&gt;
&lt;li&gt;Patching Salt master&lt;/li&gt;
&lt;li&gt;Loader internals (module search path, file loading algorithm)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not a commitment, but I hope to cover these topics in the future (if this post turns out to be useful).&lt;/p&gt;

&lt;h3&gt;
  
  
  Want to learn more about SaltStack patching?
&lt;/h3&gt;

&lt;p&gt;There are a couple of missing aspects I plan to cover in the future (and maybe release an expanded guide in the form of a small &amp;amp; focused ebook). &lt;a href="https://tips.us8.list-manage.com/subscribe?u=79863cb6907503346da4ed704&amp;amp;id=9de0347807"&gt;Subscribe to signal your interest in this topic&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>saltstack</category>
      <category>salt</category>
      <category>iac</category>
    </item>
    <item>
      <title>Text editor plugins for Salt states and YAML/Jinja</title>
      <dc:creator>Max Arnold</dc:creator>
      <pubDate>Sat, 10 Nov 2018 15:19:10 +0000</pubDate>
      <link>https://dev.to/marnold/text-editor-plugins-for-salt-states-and-yamljinja-54pg</link>
      <guid>https://dev.to/marnold/text-editor-plugins-for-salt-states-and-yamljinja-54pg</guid>
      <description>&lt;em&gt;Image by &lt;a href="https://twitter.com/Caged/status/1039937162769096704"&gt;Justin Palmer&lt;/a&gt;&lt;/em&gt;

&lt;p&gt;If you have ever written a couple of Salt states, these problems may sound familiar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A missing colon after the YAML key or a missing space after the colon&lt;/li&gt;
&lt;li&gt;Data failed to compile: State ‘foo’ in SLS ‘bar’ is not formed as a list&lt;/li&gt;
&lt;li&gt;A ParserError or ScannerError while running the &lt;code&gt;state.highstate&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;A nested dictionary, indented with two spaces instead of four&lt;/li&gt;
&lt;li&gt;State requirements at the wrong indentation level&lt;/li&gt;
&lt;li&gt;Indentation with mixed tabs and spaces, which took a surprisingly long time to debug&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And even if your states are valid YAML, they still can have no sense for Salt because your &lt;code&gt;wathc_in&lt;/code&gt; requisite has a typo. Sprinkle a few Jinja tags here and there, and the errors can get even fancier…&lt;/p&gt;

&lt;h4&gt;
  
  
  Is there anything that could be done?
&lt;/h4&gt;

&lt;p&gt;These issues definitely have come up before (&lt;a href="https://github.com/saltstack/salt/issues/802"&gt;#802&lt;/a&gt;). Smart developers probably have this figured out, right?&lt;/p&gt;

&lt;p&gt;Indeed, it is possible to validate your states, in a semi- or completely automated way, even before they touch your infrastructure. For Salt, you have the following options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Use the &lt;a href="https://docs.saltstack.com/en/latest/ref/modules/all/salt.modules.state.html#salt.modules.state.show_sls"&gt;&lt;code&gt;salt-call --local --retcode-passthrough state.show_sls YOUR_STATE&lt;/code&gt;&lt;/a&gt; either manually or using a commit hook. This triggers the state rendering process and can catch tons of possible errors.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Test your states using Test Kitchen together with &lt;a href="https://github.com/saltstack/kitchen-salt"&gt;kitchen-salt&lt;/a&gt; and some verifier. A helpful guide on this topic is available &lt;a href="https://salt-formulas.readthedocs.io/en/latest/develop/testing-formulas.html"&gt;here&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;However, all of this feels a bit heavy. You need to commit and push to run your tests, spin up VMs or containers, dive into Ruby code, and write tests for all possible pillar/grain permutations. Such a long feedback cycle could easily lead to &lt;a href="https://github.com/search?q=fuck+yaml&amp;amp;type=Commits"&gt;&lt;code&gt;git commit -m "Fuck YAML"&lt;/code&gt;&lt;/a&gt; in your commit history and a subsequent amend/rebase/squash dance (and you can’t squash the obscene commit notifications sent to the rest of your team!).&lt;/p&gt;

&lt;h4&gt;
  
  
  Is there something more lightweight?
&lt;/h4&gt;

&lt;p&gt;Well, your development environment can help you. The first layer of defense can work right in your text editor as you type, providing immediate feedback. It will catch your typos before they get committed and deployed (and let the CI system deal with more complicated stuff). An ideal editor plugin can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Help you deal with YAML indentation levels and data structures&lt;/li&gt;
&lt;li&gt;Know and highlight the most common Salt state keywords&lt;/li&gt;
&lt;li&gt;Highlight Jinja brackets and tags&lt;/li&gt;
&lt;li&gt;Autocomplete the keywords&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Below I’ll cover plugins for 9 different editors, so you do not have to switch away from your favorite one. Also, I will give a few installation/configuration tips to save you some googling. These plugins aren’t ideal, but they are open source, and you can contribute to any of them if you want (I &lt;a href="https://github.com/glynnforrest/salt-mode/pull/21"&gt;did&lt;/a&gt;).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Atom&lt;/li&gt;
&lt;li&gt;Eclipse&lt;/li&gt;
&lt;li&gt;Emacs&lt;/li&gt;
&lt;li&gt;Kate&lt;/li&gt;
&lt;li&gt;Midnight Commander&lt;/li&gt;
&lt;li&gt;Sublime Text&lt;/li&gt;
&lt;li&gt;PyCharm&lt;/li&gt;
&lt;li&gt;Vim&lt;/li&gt;
&lt;li&gt;Visual Studio Code&lt;/li&gt;
&lt;li&gt;Final tips&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your favorite editor is not covered here, or you want to share a nifty hack that helps you reduce the number of YAML mistakes, ping me on &lt;a href="https://twitter.com/intent/tweet?in_reply_to=1073154585055952896"&gt;Twitter&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Atom
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GAFhbfsx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://salt.tips/text-editor-plugins-for-salt-states-and-yaml-jinja/salt-atom.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GAFhbfsx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://salt.tips/text-editor-plugins-for-salt-states-and-yaml-jinja/salt-atom.png" alt="SaltStack package for Atom"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Out of the box, Atom understands &lt;code&gt;*.sls&lt;/code&gt; files as YAML. Plus, you could install &lt;a href="https://atom.io/packages/atom-jinja2"&gt;atom-jinja2&lt;/a&gt; and manually switch a file grammar to &lt;code&gt;YAML (Jinja Templates)&lt;/code&gt; by pressing &lt;code&gt;Ctrl+Shift+L&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For better results, you can install both &lt;a href="https://github.com/saltstack/atom-salt"&gt;atom-salt&lt;/a&gt; and &lt;a href="https://github.com/danielchatfield/atom-jinja2"&gt;atom-jinja2&lt;/a&gt;. Just click &lt;code&gt;Edit -&amp;gt; Preferences -&amp;gt; Install&lt;/code&gt;, search for &lt;code&gt;atom-salt&lt;/code&gt; or &lt;code&gt;atom-jinja&lt;/code&gt; and install them.&lt;/p&gt;

&lt;h4&gt;
  
  
  Features
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;The first &lt;code&gt;atom-salt&lt;/code&gt; language scope is &lt;code&gt;source.yaml.salt&lt;/code&gt;, which is automatically enabled for &lt;code&gt;*.sls&lt;/code&gt; files and provides syntax highlighting (YAML+Jinja combo)&lt;/li&gt;
&lt;li&gt;The second scope &lt;code&gt;source.python.salt&lt;/code&gt; is not associated with any file extension and instead is enabled for any files which start with &lt;code&gt;#!py&lt;/code&gt;. This means you get syntax highlighting for &lt;code&gt;py&lt;/code&gt;, &lt;code&gt;pydsl&lt;/code&gt;, and &lt;code&gt;pyobjects&lt;/code&gt; Salt renderers.&lt;/li&gt;
&lt;li&gt;Also, it should open Salt docs via the &lt;code&gt;salt-doc&lt;/code&gt; command, but it didn’t work for me for some reason&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Atom-jinja2&lt;/code&gt; provides the &lt;code&gt;source.yaml.jinja&lt;/code&gt; scope, which is enabled for &lt;code&gt;*.jinja&lt;/code&gt;, &lt;code&gt;*.j2&lt;/code&gt; and a bunch of &lt;a href="https://github.com/danielchatfield/atom-jinja2#new-syntax-definintions"&gt;other extensions&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Supports syntax highlighting and folding&lt;/li&gt;
&lt;li&gt;Has a couple of Jinja-related &lt;a href="https://github.com/danielchatfield/atom-jinja2/blob/master/snippets/atom-jinja2.cson"&gt;snippets&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can assign specific language scope to any extension with something like this in your &lt;code&gt;~/.atom/config.cson&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"*":
  core:
    customFileTypes:
      "source.python.salt": [
        "py.sls"
        "pysls"
      ]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  Other things to try
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://atom.io/packages/language-salt"&gt;Language-salt&lt;/a&gt; - an alternative Atom plugin for Salt (enabled for &lt;code&gt;*.sls&lt;/code&gt; files, provides the &lt;code&gt;source.salt&lt;/code&gt; scope and a bunch of &lt;a href="https://github.com/JonGretar/language-salt/blob/master/snippets/language-salt.cson"&gt;snippets&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://atom.io/packages/aligner-salt"&gt;Aligner-salt&lt;/a&gt; (&lt;a href="https://github.com/adrianlee44/atom-aligner-salt"&gt;GitHub&lt;/a&gt;) to visually align YAML attributes&lt;/li&gt;
&lt;li&gt;The indentation guides should be enabled by default by Salt plugin, but you can enforce it by clicking &lt;code&gt;Edit -&amp;gt; Preferences -&amp;gt; Editor -&amp;gt; Show Indent Guide&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Eclipse
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Y61AZXsM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://salt.tips/text-editor-plugins-for-salt-states-and-yaml-jinja/salt-eclipse.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Y61AZXsM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://salt.tips/text-editor-plugins-for-salt-states-and-yaml-jinja/salt-eclipse.png" alt="SaltStack plugin for Eclipse"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I was unable to find a good enough solution for Eclipse, but below are two directions you can try to explore:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Go to &lt;code&gt;Help -&amp;gt; Eclipse Marketplace&lt;/code&gt;, search for &lt;a href="https://github.com/oyse/yedit"&gt;“yedit”&lt;/a&gt; and install it. Then go to &lt;code&gt;Window -&amp;gt; Preferences -&amp;gt; General -&amp;gt; Editors -&amp;gt; File Associations&lt;/code&gt; and associate the &lt;code&gt;*.sls&lt;/code&gt; extension with YEdit. This enables syntax highlighting for plain YAML files (no luck with Jinja though).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Another plugin is called &lt;a href="http://www.liclipse.com/text/supported_languages.html"&gt;LiClipseText&lt;/a&gt;. It has support for both YAML and Jinja syntaxes, but can’t enable them simultaneously for a single file. It should be possible though, and there are &lt;a href="https://github.com/fabioz/LiClipseText/issues/13"&gt;some hints&lt;/a&gt; if you want to contribute.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Indentation guides are available via a separate &lt;a href="https://marketplace.eclipse.org/content/indent-guide"&gt;indent-guide&lt;/a&gt; plugin.&lt;/p&gt;

&lt;h2&gt;
  
  
  Emacs
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_vj7hdL1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://salt.tips/text-editor-plugins-for-salt-states-and-yaml-jinja/salt-emacs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_vj7hdL1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://salt.tips/text-editor-plugins-for-salt-states-and-yaml-jinja/salt-emacs.png" alt="SaltStack mode for Emacs"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/glynnforrest/salt-mode"&gt;Salt-mode&lt;/a&gt; is your best bet. It is based on &lt;code&gt;yaml-mode&lt;/code&gt; and &lt;code&gt;mmm-mode&lt;/code&gt; and is automatically enabled for &lt;code&gt;*.sls&lt;/code&gt; files. The simplest way to install is through MELPA.&lt;/p&gt;

&lt;h4&gt;
  
  
  Features
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Syntax highlighting for &lt;code&gt;*.sls&lt;/code&gt; files (also possible to enable &lt;code&gt;*.jinja&lt;/code&gt; syntax support)&lt;/li&gt;
&lt;li&gt;Indentation and alignment of expressions and statements&lt;/li&gt;
&lt;li&gt;Spell checking of comments with &lt;code&gt;flyspell&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;salt-mode-browse-doc&lt;/code&gt; to browse the documentation of the state module at point&lt;/li&gt;
&lt;li&gt;Quickly move between Salt state functions&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Things to be aware of
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;No auto-completion &lt;a href="https://github.com/glynnforrest/salt-mode/issues/17"&gt;yet&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;It uses &lt;a href="https://github.com/yoshiki/yaml-mode/"&gt;yaml-mode&lt;/a&gt; under the hood, which needs a new maintainer. If you are willing to step in, there are a number of &lt;a href="https://github.com/yoshiki/yaml-mode/issues"&gt;issues&lt;/a&gt; for you to fix.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Other things to try
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://gitlab.com/emacs-stuff/indent-tools"&gt;Indent-tools&lt;/a&gt; together with &lt;a href="https://github.com/abo-abo/hydra"&gt;hydra&lt;/a&gt; looks like a good combo for whitespace-sensitive languages like YAML and Python.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/DarthFennec/highlight-indent-guides"&gt;Highlight-indent-guides&lt;/a&gt; is worth looking at (although it adds more visual clutter). Also, there is a &lt;a href="https://www.emacswiki.org/emacs/CrosshairHighlighting"&gt;crosshair highlighting&lt;/a&gt; mode.&lt;/li&gt;
&lt;li&gt;Install &lt;a href="https://github.com/stanaka/dash-at-point"&gt;dash-at-point&lt;/a&gt; if you use &lt;a href="https://kapeli.com/dash"&gt;Dash API Documentation Browser&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;To highlight other Jinja-templated configuration files, establish the convention of naming them using the &lt;code&gt;*.jinja&lt;/code&gt; extension and add the following lines to your &lt;code&gt;init.el&lt;/code&gt; (see the &lt;a href="https://raw.githubusercontent.com/purcell/mmm-mode/master/README"&gt;README&lt;/a&gt; for more details):
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(mmm-add-mode-ext-class nil "\\.jinja\\'" 'jinja2)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Kate
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--J8CkTrHY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://salt.tips/text-editor-plugins-for-salt-states-and-yaml-jinja/salt-kate.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--J8CkTrHY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://salt.tips/text-editor-plugins-for-salt-states-and-yaml-jinja/salt-kate.png" alt="SaltStack syntax highlighter for Kate"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Pitmairen/kate-jinja2-highlighting"&gt;Kate-jinja2-highlighting&lt;/a&gt; has a bunch of syntax highlighters for different file types with Jinja on top.&lt;/p&gt;

&lt;h4&gt;
  
  
  Features
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Highlights &lt;code&gt;*.jinja&lt;/code&gt;, &lt;code&gt;*.jinja2&lt;/code&gt; and &lt;code&gt;*.j2&lt;/code&gt; using Jinja2 syntax&lt;/li&gt;
&lt;li&gt;Highlights &lt;code&gt;*.yaml&lt;/code&gt;, &lt;code&gt;*.yml&lt;/code&gt; and &lt;code&gt;*.sls&lt;/code&gt; files using YAML+Jinja2 syntax (if you apply the patch below)&lt;/li&gt;
&lt;li&gt;If you want to enable indentation guide, toggle the &lt;code&gt;Settings -&amp;gt; Configure Kate -&amp;gt; Editor Component -&amp;gt; Appearance -&amp;gt; Show indentation lines&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To install it, copy the &lt;code&gt;*.xml&lt;/code&gt; files into the &lt;code&gt;~/.local/share/org.kde.syntax-highlighting/syntax/&lt;/code&gt; folder. Then go to &lt;code&gt;Settings -&amp;gt; Configure Kate -&amp;gt; Editor Component -&amp;gt; Open/Save -&amp;gt; Modes &amp;amp; Filetypes&lt;/code&gt;, select &lt;code&gt;Markup/Jinja2/YAML&lt;/code&gt;, open the &lt;code&gt;Variables&lt;/code&gt; drop-down, and then set both &lt;code&gt;indent-width&lt;/code&gt; &lt;code&gt;tab-width&lt;/code&gt; to &lt;code&gt;2&lt;/code&gt; and &lt;code&gt;Indent using&lt;/code&gt; to &lt;code&gt;Spaces&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You also need to associate &lt;code&gt;*.sls&lt;/code&gt; files with the syntax highlighter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sed -i'' -e 's/\(\*\.yml\)"/\1;*.sls"/' yaml-jinja2.xml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;sed&lt;/code&gt; command above is equivalent to the patch below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--- yaml-jinja2.xml.orig    2018-10-25 02:39:31.000000000 +0700
+++ yaml-jinja2.xml 2018-10-25 02:39:40.000000000 +0700
@@ -3,7 +3,7 @@
 &amp;lt;!-- Author: Dr Orlovsky MA &amp;lt;maxim@orlovsky.info&amp;gt; //--&amp;gt;
 &amp;lt;!-- Autogenerated from yaml.xml --&amp;gt;
 &amp;lt;language name="Jinja2/YAML" version="1.2" kateversion="2.3" section="Markup"
- extensions="*.yaml;*.yml" mimetype="text/yaml"
+ extensions="*.yaml;*.yml;*.sls" mimetype="text/yaml"
           author="Dr Orlovsky MA (dr.orlovsky@gmail.com)" license="LGPL"&amp;gt;
   &amp;lt;highlighting&amp;gt;
     &amp;lt;contexts&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Midnight Commander
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vnrrwuyT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://salt.tips/text-editor-plugins-for-salt-states-and-yaml-jinja/salt-midnight-commander.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vnrrwuyT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://salt.tips/text-editor-plugins-for-salt-states-and-yaml-jinja/salt-midnight-commander.png" alt="SaltStack syntax highlighter for Midnight Commander"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Midnight Commander is an oldie but goodie for those of us who used &lt;a href="https://en.wikipedia.org/wiki/File_manager#Orthodox_file_managers"&gt;orthodox file managers&lt;/a&gt; back in the 90’s and can’t live without one. Personally, I use it to quickly navigate the filesystem tree and view the files without opening them in my primary editor. According to &lt;a href="https://midnight-commander.org/ticket/3293"&gt;this ticket&lt;/a&gt;, out of the box, &lt;code&gt;mcedit&lt;/code&gt; highlights &lt;code&gt;*.yml&lt;/code&gt; and &lt;code&gt;*.yaml&lt;/code&gt; files and understands simple Jinja variables (no support for tags though). Also, it does not understand &lt;code&gt;*.jinja&lt;/code&gt; files.&lt;/p&gt;

&lt;p&gt;To enable &lt;code&gt;*.sls&lt;/code&gt; highlighting you need to make a copy of the default &lt;code&gt;Syntax&lt;/code&gt; file and tweak it a bit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir -p ~/.config/mc/mcedit
sed -e 's/\(|YML)\)/|sls\1/' /usr/share/mc/syntax/Syntax &amp;gt; ~/.config/mc/mcedit/Syntax
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;sed&lt;/code&gt; command above is equivalent to the patch below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--- /home/user/.config/mc/mcedit/Syntax.orig 2017-08-05 04:03:29.000000000 +0700
+++ /home/user/.config/mc/mcedit/Syntax 2018-10-10 19:50:11.390542735 +0700
@@ -271,7 +271,7 @@
 file ..\*\\.cl$ OpenCL\sProgram
 include opencl.syntax

-file ..\*\\.(ya?ml|YML)$ YAML\sFile
+file ..\*\\.(ya?ml|sls|YML)$ YAML\sFile
 include yaml.syntax

 file .\*\\.osl$ OSL\sProgram
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  PyCharm
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--x5rzi00C--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://salt.tips/text-editor-plugins-for-salt-states-and-yaml-jinja/salt-pycharm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--x5rzi00C--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://salt.tips/text-editor-plugins-for-salt-states-and-yaml-jinja/salt-pycharm.png" alt="SaltStack support in PyCharm"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Well, it looks there are &lt;a href="https://github.com/vermut/intellij-snakeyaml/issues/14"&gt;no&lt;/a&gt; Salt-specific plugins for PyCharm. Ansible users are luckier here - PyCharm has support for &lt;a href="http://schemastore.org/json/"&gt;JSON Schemas&lt;/a&gt;, and someone from RedHat has created &lt;a href="https://github.com/shaded-enmity/ansible-schema-generator"&gt;one&lt;/a&gt;. This means code auto-completion for Ansible-specific YAML data structures right out of the box! Plus, there is a separate &lt;a href="https://plugins.jetbrains.com/plugin/7792-yaml-ansible-support"&gt;YAML/Ansible&lt;/a&gt; plugin (&lt;a href="https://github.com/vermut/intellij-ansible/"&gt;GitHub&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;However (aside from using Ansible plugins), you can tweak PyCharm to make it work with Salt states. Click &lt;code&gt;File -&amp;gt; Settings -&amp;gt; Languages &amp;amp; Frameworks -&amp;gt; Python Template Languages&lt;/code&gt;, select &lt;code&gt;Jinja2&lt;/code&gt; in the &lt;code&gt;Template language&lt;/code&gt; drop-down, click the “+” button and select &lt;code&gt;YAML&lt;/code&gt;. This should enable syntax highlighting for YAML+Jinja combo. To associate it with the &lt;code&gt;*.sls&lt;/code&gt; files, go to &lt;code&gt;File -&amp;gt; Settings -&amp;gt; Editor -&amp;gt; File Types&lt;/code&gt;, find &lt;code&gt;YAML&lt;/code&gt; and add the &lt;code&gt;*.sls&lt;/code&gt; extension.&lt;/p&gt;

&lt;h4&gt;
  
  
  Other features
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;You can reformat YAML code with the &lt;code&gt;Reformat Code&lt;/code&gt; action or via a shortcut: &lt;code&gt;Cmd + Alt + L&lt;/code&gt; on MacOS / &lt;code&gt;Ctrl + Alt + L&lt;/code&gt; on Windows and Linux&lt;/li&gt;
&lt;li&gt;To customize YAML code style settings, visit &lt;code&gt;File -&amp;gt; Settings -&amp;gt; Editor -&amp;gt; Code Style -&amp;gt; YAML&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Indentation guides are enabled by default, but you can disable them via &lt;code&gt;File -&amp;gt; Settings -&amp;gt; Editor -&amp;gt; General -&amp;gt; Appearance -&amp;gt; Show indent guides&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Sublime Text
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DFwH9SQ1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://salt.tips/text-editor-plugins-for-salt-states-and-yaml-jinja/salt-sublime-text.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DFwH9SQ1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://salt.tips/text-editor-plugins-for-salt-states-and-yaml-jinja/salt-sublime-text.png" alt="SaltStack plugin for Sublime Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The extension is available on &lt;a href="https://github.com/saltstack/sublime-text"&gt;GitHub&lt;/a&gt;. To install it click &lt;code&gt;Preferences -&amp;gt; Package Control -&amp;gt; Install Package&lt;/code&gt;, search for &lt;code&gt;saltstack&lt;/code&gt; and click to install. It is also recommended to install the &lt;a href="https://github.com/kudago/jinja2-tmbundle"&gt;separate extension&lt;/a&gt; for Jinja (go to &lt;code&gt;Package Control&lt;/code&gt; and search for &lt;code&gt;jinja2&lt;/code&gt;).&lt;/p&gt;

&lt;h4&gt;
  
  
  Features
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Syntax highlighting for &lt;code&gt;*.sls&lt;/code&gt; files&lt;/li&gt;
&lt;li&gt;Syntax highlighting for &lt;code&gt;*.j2&lt;/code&gt; files if you install the Jinja extension&lt;/li&gt;
&lt;li&gt;Several &lt;a href="https://github.com/saltstack/sublime-text/tree/master/Snippets/Jinja"&gt;snippets&lt;/a&gt; for Jinja (forked from &lt;a href="https://github.com/jpvanhal/sublime-jinja2"&gt;sublime-jinja2&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Several &lt;a href="https://github.com/saltstack/sublime-text/tree/master/Snippets/SaltStack"&gt;snippets&lt;/a&gt; for often used Salt state modules like &lt;code&gt;file&lt;/code&gt; and &lt;code&gt;pkg&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Indentation guides are enabled by default, but you may also want to set &lt;code&gt;"indent_guide_options": ["draw_normal", "draw_active"]&lt;/code&gt; in the settings to highlight currently active guide.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vim
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xbxriINJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://salt.tips/text-editor-plugins-for-salt-states-and-yaml-jinja/salt-vim.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xbxriINJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://salt.tips/text-editor-plugins-for-salt-states-and-yaml-jinja/salt-vim.png" alt="SaltStack plugin for Vim"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You need both &lt;a href="https://github.com/saltstack/salt-vim.git"&gt;salt-vim&lt;/a&gt; and &lt;a href="https://github.com/Glench/Vim-Jinja2-Syntax.git"&gt;vim-jinja2-syntax&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Features
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Syntax highlighting for &lt;code&gt;*.sls&lt;/code&gt; files and &lt;code&gt;Saltfile&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Syntax highlighting for &lt;code&gt;*.jinja2&lt;/code&gt;, &lt;code&gt;*.j2&lt;/code&gt; and &lt;code&gt;*.jinja&lt;/code&gt; files&lt;/li&gt;
&lt;li&gt;Proper indentation settings&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Space&lt;/code&gt; key will try to fold/unfold an area&lt;/li&gt;
&lt;li&gt;A visually selected block might be indented and unindented with &lt;code&gt;&amp;lt;&lt;/code&gt; and &lt;code&gt;&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are using Vim 8 or higher, you may skip the Pathogen/Vundle route and install the plugins directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir -p ~/.vim/pack/plugins/start
cd !$
git clone https://github.com/saltstack/salt-vim.git salt.vim
git clone https://github.com/Glench/Vim-Jinja2-Syntax.git jinja2.vim
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Then add the following lines to your &lt;code&gt;.vimrc&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;syntax on
set nocompatible
set modeline
filetype plugin indent on
let g:sls_use_jinja_syntax = 1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;You might also consider &lt;a href="https://github.com/thaerkh/vim-indentguides"&gt;vim-indentguides&lt;/a&gt;, &lt;a href="https://github.com/nathanaelkane/vim-indent-guides"&gt;vim-indent-guides&lt;/a&gt; or &lt;a href="https://github.com/Yggdroot/indentLine"&gt;indentLine&lt;/a&gt;. Some folks use &lt;a href="http://vim.wikia.com/wiki/Highlight_current_line"&gt;crosshair highlighting&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Visual Studio Code
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qsvVwRFe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://salt.tips/text-editor-plugins-for-salt-states-and-yaml-jinja/salt-vscode.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qsvVwRFe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://salt.tips/text-editor-plugins-for-salt-states-and-yaml-jinja/salt-vscode.png" alt="SaltStack extension for Visual Studio Code"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The VSCode extension for SaltStack is available on the &lt;a href="https://marketplace.visualstudio.com/items?itemName=korekontrol.saltstack"&gt;Marketplace&lt;/a&gt;. To install it click &lt;code&gt;File -&amp;gt; Preferences -&amp;gt; Extensions&lt;/code&gt;, then search for &lt;code&gt;saltstack&lt;/code&gt; and click install. The source code is available on &lt;a href="https://github.com/korekontrol/vscode-saltstack"&gt;GitHub&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Features
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Has syntax highlighting for &lt;code&gt;*.sls&lt;/code&gt; files (understands YAML+Jinja combo)&lt;/li&gt;
&lt;li&gt;Autocompletion of Jinja brackets&lt;/li&gt;
&lt;li&gt;Folding of indented lines in YAML&lt;/li&gt;
&lt;li&gt;Also supports &lt;code&gt;*.j2&lt;/code&gt; and &lt;code&gt;*.jinja&lt;/code&gt; extensions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you get annoyed by extra closing bracket added by autocompletion &lt;code&gt;{%%}}&lt;/code&gt;, add the following snippet to your &lt;code&gt;settings.json&lt;/code&gt; file (&lt;code&gt;File -&amp;gt; Preferences -&amp;gt; Settings&lt;/code&gt;, then press the &lt;code&gt;{}&lt;/code&gt; icon to open User Settings):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"[sls]"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"editor.autoClosingBrackets"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"never"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Final tips
&lt;/h2&gt;

&lt;h4&gt;
  
  
  For teams
&lt;/h4&gt;

&lt;p&gt;If you have a team of engineers who regularly contribute to a common Salt state tree, it is essential to establish some YAML/Jinja style conventions.&lt;/p&gt;

&lt;p&gt;You could add the following boilerplate to each state file, but it is easy to forget and requires some discipline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# -*- coding: utf-8; mode: salt; -*-
# vim: ft=sls
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Alternatively, you could ask engineers to set up their editors properly. Or you can adopt &lt;a href="http://editorconfig.org/"&gt;http://editorconfig.org/&lt;/a&gt; - just create a &lt;code&gt;.editorconfig&lt;/code&gt; in your project root, and any compatible editor will automatically pick up your coding convention.&lt;/p&gt;

&lt;p&gt;Also, it makes sense to add &lt;code&gt;.jinja&lt;/code&gt; or &lt;code&gt;.j2&lt;/code&gt; suffix to all config templates managed by Salt, to help most editors recognize the syntax.&lt;/p&gt;

&lt;h4&gt;
  
  
  YAML docs
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://kapeli.com/cheat_sheets/YAML.docset/Contents/Resources/Documents/index"&gt;YAML Cheatsheet&lt;/a&gt; (also could be used with &lt;a href="https://kapeli.com/dash"&gt;Dash&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://symfony.com/doc/current/components/yaml/yaml_format.html"&gt;The YAML Format&lt;/a&gt; (from the Symfony docs)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://yaml-multiline.info/"&gt;YAML Multiline&lt;/a&gt; (a nice cheatsheet for YAML multiline strings)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.saltstack.com/en/latest/topics/yaml/"&gt;Understanding YAML&lt;/a&gt; (from the Salt docs)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.saltstack.com/en/latest/topics/troubleshooting/yaml_idiosyncrasies.html"&gt;YAML Idiosyncrasies&lt;/a&gt; (from the Salt docs)&lt;/li&gt;
&lt;li&gt;
&lt;a href="http://yaml.org/spec/1.2/spec.html"&gt;YAML Specification&lt;/a&gt; (authoritative and comprehensive, but not so easy to read)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Jinja docs
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="http://jinja.pocoo.org/docs/templates/"&gt;Jinja Template Designer Documentation&lt;/a&gt; (please choose the appropriate Jinja version to match the one installed in your Salt environment)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.saltstack.com/en/latest/topics/jinja/index.html"&gt;Understanding Jinja&lt;/a&gt; (from the Salt docs)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Online YAML parsers/linters
&lt;/h4&gt;

&lt;p&gt;These could be helpful if you don’t want to or can’t use any editor plugins:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/Inveracity/jinjabread"&gt;https://github.com/Inveracity/jinjabread&lt;/a&gt; (made specifically for Salt)&lt;/li&gt;
&lt;li&gt;&lt;a href="http://yaml-online-parser.appspot.com/"&gt;http://yaml-online-parser.appspot.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.json2yaml.com/"&gt;https://www.json2yaml.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.yamllint.com/"&gt;http://www.yamllint.com/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also, YAML is a &lt;a href="http://yaml.org/spec/1.2/spec.html#id2759572"&gt;superset of JSON&lt;/a&gt;, so you could just use square brackets for lists and curly ones for dictionaries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/etc/http/conf/http.conf:
  file.managed:
    - source: salt://apache/http.conf
    - mode: 644
    - template: jinja
    - context: { custom_var: "override" }
    - defaults: { custom_var: "default value", other_var: 123 }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;






&lt;p&gt;If you want to be notified about tips like this one, you can subscribe to the &lt;a href="https://tips.us8.list-manage.com/subscribe?u=79863cb6907503346da4ed704&amp;amp;id=9de0347807"&gt;mailing list&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Also, you can follow me on &lt;a href="https://twitter.com/SaltTips"&gt;Twitter&lt;/a&gt; where I periodically post things like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://twitter.com/SaltTips"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--a59MRwMB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://salt.tips/text-editor-plugins-for-salt-states-and-yaml-jinja/tweet-example.png" alt="SaltTips tweet"&gt; &lt;/a&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>yaml</category>
      <category>saltstack</category>
      <category>salt</category>
    </item>
  </channel>
</rss>
