jan
jan

Reputation: 2830

How to append multiline string with leading space to a remote file in host with Ansible?

I have the same question as in Multiline strings with leading spaces, but the solutions I found there don't work for the lineinfile module I'm currently using to append to an existing text file in remote host.

Idea is to write a log with bullet list, i.e. an output file like this. It's created line by line in different tasks in the play.

Start
 - perform X
  + perform X1
 - perform Y
End

I tried literally everything and guess this is possible in YAML (https://yaml-multiline.info/), but Ansible is breaking it again like it did before (https://github.com/ansible/ansible/issues/12133). Anybody aware of any workaround?

Here are two attempts I guess should work from YAML side. First one is even a syntax error for Ansible.

- name: append log
  lineinfile:
    path: "{{ log_path }}"
    line: |6
       - perform X
        + perform X1
- name: append log
  lineinfile:
    path: "{{ log_path }}"
    line: "\
 - perform X\n
  + perform X1\n"

Output in the file in the second has leading space stripped and indention doesn't match the other bullet points from single line strings:

Start
- perform X
 + perform X1
 - perform Y
End

Upvotes: 1

Views: 1126

Answers (3)

U880D
U880D

Reputation: 12090

It is still unclear for me what you try to achieve. One point is idempotency, an other point is a mutually exclusive problem description. However,

I need to append text to a file. How do you do this with these modules?

from your description and example I understand that you have a file

Input (input.file)

Start
 - perform Y
End

Annot.: A protocol or an algorithm

in which you like to insert lines before and expect a result like

Output / Result (output.file)

Start
 - perform X
  + perform X1
 - perform Y
End

Annot.: Insert before performing Y because of the given examples. Not to append in any case as it looks like for me that Start and End are markers within the file and part of the protocol. Furthermore an algorithm or protocol depends on order otherwise it would be a log file.


One option for processing could be to patch the file.

diff input.file output.file > input.diff
cat input.diff
1a2,3
>  - perform X
>   + perform X1

Processing

So an example playbook like

---
- hosts: localhost
  become: false
  gather_facts: false

  tasks:

  - name: Apply patch to a file
    patch:
      src: input.diff
      dest: "/home/{{ ansible_user }}/test/output.file"

will result into the necessary output.


"It is created in the play at different stages line by line. In one situation I would like to add a multiline string in one append operation."

If you like to write a protocol about what happened, also know as log file and to append raw strings to an existing (log) file you may have a look into the following approach with as !unsafe marked strings

---
- hosts: localhost
  become: false
  gather_facts: false

  tasks:

  - name: Append (unsafe) lines to log
    lineinfile:
      path: log.file
      create: true
      line: !unsafe >-
        test
          - test
           + test

resulting into a file with a content of

cat log.file
test
  - test
   + test
test
  - test
   + test
test
  - test
   + test

after three (3) iterations.

Ansible uses a data type called unsafe to block templating. ... The Ansible implementation ensures that unsafe values are never templated.

This will not only give the possibility to have leading whitespace(s) but also allow special characters like { or %.

Upvotes: 1

β.εηοιτ.βε
β.εηοιτ.βε

Reputation: 39169

It is unclear to me if you want to achieve idempotency or not.

In the case you want to achieve idempotency, the lineinfile module is not the proper one to use, as it is meant to change a single line, not multiple ones:

  • This is primarily useful when you want to change a single line in a file only.
  • See the ansible.builtin.replace module if you want to change multiple, similar lines or check ansible.builtin.blockinfile if you want to insert/update/remove a block of lines in a file. For other cases, see the ansible.builtin.copy or ansible.builtin.template modules.

Source: lineinfile module synopsis


But, as far as your indentation matter is concerned, then you could use the indent filter of Jinja and place your desired lines in the vars of your module. You will also need the second parameter of the indent filter set to true, as you also want to indent the first line:

- name: append protocol
  lineinfile:
    path: "{{ protocol_path }}"
    line: "{{ _line | indent(1, true) }}"
  vars:
    _line: |
      - perform X
       + perform X1

This will render in:

 - perform X
  + perform X1

Upvotes: 2

jan
jan

Reputation: 2830

I found one workaround that uses single line strings and a loop, i.e. I document it here, but would still prefer to have a solution for the general real multiline case (e.g. from a variable):

- name: append protocol
  lineinfile:
    path: "{{ protocol_path }}"
    line: "{{ item }}"
  loop:
    - " - perform X"
    - "  + perform X1"

Upvotes: 0

Related Questions