Skip to content

Instantly share code, notes, and snippets.

@shantanoo-desai
Last active June 12, 2023 17:50
Show Gist options
  • Select an option

  • Save shantanoo-desai/bca38860af1e55d0323bbad9cb89fca1 to your computer and use it in GitHub Desktop.

Select an option

Save shantanoo-desai/bca38860af1e55d0323bbad9cb89fca1 to your computer and use it in GitHub Desktop.
- hosts: all
gather_facts: false
vars_files:
- defaults/main.yml
- vault.yml
tasks:
- name: Do Not Execute Playbook if No Flows File extra variable is mentioned
fail:
msg: >
Please specify the flows file to be deployed on the fleet using:
ansible-playbook deploy_flow.yml -e "flow=<name>.json" --ask-vault-pass
when: flow is not defined
- name: Checking if Flow File Exists in Directory
stat:
path: "{{flows_directory }}/{{ flow }}"
delegate_to: localhost
register: flow_file
- name: Flow File doesn't exist
fail:
msg: "The Flow File does not exist in Path: {{flows_directory}}"
when: not flow_file.stat.exists
- name: Reading the Flow File
set_fact:
flow_json: "{{ lookup('ansible.builtin.file', flows_directory + '/' + flow) | from_json }}"
when: flow_file.stat.exists
- name: Check for Existence of either InfluxDB, MySQL or MQTT nodes in Flows
set_fact:
nodes_exist: true
when: "flow_json | selectattr('type', 'equalto', node) | list | count > 0"
loop:
- influxdb
- mqtt-broker
- mysql
loop_control:
loop_var: node
- name: Dynamic Credential Insertion for InfluxDB, MySQL, or MQTT Configuration Nodes
# Use set_fact on `flow_json` variable because we want to update the same JSON structure
set_fact:
# Extract the dict and combine the credentials block in it
# and merge it back into the list and update `flow_json` fact
flow_json: "{{ flow_json[0:index]
+ [ node | combine(node_replacements[node.type]) ]
+ flow_json[index|int+1:] }}"
# Loop over the list of dictionaries and determine the index of the dict
# which has `type` value either `influxdb`, `mysql` or `mqtt`
when: (nodes_exist is not undefined) and (nodes_exist is true)
loop: "{{ query('ansible.utils.index_of', flow_json, 'in', matched_nodes) }}"
loop_control:
# Use `idx` as the variable name for the index obtained by loop
loop_var: index
# Observe the matches made on the Terminal as <node.id>, <node.type> for better clarity
label: "{{ node.id ~ ', ' ~ node.type }}"
# Variables needed for the loop
vars:
# call each indexed dictionary in the list as `node`
node: "{{ flow_json[index] }}"
# check if `type` is either `influxdb`, `mysql`, or `mqtt` and store it `matched_nodes` variable
matched_nodes: "{{ flow_json | selectattr('type', 'in', node_replacements.keys()) }}"
# The general Structure of the "credentials" block to be inserted if there are `matched_nodes`
# The username, password credentials will be extracted from Ansible-Vault
node_replacements:
influxdb:
credentials:
username: "{{ influxdb.username }}"
password: "{{ influxdb.password }}"
MySQLdatabase:
credentials:
user: "{{ mysql.username }}"
password: "{{ mysql.password }}"
mqtt-broker:
credentials:
username: "{{ mqtt.username }}"
password: "{{ mqtt.password }}"
- name: Preparing Flow File for Deployment
copy:
dest: "{{ flows_directory + '/.' + flow }}"
content: "{{ flow_json | to_nice_json }}"
delegate_to: localhost
- name: Obtain the Authentication Token for Node-RED Instance
ansible.builtin.uri:
# API: /auth/token HTTP POST
# Headers: Content-Type: application/json
# Body:
# client_id: node-red-admin
# grant_type: password
# scope: *
# username: Node-RED instance username
# password: Node-RED instance username
# Expected Response Code: 200
# Response Body:
# {
# "access_token": "<token>",
# "expires_in": 604800,
# "token_type": "Bearer"
# }
url: "{{ nodered_base_url }}/auth/token"
method: POST
body:
client_id: node-red-admin
grant_type: password
scope: "*"
username: "{{ nodered.username }}"
password: "{{ nodered.password }}"
body_format: form-urlencoded
status_code: 200
# Store the Response in a variable called `login`
register: login
when: flow is defined
- name: Upload Dedicated Flow on Each Node-RED Instance in Fleet
ansible.builtin.uri:
# API: /nodered/flows HTTP POST
# Headers:
# Authorization: Bearer <token>
# Content-Type: application/json
# Node-RED-API-Version: v1
# Node-RED-Deployment-Type: full
# Body:
# <Flow JSON File>
# Expected Response Code: 204
# Response body: none
url: "{{ nodered_base_url }}/flows"
method: POST
headers:
# Obtain the Token and its Type from the `login` variable from previous task
Authorization: "{{ login.json.token_type }} {{ login.json.access_token }}"
Content-Type: application/json
Node-RED-API-Version: v1
Node-RED-Deployment-Type: full
# `flow` is a variable containing the <name>.json of the node-RED flow to be deployed
body: "{{ lookup('ansible.builtin.file', flows_directory + '/.' + flow) }}"
body_format: json
status_code: 204
register: flow_upload_status
when: flow is defined
- name: Revoke the Authentication Token for Node-RED Instance
ansible.builtin.uri:
# API: /nodered/auth/revoke HTTP POST
# Headers:
# Authorization: Bearer <token>
# Content-Type: application/json
# Body:
# {"token": "<token>"}
# Expected Response Code: 200
# Response body: none
url: "{{ nodered_base_url }}/auth/revoke"
method: POST
headers:
# Obtain the Token and its Type from the `login` variable from previous task
Authorization: "{{ login.json.token_type }} {{ login.json.access_token }}"
Content-Type: application/json
body: '{"token": "{{ login.json.access_token }}" }'
body_format: json
status_code: 200
- name: Post Deployment Cleanup
file:
name: "{{ flows_directory + '/.' + flow }}"
state: absent
when: flow_upload_status.status == 204
delegate_to: localhost
@shantanoo-desai
Copy link
Author

shantanoo-desai commented Jan 26, 2023

Solution 1

from Kristianheljas on Ansible IRC Chat

kristianheljas 16:23:11
So, it is just looping trough indexes that match the items you find in �matched_nodes and replacing each index using list splicing to inject on into the correct place

- hosts: localhost
  gather_facts: false
  vars:
    node_red_flows: [
      {
        "id": "one",
        "type": "influxdb",
        "hostname": "127.0.0.1",
        "port": "8086",
        "protocol": "http",
        "database": "test",
        "name": "testfluxdb",
        "usetls": false,
        "tls": "",
        "influxdbVersion": "1.x",
        "url": "http://localhost:8086",
        "rejectUnauthorized": true
      },
      {
        "id": "f6f2187d.f17ca8",
        "type": "tab",
        "label": "Flow 1",
        "disabled": false,
        "info": ""
      },
      {
        "id": "two",
        "type": "influxdb",
        "hostname": "127.0.0.1",
        "port": "8086",
        "protocol": "http",
        "database": "test",
        "name": "testfluxdb",
        "usetls": false,
        "tls": "",
        "influxdbVersion": "1.x",
        "url": "http://localhost:8086",
        "rejectUnauthorized": true
      },
      {
        "id": "ae7554e7968c9aa1",
        "type": "influxdb in",
        "z": "f6f2187d.f17ca8",
        "influxdb": "398670264ec6e79f",
        "name": "fluxtestdb",
        "query": "",
        "rawOutput": false,
        "precision": "",
        "retentionPolicy": "",
        "org": "organisation",
        "x": 460,
        "y": 300,
        "wires": [
          [ ]
        ]
      }
    ]
  tasks:

    - set_fact:
        node_red_flows: "{{ node_red_flows[0:item] + [node_red_flows[item] | combine(combine_with)] + node_red_flows[item|int+1:] }}"
      loop: "{{ query('ansible.utils.index_of', node_red_flows, 'in', matched_nodes) }}"
      vars:
        matched_nodes: "{{ node_red_flows | selectattr('type', '==', 'influxdb') }}"
        combine_with:
          credentials:
            username: tester
            password: tester

    - debug:
        msg: "{{ node_red_flows }}"

@shantanoo-desai
Copy link
Author

shantanoo-desai commented Jan 26, 2023

Solution

by kristian heljas

explanation

I added loop_control as well to provide nicer output for the item and more meaningul name to the index (opposed to item)
[4:58:27 PM] Using the label option, you can configure how ok: [localhost] => (item=****THIS*****) looks like in the ansible output
[4:59:12 PM] And �loop_var allows you to change the default variable name �item, which in this case might have been confusing

- hosts: localhost
  gather_facts: false
  vars:
    node_red_flows: [
      {
        "id": "one",
        "type": "mysql",
        "hostname": "127.0.0.1",
        "port": "8086",
        "protocol": "http",
        "database": "test",
        "name": "testfluxdb",
        "usetls": false,
        "tls": "",
        "influxdbVersion": "1.x",
        "url": "http://localhost:8086",
        "rejectUnauthorized": true
      },
      {
        "id": "f6f2187d.f17ca8",
        "type": "tab",
        "label": "Flow 1",
        "disabled": false,
        "info": ""
      },
      {
        "id": "two",
        "type": "influxdb",
        "hostname": "127.0.0.1",
        "port": "8086",
        "protocol": "http",
        "database": "test",
        "name": "testfluxdb",
        "usetls": false,
        "tls": "",
        "influxdbVersion": "1.x",
        "url": "http://localhost:8086",
        "rejectUnauthorized": true
      },
      {
        "id": "ae7554e7968c9aa1",
        "type": "influxdb in",
        "z": "f6f2187d.f17ca8",
        "influxdb": "398670264ec6e79f",
        "name": "fluxtestdb",
        "query": "",
        "rawOutput": false,
        "precision": "",
        "retentionPolicy": "",
        "org": "organisation",
        "x": 460,
        "y": 300,
        "wires": [
          [ ]
        ]
      },
      {
        "id": "three",
        "type": "influxdb",
        "hostname": "127.0.0.1",
        "port": "8086",
        "protocol": "http",
        "database": "test",
        "name": "testfluxdb",
        "usetls": false,
        "tls": "",
        "influxdbVersion": "1.x",
        "url": "http://localhost:8086",
        "rejectUnauthorized": true
      }
    ]
  tasks:

    - set_fact:
        node_red_flows: "{{ node_red_flows[0:index] + [node | combine(node_replacements[node.type])] + node_red_flows[index|int+1:] }}"
      loop: "{{ query('ansible.utils.index_of', node_red_flows, 'in', matched_nodes) }}"
      loop_control:
        loop_var: index
        label: "{{ node.id ~ ', ' ~ node.type }}"
      vars:
        node: "{{ node_red_flows[index] }}"
        matched_nodes: "{{ node_red_flows | selectattr('type', 'in', node_replacements.keys()) }}"
        node_replacements:
          influxdb:
            credentials:
              username: influxdb-tester
              password: influxdb-tester
          mysql:
            credentials:
              username: mysql-tester
              password: mysql-tester

    - debug:
        msg: "{{ node_red_flows }}"

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