#+title: Programming Ansible #+author: Howard X. Abrams #+date: 2024-06-07 #+tags: emacs Configuring Ansible and YAML #+begin_src emacs-lisp :exports none ;;; ha-programming-ansible.el --- Configuring Ansible and YAML -*- lexical-binding: t; -*- ;; ;; © 2024 Howard X. Abrams ;; Licensed under a Creative Commons Attribution 4.0 International License. ;; See http://creativecommons.org/licenses/by/4.0/ ;; ;; Author: Howard X. Abrams ;; Maintainer: Howard X. Abrams ;; Created: June 7, 2024 ;; ;; While obvious, GNU Emacs does not include this file ;; ;; *NB:* Do not edit this file. Instead, edit the original literate file at: ;; /Users/howard.abrams/other/hamacs/ha-programming-ansible.org ;; And tangle the file to recreate this one. ;; ;;; Code: #+end_src My day job now involves a lot of Ansible, and I’ve been struggling to get the right balance here. Much of the conflict stems from whether to use [[file:ha-programming.org::*Tree Sitter][Tree Sitter]] for the YAML mode or not. * YAML Doing a lot of [[https://github.com/yoshiki/yaml-mode][YAML work]], but the =yaml-mode= project needs a new maintainer, so I might as well switch over to the T version. , so I’ve switch to [[https://github.com/zkry/yaml-pro][yaml-pro]] that is now based on Tree Sitter. Let’s make sure the Tree-Sitter version works: #+begin_src emacs-lisp (when (treesit-available-p) (use-package yaml-ts-mode :straight (:type built-in) :mode ((rx ".yamllint") (rx ".y" (optional "a") "ml" string-end)) :hook (yaml-ts-mode . (lambda () (mixed-pitch-mode -1))) :mode-hydra ((:foreign-keys run) ("Simple" (("l" ha-yaml-next-section "Next section") ("h" ha-yaml-prev-section "Previous")))))) #+end_src #+RESULTS: : major-mode-hydras/yaml-ts-mode/body Allow this mode in Org blocks: #+begin_src emacs-lisp :results silent (add-to-list 'org-babel-load-languages '(yaml-ts . t)) #+end_src And we hook #+begin_src emacs-lisp (use-package yaml-pro :straight (:host github :repo "zkry/yaml-pro") :after yaml-ts-mode :hook ((yaml-ts-mode . yaml-pro-ts-mode) (yaml-mode . yaml-pro-mode))) #+end_src Since I can never remember too many keybindings for particular nodes, we create a Hydra just for it. #+begin_src emacs-lisp (use-package major-mode-hydra :after yaml-pro :config (major-mode-hydra-define yaml-ts-mode (:foreign-keys run) ("Navigation" (("u" yaml-pro-ts-up-level "Up level" :color pink) ; C-c C-u ("J" yaml-pro-ts-next-subtree "Next subtree" :color pink) ; C-c C-n ("K" yaml-pro-ts-prev-subtree "Previous" :color pink)) ; C-c C-p "Editing" (("m" yaml-pro-ts-mark-subtree "Mark subtree") ; C-c C-@ ("x" yaml-pro-ts-kill-subtree "Kill subtree") ; C-c C-x C-w ("p" yaml-pro-ts-paste-subtree "Paste subtree")) ; C-c C-x C-y "Insert" (("e" yaml-pro-edit-ts-scalar "Edit item") ; C-c ' ("o" yaml-pro-ts-meta-return "New list item")) "Refactor" (("r" yaml-pro-ts-move-subtree-up "Raise subtree") ("t" yaml-pro-ts-move-subtree-down "Lower subtree") ("," combobulate-hydra/body ">>>")) "Documentation" (("d" hydra-devdocs/body "Devdocs"))))) #+end_src Note that these packages need the following to run properly: #+begin_src sh pip install yamllint #+end_src * Jinja2 A lot of projects (like Ansible and Zuul) uses [[https://jinja.palletsprojects.com][Jinja2]] with YAML, so we first install the [[https://github.com/paradoxxxzero/jinja2-mode][jinja2-mode]]: #+begin_src emacs-lisp (use-package jinja2-mode :mode (rx ".j2" string-end)) #+end_src Jinja is a /template/ system that integrates /inside/ formats like JSON, HTML or YAML. The [[https://polymode.github.io/][polymode]] project /glues/ modes like [[https://github.com/paradoxxxzero/jinja2-mode][jinja2-mode]] to [[https://github.com/yoshiki/yaml-mode][yaml-mode]]. I adapted this code from the [[https://github.com/emacsmirror/poly-ansible][poly-ansible]] project: #+begin_src emacs-lisp (use-package polymode :config (define-hostmode poly-yaml-hostmode :mode 'yaml-ts-mode) (defcustom pm-inner/jinja2 (pm-inner-chunkmode :mode #'jinja2-mode :head-matcher (rx "{" (or "%" "{" "#") (optional (or "+" "-"))) :tail-matcher (rx (optional (or "+" "-")) (or "%" "}" "#") "}") :head-mode 'body :tail-mode 'body :head-adjust-face t) "Jinja2 chunk." :group 'innermodes :type 'object) (define-polymode poly-yaml-jinja2-mode :hostmode 'poly-yaml-hostmode :innermodes '(pm-inner/jinja2)) (major-mode-hydra-define+ yaml-ts-mode nil ("Extensions" (("j" poly-yaml-jinja2-mode "Jinja2"))))) #+end_src We need to make sure the =mixed-pitch-mode= doesn’t screw things up. #+begin_src emacs-lisp (add-hook 'poly-yaml-jinja2-mode-hook (lambda () (mixed-pitch-mode -1))) ;; (add-hook 'yaml-ts-mode-hook 'poly-yaml-jinja2-mode) #+end_src #+begin_src yaml-ts --- # Let's see how this works - name: Busta move debug: msg: >- This {{ adjective }} {{ noun }} {{ verb }} the ball." {% for x in does %} What is this about? {% endfor %} vars: adjective: small noun: squirrel verb: ate #+end_src * Ansible Do I consider all YAML files an Ansible file needing [[https://github.com/k1LoW/emacs-ansible][ansible-mode]]? Maybe we just have a toggle for when we want the Ansible feature. #+begin_src emacs-lisp (use-package ansible :straight (:host github :repo "k1LoW/emacs-ansible") ;; :mode ((rx (or "playbooks" "roles") (one-or-more any) ".y" (optional "a") "ml") . ansible-mode) :config (setq ansible-vault-password-file "~/.ansible-vault-passfile") (major-mode-hydra-define+ yaml-ts-mode nil ("Extensions" (("a" ansible "Ansible")))) (ha-leader "t y" 'ansible)) #+end_src The [[help:ansible-vault-password-file][ansible-vault-password-file]] variable needs to change /per project/, so let’s use the =.dir-locals.el= file, for instance: #+begin_src emacs-lisp :tangle no ((nil . ((ansible-vault-password-file . "playbooks/.vault-password")))) #+end_src The YAML files get access Ansible’s documentation using the [[https://github.com/emacsorphanage/ansible-doc][ansible-doc]] project (that accesses the [[https://docs.ansible.com/ansible/latest/cli/ansible-doc.html][ansible-doc interface]]): #+begin_src emacs-lisp (use-package ansible-doc :hook (yaml-ts-mode . ansible-doc-mode) :config ;; (add-to-list 'exec-path (expand-file-name "~/.local/share/mise/installs/python/3.10/bin/ansible-doc")) (major-mode-hydra-define+ yaml-ts-mode nil ("Documentation" (("D" ansible-doc "Ansible"))))) #+end_src Can we integrate Ansible with LSP using [[https://github.com/ansible/ansible-language-server][ansible-language-server]] project (see [[https://emacs-lsp.github.io/lsp-mode/page/lsp-ansible/][this documentation]])? Using =npm= to install the program: #+begin_src sh npm install -g @ansible/ansible-language-server #+end_src But … will I get some use out of this? I’ll come back to it later. * Technical Artifacts :noexport: Let's provide a name so that the file can be required: #+begin_src emacs-lisp :exports none (provide 'ha-programming-ansible) ;;; ha-programming-ansible.el ends here #+end_src #+DESCRIPTION: Configuring Ansible and YAML #+PROPERTY: header-args:sh :tangle no #+PROPERTY: header-args:emacs-lisp :tangle yes #+PROPERTY: header-args :results none :eval no-export :comments no mkdirp yes #+OPTIONS: num:nil toc:nil todo:nil tasks:nil tags:nil date:nil #+OPTIONS: skip:nil author:nil email:nil creator:nil timestamp:nil #+INFOJS_OPT: view:nil toc:nil ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js # Local Variables: # eval: (add-hook 'after-save-hook #'org-babel-tangle t t) # End: