1#!/usr/local/bin/python3.8
2# -*- coding: utf-8 -*-
3
4# Copyright: (c) 2020, Zainab Alsaffar <Zainab.Alsaffar@mail.rit.edu>
5# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
6
7from __future__ import absolute_import, division, print_function
8__metaclass__ = type
9
10DOCUMENTATION = r'''
11---
12module: pagerduty_user
13short_description: Manage a user account on PagerDuty
14description:
15    - This module manages the creation/removal of a user account on PagerDuty.
16version_added: '1.3.0'
17author: Zainab Alsaffar (@zanssa)
18requirements:
19    - pdpyras python module = 4.1.1
20    - PagerDuty API Access
21options:
22    access_token:
23        description:
24            - An API access token to authenticate with the PagerDuty REST API.
25        required: true
26        type: str
27    pd_user:
28        description:
29            - Name of the user in PagerDuty.
30        required: true
31        type: str
32    pd_email:
33        description:
34            - The user's email address.
35            - I(pd_email) is the unique identifier used and cannot be updated using this module.
36        required: true
37        type: str
38    pd_role:
39        description:
40            - The user's role.
41        choices: ['global_admin', 'manager', 'responder', 'observer', 'stakeholder', 'limited_stakeholder', 'restricted_access']
42        default: 'responder'
43        type: str
44    state:
45        description:
46            - State of the user.
47            - On C(present), it creates a user if the user doesn't exist.
48            - On C(absent), it removes a user if the account exists.
49        choices: ['present', 'absent']
50        default: 'present'
51        type: str
52    pd_teams:
53        description:
54            - The teams to which the user belongs.
55            - Required if I(state=present).
56        type: list
57        elements: str
58notes:
59    - Supports C(check_mode).
60'''
61
62EXAMPLES = r'''
63- name: Create a user account on PagerDuty
64  community.general.pagerduty_user:
65    access_token: 'Your_Access_token'
66    pd_user: user_full_name
67    pd_email: user_email
68    pd_role: user_pd_role
69    pd_teams: user_pd_teams
70    state: "present"
71
72- name: Remove a user account from PagerDuty
73  community.general.pagerduty_user:
74    access_token: 'Your_Access_token'
75    pd_user: user_full_name
76    pd_email: user_email
77    state: "absent"
78'''
79
80RETURN = r''' # '''
81
82from ansible.module_utils.basic import AnsibleModule, missing_required_lib
83import traceback
84from os import path
85
86try:
87    from pdpyras import APISession
88    HAS_PD_PY = True
89except ImportError:
90    HAS_PD_PY = False
91    PD_IMPORT_ERR = traceback.format_exc()
92
93try:
94    from pdpyras import PDClientError
95    HAS_PD_CLIENT_ERR = True
96except ImportError:
97    HAS_PD_CLIENT_ERR = False
98    PD_CLIENT_ERR_IMPORT_ERR = traceback.format_exc()
99
100
101class PagerDutyUser(object):
102    def __init__(self, module, session):
103        self._module = module
104        self._apisession = session
105
106    # check if the user exists
107    def does_user_exist(self, pd_email):
108        for user in self._apisession.iter_all('users'):
109            if user['email'] == pd_email:
110                return user['id']
111
112    # create a user account on PD
113    def add_pd_user(self, pd_name, pd_email, pd_role):
114        try:
115            user = self._apisession.persist('users', 'email', {
116                "name": pd_name,
117                "email": pd_email,
118                "type": "user",
119                "role": pd_role,
120            })
121            return user
122
123        except PDClientError as e:
124            if e.response.status_code == 400:
125                self._module.fail_json(
126                    msg="Failed to add %s due to invalid argument" % (pd_name))
127            if e.response.status_code == 401:
128                self._module.fail_json(msg="Failed to add %s due to invalid API key" % (pd_name))
129            if e.response.status_code == 402:
130                self._module.fail_json(
131                    msg="Failed to add %s due to inability to perform the action within the API token" % (pd_name))
132            if e.response.status_code == 403:
133                self._module.fail_json(
134                    msg="Failed to add %s due to inability to review the requested resource within the API token" % (pd_name))
135            if e.response.status_code == 429:
136                self._module.fail_json(
137                    msg="Failed to add %s due to reaching the limit of making requests" % (pd_name))
138
139    # delete a user account from PD
140    def delete_user(self, pd_user_id, pd_name):
141        try:
142            user_path = path.join('/users/', pd_user_id)
143            self._apisession.rdelete(user_path)
144
145        except PDClientError as e:
146            if e.response.status_code == 404:
147                self._module.fail_json(
148                    msg="Failed to remove %s as user was not found" % (pd_name))
149            if e.response.status_code == 403:
150                self._module.fail_json(
151                    msg="Failed to remove %s due to inability to review the requested resource within the API token" % (pd_name))
152            if e.response.status_code == 401:
153                # print out the list of incidents
154                pd_incidents = self.get_incidents_assigned_to_user(pd_user_id)
155                self._module.fail_json(msg="Failed to remove %s as user has assigned incidents %s" % (pd_name, pd_incidents))
156            if e.response.status_code == 429:
157                self._module.fail_json(
158                    msg="Failed to remove %s due to reaching the limit of making requests" % (pd_name))
159
160    # get incidents assigned to a user
161    def get_incidents_assigned_to_user(self, pd_user_id):
162        incident_info = {}
163        incidents = self._apisession.list_all('incidents', params={'user_ids[]': [pd_user_id]})
164
165        for incident in incidents:
166            incident_info = {
167                'title': incident['title'],
168                'key': incident['incident_key'],
169                'status': incident['status']
170            }
171        return incident_info
172
173    # add a user to a team/teams
174    def add_user_to_teams(self, pd_user_id, pd_teams, pd_role):
175        updated_team = None
176        for team in pd_teams:
177            team_info = self._apisession.find('teams', team, attribute='name')
178            if team_info is not None:
179                try:
180                    updated_team = self._apisession.rput('/teams/' + team_info['id'] + '/users/' + pd_user_id, json={
181                        'role': pd_role
182                    })
183                except PDClientError:
184                    updated_team = None
185        return updated_team
186
187
188def main():
189    module = AnsibleModule(
190        argument_spec=dict(
191            access_token=dict(type='str', required=True, no_log=True),
192            pd_user=dict(type='str', required=True),
193            pd_email=dict(type='str', required=True),
194            state=dict(type='str', default='present', choices=['present', 'absent']),
195            pd_role=dict(type='str', default='responder',
196                         choices=['global_admin', 'manager', 'responder', 'observer', 'stakeholder', 'limited_stakeholder', 'restricted_access']),
197            pd_teams=dict(type='list', elements='str', required=False)),
198        required_if=[['state', 'present', ['pd_teams']], ],
199        supports_check_mode=True,
200    )
201
202    if not HAS_PD_PY:
203        module.fail_json(msg=missing_required_lib('pdpyras', url='https://github.com/PagerDuty/pdpyras'), exception=PD_IMPORT_ERR)
204
205    if not HAS_PD_CLIENT_ERR:
206        module.fail_json(msg=missing_required_lib('PDClientError', url='https://github.com/PagerDuty/pdpyras'), exception=PD_CLIENT_ERR_IMPORT_ERR)
207
208    access_token = module.params['access_token']
209    pd_user = module.params['pd_user']
210    pd_email = module.params['pd_email']
211    state = module.params['state']
212    pd_role = module.params['pd_role']
213    pd_teams = module.params['pd_teams']
214
215    if pd_role:
216        pd_role_gui_value = {
217            'global_admin': 'admin',
218            'manager': 'user',
219            'responder': 'limited_user',
220            'observer': 'observer',
221            'stakeholder': 'read_only_user',
222            'limited_stakeholder': 'read_only_limited_user',
223            'restricted_access': 'restricted_access'
224        }
225        pd_role = pd_role_gui_value[pd_role]
226
227    # authenticate with PD API
228    try:
229        session = APISession(access_token)
230    except PDClientError as e:
231        module.fail_json(msg="Failed to authenticate with PagerDuty: %s" % e)
232
233    user = PagerDutyUser(module, session)
234
235    user_exists = user.does_user_exist(pd_email)
236
237    if user_exists:
238        if state == "absent":
239            # remove user
240            if not module.check_mode:
241                user.delete_user(user_exists, pd_user)
242            module.exit_json(changed=True, result="Successfully deleted user %s" % pd_user)
243        else:
244            module.exit_json(changed=False, result="User %s already exists." % pd_user)
245
246        # in case that the user does not exist
247    else:
248        if state == "absent":
249            module.exit_json(changed=False, result="User %s was not found." % pd_user)
250
251        else:
252            # add user, adds user with the default notification rule and contact info (email)
253            if not module.check_mode:
254                user.add_pd_user(pd_user, pd_email, pd_role)
255                # get user's id
256                pd_user_id = user.does_user_exist(pd_email)
257                # add a user to the team/s
258                user.add_user_to_teams(pd_user_id, pd_teams, pd_role)
259            module.exit_json(changed=True, result="Successfully created & added user %s to team %s" % (pd_user, pd_teams))
260
261
262if __name__ == "__main__":
263    main()
264