diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..1fd4893 --- /dev/null +++ b/.flake8 @@ -0,0 +1,2 @@ +[flake8] +max-line-length = 160 diff --git a/library/intellij_configure_jdk.py b/library/intellij_configure_jdk.py index e7aabe3..2eecaa9 100644 --- a/library/intellij_configure_jdk.py +++ b/library/intellij_configure_jdk.py @@ -1,22 +1,15 @@ -#!/usr/bin/python +#!/usr/bin/env python3 -# Make coding more python3-ish -from __future__ import (absolute_import, division, print_function) -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils._text import to_bytes, to_native -from distutils.version import LooseVersion -import zipfile -import xml.sax.saxutils -import pwd import grp import os -__metaclass__ = type +import pwd +import xml.sax.saxutils +import zipfile +from pathlib import Path +from typing import Dict, Tuple -ANSIBLE_METADATA = { - 'metadata_version': '1.1', - 'status': ['preview'], - 'supported_by': 'community' -} +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.compat.version import LooseVersion DOCUMENTATION = ''' --- @@ -74,83 +67,75 @@ HAS_LXML = False -def pretty_print(elem): - text = etree.tostring(elem, encoding='iso-8859-1') +def pretty_print(elem: etree.Element) -> str: + text = etree.tostring(elem, encoding='unicode') parser = etree.XMLParser(remove_blank_text=True) xml = etree.fromstring(text, parser) - return etree.tostring(xml, - encoding='iso-8859-1', - pretty_print=True, - xml_declaration=False) + return etree.tostring(xml, encoding='unicode', pretty_print=True, xml_declaration=False) -def get_java_version(module, jdk_home): - executable = os.path.join(jdk_home, 'bin', 'java') - if not os.path.isfile(executable): - module.fail_json(msg='File not found: %s' % executable) +def get_java_version(module: AnsibleModule, jdk_home: Path) -> str: + executable = jdk_home / 'bin' / 'java' + if not executable.is_file(): + module.fail_json(msg=f'File not found: {executable}') - rc, out, err = module.run_command([executable, '-version']) + rc, out, err = module.run_command([str(executable), '-version']) if rc != 0: - module.fail_json(msg='Error while querying Java version: %s' % - (out + err)) + module.fail_json(msg=f'Error while querying Java version: {out + err}') return err.splitlines()[0] -def get_class_path(module, jdk_home): - jre_lib = os.path.join(jdk_home, 'jre', 'lib') +def get_class_path(module: AnsibleModule, jdk_home: Path) -> str: + jre_lib = jdk_home / 'jre' / 'lib' - jre_ext = os.path.join(jre_lib, 'ext') + jre_ext = jre_lib / 'ext' - jmods = os.path.join(jdk_home, 'jmods') + jmods = jdk_home / 'jmods' - if os.path.isdir(jre_ext): + if jre_ext.is_dir(): - files = [os.path.join(jre_lib, x) for x in os.listdir(jre_lib)] - files = files + [os.path.join(jre_ext, x) for x in os.listdir(jre_ext)] + files = list(jre_lib.iterdir()) + list(jre_ext.iterdir()) - files = [x for x in files if os.path.isfile(x) and x.endswith('.jar')] + files = [x for x in files if x.is_file() and x.suffix == '.jar'] files = sorted(files) - urls = ['jar://%s!/' % x for x in files] + urls = [f'jar://{str(x)}!/' for x in files] - elements = [ - '' % xml.sax.saxutils.quoteattr(x) - for x in urls - ] + elements = [f'' for x in urls] return "\n".join(elements) - elif os.path.isdir(jmods): + elif jmods.is_dir(): - files = [os.path.join(jmods, x) for x in os.listdir(jmods)] + files = list(jmods.iterdir()) - files = [x for x in files if os.path.isfile(x) and x.endswith('.jmod')] + files = [x for x in files if x.is_file() and x.suffix == '.jmod'] - module_names = [os.path.basename(x)[:-5] for x in files] + module_names = [x.stem for x in files] module_names = sorted(module_names) - urls = ['jrt://%s!/%s' % (jdk_home, x) for x in module_names] + urls = [f'jrt://{jdk_home}!/{x}' for x in module_names] - elements = [ - '' % xml.sax.saxutils.quoteattr(x) - for x in urls - ] + elements = [f'' for x in urls] return "\n".join(elements) else: module.fail_json( - msg=("Unsupported JDK directory layout: %s. If you're " - "using Java > 9 you may need to install the " - "jmods package e.g. yum install " - "java-11-openjdk-jmods.") % jdk_home) + msg=( + f"Unsupported JDK directory layout: {jdk_home}. If you're " + "using Java > 9 you may need to install the " + "jmods package e.g. yum install " + "java-11-openjdk-jmods." + ) + ) -def get_source_path(module, jdk_home): - jmod_src = os.path.join(jdk_home, 'lib', 'src.zip') +def get_source_path(module: AnsibleModule, jdk_home: Path) -> str: + jmod_src = jdk_home / 'lib' / 'src.zip' - if os.path.isfile(jmod_src): + if jmod_src.is_file(): with zipfile.ZipFile(jmod_src, 'r') as srczip: files = srczip.namelist() @@ -161,135 +146,108 @@ def get_source_path(module, jdk_home): module_names = sorted(module_names) - urls = [ - 'jar://%s/lib/src.zip!/%s' % (jdk_home, x) for x in module_names - ] + urls = [f'jar://{jdk_home / "lib" / "src.zip"}!/{x}' for x in module_names] - elements = [ - '' % xml.sax.saxutils.quoteattr(x) - for x in urls - ] + elements = [f'' for x in urls] return "\n".join(elements) - elif os.path.isdir(jdk_home): + elif jdk_home.is_dir(): - files = [os.path.join(jdk_home, x) for x in os.listdir(jdk_home)] + files = list(jdk_home.iterdir()) - files = [ - x for x in files if os.path.isfile(x) and x.endswith('src.zip') - ] + files = [x for x in files if x.is_file() and x.name.endswith('src.zip')] files = sorted(files) - urls = ['jar://%s!/' % x for x in files] + urls = [f'jar://{x}!/' for x in files] - elements = [ - '' % xml.sax.saxutils.quoteattr(x) - for x in urls - ] + elements = [f'' for x in urls] return "\n".join(elements) else: - module.fail_json(msg='Directory not found: %s' % jdk_home) - - -def create_jdk_xml(module, intellij_user_config_dir, jdk_name, jdk_home): - params = { - 'jdk_name': - xml.sax.saxutils.quoteattr(jdk_name), - 'java_version': - xml.sax.saxutils.quoteattr(get_java_version(module, jdk_home)), - 'jdk_home': - xml.sax.saxutils.quoteattr(jdk_home), - 'class_path': - get_class_path(module, jdk_home), - 'source_path': - get_source_path(module, jdk_home) - } - - return etree.fromstring(''' - - - - - - - - - - - - - %(class_path)s - - - - - - %(source_path)s - - - - ''' % params) - - -def make_dirs(path, mode, uid, gid): - dirs = [path] - dirname = os.path.dirname(path) - while dirname != '/': - dirs.insert(0, dirname) - dirname = os.path.dirname(dirname) - - for dirname in dirs: - if not os.path.exists(dirname): - os.mkdir(dirname, mode) - os.chown(dirname, uid, gid) - - -def configure_jdk(module, intellij_user_config_dir, jdk_name, jdk_home, uid, - gid): - options_dir = os.path.join(intellij_user_config_dir, 'options') - - project_default_path = os.path.join(options_dir, 'jdk.table.xml') - - create_jdk_table = (not os.path.isfile(project_default_path) - ) or os.path.getsize(project_default_path) == 0 + module.fail_json(msg=f'Directory not found: {jdk_home}') + + +def create_jdk_xml(module: AnsibleModule, jdk_name: str, jdk_home: Path) -> etree.Element: + java_version = get_java_version(module, jdk_home) + class_path = get_class_path(module, jdk_home) + source_path = get_source_path(module, jdk_home) + + return etree.fromstring(f''' + + + + + + + + + + + + + {class_path} + + + + + + {source_path} + + + +''') + + +def make_dirs(path: Path, mode: int, uid: int, gid: int) -> None: + dirs = [] + current = path + while not current.exists(): + dirs.append(current) + current = current.parent + dirs.reverse() + for dirpath in dirs: + dirpath.mkdir(mode=mode) + os.chown(str(dirpath), uid, gid) + dirpath.chmod(mode) + + +def configure_jdk(module: AnsibleModule, intellij_user_config_dir: Path, jdk_name: str, jdk_home: Path, uid: int, gid: int) -> Tuple[bool, Dict[str, str]]: + options_dir = intellij_user_config_dir / 'options' + project_default_path = options_dir / 'jdk.table.xml' + + create_jdk_table = (not project_default_path.is_file()) or project_default_path.stat().st_size == 0 if create_jdk_table: if not module.check_mode: - if not os.path.isdir(options_dir): + if not options_dir.is_dir(): make_dirs(options_dir, 0o775, uid, gid) - if not os.path.isfile(project_default_path): - with open(project_default_path, 'wb', 0o664) as xml_file: - xml_file.write(to_bytes('')) - os.chown(project_default_path, uid, gid) + if not project_default_path.is_file(): + project_default_path.touch() + os.chown(str(project_default_path), uid, gid) + project_default_path.chmod(0o664) jdk_table_root = etree.Element('application') jdk_table_doc = etree.ElementTree(jdk_table_root) before = '' else: - jdk_table_doc = etree.parse(project_default_path) + jdk_table_doc = etree.parse(str(project_default_path)) jdk_table_root = jdk_table_doc.getroot() before = pretty_print(jdk_table_root) if jdk_table_root.tag != 'application': - module.fail_json(msg='Unsupported root element: %s' % - jdk_table_root.tag) + module.fail_json(msg=f'Unsupported root element: {jdk_table_root.tag}') - project_jdk_table = jdk_table_root.find( - './component[@name="ProjectJdkTable"]') + project_jdk_table = jdk_table_root.find('./component[@name="ProjectJdkTable"]') if project_jdk_table is None: - project_jdk_table = etree.SubElement(jdk_table_root, - 'component', - name='ProjectJdkTable') + project_jdk_table = etree.SubElement(jdk_table_root, 'component', name='ProjectJdkTable') - new_jdk = create_jdk_xml(module, intellij_user_config_dir, jdk_name, - jdk_home) + new_jdk = create_jdk_xml(module, jdk_name, jdk_home) new_jdk_string = pretty_print(new_jdk) - old_jdk = project_jdk_table.find('./jdk/name[@value="%s"]/..' % jdk_name) + old_jdk = project_jdk_table.find(f'./jdk/name[@value="{jdk_name}"]/..') if old_jdk is None: old_jdk_string = '' changed = True @@ -303,20 +261,19 @@ def configure_jdk(module, intellij_user_config_dir, jdk_name, jdk_home, uid, after = pretty_print(jdk_table_root) if changed and not module.check_mode: - with open(project_default_path, 'wb') as xml_file: - xml_file.write(to_bytes(after)) - - return changed, {'before:': before, 'after': after} + project_default_path.write_text(after, encoding='iso-8859-1') + return changed, {'before': before, 'after': after} -def run_module(): - module_args = dict(intellij_user_config_dir=dict(type='str', - required=True), - jdk_name=dict(type='str', required=True), - jdk_home=dict(type='str', required=True), - owner=dict(type='str', required=True), - group=dict(type='str', required=True)) +def run_module() -> None: + module_args = dict( + intellij_user_config_dir=dict(type='str', required=True), + jdk_name=dict(type='str', required=True), + jdk_home=dict(type='str', required=True), + owner=dict(type='str', required=True), + group=dict(type='str', required=True) + ) module = AnsibleModule(argument_spec=module_args, supports_check_mode=True) @@ -326,48 +283,45 @@ def run_module(): try: uid = int(owner) except ValueError: - uid = pwd.getpwnam(owner).pw_uid + try: + uid = pwd.getpwnam(owner).pw_uid + except KeyError: + module.fail_json(msg=f"User '{owner}' does not exist") username = pwd.getpwuid(uid).pw_name try: gid = int(group) except ValueError: - gid = grp.getgrnam(group).gr_gid + try: + gid = grp.getgrnam(group).gr_gid + except KeyError: + module.fail_json(msg=f"Group '{group}' does not exist") - intellij_user_config_dir = os.path.expanduser( - os.path.join('~' + username, - module.params['intellij_user_config_dir'])) + intellij_user_config_dir = Path('~' + username, module.params['intellij_user_config_dir']).expanduser() jdk_name = module.params['jdk_name'] - jdk_home = os.path.expanduser(module.params['jdk_home']) + jdk_home = Path(module.params['jdk_home']).expanduser() # Check if we have lxml 2.3.0 or newer installed if not HAS_LXML: - module.fail_json( - msg='The xml ansible module requires the lxml python ' - 'library installed on the managed machine') - elif LooseVersion('.'.join( - to_native(f) for f in etree.LXML_VERSION)) < LooseVersion('2.3.0'): - module.fail_json( - msg='The xml ansible module requires lxml 2.3.0 or newer installed' - ' on the managed machine') - elif LooseVersion('.'.join( - to_native(f) for f in etree.LXML_VERSION)) < LooseVersion('3.0.0'): - module.warn( - 'Using lxml version lower than 3.0.0 does not guarantee ' - 'predictable element attribute order.') + module.fail_json(msg='The xml ansible module requires the lxml python library installed on the managed machine') + else: + lxml_version = LooseVersion('.'.join(str(f) for f in etree.LXML_VERSION)) + if lxml_version < LooseVersion('2.3.0'): + module.fail_json(msg='The xml ansible module requires lxml 2.3.0 or newer installed on the managed machine') + elif lxml_version < LooseVersion('3.0.0'): + module.warn('Using lxml version lower than 3.0.0 does not guarantee predictable element attribute order.') - changed, diff = configure_jdk(module, intellij_user_config_dir, jdk_name, - jdk_home, uid, gid) + changed, diff = configure_jdk(module, intellij_user_config_dir, jdk_name, jdk_home, uid, gid) if changed: - msg = 'JDK %s has been configured' % jdk_name + msg = f'JDK {jdk_name} has been configured' else: - msg = 'JDK %s was already configured' % jdk_name + msg = f'JDK {jdk_name} was already configured' module.exit_json(changed=changed, msg=msg, diff=diff) -def main(): +def main() -> None: run_module() diff --git a/library/intellij_install_plugin.py b/library/intellij_install_plugin.py index b9d748d..626c3fa 100644 --- a/library/intellij_install_plugin.py +++ b/library/intellij_install_plugin.py @@ -1,39 +1,22 @@ -#!/usr/bin/python - -# Make coding more python3-ish -from __future__ import (absolute_import, division, print_function) -from ansible.module_utils.urls import ConnectionError, NoSSLError, open_url -from ansible.module_utils.basic import AnsibleModule, get_distribution -from ansible.module_utils._text import to_native -from ansible.module_utils.six import PY3 -import ansible.module_utils.six.moves.urllib.error as urllib_error -from distutils.version import LooseVersion -import zipfile -import traceback -import time -import tempfile -import socket -import shutil -import re -import pwd -import os -import json -import hashlib -import grp - -try: - import httplib -except ImportError: - # Python 3 - import http.client as httplib +#!/usr/bin/env python3 -__metaclass__ = type +import grp +import hashlib +import json +import os +import pwd +import re +import shutil +import tempfile +import time +import urllib.parse +import zipfile +from pathlib import Path +from typing import Any, Optional, Tuple -ANSIBLE_METADATA = { - 'metadata_version': '1.1', - 'status': ['preview'], - 'supported_by': 'community' -} +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.compat.version import LooseVersion +from ansible.module_utils.urls import fetch_url DOCUMENTATION = ''' --- @@ -97,178 +80,74 @@ except ImportError: HAS_LXML = False -try: - from ansible.module_utils.six.moves.urllib.parse import urlencode, urljoin - HAS_URLPARSE = True -except BaseException: - HAS_URLPARSE = False +def make_dirs(path: Path, mode: int, uid: int, gid: int) -> None: + dirs_to_create = [] -def make_dirs(module, path, mode, uid, gid): - dirs = [path] - dirname = os.path.dirname(path) - while dirname != '/': - dirs.insert(0, dirname) - dirname = os.path.dirname(dirname) + while not path.exists(): + dirs_to_create.append(path) + if path.parent == path: + # Reached the root directory + break + path = path.parent + + dirs_to_create.reverse() - for dirname in dirs: - if not os.path.exists(dirname): - os.mkdir(dirname, mode) - os.chown(dirname, uid, gid) + for dir_path in dirs_to_create: + if not dir_path.exists(): + dir_path.mkdir(mode=mode, exist_ok=True) + os.chown(str(dir_path), uid, gid) -def get_root_dirname_from_zip(module, zipfile_path): - if not os.path.isfile(zipfile_path): - module.fail_json(msg='File not found: %s' % zipfile_path) +def get_root_dirname_from_zip(module: AnsibleModule, zipfile_path: Path) -> str: + if not zipfile_path.is_file(): + module.fail_json(msg=f'File not found: {zipfile_path}') with zipfile.ZipFile(zipfile_path, 'r') as z: files = z.namelist() - if len(files) == 0: - module.fail_json(msg='Plugin is empty: %s' % zipfile_path) + if not files: + module.fail_json(msg=f'Plugin is empty: {zipfile_path}') return files[0].split('/')[0] -def extract_zip(module, output_dir, zipfile_path, uid, gid): - if not os.path.isfile(zipfile_path): - module.fail_json(msg='File not found: %s' % zipfile_path) +def extract_zip(module: AnsibleModule, output_dir: Path, zipfile_path: Path, uid: int, gid: int) -> None: + if not zipfile_path.is_file(): + module.fail_json(msg=f'File not found: {zipfile_path}') with zipfile.ZipFile(zipfile_path, 'r') as z: z.extractall(output_dir) files = z.namelist() + output_dir_resolved = output_dir.resolve() + for file_entry in files: - absolute_file = os.path.join(output_dir, file_entry) - while not os.path.samefile(absolute_file, output_dir): + absolute_file = (output_dir / file_entry).resolve() + while not absolute_file.samefile(output_dir_resolved): os.chown(absolute_file, uid, gid) - absolute_file = os.path.normpath( - os.path.join(absolute_file, os.pardir)) - - -def fetch_url(module, url, method=None, timeout=10, follow_redirects=True): - - if not HAS_URLPARSE: - module.fail_json(msg='urlparse is not installed') + absolute_file = absolute_file.parent.resolve() - # ensure we use proper tempdir - old_tempdir = tempfile.tempdir - tempfile.tempdir = module.tmpdir - r = None - info = dict(url=url, status=-1) - try: - r = open_url(url, - method=method, - timeout=timeout, - follow_redirects=follow_redirects) - # Lowercase keys, to conform to py2 behavior, so that py3 and py2 are - # predictable - info.update(dict((k.lower(), v) for k, v in r.info().items())) - - # Don't be lossy, append header values for duplicate headers - # In Py2 there is nothing that needs done, py2 does this for us - if PY3: - temp_headers = {} - for name, value in r.headers.items(): - # The same as above, lower case keys to match py2 behavior, and - # create more consistent results - name = name.lower() - if name in temp_headers: - temp_headers[name] = ', '.join((temp_headers[name], value)) - else: - temp_headers[name] = value - info.update(temp_headers) - - # finally update the result with a message about the fetch - info.update( - dict(msg='OK (%s bytes)' % - r.headers.get('Content-Length', 'unknown'), - url=r.geturl(), - status=r.code)) - except NoSSLError as e: - distribution = get_distribution() - if distribution is not None and distribution.lower() == 'redhat': - module.fail_json( - msg='%s. You can also install python-ssl from EPEL' % - to_native(e), **info) - else: - module.fail_json(msg='%s' % to_native(e), **info) - except (ConnectionError, ValueError) as e: - module.fail_json(msg=to_native(e), **info) - except urllib_error.HTTPError as e: - try: - body = e.read() - except AttributeError: - body = '' - - # Try to add exception info to the output but don't fail if we can't - try: - # Lowercase keys, to conform to py2 behavior, so that py3 and py2 - # are predictable - info.update(dict((k.lower(), v) for k, v in e.info().items())) - except Exception: - pass - - info.update({'msg': to_native(e), 'body': body, 'status': e.code}) - - except urllib_error.URLError as e: - code = int(getattr(e, 'code', -1)) - info.update(dict(msg='Request failed: %s' % to_native(e), status=code)) - except socket.error as e: - info.update( - dict( - msg='Connection failure: %s' % - to_native(e), - status=- - 1)) - except httplib.BadStatusLine as e: - info.update( - dict( - msg=('Connection failure: connection was closed before a valid' - ' response was received: %s') % - to_native( - e.line), - status=- - 1)) - except Exception as e: - info.update(dict(msg='An unknown error occurred: %s' % to_native(e), - status=-1), - exception=traceback.format_exc()) - finally: - tempfile.tempdir = old_tempdir - - return r, info - - -def get_build_number_from_xml(module, intellij_home, xml): +def get_build_number_from_xml(module: AnsibleModule, intellij_home: Path, xml: Any) -> str: info_doc = etree.parse(xml) build = info_doc.find('./build/[@number]') if build is None: - build = info_doc.find( - './{http://jetbrains.org/intellij/schema/application-info}build/' - '[@number]' - ) + build = info_doc.find('./{http://jetbrains.org/intellij/schema/application-info}build/''[@number]') if build is None: - module.fail_json( - msg=('Unable to determine IntelliJ version from path: %s ' - '(unsupported schema - missing build element)') % - intellij_home) + module.fail_json(msg=f'Unable to determine IntelliJ version from path: {intellij_home} (unsupported schema - missing build element)') build_number = build.get('number') if build_number is None: - module.fail_json( - msg=('Unable to determine IntelliJ version from path: %s ' - '(unsupported schema - missing build number value)') % - intellij_home) + module.fail_json(msg=f'Unable to determine IntelliJ version from path: {intellij_home} (unsupported schema - missing build number value)') return build_number -def get_build_number_from_jar(module, intellij_home): - resources_jar = os.path.join(intellij_home, 'lib', 'resources.jar') +def get_build_number_from_jar(module: AnsibleModule, intellij_home: Path) -> Optional[str]: + resources_jar = intellij_home / 'lib' / 'resources.jar' - if not os.path.isfile(resources_jar): + if not resources_jar.is_file(): return None with zipfile.ZipFile(resources_jar, 'r') as resource_zip: @@ -278,76 +157,59 @@ def get_build_number_from_jar(module, intellij_home): except KeyError: try: with resource_zip.open('idea/ApplicationInfo.xml') as xml: - return get_build_number_from_xml(module, intellij_home, - xml) + return get_build_number_from_xml(module, intellij_home, xml) except KeyError: - module.fail_json( - msg=('Unable to determine IntelliJ version from path: %s ' - '(XML info file not found in "lib/resources.jar")') % - intellij_home) + module.fail_json(msg=f'Unable to determine IntelliJ version from path: {intellij_home} (XML info file not found in "lib/resources.jar")') -def get_build_number_from_json(module, intellij_home): - product_info_path = os.path.join(intellij_home, 'product-info.json') +def get_build_number_from_json(module: AnsibleModule, intellij_home: Path) -> str: + product_info_path = intellij_home / 'product-info.json' - if not os.path.isfile(product_info_path): - module.fail_json( - msg=('Unable to determine IntelliJ version from path: %s ' - '("product-info.json" not found)') % - intellij_home) + if not product_info_path.is_file(): + module.fail_json(msg=f'Unable to determine IntelliJ version from path: {intellij_home} ("product-info.json" not found)') - with open(product_info_path) as product_info_file: + with product_info_path.open() as product_info_file: product_info = json.load(product_info_file) return product_info['buildNumber'] -def get_build_number(module, intellij_home): - return get_build_number_from_jar( - module, intellij_home) or get_build_number_from_json( - module, intellij_home) - +def get_build_number(module: AnsibleModule, intellij_home: Path) -> str: + return get_build_number_from_jar(module, intellij_home) or get_build_number_from_json(module, intellij_home) -def get_plugin_info(module, plugin_manager_url, intellij_home, plugin_id): +def get_plugin_info(module: AnsibleModule, plugin_manager_url: str, intellij_home: Path, plugin_id: str) -> Tuple[str, str]: build_number = get_build_number(module, intellij_home) params = {'action': 'download', 'build': build_number, 'id': plugin_id} - query_params = urlencode(params) + query_params = urllib.parse.urlencode(params) - url = '%s?%s' % (plugin_manager_url, query_params) - for _ in range(0, 3): - resp, info = fetch_url(module, - url, - method='HEAD', - timeout=3, - follow_redirects=False) - if resp is not None: + url = f'{plugin_manager_url}?{query_params}' + + for _ in range(3): + module.params['follow_redirects'] = 'none' + resp, info = fetch_url(module, url, method='HEAD', timeout=3) + if resp: resp.close() - status_code = info['status'] + status_code = info.get('status', -1) if status_code == 404: - module.fail_json(msg='Unable to find plugin "%s" for build "%s"' % - (plugin_id, build_number)) - if status_code > -1 and status_code < 400: + module.fail_json(msg=f'Unable to find plugin "{plugin_id}" for build "{build_number}"') + if 0 <= status_code < 400: break - # 3 retries 5 seconds appart + # 3 retries 5 seconds apart time.sleep(5) if status_code == -1 or status_code >= 400: - module.fail_json(msg='Error querying url "%s": %s' % - (url, info['msg'])) + module.fail_json(msg=f'Error querying url "{url}": {info.get("msg", "Unknown error")}') - location = info.get('location') - if location is None: - location = info.get('Location') - if location is None: - module.fail_json(msg='Unsupported HTTP response for: %s (status=%s)' % - (url, status_code)) + location = info.get('location') or info.get('Location') + if not location: + module.fail_json(msg=f'Unsupported HTTP response for: {url} (status={status_code})') if location.startswith('http'): plugin_url = location else: - plugin_url = urljoin(plugin_manager_url, location) + plugin_url = urllib.parse.urljoin(plugin_manager_url, location) jar_pattern = re.compile(r'/(?P[^/]+\.jar)(?:\?.*)$') jar_matcher = jar_pattern.search(plugin_url) @@ -355,95 +217,90 @@ def get_plugin_info(module, plugin_manager_url, intellij_home, plugin_id): if jar_matcher: file_name = jar_matcher.group('file_name') else: - versioned_pattern = re.compile( - r'(?P[0-9]+)/(?P[0-9]+)/' - r'(?P[^/]+)(?:\?.*)$' - ) + versioned_pattern = re.compile(r'(?P[0-9]+)/(?P[0-9]+)/(?P[^/]+)(?:\?.*)$') versioned_matcher = versioned_pattern.search(plugin_url) if versioned_matcher: - file_name = '%s-%s-%s' % (versioned_matcher.group('plugin_id'), - versioned_matcher.group('update_id'), - versioned_matcher.group('file_name')) + plugin_id = versioned_matcher.group('plugin_id') + update_id = versioned_matcher.group('update_id') + file_name = versioned_matcher.group('file_name') + file_name = f'{plugin_id}-{update_id}-{file_name}' else: - hash_object = hashlib.sha256(plugin_url) - file_name = '%s-%s.zip' % (plugin_id, hash_object.hexdigest()) + hash_object = hashlib.sha256(plugin_url.encode()) + file_name = f'{plugin_id}-{hash_object.hexdigest()}.zip' return plugin_url, file_name -def download_plugin(module, plugin_url, file_name, download_cache): - if not os.path.isdir(download_cache): - os.makedirs(download_cache, 0o775) +def download_plugin(module: AnsibleModule, plugin_url: str, file_name: str, download_cache: Path) -> Path: + if not download_cache.is_dir(): + download_cache.mkdir(mode=0o775, parents=True) - download_path = os.path.join(download_cache, file_name) + download_path = download_cache / file_name - if os.path.isfile(download_path): + if download_path.is_file(): return download_path - for _ in range(0, 3): - resp, info = fetch_url(module, - plugin_url, - method='GET', - timeout=20, - follow_redirects=True) - status_code = info['status'] - - if status_code >= 200 and status_code < 300: - tmp_dest = getattr(module, 'tmpdir', None) - - fd, b_tempname = tempfile.mkstemp(dir=tmp_dest) - - f = os.fdopen(fd, 'wb') - try: - shutil.copyfileobj(resp, f) - except Exception as e: - os.remove(b_tempname) + for _ in range(3): + module.params['follow_redirects'] = 'all' + resp, info = fetch_url(module, plugin_url, method='GET', timeout=20) + status_code = info.get('status', -1) + + if 200 <= status_code < 300: + tmp_dest = module.tmpdir + + fd, tempname = tempfile.mkstemp(dir=tmp_dest) + + with os.fdopen(fd, 'wb') as f: + try: + shutil.copyfileobj(resp, f) + except Exception as e: + os.remove(tempname) + if resp: + resp.close() + module.fail_json(msg=f'Failed to create temporary content file: {e}') + if resp: resp.close() - module.fail_json( - msg='Failed to create temporary content file: %s' % - to_native(e)) - f.close() - resp.close() - - module.atomic_move(to_native(b_tempname), download_path) - + module.atomic_move(tempname, str(download_path)) return download_path - if resp is not None: + if resp: resp.close() - module.fail_json(msg='Error downloading url "%s": %s' % - (plugin_url, info['msg'])) + module.fail_json(msg=f'Error downloading url "{plugin_url}": {info["msg"]}') -def install_plugin(module, plugin_manager_url, intellij_home, plugins_dir, uid, - gid, plugin_id, download_cache): - plugin_url, file_name = get_plugin_info(module, plugin_manager_url, - intellij_home, plugin_id) +def install_plugin( + module: AnsibleModule, + plugin_manager_url: str, + intellij_home: Path, + plugins_dir: Path, + uid: int, + gid: int, + plugin_id: str, + download_cache: Path) -> bool: + plugin_url, file_name = get_plugin_info(module, plugin_manager_url, intellij_home, plugin_id) - plugin_path = download_plugin(module, plugin_url, file_name, - download_cache) + plugin_path = download_plugin(module, plugin_url, file_name, download_cache) if not module.check_mode: - make_dirs(module, plugins_dir, 0o775, uid, gid) - - if plugin_path.endswith('.jar'): - dest_path = os.path.join(plugins_dir, os.path.basename(plugin_path)) + make_dirs(plugins_dir, 0o775, uid, gid) - if os.path.exists(dest_path): + if plugin_path.suffix == '.jar': + dest_path = plugins_dir / plugin_path.name + if dest_path.exists(): return False if not module.check_mode: shutil.copy(plugin_path, dest_path) os.chown(dest_path, uid, gid) - os.chmod(dest_path, 0o664) + dest_path.chmod(0o664) return True else: root_dirname = get_root_dirname_from_zip(module, plugin_path) - plugin_dir = os.path.join(plugins_dir, root_dirname) + plugin_dir = plugins_dir / root_dirname - if os.path.exists(plugin_dir): + if plugin_dir.exists(): return False if not module.check_mode: @@ -451,21 +308,22 @@ def install_plugin(module, plugin_manager_url, intellij_home, plugins_dir, uid, return True -def run_module(): +def run_module() -> None: - module_args = dict(plugin_manager_url=dict(type='str', required=True), - intellij_home=dict(type='path', required=True), - intellij_user_plugins_dir=dict(type='path', - required=True), - owner=dict(type='str', required=True), - group=dict(type='str', required=True), - plugin_id=dict(type='str', required=True), - download_cache=dict(type='path', required=True)) + module_args = dict( + plugin_manager_url=dict(type='str', required=True), + intellij_home=dict(type='path', required=True), + intellij_user_plugins_dir=dict(type='path', required=True), + owner=dict(type='str', required=True), + group=dict(type='str', required=True), + plugin_id=dict(type='str', required=True), + download_cache=dict(type='path', required=True) + ) module = AnsibleModule(argument_spec=module_args, supports_check_mode=True) plugin_manager_url = module.params['plugin_manager_url'] - intellij_home = os.path.expanduser(module.params['intellij_home']) + intellij_home = Path(os.path.expanduser(module.params['intellij_home'])) owner = module.params['owner'] group = module.params['group'] @@ -480,42 +338,31 @@ def run_module(): except ValueError: gid = grp.getgrnam(group).gr_gid - intellij_user_plugins_dir = os.path.expanduser( - os.path.join('~' + username, - module.params['intellij_user_plugins_dir'])) + intellij_user_plugins_dir = (Path('~' + username) / module.params['intellij_user_plugins_dir']).expanduser() plugin_id = module.params['plugin_id'] - download_cache = os.path.expanduser(module.params['download_cache']) + download_cache = Path(module.params['download_cache']).expanduser() # Check if we have lxml 2.3.0 or newer installed if not HAS_LXML: - module.fail_json( - msg='The xml ansible module requires the lxml python library ' - 'installed on the managed machine') - elif LooseVersion('.'.join( - to_native(f) for f in etree.LXML_VERSION)) < LooseVersion('2.3.0'): - module.fail_json( - msg='The xml ansible module requires lxml 2.3.0 or newer ' - 'installed on the managed machine') - elif LooseVersion('.'.join( - to_native(f) for f in etree.LXML_VERSION)) < LooseVersion('3.0.0'): - module.warn( - 'Using lxml version lower than 3.0.0 does not guarantee ' - 'predictable element attribute order.' - ) - - changed = install_plugin(module, plugin_manager_url, intellij_home, - intellij_user_plugins_dir, uid, gid, plugin_id, - download_cache) + module.fail_json(msg='The xml ansible module requires the lxml python library installed on the managed machine') + else: + lxml_version = LooseVersion('.'.join(str(f) for f in etree.LXML_VERSION)) + if lxml_version < LooseVersion('2.3.0'): + module.fail_json(msg='The xml ansible module requires lxml 2.3.0 or newer installed on the managed machine') + elif lxml_version < LooseVersion('3.0.0'): + module.warn('Using lxml version lower than 3.0.0 does not guarantee predictable element attribute order.') + + changed = install_plugin(module, plugin_manager_url, intellij_home, intellij_user_plugins_dir, uid, gid, plugin_id, download_cache) if changed: - msg = 'Plugin %s has been installed' % username + msg = f'Plugin "{plugin_id}" has been installed' else: - msg = 'Plugin %s was already installed' % username + msg = f'Plugin "{plugin_id}" was already installed' module.exit_json(changed=changed, msg=msg) -def main(): +def main() -> None: run_module() diff --git a/library/intellij_set_default_inspection_profile.py b/library/intellij_set_default_inspection_profile.py index fbb3701..bb19497 100644 --- a/library/intellij_set_default_inspection_profile.py +++ b/library/intellij_set_default_inspection_profile.py @@ -1,20 +1,13 @@ -#!/usr/bin/python +#!/usr/bin/env python3 -# Make coding more python3-ish -from __future__ import (absolute_import, division, print_function) -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils._text import to_bytes, to_native -from distutils.version import LooseVersion -import pwd import grp import os -__metaclass__ = type +import pwd +from pathlib import Path +from typing import Dict, Tuple -ANSIBLE_METADATA = { - 'metadata_version': '1.1', - 'status': ['preview'], - 'supported_by': 'community' -} +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.compat.version import LooseVersion DOCUMENTATION = ''' --- @@ -70,124 +63,122 @@ HAS_LXML = False -def pretty_print(elem): - text = etree.tostring(elem, encoding='iso-8859-1') +def pretty_print(elem: etree.Element) -> str: + text = etree.tostring(elem, encoding='unicode') parser = etree.XMLParser(remove_blank_text=True) xml = etree.fromstring(text, parser) - return etree.tostring(xml, - encoding='iso-8859-1', - pretty_print=True, - xml_declaration=False) + return etree.tostring(xml, encoding='unicode', pretty_print=True, xml_declaration=False) -def set_option(elem, key, value): - option = elem.find('./option[@name="%s"]' % key) +def set_option(elem: etree.Element, key: str, value: str) -> bool: + option = elem.find(f'./option[@name="{key}"]') if option is None: option = etree.SubElement(elem, 'option', name=key) - if option.attrib.get('value', None) == value: + if option.attrib.get('value') == value: return False option.set('value', value) return True -def set_version(elem, value): +def set_version(elem: etree.Element, value: str) -> bool: version = elem.find('./version') if version is None: version = etree.SubElement(elem, 'version', value=value) - if version.attrib.get('value', None) == value: + if version.attrib.get('value') == value: return False version.set('value', value) return True -def make_dirs(path, mode, uid, gid): - dirs = [path] - dirname = os.path.dirname(path) - while dirname != '/': - dirs.insert(0, dirname) - dirname = os.path.dirname(dirname) +def make_dirs(path: Path, mode: int, uid: int, gid: int) -> None: + dirs_to_create = [] - for dirname in dirs: - if not os.path.exists(dirname): - os.mkdir(dirname, mode) - os.chown(dirname, uid, gid) + while not path.exists(): + dirs_to_create.append(path) + if path.parent == path: + # Reached the root directory + break + path = path.parent + dirs_to_create.reverse() -def set_default_inspection_profile(module, intellij_user_config_dir, - profile_name, uid, gid): - options_dir = os.path.join(intellij_user_config_dir, 'options') + for dir_path in dirs_to_create: + if not dir_path.exists(): + dir_path.mkdir(mode=mode, exist_ok=True) + os.chown(str(dir_path), uid, gid) - project_default_path = os.path.join(options_dir, 'project.default.xml') - create_project_default = (not os.path.isfile(project_default_path) - ) or os.path.getsize(project_default_path) == 0 +def set_default_inspection_profile( + module: AnsibleModule, + intellij_user_config_dir: Path, + profile_name: str, + uid: int, + gid: int +) -> Tuple[bool, Dict[str, str]]: + options_dir = intellij_user_config_dir / 'options' + project_default_path = options_dir / 'project.default.xml' + + create_project_default = ( + not project_default_path.is_file() + or project_default_path.stat().st_size == 0 + ) if create_project_default: if not module.check_mode: - if not os.path.isdir(options_dir): + if not options_dir.is_dir(): make_dirs(options_dir, 0o775, uid, gid) - if not os.path.isfile(project_default_path): - with open(project_default_path, 'wb', 0o664) as xml_file: - xml_file.write('') + if not project_default_path.is_file(): + project_default_path.touch() os.chown(project_default_path, uid, gid) + project_default_path.chmod(0o664) project_default_root = etree.Element('application') - project_default_doc = etree.ElementTree(project_default_root) before = '' else: - project_default_doc = etree.parse(project_default_path) - project_default_root = project_default_doc.getroot() + project_default_root = etree.parse(str(project_default_path)).getroot() before = pretty_print(project_default_root) if project_default_root.tag != 'application': - module.fail_json(msg='Unsupported root element: %s' % - project_default_root.tag) + module.fail_json(msg=f'Unsupported root element: {project_default_root.tag}') - project_manager = project_default_root.find( - './component[@name="ProjectManager"]') + project_manager = project_default_root.find('./component[@name="ProjectManager"]') if project_manager is None: - project_manager = etree.SubElement(project_default_root, - 'component', - name='ProjectManager') + project_manager = etree.SubElement(project_default_root, 'component', name='ProjectManager') default_project = project_manager.find('./defaultProject') if default_project is None: default_project = etree.SubElement(project_manager, 'defaultProject') - profile_manager = default_project.find( - './component[@name="InspectionProjectProfileManager"]') + profile_manager = default_project.find('./component[@name="InspectionProjectProfileManager"]') if profile_manager is None: - profile_manager = etree.SubElement( - default_project, - 'component', - name='InspectionProjectProfileManager') + profile_manager = etree.SubElement(default_project, 'component', name='InspectionProjectProfileManager') - changed = True in [ + changed = any([ set_option(profile_manager, 'PROJECT_PROFILE', profile_name), set_option(profile_manager, 'USE_PROJECT_PROFILE', 'false'), set_version(profile_manager, '1.0') - ] + ]) after = pretty_print(project_default_root) if changed and not module.check_mode: - with open(project_default_path, 'wb') as xml_file: - xml_file.write(to_bytes(after)) + project_default_path.write_text(after, encoding='iso-8859-1') - return changed, {'before:': before, 'after': after} + return changed, {'before': before, 'after': after} -def run_module(): +def run_module() -> None: - module_args = dict(intellij_user_config_dir=dict(type='str', - required=True), - profile_name=dict(type='str', required=True), - owner=dict(type='str', required=True), - group=dict(type='str', required=True)) + module_args = dict( + intellij_user_config_dir=dict(type='str', required=True), + profile_name=dict(type='str', required=True), + owner=dict(type='str', required=True), + group=dict(type='str', required=True) + ) module = AnsibleModule(argument_spec=module_args, supports_check_mode=True) @@ -205,41 +196,30 @@ def run_module(): except ValueError: gid = grp.getgrnam(group).gr_gid - intellij_user_config_dir = os.path.expanduser( - os.path.join('~' + username, - module.params['intellij_user_config_dir'])) - profile_name = os.path.expanduser(module.params['profile_name']) + intellij_user_config_dir = Path(f'~{username}', module.params['intellij_user_config_dir']).expanduser() + profile_name = module.params['profile_name'] # Check if we have lxml 2.3.0 or newer installed if not HAS_LXML: - module.fail_json( - msg='The xml ansible module requires the lxml python library ' - 'installed on the managed machine') - elif LooseVersion('.'.join( - to_native(f) for f in etree.LXML_VERSION)) < LooseVersion('2.3.0'): - module.fail_json( - msg='The xml ansible module requires lxml 2.3.0 or newer ' - 'installed on the managed machine') - elif LooseVersion('.'.join( - to_native(f) for f in etree.LXML_VERSION)) < LooseVersion('3.0.0'): - module.warn( - 'Using lxml version lower than 3.0.0 does not guarantee ' - 'predictable element attribute order.' - ) - - changed, diff = set_default_inspection_profile(module, - intellij_user_config_dir, - profile_name, uid, gid) + module.fail_json(msg='The xml ansible module requires the lxml python library installed on the managed machine') + else: + lxml_version = LooseVersion('.'.join(str(f) for f in etree.LXML_VERSION)) + if lxml_version < LooseVersion('2.3.0'): + module.fail_json(msg='The xml ansible module requires lxml 2.3.0 or newer installed on the managed machine') + elif lxml_version < LooseVersion('3.0.0'): + module.warn('Using lxml version lower than 3.0.0 does not guarantee predictable element attribute order.') + + changed, diff = set_default_inspection_profile(module, intellij_user_config_dir, profile_name, uid, gid) if changed: - msg = '%s is now the default inspection profile' % profile_name + msg = f'{profile_name} is now the default inspection profile' else: - msg = '%s is already the default inspection profile' % profile_name + msg = f'{profile_name} is already the default inspection profile' module.exit_json(changed=changed, msg=msg, diff=diff) -def main(): +def main() -> None: run_module() diff --git a/library/intellij_set_default_jdk.py b/library/intellij_set_default_jdk.py index ab32f9d..9a754ed 100644 --- a/library/intellij_set_default_jdk.py +++ b/library/intellij_set_default_jdk.py @@ -1,22 +1,14 @@ -#!/usr/bin/python +#!/usr/bin/env python3 -# Make coding more python3-ish -from __future__ import (absolute_import, division, print_function) -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils._text import to_bytes, to_native -from distutils.version import LooseVersion -import tempfile -import shutil -import pwd import grp import os -__metaclass__ = type +import pwd +import tempfile +from pathlib import Path +from typing import Any, Dict, Tuple -ANSIBLE_METADATA = { - 'metadata_version': '1.1', - 'status': ['preview'], - 'supported_by': 'community' -} +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.compat.version import LooseVersion DOCUMENTATION = ''' --- @@ -69,184 +61,157 @@ HAS_LXML = False -def pretty_print(elem): - text = etree.tostring(elem, encoding='iso-8859-1') +def pretty_print(elem: etree.Element) -> str: + text = etree.tostring(elem, encoding='unicode') parser = etree.XMLParser(remove_blank_text=True) xml = etree.fromstring(text, parser) - return etree.tostring(xml, - encoding='iso-8859-1', - pretty_print=True, - xml_declaration=False) + return etree.tostring(xml, encoding='unicode', pretty_print=True, xml_declaration=False) -def set_attrib(elem, key, value): - if elem.attrib.get(key, None) == value: +def set_attrib(elem: etree.Element, key: str, value: str) -> bool: + if elem.attrib.get(key) == value: return False elem.set(key, value) return True -def jdk_home(module, intellij_user_config_dir, jdk_name): - jdk_table_path = os.path.join(intellij_user_config_dir, 'options', - 'jdk.table.xml') - if not os.path.isfile(jdk_table_path): - module.fail_json(msg='File not found: %s' % jdk_table_path) +def jdk_home(module: AnsibleModule, intellij_user_config_dir: Path, jdk_name: str) -> Path: + jdk_table_path = intellij_user_config_dir / 'options' / 'jdk.table.xml' + if not jdk_table_path.is_file(): + module.fail_json(msg=f'File not found: {jdk_table_path}') - jdk_table_doc = etree.parse(jdk_table_path) - - jdk = jdk_table_doc.find( - './component[@name="ProjectJdkTable"]/jdk/name[@value="%s"]/..' % - jdk_name) + jdk_table_doc = etree.parse(str(jdk_table_path)) + jdk = jdk_table_doc.find(f'./component[@name="ProjectJdkTable"]/jdk/name[@value="{jdk_name}"]/..') if jdk is None: - module.fail_json( - msg='Unable to find JDK with name "%s" in jdk.table.xml' % - jdk_name) + module.fail_json(msg=f'Unable to find JDK with name "{jdk_name}" in jdk.table.xml') path_node = jdk.find('./homePath') if path_node is None: - module.fail_json(msg='Invalid XML: homePath missing for JDK: %s' % - jdk_name) + module.fail_json(msg=f'Invalid XML: homePath missing for JDK: {jdk_name}') - path = path_node.attrib.get('value', None) + path = path_node.attrib.get('value') if path is None: - module.fail_json( - msg='Invalid XML: homePath/@value missing for JDK: %s' % jdk_name) + module.fail_json(msg=f'Invalid XML: homePath/@value missing for JDK: {jdk_name}') - return path + return Path(path) -def specification_version(module, jdk_home): +def specification_version(module: AnsibleModule, jdk_home: Path) -> str: + javac = jdk_home / 'bin' / 'javac' + if not javac.is_file(): + module.fail_json(msg=f'File not found: {javac}') - dirpath = tempfile.mkdtemp() - try: - src_file = os.path.join(dirpath, 'SpecificationVersion.java') - with open(src_file, 'w') as java_file: - java_file.write(''' + java = jdk_home / 'bin' / 'java' + if not java.is_file(): + module.fail_json(msg=f'File not found: {java}') + + with tempfile.TemporaryDirectory() as dirpath: + dirpath = Path(dirpath) + src_file = dirpath / 'SpecificationVersion.java' + src_file.write_text(''' public class SpecificationVersion { public static void main(String[] args) { System.out.print(System.getProperty("java.specification.version")); } } ''') - - javac = os.path.join(jdk_home, 'bin', 'javac') - if not os.path.isfile(javac): - module.fail_json(msg='File not found: %s' % javac) - - rc, out, err = module.run_command([javac, 'SpecificationVersion.java'], - cwd=dirpath) + rc, out, err = module.run_command([str(javac), 'SpecificationVersion.java'], cwd=str(dirpath)) if rc != 0 or err: - module.fail_json( - msg='Error while querying Java specification version: %s' % - (out + err)) + module.fail_json(msg=f'Error while querying Java specification version: {out}{err}') - java = os.path.join(jdk_home, 'bin', 'java') - if not os.path.isfile(java): - module.fail_json(msg='File not found: %s' % java) - - rc, out, err = module.run_command([java, 'SpecificationVersion'], - cwd=dirpath) + rc, out, err = module.run_command([str(java), 'SpecificationVersion'], cwd=str(dirpath)) if rc != 0 or err: - module.fail_json( - msg='Error while querying Java specification version: %s' % - (out + err)) + module.fail_json(msg=f'Error while querying Java specification version: {out}{err}') return out.strip() - finally: - shutil.rmtree(dirpath) -def make_dirs(path, mode, uid, gid): - dirs = [path] - dirname = os.path.dirname(path) - while dirname != '/': - dirs.insert(0, dirname) - dirname = os.path.dirname(dirname) +def make_dirs(path: Path, mode: int, uid: int, gid: int) -> None: + dirs_to_create = [] + + while not path.exists(): + dirs_to_create.append(path) + if path.parent == path: + # Reached the root directory + break + path = path.parent - for dirname in dirs: - if not os.path.exists(dirname): - os.mkdir(dirname, mode) - os.chown(dirname, uid, gid) + dirs_to_create.reverse() + for dir_path in dirs_to_create: + if not dir_path.exists(): + dir_path.mkdir(mode=mode, exist_ok=True) + os.chown(str(dir_path), uid, gid) -def set_default_jdk(module, intellij_user_config_dir, jdk_name, uid, gid): - options_dir = os.path.join(intellij_user_config_dir, 'options') - project_default_path = os.path.join(options_dir, 'project.default.xml') +def set_default_jdk(module: AnsibleModule, intellij_user_config_dir: Path, jdk_name: str, uid: int, gid: int) -> Tuple[bool, Dict[str, Any]]: + options_dir = intellij_user_config_dir / 'options' + project_default_path = options_dir / 'project.default.xml' + + create_project_default = not project_default_path.is_file() or project_default_path.stat().st_size == 0 - create_project_default = (not os.path.isfile(project_default_path) - ) or os.path.getsize(project_default_path) == 0 if create_project_default: if not module.check_mode: - if not os.path.isdir(options_dir): + if not options_dir.is_dir(): make_dirs(options_dir, 0o775, uid, gid) - if not os.path.isfile(project_default_path): - with open(project_default_path, 'wb', 0o664) as xml_file: - xml_file.write(to_bytes('')) - os.chown(project_default_path, uid, gid) + if not project_default_path.is_file(): + project_default_path.touch() + project_default_path.chmod(project_default_path, 0o664) + os.chown(str(project_default_path), uid, gid) project_default_root = etree.Element('application') - project_default_doc = etree.ElementTree(project_default_root) before = '' else: - project_default_doc = etree.parse(project_default_path) + project_default_doc = etree.parse(str(project_default_path)) project_default_root = project_default_doc.getroot() before = pretty_print(project_default_root) if project_default_root.tag != 'application': - module.fail_json(msg='Unsupported root element: %s' % - project_default_root.tag) + module.fail_json(msg=f'Unsupported root element: {project_default_root.tag}') - project_manager = project_default_root.find( - './component[@name="ProjectManager"]') + project_manager = project_default_root.find('./component[@name="ProjectManager"]') if project_manager is None: - project_manager = etree.SubElement(project_default_root, - 'component', - name='ProjectManager') + project_manager = etree.SubElement(project_default_root, 'component', name='ProjectManager') default_project = project_manager.find('./defaultProject') if default_project is None: default_project = etree.SubElement(project_manager, 'defaultProject') - project_root_manager = default_project.find( - './component[@name="ProjectRootManager"]') + project_root_manager = default_project.find('./component[@name="ProjectRootManager"]') if project_root_manager is None: - project_root_manager = etree.SubElement(default_project, - 'component', - name='ProjectRootManager') + project_root_manager = etree.SubElement(default_project, 'component', name='ProjectRootManager') default_jdk_home = jdk_home(module, intellij_user_config_dir, jdk_name) language_level = specification_version(module, default_jdk_home) - changed = True in [ + changed = any([ set_attrib(project_root_manager, 'version', '2'), - set_attrib(project_root_manager, 'languageLevel', - 'JDK_%s' % language_level.replace('.', '_')), + set_attrib(project_root_manager, 'languageLevel', f'JDK_{language_level.replace(".", "_")}'), set_attrib(project_root_manager, 'default', 'true'), set_attrib(project_root_manager, 'assert-keyword', 'true'), set_attrib(project_root_manager, 'jdk-15', 'true'), set_attrib(project_root_manager, 'project-jdk-name', jdk_name), set_attrib(project_root_manager, 'project-jdk-type', 'JavaSDK') - ] + ]) after = pretty_print(project_default_root) if changed and not module.check_mode: - with open(project_default_path, 'wb') as xml_file: - xml_file.write(to_bytes(after)) - - return changed, {'before:': before, 'after': after} + project_default_path.write_text(after, encoding='iso-8859-1') + os.chown(str(project_default_path), uid, gid) + return changed, {'before': before, 'after': after} -def run_module(): - module_args = dict(intellij_user_config_dir=dict(type='str', - required=True), - jdk_name=dict(type='str', required=True), - owner=dict(type='str', required=True), - group=dict(type='str', required=True)) +def run_module() -> None: + module_args = dict( + intellij_user_config_dir=dict(type='str', required=True), + jdk_name=dict(type='str', required=True), + owner=dict(type='str', required=True), + group=dict(type='str', required=True) + ) module = AnsibleModule(argument_spec=module_args, supports_check_mode=True) @@ -264,40 +229,30 @@ def run_module(): except ValueError: gid = grp.getgrnam(group).gr_gid - intellij_user_config_dir = os.path.expanduser( - os.path.join('~' + username, - module.params['intellij_user_config_dir'])) - jdk_name = os.path.expanduser(module.params['jdk_name']) + intellij_user_config_dir = Path(f'~{username}', module.params['intellij_user_config_dir']).expanduser() + jdk_name = module.params['jdk_name'] # Check if we have lxml 2.3.0 or newer installed if not HAS_LXML: - module.fail_json( - msg='The xml ansible module requires the lxml python library ' - 'installed on the managed machine') - elif LooseVersion('.'.join( - to_native(f) for f in etree.LXML_VERSION)) < LooseVersion('2.3.0'): - module.fail_json( - msg='The xml ansible module requires lxml 2.3.0 or newer ' - 'installed on the managed machine') - elif LooseVersion('.'.join( - to_native(f) for f in etree.LXML_VERSION)) < LooseVersion('3.0.0'): - module.warn( - 'Using lxml version lower than 3.0.0 does not guarantee ' - 'predictable element attribute order.' - ) - - changed, diff = set_default_jdk(module, intellij_user_config_dir, jdk_name, - uid, gid) + module.fail_json(msg='The xml ansible module requires the lxml python library installed on the managed machine') + else: + lxml_version = LooseVersion('.'.join(str(f) for f in etree.LXML_VERSION)) + if lxml_version < LooseVersion('2.3.0'): + module.fail_json(msg='The xml ansible module requires lxml 2.3.0 or newer installed on the managed machine') + elif lxml_version < LooseVersion('3.0.0'): + module.warn('Using lxml version lower than 3.0.0 does not guarantee predictable element attribute order.') + + changed, diff = set_default_jdk(module, intellij_user_config_dir, jdk_name, uid, gid) if changed: - msg = '%s is now the default JDK' % jdk_name + msg = f'{jdk_name} is now the default JDK' else: - msg = '%s is already the default JDK' % jdk_name + msg = f'{jdk_name} is already the default JDK' module.exit_json(changed=changed, msg=msg, diff=diff) -def main(): +def main() -> None: run_module() diff --git a/library/intellij_set_default_maven.py b/library/intellij_set_default_maven.py index 2ac43e8..6751a7f 100644 --- a/library/intellij_set_default_maven.py +++ b/library/intellij_set_default_maven.py @@ -1,20 +1,13 @@ -#!/usr/bin/python +#!/usr/bin/env python3 -# Make coding more python3-ish -from __future__ import (absolute_import, division, print_function) -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils._text import to_bytes, to_native -from distutils.version import LooseVersion -import pwd import grp import os -__metaclass__ = type +import pwd +from pathlib import Path +from typing import Dict, Tuple -ANSIBLE_METADATA = { - 'metadata_version': '1.1', - 'status': ['preview'], - 'supported_by': 'community' -} +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.compat.version import LooseVersion DOCUMENTATION = ''' --- @@ -68,17 +61,14 @@ HAS_LXML = False -def pretty_print(elem): - text = etree.tostring(elem, encoding='iso-8859-1') +def pretty_print(elem: etree.Element) -> str: + text = etree.tostring(elem, encoding='unicode') parser = etree.XMLParser(remove_blank_text=True) xml = etree.fromstring(text, parser) - return etree.tostring(xml, - encoding='iso-8859-1', - pretty_print=True, - xml_declaration=False) + return etree.tostring(xml, encoding='unicode', pretty_print=True, xml_declaration=False) -def set_attrib(elem, key, value): +def set_attrib(elem: etree.Element, key: str, value: str) -> bool: if elem.attrib.get(key, None) == value: return False @@ -86,103 +76,93 @@ def set_attrib(elem, key, value): return True -def make_dirs(path, mode, uid, gid): - dirs = [path] - dirname = os.path.dirname(path) - while dirname != '/': - dirs.insert(0, dirname) - dirname = os.path.dirname(dirname) +def make_dirs(path: Path, mode: int, uid: int, gid: int) -> None: + dirs_to_create = [] + + while not path.exists(): + dirs_to_create.append(path) + if path.parent == path: + # Reached the root directory + break + path = path.parent - for dirname in dirs: - if not os.path.exists(dirname): - os.mkdir(dirname, mode) - os.chown(dirname, uid, gid) + dirs_to_create.reverse() + for dir_path in dirs_to_create: + if not dir_path.exists(): + dir_path.mkdir(mode=mode, exist_ok=True) + os.chown(str(dir_path), uid, gid) -def set_default_maven(module, intellij_user_config_dir, maven_home, uid, gid): - options_dir = os.path.join(intellij_user_config_dir, 'options') - project_default_path = os.path.join(options_dir, 'project.default.xml') +def set_default_maven(module: AnsibleModule, intellij_user_config_dir: Path, maven_home: Path, uid: int, gid: int) -> Tuple[bool, Dict[str, str]]: + + options_dir = intellij_user_config_dir / 'options' + project_default_path = options_dir / 'project.default.xml' + + create_project_default = (not project_default_path.is_file()) or project_default_path.stat().st_size == 0 - create_project_default = (not os.path.isfile(project_default_path) - ) or os.path.getsize(project_default_path) == 0 if create_project_default: if not module.check_mode: - if not os.path.isdir(options_dir): + if not options_dir.is_dir(): make_dirs(options_dir, 0o775, uid, gid) - if not os.path.isfile(project_default_path): - with open(project_default_path, 'wb', 0o664) as xml_file: - xml_file.write(to_bytes('')) - os.chown(project_default_path, uid, gid) + if not project_default_path.is_file(): + project_default_path.touch(mode=0o664) + os.chown(str(project_default_path), uid, gid) project_default_root = etree.Element('application') project_default_doc = etree.ElementTree(project_default_root) before = '' else: - project_default_doc = etree.parse(project_default_path) + project_default_doc = etree.parse(str(project_default_path)) project_default_root = project_default_doc.getroot() before = pretty_print(project_default_root) if project_default_root.tag != 'application': - module.fail_json(msg='Unsupported root element: %s' % - project_default_root.tag) + module.fail_json(msg=f'Unsupported root element: {project_default_root.tag}') - project_manager = project_default_root.find( - './component[@name="ProjectManager"]') + project_manager = project_default_root.find('./component[@name="ProjectManager"]') if project_manager is None: - project_manager = etree.SubElement(project_default_root, - 'component', - name='ProjectManager') + project_manager = etree.SubElement(project_default_root, 'component', name='ProjectManager') default_project = project_manager.find('./defaultProject') if default_project is None: default_project = etree.SubElement(project_manager, 'defaultProject') - mvn_import_prefs = default_project.find( - './component[@name="MavenImportPreferences"]') + mvn_import_prefs = default_project.find('./component[@name="MavenImportPreferences"]') if mvn_import_prefs is None: - mvn_import_prefs = etree.SubElement(default_project, - 'component', - name='MavenImportPreferences') + mvn_import_prefs = etree.SubElement(default_project, 'component', name='MavenImportPreferences') - general_settings = mvn_import_prefs.find( - './option[@name="generalSettings"]') + general_settings = mvn_import_prefs.find('./option[@name="generalSettings"]') if general_settings is None: - general_settings = etree.SubElement(mvn_import_prefs, - 'option', - name='generalSettings') + general_settings = etree.SubElement(mvn_import_prefs, 'option', name='generalSettings') mvn_general_settings = general_settings.find('./MavenGeneralSettings') if mvn_general_settings is None: - mvn_general_settings = etree.SubElement(general_settings, - 'MavenGeneralSettings') + mvn_general_settings = etree.SubElement(general_settings, 'MavenGeneralSettings') mvn_home_option = mvn_general_settings.find('./option[@name="mavenHome"]') if mvn_home_option is None: - mvn_home_option = etree.SubElement(mvn_general_settings, - 'option', - name='mavenHome') + mvn_home_option = etree.SubElement(mvn_general_settings, 'option', name='mavenHome') - changed = set_attrib(mvn_home_option, 'value', - os.path.expanduser(maven_home)) + changed = set_attrib(mvn_home_option, 'value', str(maven_home.expanduser())) after = pretty_print(project_default_root) if changed and not module.check_mode: - with open(project_default_path, 'wb') as xml_file: - xml_file.write(to_bytes(after)) + project_default_path.write_text(after, encoding='iso-8859-1') - return changed, {'before:': before, 'after': after} + return changed, {'before': before, 'after': after} -def run_module(): +def run_module() -> None: - module_args = dict(intellij_user_config_dir=dict(type='str', - required=True), - maven_home=dict(type='str', required=True), - owner=dict(type='str', required=True), - group=dict(type='str', required=True)) + module_args = dict( + intellij_user_config_dir=dict(type='str', required=True), + maven_home=dict(type='str', required=True), + owner=dict(type='str', required=True), + group=dict(type='str', required=True) + ) module = AnsibleModule(argument_spec=module_args, supports_check_mode=True) @@ -200,30 +180,20 @@ def run_module(): except ValueError: gid = grp.getgrnam(group).gr_gid - intellij_user_config_dir = os.path.expanduser( - os.path.join('~' + username, - module.params['intellij_user_config_dir'])) - maven_home = os.path.expanduser(module.params['maven_home']) + intellij_user_config_dir = Path('~' + username).expanduser() / module.params['intellij_user_config_dir'] + maven_home = Path(module.params['maven_home']).expanduser() # Check if we have lxml 2.3.0 or newer installed if not HAS_LXML: - module.fail_json( - msg='The xml ansible module requires the lxml python library ' - 'installed on the managed machine') - elif LooseVersion('.'.join( - to_native(f) for f in etree.LXML_VERSION)) < LooseVersion('2.3.0'): - module.fail_json( - msg='The xml ansible module requires lxml 2.3.0 or newer installed' - ' on the managed machine') - elif LooseVersion('.'.join( - to_native(f) for f in etree.LXML_VERSION)) < LooseVersion('3.0.0'): - module.warn( - 'Using lxml version lower than 3.0.0 does not guarantee ' - 'predictable element attribute order.' - ) - - changed, diff = set_default_maven(module, intellij_user_config_dir, - maven_home, uid, gid) + module.fail_json(msg='The xml ansible module requires the lxml python library installed on the managed machine') + else: + lxml_version = LooseVersion('.'.join(str(f) for f in etree.LXML_VERSION)) + if lxml_version < LooseVersion('2.3.0'): + module.fail_json(msg='The xml ansible module requires lxml 2.3.0 or newer installed on the managed machine') + elif lxml_version < LooseVersion('3.0.0'): + module.warn('Using lxml version lower than 3.0.0 does not guarantee predictable element attribute order.') + + changed, diff = set_default_maven(module, intellij_user_config_dir, maven_home, uid, gid) if changed: msg = '%s is now the default Maven installation' % maven_home @@ -233,7 +203,7 @@ def run_module(): module.exit_json(changed=changed, msg=msg, diff=diff) -def main(): +def main() -> None: run_module() diff --git a/tasks/configure.yml b/tasks/configure.yml index ad94c00..bffac21 100644 --- a/tasks/configure.yml +++ b/tasks/configure.yml @@ -1,19 +1,11 @@ # code: language=ansible --- # Needed by custom Ansible modules -- name: Install Python XML support (Python 2) - become: true - ansible.builtin.package: - name: python-lxml - state: present - when: "intellij_python_major_version == '2'" - -- name: Install Python XML support (Python 3) +- name: Install Python XML support become: true ansible.builtin.package: name: "python3-lxml" state: present - when: "intellij_python_major_version == '3'" - name: Configure license ansible.builtin.import_tasks: configure-license.yml