状态模块是映射到Salt状态的实际执行和管理的组件。
您也可以参考在Github上维护的这一份技术资料:State Modules
状态模块易于编写且简单明了。 传递到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
状态的使用,该状态需要能够处理任意关键字参数并将其传递给模块执行功能。 可以在此处找到如何处理这些关键字参数的示例。
编写良好的状态函数将遵循以下步骤:
注意:这是一个极其简化的示例。 可以通过浏览Salt状态模块的源代码以查看其他示例。
设置返回数据字典并执行任何必要的输入验证(类型检查,寻找互斥参数的使用等)。
ret = {'name': name,
'result': False,
'changes': {},
'comment': ''}
if foo and bar:
ret['comment'] = 'Only one of foo and bar is permitted'
return ret
检查是否需要进行更改。 最好通过附带的执行模块中的信息收集功能来完成此操作。 该状态应该能够使用该函数的返回值来判断该minion是否已经处于所需状态。
result = __salt__['modname.check'](name)
如果步骤2发现minion已经处于所需状态,则立即退出,并返回True
结果,而无需进行任何更改。
if result:
ret['result'] = True
ret['comment'] = '{0} is already installed'.format(name)
return ret
如果步骤2发现确实需要进行更改,请检查状态是否在测试模式下运行(即使用test=True
)。 如果是这样,则退出并返回值为“None”的结果、相关注释和(如有可能)将进行哪些更改的描述。
if __opts__['test']:
ret['result'] = None
ret['comment'] = '{0} would be installed'.format(name)
ret['changes'] = result
return ret
执行所需的更改。 应该再次使用附带的执行模块中的函数来完成此操作。 如果该函数的结果足以告诉您是否发生了错误,则可以退出并返回False
结果和相关注释以说明发生了什么。
result = __salt__['modname.install'](name)
再次从步骤2执行相同的检查,以确认minion是否处于所需状态。 就像在第2步中一样,此函数应该能够通过其返回数据来告诉您是否需要进行更改。
ret['changes'] = __salt__['modname.check'](name)
如您所见,我们将返回字典中的changes键设置为modname.check
函数的结果(就像我们在步骤4中所做的一样)。 这里的假设是,信息收集功能将返回一个字典,解释需要进行哪些更改。 这可能适合您的用例,也可能不适合。
设置返回数据并返回!
if ret['changes']:
ret['comment'] = '{0} failed to install'.format(name)
else:
ret['result'] = True
ret['comment'] = '{0} was installed'.format(name)
return ret
在使用状态模块之前,必须将其分发给各minions。 可以通过将它们放入salt://_states/
中来完成。 然后可以通过运行saltutil.sync_states
或saltutil.sync_all
将它们手动分发给minions。 或者,在运行highstate状态时,自定义状态类型将自动同步。
注意:使用用文件名中写带连字符的状态模块会导致!pyobjects
例程出现问题。 请坚持使用下划线的最佳做法。
任何已与minions同步的自定义状态(与Salt的默认状态集中的名称之一相同的)将替换具有相同名称的默认状态。 请注意,状态模块的名称根据其文件名默认为同一个(即foo.py变为状态模块foo),但是可以使用virtual函数覆盖其名称。
与执行模块一样,状态模块也可以使用__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执行模块
的原因。
另一方面,某些模块将要求将处理逻辑放置在状态模块中,文件模块就是一个很好的例子。 但是在大多数情况下,这不是最佳方法,编写特定的执行模块来执行后端工作将是最佳解决方案。
所有的Salt状态模块对彼此可用,并且状态模块可以调用其他状态模块中可用的功能。
将变量__states__
加载到模块中后,将其包装到模块中。
__states__
变量是一个包含所有状态模块的Python字典。 字典的key是代表模块名称的字符串,值是函数本身。
可以通过访问__states__
dict中的值来交叉调用Salt状态模块:
ret = __states__['file.managed'](name='/tmp/myfile', source='salt://myfile')
此代码将在file
状态模块中调用managed
函数,并将参数名称和源传递给它。
一个状态模块必须返回包含以下键/值数据的字典:
name
参数值相同。pkg
状态的changes
字典对每个更改的软件包都有一个键,其子字典中的“old”和“new”键包含该软件包的旧版本和新版本。 例如,此方案的最终更改字典如下所示:ret['changes'].update({'my_pkg_name': {'old': '',
'new': 'my_pkg_name-1.0'}})
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。
注意:States不应返回无法序列化的数据,例如frozensets。
所有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
。
如果写入的状态应支持watch监视条件,则需要声明一个watcher监视程序的功能函数。 每当调用watch监视条件时,都会调用watcher监视程序函数,并且监视程序功能应对状态本身的行为通用。
watcher函数应接受正常状态函数接受的所有选项(因为它们将传递到watcher函数中)。
watcher函数通常用于执行特定于状态的反应行为,例如,服务模块的watch监视程序重新启动指定的服务,并使监视程序使服务对环境的变化做出反应。
watcher函数还需要返回与正常状态函数返回的数据相同的数据。
某些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
。
您可以在自定义模块中调用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')
状态模块的作者应始终假定输入模块的字符串已经从字符串解码为Unicode。 在Python 2中,它们的类型为“Unicode”类型,而在Python 3中,它们的类型为str
。 从状态到其他Salt子系统(例如执行模块)的调用应传递Unicode(如果传递二进制数据,则传递字节)。 在极少数情况下,状态需要直接写入磁盘,应该在写入磁盘之前立即将Unicode编码为字符串。 作者可以使用__salt_system_encoding__
来学习系统的编码类型。 例如,’ my_string’.encode(’__ salt_system_encoding__’)。
以下是完整状态模块和功能的简化示例。 记住要通过调用执行模块来执行所有实际工作。 状态模块应仅执行“之前”和“之后”检查。
/srv/salt/_states/my_custom_state.py
。salt '*' saltutil.sync_states
/srv/salt/my_custom_state.sls
),调用自定义的状态。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.
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