4

I'd like to use Ansible's lineinfile or replace module in order to add the word splash to the cmdline in GRUB.

It should work for all the following examples:

Example 1:

  • Before: GRUB_CMDLINE_DEFAULT=""
  • After: GRUB_CMDLINE_DEFAULT="splash"

Example 2:

  • Before: GRUB_CMDLINE_DEFAULT="quiet"
  • After: GRUB_CMDLINE_DEFAULT="quiet splash"

Example 3:

  • Before: GRUB_CMDLINE_DEFAULT="quiet nomodeset"
  • After: GRUB_CMDLINE_DEFAULT="quiet nomodeset splash"

The post Ansible: insert a single word on an existing line in a file explained well how this could be done without quotes. However, I can't get it to insert the word within the quotes.

What is the required entry in the Ansible role or playbook in order to add the word splash to the cmdline as shown?

Kalsan
  • 822
  • 1
  • 8
  • 19
  • If you want to include doubles quotes in a string, just use single quotes on the outside: `'Here is "an example" that contains double qutoes'` – larsks Apr 25 '19 at 11:34

5 Answers5

9

You can do this without a shell output, with 2 lineinfiles modules.

In your example you're searching for splash:

- name: check if splash is configured in the boot command
  lineinfile:
    backup: true
    path: /etc/default/grub
    regexp: '^GRUB_CMDLINE_LINUX=".*splash'
    state: absent
  check_mode: true
  register: grub_cmdline_check
  changed_when: false

- name: insert splash if missing
  lineinfile:
    backrefs: true
    path: /etc/default/grub
    regexp: "^(GRUB_CMDLINE_LINUX=\".*)\"$"
    line: '\1 splash"'
  when: grub_cmdline_check.found == 0
  notify: update grub

The trick is to try to remove the line if we can find splash somewhere, but doing a check only check_mode: true. If the term was found (found > 0) then we don't need to update the line. If it's not found, it means we need to insert it. We append it at the end with the backrefs.

marcaurele
  • 502
  • 1
  • 6
  • 14
6

Inspired by Adam's answer, I use this one to enable IOMMU:

- name: Enable IOMMU
  ansible.builtin.lineinfile:
    path: /etc/default/grub
    regexp: '^GRUB_CMDLINE_LINUX_DEFAULT="((?:(?!intel_iommu=on).)*?)"$'
    line: 'GRUB_CMDLINE_LINUX_DEFAULT="\1 intel_iommu=on"'
    backup: true
    backrefs: true
  notify: update-grub

Please note I've had to set backrefs to true in order to \1 reference to work otherwise the captured group was not replaced.

Idempotency works fine as well.

EDIT: Please note this snippet only works with an Intel CPU and might to be updated to fit your platform.

bgondy
  • 1,198
  • 3
  • 20
  • 32
2

A possible solution is the definition of two entries as follows:

- name: "Checking GRUB cmdline"
  shell: "grep 'GRUB_CMDLINE_LINUX_DEFAULT=.*splash.*' /etc/default/grub"
  register: grub_cfg_grep
  changed_when: false
  failed_when: false

- name: "Configuring GRUB cmdline"
  replace:
    path: '/etc/default/grub'
    regexp: '^GRUB_CMDLINE_LINUX_DEFAULT="((\w.?)*)"$'
    replace: 'GRUB_CMDLINE_LINUX_DEFAULT="\1 splash"'
  when: '"splash" not in grub_cfg_grep'

Explanation: We first check if the splash keyword is present in the required line using grep. Since grep gives a negative return code when a string is not found, we suppress the errors using failed_when: false. The output of grep is saved to the grub_cfg_grep variable.

Next, we bind the replace module to the condition that the keyword splash is in the standard output of grep. The regular expression takes the old content in the quotes and adds the splash keyword behind it.

Note: In the case of an empty string before the execution, the result reads " splash" (with a space in front) but it is still a valid cmdline.

Kalsan
  • 822
  • 1
  • 8
  • 19
1

The difficulty is this line in the replace module page: "It is up to the user to maintain idempotence by ensuring that the same pattern would never match any replacements made."https://docs.ansible.com/ansible/latest/modules/replace_module.html#id4 It's easy to insert the item but actually quite tricky to make it idempotent, so the target file doesn't grow every time you run the task.

I found a way to do it in one shot with the replace module. You should be able to adapt this. My task checks the GRUB_CMDLINE_LINUX_DEFAULT line for "vt.default_red" and inserts some colour codes if not found.

My method was to copy-and-paste various nearly-there examples into the regex tester website and fiddle until it worked. I still don't grok the result, but it worked in my tests at https://www.regextester.com/ and it works in my playbook.

One problem I had was that Ansible's regex implementation apparently doesn't support conditionals, which gave me odd errors for a while.

- name: colours | configured grub command
  replace:
    path: /etc/default/grub
    regexp: '^GRUB_CMDLINE_LINUX_DEFAULT="((:?(?!vt\.default_red).)*?)"$'
    replace: 'GRUB_CMDLINE_LINUX_DEFAULT="\1 vt.default_red=0xee,..."'

The regex matches the literal string ("GRUB_CMDLINE_LINUX_DEFAULT=" and a double quote mark) at the start and the double quote mark at the end. Deconstructing the rest...

(                            - open capture group #1 (creates backref #1)
 (:?                         - open a non-capturing group (not sure what the question mark is here)
    (?!                      - negative lookahead (ie. don't match if the following string comes next)
       vt\.default_red       - the string to look for, literal dot is escaped
                      )      - close negative lookahead
                       .)    - match a single char (why?) and close the non-capturing group
                         *   - try to match the non-capturing group zero or more times
                          ?  - ... lazily (ie. get the smallest possible match)
                           ) - close capture group #1
0

What about doing this in Ansible, use perl to address your need.

- name: Change items in the file
  ansible.builtin.command:
    command: perl -i pe 's/DEFAULT="/DEFAULT="splash"/' 

Another way of looking at it. This is an old conversation, but it is still relevant.

user67275
  • 1
  • 9
  • 38
  • 64
Tdsan
  • 1
  • 1
  • This will require Perl installed and won't support check and diff modes. The linefile/replace modules would support both. In addition it needs refinement, try `echo 'DEFAULT="a param here"' | perl -pe 's/DEFAULT="/DEFAULT="splash"/'` to see a problem, extra double quote instead of space. Sed would also do: `echo 'DEFAULT="a param here"' | sed 's/DEFAULT="/DEFAULT="splash/'`. – Doncho Gunchev Jul 15 '23 at 19:13