{"id":1715,"date":"2019-12-24T11:35:00","date_gmt":"2019-12-24T06:05:00","guid":{"rendered":"https:\/\/opstree.com\/blog\/\/?p=1715"},"modified":"2020-02-24T17:02:02","modified_gmt":"2020-02-24T11:32:02","slug":"how-to-test-ansible-playbook-role-using-molecule-with-docker","status":"publish","type":"post","link":"https:\/\/opstree.com\/blog\/2019\/12\/24\/how-to-test-ansible-playbook-role-using-molecule-with-docker\/","title":{"rendered":"How to test Ansible playbook\/role using Molecules with Docker"},"content":{"rendered":"\r\n<figure class=\"wp-block-image size-large is-resized\"><a href=\"https:\/\/blog.codecentric.de\/en\/2018\/12\/continuous-infrastructure-ansible-molecule-travisci\/\" target=\"_blank\" rel=\"noopener\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-2193\" src=\"https:\/\/opstree.com\/blog\/\/wp-content\/uploads\/2020\/01\/molecule1.png?w=1024\" alt=\"\" width=\"523\" height=\"294\" \/><\/a><\/figure>\r\n\r\n\r\n\r\n<h2 class=\"wp-block-heading\">Why Molecule?<\/h2>\r\n\r\n\r\n\r\n<p>Have you ever faced issue that your Ansible code gets executed successfully but something went wrong like, service is not started, the configuration is not getting changed, etc?<\/p>\r\n<p><!--more--><\/p>\r\n\r\n\r\n\r\n<p>There is another issue which you might have faced, that your code is running successfully on Redhat 6 but not running successfully on Redhat 7, to make your code smart enough to run on every Linux flavour, in order to achieve this, molecule came into the picture. Let&#8217;s start some brainstorm in Molecule.<\/p>\r\n\r\n\r\n\r\n<p>Molecule has capability to execute YML linter and custom test cases which you have written for your Ansible code. We will explain the linter and test cases below<\/p>\r\n\r\n\r\n\r\n<h2 class=\"wp-block-heading\">Why code testing is required?<\/h2>\r\n\r\n\r\n\r\n<p>Sometimes during the playbook execution, although it executes playbook fine but it does not give us the desired result so in order to check this we should use code testing in Ansible.<\/p>\r\n\r\n\r\n\r\n<p>In general, code testing helps developer to find bugs in code\/application and make sure the same bugs don&#8217;t cause the application to break. it also helps us to deliver application\/software as per standard of code. code testing helps us to increase code stability.<\/p>\r\n\r\n\r\n\r\n<h2 class=\"wp-block-heading\">Introduction :<\/h2>\r\n\r\n\r\n\r\n<p>This whole idea is all about to use Molecule (A testing tool) you can test your Ansible code whether it&#8217;s functioning correctly on all Linux flavour including all its functionalities or not.<\/p>\r\n\r\n\r\n\r\n<p>Molecule is a code linter program that analyses your source code for potential errors. It can detect errors such as syntax errors; structural problems like the use of undefined variables, etc.<\/p>\r\n\r\n\r\n\r\n<p>The molecule has capabilities to create a VM\/Container environment automatically and on top, it will execute your ansible code to verify all its functionalities.<\/p>\r\n\r\n\r\n\r\n<p>Molecule Can also check syntax, idempotency, code quality, etc<\/p>\r\n\r\n\r\n\r\n<p>Molecule only support Ansible 2.2 or latest version<\/p>\r\n\r\n\r\n\r\n<p>NOTE: To run ansible role with the molecule in different OS flavour we can use the cloud, vagrant, containerization (Docker) <br \/>Here we will use Docker\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026<\/p>\r\n\r\n\r\n\r\n<p>Let&#8217;s Start\u2026\u2026\u2026\u2026\u2026<\/p>\r\n\r\n\r\n\r\n<h2 class=\"wp-block-heading\">How Molecule works:<\/h2>\r\n\r\n\r\n\r\n<p>&#8220;When we setup molecule a directory with name &#8220;molecule&#8221; creates inside ansible role directory then it reads it&#8217;s main configuration file &#8220;molecule.yml&#8221; inside molecule directory. Molecule then creates platform (containes\/Instances\/Servers) in your local machine once completed it executes Ansible playbook\/role inside newly created platform after successful execution, it executes test cases. Finally Molecule destroy all newly created platform&#8221;<\/p>\r\n\r\n\r\n\r\n<figure class=\"wp-block-image size-large is-resized\"><a href=\"https:\/\/blog.codecentric.de\/en\/2018\/12\/test-driven-infrastructure-ansible-molecule\/\" target=\"_blank\" rel=\"noopener\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-2054\" src=\"https:\/\/opstree.com\/blog\/\/wp-content\/uploads\/2019\/12\/molecule-1.png?w=900\" alt=\"\" width=\"640\" height=\"162\" \/><\/a><\/figure>\r\n\r\n\r\n\r\n<h2 class=\"wp-block-heading\">Installation of Molecule:<\/h2>\r\n\r\n\r\n\r\n<p>Installation of the molecule is quite simple.<\/p>\r\n\r\n\r\n\r\n<pre class=\"wp-block-syntaxhighlighter-code\">$ sudo apt-get update\r\n$ sudo apt-get install -y python-pip libssl-dev\r\n$ pip install molecule [ Install Molecule ]\r\n$ pip install --upgrade --user setuptools [ do not run in case of VM ]<\/pre>\r\n\r\n\r\n\r\n<p>That&#8217;s it\u2026\u2026\u2026\u2026<\/p>\r\n\r\n\r\n\r\n<p>Now it&#8217;s time to setup Ansible role with the molecule. We have two option to integrate Ansible with molecule:<\/p>\r\n\r\n\r\n\r\n<ol>\r\n<li>With New Ansible role<\/li>\r\n<li>with existing Ansible role<\/li>\r\n<\/ol>\r\n\r\n\r\n\r\n<h2 class=\"wp-block-heading\">1. Setup new ansible role with molecule:<\/h2>\r\n\r\n\r\n\r\n<pre class=\"wp-block-syntaxhighlighter-code\">$ molecule init role --role-name ansible-role-nginx --driver-name docker<\/pre>\r\n\r\n\r\n\r\n<p>When we run above command, a molecule directory will be created inside the ansible role directory<\/p>\r\n\r\n\r\n\r\n<h2 class=\"wp-block-heading\">2. Setup the existing ansible role with molecule:<\/h2>\r\n\r\n\r\n\r\n<p>Goto inside ansible role and run below command.<\/p>\r\n\r\n\r\n\r\n<pre class=\"wp-block-syntaxhighlighter-code\">$ molecule init scenario --driver-name docker<\/pre>\r\n\r\n\r\n\r\n<p>When we run above command, a molecule directory will be created inside the ansible role directory<\/p>\r\n\r\n\r\n\r\n<p>NOTE: Molecule internally uses ansible-galaxy init command to create a role<\/p>\r\n\r\n\r\n\r\n<p>Below is the main configuration file of the molecule:<\/p>\r\n\r\n\r\n\r\n<ul>\r\n<li>molecule.yml &#8211; Contains the definition of OS platform, dependencies, container platform driver, testing tool, etc.<\/li>\r\n<li>playbook.yml &#8211; playbook for executing the role in the vagrant\/Docker<\/li>\r\n<li>tests\/test_default.py | we can write test cases here.<\/li>\r\n<\/ul>\r\n\r\n\r\n\r\n<h2 class=\"wp-block-heading\">Content of molecule.yml<\/h2>\r\n\r\n\r\n\r\n<p>cat molecule\/default\/molecule.yml<\/p>\r\n\r\n\r\n\r\n<pre class=\"wp-block-syntaxhighlighter-code\">---\r\nmolecule:\r\n  ignore_paths:\r\n    - venv\r\n\r\ndependency:\r\n  name: galaxy\r\ndriver:\r\n  name: docker\r\nlint:\r\n  name: yamllint\t\r\nplatforms:\r\n  - name: centos7\r\n    image: centos\/systemd:latest\r\n    privileged: True\r\n  - name: ubuntu16\r\n    image: ubuntu:16.04\r\nprovisioner:\r\n  name: ansible\r\n  lint:\r\n    name: ansible-lint\r\n#    enabled: False\r\nverifier:\r\n  name: testinfra\r\n  lint:\r\n    name: flake8\r\nscenario:\r\n  name: default  # optional\r\n  create_sequence:\r\n    - create\r\n    - prepare\r\n  check_sequence:\r\n    - destroy\r\n    - dependency\r\n    - create<\/pre>\r\n\r\n\r\n\r\n<h1 class=\"wp-block-heading\">Explanation of above contents:<\/h1>\r\n\r\n\r\n\r\n<h3 class=\"wp-block-heading\">Dependency:<\/h3>\r\n\r\n\r\n\r\n<p>Testing roles may rely upon additional dependencies. Molecule handles managing these dependencies by invoking configurable dependency managers.<\/p>\r\n\r\n\r\n\r\n<p>&#8220;Ansible Galaxy&#8221; is the default dependency manager.<\/p>\r\n\r\n\r\n\r\n<h3 class=\"wp-block-heading\">Linter:<\/h3>\r\n\r\n\r\n\r\n<p>A linter is a problem which analyses our code for potential errors.<\/p>\r\n\r\n\r\n\r\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/webdevstudios.com\/2017\/04\/06\/lint-code-like-boss\/\" target=\"_blank\" rel=\"noopener\"><img decoding=\"async\" src=\"https:\/\/webdevstudios.com\/wp-content\/uploads\/2017\/04\/lint-code-like-a-boss.jpg\" alt=\"\" \/><\/a><\/figure>\r\n\r\n\r\n\r\n<h3 class=\"wp-block-heading\">What code linters can do for you?<\/h3>\r\n\r\n\r\n\r\n<p>Code linter can do:<\/p>\r\n\r\n\r\n\r\n<ol>\r\n<li>Syntax errors;<\/li>\r\n<li>Check for undefined variables;<\/li>\r\n<li>Best practice or code style guideline.<\/li>\r\n<li>Extra lines.<\/li>\r\n<li>Extra spaces. etc<\/li>\r\n<\/ol>\r\n\r\n\r\n\r\n<p>**We have linters for almost every programming languages like we have yamllint for YAML languages, etc.<\/p>\r\n\r\n\r\n\r\n<p><strong>yamllint:<\/strong> It checks for syntax validity, key repetition, lines length, trailing spaces, indentation, etc.<\/p>\r\n\r\n\r\n\r\n<p>provisioner: Ansible is the default provisioner. No other provisioner will be supported.<\/p>\r\n\r\n\r\n\r\n<p><strong>Flake8:<\/strong>&#8211; is the default verifier linter. Usage python file<\/p>\r\n\r\n\r\n\r\n<h2 class=\"wp-block-heading\">platforms:<\/h2>\r\n\r\n\r\n\r\n<p>What platform (Containers) will be created and Ansible code will be executed.<\/p>\r\n\r\n\r\n\r\n<h2 class=\"wp-block-heading\">Driver:<\/h2>\r\n\r\n\r\n\r\n<p>Driver defines your platform where your Ansible code will be executed<\/p>\r\n\r\n\r\n\r\n<p><strong>Molecule supports below drivers:<\/strong><\/p>\r\n\r\n\r\n\r\n<ul>\r\n<li>Azure<\/li>\r\n<li>Docker<\/li>\r\n<li>EC2<\/li>\r\n<li>GCE<\/li>\r\n<li>Openstack<\/li>\r\n<li>Vagrant<\/li>\r\n<\/ul>\r\n\r\n\r\n\r\n<h2 class=\"wp-block-heading\"><strong>Scenario<\/strong>:<\/h2>\r\n\r\n\r\n\r\n<p><strong>Scenario <\/strong>&#8211; scenario defines what will be performed when we run molecule<\/p>\r\n\r\n\r\n\r\n<p><strong>Below is the default scenario:<\/strong><\/p>\r\n\r\n\r\n\r\n<p>&#8211;&gt; Test matrix<\/p>\r\n\r\n\r\n\r\n<p>\u2514\u2500\u2500 default<br \/>\u251c\u2500\u2500 lint<br \/>\u251c\u2500\u2500 destroy<br \/>\u251c\u2500\u2500 dependency<br \/>\u251c\u2500\u2500 syntax<br \/>\u251c\u2500\u2500 create<br \/>\u251c\u2500\u2500 prepare<br \/>\u251c\u2500\u2500 converge<br \/>\u251c\u2500\u2500 idempotence<br \/>\u251c\u2500\u2500 side_effect<br \/>\u251c\u2500\u2500 verify<br \/>\u2514\u2500\u2500 destroy<\/p>\r\n\r\n\r\n\r\n<p>However, we can change this scenario and sequence by changing molecule.yml file :<\/p>\r\n\r\n\r\n\r\n<pre class=\"wp-block-syntaxhighlighter-code\">scenario:\r\n  name: default  # optional\r\n  create_sequence:      # molecule create \r\n    - create\r\n    - prepare\r\n  check_sequence:       # molecule check \r\n    - destroy\r\n    - dependency\r\n    - create\r\n    - prepare\r\n    - converge\r\n    - check\r\n    - destroy\r\n  converge_sequence:    # molecule converge \r\n    - dependency\r\n    - create\r\n    - prepare\r\n    - converge\r\n  destroy_sequence:     # molecule destroy \r\n    - cleanup\r\n    - destroy\r\n  test_sequence:        # molecule test \r\n#    - lint\r\n    - cleanup\r\n    - dependency\r\n    - syntax\r\n    - create\r\n    - prepare\r\n    - converge<\/pre>\r\n\r\n\r\n\r\n<p>NOTE: If anyone scenario (action) fails, others will not be executed. this is the default molecule behaviour<\/p>\r\n\r\n\r\n\r\n<h2 class=\"wp-block-heading\">Here I am defining all the scenarios:<\/h2>\r\n\r\n\r\n\r\n<p><strong> lint: <\/strong>Checks all the YAML files with yamllint<\/p>\r\n\r\n\r\n\r\n<p><strong>destroy<\/strong>: If there is already a container running with the same name, destroy that container<\/p>\r\n\r\n\r\n\r\n<p><strong>Dependency<\/strong>: This action allows you to pull dependencies from ansible-galaxy if your role requires them<\/p>\r\n\r\n\r\n\r\n<p><strong> Syntax<\/strong>: Checks the role with ansible-lint<\/p>\r\n\r\n\r\n\r\n<p><strong>create:<\/strong> Creates the Docker image and use that image to start our test container.<\/p>\r\n\r\n\r\n\r\n<p><strong>prepare:<\/strong> This action executes the prepare playbook, which brings the host to a specific state before running converge. This is useful if your role requires a pre-configuration of the system before the role is executed.<\/p>\r\n\r\n\r\n\r\n<p><strong>Example:<\/strong> prepare.yml<\/p>\r\n\r\n\r\n\r\n<pre class=\"wp-block-syntaxhighlighter-code\">---\r\n- name: Prepare\r\n  hosts: all\r\n  gather_facts: false\r\n  tasks:\r\n    - name: Install net-tools curl\r\n      apt: \r\n      \tname: ['curl', 'net-tools']\r\n      \tstate: installed \r\n      when: ansible_os_family == \"Debian\"<\/pre>\r\n\r\n\r\n\r\n<p><strong>NOTE:<\/strong> when we run &#8220;molecule converge&#8221; below task will be performed :<\/p>\r\n\r\n\r\n\r\n<p>====&gt; Create &#8211;&gt; create.yml will be called <br \/>====&gt; Prepare &#8211;&gt; prepare.yml will be called<br \/>====&gt; Provisioning &#8211;&gt; playbook.yml will be called<\/p>\r\n\r\n\r\n\r\n<p><strong>converge:<\/strong> Run the role inside the test container.<\/p>\r\n\r\n\r\n\r\n<p><strong>idempotence:<\/strong> molecule runs the playbook a second time to check for idempotence to make sure no unexpected changes are made in multiple runs:<\/p>\r\n\r\n\r\n\r\n<p><strong>side_effect: <\/strong>Intended to test HA failover scenarios or the like. See Ansible provisioner<\/p>\r\n\r\n\r\n\r\n<p><strong>verify:<\/strong> Run tests inside the container which we have written<\/p>\r\n\r\n\r\n\r\n<p><strong>destroy:<\/strong> Destroys the created container<\/p>\r\n\r\n\r\n\r\n<p><strong>NOTE:<\/strong> When we run molecule commands, a directory with name molecule created inside \/tmp which is molecule managed, which contains ansible configuration, Dockerfile for all linux flavour and ansible inventory<\/p>\r\n\r\n\r\n\r\n<p>cd \/tmp\/molecule<\/p>\r\n\r\n\r\n\r\n<p>tree <br \/>.<br \/>\u2514\u2500\u2500 osm_nginx<br \/>\u2514\u2500\u2500 default<br \/>\u251c\u2500\u2500 ansible.cfg<br \/>\u251c\u2500\u2500 Dockerfile_centos_systemd_latest<br \/>\u251c\u2500\u2500 Dockerfile_ubuntu_16_04<br \/>\u251c\u2500\u2500 inventory<br \/>\u2502 \u2514\u2500\u2500 ansible_inventory.yml<br \/>\u2514\u2500\u2500 state.yml<\/p>\r\n\r\n\r\n\r\n<p><strong>state.yml<\/strong> :- maintain scenario which has been performed .<\/p>\r\n\r\n\r\n\r\n<pre class=\"wp-block-code\"><code>Molecule managed\r\n\r\n---\r\nconverged: true\r\ncreated: true\r\ndriver: docker\r\nprepared: true<\/code><\/pre>\r\n\r\n\r\n\r\n<h2 class=\"wp-block-heading\">Testing:<\/h2>\r\n\r\n\r\n\r\n<p>This is is most important part of Molecule where we will write some test cases.<\/p>\r\n\r\n\r\n\r\n<p>Testinfra is the default test runner.<\/p>\r\n\r\n\r\n\r\n<p>Below module should be installed:<\/p>\r\n\r\n\r\n\r\n<div class=\"wp-block-group\">\r\n<div class=\"wp-block-group__inner-container is-layout-flow wp-block-group-is-layout-flow\">\r\n<ul>\r\n<li>$ <strong>pip install testinfra<\/strong><\/li>\r\n<li>$ <strong>molecule verify<\/strong><\/li>\r\n<\/ul>\r\n<\/div>\r\n<\/div>\r\n\r\n\r\n\r\n<p>Molecule calls below file for unit test using &#8220;testinfra&#8221; verifier<\/p>\r\n\r\n\r\n\r\n<p>molecule\/default\/tests\/test_default.py<\/p>\r\n\r\n\r\n\r\n<h2 class=\"wp-block-heading\">verifier:<\/h2>\r\n\r\n\r\n\r\n<p><strong>Verifier<\/strong> is used for running your test cases.<\/p>\r\n\r\n\r\n\r\n<p>Below are the three verifiers which we can use in Molecule<\/p>\r\n\r\n\r\n\r\n<ul>\r\n<li><strong> testinfra <\/strong>&#8211; It usage python language for writing test cases.<\/li>\r\n<li><strong>goss<\/strong> &#8211; It usage yml language for writing test cases.<\/li>\r\n<li><strong> serverspac <\/strong> &#8211; usage ruby language for writing test cases.<\/li>\r\n<\/ul>\r\n\r\n\r\n\r\n<p>Here I am using <strong>testinfra<\/strong> as verifier for writing test case.<\/p>\r\n\r\n\r\n\r\n<h2 class=\"wp-block-heading\">Molecule commands:<\/h2>\r\n\r\n\r\n\r\n<ul>\r\n<li># molecule check [ Run playbook.yml in check mode ]<\/li>\r\n<li># molecule create [ Create instance\/ Platform]<\/li>\r\n<li># molecule destroy [ destroy instance \/ Platform]<\/li>\r\n<li># molecule verify [ perform unit test ]<\/li>\r\n<li># molecule test [ It performs below default scenario in sequence ]<\/li>\r\n<li># molecule prepare<\/li>\r\n<li>#molecule converge<\/li>\r\n<\/ul>\r\n\r\n\r\n\r\n<p><strong>NOTE<\/strong>: To enable logs to run a command with &#8211;debug flag<\/p>\r\n\r\n\r\n\r\n<p>$ molecule &#8211;debug test<\/p>\r\n\r\n\r\n\r\n<h1 class=\"wp-block-heading\">Sample Test cases :<\/h1>\r\n\r\n\r\n\r\n<figure class=\"wp-block-image size-large is-resized\"><a href=\"https:\/\/www.gcreddy.com\/2017\/07\/writing-sample-test-case.html\" target=\"_blank\" rel=\"noopener\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.gcreddy.com\/wp-content\/uploads\/2018\/08\/Test-Case-Writing.jpg\" alt=\"\" width=\"469\" height=\"263\" \/><\/a><\/figure>\r\n\r\n\r\n\r\n<p>cat molecule\/default\/tests\/test_default.py<\/p>\r\n\r\n\r\n\r\n<div class=\"wp-block-group\">\r\n<div class=\"wp-block-group__inner-container is-layout-flow wp-block-group-is-layout-flow\">\r\n<pre class=\"wp-block-syntaxhighlighter-code\">import os\r\n\r\nimport testinfra.utils.ansible_runner\r\n\r\ntestinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(\r\n    os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('all')\r\n\r\ndef test_user(host):\r\n    user = host.user(\"www-data\")\r\n    assert user.exists\r\n\r\ndef test_nginx_is_installed(host):\r\n    nginx = host.package(\"nginx\")\r\n    assert nginx.is_installed\r\n\r\n\r\ndef test_nginx_running_and_enabled(host):\r\n  os = host.system_info.distribution\r\n  if os == 'debian':\r\n    nginx1 = host.service(\"nginx\")\r\n    assert nginx1.is_running\r\n    assert nginx1.is_enabled\r\n\r\ndef test_nginx_is_listening(host):\r\n    assert host.socket('tcp:\/\/127.0.0.1:80').is_listening<\/pre>\r\n<\/div>\r\n<\/div>\r\n\r\n\r\n\r\n<p>\u00a0That&#8217;s all ! we have covered all required topics which will help you to create your own environment of Molecule and test cases.<\/p>\r\n\r\n\r\n\r\n<p>Thanks all !!! see you soon with new and effective blog \ud83d\ude42<\/p>\r\n\r\n\r\n\r\n<figure class=\"wp-block-image size-large is-resized\"><a href=\"https:\/\/www.pngfuel.com\/free-png\/anvym\" target=\"_blank\" rel=\"noopener\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-2035\" src=\"https:\/\/opstree.com\/blog\/\/wp-content\/uploads\/2019\/12\/good-bye.jpg?w=650\" alt=\"\" width=\"254\" height=\"318\" \/><\/a><\/figure>\r\n\r\n\r\n\r\n<h2 class=\"wp-block-heading\">Links you may refer:<\/h2>\r\n\r\n\r\n\r\n<p><a href=\"https:\/\/yamllint.readthedocs.io\/en\/stable\/\" target=\"_blank\" rel=\"noopener\">https:\/\/yamllint.readthedocs.io\/en\/stable\/<\/a><\/p>\r\n","protected":false},"excerpt":{"rendered":"<p>Why Molecule? Have you ever faced issue that your Ansible code gets executed successfully but something went wrong like, service is not started, the configuration is not getting changed, etc?<\/p>\n","protected":false},"author":173533708,"featured_media":29900,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_coblocks_attr":"","_coblocks_dimensions":"","_coblocks_responsive_height":"","_coblocks_accordion_ie_support":"","jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":{"image_generator_settings":{"template":"highway","enabled":false},"version":2}},"categories":[4011177,28070474,4504191],"tags":[],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"https:\/\/opstree.com\/blog\/wp-content\/uploads\/2025\/11\/DevSecOps-1.jpg","jetpack_likes_enabled":true,"jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/pfDBOm-rF","jetpack-related-posts":[],"_links":{"self":[{"href":"https:\/\/opstree.com\/blog\/wp-json\/wp\/v2\/posts\/1715"}],"collection":[{"href":"https:\/\/opstree.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/opstree.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/opstree.com\/blog\/wp-json\/wp\/v2\/users\/173533708"}],"replies":[{"embeddable":true,"href":"https:\/\/opstree.com\/blog\/wp-json\/wp\/v2\/comments?post=1715"}],"version-history":[{"count":25,"href":"https:\/\/opstree.com\/blog\/wp-json\/wp\/v2\/posts\/1715\/revisions"}],"predecessor-version":[{"id":2368,"href":"https:\/\/opstree.com\/blog\/wp-json\/wp\/v2\/posts\/1715\/revisions\/2368"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/opstree.com\/blog\/wp-json\/wp\/v2\/media\/29900"}],"wp:attachment":[{"href":"https:\/\/opstree.com\/blog\/wp-json\/wp\/v2\/media?parent=1715"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/opstree.com\/blog\/wp-json\/wp\/v2\/categories?post=1715"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/opstree.com\/blog\/wp-json\/wp\/v2\/tags?post=1715"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}