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
« prev ^ index » next coverage.py v7.14.1, created at 2026-10-30 17:06 -0400
1# -*- coding: utf-8 -*-
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.
31from __future__ import absolute_import, division, print_function
33__metaclass__ = type
35HAS_URLLIB3 = True
36try:
37 import urllib3
38except ImportError:
39 HAS_URLLIB3 = False
41HAS_DISTRO = True
42try:
43 import distro
44except ImportError:
45 HAS_DISTRO = False
47HAS_PYPURECLIENT = True
48try:
49 from pypureclient import flashblade
50except ImportError:
51 PYPURECLIENT = False
53from os import environ
54import platform
56from ansible_collections.everpure.flashblade.plugins.module_utils.common import (
57 get_error_message,
58)
60VERSION = "1.5"
61USER_AGENT_BASE = "Ansible"
62API_AGENT_VERSION = "1.5"
65def get_system(module):
66 """Get authenticated FlashBlade client connection.
68 Creates and returns an authenticated pypureclient FlashBlade client object.
69 Credentials can be provided via module parameters or environment variables.
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
77 Returns:
78 flashblade.Client: Authenticated FlashBlade client object
80 Raises:
81 AnsibleModule.fail_json: If authentication fails or pypureclient is not installed
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
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 }
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")
135 common = {"target": target, "user_agent": user_agent}
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
169def purefb_argument_spec():
170 """Return standard argument specification for FlashBlade modules.
172 Provides the base argument_spec dictionary that should be used by all
173 FlashBlade modules for consistent authentication and connection handling.
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)
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 """
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 )