1""" 2Manage LXD images. 3 4.. versionadded:: 2019.2.0 5 6.. link: https://github.com/lxc/pylxd/blob/master/doc/source/installation.rst 7 8.. note: 9 10 - :role:`pylxd <link>` version 2 is required to let this work, 11 currently only available via pip. 12 13 To install on Ubuntu: 14 15 $ apt-get install libssl-dev python-pip 16 $ pip install -U pylxd 17 18 - you need lxd installed on the minion 19 for the init() and version() methods. 20 21 - for the config_get() and config_get() methods 22 you need to have lxd-client installed. 23 24 25:maintainer: René Jochum <rene@jochums.at> 26:maturity: new 27:depends: python-pylxd 28:platform: Linux 29""" 30 31 32from salt.exceptions import CommandExecutionError, SaltInvocationError 33 34__docformat__ = "restructuredtext en" 35 36__virtualname__ = "lxd_image" 37 38 39def __virtual__(): 40 """ 41 Only load if the lxd module is available in __salt__ 42 """ 43 if "lxd.version" in __salt__: 44 return __virtualname__ 45 return (False, "lxd module could not be loaded") 46 47 48def present( 49 name, 50 source, 51 aliases=None, 52 public=None, 53 auto_update=None, 54 remote_addr=None, 55 cert=None, 56 key=None, 57 verify_cert=True, 58): 59 """ 60 Ensure an image exists, copy it else from source 61 62 name : 63 An alias of the image, this is used to check if the image exists and 64 it will be added as alias to the image on copy/create. 65 66 source : 67 Source dict. 68 69 For an LXD to LXD copy: 70 71 .. code-block:: yaml 72 73 source: 74 type: lxd 75 name: ubuntu/xenial/amd64 # This can also be a fingerprint. 76 remote_addr: https://images.linuxcontainers.org:8443 77 cert: ~/.config/lxd/client.crt 78 key: ~/.config/lxd/client.key 79 verify_cert: False 80 81 .. attention: 82 83 For this kind of remote you also need to provide: 84 - a https:// remote_addr 85 - a cert and key 86 - verify_cert 87 88 From file: 89 90 .. code-block:: yaml 91 92 source: 93 type: file 94 filename: salt://lxd/files/busybox.tar.xz 95 saltenv: base 96 97 From simplestreams: 98 99 .. code-block:: yaml 100 101 source: 102 type: simplestreams 103 server: https://cloud-images.ubuntu.com/releases 104 name: xenial/amd64 105 106 From an URL: 107 108 .. code-block:: yaml 109 110 source: 111 type: url 112 url: https://dl.stgraber.org/lxd 113 114 aliases : 115 List of aliases to append, can be empty. 116 117 public : 118 Make this image public available on this instance? 119 None on source_type LXD means copy source 120 None on source_type file means False 121 122 auto_update : 123 Try to auto-update from the original source? 124 None on source_type LXD means copy source 125 source_type file does not have auto-update. 126 127 remote_addr : 128 An URL to a remote Server, you also have to give cert and key if you 129 provide remote_addr! 130 131 Examples: 132 https://myserver.lan:8443 133 /var/lib/mysocket.sock 134 135 cert : 136 PEM Formatted SSL Zertifikate. 137 138 Examples: 139 ~/.config/lxc/client.crt 140 141 key : 142 PEM Formatted SSL Key. 143 144 Examples: 145 ~/.config/lxc/client.key 146 147 verify_cert : True 148 Wherever to verify the cert, this is by default True 149 but in the most cases you want to set it off as LXD 150 normally uses self-signed certificates. 151 """ 152 if aliases is None: 153 aliases = [] 154 155 # Create a copy of aliases, since we're modifying it here 156 aliases = aliases[:] 157 ret = { 158 "name": name, 159 "source": source, 160 "aliases": aliases, 161 "public": public, 162 "auto_update": auto_update, 163 "remote_addr": remote_addr, 164 "cert": cert, 165 "key": key, 166 "verify_cert": verify_cert, 167 "changes": {}, 168 } 169 170 image = None 171 try: 172 image = __salt__["lxd.image_get_by_alias"]( 173 name, remote_addr, cert, key, verify_cert, _raw=True 174 ) 175 except CommandExecutionError as e: 176 return _error(ret, str(e)) 177 except SaltInvocationError as e: 178 # Image not found 179 pass 180 181 if image is None: 182 if __opts__["test"]: 183 # Test is on, just return that we would create the image 184 msg = 'Would create the image "{}"'.format(name) 185 ret["changes"] = {"created": msg} 186 return _unchanged(ret, msg) 187 188 try: 189 if source["type"] == "lxd": 190 image = __salt__["lxd.image_copy_lxd"]( 191 source["name"], 192 src_remote_addr=source["remote_addr"], 193 src_cert=source["cert"], 194 src_key=source["key"], 195 src_verify_cert=source.get("verify_cert", True), 196 remote_addr=remote_addr, 197 cert=cert, 198 key=key, 199 verify_cert=verify_cert, 200 aliases=aliases, 201 public=public, 202 auto_update=auto_update, 203 _raw=True, 204 ) 205 206 if source["type"] == "file": 207 if "saltenv" not in source: 208 source["saltenv"] = __env__ 209 image = __salt__["lxd.image_from_file"]( 210 source["filename"], 211 remote_addr=remote_addr, 212 cert=cert, 213 key=key, 214 verify_cert=verify_cert, 215 aliases=aliases, 216 public=False if public is None else public, 217 saltenv=source["saltenv"], 218 _raw=True, 219 ) 220 221 if source["type"] == "simplestreams": 222 image = __salt__["lxd.image_from_simplestreams"]( 223 source["server"], 224 source["name"], 225 remote_addr=remote_addr, 226 cert=cert, 227 key=key, 228 verify_cert=verify_cert, 229 aliases=aliases, 230 public=False if public is None else public, 231 auto_update=False if auto_update is None else auto_update, 232 _raw=True, 233 ) 234 235 if source["type"] == "url": 236 image = __salt__["lxd.image_from_url"]( 237 source["url"], 238 remote_addr=remote_addr, 239 cert=cert, 240 key=key, 241 verify_cert=verify_cert, 242 aliases=aliases, 243 public=False if public is None else public, 244 auto_update=False if auto_update is None else auto_update, 245 _raw=True, 246 ) 247 except CommandExecutionError as e: 248 return _error(ret, str(e)) 249 250 # Sync aliases 251 if name not in aliases: 252 aliases.append(name) 253 254 old_aliases = {str(a["name"]) for a in image.aliases} 255 new_aliases = set(map(str, aliases)) 256 257 alias_changes = [] 258 # Removed aliases 259 for k in old_aliases.difference(new_aliases): 260 if not __opts__["test"]: 261 __salt__["lxd.image_alias_delete"](image, k) 262 alias_changes.append('Removed alias "{}"'.format(k)) 263 else: 264 alias_changes.append('Would remove alias "{}"'.format(k)) 265 266 # New aliases 267 for k in new_aliases.difference(old_aliases): 268 if not __opts__["test"]: 269 __salt__["lxd.image_alias_add"](image, k, "") 270 alias_changes.append('Added alias "{}"'.format(k)) 271 else: 272 alias_changes.append('Would add alias "{}"'.format(k)) 273 274 if alias_changes: 275 ret["changes"]["aliases"] = alias_changes 276 277 # Set public 278 if public is not None and image.public != public: 279 if not __opts__["test"]: 280 ret["changes"]["public"] = "Setting the image public to {!s}".format(public) 281 image.public = public 282 __salt__["lxd.pylxd_save_object"](image) 283 else: 284 ret["changes"]["public"] = "Would set public to {!s}".format(public) 285 286 if __opts__["test"] and ret["changes"]: 287 return _unchanged(ret, "Would do {} changes".format(len(ret["changes"].keys()))) 288 289 return _success(ret, "{} changes".format(len(ret["changes"].keys()))) 290 291 292def absent(name, remote_addr=None, cert=None, key=None, verify_cert=True): 293 """ 294 name : 295 An alias or fingerprint of the image to check and delete. 296 297 remote_addr : 298 An URL to a remote Server, you also have to give cert and key if you 299 provide remote_addr! 300 301 Examples: 302 https://myserver.lan:8443 303 /var/lib/mysocket.sock 304 305 cert : 306 PEM Formatted SSL Zertifikate. 307 308 Examples: 309 ~/.config/lxc/client.crt 310 311 key : 312 PEM Formatted SSL Key. 313 314 Examples: 315 ~/.config/lxc/client.key 316 317 verify_cert : True 318 Wherever to verify the cert, this is by default True 319 but in the most cases you want to set it off as LXD 320 normally uses self-signed certificates. 321 """ 322 ret = { 323 "name": name, 324 "remote_addr": remote_addr, 325 "cert": cert, 326 "key": key, 327 "verify_cert": verify_cert, 328 "changes": {}, 329 } 330 image = None 331 try: 332 image = __salt__["lxd.image_get_by_alias"]( 333 name, remote_addr, cert, key, verify_cert, _raw=True 334 ) 335 except CommandExecutionError as e: 336 return _error(ret, str(e)) 337 except SaltInvocationError as e: 338 try: 339 image = __salt__["lxd.image_get"]( 340 name, remote_addr, cert, key, verify_cert, _raw=True 341 ) 342 except CommandExecutionError as e: 343 return _error(ret, str(e)) 344 except SaltInvocationError as e: 345 return _success(ret, 'Image "{}" not found.'.format(name)) 346 347 if __opts__["test"]: 348 ret["changes"] = {"removed": 'Image "{}" would get deleted.'.format(name)} 349 return _success(ret, ret["changes"]["removed"]) 350 351 __salt__["lxd.image_delete"](image) 352 353 ret["changes"] = {"removed": 'Image "{}" has been deleted.'.format(name)} 354 return _success(ret, ret["changes"]["removed"]) 355 356 357def _success(ret, success_msg): 358 ret["result"] = True 359 ret["comment"] = success_msg 360 if "changes" not in ret: 361 ret["changes"] = {} 362 return ret 363 364 365def _unchanged(ret, msg): 366 ret["result"] = None 367 ret["comment"] = msg 368 if "changes" not in ret: 369 ret["changes"] = {} 370 return ret 371 372 373def _error(ret, err_msg): 374 ret["result"] = False 375 ret["comment"] = err_msg 376 if "changes" not in ret: 377 ret["changes"] = {} 378 return ret 379