Coverage for plugins/modules/purefb_s3acc_export.py: 97%

87 statements  

« prev     ^ index     » next       coverage.py v7.14.1, created at 2026-10-30 17:06 -0400

1#!/usr/bin/python 

2# -*- coding: utf-8 -*- 

3 

4# (c) 2026 

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 

9__metaclass__ = type 

10 

11ANSIBLE_METADATA = { 

12 "metadata_version": "1.1", 

13 "status": ["preview"], 

14 "supported_by": "community", 

15} 

16 

17DOCUMENTATION = r""" 

18--- 

19module: purefb_s3acc_export 

20version_added: '1.26.0' 

21short_description: Manage FlashBlade Object Store Account exports 

22description: 

23- Create, update or delete a FlashBlade object-store account export. 

24- An account export binds an object-store account to a server using an 

25 S3 export policy, making the account's buckets visible for S3 through 

26 that server. 

27- The account export is uniquely identified by the (I(account), I(server)) 

28 pair. 

29author: 

30- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@everpuredata.com> 

31options: 

32 state: 

33 description: 

34 - Define whether the account export should exist or not. 

35 default: present 

36 choices: [ absent, present ] 

37 type: str 

38 account: 

39 description: 

40 - Name of the object store account to export. 

41 type: str 

42 required: true 

43 server: 

44 description: 

45 - Name of the server the account will be exported on. 

46 type: str 

47 required: true 

48 policy: 

49 description: 

50 - Name of the S3 export policy used for the export. 

51 - Required when creating a new account export. 

52 - When supplied on update, the export will be re-pointed at the 

53 named policy. 

54 type: str 

55 enabled: 

56 description: 

57 - If C(true) the account export is enabled. 

58 - Defaults to C(true) on creation if not specified. 

59 type: bool 

60 context: 

61 description: 

62 - Name of fleet member on which to perform the operation. 

63 - This requires the array receiving the request is a member of a fleet 

64 and the context name to be a member of the same fleet. 

65 type: str 

66 default: "" 

67extends_documentation_fragment: 

68- everpure.flashblade.everpure.fb 

69""" 

70 

71EXAMPLES = r""" 

72- name: Export account acme on server fb-01 using policy acme-export 

73 everpure.flashblade.purefb_s3acc_export: 

74 account: acme 

75 server: fb-01 

76 policy: acme-export 

77 fb_url: 10.10.10.2 

78 api_token: T-68618f31-0c9e-4e57-aa44-5306a2cf10e3 

79 

80- name: Disable an existing account export 

81 everpure.flashblade.purefb_s3acc_export: 

82 account: acme 

83 server: fb-01 

84 enabled: false 

85 fb_url: 10.10.10.2 

86 api_token: T-68618f31-0c9e-4e57-aa44-5306a2cf10e3 

87 

88- name: Re-point an existing account export at a different policy 

89 everpure.flashblade.purefb_s3acc_export: 

90 account: acme 

91 server: fb-01 

92 policy: acme-export-v2 

93 fb_url: 10.10.10.2 

94 api_token: T-68618f31-0c9e-4e57-aa44-5306a2cf10e3 

95 

96- name: Delete an account export 

97 everpure.flashblade.purefb_s3acc_export: 

98 account: acme 

99 server: fb-01 

100 state: absent 

101 fb_url: 10.10.10.2 

102 api_token: T-68618f31-0c9e-4e57-aa44-5306a2cf10e3 

103""" 

104 

105RETURN = r""" 

106""" 

107 

108HAS_PYPURECLIENT = True 

109try: 

110 from pypureclient.flashblade import ( 

111 ObjectStoreAccountExportPost, 

112 ObjectStoreAccountExportPatch, 

113 Reference, 

114 ) 

115except ImportError: 

116 HAS_PYPURECLIENT = False 

117 

118from ansible.module_utils.basic import AnsibleModule 

119from ansible_collections.everpure.flashblade.plugins.module_utils.purefb import ( 

120 get_system, 

121 purefb_argument_spec, 

122) 

123from ansible_collections.everpure.flashblade.plugins.module_utils.common import ( 

124 get_error_message, 

125 get_rest_api_version, 

126) 

127from ansible_collections.everpure.flashblade.plugins.module_utils.version import ( 

128 LooseVersion, 

129) 

130 

131MIN_REQUIRED_API_VERSION = "2.20" 

132 

133 

134def _context_kwargs(module): 

135 """Build context_names kwargs only when a context is supplied.""" 

136 if module.params["context"]: 

137 return {"context_names": [module.params["context"]]} 

138 return {} 

139 

140 

141def _get_export(module, blade): 

142 """Return the existing account export object or None.""" 

143 filter_string = "member.name='{0}' and server.name='{1}'".format( 

144 module.params["account"], module.params["server"] 

145 ) 

146 res = blade.get_object_store_account_exports( 

147 filter=filter_string, **_context_kwargs(module) 

148 ) 

149 if res.status_code != 200: 

150 return None 

151 items = list(res.items) 

152 return items[0] if items else None 

153 

154 

155def delete_export(module, blade, export): 

156 """Delete an account export.""" 

157 changed = True 

158 if not module.check_mode: 

159 res = blade.delete_object_store_account_exports( 

160 names=[getattr(export, "name", None)], **_context_kwargs(module) 

161 ) 

162 if res.status_code != 200: 

163 module.fail_json( 

164 msg="Failed to delete account export for account {0} on server {1}. Error: {2}".format( 

165 module.params["account"], 

166 module.params["server"], 

167 get_error_message(res), 

168 ) 

169 ) 

170 module.exit_json(changed=changed) 

171 

172 

173def create_export(module, blade): 

174 """Create a new account export.""" 

175 if not module.params["policy"]: 

176 module.fail_json(msg="policy is required when creating a new account export.") 

177 changed = True 

178 post_kwargs = {"server": Reference(name=module.params["server"])} 

179 if module.params["enabled"] is not None: 

180 post_kwargs["export_enabled"] = module.params["enabled"] 

181 if not module.check_mode: 

182 res = blade.post_object_store_account_exports( 

183 member_names=[module.params["account"]], 

184 policy_names=[module.params["policy"]], 

185 object_store_account_export=ObjectStoreAccountExportPost(**post_kwargs), 

186 **_context_kwargs(module), 

187 ) 

188 if res.status_code != 200: 

189 module.fail_json( 

190 msg="Failed to create account export for account {0} on server {1}. Error: {2}".format( 

191 module.params["account"], 

192 module.params["server"], 

193 get_error_message(res), 

194 ) 

195 ) 

196 module.exit_json(changed=changed) 

197 

198 

199def update_export(module, blade, export): 

200 """Update an existing account export.""" 

201 changed = False 

202 patch_kwargs = {} 

203 

204 if module.params["enabled"] is not None and module.params["enabled"] != getattr( 

205 export, "enabled", None 

206 ): 

207 patch_kwargs["export_enabled"] = module.params["enabled"] 

208 

209 if module.params["policy"]: 

210 current_policy = getattr(getattr(export, "policy", None), "name", None) 

211 if module.params["policy"] != current_policy: 

212 patch_kwargs["policy"] = Reference(name=module.params["policy"]) 

213 

214 if patch_kwargs: 

215 changed = True 

216 if not module.check_mode: 

217 res = blade.patch_object_store_account_exports( 

218 names=[getattr(export, "name", None)], 

219 object_store_account_export=ObjectStoreAccountExportPatch( 

220 **patch_kwargs 

221 ), 

222 **_context_kwargs(module), 

223 ) 

224 if res.status_code != 200: 

225 module.fail_json( 

226 msg="Failed to update account export for account {0} on server {1}. Error: {2}".format( 

227 module.params["account"], 

228 module.params["server"], 

229 get_error_message(res), 

230 ) 

231 ) 

232 

233 module.exit_json(changed=changed) 

234 

235 

236def main(): 

237 argument_spec = purefb_argument_spec() 

238 argument_spec.update( 

239 dict( 

240 state=dict(type="str", default="present", choices=["absent", "present"]), 

241 account=dict(type="str", required=True), 

242 server=dict(type="str", required=True), 

243 policy=dict(type="str"), 

244 enabled=dict(type="bool"), 

245 context=dict(type="str", default=""), 

246 ) 

247 ) 

248 

249 module = AnsibleModule(argument_spec, supports_check_mode=True) 

250 

251 if not HAS_PYPURECLIENT: 

252 module.fail_json(msg="py-pure-client sdk is required for this module") 

253 

254 blade = get_system(module) 

255 api_version = get_rest_api_version(blade) 

256 if LooseVersion(MIN_REQUIRED_API_VERSION) > LooseVersion(api_version): 

257 module.fail_json( 

258 msg="FlashBlade REST version not supported. " 

259 "Minimum version required: {0}".format(MIN_REQUIRED_API_VERSION) 

260 ) 

261 

262 if not module.params["context"]: 

263 # Only default the context to the local array name when this array is a 

264 # member of a fleet. A standalone array rejects any fleet context 

265 # ("Cannot find array in fleet: not in a fleet"), so leave context 

266 # unset - no context_names is then sent. 

267 fleet_res = blade.get_fleets() 

268 if fleet_res.status_code == 200 and list(fleet_res.items): 

269 module.params["context"] = list(blade.get_arrays().items)[0].name 

270 

271 state = module.params["state"] 

272 export = _get_export(module, blade) 

273 

274 if state == "present" and export is None: 

275 create_export(module, blade) 

276 elif state == "present" and export is not None: 

277 update_export(module, blade, export) 

278 elif state == "absent" and export is not None: 

279 delete_export(module, blade, export) 

280 

281 module.exit_json(changed=False) 

282 

283 

284if __name__ == "__main__": 

285 main()