Format JSON Ansible

Ansible présente en sortie standard les résultats de ses actions en format JSON.

1. JavaScript Object Notation (JSON)

JavaScript Object Notation (JSON) est un format de données textuelles dérivé de la notation des objets du langage JavaScript. Il permet de représenter de l’information structurée comme le permet XML par exemple.

Les types de données de base de JSON sont les suivantes :

  • Nombre
  • Chaîne de caractères : une séquence de zéro ou plus de caractères Unicode. Les chaînes de caractères sont délimitées par des guillemets (”"” et “"”) et supportent une barre oblique inversée comme carctère d’échappement (”\”).
  • Booléen : l’une ou l’autre des valeurs “true” ou “false”.
  • Tableau (array) : une liste ordonnée de zéro ou plus de valeurs, dont chacune peut être de n’importe quel type. Les tableaux utilisent la notation par crochets (”[” et “]”) et les éléments sont séparés par des virgules (”,”).
  • Objet : une collection non ordonnée de paires nom-valeur dont les noms (aussi appelés clés) sont des chaînes de caractères. Puisque les objets sont destinés à représenter des tableaux associatifs, il est recommandé, bien que non requis, que chaque clé soit unique dans un objet. Les objets sont délimités par des accolades (”{” et “}”) et séparés par des virgules, tandis que dans chaque paire, le caractère deux-points “:” sépare la clé ou le nom de sa valeur.
  • nulle : Une valeur vide, en utilisant le mot “null

Les espaces limités sont autorisés et ignorés autour ou entre les éléments syntaxiques (valeurs et ponctuation, mais pas à l’intérieur d’une valeur de chaîne). Seuls quatre caractères spécifiques sont considérés comme des espaces : espace, tabulation horizontale, saut de ligne et retour chariot.

JSON ne fournit pas de syntaxe pour les commentaires.

2. Exemple de format JSON

{
  "firstName": "John",
  "lastName": "Smith",
  "age": 25,
  "address": {
    "streetAddress": "21 2nd Street",
    "city": "New York",
    "state": "NY",
    "postalCode": "10021"
  },
  "phoneNumber": [
    {
      "type": "home",
      "number": "212 555-1234"
    },
    {
      "type": "fax",
      "number": "646 555-4567"
    }
  ],
  "gender": {
    "type": "male"
  }
}

Ces données sont constituées de 6 clés :

  • “address”,
  • “age”,
  • “firstName”,
  • “gender”,
  • “lastName”,
  • et “phoneNumber”

L’objet “address” est consitué de 4 clés.

  • “city”,
  • “postalCode”,
  • “state”,
  • et “streetAddress”

L’objet “phoneNumber” d’un tableau à deux entrées contenant chacune deux clés.

{
  "type": "home",
  "number": "212 555-1234"
},
{
  "type": "fax",
  "number": "646 555-4567"
}

3. Traitement avec jq

Le logiciel jq est outil de traitement du texte comme sed mais pour traiter des sorties JSON.

Avant tout il faut des données à traiter, par exemple l’exemple qui précède :

cat << EOF > data
{
  "firstName": "John",
  "lastName": "Smith",
  "age": 25,
  "address": {
    "streetAddress": "21 2nd Street",
    "city": "New York",
    "state": "NY",
    "postalCode": "10021"
  },
  "phoneNumber": [
    {
      "type": "home",
      "number": "212 555-1234"
    },
    {
      "type": "fax",
      "number": "646 555-4567"
    }
  ],
  "gender": {
    "type": "male"
  }
}
EOF

Les données à traitement viennent entrée de la commande :

cat data | jq
{
  "firstName": "John",
  "lastName": "Smith",
  "age": 25,
  "address": {
    "streetAddress": "21 2nd Street",
    "city": "New York",
    "state": "NY",
    "postalCode": "10021"
  },
  "phoneNumber": [
    {
      "type": "home",
      "number": "212 555-1234"
    },
    {
      "type": "fax",
      "number": "646 555-4567"
    }
  ],
  "gender": {
    "type": "male"
  }
}

Le filtre se place entre trémas pour incorporer des filtres divers :

cat data | jq '.'

Par exemple le filtre keys affiche les clés d’un objet :

cat data | jq '. | keys'
[
  "address",
  "age",
  "firstName",
  "gender",
  "lastName",
  "phoneNumber"
]

Par exemple le filtre length compte les objets :

cat data | jq '. | length'
6

On peut afficher la valeur d’une clé :

cat data | jq '.firstName'
"John"
cat data | jq '.address'
{
  "streetAddress": "21 2nd Street",
  "city": "New York",
  "state": "NY",
  "postalCode": "10021"
}
cat data | jq '.address.city'
"New York"

Cela fonctionne tant que l’on a à faire avec des objets. Mais cela ne va plus avec des dictionnaires :

cat data | jq '.phoneNumber'
[
  {
    "type": "home",
    "number": "212 555-1234"
  },
  {
    "type": "fax",
    "number": "646 555-4567"
  }
]
cat data | jq '.phoneNumber.type'
jq: error (at <stdin>:24): Cannot index array with string "type"

Reprenons, l’objet phoneNumber est consitué d’un dictionnaire de deux objets :

cat data | jq '.phoneNumber'
[
  {
    "type": "home",
    "number": "212 555-1234"
  },
  {
    "type": "fax",
    "number": "646 555-4567"
  }
]

Affichons les objets séparément :

cat data | jq '.phoneNumber[]'
{
  "type": "home",
  "number": "212 555-1234"
}
{
  "type": "fax",
  "number": "646 555-4567"
}

Filtrons par clé type :

cat data | jq '.phoneNumber[] | .type'
"home"
"fax"

Filtrons sur le second objet :

cat data | jq '.phoneNumber[1]'
{
  "type": "fax",
  "number": "646 555-4567"
}

Quelle est la valeur de la seconde clé du champ number ?

cat data | jq '.phoneNumber[1].number'
"646 555-4567"

5. Module setup

Le module setup exécuté en mode ad-hoc permet de récupérer des méta-données sur les hôtes en format JSON.

ansible localhost -m setup

Ce traitement traite la sortie standard en seule ligne moins la chaîne de caractère | SUCCESS =>

ansible localhost -m setup -o | sed 's/.*> {/{/g')

Pour traitement ultérieur, on valorise une variable $FACTS :

FACTS=$(ansible localhost -m setup -o | sed 's/.*> {/{/g')

Voyez vous-même :

echo $FACTS

6. Sorties JSON

Formatage JSON

Début des donées :

echo $FACTS | jq . | head -20
{
  "ansible_facts": {
    "ansible_all_ipv4_addresses": [
      "10.6.19.161",
      "172.17.0.1",
      "192.168.122.1"
    ],
    "ansible_all_ipv6_addresses": [
      "fe80::207:cbff:fe0b:1b57"
    ],
    "ansible_apparmor": {
      "status": "enabled"
    },
    "ansible_architecture": "x86_64",
    "ansible_bios_date": "01/17/2018",
    "ansible_bios_version": "00.00.00.0012",
    "ansible_cmdline": {
      "boot": "local",
      "console": "ttyS1,9600n8",
      "nbd.max_part": "16",

Fin des données :

echo $FACTS | jq . | tail -20
      "hw_timestamp_filters": [],
      "macaddress": "52:54:00:c9:73:7e",
      "mtu": 1500,
      "promisc": true,
      "timestamping": [
        "tx_software",
        "rx_software",
        "software"
      ],
      "type": "ether"
    },
    "ansible_virtualization_role": "host",
    "ansible_virtualization_type": "kvm",
    "gather_subset": [
      "all"
    ],
    "module_setup": true
  },
  "changed": false
}

Nombre d’objets ?

echo $FACTS | jq '. | length'
2

Quels objets ?

echo $FACTS | jq '. | keys'
[
  "ansible_facts",
  "changed"
]

Quels objets dans “ansible_facts” ?

echo $FACTS | jq '.ansible_facts | keys'
[
  "ansible_all_ipv4_addresses",
  "ansible_all_ipv6_addresses",
  "ansible_apparmor",
  "ansible_architecture",
  "ansible_bios_date",
  "ansible_bios_version",
  "ansible_cmdline",
  "ansible_date_time",
  "ansible_default_ipv4",
  "ansible_default_ipv6",
  "ansible_device_links",
  "ansible_devices",
  "ansible_distribution",
  "ansible_distribution_file_parsed",
  "ansible_distribution_file_path",
  "ansible_distribution_file_variety",
  "ansible_distribution_major_version",
  "ansible_distribution_release",
  "ansible_distribution_version",
  "ansible_dns",
  "ansible_docker0",
  "ansible_domain",
  "ansible_effective_group_id",
  "ansible_effective_user_id",
  "ansible_env",
  "ansible_eth0",
  "ansible_fips",
  "ansible_form_factor",
  "ansible_fqdn",
  "ansible_hostname",
  "ansible_interfaces",
  "ansible_is_chroot",
  "ansible_iscsi_iqn",
  "ansible_kernel",
  "ansible_lo",
  "ansible_local",
  "ansible_lsb",
  "ansible_lvm",
  "ansible_machine",
  "ansible_machine_id",
  "ansible_memfree_mb",
  "ansible_memory_mb",
  "ansible_memtotal_mb",
  "ansible_mounts",
  "ansible_nodename",
  "ansible_os_family",
  "ansible_pkg_mgr",
  "ansible_processor",
  "ansible_processor_cores",
  "ansible_processor_count",
  "ansible_processor_threads_per_core",
  "ansible_processor_vcpus",
  "ansible_product_name",
  "ansible_product_serial",
  "ansible_product_uuid",
  "ansible_product_version",
  "ansible_python",
  "ansible_python_version",
  "ansible_real_group_id",
  "ansible_real_user_id",
  "ansible_selinux",
  "ansible_selinux_python_present",
  "ansible_service_mgr",
  "ansible_ssh_host_key_dsa_public",
  "ansible_ssh_host_key_ecdsa_public",
  "ansible_ssh_host_key_ed25519_public",
  "ansible_ssh_host_key_rsa_public",
  "ansible_swapfree_mb",
  "ansible_swaptotal_mb",
  "ansible_system",
  "ansible_system_capabilities",
  "ansible_system_capabilities_enforced",
  "ansible_system_vendor",
  "ansible_uptime_seconds",
  "ansible_user_dir",
  "ansible_user_gecos",
  "ansible_user_gid",
  "ansible_user_id",
  "ansible_user_shell",
  "ansible_user_uid",
  "ansible_userspace_architecture",
  "ansible_userspace_bits",
  "ansible_virbr0",
  "ansible_virbr0_nic",
  "ansible_virtualization_role",
  "ansible_virtualization_type",
  "gather_subset",
  "module_setup"
]

De quoi est composé l’objet “ansible_facts.ansible_all_ipv4_addresses” ?

echo $FACTS | jq '.ansible_facts.ansible_all_ipv4_addresses | keys'
[
  "10.6.19.161",
  "172.17.0.1",
  "192.168.122.1"
]

Un tableau à trois entrées. Quel est la seconde entrée ?

echo $FACTS | jq '.ansible_facts.ansible_all_ipv4_addresses[1]'