Skip to content

Instantly share code, notes, and snippets.

@skx
Last active March 14, 2024 06:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save skx/ab53c1c8b35b857f30503179b7ebcdb3 to your computer and use it in GitHub Desktop.
Save skx/ab53c1c8b35b857f30503179b7ebcdb3 to your computer and use it in GitHub Desktop.
Overview of catching yaml issues for kubernetes

Automating the use of Linting Tools

With a custom YAML check

Many linters are external (shell) commands you can invoke with a filename, and which exit with non-zero status-codes on failure.

The problems a given linter will report upon will obviously vary, but actually executing a linter automatically is broadly tool-agnostic, so automating is pretty simple:

  • When you save a file, and it is of a type which has a linter associated with it:
    • Run the external linter command passing the filename as an argument.
      • If the command exits with a non-zero exit-code assume the linter reported failure of some kind.
        • Then show the output it produced.
      • If the command exits with a zero exit-code we assume success and do nothing.

So the only difficult part is knowing what command to execute as a linter for any given filetype:

  • For a shell-script we'd call "shellcheck ..".
  • For a terraform file we'd call "tflint ..".
  • For a python-file we'd call "python3 -m py_compile .."
    • Well something similar, we don't need to have a .pyc file created, etc, etc.

For extra flexibility and options we can also allow the execution of Emacs Lisp to allow custom checks to be made. Same process here:

  • If the lisp returns nil, do nothing.
  • If the lisp returns some content, treat it as a message/warning to display to the user.

The save-check package is my naive approach to the problem, and contains some simple defaults which work well for me. Loading and using the package is pretty simple, with modern emacs I can enable it like so:

(use-package save-check
  :config
    (setq  save-check-show-eval t)
    (global-save-check-mode t))

Now if I want to look for duplicate keys when YAML files are saved, in addition to the standard linting the package enables by default, I can write a simple function:

  • Look for all top-level headers.
    • A simple regular expression will find them ^([a-zA-Z0-9]+):
  • Find any duplicates in the list, filtering out false-positives which are expected.
  • If any remain then return them to display - because that's a linter-detected failure.

I wrote the following simple function to look for duplicates:

(defun save-check-custom-yaml ()
  "Determine whether there are duplicate top-level keys in the current buffer.

This function is primarily written to be invoked by `save-check' on YAML buffers,
but the code is actually not specific to that purpose."
  (interactive)
  (let ((matches))
    ;; get the matches - i.e. top-level keys
    (save-match-data
      (save-excursion
        (save-restriction
          (widen)
          (goto-char 1)
          (while (search-forward-regexp "^\\([a-zA-Z0-9]+\\):" nil t 1)
            (push (match-string-no-properties 0) matches)))))
    ;; now matches contains a list of all top-level keys - look for any duplicate entries
    (setq matches (delete-dups (seq-filter
                                (lambda (el) (member el (cdr (member el matches))))
                                matches)))

    ;; remove false-positives which might occur more than once even without error.
    (dolist (known '("apiVersion:" "kind:" "metadata:" "spec:"))
      (setq matches (delete known matches)))

    ;; if we have duplicates - format the response based on one vs. multiple duplicates.
    (if matches
        (if (= 1 (length matches))
            (format "There is a duplicate top-level key %s" matches)
          (format "There are %d duplicate top-level keys %s" (length matches) matches))
      nil)))

With that custom function defined I can now add it to the list of known linters:

(add-to-list 'save-check-config
      '(:mode yaml-mode
        :eval (save-check-custom-yaml)))

And now when I save YAML files any duplicated top-level keys will show themselves when I save the file.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment