usersdb.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. #!/usr/bin/python
  2. USERVALUES = [ 'append', 'comment', 'createhome', 'expires', 'force', 'generate_ssh_key', 'group', 'groups', 'home',
  3. 'login_class', 'move_home', 'name', 'non_unique', 'password', 'remove', 'shell', 'skeleton', 'ssh_key_bits',
  4. 'ssh_key_comment', 'ssh_key_file', 'ssh_key_passphrase', 'ssh_key_type', 'state', 'system', 'uid', 'update_password',
  5. 'keys']
  6. class UsersDB(object):
  7. def __init__(self, module):
  8. self.module = module
  9. self.users_db = self.module.params["usersdb"]
  10. self.source_user_db = self.module.params["source_userdb"]
  11. self.extract_extra_keys = self.module.params["extract_extra_keys"]
  12. # If we have to userdb and source db lets merge them if not
  13. if self.users_db and self.source_user_db:
  14. self.users_db.update(self.source_user_db)
  15. if self.source_user_db and not self.users_db:
  16. self.users_db = self.source_user_db
  17. if not self.users_db and not self.source_user_db:
  18. self.module.fail_json(msg="Missing argument. You must defined either 'usersdb' or 'source_userdb'.")
  19. self.teams_db = self.module.params["teamsdb"]
  20. self.servers_db = self.module.params["serversdb"]
  21. # Databases
  22. self.lookup_key_db = {} # used for quick lookup
  23. self.expanded_users_db = [] # Used in simple mode
  24. self.expanded_users_key_db = [] # Used in simple mode
  25. self.expanded_server_db = [] # Used in advanced mode for merged User + server
  26. self.expanded_server_key_db = [] # Used in advanced mode
  27. self.extra_users_data = [] # Used for extra data that is not related to user module
  28. self.extra_server_data = [] # Used for extra data that is not related to user module for server mode
  29. def _concat_keys(self, user_name, user_keys=None, server_keys=None, user_status=False):
  30. # Concat keys (if possible) and update username to keys
  31. new_user_keys = []
  32. if user_keys and server_keys:
  33. for user_key in user_keys:
  34. new_user_key = dict(user_key, **server_keys)
  35. new_user_key.pop("name", None)
  36. new_user_key.pop("user", None)
  37. if user_status:
  38. new_user_key.update({"state": "absent"})
  39. new_user_keys.append(new_user_key)
  40. elif server_keys:
  41. # server key is dic
  42. server_keys.pop("name", None)
  43. new_user_keys = [server_keys]
  44. elif user_keys:
  45. for user_key in user_keys:
  46. new_user_key = user_key
  47. new_user_key.pop("user", None)
  48. new_user_key.pop("name", None)
  49. if user_status:
  50. new_user_key.update({"state": "absent"})
  51. new_user_keys.append(new_user_key)
  52. else:
  53. # TODO: Should work without keys
  54. # self.module.fail_json(msg="user '{}' list has no keys defined.".format(user_name))
  55. pass
  56. return new_user_keys
  57. def _merge_key(self, user_keys, sever_keys, user_name, user_status=False):
  58. # Rules ( no real merge happens )
  59. # 1- Default use the user key
  60. # 2- if server has defined keys then use those instead no merge here
  61. if sever_keys:
  62. merged_keys = []
  63. for server_key in sever_keys:
  64. if "user" in server_key:
  65. user = server_key.pop("user", False)
  66. account_key = self.lookup_key_db.get(user)
  67. user_definition = self.users_db.get(user, {})
  68. if user_definition.get("state", "present") in ("absent", "delete", "deleted", "remove", "removed"):
  69. user_status = "absent"
  70. merged_keys += self._concat_keys(user_name, account_key, server_key, user_status=user_status)
  71. elif "team" in server_key:
  72. self.module.fail_json(msg="Team key is not yet implemented")
  73. elif "key" in server_key:
  74. merged_keys += self._concat_keys(user_name, server_keys=server_key, user_status=user_status)
  75. else:
  76. # TODO: Should work without keys
  77. # self.module.fail_json(msg="user '{}' list has no keys defined.".format(user_name))
  78. pass
  79. return merged_keys
  80. else:
  81. return self._concat_keys(user_name, user_keys=user_keys, user_status=user_status)
  82. def _merge_user(self, user_name, user_server):
  83. user_definition = self.users_db.get(user_name, False)
  84. if not user_definition:
  85. self.module.fail_json(msg="'%s' user has no definition" % user_name)
  86. if user_definition.get("state", "present") in ("absent", "delete", "deleted", "remove", "removed"):
  87. user_status = "absent"
  88. else:
  89. user_status = False
  90. # Merge User and Server ( Server has precedence in this case )
  91. merged_user = dict(user_definition.items() + user_server.items())
  92. if user_status:
  93. merged_user.update({"state": "absent"})
  94. user_server = merged_user
  95. user_db_key = self.lookup_key_db.get(user_name, None)
  96. user_server_keys = self._merge_key(user_db_key, user_server.get("keys", None), user_name, user_status)
  97. # In case of team user dict will not be defined so lets just define anyway
  98. user_server.update({"user": user_name})
  99. # Populate DBs
  100. user_server.pop("keys", None) # Get rid of keys
  101. user_server.pop("team", None) # Get rid of team if exists
  102. self.expanded_server_db.append(user_server)
  103. if len(user_server_keys) > 0:
  104. self.expanded_server_key_db.append({"user": user_name, "keys": user_server_keys})
  105. def expand_servers_extra(self, user_name):
  106. ## Add self.extra_server_data
  107. extra_user_item = filter(lambda exta_user: exta_user['name'] == user_name, self.extra_users_data )
  108. # not empty than add
  109. if extra_user_item != []:
  110. # We make a good assumption that we only get one item :( which is somehow true but probably need to check it
  111. self.extra_server_data.append(dict(extra_user_item[0]))
  112. def expand_servers(self):
  113. # Advanced mode Merges users and servers data
  114. # Expand server will overwrite same attributes defined in user db except for state = "absent"
  115. for user_server in self.servers_db:
  116. team_name = user_server.get("team", False)
  117. user_name = user_server.get("user", False) or user_server.get("name", False)
  118. if user_name:
  119. self._merge_user(user_name, user_server)
  120. ## Add self.extra_server_data
  121. if self.extract_extra_keys:
  122. self.expand_servers_extra(user_name)
  123. elif team_name:
  124. team_definition = self.teams_db.get(team_name, False)
  125. if not team_definition:
  126. self.module.fail_json(msg="'%s' team has no definition" % team_name)
  127. for user_in_team in team_definition:
  128. ## Add self.extra_server_data
  129. if self.extract_extra_keys:
  130. self.expand_servers_extra(user_in_team)
  131. self._merge_user(user_in_team, user_server)
  132. else:
  133. self.module.fail_json(msg="Your server definition has no user or team. Please check your data type. "
  134. "for '{}'".format(user_server))
  135. def expand_keys(self, keys, user):
  136. # if len(keys) == 0:
  137. # # TODO: Should work without keys
  138. # self.module.fail_json(msg="user '{}' has no keys defined.".format(user))
  139. user_keys = []
  140. # If key is not a list than its a raw key string
  141. if not isinstance(keys, list):
  142. user_keys.append({"key": keys})
  143. else:
  144. for key in keys:
  145. # Basic syntax check
  146. if isinstance(key, basestring) and "ssh-" in key:
  147. user_keys.append({"key": key})
  148. elif "key" not in key and "name" not in key:
  149. # TODO: Should work without keys
  150. # self.module.fail_json(msg="user '{}' list has no keys defined.".format(key.keys()))
  151. pass
  152. else:
  153. # All is okay just add the dict
  154. user_keys.append(key)
  155. return user_keys
  156. def expand_users(self):
  157. # Get User database which is a dic and create expendaded_user_db and key_db
  158. # Put keys in right dictionary format
  159. for username, user_options in self.users_db.iteritems():
  160. user = {"name": username} # create the account name
  161. # 1- Check for extra keys that dont translate to ansible user module
  162. if self.extract_extra_keys:
  163. extra_user_data = None
  164. for dic_key in user_options.keys():
  165. if dic_key not in USERVALUES:
  166. # Add user and state
  167. if not extra_user_data:
  168. extra_user_data = dict(user)
  169. extra_user_data.update({ "state": user_options.get("state", "present")})
  170. extra_user_data.update({ dic_key: user_options[dic_key] })
  171. user_options.pop(dic_key, None) # Remove item from user DB
  172. # Add extras to a list if any
  173. if extra_user_data:
  174. self.extra_users_data.append(dict(extra_user_data))
  175. # 2- Convert dic to list (servers_db style)
  176. user.update(user_options) # update all other option
  177. # 3- Compile key
  178. unformatted_keys = user_options.get("keys", [])
  179. keys = self.expand_keys(unformatted_keys, user)
  180. # 4- remove keys from userdb if exists
  181. user.pop("keys", None)
  182. # 5- Populate DBs
  183. self.expanded_users_db.append(user) # Populate new list user db
  184. self.expanded_users_key_db.append({"user": username, "keys": keys})
  185. if len(keys) > 0:
  186. self.lookup_key_db.update({username: keys}) # Populate dict key db
  187. def main(self):
  188. self.expand_users()
  189. if self.servers_db and len(self.servers_db) > 0:
  190. # Advanced mode we have to do merges and stuff :D
  191. self.expand_servers()
  192. result = {"changed": False, "msg": "",
  193. "users_db": self.expanded_server_db,
  194. "key_db": self.expanded_server_key_db}
  195. # Add extras if options for servers
  196. if self.extract_extra_keys:
  197. result.update({ "extra" : self.extra_server_data })
  198. else:
  199. # Simple mode no servers db
  200. result = {"changed": False, "msg": "",
  201. "users_db": self.expanded_users_db,
  202. "key_db": self.expanded_users_key_db}
  203. # Add extras if options
  204. if self.extract_extra_keys:
  205. result.update({ "extra" : self.extra_users_data })
  206. self.module.exit_json(**result)
  207. def main():
  208. module = AnsibleModule(
  209. argument_spec=dict(
  210. usersdb=dict(default=None, required=False, type="dict"),
  211. source_userdb=dict(default=None, required=False, type="dict"),
  212. teamsdb=dict(default=None, required=False), # Should be dict but would break if value is false/none
  213. serversdb=dict(default=None, required=False),
  214. extract_extra_keys=dict(default=True, required=False),
  215. ),
  216. supports_check_mode=False
  217. )
  218. UsersDB(module).main()
  219. # import module snippets
  220. from ansible.module_utils.basic import *
  221. main()