В моей книге игр я хочу получить список всех локальных пользователей с пользовательским интерфейсом выше 14000.
Я использовал ansible.builtin.slurp в /etc/passwd или ansible.builtin.getent, но реальная проблема заключается в том, что значение UID, которое я получаю, является строкой, а не int, поэтому я не могу правильно фильтровать, и все результаты, которые я получаю, пусты, или жаловаться, потому что это не целое число.
- name: Get all users from /etc/passwd
ansible.builtin.getent:
database: passwd
register: users_info
- name: Filter users with UID greater than 14000
set_fact:
filtered_users: >-
{{
users_info.ansible_facts.getent_passwd |
dict2items |
selectattr('value.1', 'int') |
selectattr('value.1', >, 14000) |
map(attribute='key') |
list
}}
Я думал, что selectattr('value.1', 'int')
преобразуется в int, но результат всегда пустой. (У меня есть UID выше 14000 в dict)
Здесь пользовательское определение, предоставляемое getent, все UID заключены в кавычки, поэтому они представляют собой строку.
root:
- x
- '0'
- '0'
- root
- /root
- /bin/bash
shutdown:
- x
- '6'
- '0'
- shutdown
- /sbin
- /sbin/shutdown
sshd:
- x
- '74'
- '74'
- Privilege-separated SSH
- /usr/share/empty.sshd
- /sbin/nologin
У меня закончилась идея попробовать, кроме запуска какого-нибудь командного процессора оболочки, которого я бы избегал.
Я вижу несколько проблем с вашей цепочкой фильтров:
filtered_users: >-
{{
users_info.ansible_facts.getent_passwd |
dict2items |
selectattr('value.1', 'int') |
selectattr('value.1', >, 14000) |
map(attribute='key') |
list
}}
Во-первых, нет теста с именем int
, хотя есть фильтр с именем целое число. К сожалению, это проверка, а не фильтр преобразования, поэтому, поскольку во всех случаях значение value.1
является строкой, этот фильтр selectattr()
отклонит все ваши записи, что приведет к созданию пустого списка. Все, что происходит после этого момента, не имеет значения, поскольку фильтровать уже нечего.
Например:
- hosts: localhost
gather_facts: false
vars:
example:
- val: '1'
- val: '2'
- val: '3'
tasks:
- debug:
msg: "{{ example | selectattr('val', 'integer') }}"
Результаты:
ok: [localhost] => {
"msg": []
}
Я думаю, вы найдете решение, используя фильтр json_query проще. Для того, что вы хотите, мы могли бы сделать это:
- name: Filter users with UID > 1000
set_fact:
filtered_users: >-
{{
users_info.ansible_facts.getent_passwd |
dict2items |
json_query('[?to_number(value[1]) > `1000`].key')
}}
Здесь мы используем функцию to_number для преобразования строковых значений в целые числа для сравнения.
В моей системе это производит:
ok: [localhost] => {
"filtered_users": [
"nobody"
]
}
Благодаря ответу @larsks я смог изменить свой запрос для фильтрации 14000-15000 UID следующим образом, что дает ожидаемый результат:
- name: Filter users 14k-15k range
set_fact:
filtered_users: >-
{{
users_info.ansible_facts.getent_passwd |
dict2items |
json_query('[?to_number(value[1]) > `14000` && to_number(value[1]) <= `15000`].key')
}}
Используйте Community.general.jc для анализа файла /etc/passwd. Объявить пункт назначения
dest_dir: /tmp/ansible/fetch
и получить файл
- fetch:
src: /etc/passwd
dest: "{{ dest_dir }}"
Объявите путь и проанализируйте файл
passwd_path: "{{ dest_dir }}/{{ inventory_hostname }}/etc/passwd"
my_users: "{{ lookup('file', passwd_path) | community.general.jc('passwd') }}"
дает (в сокращении)
my_users:
- comment: Charlie &
gid: 0
home: /root
password: '*'
shell: /bin/sh
uid: 0
username: root
- comment: Bourne-again Superuser
...
Атрибуты uid и gid являются целыми числами. Вы можете проверить это
- debug:
msg: |
{{ my_users.0.username }}
{{ my_users.0.uid }}
{{ my_users.0.uid | type_debug }}
дает
msg: |-
root
0
int
Теперь выбор работает как положено.
filtered_users: "{{ my_users |
selectattr('uid', '>', 14000) |
map(attribute='username') }}"
дает
filtered_users:
- nobody
- hosts: host_a
vars:
dest_dir: /tmp/ansible/fetch
passwd_path: "{{ dest_dir }}/{{ inventory_hostname }}/etc/passwd"
my_users: "{{ lookup('file', passwd_path) | community.general.jc('passwd') }}"
filtered_users: "{{ my_users | selectattr('uid', '>', 14000) | map(attribute='username') }}"
tasks:
- fetch:
src: /etc/passwd
dest: "{{ dest_dir }}"
- debug:
var: my_users
- debug:
msg: |
{{ my_users.0.username }}
{{ my_users.0.uid }}
{{ my_users.0.uid | type_debug }}
- debug:
var: filtered_users
Put the declarations into the group_vars
shell> cat group_vars/all.yml
dest_dir: /tmp/ansible/fetch
passwd_path: "{{ dest_dir }}/{{ inventory_hostname }}/etc/passwd"
my_users: "{{ lookup('file', passwd_path) | community.general.jc('passwd') }}"
uid_min: 0
uid_max: 65535
filtered_users_min: "{{ my_users | selectattr('uid', '>=', uid_min|int) | map(attribute='username') }}"
filtered_users_max: "{{ my_users | selectattr('uid', '<=', uid_max|int) | map(attribute='username') }}"
filtered_users: "{{ filtered_users_min | intersect(filtered_users_max) }}"
In the playbook, fetch the file and declare the interval. For example,
shell> cat playbook.yml
---
- hosts: host_a
tasks:
- fetch:
src: /etc/passwd
dest: "{{ dest_dir }}"
- debug:
var: filtered_users
vars:
uid_min: 14000