SaltStack State Modules - 状态模块开发技巧

原文链接: https://docs.saltstack.com/en/latest/ref/states/writing.html

状态模块是映射到Salt状态的实际执行和管理的组件。

您也可以参考在Github上维护的这一份技术资料:State Modules

States are Easy to Write! - 开发一个状态模块很容易

状态模块易于编写且简单明了。 传递到SLS数据结构的信息将直接映射到状态模块。

从SLS数据映射信息很简单,此示例可以说明:

/etc/salt/master: # maps to "name", unless a "name" argument is specified below
  file.managed: # maps to . - e.g. "managed" in https://github.com/saltstack/salt/tree/develop/salt/states/file.py
    - user: root # one of many options passed to the manage function
    - group: root
    - mode: 644
    - source: salt://salt/master

因此,此SLS数据可以直接链接到模块、函数以及传递给该函数的参数。

这确实带来了一点负担,因为函数名称、状态名称和函数参数直接定义用户接口,因此它们在状态模块内部应该非常易于阅读。

Keyword Arguments - 关键字参数

Salt在编译、渲染状态时将许多关键字参数传递给状态,包括环境、状态的唯一标识符等。 此外,请记住,状态的必要条件是关键字参数的一部分。 因此,如果您需要在状态中遍历关键字参数,则必须适当考虑和处理这些参数。 一个这样的示例是关于pkgrepo.managed状态的使用,该状态需要能够处理任意关键字参数并将其传递给模块执行功能。 可以在此处找到如何处理这些关键字参数的示例。

Best Practices - 最佳实践

编写良好的状态函数将遵循以下步骤:

注意:这是一个极其简化的示例。 可以通过浏览Salt状态模块的源代码以查看其他示例。

  1. 设置返回数据字典并执行任何必要的输入验证(类型检查,寻找互斥参数的使用等)。

    ret = {'name': name,
           'result': False,
           'changes': {},
           'comment': ''}
    
    if foo and bar:
        ret['comment'] = 'Only one of foo and bar is permitted'
        return ret
    
  2. 检查是否需要进行更改。 最好通过附带的执行模块中的信息收集功能来完成此操作。 该状态应该能够使用该函数的返回值来判断该minion是否已经处于所需状态。

    result = __salt__['modname.check'](name)
    
  3. 如果步骤2发现minion已经处于所需状态,则立即退出,并返回True结果,而无需进行任何更改。

    if result:
        ret['result'] = True
        ret['comment'] = '{0} is already installed'.format(name)
        return ret
    
  4. 如果步骤2发现确实需要进行更改,请检查状态是否在测试模式下运行(即使用test=True)。 如果是这样,则退出并返回值为“None”的结果、相关注释和(如有可能)将进行哪些更改的描述。

    if __opts__['test']:
        ret['result'] = None
        ret['comment'] = '{0} would be installed'.format(name)
        ret['changes'] = result
        return ret
    
  5. 执行所需的更改。 应该再次使用附带的执行模块中的函数来完成此操作。 如果该函数的结果足以告诉您是否发生了错误,则可以退出并返回False结果和相关注释以说明发生了什么。

    result = __salt__['modname.install'](name)
    
  6. 再次从步骤2执行相同的检查,以确认minion是否处于所需状态。 就像在第2步中一样,此函数应该能够通过其返回数据来告诉您是否需要进行更改。

    ret['changes'] = __salt__['modname.check'](name)
    

    如您所见,我们将返回字典中的changes键设置为modname.check函数的结果(就像我们在步骤4中所做的一样)。 这里的假设是,信息收集功能将返回一个字典,解释需要进行哪些更改。 这可能适合您的用例,也可能不适合。

  7. 设置返回数据并返回!

    if ret['changes']:
        ret['comment'] = '{0} failed to install'.format(name)
    else:
        ret['result'] = True
        ret['comment'] = '{0} was installed'.format(name)
    
    return ret
    

Using Custom State Modules - 使用自定义的State模块

在使用状态模块之前,必须将其分发给各minions。 可以通过将它们放入salt://_states/中来完成。 然后可以通过运行saltutil.sync_statessaltutil.sync_all将它们手动分发给minions。 或者,在运行highstate状态时,自定义状态类型将自动同步。

注意:使用用文件名中写带连字符的状态模块会导致!pyobjects例程出现问题。 请坚持使用下划线的最佳做法。

任何已与minions同步的自定义状态(与Salt的默认状态集中的名称之一相同的)将替换具有相同名称的默认状态。 请注意,状态模块的名称根据其文件名默认为同一个(即foo.py变为状态模块foo),但是可以使用virtual函数覆盖其名称。

Cross Calling Execution Modules from States - 在States状态文件中交叉调用执行模块

与执行模块一样,状态模块也可以使用__salt____grains__数据。 请参阅[交叉调用执行模块](https://github.com/watermelonbig/SaltStack-Chinese-ManualBook/blob/master/chapter06/06-3.Writing-Execution-Modules.md#Cross-Calling-Execution Modules—Salt执行模块的交叉调用)。

重要的是要注意,除非需要,否则不应在状态模块中完成状态管理的实际工作。 pkg状态模块就是一个很好的例子。 该模块不执行任何软件包管理工作,仅调用pkg执行模块。 这使得pkg状态模块完全通用,这就是为什么只有一个pkg状态模块和许多后端pkg执行模块的原因。

另一方面,某些模块将要求将处理逻辑放置在状态模块中,文件模块就是一个很好的例子。 但是在大多数情况下,这不是最佳方法,编写特定的执行模块来执行后端工作将是最佳解决方案。

Cross Calling State Modules - 在States状态文件中交叉调用状态模块

所有的Salt状态模块对彼此可用,并且状态模块可以调用其他状态模块中可用的功能。

将变量__states__加载到模块中后,将其包装到模块中。

__states__变量是一个包含所有状态模块的Python字典。 字典的key是代表模块名称的字符串,值是函数本身。

可以通过访问__states__ dict中的值来交叉调用Salt状态模块:

ret = __states__['file.managed'](name='/tmp/myfile', source='salt://myfile')

此代码将在file状态模块中调用managed函数,并将参数名称和源传递给它。

Return Data - 返回数据

一个状态模块必须返回包含以下键/值数据的字典:

  • name:与传递给状态的name参数值相同。
  • changes:一个描述变更的字典。 每个被更改的事物都应该是一个键,其值则作为另一个字典,其中包含旧/新值的键称为“old”和“new”。 例如,pkg状态的changes字典对每个更改的软件包都有一个键,其子字典中的“old”和“new”键包含该软件包的旧版本和新版本。 例如,此方案的最终更改字典如下所示:
    ret['changes'].update({'my_pkg_name': {'old': '',
                                           'new': 'my_pkg_name-1.0'}})
    
  • result: 取值为三个值之一。 如果操作成功,则为True;否则为False;如果状态以测试模式运行,则为None,即test=True;如果状态未以测试模式运行,则将进行更改。
live mode test mode
no changes True True
successful changes True None
failed changes False False or None

注意:测试模式无法预测更改是否成功,因此未决更改的结果通常为“None”。

但是,如果状态将失败并且可以在测试模式下确定而不应用更改,则可以返回False。

  • comment:字符串列表或总结结果的单个字符串。 请注意,自Salt 2018.3.0起已支持字符串列表。 字符串列表将与换行符一起形成最终注释; 这对于允许来自一个状态子部分的多个注释很有用。 最好保持行适当长短(可根据需要使用多行),并以标点符号(例如句点)结尾以界定多个注释。

注意:States不应返回无法序列化的数据,例如frozensets。

Test State - 测试状态模块

所有States都应检查并支持通过选项的测试。 这将返回有关如果实际运行状态会发生什么变化的数据。 此类检查的示例如下所示:

# Return comment of changes if test.
if __opts__['test']:
    ret['result'] = None
    ret['comment'] = 'State Foo will execute with param {0}'.format(bar)
    return ret

在对minions执行任何实际操作之前,请确保进行了测试并得到返回数据。

注意:编写测试支持时,请务必参考上面列出的result表并显示任何可能的更改。 寻找状态变化对于test=true功能至关重要。 如果使用test=true预测状态没有变化(或在配置文件中test:true),则最终状态的结果不应为None

Watcher Function - watcher监视函数

如果写入的状态应支持watch监视条件,则需要声明一个watcher监视程序的功能函数。 每当调用watch监视条件时,都会调用watcher监视程序函数,并且监视程序功能应对状态本身的行为通用。

watcher函数应接受正常状态函数接受的所有选项(因为它们将传递到watcher函数中)。

watcher函数通常用于执行特定于状态的反应行为,例如,服务模块的watch监视程序重新启动指定的服务,并使监视程序使服务对环境的变化做出反应。

watcher函数还需要返回与正常状态函数返回的数据相同的数据。

Mod_init Interface - 初始化接口

某些states只需执行一次操作即可确保已建立环境,或者可以预定义该state行为的全局某些条件。这是mod_init接口的领域。

状态模块可以具有一个名为mod_init的函数,该函数在调用此类型的第一个状态时执行。创建此接口主要是为了改善pkg状态。安装软件包时,需要刷新软件包元数据,但是每次安装软件包时刷新软件包元数据都是浪费的。 pkg状态的mod_init函数将标志设置为down,以便第一次(只有第一次)软件包安装尝试才能刷新软件包数据库(当然,可以在pkg状态下通过refresh选项手动调用软件包数据库进行刷新) 。

mod_init函数必须接受给定执行状态的Low State Data状态数据作为参数。低状态数据是一个字典,可以通过执行state.show_lowstate函数看到。然后,mod_init函数必须返回布尔值。如果返回值为True,则不会再次执行mod_init函数,这意味着已设置所需的行为。否则,如果mod_init函数返回False,则下次调用该函数。

pkg状态模块中可以找到mod_init函数的一个很好的例子:

def mod_init(low):
    '''
    Refresh the package database here so that it only needs to happen once
    '''
    if low['fun'] == 'installed' or low['fun'] == 'latest':
        rtag = __gen_rtag()
        if not os.path.exists(rtag):
            open(rtag, 'w+').write('')
        return True
    else:
        return False

pkg状态的mod_init函数将低状态数据接受为low,然后检查被调用的函数是否要安装软件包,如果该函数不打算安装软件包,则无需刷新软件包数据库。 因此,如果软件包数据库准备刷新,则返回True且下次评估pkg状态时不会调用mod_init,否则返回False且下次评估pkg状态时将调用mod_init

Log Output - 日志输出

您可以在自定义模块中调用logger记录器,以将消息写入minion的日志。 以下代码片段演示了如何编写日志消息:

import logging

log = logging.getLogger(__name__)

log.info('Here is Some Information')
log.warning('You Should Not Do That')
log.error('It Is Busted')

Strings and Unicode

状态模块的作者应始终假定输入模块的字符串已经从字符串解码为Unicode。 在Python 2中,它们的类型为“Unicode”类型,而在Python 3中,它们的类型为str。 从状态到其他Salt子系统(例如执行模块)的调用应传递Unicode(如果传递二进制数据,则传递字节)。 在极少数情况下,状态需要直接写入磁盘,应该在写入磁盘之前立即将Unicode编码为字符串。 作者可以使用__salt_system_encoding__来学习系统的编码类型。 例如,’ my_string’.encode(’__ salt_system_encoding__’)。

Full State Module Example - 一个完整的状态模块的使用示例

以下是完整状态模块和功能的简化示例。 记住要通过调用执行模块来执行所有实际工作。 状态模块应仅执行“之前”和“之后”检查。

  1. 通过将代码放入以下路径的文件中来创建自定义状态模块:/srv/salt/_states/my_custom_state.py
  2. 将自定义状态模块分发到各minions:
    salt '*' saltutil.sync_states
    
  3. 创建一个新的状态文件(例如/srv/salt/my_custom_state.sls),调用自定义的状态。
  4. 将以下SLS配置添加到在步骤3中创建的文件中:
    human_friendly_state_id:        # An arbitrary state ID declaration.
      my_custom_state:              # The custom state module name.
        - enforce_custom_thing      # The function in the custom state module.
        - name: a_value             # Maps to the ``name`` parameter in the custom function.
        - foo: Foo                  # Specify the required ``foo`` parameter.
        - bar: False                # Override the default value for the ``bar`` parameter.
    

Example state module - 状态模块例子

import salt.exceptions

def enforce_custom_thing(name, foo, bar=True):
    '''
    Enforce the state of a custom thing

    This state module does a custom thing. It calls out to the execution module
    ``my_custom_module`` in order to check the current system and perform any
    needed changes.

    name
        The thing to do something to
    foo
        A required argument
    bar : True
        An argument with a default value
    '''
    ret = {
        'name': name,
        'changes': {},
        'result': False,
        'comment': '',
        }

    # Start with basic error-checking. Do all the passed parameters make sense
    # and agree with each-other?
    if bar == True and foo.startswith('Foo'):
        raise salt.exceptions.SaltInvocationError(
            'Argument "foo" cannot start with "Foo" if argument "bar" is True.')

    # Check the current state of the system. Does anything need to change?
    current_state = __salt__['my_custom_module.current_state'](name)

    if current_state == foo:
        ret['result'] = True
        ret['comment'] = 'System already in the correct state'
        return ret

    # The state of the system does need to be changed. Check if we're running
    # in ``test=true`` mode.
    if __opts__['test'] == True:
        ret['comment'] = 'The state of "{0}" will be changed.'.format(name)
        ret['changes'] = {
            'old': current_state,
            'new': 'Description, diff, whatever of the new state',
        }

        # Return ``None`` when running with ``test=true``.
        ret['result'] = None

        return ret

    # Finally, make the actual change and return the result.
    new_state = __salt__['my_custom_module.change_state'](name, foo)

    ret['comment'] = 'The state of "{0}" was changed!'.format(name)

    ret['changes'] = {
        'old': current_state,
        'new': new_state,
    }

    ret['result'] = True

    return ret

你可能感兴趣的:(SaltStack)