1""" 2Vistara Runner 3 4Runner to interact with the Vistara (http://www.vistarait.com/) REST API 5 6:codeauthor: Brad Thurber <brad.thurber@gmail.com> 7 8To use this runner, the Vistara client_id and Vistara oauth2 client_key 9and client_secret must be set in the master config. 10 11For example ``/etc/salt/master.d/_vistara.conf``: 12 13.. code-block:: yaml 14 15 vistara: 16 client_id: client_012345 17 client_key: N0tReallyaR3alKeyButShouldB12345 18 client_secret: ThisI5AreallyLongsecretKeyIwonderwhyTheyMakethemSoBigTheseDays00 19 20 21""" 22 23import logging 24 25import salt.output 26 27# See https://docs.saltproject.io/en/latest/topics/tutorials/http.html 28import salt.utils.http 29 30log = logging.getLogger(__name__) 31 32 33def __virtual__(): 34 """ 35 Check to see if master config has the necessary config 36 """ 37 vistara_config = __opts__["vistara"] if "vistara" in __opts__ else None 38 39 if vistara_config: 40 client_id = vistara_config.get("client_id", None) 41 client_key = vistara_config.get("client_key", None) 42 client_secret = vistara_config.get("client_secret", None) 43 44 if not client_id or not client_key or not client_secret: 45 return ( 46 False, 47 "vistara client_id or client_key or client_secret " 48 "has not been specified in the Salt master config.", 49 ) 50 return True 51 52 return ( 53 False, 54 "vistara config has not been specificed in the Salt master " 55 "config. See documentation for this runner.", 56 ) 57 58 59def _get_vistara_configuration(): 60 """ 61 Return the Vistara configuration read from the master config 62 """ 63 return { 64 "client_id": __opts__["vistara"]["client_id"], 65 "client_key": __opts__["vistara"]["client_key"], 66 "client_secret": __opts__["vistara"]["client_secret"], 67 } 68 69 70def delete_device(name, safety_on=True): 71 """ 72 Deletes a device from Vistara based on DNS name or partial name. By default, 73 delete_device will only perform the delete if a single host is returned. Set 74 safety_on=False to delete all matches (up to default API search page size) 75 76 CLI Example: 77 78 .. code-block:: bash 79 80 salt-run vistara.delete_device 'hostname-101.mycompany.com' 81 salt-run vistara.delete_device 'hostname-101' 82 salt-run vistara.delete_device 'hostname-1' safety_on=False 83 84 """ 85 86 config = _get_vistara_configuration() 87 if not config: 88 return False 89 90 access_token = _get_oath2_access_token( 91 config["client_key"], config["client_secret"] 92 ) 93 94 if not access_token: 95 return "Vistara access token not available" 96 97 query_string = "dnsName:{}".format(name) 98 99 devices = _search_devices(query_string, config["client_id"], access_token) 100 101 if not devices: 102 return "No devices found" 103 104 device_count = len(devices) 105 106 if safety_on and device_count != 1: 107 return ( 108 "Expected to delete 1 device and found {}. " 109 "Set safety_on=False to override.".format(device_count) 110 ) 111 112 delete_responses = [] 113 for device in devices: 114 device_id = device["id"] 115 log.debug(device_id) 116 delete_response = _delete_resource(device_id, config["client_id"], access_token) 117 if not delete_response: 118 return False 119 delete_responses.append(delete_response) 120 121 return delete_responses 122 123 124def _search_devices(query_string, client_id, access_token): 125 126 authstring = "Bearer {}".format(access_token) 127 128 headers = { 129 "Authorization": authstring, 130 "Content-Type": "application/json", 131 "Accept": "application/json", 132 } 133 134 params = {"queryString": query_string} 135 136 method = "GET" 137 url = "https://api.vistara.io/api/v2/tenants/{}/devices/search".format(client_id) 138 139 resp = salt.utils.http.query( 140 url=url, method=method, header_dict=headers, params=params, opts=__opts__ 141 ) 142 143 respbody = resp.get("body", None) 144 if not respbody: 145 return False 146 147 respbodydict = salt.utils.json.loads(resp["body"]) 148 deviceresults = respbodydict["results"] 149 150 return deviceresults 151 152 153def _delete_resource(device_id, client_id, access_token): 154 155 authstring = "Bearer {}".format(access_token) 156 157 headers = { 158 "Authorization": authstring, 159 "Content-Type": "application/json", 160 "Accept": "application/json", 161 } 162 163 method = "DELETE" 164 url = "https://api.vistara.io/api/v2/tenants/{}/rtype/DEVICE/resource/{}".format( 165 client_id, device_id 166 ) 167 168 resp = salt.utils.http.query( 169 url=url, method=method, header_dict=headers, opts=__opts__ 170 ) 171 172 respbody = resp.get("body", None) 173 if not respbody: 174 return False 175 176 respbodydict = salt.utils.json.loads(resp["body"]) 177 178 return respbodydict 179 180 181def _get_oath2_access_token(client_key, client_secret): 182 """ 183 Query the vistara API and get an access_token 184 185 """ 186 if not client_key and not client_secret: 187 log.error( 188 "client_key and client_secret have not been specified " 189 "and are required parameters." 190 ) 191 return False 192 193 method = "POST" 194 url = "https://api.vistara.io/auth/oauth/token" 195 headers = { 196 "Content-Type": "application/x-www-form-urlencoded", 197 "Accept": "application/json", 198 } 199 200 params = { 201 "grant_type": "client_credentials", 202 "client_id": client_key, 203 "client_secret": client_secret, 204 } 205 206 resp = salt.utils.http.query( 207 url=url, method=method, header_dict=headers, params=params, opts=__opts__ 208 ) 209 210 respbody = resp.get("body", None) 211 212 if not respbody: 213 return False 214 215 access_token = salt.utils.json.loads(respbody)["access_token"] 216 return access_token 217