狀態模塊是映射到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 <filename>.<function> - 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狀態模塊的源代碼以查看其他示例。
-
設置返回數據字典並執行任何必要的輸入驗證(類型檢查,尋找互斥參數的使用等)。
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
Using Custom State Modules - 使用自定義的State模塊
在使用狀態模塊之前,必須將其分發給各minions。 可以通過將它們放入salt://_states/
中來完成。 然後可以通過運行saltutil.sync_states
或saltutil.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 - 一個完整的狀態模塊的使用示例
以下是完整狀態模塊和功能的簡化示例。 記住要通過調用執行模塊來執行所有實際工作。 狀態模塊應僅執行“之前”和“之後”檢查。
- 通過將代碼放入以下路徑的文件中來創建自定義狀態模塊:
/srv/salt/_states/my_custom_state.py
。 - 將自定義狀態模塊分發到各minions:
salt '*' saltutil.sync_states
- 創建一個新的狀態文件(例如
/srv/salt/my_custom_state.sls
),調用自定義的狀態。 - 將以下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