1#!/usr/local/bin/python3.8
2# -*- coding: utf-8 -*-
3# https://github.com/ansible/ansible/issues/65816
4# https://github.com/PyCQA/pylint/issues/214
5
6# (c) 2018, Adam Miller (admiller@redhat.com)
7# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
8
9from __future__ import absolute_import, division, print_function
10
11__metaclass__ = type
12
13DOCUMENTATION = """
14---
15module: data_input_network
16short_description: Manage Splunk Data Inputs of type TCP or UDP
17description:
18  - This module allows for addition or deletion of TCP and UDP Data Inputs in Splunk.
19version_added: "1.0.0"
20options:
21  protocol:
22    description:
23      - Choose between tcp or udp
24    required: True
25    choices:
26      - 'tcp'
27      - 'udp'
28    type: str
29  connection_host:
30    description:
31      - Set the host for the remote server that is sending data.
32      - C(ip) sets the host to the IP address of the remote server sending data.
33      - C(dns) sets the host to the reverse DNS entry for the IP address of the remote server sending data.
34      - C(none) leaves the host as specified in inputs.conf, which is typically the Splunk system hostname.
35    default: "ip"
36    required: False
37    type: str
38    choices:
39      - "ip"
40      - "dns"
41      - "none"
42  state:
43    description:
44      - Enable, disable, create, or destroy
45    choices:
46      - "present"
47      - "absent"
48      - "enabled"
49      - "disable"
50    required: False
51    default: "present"
52    type: str
53  datatype:
54    description: >
55      Forwarders can transmit three types of data: raw, unparsed, or parsed.
56      C(cooked) data refers to parsed and unparsed formats.
57    choices:
58      - "cooked"
59      - "raw"
60    default: "raw"
61    required: False
62    type: str
63  host:
64    description:
65      - Host from which the indexer gets data.
66    required: False
67    type: str
68  index:
69    description:
70      - default Index to store generated events.
71    type: str
72  name:
73    description:
74      - The input port which receives raw data.
75    required: True
76    type: str
77  queue:
78    description:
79      - Specifies where the input processor should deposit the events it reads. Defaults to parsingQueue.
80      - Set queue to parsingQueue to apply props.conf and other parsing rules to your data. For more
81        information about props.conf and rules for timestamping and linebreaking, refer to props.conf and
82        the online documentation at "Monitor files and directories with inputs.conf"
83      - Set queue to indexQueue to send your data directly into the index.
84    choices:
85      - "parsingQueue"
86      - "indexQueue"
87    type: str
88    required: False
89    default: "parsingQueue"
90  rawTcpDoneTimeout:
91    description:
92      - Specifies in seconds the timeout value for adding a Done-key.
93      - If a connection over the port specified by name remains idle after receiving data for specified
94        number of seconds, it adds a Done-key. This implies the last event is completely received.
95    default: 10
96    type: int
97    required: False
98  restrictToHost:
99    description:
100      - Allows for restricting this input to only accept data from the host specified here.
101    required: False
102    type: str
103  ssl:
104    description:
105      - Enable or disble ssl for the data stream
106    required: False
107    type: bool
108  source:
109    description:
110      - Sets the source key/field for events from this input. Defaults to the input file path.
111      - >
112        Sets the source key initial value. The key is used during parsing/indexing, in particular to set
113        the source field during indexing. It is also the source field used at search time. As a convenience,
114        the chosen string is prepended with 'source::'.
115      - >
116        Note: Overriding the source key is generally not recommended. Typically, the input layer provides a
117        more accurate string to aid in problem analysis and investigation, accurately recording the file from
118        which the data was retrieved. Consider use of source types, tagging, and search wildcards before
119        overriding this value.
120    type: str
121  sourcetype:
122    description:
123      - Set the source type for events from this input.
124      - '"sourcetype=" is automatically prepended to <string>.'
125      - Defaults to audittrail (if signedaudit=True) or fschange (if signedaudit=False).
126    type: str
127
128author: Ansible Security Automation Team (@maxamillion) <https://github.com/ansible-security>
129"""
130
131EXAMPLES = """
132- name: Example adding data input network with splunk.es.data_input_network
133  splunk.es.data_input_network:
134    name: "8099"
135    protocol: "tcp"
136    state: "present"
137"""
138
139
140from ansible.module_utils.basic import AnsibleModule
141from ansible.module_utils._text import to_text
142
143from ansible.module_utils.urls import Request
144from ansible.module_utils.six.moves.urllib.parse import urlencode, quote_plus
145from ansible.module_utils.six.moves.urllib.error import HTTPError
146from ansible_collections.splunk.es.plugins.module_utils.splunk import (
147    SplunkRequest,
148    parse_splunk_args,
149)
150
151import copy
152
153
154def main():
155
156    argspec = dict(
157        state=dict(
158            required=False,
159            choices=["present", "absent", "enabled", "disable"],
160            default="present",
161            type="str",
162        ),
163        connection_host=dict(
164            required=False, choices=["ip", "dns", "none"], default="ip", type="str"
165        ),
166        host=dict(required=False, type="str", default=None),
167        index=dict(required=False, type="str", default=None),
168        name=dict(required=True, type="str"),
169        protocol=dict(required=True, type="str", choices=["tcp", "udp"]),
170        queue=dict(
171            required=False,
172            type="str",
173            choices=["parsingQueue", "indexQueue"],
174            default="parsingQueue",
175        ),
176        rawTcpDoneTimeout=dict(required=False, type="int", default=10),
177        restrictToHost=dict(required=False, type="str", default=None),
178        ssl=dict(required=False, type="bool", default=None),
179        source=dict(required=False, type="str", default=None),
180        sourcetype=dict(required=False, type="str", default=None),
181        datatype=dict(required=False, choices=["cooked", "raw"], default="raw"),
182    )
183
184    module = AnsibleModule(argument_spec=argspec, supports_check_mode=True)
185
186    splunk_request = SplunkRequest(
187        module,
188        headers={"Content-Type": "application/x-www-form-urlencoded"},
189        not_rest_data_keys=["state", "datatype", "protocol"],
190    )
191    # This is where the splunk_* args are processed
192    request_data = splunk_request.get_data()
193
194    query_dict = splunk_request.get_by_path(
195        "servicesNS/nobody/search/data/inputs/{0}/{1}/{2}".format(
196            quote_plus(module.params["protocol"]),
197            quote_plus(module.params["datatype"]),
198            quote_plus(module.params["name"]),
199        )
200    )
201
202    if module.params["state"] in ["present", "enabled", "disabled"]:
203        _data = splunk_request.get_data()
204        if module.params["state"] in ["present", "enabled"]:
205            _data["disabled"] = False
206        else:
207            _data["disabled"] = True
208        if query_dict:
209            needs_change = False
210            for arg in request_data:
211                if arg in query_dict["entry"][0]["content"]:
212                    if to_text(query_dict["entry"][0]["content"][arg]) != to_text(
213                        request_data[arg]
214                    ):
215                        needs_change = True
216            if not needs_change:
217                module.exit_json(
218                    changed=False, msg="Nothing to do.", splunk_data=query_dict
219                )
220            if module.check_mode and needs_change:
221                module.exit_json(
222                    changed=True,
223                    msg="A change would have been made if not in check mode.",
224                    splunk_data=query_dict,
225                )
226            if needs_change:
227                splunk_data = splunk_request.create_update(
228                    "servicesNS/nobody/search/data/inputs/{0}/{1}/{2}".format(
229                        quote_plus(module.params["protocol"]),
230                        quote_plus(module.params["datatype"]),
231                        quote_plus(module.params["name"]),
232                        data=urlencode(_data),
233                    )
234                )
235            if module.params["state"] in ["present", "enabled"]:
236                module.exit_json(
237                    changed=True, msg="{0} updated.", splunk_data=splunk_data
238                )
239            else:
240                module.exit_json(
241                    changed=True, msg="{0} disabled.", splunk_data=splunk_data
242                )
243        else:
244            # Create it
245            splunk_data = splunk_request.create_update(
246                "servicesNS/nobody/search/data/inputs/{0}/{1}".format(
247                    quote_plus(module.params["protocol"]),
248                    quote_plus(module.params["datatype"]),
249                ),
250                data=urlencode(_data),
251            )
252            module.exit_json(changed=True, msg="{0} created.", splunk_data=splunk_data)
253    elif module.params["state"] == "absent":
254        if query_dict:
255            splunk_data = splunk_request.delete_by_path(
256                "servicesNS/nobody/search/data/inputs/{0}/{1}/{2}".format(
257                    quote_plus(module.params["protocol"]),
258                    quote_plus(module.params["datatype"]),
259                    quote_plus(module.params["name"]),
260                )
261            )
262            module.exit_json(
263                changed=True,
264                msg="Deleted {0}.".format(module.params["name"]),
265                splunk_data=splunk_data,
266            )
267
268    module.exit_json(changed=False, msg="Nothing to do.", splunk_data={})
269
270
271if __name__ == "__main__":
272    main()
273