* Initial commit for Pure Storage Ansible module
* Initial commit for Pure Storage Ansible module
* Initial commit for Pure Storage Ansible module
* Fix import issues as required by post-2.2
* Move last import to top
* Follow suggestions and only implement one module per PR
Fix documentation changes requested
* Documentation and formatting changes
... | ... |
@@ -203,6 +203,11 @@ Ansible Changes By Release |
203 | 203 |
* gcp_healthcheck |
204 | 204 |
* gcp_target_proxy |
205 | 205 |
* gcp_url_map |
206 |
+- purestorage |
|
207 |
+ * purefa_host |
|
208 |
+ * purefa_volume |
|
209 |
+ * purefa_hg |
|
210 |
+ * purefa_pg |
|
206 | 211 |
- rundeck |
207 | 212 |
* rundeck_acl_policy |
208 | 213 |
* rundeck_project |
... | ... |
@@ -37,6 +37,7 @@ The following is a list of module_utils files and a general description. The mod |
37 | 37 |
- openstack.py - Utilities for modules that work with Openstack instances. |
38 | 38 |
- openswitch.py - Definitions and helper functions for modules that manage OpenSwitch devices |
39 | 39 |
- powershell.ps1 - Utilities for working with Microsoft Windows clients |
40 |
+- pure.py - Functions and utilities for modules that work with the Pure Storage storage platforms. |
|
40 | 41 |
- pycompat24.py - Exception workaround for Python 2.4. |
41 | 42 |
- rax.py - Definitions and helper functions for modules that work with Rackspace resources. |
42 | 43 |
- redhat.py - Functions for modules that manage Red Hat Network registration and subscriptions |
43 | 44 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,77 @@ |
0 |
+# -*- coding: utf-8 -*- |
|
1 |
+ |
|
2 |
+# This code is part of Ansible, but is an independent component. |
|
3 |
+# This particular file snippet, and this file snippet only, is BSD licensed. |
|
4 |
+# Modules you write using this snippet, which is embedded dynamically by Ansible |
|
5 |
+# still belong to the author of the module, and may assign their own license |
|
6 |
+# to the complete work. |
|
7 |
+# |
|
8 |
+# Copyright (c), Simon Dodsley <simon@purestorage.com>,2017 |
|
9 |
+# All rights reserved. |
|
10 |
+# |
|
11 |
+# Redistribution and use in source and binary forms, with or without modification, |
|
12 |
+# are permitted provided that the following conditions are met: |
|
13 |
+# |
|
14 |
+# * Redistributions of source code must retain the above copyright |
|
15 |
+# notice, this list of conditions and the following disclaimer. |
|
16 |
+# * Redistributions in binary form must reproduce the above copyright notice, |
|
17 |
+# this list of conditions and the following disclaimer in the documentation |
|
18 |
+# and/or other materials provided with the distribution. |
|
19 |
+# |
|
20 |
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
|
21 |
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
|
22 |
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
|
23 |
+# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
|
24 |
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
|
25 |
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
|
26 |
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
|
27 |
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE |
|
28 |
+# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
29 |
+ |
|
30 |
+HAS_PURESTORAGE = True |
|
31 |
+try: |
|
32 |
+ from purestorage import purestorage |
|
33 |
+except ImportError: |
|
34 |
+ HAS_PURESTORAGE = False |
|
35 |
+ |
|
36 |
+from functools import wraps |
|
37 |
+from os import environ |
|
38 |
+from os import path |
|
39 |
+import platform |
|
40 |
+ |
|
41 |
+VERSION = 1.0 |
|
42 |
+USER_AGENT_BASE = 'Ansible' |
|
43 |
+ |
|
44 |
+ |
|
45 |
+def get_system(module): |
|
46 |
+ """Return System Object or Fail""" |
|
47 |
+ user_agent = '%(base)s %(class)s/%(version)s (%(platform)s)' % { |
|
48 |
+ 'base': USER_AGENT_BASE, |
|
49 |
+ 'class': __name__, |
|
50 |
+ 'version': VERSION, |
|
51 |
+ 'platform': platform.platform() |
|
52 |
+ } |
|
53 |
+ array_name = module.params['fa_url'] |
|
54 |
+ api = module.params['api_token'] |
|
55 |
+ |
|
56 |
+ if array_name and api: |
|
57 |
+ system = purestorage.FlashArray(array_name, api_token=api, user_agent=user_agent) |
|
58 |
+ elif environ.get('PUREFA_URL') and environ.get('PUREFA_API'): |
|
59 |
+ system = purestorage.FlashArray(environ.get('PUREFA_URL'), api_token=(environ.get('PUREFA_API')), user_agent=user_agent) |
|
60 |
+ else: |
|
61 |
+ module.fail_json(msg="You must set PUREFA_URL and PUREFA_API environment variables or the fa_url and api_token module arguments") |
|
62 |
+ |
|
63 |
+ try: |
|
64 |
+ system.get() |
|
65 |
+ except Exception: |
|
66 |
+ module.fail_json(msg="Pure Storage FlashArray authentication failed. Check your credentials") |
|
67 |
+ return system |
|
68 |
+ |
|
69 |
+ |
|
70 |
+def purefa_argument_spec(): |
|
71 |
+ """Return standard base dictionary used for the argument_spec argument in AnsibleModule""" |
|
72 |
+ |
|
73 |
+ return dict( |
|
74 |
+ fa_url=dict(), |
|
75 |
+ api_token=dict(no_log=True), |
|
76 |
+ ) |
1 | 78 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,207 @@ |
0 |
+#!/usr/bin/python |
|
1 |
+# -*- coding: utf-8 -*- |
|
2 |
+ |
|
3 |
+# (c) 2017, Simon Dodsley (simon@purestorage.com) |
|
4 |
+# |
|
5 |
+# This file is part of Ansible |
|
6 |
+# |
|
7 |
+# Ansible is free software: you can redistribute it and/or modify |
|
8 |
+# it under the terms of the GNU General Public License as published by |
|
9 |
+# the Free Software Foundation, either version 3 of the License, or |
|
10 |
+# (at your option) any later version. |
|
11 |
+# |
|
12 |
+# Ansible is distributed in the hope that it will be useful, |
|
13 |
+# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
14 |
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
15 |
+# GNU General Public License for more details. |
|
16 |
+# |
|
17 |
+# You should have received a copy of the GNU General Public License |
|
18 |
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>. |
|
19 |
+ |
|
20 |
+ANSIBLE_METADATA = {'metadata_version': '1.0', |
|
21 |
+ 'status': ['preview'], |
|
22 |
+ 'supported_by': 'community'} |
|
23 |
+ |
|
24 |
+ |
|
25 |
+DOCUMENTATION = ''' |
|
26 |
+--- |
|
27 |
+module: purefa_host |
|
28 |
+version_added: "2.4" |
|
29 |
+short_description: Create, Delete and Modify Hosts on Pure Storage FlashArray |
|
30 |
+description: |
|
31 |
+ - This module creates, deletes or modifies hosts on Pure Storage FlashArray. |
|
32 |
+author: Simon Dodsley (@simondodsley) |
|
33 |
+options: |
|
34 |
+ host: |
|
35 |
+ description: |
|
36 |
+ - Host Name |
|
37 |
+ required: true |
|
38 |
+ state: |
|
39 |
+ description: |
|
40 |
+ - Creates host. |
|
41 |
+ - When removing host all connected volumes will be disconnected. |
|
42 |
+ required: false |
|
43 |
+ default: present |
|
44 |
+ choices: [ "present", "absent" ] |
|
45 |
+ protocol: |
|
46 |
+ description: |
|
47 |
+ - Defines the host connection protocol for volumes. |
|
48 |
+ required: false |
|
49 |
+ default: iscsi |
|
50 |
+ choices: [ "iscsi", "fc" ] |
|
51 |
+ wwns: |
|
52 |
+ description: |
|
53 |
+ - List of wwns of the host if protocol is fc |
|
54 |
+ required: false |
|
55 |
+ iqn: |
|
56 |
+ description: |
|
57 |
+ - List of IQNs of the host if protocol is iscsi |
|
58 |
+ required: false |
|
59 |
+ volume: |
|
60 |
+ description: |
|
61 |
+ - Volume name to map to the host |
|
62 |
+ required: false |
|
63 |
+extends_documentation_fragment: |
|
64 |
+ - purestorage |
|
65 |
+''' |
|
66 |
+ |
|
67 |
+EXAMPLES = ''' |
|
68 |
+- name: Create new new host |
|
69 |
+ purefa_host: |
|
70 |
+ host: foo |
|
71 |
+ fa_url: 10.10.10.2 |
|
72 |
+ api_token: e31060a7-21fc-e277-6240-25983c6c4592 |
|
73 |
+ |
|
74 |
+- name: Delete host |
|
75 |
+ purefa_host: |
|
76 |
+ host: foo |
|
77 |
+ state: absent |
|
78 |
+ fa_url: 10.10.10.2 |
|
79 |
+ api_token: e31060a7-21fc-e277-6240-25983c6c4592 |
|
80 |
+ |
|
81 |
+- name: Make sure host bar is available with wwn ports |
|
82 |
+ purefa_host: |
|
83 |
+ host: bar |
|
84 |
+ protocol: fc |
|
85 |
+ wwns: |
|
86 |
+ - "00:00:00:00:00:00:00" |
|
87 |
+ - "11:11:11:11:11:11:11" |
|
88 |
+ fa_url: 10.10.10.2 |
|
89 |
+ api_token: e31060a7-21fc-e277-6240-25983c6c4592 |
|
90 |
+ |
|
91 |
+- name: Make sure host bar is available with iSCSI ports |
|
92 |
+ purefa_host: |
|
93 |
+ host: bar |
|
94 |
+ protocol: iscsi |
|
95 |
+ iqn: |
|
96 |
+ - "iqn.1994-05.com.redhat:7d366003913" |
|
97 |
+ fa_url: 10.10.10.2 |
|
98 |
+ api_token: e31060a7-21fc-e277-6240-25983c6c4592 |
|
99 |
+ |
|
100 |
+- name: Map host foo to volume bar |
|
101 |
+ purefa_host: |
|
102 |
+ host: foo |
|
103 |
+ volume: bar |
|
104 |
+ fa_url: 10.10.10.2 |
|
105 |
+ api_token: e31060a7-21fc-e277-6240-25983c6c4592 |
|
106 |
+''' |
|
107 |
+ |
|
108 |
+RETURN = ''' |
|
109 |
+''' |
|
110 |
+ |
|
111 |
+from ansible.module_utils.basic import AnsibleModule |
|
112 |
+from ansible.module_utils.pure import get_system, purefa_argument_spec |
|
113 |
+ |
|
114 |
+ |
|
115 |
+HAS_PURESTORAGE = True |
|
116 |
+try: |
|
117 |
+ from purestorage import purestorage |
|
118 |
+except ImportError: |
|
119 |
+ HAS_PURESTORAGE = False |
|
120 |
+ |
|
121 |
+ |
|
122 |
+def get_host(module, array): |
|
123 |
+ |
|
124 |
+ host = None |
|
125 |
+ |
|
126 |
+ for h in array.list_hosts(): |
|
127 |
+ if h["name"] == module.params['host']: |
|
128 |
+ host = h |
|
129 |
+ break |
|
130 |
+ |
|
131 |
+ return host |
|
132 |
+ |
|
133 |
+ |
|
134 |
+def make_host(module, array): |
|
135 |
+ |
|
136 |
+ changed = True |
|
137 |
+ |
|
138 |
+ if not module.check_mode: |
|
139 |
+ host = array.create_host(module.params['host']) |
|
140 |
+ if module.params['protocol'] == 'iscsi': |
|
141 |
+ if module.params['iqn']: |
|
142 |
+ array.set_host(module.params['host'], addiqnlist=module.params['iqn']) |
|
143 |
+ if module.params['protocol'] == 'fc': |
|
144 |
+ if module.params['wwns']: |
|
145 |
+ array.set_host(module.params['host'], addwwnlist=module.params['wwns']) |
|
146 |
+ if module.params['volume']: |
|
147 |
+ array.connect_host(module.params['host'], module.params['volume']) |
|
148 |
+ module.exit_json(changed=changed) |
|
149 |
+ |
|
150 |
+ |
|
151 |
+def update_host(module, array): |
|
152 |
+ changed = False |
|
153 |
+ host = module.params['host'] |
|
154 |
+ module.exit_json(changed=changed) |
|
155 |
+ |
|
156 |
+ |
|
157 |
+def delete_host(module, array): |
|
158 |
+ changed = True |
|
159 |
+ if not module.check_mode: |
|
160 |
+ for vol in array.list_host_connections(module.params['host']): |
|
161 |
+ array.disconnect_host(module.params['host'], vol["vol"]) |
|
162 |
+ array.delete_host(module.params['host']) |
|
163 |
+ module.exit_json(changed=changed) |
|
164 |
+ |
|
165 |
+ |
|
166 |
+def main(): |
|
167 |
+ argument_spec = purefa_argument_spec() |
|
168 |
+ argument_spec.update( |
|
169 |
+ dict( |
|
170 |
+ host=dict(required=True), |
|
171 |
+ state=dict(default='present', choices=['present', 'absent']), |
|
172 |
+ protocol=dict(default='iscsi', choices=['iscsi', 'fc']), |
|
173 |
+ iqn=dict(type='list'), |
|
174 |
+ wwns=dict(type='list'), |
|
175 |
+ volume=dict() |
|
176 |
+ ) |
|
177 |
+ ) |
|
178 |
+ |
|
179 |
+ module = AnsibleModule(argument_spec, supports_check_mode=True) |
|
180 |
+ |
|
181 |
+ if not HAS_PURESTORAGE: |
|
182 |
+ module.fail_json(msg='purestorage sdk is required for this module in host') |
|
183 |
+ |
|
184 |
+ state = module.params['state'] |
|
185 |
+ protocol = module.params['protocol'] |
|
186 |
+ array = get_system(module) |
|
187 |
+ host = get_host(module, array) |
|
188 |
+ |
|
189 |
+ if module.params['volume']: |
|
190 |
+ try: |
|
191 |
+ array.get_volume(module.params['volume']) |
|
192 |
+ except: |
|
193 |
+ module.fail_json(msg='Volume {} not found'.format(module.params['volume'])) |
|
194 |
+ |
|
195 |
+ if host and state == 'present': |
|
196 |
+ update_host(module, array) |
|
197 |
+ elif host and state == 'absent': |
|
198 |
+ delete_host(module, array) |
|
199 |
+ elif host is None and state == 'absent': |
|
200 |
+ module.exit_json(changed=False) |
|
201 |
+ else: |
|
202 |
+ make_host(module, array) |
|
203 |
+ |
|
204 |
+ |
|
205 |
+if __name__ == '__main__': |
|
206 |
+ main() |
0 | 207 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,40 @@ |
0 |
+# |
|
1 |
+# (c) 2017, Simon Dodsley <simon@purestorage.com> |
|
2 |
+# |
|
3 |
+# This file is part of Ansible |
|
4 |
+# |
|
5 |
+# Ansible is free software: you can redistribute it and/or modify |
|
6 |
+# it under the terms of the GNU General Public License as published by |
|
7 |
+# the Free Software Foundation, either version 3 of the License, or |
|
8 |
+# (at your option) any later version. |
|
9 |
+# |
|
10 |
+# Ansible is distributed in the hope that it will be useful, |
|
11 |
+# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
12 |
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
13 |
+# GNU General Public License for more details. |
|
14 |
+# |
|
15 |
+# You should have received a copy of the GNU General Public License |
|
16 |
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>. |
|
17 |
+ |
|
18 |
+ |
|
19 |
+class ModuleDocFragment(object): |
|
20 |
+ |
|
21 |
+ # Standard Pure Storage documentation fragment |
|
22 |
+ DOCUMENTATION = ''' |
|
23 |
+options: |
|
24 |
+ fa_url: |
|
25 |
+ description: |
|
26 |
+ - FlashArray management IPv4 address or Hostname. |
|
27 |
+ required: true |
|
28 |
+ api_token: |
|
29 |
+ description: |
|
30 |
+ - FlashArray API token for admin privilaged user. |
|
31 |
+ required: true |
|
32 |
+notes: |
|
33 |
+ - This module requires purestorage python library |
|
34 |
+ - You must set C(PUREFA_URL) and C(PUREFA_API) environment variables |
|
35 |
+ if I(url) and I(api_token) arguments are not passed to the module directly |
|
36 |
+requirements: |
|
37 |
+ - "python >= 2.7" |
|
38 |
+ - purestorage |
|
39 |
+''' |