Coverage for plugins/module_utils/purefb.py: 87%

54 statements  

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

1# -*- coding: utf-8 -*- 

2 

3# This code is part of Ansible, but is an independent component. 

4# This particular file snippet, and this file snippet only, is BSD licensed. 

5# Modules you write using this snippet, which is embedded dynamically by Ansible 

6# still belong to the author of the module, and may assign their own license 

7# to the complete work. 

8# 

9# Copyright (c), Simon Dodsley <simon@everpuredata.com>,2017 

10# All rights reserved. 

11# 

12# Redistribution and use in source and binary forms, with or without modification, 

13# are permitted provided that the following conditions are met: 

14# 

15# * Redistributions of source code must retain the above copyright 

16# notice, this list of conditions and the following disclaimer. 

17# * Redistributions in binary form must reproduce the above copyright notice, 

18# this list of conditions and the following disclaimer in the documentation 

19# and/or other materials provided with the distribution. 

20# 

21# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 

22# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 

23# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 

24# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 

25# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 

26# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 

27# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 

28# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 

29# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 

30 

31from __future__ import absolute_import, division, print_function 

32 

33__metaclass__ = type 

34 

35HAS_URLLIB3 = True 

36try: 

37 import urllib3 

38except ImportError: 

39 HAS_URLLIB3 = False 

40 

41HAS_DISTRO = True 

42try: 

43 import distro 

44except ImportError: 

45 HAS_DISTRO = False 

46 

47HAS_PYPURECLIENT = True 

48try: 

49 from pypureclient import flashblade 

50except ImportError: 

51 PYPURECLIENT = False 

52 

53from os import environ 

54import platform 

55 

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

57 get_error_message, 

58) 

59 

60VERSION = "1.5" 

61USER_AGENT_BASE = "Ansible" 

62API_AGENT_VERSION = "1.5" 

63 

64 

65def get_system(module): 

66 """Get authenticated FlashBlade client connection. 

67 

68 Creates and returns an authenticated pypureclient FlashBlade client object. 

69 Credentials can be provided via module parameters or environment variables. 

70 

71 Args: 

72 module: Ansible module object with params: 

73 - fb_url: FlashBlade management URL 

74 - api_token: API authentication token 

75 - disable_warnings: Whether to disable urllib3 warnings 

76 

77 Returns: 

78 flashblade.Client: Authenticated FlashBlade client object 

79 

80 Raises: 

81 AnsibleModule.fail_json: If authentication fails or pypureclient is not installed 

82 

83 Environment Variables: 

84 PUREFB_URL: FlashBlade URL (alternative to fb_url parameter) 

85 PUREFB_API: API token (alternative to api_token parameter) 

86 PUREFB_ID_TOKEN: Pre-signed JWT (alternative to id_token parameter) 

87 PUREFB_PRIVATE_KEY_FILE / PUREFB_PRIVATE_KEY_PASSWORD 

88 PUREFB_USERNAME / PUREFB_CLIENT_ID / PUREFB_KEY_ID / PUREFB_ISSUER 

89 

90 Note: 

91 Module parameters take precedence over environment variables. 

92 Three mutually-exclusive authentication modes are supported: 

93 1. api_token - static API token (default, backwards compatible). 

94 2. id_token - a pre-signed JWT, exchanged by the array for an 

95 access token at /oauth2/1.0/token. 

96 3. private_key_file with client_id, key_id, issuer and username - 

97 the SDK signs the JWT locally before exchange. 

98 Modes 2 and 3 require a matching API Client registered on the array 

99 (see the purefb_apiclient module). 

100 """ 

101 if not HAS_PYPURECLIENT: 

102 module.fail_json(msg="pypureclient SDK not installed.") 

103 if module.params["disable_warnings"]: 

104 urllib3.disable_warnings() 

105 if HAS_DISTRO: 

106 user_agent = "%(base)s %(class)s/%(version)s (%(platform)s)" % { 

107 "base": USER_AGENT_BASE, 

108 "class": __name__, 

109 "version": VERSION, 

110 "platform": distro.name(pretty=True), 

111 } 

112 else: 

113 user_agent = "%(base)s %(class)s/%(version)s (%(platform)s)" % { 

114 "base": USER_AGENT_BASE, 

115 "class": __name__, 

116 "version": VERSION, 

117 "platform": platform.platform(), 

118 } 

119 

120 # Module parameters take precedence over the matching PUREFB_* env vars. 

121 target = module.params["fb_url"] or environ.get("PUREFB_URL") 

122 api = module.params["api_token"] or environ.get("PUREFB_API") 

123 id_token = module.params.get("id_token") or environ.get("PUREFB_ID_TOKEN") 

124 private_key_file = module.params.get("private_key_file") or environ.get( 

125 "PUREFB_PRIVATE_KEY_FILE" 

126 ) 

127 private_key_password = module.params.get("private_key_password") or environ.get( 

128 "PUREFB_PRIVATE_KEY_PASSWORD" 

129 ) 

130 username = module.params.get("username") or environ.get("PUREFB_USERNAME") 

131 client_id = module.params.get("client_id") or environ.get("PUREFB_CLIENT_ID") 

132 key_id = module.params.get("key_id") or environ.get("PUREFB_KEY_ID") 

133 issuer = module.params.get("issuer") or environ.get("PUREFB_ISSUER") 

134 

135 common = {"target": target, "user_agent": user_agent} 

136 

137 if target and api: 

138 system = flashblade.Client(api_token=api, **common) 

139 elif target and id_token: 

140 system = flashblade.Client(id_token=id_token, **common) 

141 elif target and private_key_file and client_id and key_id and issuer and username: 

142 system = flashblade.Client( 

143 private_key_file=private_key_file, 

144 private_key_password=private_key_password, 

145 client_id=client_id, 

146 key_id=key_id, 

147 issuer=issuer, 

148 username=username, 

149 **common, 

150 ) 

151 else: 

152 module.fail_json( 

153 msg="You must set PUREFB_URL and PUREFB_API environment variables " 

154 "or the fb_url and api_token module arguments. Alternatively, use " 

155 "token-based authentication via id_token, or private_key_file with " 

156 "client_id, key_id, issuer and username (or the matching PUREFB_* " 

157 "environment variables)." 

158 ) 

159 res = system.get_hardware() 

160 if res.status_code != 200: 

161 module.fail_json( 

162 msg="Pure Storage FlashBlade authentication failed. Error: {0}".format( 

163 get_error_message(res) 

164 ) 

165 ) 

166 return system 

167 

168 

169def purefb_argument_spec(): 

170 """Return standard argument specification for FlashBlade modules. 

171 

172 Provides the base argument_spec dictionary that should be used by all 

173 FlashBlade modules for consistent authentication and connection handling. 

174 

175 Returns: 

176 dict: Argument specification dictionary containing: 

177 - fb_url: FlashBlade management URL (optional, can use env var) 

178 - api_token: API authentication token (optional, can use env var, no_log=True) 

179 - disable_warnings: Disable urllib3 warnings (bool, default=False) 

180 

181 Example: 

182 >>> argument_spec = purefb_argument_spec() 

183 >>> argument_spec.update(dict( 

184 ... name=dict(required=True, type='str'), 

185 ... state=dict(default='present', choices=['present', 'absent']) 

186 ... )) 

187 >>> module = AnsibleModule(argument_spec=argument_spec) 

188 """ 

189 

190 return dict( 

191 fb_url=dict(), 

192 api_token=dict(no_log=True), 

193 # OAuth2 / API-client token authentication (alternatives to api_token). 

194 # See the purefb_apiclient module for registering the trusted key. 

195 id_token=dict(no_log=True), 

196 private_key_file=dict(no_log=False), 

197 private_key_password=dict(no_log=True), 

198 username=dict(), 

199 client_id=dict(), 

200 key_id=dict(no_log=False), 

201 issuer=dict(), 

202 disable_warnings=dict(type="bool", default=False), 

203 )