Formats JSON et YAML Ansible Objectifs de certification RHCE EX294 (RHEL8) Si vous poursuivez des objectifs de certification, vous pourriez trouvez ici des bases qui vous aideront pour : 2. Maîtrise des composants de base d’Ansible 2.3. Variables 2.4. Facts 6. Création des jeux et des playbooks Ansible 9. Utilisation des fonctions Ansible avancées 9.1. Créer et utiliser des modèles pour générer des fichiers de configuration personnalisés 9.3. Utilisation de variables et de facts Ansible 1. Formats de présentation des données On présentera brièvement dans cette section trois formats de présentation de données : XML, le standard JSON, le standard en version lisible YAML, pour présenter des données utilisateur Le format JSON sera plus amplement étudié dans une section suivante. 1.1. XML L’Extensible Markup Language, généralement appelé XML est un métalangage informatique de balisage générique qui est un sous-ensemble du Standard Generalized Markup Language (SGML). Sa syntaxe est dite “extensible” car elle permet de définir différents langages avec chacun leur vocabulaire et leur grammaire, comme XHTML, XSLT, RSS, SVG… Elle est reconnaissable par son usage des chevrons (<, >) encadrant les noms des balises. L’objectif initial de XML est de faciliter l’échange automatisé de contenus complexes (arbres, texte enrichi, etc.) entre systèmes d’informations hétérogènes (interopérabilité). Avec ses outils et langages associés, une application XML respecte généralement certains principes : la structure d’un document XML est définie et validable par un schéma ; un document XML est entièrement transformable dans un autre document XML. Exemple : 1.2. 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. { "menu": { "id": "file", "value": "File", "popup": { "menuitem": [ { "value": "New", "onclick": "CreateNewDoc()" }, { "value": "Open", "onclick": "OpenDoc()" }, { "value": "Close", "onclick": "CloseDoc()" } ] } } } Remarque : JSON ne supporte pas les commentaires au contraire de YAML ou de XML. 1.3. YAML YAML, acronyme de “Yet Another Markup Language” dans sa version 1.0, il devient l’acronyme récursif de “YAML Ain’t Markup Language” (“YAML n’est pas un langage de balisage”) dans sa version 1.1, est un format de représentation de données par sérialisation Unicode. Il reprend des concepts d’autres langages comme XML. YAML est facilement à lire et à encoder pour un humain. menu: id: file value: File popup: menuitem: - value: New onclick: CreateNewDoc() - value: Open onclick: OpenDoc() - value: Close onclick: CloseDoc() 1.4. Valider, convertir et travailler avec les formats de données Code Beautify 2. JavaScript Object Notation (JSON) JavaScript Object Notation (JSON) est donc 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. On en trouvera une description dans le RFC 8259. JSON se base sur deux structures: Une collection de couples nom/valeur. Divers langages la réifient par un “objet”, un “enregistrement”, une “structure”, un “dictionnaire”, une “table de hachage”, une “liste typée” ou un “tableau associatif”. Une liste de valeurs ordonnées. La plupart des langages la réifient par un “tableau”, un “vecteur”, une “liste” ou une “suite”. 2.1. Types de données JSON Les types de données de base de JSON sont les suivantes : 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.1 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. La valeur peut être un tableau, un nombre, une chaîne de caractère, ou une valeur boléenne, ou nulle. 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 (“,”). Nombre : est déclaré sans protection des guillemets. Chaîne de caractères (string) : 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 caractère d’échappement (“\”). Booléen : valeur binaire, l’une ou l’autre des valeurs “true” ou “false”, sans guillemets. Nulle : Une valeur vide, en utilisant le mot “null”, sans guillemets. 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.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” est constitué d’un tableau à deux entrées contenant chacune deux clés. { "type": "home", "number": "212 555-1234" }, { "type": "fax", "number": "646 555-4567" } 2.3. Traitement avec jq Le logiciel jq est outil de traitement du texte à la manière de sed mais pour traiter des sorties JSON. ansible localhost -b -m package -a "name=jq state=present" 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 à traiter viennent en 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 :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 la 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" 2.4. 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 2.5. 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 } Quel est le nombre d’objets ? echo $FACTS | jq '. | length' 2 Quels sont ces deux objets ? echo $FACTS | jq '. | keys' [ "ansible_facts", "changed" ] Quels sont les objets imbriqués 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" ] C’est un tableau à trois entrées. Quelle est la seconde entrée ? echo $FACTS | jq '.ansible_facts.ansible_all_ipv4_addresses[1]' 3. Format YAML pour Ansible Pour Ansible, presque tous les fichiers YAML commencent par une liste. Chaque élément de la liste est une liste de paires clé / valeur, communément appelée “hash” ou “dictionary”. Il est donc nécessaire de savoir écrire des listes et des dictionnaires en YAML.2 On ici trouvera le minimum à connaître pour utiliser Ansible avec des fichiers de variables de données. 3.1. Début du fichier Tous les fichiers YAML (indépendamment de leur association avec Ansible ou non) peuvent éventuellement commencer par --- et se terminer par .... Cela fait partie du format YAML et indique le début et la fin d’un document. 3.2. Listes Tous les membres d’une liste sont des lignes commençant au même niveau d’indentation avec un tiret et un espace : - --- ## A list of tasty fruits - Apple - Orange - Strawberry - Mango ... 3.3. Dictionnaires Un dictionnaire est représenté sous une forme simple (les deux points doivent être suivis d’un espace): key: value, clé: valeur ## An employee record martin: name: Martin D'vloper job: Developer skill: Elite La relation entre les données se fait via indentation (2 ou 4 espaces par niveau). Les valeurs de type “string” (texte) peuvent être encadrées par des guillemets " ou des trémas '. La valeurs numérique ne doivent pas être encadrées. 3.4. Dictionnaires et listes Des structures de données plus complexes sont possibles, telles que des listes de dictionnaires, c’est-à-dire des dictionnaires dont les valeurs sont des listes ou un mélange des deux: ## Employee records - martin: name: Martin D'vloper job: Developer skills: - python - perl - pascal - tabitha: name: Tabitha Bitumen job: Developer skills: - lisp - fortran - erlang 3.5. Forme abrégée Les dictionnaires et les listes peuvent également être représentés sous une forme abrégée si vous voulez vraiment: --- martin: {name: Martin D'vloper, job: Developer, skill: Elite} fruits: ['Apple', 'Orange', 'Strawberry', 'Mango'] Celles-ci sont appelées “collections de flux”. 3.6. Valeur booléenne On peut également spécifier une valeur booléenne (true / false) sous plusieurs formes : --- create_key: yes needs_agent: no knows_oop: True likes_emacs: TRUE uses_cvs: false 3.7. Valeurs sur plusieurs lignes Les valeurs peuvent couvrir plusieurs lignes en utilisant | ou >. Le fait de couvrir plusieurs lignes en utilisant un “Literal Block Scalar” | inclura les nouvelles lignes et tous les espaces de fin. L’utilisation d’un “Folded Block Scalar” > pliera les lignes nouvelles dans les espaces; il est utilisé pour rendre ce qui serait autrement une très longue ligne plus facile à lire et à éditer. Dans les deux cas, l’indentation sera ignorée. Les exemples sont: --- include_newlines: | exactly as you see will appear these three lines of poetry fold_newlines: > this is really a single line of text despite appearances Alors que dans l’exemple ci-dessus toutes les nouvelles lignes sont repliées dans des espaces, il existe deux manières de faire respecter une nouvelle ligne à conserver : --- fold_some_newlines: > a b c d e f same_as: "a b\nc d\n e\nf\n" 3.8. Synthèse du format YAML Combinons ce que nous avons appris jusqu’ici dans un exemple YAML arbitraire. Cela n’a vraiment rien à voir avec Ansible, mais cela vous donnera une idée du format de présentation de données à maîtriser. --- ## An employee record name: Martin D'vloper job: Developer skill: Elite employed: True foods: - Apple - Orange - Strawberry - Mango languages: perl: Elite python: Elite pascal: Lame education: | 4 GCSEs 3 A-Levels BSc in the Internet of Things 3.9. Appel aux variables Les variables sont appelées dans les livres de jeu Ansible écrit en YAML en étant encadré par des doubles accolades avec espace (Jinja2). - name: "print ansible_hostname variable play" hosts: all gather_facts: True tasks: - name: "print ansible_hostname variable task" debug: msg: "{{ ansible_hostname }}" … 4. Modélisation Jinja2 Jinja2 Exemple3. apt update && apt install npm npm install -g json2yaml cat << EOF > template.j2 {{ ansible_facts.ansible_hostname }} {% for capability in ansible_facts.ansible_system_capabilities %} {{ capability }} {% endfor %} EOF ansible localhost -m setup -o | sed 's/.*> {/{/g' | json2yaml > facts.yaml ansible localhost -m template -a "src=template.j2 dest=/tmp/file.tmp" -e "@facts.yaml" cat /tmp/file.tmp 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. ↩ YAML Syntax ↩ Voir Ansible snippets - manipulating JSON data et Json parsing in Ansible. ↩