Skip to content

Latest commit

 

History

History
292 lines (247 loc) · 11.4 KB

ha-programming-ansible.org

File metadata and controls

292 lines (247 loc) · 11.4 KB

Programming Ansible

Configuring Ansible and YAML

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 Tree Sitter for the YAML mode or not.

Visual Indentation

Moving by lines is our default navigation mode, but for Yaml and Python, that doesn’t have s-expressions, we need something to move around by blocks. The spatial navigation project attempts to address this. The problem is how to bind the functions.

The obvious keybindings are M-h/j/k/l … but that is used … well, somewhat. In org files, this is a way to move the outline of subtrees around. Useful, and spatial-navigate wouldn’t be helpful in org files anyway.

(use-package spatial-navigate
  :straight (:repo "https://codeberg.org/ideasman42/emacs-spatial-navigate")
  :config
  (pretty-hydra-define spatial-navigate (:color amaranth :quit-key "q")
    ("Box"
     (("k" spatial-navigate-backward-vertical-box "up")
      ("j" spatial-navigate-forward-vertical-box "Down")
      ("h" spatial-navigate-backward-horizontal-box "Down")
      ("l" spatial-navigate-forward-horizontal-box "Down"))
     "Bar"
     (("K" spatial-navigate-backward-vertical-bar "up")
      ("J" spatial-navigate-forward-vertical-bar "Down")
      ("H" spatial-navigate-backward-horizontal-bar "Down")
      ("L" spatial-navigate-forward-horizontal-bar "Down")))))

And we can attach this menu to the “g” section:

(use-package spatial-navigate
  :general
  (:states '(normal visual motion operator)
       "g t" '("spatial nav" . spatial-navigate/body)))

The Highlight-Indentation project highlights each of the indent levels, which could be helpful for these modes:

(use-package highlight-indentation
  :straight (:host github :repo "antonj/Highlight-Indentation-for-Emacs")
  :hook ((yaml-mode . highlight-indentation-mode)
         (yaml-ts-mode . highlight-indentation-mode)
         (python-mode . highlight-indentation-mode)))

This project has another display feature, which just lines up the current level. So I’ve created a toggle for this:

(use-package highlight-indentation
  :config
  (setq highlight-indentation-blank-lines t)
  (set-face-background 'highlight-indentation-face "#332c26")
  (set-face-background 'highlight-indentation-current-column-face "#66615c")

  (defun ha-toggle-highlight-indentation ()
    "Toggles through the indentation modes."
    (interactive)
    (cond
     (highlight-indentation-mode
      (progn
        (highlight-indentation-mode -1)
        (highlight-indentation-current-column-mode 1)))
     (highlight-indentation-current-column-mode
      (progn
        (highlight-indentation-mode -1)
        (highlight-indentation-current-column-mode -1)))
     (t
      (progn
        (highlight-indentation-mode 1)
        (highlight-indentation-current-column-mode -1)))))

  (ha-leader "t i" '("show indents" . ha-toggle-highlight-indentation)))

YAML

Doing a lot of 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 yaml-pro that is now based on Tree Sitter. Let’s make sure the Tree-Sitter version works:

(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"))))))

Allow this mode in Org blocks:

(add-to-list 'org-babel-load-languages '(yaml-ts . t))

And we hook

(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)))

Since I can never remember too many keybindings for particular nodes, we create a Hydra just for it.

(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")))))

Note that these packages need the following to run properly:

pip install yamllint

Jinja2

A lot of projects (like Ansible and Zuul) uses Jinja2 with YAML, so we first install the jinja2-mode:

(use-package jinja2-mode
  :mode (rx ".j2" string-end))

Jinja is a template system that integrates inside formats like JSON, HTML or YAML. The polymode project glues modes like jinja2-mode to yaml-mode.

I adapted this code from the poly-ansible project:

(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")))))

We need to make sure the mixed-pitch-mode doesn’t screw things up.

(add-hook 'poly-yaml-jinja2-mode-hook (lambda () (mixed-pitch-mode -1)))

We can hook this up to Org, via:

(add-to-list 'org-babel-load-languages '(poly-yaml-jinja2 . t))

Now we can use either yaml-ts or poly-yaml-jinja2 (which perhaps we should make an alias?):

---
# 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

Ansible

Do I consider all YAML files an Ansible file needing ansible-mode? Maybe we just have a toggle for when we want the Ansible feature.

(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))

The ansible-vault-password-file variable needs to change per project, so let’s use the .dir-locals.el file, for instance:

((nil . ((ansible-vault-password-file . "playbooks/.vault-password"))))

The YAML files get access Ansible’s documentation using the ansible-doc project (that accesses the ansible-doc interface):

(use-package ansible-doc
  :after yaml-ts-mode
  :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")))))

Can we integrate Ansible with LSP using ansible-language-server project (see this documentation)?

Using npm to install the program:

npm install -g @ansible/ansible-language-server

But … will I get some use out of this? I’ll come back to it later.

Technical Artifacts

Let’s provide a name so that the file can be required: