From 5dad5fb4b3cddae1a5b82ed105b4ca7d6f07edf3 Mon Sep 17 00:00:00 2001 From: Vladimir Still <git@vstill.eu> Date: Sat, 5 Feb 2022 19:13:29 +0100 Subject: [PATCH] ansible: A new GHC-installer role --- ansible/library/deb_alternatives.py | 116 +++++++++++++++++++++ ansible/roles/ghc_udeb/tasks/install.yml | 125 +++++++++++++++++++++++ ansible/roles/ghc_udeb/tasks/main.yml | 19 ++++ 3 files changed, 260 insertions(+) create mode 100644 ansible/library/deb_alternatives.py create mode 100644 ansible/roles/ghc_udeb/tasks/install.yml create mode 100644 ansible/roles/ghc_udeb/tasks/main.yml diff --git a/ansible/library/deb_alternatives.py b/ansible/library/deb_alternatives.py new file mode 100644 index 0000000..21dafd4 --- /dev/null +++ b/ansible/library/deb_alternatives.py @@ -0,0 +1,116 @@ +#!/usr/bin/python + +# based on +# https://github.com/ansible-collections/community.general/blob/main/plugins/modules/system/alternatives.py + +import os +import re +import subprocess +from ansible.module_utils.basic import AnsibleModule + +def main(): + + fields = { + "name": {"required": True, "type": "str"}, + "path": {"required": True, "type": "path"}, + "link": {"type": "path"}, + "priority": {"type": "int", "default": 50}, + "state": {"default": "set", "choices": ["present", "set", "auto"], "type": "str"}, + "slaves": {"type": "list", "default": []} + } + module = AnsibleModule(argument_spec=fields, supports_check_mode=True) + + params = module.params + name = params['name'] + path = params['path'] + link = params['link'] + priority = params['priority'] + state = params["state"] + slaves = params['slaves'] + + UPDATE_ALTERNATIVES = module.get_bin_path('update-alternatives', True) + + current_path = None + all_alternatives = [] + + # Run `update-alternatives --display <name>` to find existing alternatives + (rc, display_output, dummy) = module.run_command( + ['env', 'LC_ALL=C', UPDATE_ALTERNATIVES, '--display', name] + ) + + if rc == 0: + # Alternatives already exist for this link group + # Parse the output to determine the current path of the symlink and + # available alternatives + current_path_regex = re.compile(r'^\s*link currently points to (.*)$', + re.MULTILINE) + alternative_regex = re.compile(r'^(\/.*)\s-\s(?:family\s\S+\s)?priority', re.MULTILINE) + + match = current_path_regex.search(display_output) + if match: + current_path = match.group(1) + all_alternatives = alternative_regex.findall(display_output) + + if not link: + # Read the current symlink target from `update-alternatives --query` + # in case we need to install the new alternative before setting it. + # + # This is only compatible on Debian-based systems, as the other + # alternatives don't have --query available + rc, query_output, dummy = module.run_command( + ['env', 'LC_ALL=C', UPDATE_ALTERNATIVES, '--query', name] + ) + if rc == 0: + for line in query_output.splitlines(): + if line.startswith('Link:'): + link = line.split()[1] + break + + if current_path != path or len(slaves): # TODO: properly check changed status in presence of slaves + if module.check_mode: + module.exit_json(changed=True, current_path=current_path, slaves=slaves) + try: + # install the requested path if necessary + if path not in all_alternatives or len(slaves): + if not os.path.exists(path): + module.fail_json(msg="Specified path %s does not exist" % path) + if not link: + module.fail_json(msg="Needed to install the alternative, but unable to do so as we are missing the link") + + cmd_slaves = [] + SLAVE_KEYS = ["link", "name", "path"] + for slave in slaves: + for key in SLAVE_KEYS: + if key not in slave: + module.fail_json(msg="Key %s missing from slave list, required keys are: %s" + % (key, ", ".join(SLAVE_KEYS))) + cmd_slaves.extend(["--slave", slave["link"], slave["name"], slave["path"]]) + + module.run_command( + [UPDATE_ALTERNATIVES, '--install', link, name, path, str(priority)] + cmd_slaves, + check_rc=True + ) + + if state == "set": + # select the requested path + module.run_command( + [UPDATE_ALTERNATIVES, '--set', name, path], + check_rc=True + ) + + elif state == "auto": + # set the path to auto mode + module.run_command( + [UPDATE_ALTERNATIVES, '--auto', name], + check_rc=True + ) + + module.exit_json(changed=True, slaves=slaves) + except subprocess.CalledProcessError as cpe: + module.fail_json(msg=str(dir(cpe))) + else: + module.exit_json(changed=False, slaves=slaves) + + +if __name__ == '__main__': + main() diff --git a/ansible/roles/ghc_udeb/tasks/install.yml b/ansible/roles/ghc_udeb/tasks/install.yml new file mode 100644 index 0000000..322011f --- /dev/null +++ b/ansible/roles/ghc_udeb/tasks/install.yml @@ -0,0 +1,125 @@ +--- +- name: Does GHC exist? + stat: + path: "/opt/ghc/{{version.ghc}}/bin/ghci-{{version.ghc}}" + register: ghc + +- name: Does Cabal exist? + stat: + path: "/opt/cabal/{{version.cabal}}/bin/cabal" + when: version.cabal is defined + register: cabal + +- name: GHC Dir + file: + state: directory + path: "/opt/ghc/{{version.ghc}}" + +- name: Cabal Dir + file: + state: directory + path: "/opt/cabal/{{version.cabal}}/bin" + when: version.cabal is defined + +- name: Build GHC & cabal if it does not exist + block: + - name: Build GHC under the builder user + block: + - name: "Download GHC" + unarchive: + src: 'https://downloads.haskell.org/~ghc/{{version.ghc}}/ghc-{{version.ghc}}-x86_64-deb9-linux.tar.xz' + dest: "/home/builder/ghc/" + remote_src: True + + - name: Build GHC + shell: + chdir: '/home/builder/ghc/ghc-{{version.ghc}}' + cmd: "./configure --prefix=/opt/ghc/{{version.ghc}}/" + become_user: builder + become: true + + - name: Install GHC + shell: + chdir: '/home/builder/ghc/ghc-{{version.ghc}}' + cmd: make install -j8 + + when: not ghc.stat.exists + +- name: Add cabal + unarchive: + src: "https://downloads.haskell.org/~cabal/cabal-install-{{version.cabal}}/cabal-install-{{version.cabal}}-x86_64-linux-deb10.tar.xz" + dest: "/opt/cabal/{{version.cabal}}/bin/" + remote_src: true + when: version.cabal is defined and not cabal.stat.exists + +- name: Setup opt-GHC alteranative + deb_alternatives: + name: "opt-ghc{{item}}" + link: "/opt/ghc/bin/ghc{{item}}" + path: "/opt/ghc/{{version.ghc}}/bin/ghc" + priority: "{{version_idx + 100}}" + state: present + slaves: + - link: "/opt/ghc/bin/ghci{{item}}" + name: "opt-ghci{{item}}" + path: "/opt/ghc/{{version.ghc}}/bin/ghci" + - link: "/opt/ghc/bin/runghc{{item}}" + name: "opt-runghc{{item}}" + path: "/opt/ghc/{{version.ghc}}/bin/runghc" + - link: "/opt/ghc/bin/ghc-pkg{{item}}" + name: "opt-ghc-pkg{{item}}" + path: "/opt/ghc/{{version.ghc}}/bin/ghc-pkg" + - link: "/opt/ghc/bin/haddock{{item}}" + name: "opt-haddock{{item}}" + path: "/opt/ghc/{{version.ghc}}/bin/haddock" + loop: + - "" + - "-{{version.ghc}}" + +- name: Setup GHC alternative + deb_alternatives: + name: "ghc{{item}}" + link: "/usr/bin/ghc{{item}}" + path: "/etc/alternatives/opt-ghc{{item}}" + priority: 100 + state: present + slaves: + - link: "/usr/bin/ghci{{item}}" + name: "ghci{{item}}" + path: "/etc/alternatives/opt-ghci{{item}}" + - link: "/usr/bin/runghc{{item}}" + name: "runghc{{item}}" + path: "/etc/alternatives/opt-runghc{{item}}" + - link: "/usr/bin/ghc-pkg{{item}}" + name: "ghc-pkg{{item}}" + path: "/etc/alternatives/opt-ghc-pkg{{item}}" + - link: "/usr/bin/haddock{{item}}" + name: "haddock{{item}}" + path: "/etc/alternatives/opt-haddock{{item}}" + loop: + - "" + - "-{{version.ghc}}" + +- name: Setup opt-cabal alternative + deb_alternatives: + name: "opt-cabal{{item}}" + link: "/opt/ghc/bin/cabal{{item}}" + path: "/opt/cabal/{{version.cabal}}/bin/cabal" + priority: "{{version_idx + 100}}" + state: present + loop: + - "" + - "-{{version.cabal}}" + when: version.cabal is defined + +- name: Setup cabal alternative + deb_alternatives: + name: "cabal{{item}}" + link: "/usr/bin/cabal{{item}}" + path: "/etc/alternatives/opt-cabal{{item}}" + priority: 100 + state: present + loop: + - "" + - "-{{version.cabal}}" + when: version.cabal is defined diff --git a/ansible/roles/ghc_udeb/tasks/main.yml b/ansible/roles/ghc_udeb/tasks/main.yml new file mode 100644 index 0000000..77eef4f --- /dev/null +++ b/ansible/roles/ghc_udeb/tasks/main.yml @@ -0,0 +1,19 @@ +--- +- name: Builder user + user: + name: builder + create_home: true + shell: /bin/bash + system: true + +- name: GHC source dir + file: + state: directory + owner: "builder" + path: "/home/builder/ghc" + +- include_tasks: install.yml + loop: "{{versions}}" + loop_control: + loop_var: version + index_var: version_idx -- GitLab