<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://taras.pro/feed.xml" rel="self" type="application/atom+xml" /><link href="https://taras.pro/" rel="alternate" type="text/html" /><updated>2025-10-28T19:38:15+00:00</updated><id>https://taras.pro/feed.xml</id><title type="html">Taras Omelianenko 🇺🇦</title><subtitle>Software Engineer, DevOps, and SRE. Ethical hacker &amp; Researcher.</subtitle><author><name>Taras Omelianenko</name><email>inbox@taras.pro</email><uri>https://taras.pro</uri></author><entry><title type="html">Fixing Service Account Key Creation Constraint in Google Cloud Console</title><link href="https://taras.pro/devops/cloud%20infrastructure/Google-Cloud-Service-Account-Key-Creation-Constraint/" rel="alternate" type="text/html" title="Fixing Service Account Key Creation Constraint in Google Cloud Console" /><published>2025-10-28T00:00:00+00:00</published><updated>2025-10-28T00:00:00+00:00</updated><id>https://taras.pro/devops/cloud%20infrastructure/Google-Cloud-Service-Account-Key-Creation-Constraint</id><content type="html" xml:base="https://taras.pro/devops/cloud%20infrastructure/Google-Cloud-Service-Account-Key-Creation-Constraint/"><![CDATA[<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body>
<h1 id="-fixing-service-account-key-creation-constraint-in-google-cloud-console">🔐 Fixing Service Account Key Creation Constraint in Google Cloud Console</h1>

<p>When working with Google Cloud, you might encounter this frustrating error while trying to create a service account key:</p>

<blockquote>
  <p><strong>The organization policy constraint ‘iam.disableServiceAccountKeyCreation’ is enforced. This constraint disables the creation of new service account keys</strong></p>
</blockquote>

<p>This constraint is typically enforced at the organization level as a security measure, but it can block legitimate use cases. In this guide, I’ll walk you through the solution to bypass this policy <strong>without using the CLI</strong>.</p>

<hr>

<h2 id="-why-does-this-happen">🤔 Why Does This Happen?</h2>

<p>Organizations enforce the <code class="language-plaintext highlighter-rouge">iam.disableServiceAccountKeyCreation</code> policy to:</p>

<ul>
  <li>Prevent unauthorized key creation</li>
  <li>Reduce credential sprawl</li>
  <li>Enforce security best practices</li>
</ul>

<p>However, legitimate administrators or service accounts sometimes need to create keys. The solution requires appropriate IAM roles.</p>

<hr>

<h2 id="-solution-step-by-step-guide">✅ Solution: Step-by-Step Guide</h2>

<h3 id="step-1-navigate-to-google-cloud-console">Step 1: Navigate to Google Cloud Console</h3>

<ol>
  <li>Go to <a href="https://console.cloud.google.com" rel="external nofollow" target="_blank">Google Cloud Console</a>
</li>
  <li>Click on the project selector dropdown at the top (showing your current project name)</li>
  <li>Select the <strong>Organization</strong> you need to modify (if you’re at the project level, you need to go to the organization level)</li>
</ol>

<h3 id="step-2-access-more-actions">Step 2: Access More Actions</h3>

<ol>
  <li>Click the <strong>“More Actions”</strong> button (the three vertical dots <code class="language-plaintext highlighter-rouge">⋮</code>) on the right side of the project/organization selector</li>
  <li>A dropdown menu will appear with various options</li>
</ol>

<h3 id="step-3-navigate-to-iam-permissions">Step 3: Navigate to IAM Permissions</h3>

<ol>
  <li>From the dropdown menu, click on <strong>“IAM &amp; Admin”</strong> or <strong>“IAM/PERMISSIONS”</strong>
</li>
  <li>This will take you to the IAM roles management page</li>
</ol>

<h3 id="step-4-assign-necessary-roles">Step 4: Assign Necessary Roles</h3>

<ol>
  <li>Find your user account in the list of members</li>
  <li>Click on the <strong>Edit</strong> button (pencil icon) next to your user</li>
  <li>In the side panel that appears, click <strong>”+ Add Another Role”</strong>
</li>
  <li>Add these two roles:
    <ul>
      <li>
<strong>Organization Policy Administrator</strong> — required to manage organization policies</li>
      <li>
<strong>Organization Administrator</strong> — grants administrative access at the organization level</li>
    </ul>
  </li>
</ol>

<blockquote>
  <p><strong>⚠️ Important:</strong> These roles must be assigned at the <strong>organization level</strong>, not the project level. If you’re at the project level, the “Organization Policy Administrator” option won’t appear in the role list.</p>
</blockquote>

<h3 id="step-5-access-organization-policies">Step 5: Access Organization Policies</h3>

<p>After assigning the roles (wait a few moments for the roles to propagate):</p>

<ol>
  <li>
    <p>Click on <strong>“Organization Policies”</strong> in the left sidebar under <strong>IAM &amp; Admin</strong></p>

    <p><strong>OR</strong></p>

    <p>Repeat Steps 1-2 above and select <strong>“Organization Policies”</strong> from the dropdown menu</p>
  </li>
</ol>

<h3 id="step-6-edit-the-service-account-key-creation-policy">Step 6: Edit the Service Account Key Creation Policy</h3>

<ol>
  <li>On the Organization Policies page, use the search bar to find: <strong>“Disable service account key creation”</strong>
</li>
  <li>Click on the matching policy</li>
  <li>Click the <strong>“Edit Policy”</strong> button</li>
  <li>Modify the rule to either:
    <ul>
      <li>
<strong>Remove the policy entirely</strong> (if you want to allow all service account key creation)</li>
      <li>
<strong>Add exceptions</strong> for specific service accounts or projects</li>
      <li>
<strong>Change the enforcement level</strong> to “advisory only” instead of “enforced”</li>
    </ul>
  </li>
  <li>Click <strong>“Save”</strong> to apply changes</li>
</ol>

<h3 id="step-7-verify-the-fix">Step 7: Verify the Fix</h3>

<ol>
  <li>Navigate back to your project</li>
  <li>Go to <strong>IAM &amp; Admin</strong> → <strong>Service Accounts</strong>
</li>
  <li>Select your service account</li>
  <li>Go to the <strong>Keys</strong> tab</li>
  <li>Click <strong>“Add Key”</strong> → <strong>“Create new key”</strong>
</li>
  <li>You should now be able to create a new key!</li>
</ol>

<hr>

<h2 id="-alternative-approaches">🎯 Alternative Approaches</h2>

<p>If you need a more granular solution:</p>

<h3 id="option-a-exempt-specific-projects">Option A: Exempt Specific Projects</h3>

<p>Instead of disabling the policy entirely, you can add exemptions:</p>

<ol>
  <li>In Organization Policies, edit the “Disable service account key creation” policy</li>
  <li>Under <strong>Enforcement</strong>, add exceptions for specific projects or service accounts</li>
  <li>This allows key creation only where needed</li>
</ol>

<h3 id="option-b-use-service-account-impersonation">Option B: Use Service Account Impersonation</h3>

<p>Instead of creating new keys:</p>

<ol>
  <li>Keep the constraint enabled</li>
  <li>Use <strong>Service Account Impersonation</strong> with short-lived credentials</li>
  <li>Generate access tokens via the <a href="https://cloud.google.com/iam/docs/impersonating-service-accounts" rel="external nofollow" target="_blank">IAM API</a>
</li>
</ol>

<h3 id="option-c-manage-keys-via-terraformiac">Option C: Manage Keys via Terraform/IaC</h3>

<p>If using Infrastructure as Code, the policy might be less restrictive for automated deployments through trusted CI/CD pipelines.</p>

<hr>

<h2 id="-security-best-practices">🔒 Security Best Practices</h2>

<p>Once you’ve enabled service account key creation, remember:</p>

<p>✅ <strong>Limit key creation</strong> — Only create keys when necessary<br>
✅ <strong>Rotate regularly</strong> — Delete unused keys and rotate active ones quarterly<br>
✅ <strong>Use minimal permissions</strong> — Apply principle of least privilege to service accounts<br>
✅ <strong>Monitor key usage</strong> — Check audit logs for service account key usage<br>
✅ <strong>Consider alternatives</strong> — Use Workload Identity or service account impersonation when possible</p>

<hr>

<h2 id="-testing-your-setup">🧪 Testing Your Setup</h2>

<p>After making these changes:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># List service accounts in your project</span>
gcloud iam service-accounts list

<span class="c"># Create a key for a service account (if using gcloud CLI)</span>
gcloud iam service-accounts keys create key.json <span class="se">\</span>
  <span class="nt">--iam-account</span><span class="o">=</span>SERVICE_ACCOUNT_EMAIL@PROJECT_ID.iam.gserviceaccount.com

<span class="c"># Verify key was created</span>
gcloud iam service-accounts keys list <span class="se">\</span>
  <span class="nt">--iam-account</span><span class="o">=</span>SERVICE_ACCOUNT_EMAIL@PROJECT_ID.iam.gserviceaccount.com
</code></pre></div></div>

<hr>

<h2 id="-common-issues">🚨 Common Issues</h2>

<h3 id="organization-policy-administrator-role-not-visible">“Organization Policy Administrator role not visible”</h3>

<ul>
  <li>
<strong>Cause:</strong> You’re at the project level instead of the organization level</li>
  <li>
<strong>Solution:</strong> Make sure you’re assigning roles at the organization level in IAM &amp; Admin</li>
</ul>

<h3 id="permission-denied-after-adding-roles">“Permission denied” after adding roles</h3>

<ul>
  <li>
<strong>Cause:</strong> Role propagation takes time</li>
  <li>
<strong>Solution:</strong> Wait 5-10 minutes and try again, or log out and back in</li>
</ul>

<h3 id="policy-change-not-taking-effect">Policy change not taking effect</h3>

<ul>
  <li>
<strong>Cause:</strong> Cache or role synchronization delay</li>
  <li>
<strong>Solution:</strong> Clear browser cache or use incognito mode to test</li>
</ul>

<hr>

<h2 id="-related-resources">📚 Related Resources</h2>

<ul>
  <li><a href="https://cloud.google.com/resource-manager/docs/organization-policy/overview" rel="external nofollow" target="_blank">Google Cloud Organization Policies Documentation</a></li>
  <li><a href="https://cloud.google.com/docs/authentication/best-practices-applications" rel="external nofollow" target="_blank">Service Account Best Practices</a></li>
  <li><a href="https://cloud.google.com/iam/docs/workload-identity-federation" rel="external nofollow" target="_blank">Workload Identity Federation</a></li>
</ul>

<hr>

<h2 id="-summary">📝 Summary</h2>

<p>The <code class="language-plaintext highlighter-rouge">iam.disableServiceAccountKeyCreation</code> constraint is a powerful security control, but with the right IAM roles, you can manage it when needed:</p>

<ol>
  <li>✅ Assign yourself <strong>Organization Policy Administrator</strong> and <strong>Organization Administrator</strong> roles at the organization level</li>
  <li>✅ Navigate to <strong>Organization Policies</strong>
</li>
  <li>✅ Edit the <strong>“Disable service account key creation”</strong> policy</li>
  <li>✅ Modify the enforcement rule to suit your needs</li>
  <li>✅ Create service account keys as needed</li>
</ol>

<p>Remember: <strong>With great power comes great responsibility</strong> — use this capability carefully and monitor key creation activities!</p>

<hr>

<p><strong>Happy cloud engineering!</strong> ☁️</p>
</body></html>]]></content><author><name>Taras Omelianenko</name><email>inbox@taras.pro</email><uri>https://taras.pro</uri></author><category term="DevOps" /><category term="Cloud Infrastructure" /><category term="Google Cloud" /><category term="GCP" /><category term="Service Account" /><category term="IAM" /><category term="Organization Policy" /><category term="Troubleshooting" /><summary type="html"><![CDATA[🔐 Fixing Service Account Key Creation Constraint in Google Cloud Console]]></summary></entry><entry><title type="html">One Calendar to Rule Them All: Sync Your Private Calendars with Google Apps Script</title><link href="https://taras.pro/productivity/One-Calendar-to-Rule-Them-All-Sync-Your-Private-Calendars-with-Google-Apps-Script/" rel="alternate" type="text/html" title="One Calendar to Rule Them All: Sync Your Private Calendars with Google Apps Script" /><published>2025-07-18T00:00:00+00:00</published><updated>2025-07-18T00:00:00+00:00</updated><id>https://taras.pro/productivity/One-Calendar-to-Rule-Them-All-Sync-Your-Private-Calendars-with-Google-Apps-Script</id><content type="html" xml:base="https://taras.pro/productivity/One-Calendar-to-Rule-Them-All-Sync-Your-Private-Calendars-with-Google-Apps-Script/"><![CDATA[<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body>
<p>Are you juggling multiple Google Calendars? Perhaps one for work, one for personal life, and another for a side project or a team you’re on. It’s a common scenario, but it often leads to a fragmented view of your time, making it easy to double-book yourself or miss important commitments. What if you could see all your busy times in one central place, without having to make all your private calendars public?</p>

<p>This guide will walk you through setting up a powerful Google Apps Script that automatically syncs your free/busy information from several private calendars to a single, master calendar.</p>

<h2 id="why-you-need-this-solution">Why You Need This Solution</h2>

<p>Imagine these common scenarios:</p>

<ul>
  <li>
<strong>The Consultant</strong>: You work with multiple clients, each with their own Google Workspace. You need to block off time on your primary work calendar when you have a meeting in a client’s calendar, but you can’t share your client’s calendar publicly.</li>
  <li>
<strong>The Work-Life Balancer</strong>: You want your colleagues to see when you’re busy with a personal appointment (like a doctor’s visit or a parent-teacher conference) without them seeing the actual details of the event. You need to block off the time on your work calendar as simply “Busy”.</li>
  <li>
<strong>The Team Player</strong>: You’re part of a project team, and you need to share your availability with the project manager. Instead of giving them access to your detailed personal and work calendars, you can provide them with a single calendar that only shows when you are free or busy.</li>
</ul>

<p>This script solves these problems by creating “shadow” events. It looks at your source calendars and, for every event it finds, it creates a corresponding event on your master calendar with a generic title like “Busy”. This way, your availability is accurately reflected everywhere, but the sensitive details of your appointments remain private.</p>

<h2 id="the-synchronization-script">The Synchronization Script</h2>

<p>This is the complete Google Apps Script you will be using. You don’t need to understand every line to use it, but it’s provided here for transparency and for those who wish to customize it further.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
 * @fileoverview Syncs multiple Google Calendars to a single calendar.
 *
 * This script is designed to sync events from multiple source calendars
 * (including private, unshared calendars via iCal URLs) to a single 
 * destination calendar. It only syncs the free/busy status, creating 
 * "Busy" events on the destination calendar.
 *
 * @version 2.0
 */</span>

<span class="c1">// --- START OF CONFIGURATION ---</span>

<span class="kd">const</span> <span class="nx">SOURCE_CALENDARS</span> <span class="o">=</span> <span class="p">[</span>
  <span class="c1">// Example for a shared calendar:</span>
  <span class="c1">// { id: 'work_calendar@your_domain.com', type: 'id' }, </span>
  
  <span class="c1">// Example for a private calendar from another account:</span>
  <span class="c1">// { id: 'SECRET_ICAL_URL_FROM_ANOTHER_ACCOUNT', type: 'ical' }, </span>
<span class="p">];</span>

<span class="kd">const</span> <span class="nx">DESTINATION_CALENDAR_ID</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">YOUR_DESTINATION_CALENDAR_ID@group.calendar.google.com</span><span class="dl">'</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">DAYS_IN_PAST</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">DAYS_IN_FUTURE</span> <span class="o">=</span> <span class="mi">30</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">EVENT_TITLE</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">Busy</span><span class="dl">'</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">SYNC_TAG</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">#gcal_sync</span><span class="dl">'</span><span class="p">;</span>

<span class="c1">// --- END OF CONFIGURATION ---</span>

<span class="kd">function</span> <span class="nx">getSourceCalendar</span><span class="p">(</span><span class="nx">source</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">try</span> <span class="p">{</span>
    <span class="kd">let</span> <span class="nx">calendar</span> <span class="o">=</span> <span class="nx">CalendarApp</span><span class="p">.</span><span class="nx">getCalendarById</span><span class="p">(</span><span class="nx">source</span><span class="p">.</span><span class="nx">id</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">calendar</span> <span class="o">&amp;&amp;</span> <span class="nx">source</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">ical</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">Logger</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Subscribing to iCal calendar: </span><span class="p">${</span><span class="nx">source</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
      <span class="nx">calendar</span> <span class="o">=</span> <span class="nx">CalendarApp</span><span class="p">.</span><span class="nx">subscribeToCalendar</span><span class="p">(</span><span class="nx">source</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span> <span class="p">{</span> <span class="na">hidden</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="na">selected</span><span class="p">:</span> <span class="kc">false</span> <span class="p">});</span>
    <span class="p">}</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">calendar</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">Logger</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Could not find or subscribe to calendar with ID: </span><span class="p">${</span><span class="nx">source</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
      <span class="k">return</span> <span class="kc">null</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="nx">calendar</span><span class="p">;</span>
  <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">Logger</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Error accessing or subscribing to calendar </span><span class="p">${</span><span class="nx">source</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span><span class="s2">: </span><span class="p">${</span><span class="nx">e</span><span class="p">.</span><span class="nx">toString</span><span class="p">()}</span><span class="s2">`</span><span class="p">);</span>
    <span class="k">return</span> <span class="kc">null</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nx">syncCalendars</span><span class="p">()</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">now</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">();</span>
  <span class="kd">const</span> <span class="nx">startDate</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="nx">now</span><span class="p">.</span><span class="nx">getTime</span><span class="p">()</span> <span class="o">-</span> <span class="nx">DAYS_IN_PAST</span> <span class="o">*</span> <span class="mi">24</span> <span class="o">*</span> <span class="mi">60</span> <span class="o">*</span> <span class="mi">60</span> <span class="o">*</span> <span class="mi">1000</span><span class="p">);</span>
  <span class="kd">const</span> <span class="nx">endDate</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="nx">now</span><span class="p">.</span><span class="nx">getTime</span><span class="p">()</span> <span class="o">+</span> <span class="nx">DAYS_IN_FUTURE</span> <span class="o">*</span> <span class="mi">24</span> <span class="o">*</span> <span class="mi">60</span> <span class="o">*</span> <span class="mi">60</span> <span class="o">*</span> <span class="mi">1000</span><span class="p">);</span>
  <span class="kd">const</span> <span class="nx">destinationCalendar</span> <span class="o">=</span> <span class="nx">CalendarApp</span><span class="p">.</span><span class="nx">getCalendarById</span><span class="p">(</span><span class="nx">DESTINATION_CALENDAR_ID</span><span class="p">);</span>

  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">destinationCalendar</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">Logger</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Destination calendar not found.</span><span class="dl">'</span><span class="p">);</span>
    <span class="k">return</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="nx">deleteSyncedEvents</span><span class="p">(</span><span class="nx">destinationCalendar</span><span class="p">,</span> <span class="nx">startDate</span><span class="p">,</span> <span class="nx">endDate</span><span class="p">);</span>

  <span class="nx">SOURCE_CALENDARS</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">source</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">sourceCalendar</span> <span class="o">=</span> <span class="nx">getSourceCalendar</span><span class="p">(</span><span class="nx">source</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">sourceCalendar</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">syncSingleCalendar</span><span class="p">(</span><span class="nx">sourceCalendar</span><span class="p">,</span> <span class="nx">destinationCalendar</span><span class="p">,</span> <span class="nx">startDate</span><span class="p">,</span> <span class="nx">endDate</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
      <span class="nx">Logger</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Skipping source calendar: </span><span class="p">${</span><span class="nx">source</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
    <span class="p">}</span>
  <span class="p">});</span>

  <span class="nx">Logger</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Calendar sync complete.</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nx">deleteSyncedEvents</span><span class="p">(</span><span class="nx">calendar</span><span class="p">,</span> <span class="nx">startDate</span><span class="p">,</span> <span class="nx">endDate</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">syncedEvents</span> <span class="o">=</span> <span class="nx">calendar</span><span class="p">.</span><span class="nx">getEvents</span><span class="p">(</span><span class="nx">startDate</span><span class="p">,</span> <span class="nx">endDate</span><span class="p">,</span> <span class="p">{</span> <span class="na">search</span><span class="p">:</span> <span class="nx">SYNC_TAG</span> <span class="p">});</span>
  <span class="nx">syncedEvents</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">event</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="k">try</span> <span class="p">{</span>
      <span class="nx">event</span><span class="p">.</span><span class="nx">deleteEvent</span><span class="p">();</span>
    <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">Logger</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Error deleting event: </span><span class="p">${</span><span class="nx">e</span><span class="p">.</span><span class="nx">toString</span><span class="p">()}</span><span class="s2">`</span><span class="p">);</span>
    <span class="p">}</span>
  <span class="p">});</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nx">syncSingleCalendar</span><span class="p">(</span><span class="nx">sourceCalendar</span><span class="p">,</span> <span class="nx">destinationCalendar</span><span class="p">,</span> <span class="nx">startDate</span><span class="p">,</span> <span class="nx">endDate</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">events</span> <span class="o">=</span> <span class="nx">sourceCalendar</span><span class="p">.</span><span class="nx">getEvents</span><span class="p">(</span><span class="nx">startDate</span><span class="p">,</span> <span class="nx">endDate</span><span class="p">);</span>
  <span class="nx">events</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">event</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">event</span><span class="p">.</span><span class="nx">getMyStatus</span><span class="p">()</span> <span class="o">===</span> <span class="nx">CalendarApp</span><span class="p">.</span><span class="nx">GuestStatus</span><span class="p">.</span><span class="nx">NO</span> <span class="o">||</span> <span class="nx">event</span><span class="p">.</span><span class="nx">isAllDayEvent</span><span class="p">())</span> <span class="p">{</span>
      <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">try</span> <span class="p">{</span>
      <span class="nx">destinationCalendar</span><span class="p">.</span><span class="nx">createEvent</span><span class="p">(</span>
        <span class="nx">EVENT_TITLE</span><span class="p">,</span>
        <span class="nx">event</span><span class="p">.</span><span class="nx">getStartTime</span><span class="p">(),</span>
        <span class="nx">event</span><span class="p">.</span><span class="nx">getEndTime</span><span class="p">(),</span>
        <span class="p">{</span> <span class="na">description</span><span class="p">:</span> <span class="s2">`Synced from </span><span class="p">${</span><span class="nx">sourceCalendar</span><span class="p">.</span><span class="nx">getName</span><span class="p">()}</span><span class="s2">\n</span><span class="p">${</span><span class="nx">SYNC_TAG</span><span class="p">}</span><span class="s2">`</span> <span class="p">}</span>
      <span class="p">).</span><span class="nx">setVisibility</span><span class="p">(</span><span class="nx">CalendarApp</span><span class="p">.</span><span class="nx">Visibility</span><span class="p">.</span><span class="nx">PRIVATE</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">Logger</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Error creating event: </span><span class="p">${</span><span class="nx">e</span><span class="p">.</span><span class="nx">toString</span><span class="p">()}</span><span class="s2">`</span><span class="p">);</span>
    <span class="p">}</span>
  <span class="p">});</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nx">createTrigger</span><span class="p">()</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">allTriggers</span> <span class="o">=</span> <span class="nx">ScriptApp</span><span class="p">.</span><span class="nx">getProjectTriggers</span><span class="p">();</span>
  <span class="nx">allTriggers</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">trigger</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">trigger</span><span class="p">.</span><span class="nx">getHandlerFunction</span><span class="p">()</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">syncCalendars</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">ScriptApp</span><span class="p">.</span><span class="nx">deleteTrigger</span><span class="p">(</span><span class="nx">trigger</span><span class="p">);</span>
    <span class="p">}</span>
  <span class="p">});</span>
  <span class="nx">ScriptApp</span><span class="p">.</span><span class="nx">newTrigger</span><span class="p">(</span><span class="dl">'</span><span class="s1">syncCalendars</span><span class="dl">'</span><span class="p">).</span><span class="nx">timeBased</span><span class="p">().</span><span class="nx">everyHours</span><span class="p">(</span><span class="mi">1</span><span class="p">).</span><span class="nx">create</span><span class="p">();</span>
  <span class="nx">Logger</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Trigger created successfully.</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="step-by-step-configuration-guide">Step-by-Step Configuration Guide</h2>

<p>Here’s how to set up your automated calendar synchronization.</p>

<h3 id="step-1-open-google-apps-script-and-paste-the-code">Step 1: Open Google Apps Script and Paste the Code</h3>

<ol>
  <li>Navigate to <a href="https://script.google.com" rel="external nofollow" target="_blank">script.google.com</a> and click <strong>New project</strong>.</li>
  <li>Delete any placeholder code in the <code class="language-plaintext highlighter-rouge">Code.gs</code> file.</li>
  <li>Copy the entire script from the section above and paste it into the editor.</li>
  <li>Click the save icon and give your project a name, such as “Master Calendar Sync”.</li>
</ol>

<h3 id="step-2-share-your-source-calendars">Step 2: Share Your Source Calendars</h3>

<p>For the script to read events from calendars you own (or have direct access to), you must share them with the Google account that will run the script. You only need to do this for calendars you configure with <code class="language-plaintext highlighter-rouge">type: 'id'</code>. You can skip this for calendars you are syncing via the secret iCal URL.</p>

<ol>
  <li>Open Google Calendar.</li>
  <li>On the left, find a source calendar you want to sync. Hover over it and click the three-dot menu (⋮).</li>
  <li>Select <strong>Settings and sharing</strong>.</li>
  <li>Under the <strong>Share with specific people or groups</strong> section, click <strong>Add people and groups</strong>.</li>
  <li>Enter the email address of the Google account where you are setting up this script.</li>
  <li>In the permissions dropdown, select <strong>See all event details</strong>.</li>
  <li>Click <strong>Send</strong>. On you destination account, open received Email and click <strong>Join shared calendar</strong>. Add calendar in new window.</li>
  <li>Repeat this process for all source calendars that you are syncing by their ID.</li>
</ol>

<h3 id="step-3-configure-your-calendars-in-the-script">Step 3: Configure Your Calendars in the Script</h3>

<p>This is the most crucial part. You need to tell the script which calendars to read from and which one to write to by editing the CONFIGURATION section at the top of the script.</p>

<p>Locate the <code class="language-plaintext highlighter-rouge">SOURCE_CALENDARS</code> array. This is where you’ll list all the calendars you want to sync from. You can add two types:</p>

<p><strong>Calendars You Own or Have Access To (id)</strong>: For your primary calendar or any calendar shared with your Google account as described in Step 2.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span> <span class="nl">id</span><span class="p">:</span> <span class="dl">'</span><span class="s1">your.email@gmail.com</span><span class="dl">'</span><span class="p">,</span> <span class="nx">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">id</span><span class="dl">'</span> <span class="p">}</span>
</code></pre></div></div>

<p><strong>Private, Unshared Calendars (ical)</strong>: For calendars from other accounts (like a client’s or a separate personal account).</p>

<p>How to get the iCal URL:</p>

<ol>
  <li>In the Google account that owns the private calendar, open Google Calendar.</li>
  <li>Find the calendar on the left, click the three-dot menu (⋮), and select <strong>Settings and sharing</strong>.</li>
  <li>Scroll to <strong>Integrate calendar</strong> and copy the URL under <strong>Secret address in iCal format</strong>.</li>
  <li>Add it to the script like this:</li>
</ol>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span> <span class="nl">id</span><span class="p">:</span> <span class="dl">'</span><span class="s1">https://calendar.google.com/calendar/ical/..../private-..../basic.ics</span><span class="dl">'</span><span class="p">,</span> <span class="nx">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">ical</span><span class="dl">'</span> <span class="p">}</span>
</code></pre></div></div>

<p><strong>Set Your Destination Calendar:</strong></p>

<ol>
  <li>Find the <code class="language-plaintext highlighter-rouge">DESTINATION_CALENDAR_ID</code> variable.</li>
  <li>Replace the placeholder value with the ID of the calendar you want all the “Busy” events to be created on. It’s recommended to create a new, separate calendar for this purpose.</li>
</ol>

<h3 id="step-4-for-google-workspace-users-administrator-steps">Step 4: For Google Workspace Users (Administrator Steps)</h3>

<p>If you are using a Google Workspace account (e.g., you@yourcompany.com) and can’t find the “Secret address in iCal format” for a private calendar, your administrator has likely disabled external sharing. You will need to ask your Google Workspace administrator to perform the following steps:</p>

<ol>
  <li>Log in to the Google Admin console at <a href="https://admin.google.com" rel="external nofollow" target="_blank">admin.google.com</a>.</li>
  <li>Navigate to <strong>Apps &gt; Google Workspace &gt; Calendar</strong>.</li>
  <li>Click on <strong>Sharing settings</strong>.</li>
  <li>In the <strong>External Sharing options</strong> section, choose a setting that allows sharing information. The recommended setting for this use case is “Share all information, but outsiders cannot change calendars”.</li>
  <li>Click <strong>Save</strong>.</li>
</ol>

<p>After the administrator makes this change (it may take a few minutes to apply), the “Secret address in iCal format” will become available in your calendar’s settings page.</p>

<h3 id="step-5-first-run-and-authorization">Step 5: First Run and Authorization</h3>

<ol>
  <li>In the Apps Script editor toolbar, ensure the <code class="language-plaintext highlighter-rouge">syncCalendars</code> function is selected in the dropdown menu.</li>
  <li>Click <strong>Run</strong>.</li>
  <li>A popup will appear asking for authorization. Click <strong>Review permissions</strong>.</li>
  <li>Choose your Google account.</li>
  <li>You’ll likely see a screen saying “Google hasn’t verified this app”. This is normal for personal scripts. Click <strong>Advanced</strong>, and then click <strong>Go to (your project name) (unsafe)</strong>.</li>
  <li>Click <strong>Allow</strong> to grant the script the necessary permissions to manage your calendars.</li>
  <li>The script will now run for the first time, syncing your calendars.</li>
</ol>

<h3 id="step-6-set-up-automatic-syncing">Step 6: Set Up Automatic Syncing</h3>

<p>To keep your master calendar up-to-date automatically, you need to create a trigger.</p>

<ol>
  <li>In the Apps Script editor, select the <code class="language-plaintext highlighter-rouge">createTrigger</code> function from the dropdown menu.</li>
  <li>Click <strong>Run</strong>. You’ll see a confirmation log once the trigger is created.</li>
</ol>

<p>That’s it! The script will now run every hour, deleting the old “Busy” events and creating new ones to perfectly reflect the current state of all your source calendars. You now have a single, reliable view of your true availability across all your commitments.</p>
</body></html>]]></content><author><name>Taras Omelianenko</name><email>inbox@taras.pro</email><uri>https://taras.pro</uri></author><category term="Productivity" /><category term="Google Calendar" /><category term="Google Apps Script" /><category term="Automation" /><category term="Productivity" /><summary type="html"><![CDATA[Are you juggling multiple Google Calendars? Perhaps one for work, one for personal life, and another for a side project or a team you’re on. It’s a common scenario, but it often leads to a fragmented view of your time, making it easy to double-book yourself or miss important commitments. What if you could see all your busy times in one central place, without having to make all your private calendars public?]]></summary></entry><entry><title type="html">My Scientific Journey: From Idea to Insights with AI</title><link href="https://taras.pro/science/Scientific-workflow/" rel="alternate" type="text/html" title="My Scientific Journey: From Idea to Insights with AI" /><published>2025-07-12T00:00:00+00:00</published><updated>2025-07-12T00:00:00+00:00</updated><id>https://taras.pro/science/Scientific-workflow</id><content type="html" xml:base="https://taras.pro/science/Scientific-workflow/"><![CDATA[<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body>
<p>Scientific research is an exciting but complex process. It requires structure, creativity, and careful attention to detail. In this article, I describe my own scientific “flow” — the sequence of steps I follow, and show how artificial intelligence (AI) tools can dramatically improve each stage.</p>

<h2 id="1-my-personal-scientific-flow">1. My Personal Scientific Flow</h2>

<p>My approach to research can be represented as five main stages. Each step logically follows from the previous one, creating a cohesive process — from the birth of an idea to final conclusions.</p>

<p>Here’s what this scheme looks like:</p>

<pre><code class="language-mermaid">graph TD;
    A[Stage 1: Topic and Hypothesis Formulation] --&gt; B[Stage 2: Deep Literature Review];
    B --&gt; C[Stage 3: Research Planning and Data Collection];
    C --&gt; D[Stage 4: Data Analysis];
    D --&gt; E[Stage 5: Interpretation, Writing, and Publication];
</code></pre>

<h2 id="2-ai-application-points-in-my-flow">2. AI Application Points in My Flow</h2>

<p>AI is not a replacement for the researcher, but a powerful assistant that automates routine tasks, accelerates analysis, and helps identify non-obvious connections. Let’s examine its role at each stage.</p>

<h3 id="stage-1-topic-and-hypothesis-formulation"><strong>Stage 1: Topic and Hypothesis Formulation</strong></h3>

<p>At this stage, the main task is to find a relevant and not fully studied problem.</p>

<ul>
  <li>
<strong>Tool/Approach:</strong> Generative models (ChatGPT, Gemini), trend analysis tools (Scite, ResearchRabbit).</li>
  <li>
<strong>Tasks:</strong> Brainstorming, searching for “white spots” in research, formulating initial hypotheses. You can ask the model: “What are the open questions in the field of [your field] at the intersection with [another field]?”</li>
  <li>
<strong>Benefits:</strong> <strong>Acceleration.</strong> AI quickly generates dozens of ideas and summarizes the latest publications, which would take days to do manually.</li>
</ul>

<h3 id="stage-2-deep-literature-review"><strong>Stage 2: Deep Literature Review</strong></h3>

<p>Traditional literature review is one of the most labor-intensive stages.</p>

<ul>
  <li>
<strong>Tool/Approach:</strong> Retrieval-Augmented Generation (RAG) systems, such as Elicit, Consensus, or specialized AI tools for scientists.</li>
  <li>
<strong>Tasks:</strong> Quick search, summarization, and systematization of dozens and hundreds of scientific articles. A RAG system can answer a specific question (“Which treatment methods for disease X showed the highest effectiveness in clinical trials?”), providing a summarized answer with source references.</li>
  <li>
<strong>Benefits:</strong> <strong>Depth of analysis and automation.</strong> Instead of reading 100 articles, you can analyze their key findings in an hour. This frees up time for critical thinking.</li>
</ul>

<h3 id="stage-3-research-planning-and-data-collection"><strong>Stage 3: Research Planning and Data Collection</strong></h3>

<p>Proper experimental or research design is the key to reliable results.</p>

<ul>
  <li>
<strong>Tool/Approach:</strong> AI assistants (Copilot), statistical simulators.</li>
  <li>
<strong>Tasks:</strong> Help in choosing appropriate statistical methods, calculating required sample size, generating code for data collection from open sources (parsing).</li>
  <li>
<strong>Benefits:</strong> <strong>Accuracy and efficiency.</strong> AI helps avoid common methodological errors and automates data collection.</li>
</ul>

<h3 id="stage-4-data-analysis"><strong>Stage 4: Data Analysis</strong></h3>

<p>This is the area where AI and machine learning (ML) reveal their full potential.</p>

<ul>
  <li>
<strong>Tool/Approach:</strong> ML models (regression, classification, clustering), Python libraries (Pandas, Scikit-learn), AI tools for visualization.</li>
  <li>
<strong>Tasks:</strong> Identifying complex, non-linear dependencies in data that are difficult to notice with traditional statistical methods. Building predictive models.</li>
  <li>
<strong>Benefits:</strong> <strong>Deeper analysis.</strong> ML can find hidden patterns in large datasets, leading to new scientific discoveries.</li>
</ul>

<h3 id="stage-5-interpretation-writing-and-publication"><strong>Stage 5: Interpretation, Writing, and Publication</strong></h3>

<p>Writing a scientific article requires clarity, logic, and adherence to academic style.</p>

<ul>
  <li>
<strong>Tool/Approach:</strong> Generative models (GPT for drafts), grammar and style checking tools (Grammarly), AI assistants for translation and formatting.</li>
  <li>
<strong>Tasks:</strong> Creating drafts of individual sections (introduction, methods description), rephrasing sentences for clarity, checking text for plagiarism, preparing abstracts.</li>
  <li>
<strong>Benefits:</strong> <strong>Speed and quality.</strong> AI significantly accelerates the writing process, allowing focus on content rather than form.</li>
</ul>

<h2 id="3--4-practical-example-dataset-analysis-in-google-colab">3. &amp; 4. Practical Example: Dataset Analysis in Google Colab</h2>

<p>For demonstration, I took the classic <strong>“Wine Quality”</strong> dataset from the UCI repository. It contains chemical characteristics of white wine and its quality rating on a 10-point scale.</p>

<p><strong>Task:</strong> Calculate basic descriptive statistics and visualize the distribution of wine quality to understand which ratings are most common.</p>

<p>Here’s the code that can be executed in Google Colab:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Import necessary libraries
</span><span class="kn">import</span> <span class="nn">pandas</span> <span class="k">as</span> <span class="n">pd</span>
<span class="kn">import</span> <span class="nn">matplotlib.pyplot</span> <span class="k">as</span> <span class="n">plt</span>
<span class="kn">import</span> <span class="nn">seaborn</span> <span class="k">as</span> <span class="n">sns</span>

<span class="c1"># URL to the white wine dataset
</span><span class="n">url</span> <span class="o">=</span> <span class="s">'https://archive.ics.uci.edu/ml/machine-learning-databases/wine-quality/winequality-white.csv'</span>

<span class="c1"># Load data into DataFrame, using semicolon as separator
</span><span class="k">try</span><span class="p">:</span>
    <span class="n">wine_df</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="n">read_csv</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">sep</span><span class="o">=</span><span class="s">';'</span><span class="p">)</span>
    <span class="k">print</span><span class="p">(</span><span class="s">"Data loaded successfully."</span><span class="p">)</span>
<span class="k">except</span> <span class="nb">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"Data loading error: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>

<span class="c1"># Display first 5 rows for familiarization
</span><span class="k">print</span><span class="p">(</span><span class="s">"First 5 rows of data:"</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">wine_df</span><span class="p">.</span><span class="n">head</span><span class="p">())</span>

<span class="c1"># 1. Calculate descriptive statistics for all indicators
</span><span class="k">print</span><span class="p">(</span><span class="s">"</span><span class="se">\n</span><span class="s">Descriptive statistics:"</span><span class="p">)</span>
<span class="c1"># .T transposes the output, making it more readable
</span><span class="k">print</span><span class="p">(</span><span class="n">wine_df</span><span class="p">.</span><span class="n">describe</span><span class="p">().</span><span class="n">T</span><span class="p">)</span>

<span class="c1"># 2. Calculate mode for quality indicator
</span><span class="n">quality_mode</span> <span class="o">=</span> <span class="n">wine_df</span><span class="p">[</span><span class="s">'quality'</span><span class="p">].</span><span class="n">mode</span><span class="p">()[</span><span class="mi">0</span><span class="p">]</span>
<span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"</span><span class="se">\n</span><span class="s">Mode for 'quality' indicator: </span><span class="si">{</span><span class="n">quality_mode</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>

<span class="c1"># 3. Build visualization - histogram of wine quality distribution
</span><span class="n">plt</span><span class="p">.</span><span class="n">style</span><span class="p">.</span><span class="n">use</span><span class="p">(</span><span class="s">'seaborn-v0_8-whitegrid'</span><span class="p">)</span> <span class="c1"># Use modern style
</span><span class="n">fig</span><span class="p">,</span> <span class="n">ax</span> <span class="o">=</span> <span class="n">plt</span><span class="p">.</span><span class="n">subplots</span><span class="p">(</span><span class="n">figsize</span><span class="o">=</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span> <span class="mi">6</span><span class="p">))</span>

<span class="n">sns</span><span class="p">.</span><span class="n">histplot</span><span class="p">(</span><span class="n">wine_df</span><span class="p">[</span><span class="s">'quality'</span><span class="p">],</span> <span class="n">bins</span><span class="o">=</span><span class="mi">6</span><span class="p">,</span> <span class="n">kde</span><span class="o">=</span><span class="bp">False</span><span class="p">,</span> <span class="n">ax</span><span class="o">=</span><span class="n">ax</span><span class="p">,</span> <span class="n">color</span><span class="o">=</span><span class="s">'skyblue'</span><span class="p">,</span> <span class="n">discrete</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>

<span class="n">ax</span><span class="p">.</span><span class="n">set_title</span><span class="p">(</span><span class="s">'Distribution of White Wine Quality Ratings'</span><span class="p">,</span> <span class="n">fontsize</span><span class="o">=</span><span class="mi">16</span><span class="p">)</span>
<span class="n">ax</span><span class="p">.</span><span class="n">set_xlabel</span><span class="p">(</span><span class="s">'Quality Rating (from 3 to 9)'</span><span class="p">,</span> <span class="n">fontsize</span><span class="o">=</span><span class="mi">12</span><span class="p">)</span>
<span class="n">ax</span><span class="p">.</span><span class="n">set_ylabel</span><span class="p">(</span><span class="s">'Number of Samples'</span><span class="p">,</span> <span class="n">fontsize</span><span class="o">=</span><span class="mi">12</span><span class="p">)</span>
<span class="n">ax</span><span class="p">.</span><span class="n">set_xticks</span><span class="p">(</span><span class="nb">range</span><span class="p">(</span><span class="n">wine_df</span><span class="p">[</span><span class="s">'quality'</span><span class="p">].</span><span class="nb">min</span><span class="p">(),</span> <span class="n">wine_df</span><span class="p">[</span><span class="s">'quality'</span><span class="p">].</span><span class="nb">max</span><span class="p">()</span> <span class="o">+</span> <span class="mi">1</span><span class="p">))</span> <span class="c1"># Clear marks on X axis
</span>
<span class="c1"># Add labels with counts on each bar
</span><span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">ax</span><span class="p">.</span><span class="n">patches</span><span class="p">:</span>
    <span class="n">ax</span><span class="p">.</span><span class="n">annotate</span><span class="p">(</span><span class="sa">f</span><span class="s">'</span><span class="si">{</span><span class="nb">int</span><span class="p">(</span><span class="n">p</span><span class="p">.</span><span class="n">get_height</span><span class="p">())</span><span class="si">}</span><span class="s">'</span><span class="p">,</span> <span class="p">(</span><span class="n">p</span><span class="p">.</span><span class="n">get_x</span><span class="p">()</span> <span class="o">+</span> <span class="n">p</span><span class="p">.</span><span class="n">get_width</span><span class="p">()</span> <span class="o">/</span> <span class="mf">2.</span><span class="p">,</span> <span class="n">p</span><span class="p">.</span><span class="n">get_height</span><span class="p">()),</span>
                <span class="n">ha</span><span class="o">=</span><span class="s">'center'</span><span class="p">,</span> <span class="n">va</span><span class="o">=</span><span class="s">'center'</span><span class="p">,</span> <span class="n">xytext</span><span class="o">=</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">9</span><span class="p">),</span> <span class="n">textcoords</span><span class="o">=</span><span class="s">'offset points'</span><span class="p">)</span>

<span class="n">plt</span><span class="p">.</span><span class="n">show</span><span class="p">()</span>

</code></pre></div></div>

<p><img src="/assets/posts/2023-07-12-Scientific-workflow/diagram.png" alt="Distribution of White Wine Quality Ratings"></p>

<p><a href="/assets/posts/2023-07-12-Scientific-workflow/2023-07-12-Scientific-workflow.pdf">Download PDF with Jupyter notebook execution results</a></p>

<h2 id="5-brief-interpretation-of-results">5. Brief Interpretation of Results</h2>

<p>Analysis of the provided data allows us to draw several key conclusions:</p>

<ol>
  <li>
    <p><strong>Central tendencies:</strong></p>

    <ul>
      <li>
<strong>Mean value</strong> of wine quality is approximately <strong>5.88</strong>.</li>
      <li>
<strong>Median (50%)</strong> equals <strong>6</strong>. This means that half of the wines have a rating of 6 or lower, and the other half — 6 or higher.</li>
      <li>
<strong>Mode</strong>, or the most frequent rating, also equals <strong>6</strong>.</li>
    </ul>
  </li>
  <li>
    <p><strong>Data spread:</strong></p>

    <ul>
      <li>
<strong>Standard deviation (std)</strong> for quality is <strong>0.88</strong>. This indicates that most ratings are grouped quite close to the mean value (mainly in the 5-7 range).</li>
      <li>Quality ratings range from <strong>3 (min)</strong> to <strong>9 (max)</strong>. There are no wines with the highest (10) or lowest (1/2) ratings in the dataset.</li>
    </ul>
  </li>
  <li>
    <p><strong>Conclusions from visualization:</strong></p>

    <ul>
      <li>The histogram clearly shows that the distribution of quality ratings is not uniform. It resembles a <strong>normal distribution</strong> with a left skew.</li>
      <li>
<strong>The vast majority of wines (2198 samples)</strong> have a rating of <strong>6</strong>. Second place goes to wines with a rating of <strong>5</strong> (1457 samples).</li>
      <li>Wines with very high (8, 9) or very low (3, 4) ratings are significantly fewer.</li>
    </ul>
  </li>
</ol>

<p><strong>General conclusion:</strong> Based on this statistics, we can assert that most white wines in this dataset are of “average” quality. This changes the initial understanding, since without analysis one might assume that ratings are distributed uniformly. Now we see that extreme ratings (very good or very bad) are rare. This can become a starting point for the next step: identifying chemical characteristics that most distinguish “outstanding” wines (rating 8+) from “average” ones.</p>
</body></html>]]></content><author><name>Taras Omelianenko</name><email>inbox@taras.pro</email><uri>https://taras.pro</uri></author><category term="Science" /><category term="Scientific Research" /><category term="AI" /><category term="Python" /><category term="Data Science" /><category term="Academic Writing" /><category term="Research Workflow" /><summary type="html"><![CDATA[Scientific research is an exciting but complex process. It requires structure, creativity, and careful attention to detail. In this article, I describe my own scientific “flow” — the sequence of steps I follow, and show how artificial intelligence (AI) tools can dramatically improve each stage.]]></summary></entry><entry><title type="html">Testing Mermaid Diagrams</title><link href="https://taras.pro/development/mermaid-test/" rel="alternate" type="text/html" title="Testing Mermaid Diagrams" /><published>2025-07-12T00:00:00+00:00</published><updated>2025-07-12T00:00:00+00:00</updated><id>https://taras.pro/development/mermaid-test</id><content type="html" xml:base="https://taras.pro/development/mermaid-test/"><![CDATA[<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body>
<p>This is a test post to verify that Mermaid diagrams are working correctly on the site.</p>

<h2 id="simple-flowchart">Simple Flowchart</h2>

<pre><code class="language-mermaid">graph TD;
    A[Start] --&gt; B[Initialize];
    B --&gt; C{Check Status};
    C --&gt;|Success| D[Process Data];
    C --&gt;|Error| E[Handle Error];
    D --&gt; F[Complete];
    E --&gt; F;
</code></pre>

<h2 id="sequence-diagram">Sequence Diagram</h2>

<pre><code class="language-mermaid">sequenceDiagram
    participant User
    participant Browser
    participant Server
    User-&gt;&gt;Browser: Click button
    Browser-&gt;&gt;Server: Send request
    Server--&gt;&gt;Browser: Return response
    Browser--&gt;&gt;User: Display result
</code></pre>

<h2 id="simple-timeline">Simple Timeline</h2>

<pre><code class="language-mermaid">gantt
    title Development Timeline
    dateFormat  YYYY-MM-DD
    section Planning
    Research        :done, research, 2025-07-01, 2025-07-05
    Design          :done, design, after research, 3d
    section Development
    Setup           :active, setup, 2025-07-12, 1d
    Implementation  :impl, after setup, 5d
    Testing         :test, after impl, 2d
</code></pre>

<p>The diagrams above should render correctly if Mermaid support is working properly.</p>
</body></html>]]></content><author><name>Taras Omelianenko</name><email>inbox@taras.pro</email><uri>https://taras.pro</uri></author><category term="Development" /><category term="Testing" /><category term="Mermaid" /><category term="Diagrams" /><summary type="html"><![CDATA[This is a test post to verify that Mermaid diagrams are working correctly on the site.]]></summary></entry><entry><title type="html">Using Prometheus Metrics for Health Checks in Kuma via a Sidecar Pattern</title><link href="https://taras.pro/devops/service%20mesh/Kuma-Uptime/" rel="alternate" type="text/html" title="Using Prometheus Metrics for Health Checks in Kuma via a Sidecar Pattern" /><published>2025-05-29T00:00:00+00:00</published><updated>2025-05-29T00:00:00+00:00</updated><id>https://taras.pro/devops/service%20mesh/Kuma-Uptime</id><content type="html" xml:base="https://taras.pro/devops/service%20mesh/Kuma-Uptime/"><![CDATA[<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body>
<p>Learn how to implement Prometheus-based health checks in Kuma using a sidecar container. This guide covers everything from metric evaluation to Docker and Kubernetes setup for reliable service mesh monitoring.</p>

<h1 id="-using-prometheus-metrics-for-health-checks-in-kuma-via-a-sidecar-pattern">🧩 Using Prometheus Metrics for Health Checks in Kuma via a Sidecar Pattern</h1>

<p>In service meshes like <strong>Kuma</strong>, health checks typically rely on TCP or HTTP probes.<br>
But what if your service’s health depends on a <strong>Prometheus metric</strong>, such as <code class="language-plaintext highlighter-rouge">my_app_up == 1</code>?</p>

<p>Kuma doesn’t support metric-based health checks natively — but you can bridge the gap with a lightweight <strong>sidecar container</strong> that interprets Prometheus metrics and exposes a simple <code class="language-plaintext highlighter-rouge">/health</code> endpoint for Kuma to consume.</p>

<p>This post shows you how.</p>

<hr>

<h2 id="️-architecture-overview">🗺️ Architecture Overview</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>         +----------------+
         |  Your Service  |
         | (Prometheus    |
         |  metrics here) |
         +-------+--------+
                 |
                 | exposes /metrics
                 |
         +-------v--------+
         | Sidecar Proxy  |
         | /health --&gt; 200|
         | if metric OK   |
         +----------------+
                 |
        Kuma probes /health on port 9000
</code></pre></div></div>

<hr>

<h2 id="️-step-1-create-the-sidecar-health-proxy">🛠️ Step 1: Create the Sidecar Health Proxy</h2>

<p>This proxy reads your app’s Prometheus metrics and returns <code class="language-plaintext highlighter-rouge">HTTP 200</code> only if the metric <code class="language-plaintext highlighter-rouge">my_app_up</code> is <code class="language-plaintext highlighter-rouge">1</code>.</p>

<p><strong><code class="language-plaintext highlighter-rouge">health_proxy.py</code>:</strong></p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">flask</span> <span class="kn">import</span> <span class="n">Flask</span><span class="p">,</span> <span class="n">Response</span>
<span class="kn">import</span> <span class="nn">requests</span>

<span class="n">app</span> <span class="o">=</span> <span class="n">Flask</span><span class="p">(</span><span class="n">__name__</span><span class="p">)</span>

<span class="o">@</span><span class="n">app</span><span class="p">.</span><span class="n">route</span><span class="p">(</span><span class="s">'/health'</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">health_check</span><span class="p">():</span>
    <span class="k">try</span><span class="p">:</span>
        <span class="n">resp</span> <span class="o">=</span> <span class="n">requests</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"http://localhost:8080/metrics"</span><span class="p">)</span>  <span class="c1"># Replace with your app port
</span>        <span class="k">if</span> <span class="s">'my_app_up 1'</span> <span class="ow">in</span> <span class="n">resp</span><span class="p">.</span><span class="n">text</span><span class="p">:</span>
            <span class="k">return</span> <span class="n">Response</span><span class="p">(</span><span class="s">"OK"</span><span class="p">,</span> <span class="n">status</span><span class="o">=</span><span class="mi">200</span><span class="p">)</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="k">return</span> <span class="n">Response</span><span class="p">(</span><span class="s">"Unhealthy"</span><span class="p">,</span> <span class="n">status</span><span class="o">=</span><span class="mi">500</span><span class="p">)</span>
    <span class="k">except</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">Response</span><span class="p">(</span><span class="s">"Unhealthy"</span><span class="p">,</span> <span class="n">status</span><span class="o">=</span><span class="mi">500</span><span class="p">)</span>

<span class="n">app</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">host</span><span class="o">=</span><span class="s">'0.0.0.0'</span><span class="p">,</span> <span class="n">port</span><span class="o">=</span><span class="mi">9000</span><span class="p">)</span>
</code></pre></div></div>

<hr>

<h2 id="-step-2-build-the-docker-image">🐳 Step 2: Build the Docker Image</h2>

<p><strong><code class="language-plaintext highlighter-rouge">Dockerfile</code>:</strong></p>
<div class="language-Dockerfile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">FROM</span><span class="s"> python:3.10-slim</span>

<span class="k">WORKDIR</span><span class="s"> /app</span>
<span class="k">COPY</span><span class="s"> health_proxy.py .</span>

<span class="k">RUN </span>pip <span class="nb">install </span>flask requests

<span class="k">EXPOSE</span><span class="s"> 9000</span>
<span class="k">CMD</span><span class="s"> ["python", "health_proxy.py"]</span>
</code></pre></div></div>

<p>Build and push:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker build <span class="nt">-t</span> yourdockerhub/health-proxy:latest <span class="nb">.</span>
docker push yourdockerhub/health-proxy:latest
</code></pre></div></div>

<hr>

<h2 id="-step-3-deploy-to-kubernetes">🎯 Step 3: Deploy to Kubernetes</h2>

<p>Here’s a sample deployment with the main app and health proxy sidecar.<br>
It also includes a <strong>Kuma HealthCheck</strong> policy.</p>

<p><strong><code class="language-plaintext highlighter-rouge">kuma-metric-health.yaml</code>:</strong></p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Namespace</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">kuma-demo</span>
  <span class="na">labels</span><span class="pi">:</span>
    <span class="na">kuma.io/mesh</span><span class="pi">:</span> <span class="s">default</span>

<span class="nn">---</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">apps/v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Deployment</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">demo-app</span>
  <span class="na">namespace</span><span class="pi">:</span> <span class="s">kuma-demo</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">replicas</span><span class="pi">:</span> <span class="m">1</span>
  <span class="na">selector</span><span class="pi">:</span>
    <span class="na">matchLabels</span><span class="pi">:</span>
      <span class="na">app</span><span class="pi">:</span> <span class="s">demo</span>
  <span class="na">template</span><span class="pi">:</span>
    <span class="na">metadata</span><span class="pi">:</span>
      <span class="na">labels</span><span class="pi">:</span>
        <span class="na">app</span><span class="pi">:</span> <span class="s">demo</span>
        <span class="na">kuma.io/service</span><span class="pi">:</span> <span class="s">demo</span>
    <span class="na">spec</span><span class="pi">:</span>
      <span class="na">containers</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">app</span>
          <span class="na">image</span><span class="pi">:</span> <span class="s">your-app-image</span>  <span class="c1"># Replace with your actual app image</span>
          <span class="na">ports</span><span class="pi">:</span>
            <span class="pi">-</span> <span class="na">containerPort</span><span class="pi">:</span> <span class="m">8080</span>
        <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">health-proxy</span>
          <span class="na">image</span><span class="pi">:</span> <span class="s">yourdockerhub/health-proxy:latest</span>
          <span class="na">ports</span><span class="pi">:</span>
            <span class="pi">-</span> <span class="na">containerPort</span><span class="pi">:</span> <span class="m">9000</span>
<span class="nn">---</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">kuma.io/v1alpha1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">HealthCheck</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">demo-metric-health</span>
  <span class="na">namespace</span><span class="pi">:</span> <span class="s">kuma-demo</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">sources</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">match</span><span class="pi">:</span>
        <span class="na">service</span><span class="pi">:</span> <span class="s">demo</span>
  <span class="na">destinations</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">match</span><span class="pi">:</span>
        <span class="na">service</span><span class="pi">:</span> <span class="s">demo</span>
  <span class="na">conf</span><span class="pi">:</span>
    <span class="na">interval</span><span class="pi">:</span> <span class="s">10s</span>
    <span class="na">timeout</span><span class="pi">:</span> <span class="s">2s</span>
    <span class="na">unhealthyThreshold</span><span class="pi">:</span> <span class="m">3</span>
    <span class="na">healthyThreshold</span><span class="pi">:</span> <span class="m">1</span>
    <span class="na">http</span><span class="pi">:</span>
      <span class="na">path</span><span class="pi">:</span> <span class="s">/health</span>
      <span class="na">port</span><span class="pi">:</span> <span class="m">9000</span>
</code></pre></div></div>

<hr>

<h2 id="-step-4-validate-the-setup">🧪 Step 4: Validate the Setup</h2>

<ol>
  <li>Ensure Prometheus metrics are available at <code class="language-plaintext highlighter-rouge">/metrics</code> on port <code class="language-plaintext highlighter-rouge">8080</code>.</li>
  <li>Deploy the app and sidecar.</li>
  <li>Kuma will ping the proxy’s <code class="language-plaintext highlighter-rouge">/health</code> every 10s.</li>
  <li>Test failure conditions by changing the metric:
    <ul>
      <li>
<code class="language-plaintext highlighter-rouge">my_app_up 1</code> → sidecar returns <code class="language-plaintext highlighter-rouge">200 OK</code> → Kuma marks healthy.</li>
      <li>
<code class="language-plaintext highlighter-rouge">my_app_up 0</code> → sidecar returns <code class="language-plaintext highlighter-rouge">500</code> → Kuma marks as unhealthy.</li>
    </ul>
  </li>
</ol>

<hr>

<h2 id="-conclusion">🎓 Conclusion</h2>

<p>While Kuma doesn’t natively support Prometheus-based health checks, you can <strong>convert metrics into HTTP health signals</strong> with this sidecar pattern.</p>

<p>✅ Lightweight<br>
✅ Language-agnostic<br>
✅ Works in any environment</p>

<p>Now Kuma will <strong>automatically remove</strong> services from the mesh when your custom metric signals a problem.</p>

<hr>

<h2 id="-ideas-for-extension">🔁 Ideas for Extension</h2>

<ul>
  <li>Use multiple metrics in the health logic</li>
  <li>Add caching to avoid high metric polling rates</li>
  <li>Build a Helm chart for easier reuse</li>
</ul>

<hr>

<p><strong>Happy meshing!</strong></p>
</body></html>]]></content><author><name>Taras Omelianenko</name><email>inbox@taras.pro</email><uri>https://taras.pro</uri></author><category term="DevOps" /><category term="Service Mesh" /><category term="Kuma" /><category term="Prometheus" /><category term="Service Mesh" /><category term="Kubernetes" /><category term="Health Check" /><summary type="html"><![CDATA[Learn how to implement Prometheus-based health checks in Kuma using a sidecar container. This guide covers everything from metric evaluation to Docker and Kubernetes setup for reliable service mesh monitoring.]]></summary></entry></feed>