diff --git a/doc/ref/configuration/minion.rst b/doc/ref/configuration/minion.rst index 23f4bec3306a..a5d77145a1d3 100644 --- a/doc/ref/configuration/minion.rst +++ b/doc/ref/configuration/minion.rst @@ -652,6 +652,35 @@ FQDN (for instance, Solaris). append_domain: foo.org +.. conf_minion:: minion_id_remove_domain + +``minion_id_remove_domain`` +--------------------------- + +.. versionadded:: Neon + +Default: ``False`` + +Remove a domain when the minion id is generated as a fully qualified domain +name (either by the user provided ``id_function``, or by Salt). This is useful +when the minions shall be named like hostnames. Can be a single domain (to +prevent name clashes), or True, to remove all domains. + +Examples: + - minion_id_remove_domain = foo.org + - FQDN = king_bob.foo.org --> minion_id = king_bob + - FQDN = king_bob.bar.org --> minion_id = king_bob.bar.org + - minion_id_remove_domain = True + - FQDN = king_bob.foo.org --> minion_id = king_bob + - FQDN = king_bob.bar.org --> minion_id = king_bob + + +For more information, please see :issue:`49212` and :pull:`49378`. + +.. code-block:: yaml + + minion_id_remove_domain: foo.org + .. conf_minion:: minion_id_lowercase ``minion_id_lowercase`` diff --git a/salt/config/__init__.py b/salt/config/__init__.py index b2f4350c57f9..b4ef2ff7bac4 100644 --- a/salt/config/__init__.py +++ b/salt/config/__init__.py @@ -954,6 +954,9 @@ def _gather_buffer_space(): # Always generate minion id in lowercase. 'minion_id_lowercase': bool, + # Remove either a single domain (foo.org), or all (True) from a generated minion id. + 'minion_id_remove_domain': (six.string_types, bool), + # If set, the master will sign all publications before they are sent out 'sign_pub_messages': bool, @@ -1440,6 +1443,7 @@ def _gather_buffer_space(): 'grains_refresh_every': 0, 'minion_id_caching': True, 'minion_id_lowercase': False, + 'minion_id_remove_domain': False, 'keysize': 2048, 'transport': 'zeromq', 'auth_timeout': 5, @@ -3565,6 +3569,26 @@ def call_id_function(opts): sys.exit(salt.defaults.exitcodes.EX_GENERIC) +def remove_domain_from_fqdn(opts, newid): + ''' + Depending on the values of `minion_id_remove_domain`, + remove all domains or a single domain from a FQDN, effectivly generating a hostname. + ''' + opt_domain = opts.get('minion_id_remove_domain') + if opt_domain is True: + if '.' in newid: + # Remove any domain + newid, xdomain = newid.split('.', 1) + log.debug('Removed any domain (%s) from minion id.', xdomain) + else: + # Must be string type + if newid.upper().endswith('.' + opt_domain.upper()): + # Remove single domain + newid = newid[:-len('.' + opt_domain)] + log.debug('Removed single domain %s from minion id.', opt_domain) + return newid + + def get_id(opts, cache_minion_id=False): ''' Guess the id of the minion. @@ -3616,6 +3640,11 @@ def get_id(opts, cache_minion_id=False): if opts.get('minion_id_lowercase'): newid = newid.lower() log.debug('Changed minion id %s to lowercase.', newid) + + # Optionally remove one or many domains in a generated minion id + if opts.get('minion_id_remove_domain'): + newid = remove_domain_from_fqdn(opts, newid) + if '__role' in opts and opts.get('__role') == 'minion': if opts.get('id_function'): log.debug( diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py index 5538cf9714f6..a52fcbd842f6 100644 --- a/tests/unit/test_config.py +++ b/tests/unit/test_config.py @@ -707,6 +707,79 @@ def test_minion_id_lowercase(self, tempdir): self.assertEqual(config['minion_id_lowercase'], True) # Check the configuration self.assertEqual(config['id'], 'king_bob') + @with_tempdir() + def test_minion_id_remove_domain_string_positive(self, tempdir): + ''' + This tests that the values of `minion_id_remove_domain` is suppressed from a generated minion id, + effectivly generating a hostname minion_id. + ''' + minion_config = os.path.join(tempdir, 'minion') + with salt.utils.files.fopen(minion_config, 'w') as fp_: + fp_.write(textwrap.dedent('''\ + id_function: + test.echo: + text: king_bob.foo.org + minion_id_remove_domain: foo.org + minion_id_caching: False + ''')) + + # Let's load the configuration + config = sconfig.minion_config(minion_config) + self.assertEqual(config['minion_id_remove_domain'], 'foo.org') + self.assertEqual(config['id'], 'king_bob') + + @with_tempdir() + def test_minion_id_remove_domain_string_negative(self, tempdir): + ''' + See above + ''' + minion_config = os.path.join(tempdir, 'minion') + with salt.utils.files.fopen(minion_config, 'w') as fp_: + fp_.write(textwrap.dedent('''\ + id_function: + test.echo: + text: king_bob.foo.org + minion_id_remove_domain: bar.org + minion_id_caching: False + ''')) + + config = sconfig.minion_config(minion_config) + self.assertEqual(config['id'], 'king_bob.foo.org') + + @with_tempdir() + def test_minion_id_remove_domain_bool_true(self, tempdir): + ''' + See above + ''' + minion_config = os.path.join(tempdir, 'minion') + with salt.utils.files.fopen(minion_config, 'w') as fp_: + fp_.write(textwrap.dedent('''\ + id_function: + test.echo: + text: king_bob.foo.org + minion_id_remove_domain: True + minion_id_caching: False + ''')) + config = sconfig.minion_config(minion_config) + self.assertEqual(config['id'], 'king_bob') + + @with_tempdir() + def test_minion_id_remove_domain_bool_false(self, tempdir): + ''' + See above + ''' + minion_config = os.path.join(tempdir, 'minion') + with salt.utils.files.fopen(minion_config, 'w') as fp_: + fp_.write(textwrap.dedent('''\ + id_function: + test.echo: + text: king_bob.foo.org + minion_id_remove_domain: False + minion_id_caching: False + ''')) + config = sconfig.minion_config(minion_config) + self.assertEqual(config['id'], 'king_bob.foo.org') + @with_tempdir() def test_backend_rename(self, tempdir): '''