{"id":1495,"date":"2020-12-21T11:42:40","date_gmt":"2020-12-21T19:42:40","guid":{"rendered":"http:\/\/blog.nillsf.com\/?p=1495"},"modified":"2020-12-21T11:42:42","modified_gmt":"2020-12-21T19:42:42","slug":"exploring-the-new-github-actions-continuous-delivery-functionality","status":"publish","type":"post","link":"https:\/\/blog.nillsf.com\/index.php\/2020\/12\/21\/exploring-the-new-github-actions-continuous-delivery-functionality\/","title":{"rendered":"Exploring the new GitHub Actions continuous delivery functionality"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">At GitHub Universe in early December 2020, some new functionalities for GitHub Actions focused on continuous delivery were introduced. In this blog post, we&#8217;ll explore them in the context of a simple web-app deployment to a Kubernetes cluster. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Before diving into what&#8217;s new, let&#8217;s have a look at what GitHub Actions are:<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What are GitHub Actions?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">GitHub Actions are an automation workflow integrated with GitHub repositories. When they were <a href=\"https:\/\/github.blog\/2018-10-17-action-demos\/\">initially launched in 2018<\/a>, Actions were mainly targeted towards workflow automation in\/around your GitHub repo. Since then, Actions has evolved and has grown into a continuous integration and continuous delivery platform. With the new announcements, using actions for continuous delivery has become even better.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Actions are integrated into your GitHub repository. A repository can have multiple Actions associated with it. Each Action is defined as a YAML file that is integrated with your GitHub repo. Each Action can be triggered by one or multiple events on your repo, such as a push to the main branch, a new pull request, a new issue, and a lot of other events.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Within an Action, you define a number of jobs. Each job has a number of steps, where each step is a command or set of commands that can be run. By default, jobs run in paralel, and A step can be as easy as running something on the command line, or it can be executing one of over <a href=\"https:\/\/github.com\/marketplace?type=actions\">6000 Actions from the GitHub marketplac<\/a>e.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">A GitHub Actions example<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">An example GitHub Actions that I used for an earlier blog post <a href=\"https:\/\/github.com\/NillsF\/python-webhook\/blob\/main\/.github\/workflows\/build_and_push.yml\">is contained here<\/a>. Let&#8217;s cover the important steps in that action:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>name: build_and_push\n\non:\n  push:\n    branches: [ main ]\n  pull_request:\n    branches: [ main ]<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">First, we cover the basics. We give the Action a name (in this case build_and_push), and we define when it is run. In this case, we run it on pushes and pull_requests against the main branch.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>jobs:\n  build:\n    runs-on: ubuntu-latest<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Next, we have the definition of the jobs. In this Action, I am only using a single job, but it could have been split up into different jobs. In this case, we&#8217;re defining that we&#8217;re running the Action on <a href=\"https:\/\/docs.github.com\/en\/free-pro-team@latest\/actions\/reference\/workflow-syntax-for-github-actions#github-hosted-runners\">Ubuntu-latest. You could also run it on Windows or MacOS<\/a> as GitHub hosted runners, or you could run the Actions on a self-hosted runner. <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>    steps:\n      - uses: actions\/checkout@v2\n\n      - name: az login\n        run: az login --service-principal --username ${{ secrets.APP_ID }} --password ${{ secrets.PASSWORD }} --tenant ${{ secrets.TENANT_ID }} \n        \n      - name: Build Docker Container\n        run: docker build . -t nfvnas.azurecr.io\/python-admission\/python-admission:${{ github.run_number }}\n\n      - name: ACR login\n        run: az acr login -n nfvnas\n\n      - name: Push container\n        run: docker push nfvnas.azurecr.io\/python-admission\/python-admission:${{ github.run_number }}\n\n      - name: Update deployment\n        run: sed -i 's|nfvnas.azurecr.io\/python-admission\/python-admission:.*|nfvnas.azurecr.io\/python-admission\/python-admission:${{ github.run_number }}|gi' deploy.yaml\n\n      - name: Get AKS credentials\n        run: az aks get-credentials -n win-aks -g win-aks\n\n      - name: Update admission controller\n        run: kubectl delete -f admission.yaml\n        continue-on-error: true\n...<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Finally, we get to the meat and potatoes of the Action: the actual steps. As you can see, most of the steps in my Action are commands I&#8217;m running on the Shell in the GitHub Action. I&#8217;m doing a number of steps involved in building the new image and then deploying it to Kubernetes. You can find <a href=\"https:\/\/github.com\/NillsF\/python-webhook\/blob\/main\/.github\/workflows\/build_and_push.yml\">the full definition of the Action<\/a> on my GitHub repo.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Two things worth pointing out in the steps in the action:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Secrets: To log into the Azure Cloud Shell, I&#8217;m using secrets contained with GitHub actions.<\/li><li>Variables: I&#8217;m accessing the GitHub actions runner count variable. I&#8217;m using that to increment the container image version. Another option would have been to link that the SHA hash of the git commit.<\/li><\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">What&#8217;s new for continuous delivery in GitHub Actions<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The reason I&#8217;m writing this blog post is to cover <a href=\"https:\/\/github.blog\/changelog\/2020-12-15-github-actions-environments-environment-protection-rules-and-environment-secrets-beta\/\">what&#8217;s new for GitHub Actions and continuous delivery<\/a>. GitHub Actions has introduced the capability of environments, which help with continuous delivery on two fronts:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><strong>Environment protection rules<\/strong>: These rules allow you to pause the roll-out of your GitHub Action. You can continue the Action after a set amount of people have reviewed the deployment and\/or a specified wait timer. <\/li><li><strong>Environment secrets:<\/strong> With environments, GitHub actions also introduced environment-specific scoped secrets. As a deployment is running through dev-stage-production, you could have different secret values for each environment, while using the same name for the secret itself.<\/li><\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s have a look at what this means in a practical demo:<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Demo of continuous delivery in GitHub Actions on a Kubernetes cluster<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">For our continuous delivery demo, we will build a very light website showing the version of our software. We&#8217;ll deploy this on a Kubernetes cluster, where a single ingress will distribute the traffic between test and prod. All of this is hosted on <a href=\"https:\/\/github.com\/NillsF\/Actions-CD\">GitHub<\/a>. This is what the architecture looks like (<em>don&#8217;t worry, it&#8217;s less complicated than it looks<\/em>):<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"638\" height=\"443\" src=\"\/wp-content\/uploads\/2020\/12\/architecturel.png\" alt=\"\" class=\"wp-image-1496\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2020\/12\/architecturel.png 638w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2020\/12\/architecturel-300x208.png 300w\" sizes=\"auto, (max-width: 638px) 100vw, 638px\" \/><figcaption>Demo application we&#8217;ll use for explorer GitHub actions environments<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">To focus on the purpose of this blog post, I went ahead and did the following things already:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Created Azure Container Registry and AKS cluster.<\/li><li>Created GitHub actions to build container and push the container to ACR.<\/li><\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">So far, our GitHub action looks like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>name: Build-push-deploy\n\non:\n  push:\n    branches: [ main ]\n\n  workflow_dispatch:\n\njobs:\n  build-push:\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions\/checkout@v2\n        \n      - name: az login\n        run: az login --service-principal --username ${{ secrets.APP_ID }} --password ${{ secrets.PASSWORD }} --tenant ${{ secrets.TENANT_ID }} \n        \n      - name: Build Docker Container\n        run: docker build . -t nfacr.azurecr.io\/webdemo\/webdemo:${{ github.run_number }}\n\n      - name: ACR login\n        run: az acr login -n nfacr\n\n      - name: Push container\n        run: docker push nfacr.azurecr.io\/webdemo\/webdemo:${{ github.run_number }}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s pick things up here by creating the deployment step for the test namespace. First, we&#8217;ll create an environment so I can enforce manual approval:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/nillsfblog.azureedge.net\/gifs\/test-namespace.gif\" alt=\"\"\/><figcaption>Experience creating and setting up the test environment in GitHub.<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">In the GIF above you&#8217;ll see the steps we took to create the environment. Let&#8217;s now also create a job in our GitHub action to use this environment and its manual approval step. <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  deploy-test:\n    needs: build-push\n    runs-on: ubuntu-latest\n    environment: test\n    steps:\n      - uses: actions\/checkout@v2\n\n      - name: az login\n        run: az login --service-principal --username ${{ secrets.APP_ID }} --password ${{ secrets.PASSWORD }} --tenant ${{ secrets.TENANT_ID }} \n\n      - name: get AKS credentials\n        run: az aks get-credentials -n nfaks -g gh-actions\n\n      - name: deploy (will deploy base infra if not exists)\n        run: kubectl create -f test.yaml\n        continue-on-error: true\n\n      - name: update deployment image\n        run: kubectl set image deployment\/test-website -n test website=nfacr.azurecr.io\/webdemo\/webdemo:${{ github.run_number }}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">I committed this to the GitHub repo on the main branch to trigger the GitHub Action. To show the update, I also updated the index.html file to reflect version 2 is live.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Committing this to the main branch has indeed triggered the action, and after a couple seconds for the build step the finish, it also triggered the manual approval:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"533\" src=\"\/wp-content\/uploads\/2020\/12\/image-22-1024x533.png\" alt=\"\" class=\"wp-image-1497\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2020\/12\/image-22-1024x533.png 1024w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2020\/12\/image-22-300x156.png 300w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2020\/12\/image-22-768x400.png 768w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2020\/12\/image-22-1536x800.png 1536w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2020\/12\/image-22.png 1886w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption>Action waiting for manual approval<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">If we hit the review deployments button, we can approve and deploy.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"976\" height=\"637\" src=\"\/wp-content\/uploads\/2020\/12\/image-23.png\" alt=\"\" class=\"wp-image-1498\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2020\/12\/image-23.png 976w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2020\/12\/image-23-300x196.png 300w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2020\/12\/image-23-768x501.png 768w\" sizes=\"auto, (max-width: 976px) 100vw, 976px\" \/><figcaption>Reviewing pending deployments.<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">And if you check in when the deployment is complete, you can track who approved to release to happen:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"572\" src=\"\/wp-content\/uploads\/2020\/12\/image-24-1024x572.png\" alt=\"\" class=\"wp-image-1502\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2020\/12\/image-24-1024x572.png 1024w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2020\/12\/image-24-300x167.png 300w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2020\/12\/image-24-768x429.png 768w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2020\/12\/image-24.png 1161w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption>You can track who approved the release to happen.<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">And the release did actually update the deployment on the Kubernetes cluster:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"545\" height=\"231\" src=\"\/wp-content\/uploads\/2020\/12\/image-25.png\" alt=\"\" class=\"wp-image-1503\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2020\/12\/image-25.png 545w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2020\/12\/image-25-300x127.png 300w\" sizes=\"auto, (max-width: 545px) 100vw, 545px\" \/><figcaption>Version 2 is deployed to test, version 1 is still in prod.<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">We can do the same thing for prod. The action for prod looks very similar to the one from test &#8211; with the only difference being we&#8217;re deploying to prod rather than to test.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  deploy-prod:\n    needs: deploy-test\n    runs-on: ubuntu-latest\n    environment: prod\n    steps:\n      - uses: actions\/checkout@v2\n\n      - name: az login\n        run: az login --service-principal --username ${{ secrets.APP_ID }} --password ${{ secrets.PASSWORD }} --tenant ${{ secrets.TENANT_ID }} \n\n      - name: get AKS credentials\n        run: az aks get-credentials -n nfaks -g gh-actions\n\n      - name: deploy (will deploy base infra if not exists)\n        run: kubectl create -f prod.yaml\n        continue-on-error: true\n\n      - name: update deployment image\n        run: kubectl set image deployment\/prod-website -n prod website=nfacr.azurecr.io\/webdemo\/webdemo:${{ github.run_number }}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Before committing this to git, let&#8217;s also create the second environment:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"863\" height=\"252\" src=\"\/wp-content\/uploads\/2020\/12\/image-26.png\" alt=\"\" class=\"wp-image-1504\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2020\/12\/image-26.png 863w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2020\/12\/image-26-300x88.png 300w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2020\/12\/image-26-768x224.png 768w\" sizes=\"auto, (max-width: 863px) 100vw, 863px\" \/><figcaption>Created the prod environment as well.<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">After creating the prod environment, we can then go ahead and commit our updated action with a HTML update so we can verify the deployment. The first thing that happens, is that we&#8217;re asked to confirm the test deployment:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"868\" height=\"473\" src=\"\/wp-content\/uploads\/2020\/12\/image-27.png\" alt=\"\" class=\"wp-image-1505\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2020\/12\/image-27.png 868w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2020\/12\/image-27-300x163.png 300w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2020\/12\/image-27-768x419.png 768w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2020\/12\/image-27-750x410.png 750w\" sizes=\"auto, (max-width: 868px) 100vw, 868px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">We can approve this similarly to how we did that previously. What I didn&#8217;t realize previously is how there&#8217;s a neat little progress bar to track deployment status. <\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"855\" height=\"289\" src=\"\/wp-content\/uploads\/2020\/12\/image-28.png\" alt=\"\" class=\"wp-image-1506\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2020\/12\/image-28.png 855w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2020\/12\/image-28-300x101.png 300w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2020\/12\/image-28-768x260.png 768w\" sizes=\"auto, (max-width: 855px) 100vw, 855px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Then we wait for the test deployment to finish, and we are asked to review the production deployment:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"856\" height=\"475\" src=\"\/wp-content\/uploads\/2020\/12\/image-29.png\" alt=\"\" class=\"wp-image-1507\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2020\/12\/image-29.png 856w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2020\/12\/image-29-300x166.png 300w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2020\/12\/image-29-768x426.png 768w\" sizes=\"auto, (max-width: 856px) 100vw, 856px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">And after approving the deployment to prod, we can afterwards see the full deployment review and see who approved each release.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"791\" src=\"\/wp-content\/uploads\/2020\/12\/image-30-1024x791.png\" alt=\"\" class=\"wp-image-1508\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2020\/12\/image-30-1024x791.png 1024w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2020\/12\/image-30-300x232.png 300w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2020\/12\/image-30-768x593.png 768w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2020\/12\/image-30.png 1272w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption>You can see the deployment review of each release.<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">And if we check both web pages, both are running on v3 now.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"817\" height=\"348\" src=\"\/wp-content\/uploads\/2020\/12\/image-31.png\" alt=\"\" class=\"wp-image-1509\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2020\/12\/image-31.png 817w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2020\/12\/image-31-300x128.png 300w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2020\/12\/image-31-768x327.png 768w\" sizes=\"auto, (max-width: 817px) 100vw, 817px\" \/><figcaption>Both sites are now running version 3.<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">And that&#8217;s how you can use the deployment review feature in GitHub Actions.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Summary<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">In this post we reviewed the recent updates in GitHub actions related to continuous delivery. The new environments in GitHub actions allow you to request manual approval or do time based delays. We explored the manual approval step in this post.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Another interesting new feature is the ability to have environment-scoped secrets. We didn&#8217;t explore that in this blog post, but with environment-scoped secrets, you could use the same secret name but have different values per environment. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>At GitHub Universe in early December 2020, some new functionalities for GitHub Actions focused on continuous delivery were introduced. In this blog post, we&#8217;ll explore them in the context of a simple web-app deployment to a Kubernetes cluster. Before diving into what&#8217;s new, let&#8217;s have a look at what GitHub Actions are: What are GitHub [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":1512,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[3,58,4,31],"tags":[52,53,147,18],"class_list":["post-1495","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devops","category-kubernetes","category-management","category-software-development","tag-devops","tag-github","tag-github-actions","tag-kubernetes"],"jetpack_featured_media_url":"https:\/\/nillsfblog.blob.core.windows.net\/media\/2020\/12\/2020-12-21-09_46_56-PowerPoint-Slide-Show-Customize-core-dumps-in-Azure-Kubernetes.pptx.png","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/blog.nillsf.com\/index.php\/wp-json\/wp\/v2\/posts\/1495","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.nillsf.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.nillsf.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.nillsf.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.nillsf.com\/index.php\/wp-json\/wp\/v2\/comments?post=1495"}],"version-history":[{"count":2,"href":"https:\/\/blog.nillsf.com\/index.php\/wp-json\/wp\/v2\/posts\/1495\/revisions"}],"predecessor-version":[{"id":1510,"href":"https:\/\/blog.nillsf.com\/index.php\/wp-json\/wp\/v2\/posts\/1495\/revisions\/1510"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.nillsf.com\/index.php\/wp-json\/wp\/v2\/media\/1512"}],"wp:attachment":[{"href":"https:\/\/blog.nillsf.com\/index.php\/wp-json\/wp\/v2\/media?parent=1495"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.nillsf.com\/index.php\/wp-json\/wp\/v2\/categories?post=1495"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.nillsf.com\/index.php\/wp-json\/wp\/v2\/tags?post=1495"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}