Read from text file then add a user

I am trying to read from a file and use the content within to add a user. Here is what my text file looks like:

IP: x.x.x.x
version: x.x
username: ted password: secretpassword
username: bill password: another password
… other data here

And here is how I am reading it. i want to read in only the lines that begin with username, then use that data to create a user.

hosts: localhost
gather_facts: false
vars:
content: “{{ lookup(‘file’, ‘data.txt’) }}”
tasks:

  • name: get usernames
    set_fact: content.split(‘\n’) | map(‘trim’) | select(‘search’), ‘^username’) | list }}"
  • name: create user
    user:
    name: “{{ item.username }}”
    group: “{{ item.username }}”
    loop: “{{ newcontent }}”

The variable newcontent contains this:

username: ted password: secretpassword
username: bill password: another password

How do I get the username and password from the list newcontent?

Looks like you might need to use some sort of combination of splits? ansible.builtin.split filter – split a string into a list — Ansible Community Documentation

The hard part is getting the quotes right. Look at this:

---
- name: Read user info
  hosts: localhost
  gather_facts: false
  vars:
    # content: "{{ lookup('file', 'username.txt') }}"
    content: |
      IP: x.x.x.x
      version: x.x
      username: bob password: secretpassword
      username: carol password: another password
      IP: x.x.x.x
      version: x.x
      username: ted password: lksj'ldf"98<79>6768z67%&^%&
      username: alice password: another password
  tasks:
    - name: Parse username and passwords from content
      ansible.builtin.debug:
        msg: 'username: «{{ item[0] }}», password: «{{ item[1] }}»' 
      loop: "{{ content |
                split('\n') |
                select('match', 'username:') |
                map('regex_replace', '^username: (.*?) password: (.*)$', '\\1\n\\2') |
                map('split', '\n') }}"

which produces

TASK [Parse username and passwords from content] *****************************
task path: /home/utoddl/ansible/username.yml:17
ok: [localhost] => (item=['bob', 'secretpassword']) => 
  msg: 'username: «bob», password: «secretpassword»'
ok: [localhost] => (item=['carol', 'another password']) => 
  msg: 'username: «carol», password: «another password»'
ok: [localhost] => (item=['ted', 'lksj\'ldf"98<79>6768z67%&^%&']) => 
  msg: 'username: «ted», password: «lksj''ldf"98<79>6768z67%&^%&»'
ok: [localhost] => (item=['alice', 'another password']) => 
  msg: 'username: «alice», password: «another password»'

Note particularly that loop: expression. It’s a double-quoted string, so the what looks like a single-quoted “\” and “n” in those splits are actually literal new-line characters by the time split is invoked. Similarly, that '\\1\n\\2' in the regex_replace becomes:

  • a single backslash
  • the digit 1
  • a literal new-line
  • another single backslash
  • the digit 2

by the time regex_replace is invoked.

Anyway, maybe that will be of some help. Good luck.

4 Likes

Good stuff! That’s some handy regex right there.

1 Like

In the short term, regular expressions are a good approach, but in the long term, tricky implementations can easily be a technical debt.

As a next step, I personally recommend you to consider changing the original text file format to make it easier to process in Ansible :smiley:
For example, CSV, YAML, etc…

username,password
ted,secretpassword
bill,another password
IP: x.x.x.x
version: x.x
users:
  - {"username": "ted", "password": "secretpassword"}
  - {"username": "bill", "password": "another password"}
6 Likes

We currently have it working with a yaml file, but the people in charge want a text file, or json (which I haven’t tested yet) as the data is ingested to another system. I’ll try the regex example this morning.

+1 for json. That would be super easy to work with in Ansible.

Thanks. I get something a bit different though. This is what I get printed out:

(item=[‘username: ted password: secretpassword’] ) => {
msg: “username: "username: ted password: secretpassword", password: "username: ted password is secretpassword"”

(item=[‘username: bob password: secretpassword’] ) => {
msg: “username: "username: bob password: secretpassword", password: "username: bob password is secretpassword"”

Can you give us more context? What are you reading in, and how?

Sure. The data.txt file has this:
username: ted password: secretpassword
username: bob password: another password

The ansible starts like this:

hosts: localhost
gather_facts: false
vars:
content: “{{ lookup(‘file’, ‘data.txt’) }}”

tasks:

  • debug:
    msg: “{{ content }}”

    then the code I posted above

“Content” has the correct data in it.

Sorry to sound picky, @kathyl , but it’s a lot easier to fix, or suggest fixes, to code we can copy-n-paste than it is to try to pull together snippets from prior posts. Also, if you can bracket your code between tripple-backtick lines that would help a lot, too. Otherwise your yaml lists come out as markdown bullet points.

2 Likes

That’s not picky at all - makes complete sense. Here is my code in its totality:

gather_facts: false
vars:
content: “{{ lookup(‘file’, ‘data.txt’) }}”

tasks:

     - name: Debug
       ansible.builtin.debug:
          msg: “{{ content }}”

    - name: Parse username and passwords from content
      ansible.builtin.debug:
        msg: 'username: «{{ item[0] }}», password: «{{ item[1] }}»' 
      loop: "{{ content |
                split('\n') |
                select('match', 'username:') |
                map('regex_replace', '^username: (.*?) password: (.*)$', '\\1\n\\2') |
                map('split', '\n') }}"

My data.txt reads as follows:

   username: bob, password: anotherpassword ```

Ah yes, I see the problem now. You’ve been bitten by smart quotes. This:

content: “{{ lookup(‘file’, ‘data.txt’) }}”

should be:

content: "{{ lookup('file', 'data.txt') }}"

Note that both the single- and double-quotes need fixing. It’s also an issue in your debug task’s msg: line. Somehow, all the quotes in your loop: expression seem to be intact.

It is very hard to see the difference in the default size here on the forum. I have to [ctrl] + [shift] + [+] a couple of times before I can see it.

2 Likes

It must be the font I am using here. My code has ’ and " - the correct type of quotes.

This might not be the issue, but

Is not the same as ' :person_shrugging: .

Of the two you just posted, the single-quote is "U+2019", not "U+0027". The double-quote is okay: "U+0022".

(Note: discourse is converting double-quotes outside of back-ticks to “those other marks” – but only when they are in pairs. I had not noticed that before. Probably works okay in text. Horrible for un-back-tick quoted code.)

2 Likes

In my code i am using “U+2019” and “U+0022”

1 Like

You’re rather inconsistent. Does your data.txt file have comma delimitters? That would have made things a lot easier to begin with.

If there’s a comma, re-using @utoddl’s snippet earlier:

---
- name: Read user info
  hosts: localhost
  gather_facts: false
  vars:
    # content: "{{ lookup('file', 'username.txt') }}"
    content: |
      IP: x.x.x.x
      version: x.x
      username: bob, password: secretpassword
      username: carol, password: another password
      IP: x.x.x.x
      version: x.x
      username: ted, password: lksj'ldf"98<79>6768z67%&^%&
      username: alice, password: another password
  tasks:
    - name: Parse username and passwords from content
      ansible.builtin.debug:
        msg: 'username: {{ item.username }}, password: {{ item.password }}'
      loop: "{{ content |
                split('\n') |
                select('match', 'username:') |
                map('regex_replace', ',\\s+', '\n') |
                map('from_yaml')
                }}"

results in:

PLAY [Read user info] ****************************************************************************************************************************************************************************************

TASK [Parse username and passwords from content] *************************************************************************************************************************************************************
ok: [localhost] => (item={'username': 'bob', 'password': 'secretpassword'}) => {
    "msg": "username: bob, password: secretpassword"
}
ok: [localhost] => (item={'username': 'carol', 'password': 'another password'}) => {
    "msg": "username: carol, password: another password"
}
ok: [localhost] => (item={'username': 'ted', 'password': 'lksj\'ldf"98<79>6768z67%&^%&'}) => {
    "msg": "username: ted, password: lksj'ldf\"98<79>6768z67%&^%&"
}
ok: [localhost] => (item={'username': 'alice', 'password': 'another password'}) => {
    "msg": "username: alice, password: another password"
}

PLAY RECAP ***************************************************************************************************************************************************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
1 Like

That works perfectly - thank you so much!