summaryrefslogtreecommitdiffstats
path: root/lib/ansible/modules/getent.py
blob: 1f76380033071372d61d1e9df86758d982c02985 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# -*- coding: utf-8 -*-

# Copyright: (c) 2014, Brian Coca <brian.coca+dev@gmail.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

from __future__ import absolute_import, division, print_function
__metaclass__ = type


DOCUMENTATION = r'''
---
module: getent
short_description: A wrapper to the unix getent utility
description:
     - Runs getent against one of it's various databases and returns information into
       the host's facts, in a getent_<database> prefixed variable.
version_added: "1.8"
options:
    database:
        description:
            - The name of a getent database supported by the target system (passwd, group,
              hosts, etc).
        type: str
        required: True
    key:
        description:
            - Key from which to return values from the specified database, otherwise the
              full contents are returned.
        type: str
        default: ''
    service:
        description:
            - Override all databases with the specified service
            - The underlying system must support the service flag which is not always available.
        type: str
        version_added: "2.9"
    split:
        description:
            - Character used to split the database values into lists/arrays such as C(:) or C(\t),
              otherwise it will try to pick one depending on the database.
        type: str
    fail_key:
        description:
            - If a supplied key is missing this will make the task fail if C(true).
        type: bool
        default: 'yes'
extends_documentation_fragment:
  - action_common_attributes
  - action_common_attributes.facts
attributes:
    check_mode:
        support: full
    diff_mode:
        support: none
    facts:
        support: full
    platform:
        platforms: posix
notes:
   - Not all databases support enumeration, check system documentation for details.
author:
- Brian Coca (@bcoca)
'''

EXAMPLES = '''
- name: Get root user info
  ansible.builtin.getent:
    database: passwd
    key: root
- ansible.builtin.debug:
    var: ansible_facts.getent_passwd

- name: Get all groups
  ansible.builtin.getent:
    database: group
    split: ':'
- ansible.builtin.debug:
    var: ansible_facts.getent_group

- name: Get all hosts, split by tab
  ansible.builtin.getent:
    database: hosts
- ansible.builtin.debug:
    var: ansible_facts.getent_hosts

- name: Get http service info, no error if missing
  ansible.builtin.getent:
    database: services
    key: http
    fail_key: False
- ansible.builtin.debug:
    var: ansible_facts.getent_services

- name: Get user password hash (requires sudo/root)
  ansible.builtin.getent:
    database: shadow
    key: www-data
    split: ':'
- ansible.builtin.debug:
    var: ansible_facts.getent_shadow

'''

RETURN = '''
ansible_facts:
  description: Facts to add to ansible_facts.
  returned: always
  type: dict
  contains:
    getent_<database>:
      description:
        - A list of results or a single result as a list of the fields the db provides
        - The list elements depend on the database queried, see getent man page for the structure
        - Starting at 2.11 it now returns multiple duplicate entries, previouslly it only returned the last one
      returned: always
      type: list
'''

import traceback

from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native


def main():
    module = AnsibleModule(
        argument_spec=dict(
            database=dict(type='str', required=True),
            key=dict(type='str', no_log=False),
            service=dict(type='str'),
            split=dict(type='str'),
            fail_key=dict(type='bool', default=True),
        ),
        supports_check_mode=True,
    )

    colon = ['passwd', 'shadow', 'group', 'gshadow']

    database = module.params['database']
    key = module.params.get('key')
    split = module.params.get('split')
    service = module.params.get('service')
    fail_key = module.params.get('fail_key')

    getent_bin = module.get_bin_path('getent', True)

    if key is not None:
        cmd = [getent_bin, database, key]
    else:
        cmd = [getent_bin, database]

    if service is not None:
        cmd.extend(['-s', service])

    if split is None and database in colon:
        split = ':'

    try:
        rc, out, err = module.run_command(cmd)
    except Exception as e:
        module.fail_json(msg=to_native(e), exception=traceback.format_exc())

    msg = "Unexpected failure!"
    dbtree = 'getent_%s' % database
    results = {dbtree: {}}

    if rc == 0:
        seen = {}
        for line in out.splitlines():
            record = line.split(split)

            if record[0] in seen:
                # more than one result for same key, ensure we store in a list
                if seen[record[0]] == 1:
                    results[dbtree][record[0]] = [results[dbtree][record[0]]]

                results[dbtree][record[0]].append(record[1:])
                seen[record[0]] += 1
            else:
                # new key/value, just assign
                results[dbtree][record[0]] = record[1:]
                seen[record[0]] = 1

        module.exit_json(ansible_facts=results)

    elif rc == 1:
        msg = "Missing arguments, or database unknown."
    elif rc == 2:
        msg = "One or more supplied key could not be found in the database."
        if not fail_key:
            results[dbtree][key] = None
            module.exit_json(ansible_facts=results, msg=msg)
    elif rc == 3:
        msg = "Enumeration not supported on this database."

    module.fail_json(msg=msg)


if __name__ == '__main__':
    main()