Module to patch (update) environment variables file with version

Scenario

I have an artifact with an .env file which can have the following structure:

vSERVICE1=1.8.1
vSERVICE2=2.0.0

The values of the env vars are updated which I would like patch to an existing production .env in the /home/user/project/.env

The production .env file has more environment variables e.g.:

vSERVICE1=1.8.0
vSERVICE2=1.9.0
vSERVICE3=2.1.2
vSERVICE4=0.0.1

I wish to patch the vSERVICE1 and vSERVICE2 environment variables without altering the other values in the production .env file.

At the moment I can obtain the difference using diff and check_mode:

- ansible.builtin.copy:
    src: /tmp/artifacts/.env
    dest: /home/user/project/.env
    check_mode: true
    diff: true

which is overwriting the production file which implies copy might not be the best solution. Can patch the file without regex gymnastics or do I have to resort to lineinfile module?

Initial Solution

- hosts: localhost
  gather_facts: false
  tasks:
    - name: 'read each line of the  new env var file'
      ansible.builtin.set_fact:
        new_env: "{{ lookup('ansible.builtin.file', '/tmp/artifacts/.env')  | split }}"
   - name: 'replace any lines that match the env vars of the new file'
     ansible.builtin.lineinfile:
       path: /home/user/project/.env
       regexp: '^{{ service_name }}='
       line: "{{ item }}"
     check_mode: true
     diff: true
     vars:
       service_name: "{{ item.split('=')[0] }}"
     loop: "{{ new_env }}"

This works because I read the new env var file and split it based on = character. I use the first instance and call it service_name in a loop and tell lineinfile to replace the instance with the updated value.

I am open to better solutions but thought I would keep this solution if there isn’t any

2 Likes

I suppose you could use ansible.builtin.combine, although any comments or blank lines in the production env file will be lost.

  • Load env files as dict, by reading it as YAML file after replacing = with :
  • Merge those two dict
  • Save production env file by converting merged dict to YAML string and replacing : with =
- hosts: localhost
  gather_facts: false
  vars:
    env_production: "prod.env"
    env_patch: "patch.env"
  
  tasks:
    - name: Patch production env file
      ansible.builtin.copy:
        content: "{{ dict_merged | ansible.builtin.to_nice_yaml | regex_replace(': ', '=') }}"
        dest: "{{ env_production }}"
      vars:
        dict_production: "{{ lookup('file', env_production) | replace('=', ': ') | ansible.builtin.from_yaml }}"
        dict_patch: "{{ lookup('file', env_patch) | replace('=', ': ') | ansible.builtin.from_yaml }}"
        dict_merged: "{{ dict_production | ansible.builtin.combine(dict_patch) }}"
4 Likes

There are several great ways to read, write and modify INI files using Ansible, I linked to them in this comment, .ini / .conf / .env file lookup, fliters and modules:

JC INI parsers that can be used with the JC filter :

I’d suggest using these rather than lineinfile.

2 Likes

@chris
Thanks for updating, I first thought about treating it as an ini file, but I’m a bit obsessed with a solution that doesn’t use community.general as much as possible :smiley:

2 Likes

Fair enough, I suspect I don’t have any roles that don’t make a lot of use of community.general and especially the jc and json_query filters!

I don’t know what I do without json_query!

1 Like

since the filter moved to community.general I’ve just been using GitHub - jqlang/jq: Command-line JSON processor (so lightweight!)