У меня есть следующий список словарей:
"errorlist": [
{
"error": "Not found",
"path": "/tmp/working/directory1/file1"
},
{
"error": "Not found",
"path": "/tmp/working/directory2/file1"
},
{
"error": "Not found",
"path": "/tmp/working/directory1/file2"
},
{
"error": "Not found",
"path": "/tmp/working/directory2/file2"
}
]
Я хотел бы удалить часть значений пути «/tmp/working/», т.е. преобразовать абсолютные пути в относительные.
Я задавал подобные вопросы при поиске в Интернете, но не нашел решения. Это должно быть довольно просто, но я не могу уложить в этом голову.
Ожидаемым результатом должен быть либо исходный список с обновленными значениями, либо новый список с той же структурой.
Ansible — не лучший инструмент для манипулирования данными. Для того, что вы хотите сделать, самым простым решением, вероятно, будет собственный фильтр, написанный на Python. Поместите следующее в filter_plugins/filters.py
(где каталог filter_plugins
находится в том же каталоге, что и ваша книга игр):
def replaceInAttribute(data, attr, find, replace):
for d in data:
if attr in d:
d[attr] = d[attr].replace(find, replace)
return data
class FilterModule:
def filters(self):
return {
"replaceInAttribute": replaceInAttribute,
}
И теперь вы можете написать такую пьесу:
- hosts: localhost
gather_facts: false
vars:
errorlist: [
{
"error": "Not found",
"path": "/tmp/working/directory1/file1"
},
{
"error": "Not found",
"path": "/tmp/working/directory2/file1"
},
{
"error": "Not found",
"path": "/tmp/working/directory1/file2"
},
{
"error": "Not found",
"path": "/tmp/working/directory2/file2"
}
]
tasks:
- debug:
msg: "{{ errorlist | replaceInAttribute('path', '/tmp/working/', '') }}"
И получите этот вывод:
TASK [debug] *************************************************************************************************************************************************************
ok: [localhost] => {
"msg": [
{
"error": "Not found",
"path": "directory1/file1"
},
{
"error": "Not found",
"path": "directory2/file1"
},
{
"error": "Not found",
"path": "directory1/file2"
},
{
"error": "Not found",
"path": "directory2/file2"
}
]
}
Представленный здесь фильтр немного сложнее, чем должен быть, поскольку он поддерживает поиск/замену произвольных атрибутов.
@ Jedi6, но сам Ansibe и почти каждый модуль, фильтр и т. д. написаны на Python. Итак, вы все равно собираетесь использовать Python. Даже если вы обнаружите, что стоимость обслуживания и эксплуатации собственного пользовательского фильтра высока, это может оказаться дешевле, чем обслуживание и эксплуатация сборника сценариев Ansible с множеством готовых фильтров для сложных манипуляций с данными.
Отфильтруйте базовые имена и создайте список словарей.
paths: "{{ errorlist | map(attribute='path') | map('basename') |
map('community.general.dict_kv', 'path') }}"
дает
paths:
- {path: file1}
- {path: file1}
- {path: file2}
- {path: file2}
заархивируйте списки и объедините элементы
result: "{{ errorlist | zip(paths) | map('combine') }}"
дает
result:
- {error: Not found, path: file1}
- {error: Not found, path: file1}
- {error: Not found, path: file2}
- {error: Not found, path: file2}
Подберите трубы в соответствии с вашими потребностями.
- hosts: localhost
vars:
errorlist:
- {error: Not found, path: /tmp/working/directory1/file1}
- {error: Not found, path: /tmp/working/directory2/file1}
- {error: Not found, path: /tmp/working/directory1/file2}
- {error: Not found, path: /tmp/working/directory2/file2}
paths: "{{ errorlist | map(attribute='path') | map('basename') |
map('community.general.dict_kv', 'path') }}"
result: "{{ errorlist | zip(paths) | map('combine') }}"
tasks:
- debug:
var: paths | to_yaml
- debug:
var: result | to_yaml
paths: "{{ errorlist | map(attribute='path') |
map('regex_replace', '/tmp/working/(.*)', '\\1') |
map('community.general.dict_kv', 'path') }}"
gives
paths:
- {path: directory1/file1}
- {path: directory2/file1}
- {path: directory1/file2}
- {path: directory2/file2}
This will result in
result:
- {error: Not found, path: directory1/file1}
- {error: Not found, path: directory2/file1}
- {error: Not found, path: directory1/file2}
- {error: Not found, path: directory2/file2}
Вопрос: «Как мне добиться того же результата без Community.general.dict_kv?»
О: Используйте json_query. Приведенное ниже объявление дает тот же результат
paths: "{{ errorlist | map(attribute='path') |
map('regex_replace', '/tmp/working/(.*)', '\\1') |
json_query('[].{path: @}') }}"
В Ansible 2.9 и ниже попробуйте добавить список фильтров.
paths: "{{ errorlist | map(attribute='path') | list |
map('regex_replace', '/tmp/working/(.*)', '\\1') | list |
json_query('[].{path: @}') | list }}"
Debugging
Decompose the pipe
path1: "{{ errorlist | map(attribute='path') }}"
path2: "{{ errorlist | map(attribute='path') |
map('regex_replace', '/tmp/working/(.*)', '\\1') }}"
path3: "{{ errorlist | map(attribute='path') |
map('regex_replace', '/tmp/working/(.*)', '\\1') |
json_query('[].{path: @}') }}"
and display the types of intermediary results
- debug:
msg: "{{ path1 | type_debug }}"
- debug:
msg: "{{ path2 | type_debug }}"
- debug:
msg: "{{ path3 | type_debug }}"
Мне также нужен относительный каталог каждого файла, а не только базовое имя.
Используйте regex_replace в канале. Я добавил пример.
Тестирование в локальной среде сработало, но в реальной среде используется Ansible 2.9.x, и фильтр Community.general.dict_kv отсутствует. Как я могу достичь того же результата без него?
Используйте json_query. Я добавил пример.
Пришлось добавить «| list» перед json_query, иначе переменная paths была пустой. Но теперь я получаю «<объект-генератор do_map в ....>» для переменной результата.
См. Ansible: ошибка: объект генератора не повторяется.... Я добавил пример.
Я согласен, что Ansible — не лучший инструмент для манипулирования данными, но я бы предпочел не использовать Python. В любом случае спасибо.