<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Azure on Ash Tewari</title><link>https://www.tewari.info/tag/azure/</link><description>Recent content in Azure on Ash Tewari</description><generator>Hugo -- gohugo.io</generator><language>en-US</language><lastBuildDate>Mon, 06 Apr 2026 04:48:17 +0000</lastBuildDate><atom:link href="https://www.tewari.info/tag/azure/feed.xml" rel="self" type="application/rss+xml"/><item><title>Giving OpenClaw Secure Access to Cloud Services Without Sharing Your Password</title><link>https://www.tewari.info/2026/04/06/device-code-auth-flow-with-openclaw/</link><pubDate>Mon, 06 Apr 2026 04:48:17 +0000</pubDate><guid>https://www.tewari.info/2026/04/06/device-code-auth-flow-with-openclaw/</guid><description>&lt;p&gt;Device Code Flow has been built into OAuth2 for years, originally designed for TVs and game consoles. It works just as well for a Docker container. It requires no credentials stored on the agent machine. It gives you narrow, revocable access that the agent cannot exceed.&lt;/p&gt;&#10;&lt;p&gt;I have OpenClaw deployed in a sandboxed docker container on a dedicated host. I communicate with it via a Telegram bot, in a locked-down chat.&lt;/p&gt;&#10;&lt;p&gt;I wired the device-code authentication pattern up with OpenClaw and Microsoft (Personal/Consumer) services, but the authentication approach works with any app and any command-line tool you want to run in a headless environment, including Custom APIs. Device code flow + Entra ID app registrations give you a zero-stored-credentials gateway to any HTTP API you can build, not just Microsoft Graph.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://www.tewari.info/wp-content/openclaw-telegram-integration.png"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;&lt;strong&gt;What Is Device Code Flow?&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;OAuth2 device code flow (RFC 8628) was designed for &amp;ldquo;input-constrained devices&amp;rdquo; — things without a keyboard or a browser. Think of how you sign in to Netflix on a smart TV: a short code appears on screen, you visit a URL on your phone, you type the code in, and the TV logs in. You never type your password on the TV.&lt;/p&gt;&#10;&lt;p&gt;The official name for that pattern is the device authorization grant. A Docker container is, from the protocol&amp;rsquo;s perspective, exactly the same kind of device. It has no browser. It cannot perform an interactive redirect. But it can make HTTP requests, and that is all it needs.&lt;/p&gt;&#10;&lt;p&gt;The flow has three steps:&lt;/p&gt;&#10;&lt;p&gt;1. The app posts to the identity provider&amp;rsquo;s device code endpoint. It gets back a short user code (like &amp;ldquo;WDJB-MJHT&amp;rdquo;), a verification URL, and a polling device code.&lt;/p&gt;&#10;&lt;p&gt;2. The user visits the URL on any browser, on any device — their phone, laptop, anything — and enters the short code. They sign in normally, with their usual credentials and MFA if it is enabled.&lt;/p&gt;&#10;&lt;p&gt;3. The app polls the token endpoint in the background. Once the user finishes signing in, the poll returns a real access token and a refresh token. The app stores these and uses them for API calls going forward.&lt;/p&gt;&#10;&lt;p&gt;The app never sees the password. The password goes directly from the user&amp;rsquo;s browser to the identity provider. The app only ever handles two things: a short temporary code that it sends to the user, and a token that the identity provider gives back once the user has authenticated.&lt;/p&gt;&#10;&lt;p&gt;OpenClaw running in a Docker container fits this model exactly.&lt;/p&gt;&#10;&lt;h2 id="which-services-support-this"&gt;Which Services Support this?&#10;&lt;/h2&gt;&lt;p&gt;Device code flow is not a Microsoft-only feature. It is a standard OAuth2 extension (RFC 8628) and most major identity providers support it — including Microsoft, Google, GitHub, and AWS. If a service uses Okta or Auth0 for identity, those support it too.&lt;/p&gt;&#10;&lt;h2 id="the-security-architecture"&gt;The Security Architecture&#10;&lt;/h2&gt;&lt;p&gt;The agent never sees your credentials. Your password is typed in your browser, on your device, to your identity provider&amp;rsquo;s servers. It never touches the container. The agent only handles a short temporary code to give you, and a token issued by the provider once you have authenticated.&lt;/p&gt;&#10;&lt;p&gt;The device code is one-time and short-lived. After the user authenticates, the code is permanently invalidated. An intercepted code is useless without the user&amp;rsquo;s credentials and MFA.&lt;/p&gt;&#10;&lt;p&gt;Scopes define the ceiling. You configure the OAuth app or app registration to request only specific permissions. The agent cannot exceed those scopes. If you configure read-only access to email, the token cannot be used to send or delete email.&lt;/p&gt;&#10;&lt;p&gt;This is the authentication pattern that many of the consumer devices you already own use to access your accounts on your behalf. Your TV&amp;rsquo;s YouTube app, your smart home hub&amp;rsquo;s Google integration, the GitHub CLI you use on your workstation — these all use device flow. You have been trusting it for years without knowing what it was called.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://www.tewari.info/wp-content/device-code-flow-workflow.drawio.png"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;h2 id="how-the-implementation-works"&gt;How the implementation works&#10;&lt;/h2&gt;&lt;p&gt;There are three components, and they run as separate Docker services that talk to each other through the Docker socket.&lt;/p&gt;&#10;&lt;p&gt;The app sidecar is your application container. It runs your CLI tool and your auth logic. It does nothing on its own. Its only job is to hold the authenticated token cache and execute commands on demand when OpenClaw calls into it.&lt;/p&gt;&#10;&lt;p&gt;OpenClaw is the AI agent container. It connects to Telegram, understands natural language, and knows which skills to run for which requests. It does not know anything about OAuth or your specific app. It just runs shell commands you specified and reports results back to you.&lt;/p&gt;&#10;&lt;p&gt;The Telegram auth bridge is a small script that lives inside the sidecar. It registers a device code callback, requests an auth flow, and uses the Telegram Bot API to forward the code to you — then confirms when authentication completes.&lt;/p&gt;&#10;&lt;p&gt;The compose file looks roughly like this:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;services&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;myapp-sidecar&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;ghcr.io/youruser/myapp:latest&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;command&lt;/span&gt;: [&lt;span style="color:#e6db74"&gt;&amp;#34;sleep&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;infinity&amp;#34;&lt;/span&gt;]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;environment&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;XDG_DATA_HOME=/data&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;TELEGRAM_CHAT_ID=${TELEGRAM_CHAT_ID}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;${APP_DATA_DIR:-./app-data}:/data&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;restart&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;unless-stopped&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;openclaw-gateway&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;build&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;context&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;.&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;dockerfile&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Dockerfile.openclaw&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;/var/run/docker.sock:/var/run/docker.sock&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;./skills:/app/skills&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;group_add&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;${DOCKER_GID:-999}&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;environment&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ports&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;18789:18789&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;restart&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;unless-stopped&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="the-callback-pattern"&gt;The Callback Pattern&#10;&lt;/h2&gt;&lt;p&gt;The auth manager has a method called set_device_code_callback. You pass it a function, and when the device code is ready, the auth manager calls your function with the code and the verification URL rather than trying to open a browser.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AuthManager&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;__init__&lt;/span&gt;(self, client_id: str, authority: str) &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;None&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_client_id &lt;span style="color:#f92672"&gt;=&lt;/span&gt; client_id&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_authority &lt;span style="color:#f92672"&gt;=&lt;/span&gt; authority&lt;span style="color:#f92672"&gt;.&lt;/span&gt;rstrip(&lt;span style="color:#e6db74"&gt;&amp;#34;/&amp;#34;&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_on_device_code &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;None&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_tokens &lt;span style="color:#f92672"&gt;=&lt;/span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_load_cache()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;set_device_code_callback&lt;/span&gt;(self, fn) &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;None&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_on_device_code &lt;span style="color:#f92672"&gt;=&lt;/span&gt; fn&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;_device_code_auth&lt;/span&gt;(self) &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;None&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;with&lt;/span&gt; httpx&lt;span style="color:#f92672"&gt;.&lt;/span&gt;AsyncClient() &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; client:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; resp &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; client&lt;span style="color:#f92672"&gt;.&lt;/span&gt;post(&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_authority&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;/oauth2/v2.0/devicecode&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; data&lt;span style="color:#f92672"&gt;=&lt;/span&gt;{&lt;span style="color:#e6db74"&gt;&amp;#34;client_id&amp;#34;&lt;/span&gt;: self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_client_id, &lt;span style="color:#e6db74"&gt;&amp;#34;scope&amp;#34;&lt;/span&gt;: OAUTH_SCOPES},&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; timeout&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;30&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; flow &lt;span style="color:#f92672"&gt;=&lt;/span&gt; resp&lt;span style="color:#f92672"&gt;.&lt;/span&gt;json()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; user_code &lt;span style="color:#f92672"&gt;=&lt;/span&gt; flow[&lt;span style="color:#e6db74"&gt;&amp;#34;user_code&amp;#34;&lt;/span&gt;]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; verification_uri &lt;span style="color:#f92672"&gt;=&lt;/span&gt; flow[&lt;span style="color:#e6db74"&gt;&amp;#34;verification_uri&amp;#34;&lt;/span&gt;]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; device_code &lt;span style="color:#f92672"&gt;=&lt;/span&gt; flow[&lt;span style="color:#e6db74"&gt;&amp;#34;device_code&amp;#34;&lt;/span&gt;]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; interval &lt;span style="color:#f92672"&gt;=&lt;/span&gt; flow&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get(&lt;span style="color:#e6db74"&gt;&amp;#34;interval&amp;#34;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; expires_in &lt;span style="color:#f92672"&gt;=&lt;/span&gt; flow&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get(&lt;span style="color:#e6db74"&gt;&amp;#34;expires_in&amp;#34;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;300&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_on_device_code:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_on_device_code({&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;user_code&amp;#34;&lt;/span&gt;: user_code,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;verification_uri&amp;#34;&lt;/span&gt;: verification_uri,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;message&amp;#34;&lt;/span&gt;: flow&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get(&lt;span style="color:#e6db74"&gt;&amp;#34;message&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;),&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; })&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; webbrowser&lt;span style="color:#f92672"&gt;.&lt;/span&gt;open(verification_uri)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;except&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Exception&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pass&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; deadline &lt;span style="color:#f92672"&gt;=&lt;/span&gt; time&lt;span style="color:#f92672"&gt;.&lt;/span&gt;time() &lt;span style="color:#f92672"&gt;+&lt;/span&gt; expires_in&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;while&lt;/span&gt; time&lt;span style="color:#f92672"&gt;.&lt;/span&gt;time() &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; deadline:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; asyncio&lt;span style="color:#f92672"&gt;.&lt;/span&gt;sleep(interval)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;with&lt;/span&gt; httpx&lt;span style="color:#f92672"&gt;.&lt;/span&gt;AsyncClient() &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; client:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; resp &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; client&lt;span style="color:#f92672"&gt;.&lt;/span&gt;post(&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_authority&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;/oauth2/v2.0/token&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; data&lt;span style="color:#f92672"&gt;=&lt;/span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;client_id&amp;#34;&lt;/span&gt;: self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_client_id,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;grant_type&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;urn:ietf:params:oauth:grant-type:device_code&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;device_code&amp;#34;&lt;/span&gt;: device_code,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; timeout&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;30&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; body &lt;span style="color:#f92672"&gt;=&lt;/span&gt; resp&lt;span style="color:#f92672"&gt;.&lt;/span&gt;json()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; resp&lt;span style="color:#f92672"&gt;.&lt;/span&gt;status_code &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;200&lt;/span&gt; &lt;span style="color:#f92672"&gt;and&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;access_token&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;in&lt;/span&gt; body:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_store_tokens(body)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; error &lt;span style="color:#f92672"&gt;=&lt;/span&gt; body&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get(&lt;span style="color:#e6db74"&gt;&amp;#34;error&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; error &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;authorization_pending&amp;#34;&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;continue&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;elif&lt;/span&gt; error &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;slow_down&amp;#34;&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; interval &lt;span style="color:#f92672"&gt;+=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;elif&lt;/span&gt; error &lt;span style="color:#f92672"&gt;in&lt;/span&gt; (&lt;span style="color:#e6db74"&gt;&amp;#34;authorization_declined&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;expired_token&amp;#34;&lt;/span&gt;):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;raise&lt;/span&gt; AuthError(error)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;raise&lt;/span&gt; AuthError(body&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get(&lt;span style="color:#e6db74"&gt;&amp;#34;error_description&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;Auth failed&amp;#34;&lt;/span&gt;))&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The polling loop handles the three error codes the spec defines: authorization_pending (keep waiting), slow_down (back off and increase the interval by 5 seconds), and the terminal errors that stop polling.&lt;/p&gt;&#10;&lt;h2 id="the-telegram-bridge"&gt;The Telegram Bridge&#10;&lt;/h2&gt;&lt;p&gt;With the callback mechanism in place, the Telegram bridge is just a script that registers a callback and uses the Telegram Bot API to deliver the code to you:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; asyncio&lt;span style="color:#f92672"&gt;,&lt;/span&gt; os&lt;span style="color:#f92672"&gt;,&lt;/span&gt; sys&lt;span style="color:#f92672"&gt;,&lt;/span&gt; traceback&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; httpx&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; your_app.auth &lt;span style="color:#f92672"&gt;import&lt;/span&gt; AuthManager&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; your_app.config &lt;span style="color:#f92672"&gt;import&lt;/span&gt; Settings&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;TELEGRAM_BOT_TOKEN &lt;span style="color:#f92672"&gt;=&lt;/span&gt; os&lt;span style="color:#f92672"&gt;.&lt;/span&gt;environ[&lt;span style="color:#e6db74"&gt;&amp;#34;TELEGRAM_BOT_TOKEN&amp;#34;&lt;/span&gt;]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;TELEGRAM_CHAT_ID &lt;span style="color:#f92672"&gt;=&lt;/span&gt; os&lt;span style="color:#f92672"&gt;.&lt;/span&gt;environ[&lt;span style="color:#e6db74"&gt;&amp;#34;TELEGRAM_CHAT_ID&amp;#34;&lt;/span&gt;]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;send_telegram&lt;/span&gt;(text: str) &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;None&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; url &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;https://api.telegram.org/bot&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;TELEGRAM_BOT_TOKEN&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;/sendMessage&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;with&lt;/span&gt; httpx&lt;span style="color:#f92672"&gt;.&lt;/span&gt;AsyncClient() &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; client:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; client&lt;span style="color:#f92672"&gt;.&lt;/span&gt;post(url, json&lt;span style="color:#f92672"&gt;=&lt;/span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;chat_id&amp;#34;&lt;/span&gt;: TELEGRAM_CHAT_ID,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;text&amp;#34;&lt;/span&gt;: text,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;parse_mode&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Markdown&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }, timeout&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;30&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; int:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; settings &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Settings()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; auth &lt;span style="color:#f92672"&gt;=&lt;/span&gt; AuthManager(settings&lt;span style="color:#f92672"&gt;.&lt;/span&gt;client_id, settings&lt;span style="color:#f92672"&gt;.&lt;/span&gt;authority)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; auth&lt;span style="color:#f92672"&gt;.&lt;/span&gt;is_signed_in():&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; send_telegram(&lt;span style="color:#e6db74"&gt;&amp;#34;Already authenticated.&amp;#34;&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;on_device_code&lt;/span&gt;(info: dict) &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;None&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; msg &lt;span style="color:#f92672"&gt;=&lt;/span&gt; (&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;*Auth Required*&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;\n\n&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Go to: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;info[&lt;span style="color:#e6db74"&gt;&amp;#39;verification_uri&amp;#39;&lt;/span&gt;]&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;\n&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Enter code: `&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;info[&lt;span style="color:#e6db74"&gt;&amp;#39;user_code&amp;#39;&lt;/span&gt;]&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;\n\n&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Complete sign-in in your browser and I&amp;#39;ll confirm when done.&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; asyncio&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get_event_loop()&lt;span style="color:#f92672"&gt;.&lt;/span&gt;create_task(send_telegram(msg))&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;except&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;RuntimeError&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;import&lt;/span&gt; requests&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; requests&lt;span style="color:#f92672"&gt;.&lt;/span&gt;post(&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;https://api.telegram.org/bot&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;TELEGRAM_BOT_TOKEN&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;/sendMessage&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; json&lt;span style="color:#f92672"&gt;=&lt;/span&gt;{&lt;span style="color:#e6db74"&gt;&amp;#34;chat_id&amp;#34;&lt;/span&gt;: TELEGRAM_CHAT_ID, &lt;span style="color:#e6db74"&gt;&amp;#34;text&amp;#34;&lt;/span&gt;: msg, &lt;span style="color:#e6db74"&gt;&amp;#34;parse_mode&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Markdown&amp;#34;&lt;/span&gt;},&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; timeout&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; auth&lt;span style="color:#f92672"&gt;.&lt;/span&gt;set_device_code_callback(on_device_code)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; user_info &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; auth&lt;span style="color:#f92672"&gt;.&lt;/span&gt;sign_in()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; send_telegram(&lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;*Signed in* as &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;user_info[&lt;span style="color:#e6db74"&gt;&amp;#39;name&amp;#39;&lt;/span&gt;]&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt; (&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;user_info[&lt;span style="color:#e6db74"&gt;&amp;#39;email&amp;#39;&lt;/span&gt;]&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;)&amp;#34;&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;except&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Exception&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; exc:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; send_telegram(&lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;*Auth failed:* &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;exc&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;\n&lt;/span&gt;&lt;span style="color:#e6db74"&gt;```&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;traceback&lt;span style="color:#f92672"&gt;.&lt;/span&gt;format_exc()[:&lt;span style="color:#ae81ff"&gt;800&lt;/span&gt;]&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;```&amp;#34;&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; __name__ &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;__main__&amp;#34;&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sys&lt;span style="color:#f92672"&gt;.&lt;/span&gt;exit(asyncio&lt;span style="color:#f92672"&gt;.&lt;/span&gt;run(main()))&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="the-openclaw-skill"&gt;The OpenClaw Skill&#10;&lt;/h2&gt;&lt;p&gt;On the OpenClaw side, a &amp;ldquo;skill&amp;rdquo; is a markdown file. When OpenClaw sees a trigger phrase in Telegram, it runs the associated shell command. The markdown file looks like this:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;---&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;myapp-auth&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;description&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Authenticate with the cloud service via device code flow, coordinated through Telegram.&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;---&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;## myapp-auth&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Use this skill when the user sends &amp;#34;/auth&amp;#34;, &amp;#34;authenticate&amp;#34;, or &amp;#34;sign in&amp;#34;.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Run the following command and wait up to 360 seconds for it to complete.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;The script sends the device code to the user via Telegram and confirms when done.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; docker exec myapp-sidecar-1 python /app/scripts/telegram_auth.py&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="summary"&gt;Summary&#10;&lt;/h2&gt;&lt;p&gt;Device code flow is a mature, widely-supported OAuth2 pattern that maps naturally onto OpenClaw running in Docker container. With this pattern OpenClaw never handles your credentials. It gets scoped, revocable tokens from the identity provider, after you have authenticated in your own browser. A Telegram bot is all you need to coordinate the handoff of the temporary code.&lt;/p&gt;&#10;&lt;p&gt;The approach works for many use-cases across Microsoft accounts, Google, GitHub, AWS, Auth0, Okta and others. It supports exactly the kinds of personal automations that make an OpenClaw genuinely useful in daily life.&lt;/p&gt;&#10;&lt;h2 id="references"&gt;References&#10;&lt;/h2&gt;&lt;ul&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://docs.openclaw.ai/" target="_blank" rel="noopener"&#10; &gt;OpenClaw Documentation&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://docs.openclaw.ai/tools/creating-skills" target="_blank" rel="noopener"&#10; &gt;OpenClaw: Creating Skills&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://datatracker.ietf.org/doc/html/rfc8628" target="_blank" rel="noopener"&#10; &gt;RFC 8628: OAuth 2.0 Device Authorization Grant&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://learn.microsoft.com/en-us/entra/identity-platform/scenario-desktop-acquire-token-device-code-flow" target="_blank" rel="noopener"&#10; &gt;Microsoft Identity Platform: Acquire a Token Using Device Code Flow&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://learn.microsoft.com/en-us/graph/permissions-reference" target="_blank" rel="noopener"&#10; &gt;Microsoft Graph Permissions Reference&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://auth0.com/docs/get-started/authentication-and-authorization-flow/device-authorization-flow" target="_blank" rel="noopener"&#10; &gt;Auth0: Device Authorization Flow&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://developer.okta.com/docs/guides/device-authorization-grant/main/" target="_blank" rel="noopener"&#10; &gt;Okta: Configure Device Authorization Grant Flow&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://core.telegram.org/bots/api" target="_blank" rel="noopener"&#10; &gt;Telegram Bot API&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://docs.docker.com/reference/compose-file/" target="_blank" rel="noopener"&#10; &gt;Docker Compose File Reference&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description></item><item><title>SPCHR: Speech To Text App for Windows</title><link>https://www.tewari.info/2025/02/09/spchr-speech-to-text-app-for-windows/</link><pubDate>Mon, 10 Feb 2025 04:32:25 +0000</pubDate><guid>https://www.tewari.info/2025/02/09/spchr-speech-to-text-app-for-windows/</guid><description>&lt;p&gt;I&amp;rsquo;m excited to announce the release of SPCHR. Pronounced as &amp;ldquo;speaker&amp;rdquo;. I know, I know. My apologies, it was too late at night and that&amp;rsquo;s the best I could come up with ;)&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;It is a Windows desktop application for speech-to-text transcription.&lt;/li&gt;&#10;&lt;li&gt;You can use it to &amp;ldquo;add&amp;rdquo; voice input to applications that don’t natively support it. It works anywhere you can type or paste text.&lt;/li&gt;&#10;&lt;li&gt;You can use it entirely locally on your PC if you like, so it is private and secure.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="key-features"&gt;Key Features&#10;&lt;/h2&gt;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;Flexible&lt;/strong&gt;: SPCHR can use either Azure Speech Services or a local OpenAI Whisper model, giving you the flexibility to choose between cloud-based and local transcription.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Zero Configuration&lt;/strong&gt;: The application works immediately with local Whisper processing - no account setup required!&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Global Hotkey&lt;/strong&gt;: Start and stop recording from &lt;em&gt;any&lt;/em&gt; application with Ctrl+Alt+L.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Seamless Integration&lt;/strong&gt;: Transcribed text is automatically pasted into your active window.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="getting-started"&gt;Getting Started&#10;&lt;/h2&gt;&lt;ul&gt;&#10;&lt;li&gt;Clone the repository&lt;/li&gt;&#10;&lt;li&gt;Build the solution using Visual Studio and run it&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Press Ctrl+Alt+L and start speaking&lt;/strong&gt;&lt;/li&gt;&#10;&lt;li&gt;Watch as your words appear in your active window&lt;/li&gt;&#10;&lt;li&gt;For those wanting cloud-based transcription, simply add your Azure Speech Services credentials to the configuration file.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="open-source"&gt;Open Source&#10;&lt;/h2&gt;&lt;p&gt;SPCHR is released under the MIT License, and I welcome contributions from the community. Whether you&amp;rsquo;re interested in adding features, fixing bugs, or improving documentation, your help is appreciated.&lt;/p&gt;&#10;&lt;p&gt;Check out &lt;a class="link" href="https://github.com/ashtewari/spchr" target="_blank" rel="noopener"&#10; &gt;GitHub repository&lt;/a&gt;&lt;/p&gt;&#10;&lt;h2 id="whats-next"&gt;What&amp;rsquo;s next&#10;&lt;/h2&gt;&lt;p&gt;This is just the beginning for SPCHR. Several improvements can be done:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Additional language support&lt;/li&gt;&#10;&lt;li&gt;Customizable hotkeys&lt;/li&gt;&#10;&lt;li&gt;Installer&lt;/li&gt;&#10;&lt;li&gt;UI Enhancements&lt;/li&gt;&#10;&lt;li&gt;AI Enhanced Features&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="try-it-out"&gt;Try it out&#10;&lt;/h2&gt;&lt;p&gt;Ready to transform how you interact with your computer? Visit the &lt;a class="link" href="https://github.com/ashtewari/spchr" target="_blank" rel="noopener"&#10; &gt;GitHub repository&lt;/a&gt; to get started with SPCHR today.&lt;/p&gt;&#10;&lt;p&gt;I&amp;rsquo;m excited to see how you&amp;rsquo;ll use SPCHR in your workflow and look forward to your feedback!&lt;/p&gt;&#10;</description></item><item><title>Kusto Queries on AKS Clusters</title><link>https://www.tewari.info/2021/04/25/kusto-queries-on-aks-clusters/</link><pubDate>Sun, 25 Apr 2021 19:40:55 +0000</pubDate><guid>https://www.tewari.info/2021/04/25/kusto-queries-on-aks-clusters/</guid><description>&lt;p&gt;&lt;a class="link" href="https://docs.microsoft.com/en-us/azure/data-explorer/kusto/query/" target="_blank" rel="noopener"&#10; &gt;Kusto&lt;/a&gt; query language can be used to get &lt;a class="link" href="https://docs.microsoft.com/en-us/azure/azure-monitor/containers/container-insights-log-search" target="_blank" rel="noopener"&#10; &gt;insights&lt;/a&gt; into Azure Kubernetes Service (&lt;a class="link" href="https://azure.microsoft.com/en-us/services/kubernetes-service/" target="_blank" rel="noopener"&#10; &gt;AKS&lt;/a&gt;) clusters. &lt;a class="link" href="https://docs.microsoft.com/en-us/azure/azure-monitor/containers/container-insights-overview" target="_blank" rel="noopener"&#10; &gt;Container insights&lt;/a&gt; collects data from AKS clusters and forwards it to &lt;a class="link" href="https://docs.microsoft.com/en-us/azure/azure-monitor/logs/log-analytics-tutorial" target="_blank" rel="noopener"&#10; &gt;Log Analytics&lt;/a&gt; workspace, if enabled for a cluster. This data is available for querying in the &lt;a class="link" href="https://docs.microsoft.com/en-us/azure/azure-monitor/logs/data-platform-logs" target="_blank" rel="noopener"&#10; &gt;Azure Monitor&lt;/a&gt;. Here is an example of how you can query the pods not in running state in specific namespaces.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;KubePodInventory&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;| where Namespace in (&amp;#34;dv&amp;#34;,&amp;#34;test&amp;#34;,&amp;#34;prod&amp;#34;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;| where ContainerStatus != &amp;#34;Running&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;| where ContainerStatusReason !in (&amp;#34;&amp;#34;, &amp;#34;Completed&amp;#34;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;| distinct Namespace, Name&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;figure&gt;&lt;img src="https://www.tewari.info/wp-content/image-7.png"&#10;&#9;&#9;&#9;alt="Log Analytics query results listing pods not in running state by namespace"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;The following query includes the name of the AKS cluster and renders the output as a stacked bar chart.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;KubePodInventory&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;| where Namespace in (&amp;#34;dv&amp;#34;,&amp;#34;test&amp;#34;,&amp;#34;prod&amp;#34;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;| where ContainerStatus != &amp;#34;Running&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;| where ContainerStatusReason !in (&amp;#34;&amp;#34;, &amp;#34;Completed&amp;#34;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;| distinct ClusterName, Namespace, Name&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;| summarize dcount(Name) by ClusterName, Namespace&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;| render columnchart kind=stacked100&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;figure&gt;&lt;img src="https://www.tewari.info/wp-content/image-8-1024x582.png"&#10;&#9;&#9;&#9;alt="Stacked column chart of non-running pods by AKS cluster and namespace"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;You can include multiple AKS clusters in the scope in which this query is executed by clicking on [Select scope] hyperlink.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://www.tewari.info/wp-content/image-12.png"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;figure&gt;&lt;img src="https://www.tewari.info/wp-content/image-15.png"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Create an Azure Dashboard panel with this output by clicking on [Pin to dashboard] button.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://www.tewari.info/wp-content/image-13.png"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;figure&gt;&lt;img src="https://www.tewari.info/wp-content/image-11-1024x676.png"&#10;&#9;&#9;&#9;alt="Azure dashboard showing the pinned AKS pod status chart"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;You can also execute this Kusto query directly using powershell.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$workspaceName = &amp;#34;DefaultWorkspace-6637b095-xxxx-xxxx-xxxx-xxxxxxxxxxx-EUS&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$workspaceRG = &amp;#34;defaultresourcegroup-eus&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$WorkspaceID = (Get-AzOperationalInsightsWorkspace -Name $workspaceName -ResourceGroupName $workspaceRG).CustomerID&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$query = &amp;#39;KubePodInventory | where Namespace in (&amp;#34;dv&amp;#34;,&amp;#34;test&amp;#34;,&amp;#34;prod&amp;#34;) | where ContainerStatus != &amp;#34;Running&amp;#34; | where ContainerStatusReason !in (&amp;#34;&amp;#34;, &amp;#34;Completed&amp;#34;) | distinct ClusterName, Namespace, Name | summarize dcount(Name) by ClusterName, Namespace&amp;#39;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$result = Invoke-AzOperationalInsightsQuery -WorkspaceId $WorkspaceID -Query $query -Timespan (New-TimeSpan -days 1)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$result.results&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This allows you to include the results of your custom Kusto queries in any reports you might run using Azure Automation Runbooks.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://www.tewari.info/wp-content/image-14-1024x392.png"&#10;&#9;&#9;&#9;alt="PowerShell session running the Kusto query with Invoke-AzOperationalInsightsQuery"&gt;&#10;&lt;/figure&gt;&#10;&#10;</description></item><item><title>AKS - Adding SSH Keys to VMSS Nodes</title><link>https://www.tewari.info/2021/03/27/aks-adding-ssh-keys-to-vmss-nodes/</link><pubDate>Sun, 28 Mar 2021 03:33:11 +0000</pubDate><guid>https://www.tewari.info/2021/03/27/aks-adding-ssh-keys-to-vmss-nodes/</guid><description>&lt;p&gt;You can connect to Azure Kubernetes Service (AKS) nodes using ssh. It is documented here: &lt;a class="link" href="https://docs.microsoft.com/en-us/azure/aks/ssh" target="_blank" rel="noopener"&#10; &gt;Connect with SSH to Azure Kubernetes Service (AKS) cluster nodes for maintenance or troubleshooting&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;I needed to access nodes on the System node pool for collecting some logs recently, but the process documented above was not working for me. It turns out that there were two different issues, both related to adding your SSH keys to the nodes in a virtual machine scale set (VMSS).&lt;/p&gt;&#10;&lt;p&gt;This is the az cli command that adds the ssh keys to the VMSS:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;az vmss extension set \&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --resource-group $CLUSTER_RESOURCE_GROUP \&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --vmss-name $SCALE_SET_NAME \&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --name VMAccessForLinux \&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --publisher Microsoft.OSTCExtensions \&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --version 1.4 \&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --protected-settings &amp;#34;{\&amp;#34;username\&amp;#34;:\&amp;#34;azureuser\&amp;#34;, \&amp;#34;ssh_key\&amp;#34;:\&amp;#34;$(cat ~/.ssh/id_rsa.pub)\&amp;#34;}&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I was running this command from powershell on a windows host. So, the first modification I needed was to escape the double quotes by doubling them. You can also &lt;a class="link" href="http://www.rlmueller.net/PowerShellEscape.htm" target="_blank" rel="noopener"&#10; &gt;use back ticks&lt;/a&gt; instead of doubling the double quotes.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;az vmss extension set \&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --resource-group $CLUSTER_RESOURCE_GROUP \&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --vmss-name $SCALE_SET_NAME \&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --name VMAccessForLinux \&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --publisher Microsoft.OSTCExtensions \&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --version 1.4 \&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --protected-settings &amp;#34;{\&amp;#34;&amp;#34;username\&amp;#34;&amp;#34;:\&amp;#34;&amp;#34;azureuser\&amp;#34;&amp;#34;, \&amp;#34;&amp;#34;ssh_key\&amp;#34;&amp;#34;:\&amp;#34;&amp;#34;$(cat ~/.ssh/id_rsa.pub)\&amp;#34;&amp;#34;}&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The second issue was due to the fact that ssh-keygen adds a comment to the id_rsa.pub file. By default, it adds username@hostname as the comment at the end of the file . Normally, this comment wouldn&amp;rsquo;t cause any problems, but in this case it was causing an error - &amp;ldquo;Invalid escape sequence \uXXXX&amp;rdquo;.&lt;/p&gt;&#10;&lt;p&gt;The problem was that my domain user name starts with character &amp;lsquo;u&amp;rsquo; and &amp;ldquo;domain\userid1&amp;rdquo; was being interpreted as containing a unicode character &amp;ldquo;\useri&amp;rdquo; which is obviously not a valid unicode character. The workaround here is to either delete the comment from the id_rsa.pub file (it works just fine without it) or to override the default comment when generating the ssh key by specifying your own comment via -C command line option.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ssh-keygen -C mycomment&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Finally, there is another way to execute &lt;a class="link" href="https://docs.microsoft.com/en-us/cli/azure/vmss/extension?view=azure-cli-latest#az_vmss_extension_set" target="_blank" rel="noopener"&#10; &gt;az vmss extension set&lt;/a&gt; command to get around both of these issues by creating a json file and passing that as the value of &amp;ndash;protected-settings argument. Here is how -&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Create a json file, protected_settings.json, with the following content:&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#9;&amp;#34;username&amp;#34; : &amp;#34;azureuser&amp;#34;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#9;&amp;#34;ssh_key&amp;#34; : &amp;#34;REPLACE_THIS_WITH_CONTENT_OF_ID_RSA_PUB_FILE&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;&#10;&lt;li&gt;Pass this file as the command line argument:&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;az vmss extension set \&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --resource-group $CLUSTER_RESOURCE_GROUP \&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --vmss-name $SCALE_SET_NAME \&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --name VMAccessForLinux \&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --publisher Microsoft.OSTCExtensions \&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --version 1.4 \&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --protected-settings protected_settings.json&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Works like a charm!&lt;/p&gt;&#10;</description></item><item><title>AKS Supported Kubernetes Versions</title><link>https://www.tewari.info/2020/11/08/aks-supported-kubernetes-versions/</link><pubDate>Mon, 09 Nov 2020 03:08:17 +0000</pubDate><guid>https://www.tewari.info/2020/11/08/aks-supported-kubernetes-versions/</guid><description>&lt;p&gt;&lt;a class="link" href="https://docs.microsoft.com/en-us/azure/aks/" target="_blank" rel="noopener"&#10; &gt;Azure Kubernetes Service&lt;/a&gt; (AKS) &lt;a class="link" href="https://docs.microsoft.com/en-us/azure/aks/supported-kubernetes-versions" target="_blank" rel="noopener"&#10; &gt;supports specific versions&lt;/a&gt; of &lt;a class="link" href="https://kubernetes.io/" target="_blank" rel="noopener"&#10; &gt;Kubernetes&lt;/a&gt;.&lt;br&gt;&#10;It is necessary to regularly monitor the release of new versions and upgrade your AKS clusters to supported versions in order to remain in compliance with &lt;a class="link" href="https://docs.microsoft.com/en-us/azure/aks/supported-kubernetes-versions#kubernetes-version-support-policy" target="_blank" rel="noopener"&#10; &gt;AKS Kubernetes Version Support Policy&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;&lt;a class="link" href="https://docs.microsoft.com/en-us/azure/aks/supported-kubernetes-versions#release-and-deprecation-process" target="_blank" rel="noopener"&#10; &gt;AKS announces&lt;/a&gt; the planned date of a new minor version release and corresponding old version deprecation via AKS &lt;a class="link" href="https://github.com/Azure/AKS/releases" target="_blank" rel="noopener"&#10; &gt;Release notes&lt;/a&gt; at least 30 days prior to removal. An email notification is sent to the subscription administrators with the planned version removal dates. You get 30 days from version removal to upgrade to a supported minor version release. Patch versions can be released anytime and you get 30 days from the removal date to upgrade to a supported patch version.&lt;/p&gt;&#10;&lt;p&gt;You should test new target Kubernetes versions and upgrade your AKS clusters in a timely manner. For that, it is necessary to proactively monitor the AKS release notes. This can easily become a chore in a large Enterprise environment. Here is a Powershell script to make it easier to stay on top of Kubernetes version releases in AKS and publish/share it with others: &lt;a class="link" href="https://gist.github.com/ashtewari/31ce67d89b1878db52ca7558dbb3e194" target="_blank" rel="noopener"&#10; &gt;Get-AksSupportedVersions&lt;/a&gt;.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$aksVersionsJson = az aks get-versions --location eastus&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$aksVersions = $aksVersionsJson | ConvertFrom-Json&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$aksVersions.orchestrators.upgrades.orchestratorVersion&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$data = $aksVersions.orchestrators | Select-Object `&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -Property @{Name=&amp;#34;Version&amp;#34;;Expression={$_.orchestratorVersion}} `&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; , @{Name=&amp;#34;Default&amp;#34;;Expression={$_.default}} `&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; , @{Name=&amp;#34;Preview&amp;#34;;Expression={$_.isPreview}} `&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; , @{Name=&amp;#34;Upgrades&amp;#34;;Expression={$_.upgrades.orchestratorVersion -join &amp;#34;, &amp;#34;}}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$versionTable = $data | ConvertTo-Html -Fragment&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$versionTableString = $versionTable | Out-String&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$html = New-Object -ComObject &amp;#34;HTMLFile&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$html.IHTMLDocument2_write($versionTableString)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$tables = $html.body.getElementsByTagName(&amp;#34;table&amp;#34;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$rptString = &amp;#34;&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ForEach($table in $tables){&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ForEach($row in $table.rows){&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; $cellCount = 0&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ForEach($cell in $row.cells){&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; $cellCount++&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; if(($cellCount -eq 2) -and ($cell.innertext -eq &amp;#39;True&amp;#39;))&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; $row.className = &amp;#34;OkStatus&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; if(($cellCount -eq 3) -and ($cell.innertext -eq &amp;#39;True&amp;#39;))&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; $row.className = &amp;#34;WarningStatus&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; $rptString += $table.outerHTML&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$reportDate = $(get-date -DisplayHint DateTime) | Out-String&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$fileTS = $(get-date -Format &amp;#34;yyyyMMdd&amp;#34;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$fileName = &amp;#34;aks-versions-$fileTS.html&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$rptTitle = &amp;#34;&amp;lt;h1&amp;gt;AKS Kubernetes Versions&amp;lt;/h1&amp;gt;&amp;lt;p&amp;gt;$reportDate&amp;lt;/p&amp;gt;&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$report = ConvertTo-Html -Title &amp;#34;AKS Versions&amp;#34; -Body &amp;#34;$rptTitle $rptString&amp;#34; -Head $header&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$report | Out-File &amp;#34;$fileName&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It creates an HTML report of currently supported Kubernetes versions in AKS along with their respective upgrade paths. Here is an example of the report generated by the script referenced above :&lt;/p&gt;&#10;&lt;h1 id="aks-kubernetes-versions"&gt;AKS Kubernetes Versions&#10;&lt;/h1&gt;&lt;p&gt;Sunday, November 8, 2020 8:47:42 PM&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Version&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Default&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Preview&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Upgrades&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1.16.13&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1.16.15, 1.17.9, 1.17.11&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1.16.15&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1.17.9, 1.17.11&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1.17.9&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1.17.11, 1.18.6, 1.18.8&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1.17.11&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;True&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1.18.6, 1.18.8&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1.18.6&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1.18.8, 1.19.0&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1.18.8&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1.19.0&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1.19.0&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;True&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;Reference: &lt;a class="link" href="https://adamtheautomator.com/powershell-convertto-html/" target="_blank" rel="noopener"&#10; &gt;How To Create An HTML Report With PowerShell&lt;/a&gt;&lt;/p&gt;&#10;</description></item><item><title>Ethereum Blockchain-As-A-Service in Azure Cloud</title><link>https://www.tewari.info/2016/10/18/ethereum-blockchain-as-a-service-in-azure-cloud/</link><pubDate>Tue, 18 Oct 2016 21:24:56 +0000</pubDate><guid>https://www.tewari.info/2016/10/18/ethereum-blockchain-as-a-service-in-azure-cloud/</guid><description>&lt;p&gt;&lt;a class="link" href="https://www.ethereum.org/" target="_blank" rel="noopener"&#10; &gt;Ethereum&lt;/a&gt; Blockchain (EBaas) as a Service provided by Microsoft Azure and &lt;a class="link" href="https://consensys.net/ventures/core-components/" target="_blank" rel="noopener"&#10; &gt;ConsenSys&lt;/a&gt; allows for enterprise customers and partners to play, learn, and fail fast at a low cost in a ready-made dev/test/production environment. It will allow them to create private, public and consortium based &lt;a class="link" href="https://en.wikipedia.org/wiki/Blockchain" target="_blank" rel="noopener"&#10; &gt;Blockchain&lt;/a&gt; environments very quickly in Azure. In this session, you will learn how to get started with prototyping building blocks of a decentralized application using &lt;a class="link" href="http://https://azure.microsoft.com/en-us/blog/ethereum-blockchain-as-a-service-now-on-azure/" target="_blank" rel="noopener"&#10; &gt;EBaas&lt;/a&gt; in Windows Azure.&lt;/p&gt;&#10;&lt;p&gt;Venue: &lt;a class="link" href="https://www.meetup.com/Triangle-Azure-User-Group-TriAUG/events/234696204/" target="_blank" rel="noopener"&#10; &gt;Triangle Azure User Group&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;Slides : &lt;a class="link" href="https://1drv.ms/b/s!Asey4wYZPY_tgZWFPzq9KvZszYbOass" target="_blank" rel="noopener"&#10; &gt;download&lt;/a&gt;.&lt;/p&gt;&#10;</description></item><item><title>KeyNode with Node.js and Microsoft Azure</title><link>https://www.tewari.info/2016/03/10/keynode-with-node-js-and-microsoft-azure/</link><pubDate>Fri, 11 Mar 2016 00:23:06 +0000</pubDate><guid>https://www.tewari.info/2016/03/10/keynode-with-node-js-and-microsoft-azure/</guid><description>&lt;p&gt;&lt;a class="link" href="https://github.com/ashtewari/keynode" title="KeyNode"&#10; target="_blank" rel="noopener"&#10; &gt;KeyNode&lt;/a&gt; is a application to issue and verify software license keys. Technology stack for KeyNode is &lt;a class="link" href="https://nodejs.org/" title="Node.js"&#10; target="_blank" rel="noopener"&#10; &gt;Node.j&lt;/a&gt; s, &lt;a class="link" href="https://www.mongodb.org/" title="MongoDB"&#10; target="_blank" rel="noopener"&#10; &gt;MongoDB&lt;/a&gt; and &lt;a class="link" href="https://azure.microsoft.com/" title="Microsoft Azure"&#10; target="_blank" rel="noopener"&#10; &gt;Microsoft Azure&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;I had built this functionality with &lt;a class="link" href="https://c9.io" title="C9.io"&#10; target="_blank" rel="noopener"&#10; &gt;C9.io&lt;/a&gt; (a cloud-based IDE with a built-in source code repository and debugger), &lt;a class="link" href="http://www.prnewswire.com/news-releases/mongohq-becomes-compose-270174611.html" target="_blank" rel="noopener"&#10; &gt;mongohq&lt;/a&gt; (MongoDB as a service - now part of &lt;a class="link" href="https://www.compose.io/" title="compose.io"&#10; target="_blank" rel="noopener"&#10; &gt;compose.io&lt;/a&gt;) and &lt;a class="link" href="https://www.ctl.io/appfog/" title="AppFog"&#10; target="_blank" rel="noopener"&#10; &gt;appfog&lt;/a&gt; (Cloud PAAS built on top of &lt;a class="link" href="https://www.cloudfoundry.org/" title="CloudFoundry"&#10; target="_blank" rel="noopener"&#10; &gt;CloudFoundry&lt;/a&gt;). It used SMTP/gmail to email license files. That was the version I created a couple of years ago to issue tamper-proof signed xml license files for &lt;a class="link" href="http://codedemo.net/" target="_blank" rel="noopener"&#10; &gt;CodeDemo&lt;/a&gt; (a code snippet tool for developers, presenters and instructors).&lt;/p&gt;&#10;&lt;p&gt;For KeyNode (open source) I switched to a different toolset : &lt;a class="link" href="https://code.visualstudio.com/" title="Visual Studio Code"&#10; target="_blank" rel="noopener"&#10; &gt;Visual Studio Code&lt;/a&gt; and &lt;a class="link" href="https://azure.microsoft.com/" target="_blank" rel="noopener"&#10; &gt;Windows Azure&lt;/a&gt;, simplified the code to remove signed xml file and open-sourced it on &lt;a class="link" href="https://github.com/ashtewari/keynode" target="_blank" rel="noopener"&#10; &gt;GitHub&lt;/a&gt;. Signed xml allowed offline verification in CodeDemo (a Wpf/Desktop app). Removing signed xml requires verification to happen online. I am working on adding the web endpoint for verification of license keys. This version uses &lt;a class="link" href="https://sendgrid.com/" title="SendGrid"&#10; target="_blank" rel="noopener"&#10; &gt;SendGrid&lt;/a&gt; to email license keys. KeyNode is deployed as a &lt;a class="link" href="https://azure.microsoft.com/en-us/services/app-service/web/" target="_blank" rel="noopener"&#10; &gt;Windows Azure Web App&lt;/a&gt;. The Azure Web App is on Continuous Deployment feed from the source code repository on GitHub.&lt;/p&gt;&#10;&lt;p&gt;I created and tested this Node.js application locally without IIS and deployed it as an Azure Web App without making any changes to the code at all. Node.js applications are hosted in Azure under IIS with &lt;a class="link" href="https://github.com/tjanczuk/iisnode" title="iisnode"&#10; target="_blank" rel="noopener"&#10; &gt;iisnode&lt;/a&gt;. &lt;a class="link" href="https://github.com/tjanczuk/iisnode" target="_blank" rel="noopener"&#10; &gt;Iisnode&lt;/a&gt; is a native IIS module that allows hosting of node.js applications in IIS on Windows. Read more about iisnode &lt;a class="link" href="https://tomasz.janczuk.org/2011/08/hosting-nodejs-applications-in-iis-on.html" target="_blank" rel="noopener"&#10; &gt;here&lt;/a&gt;. Iisnode architecture also makes it significantly &lt;a class="link" href="https://blogs.msdn.microsoft.com/hanuk/2012/05/04/top-benefits-of-running-node-js-on-windows-azure/" target="_blank" rel="noopener"&#10; &gt;easier&lt;/a&gt; to take advantages of scalability afforded by Azure.&lt;/p&gt;&#10;&lt;p&gt;KeyNode is a work in progress. My plan is to use this as the basis for further explorations in the following areas :&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;DevOps, Docker and Microservices (at miniature scale of course!)&lt;/li&gt;&#10;&lt;li&gt;Create a Web UI with &lt;a class="link" href="http://expressjs.com/" target="_blank" rel="noopener"&#10; &gt;Express&lt;/a&gt;(a Node.js web application framework)&lt;/li&gt;&#10;&lt;li&gt;Integrate with &lt;a class="link" href="https://azure.microsoft.com/en-us/services/storage/" target="_blank" rel="noopener"&#10; &gt;Azure Storage/Queues&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;and more&amp;hellip;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;I invite you to check out the &lt;a class="link" href="http://keynode.azurewebsites.net/" target="_blank" rel="noopener"&#10; &gt;live site on Azure&lt;/a&gt; and fork it for your own experiments : &lt;a class="link" href="https://github.com/ashtewari/keynode" target="_blank" rel="noopener"&#10; &gt;KeyNode on GitHub&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;Resources :&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://azure.microsoft.com/en-us/develop/nodejs/" target="_blank" rel="noopener"&#10; &gt;Node.js Developer Center&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://tomasz.janczuk.org/2011/08/hosting-nodejs-applications-in-iis-on.html" target="_blank" rel="noopener"&#10; &gt;Hosting Node.js Applications in IIS on Windows&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://blogs.msdn.microsoft.com/hanuk/2012/05/04/top-benefits-of-running-node-js-on-windows-azure/" target="_blank" rel="noopener"&#10; &gt;Top Benefits of Running Node.js on Windows Azure&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Photo Credit : Piano Keyboard (&lt;a class="link" href="https://www.kpmalinowski.pl/" target="_blank" rel="noopener"&#10; &gt;www.kpmalinowski.pl&lt;/a&gt;)&lt;/p&gt;&#10;</description></item><item><title>PackRaft with Xamarin and Microsoft Azure</title><link>https://www.tewari.info/2015/12/19/packraft-with-xamarin-and-microsoft-azure/</link><pubDate>Sun, 20 Dec 2015 03:22:26 +0000</pubDate><guid>https://www.tewari.info/2015/12/19/packraft-with-xamarin-and-microsoft-azure/</guid><description>&lt;p&gt;PackRaft is a cross-platform mobile application built with &lt;a class="link" href="http://developer.xamarin.com/" target="_blank" rel="noopener"&#10; &gt;Xamarin&lt;/a&gt;. It integrates with Microsoft Azure services, such as &lt;a class="link" href="https://azure.microsoft.com/en-us/services/storage/tables/" target="_blank" rel="noopener"&#10; &gt;Azure Table Storage&lt;/a&gt; and &lt;a class="link" href="https://azure.microsoft.com/en-us/services/app-service/" target="_blank" rel="noopener"&#10; &gt;Azure App Service&lt;/a&gt;. It is created with &lt;a class="link" href="http://developer.xamarin.com/guides/cross-platform/xamarin-forms/" target="_blank" rel="noopener"&#10; &gt;Xamarin.Forms&lt;/a&gt; for iOS and Android (Windows version will be coming soon).&lt;/p&gt;&#10;&lt;p&gt;&lt;a class="link" href="https://www.tewari.info/wp-content/packraft-android-riders.png" &gt;&lt;img alt="packraft-android-riders" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://www.tewari.info/wp-content/packraft-android-riders.png"&gt;&lt;/a&gt;&lt;a class="link" href="https://www.tewari.info/wp-content/packraft-iphone-assignments.png" &gt;&lt;img alt="packraft-iphone-assignments" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://www.tewari.info/wp-content/packraft-iphone5.png"&gt;&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;The purpose of creating this sample application was to expand expertise in cross-platform mobile development with Windows Azure integration, specifically with Xamarin.Forms and Azure App Service. With Xamarin.Forms, it is possible to achieve close to 100% code reuse across these platforms for certain type of apps. Azure App Service provide a set of rich cloud backend services for your mobile applications.&lt;/p&gt;&#10;&lt;p&gt;&lt;a class="link" href="https://www.tewari.info/wp-content/PackRaft-Platform1.png" &gt;&lt;img alt="PackRaft-Platform" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://www.tewari.info/wp-content/PackRaft-Platform1.png"&gt;&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;PackRaft allows the user to setup a fleet of vehicles of various seating capacities. Groups of riders are assigned to these rides according to the count of riders in each group. You can specify that certain groups be kept together on a single vehicle - and the app tries to honor those requests if possible. I did this via a rudimentary implementation of a bin packing algorithm. This implementation is deployed as an &lt;a class="link" href="https://azure.microsoft.com/en-us/services/app-service/api/" target="_blank" rel="noopener"&#10; &gt;API App&lt;/a&gt; in Microsoft Azure.&lt;/p&gt;&#10;&lt;p&gt;PackRaft uses &lt;a class="link" href="https://github.com/Azure/azure-mobile-services" target="_blank" rel="noopener"&#10; &gt;Azure Mobile Services SDK&lt;/a&gt; to persist Fleet data to Azure Table Storage and to authenticate users with Facebook and Microsoft accounts. Twitter and Google account authentication is also supported by Azure Mobile Services SDK out of the box and those will also be enabled in PackRaft soon.&lt;/p&gt;&#10;&lt;p&gt;PackRaft is available on Android, iPhone and iPad.&#10;&lt;a class="link" href="https://play.google.com/store/apps/details?id=com.ashtewari.packmyride&amp;amp;utm_source=global_co&amp;amp;utm_medium=prtnr&amp;amp;utm_content=Mar2515&amp;amp;utm_campaign=PartBadge&amp;amp;pcampaignid=MKT-AC-global-none-all-co-pr-py-PartBadges-Oct1515-1" target="_blank" rel="noopener"&#10; &gt;&lt;img alt="Get it on Google Play" class="gallery-image" data-flex-basis="810px" data-flex-grow="337" height="242" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://play.google.com/intl/en_us/badges/images/apps/en-play-badge.png" srcset="https://www.tewari.info/en-play-badge_8894513479543131546_hu_5bf219cf694dcfc4.png 800w, https://play.google.com/intl/en_us/badges/images/apps/en-play-badge.png 817w" width="817"&gt;&lt;/a&gt;&lt;a class="link" href="https://itunes.apple.com/us/app/packraft/id1059614859" target="_blank" rel="noopener"&#10; &gt;&lt;img alt="Download iOS app on App Store" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://www.tewari.info/images/Download_on_the_App_Store_Badge_US-UK_135x40.svg"&gt;&lt;/a&gt;&lt;/p&gt;&#10;</description></item><item><title>NoSQL in Windows Azure</title><link>https://www.tewari.info/2014/05/02/nosql-in-windows-azure/</link><pubDate>Fri, 02 May 2014 19:36:21 +0000</pubDate><guid>https://www.tewari.info/2014/05/02/nosql-in-windows-azure/</guid><description>&lt;p&gt;You may have questions like these - What are my NoSQL options in Windows Azure? Can I run MongoDB on linux on Windows Azure? Why would I want to do that? Which one of the NoSQL options are available as managed services? Are all kinds of NoSQL databases available? Are there any limitations? What are options for data analytics?&#10;Or may be you have questions like these - Is it true that NoSQL databases don&amp;rsquo;t have schema? Do they have indexes? What about Views? Should I choose MongoDB or Redis? What is eventual consistency?&#10;In this presentation, you will learn all about NoSQL technologies in Windows Azure Cloud Platform. And you will get get answers to questions like these.&#10;Carolina Code Camp - 2014&lt;/p&gt;&#10;</description></item><item><title>SQL Azure Notes</title><link>https://www.tewari.info/2009/09/10/sql-azure-notes/</link><pubDate>Fri, 11 Sep 2009 03:45:26 +0000</pubDate><guid>https://www.tewari.info/2009/09/10/sql-azure-notes/</guid><description>&lt;p&gt;My invitation code for &lt;a class="link" href="http://www.microsoft.com/azure/sql.mspx" target="_blank" rel="noopener"&#10; &gt;SQL Azure CTP&lt;/a&gt; finally arrived. I had been waiting for this.&lt;/p&gt;&#10;&lt;p&gt;&lt;img alt="sqlazure-manage.png" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://www.tewari.info/images/sqlazure-manage.png"&gt;&lt;/p&gt;&#10;&lt;p&gt;You can create a database in from the SQL Azure Management page, shown above.&lt;/p&gt;&#10;&lt;p&gt;After you have created the database, of course, you can use &lt;a class="link" href="https://www.tewari.info/dbupdater/" &gt;DbUpdater&lt;/a&gt; with your SQL Azure databases. Here is the command-line :&#10;DbUpdater.exe ..\SqlScripts /server:&amp;ldquo;tcp:SERVER.ctp.database.windows.net&amp;rdquo; /db:DATABASE /user:&amp;ldquo;USER@SERVER&amp;rdquo; /password:PASSWORD /confirm:true&lt;/p&gt;&#10;&lt;p&gt;And you will need this DbUpdater.exe.config file :&lt;/p&gt;&#10;&lt;?xml version="1.0" encoding="utf-8" ?&gt;&#10;&lt;configuration&gt;&#10;&lt;appSettings&gt;&#10;&lt;add key="file-prefix" value="db" /&gt;&#10;&lt;add key="file-extension" value="sql" /&gt;&#10;&lt;add key="schema-versions-name" value="schema-versions-table.sql" /&gt;&#10;&lt;add key="baseline-name" value="baseline.sql" /&gt;&#10;&lt;add key="post-script-prefix" value="post" /&gt;&#10;&lt;add key="post-script-always-apply" value="true" /&gt;&#10;&lt;add key="exe-file" value="C:\\Program Files\\Microsoft SQL Server\\90\\Tools\\Binn\\SQLCMD.EXE" /&gt;&#10;&lt;add key="exe-args" value='-U %UserName% -P %Password% -S "%ServerName%" -d %DbName% -i "%SqlFile%"' /&gt;&#10;&lt;add key="dialect" value="NHibernate.Dialect.MsSql2000Dialect" /&gt;&#10;&lt;add key="driver\_class" value="NHibernate.Driver.SqlClientDriver" /&gt;&#10;&lt;add key="connection\_string" value="Server=%ServerName%;Database=%DbName%;User ID=%UserName%;Password=%Password%;Trusted\_Connection=False;" /&gt;&#10;&lt;/appSettings&gt;&#10;&lt;/configuration&gt;&#10;&lt;p&gt;You can run sqlcmd.exe directly to execute t-sql scripts on SQL Azure. Here is a sample command :&#10;sqlcmd.exe -S SERVER.ctp.database.windows.net -U USER@SERVER -P PASSWORD -d DB -i &amp;ldquo;baseline.sql&amp;rdquo;&lt;/p&gt;&#10;&lt;p&gt;I received these errors when I executed my SQL Server 2005 scripts on a SQL Azure database -&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&amp;lsquo;ANSI_NULLS&amp;rsquo; is not a recognized SET option.&lt;/li&gt;&#10;&lt;li&gt;Deprecated feature &amp;lsquo;SET ANSI_PADDING OFF&amp;rsquo; is not supported in this version of SQL Server.&lt;/li&gt;&#10;&lt;li&gt;Deprecated feature &amp;lsquo;More than two-part column name&amp;rsquo; is not supported in this version of SQL Server.&lt;/li&gt;&#10;&lt;li&gt;Deprecated feature &amp;lsquo;Data types: text ntext or image&amp;rsquo; is not supported in this version of SQL Server.&lt;/li&gt;&#10;&lt;li&gt;Deprecated feature &amp;lsquo;Table hint without WITH&amp;rsquo; is not supported in this version of SQL Server.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;These are not the limitations of the SQL Azure platform. SQL Azure is based on SQL Server 2008 Database Engine. The complete list of deprecated features in SQL Server 2008 are listed here along with the replacements (if any) : &lt;a class="link" href="http://msdn.microsoft.com/en-us/library/ms143729.aspx" target="_blank" rel="noopener"&#10; &gt;Deprecated Database Engine Features in SQL Server 2008 - MSDN.&lt;/a&gt;&#10;After modifying the scripts to replace the deprecated features, the scripts could be executed without any further issues. All stored procedures, triggers and views were created without any errors.&lt;/p&gt;&#10;&lt;p&gt;The following error is a SQL Azure limitation.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&amp;lsquo;Filegroup reference and partitioning scheme&amp;rsquo; is not supported in this version of SQL Server.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Here is more detailed information on unsupported T-SQL - &lt;a class="link" href="http://msdn.microsoft.com/en-us/library/ee336253.aspx" target="_blank" rel="noopener"&#10; &gt;Unsupported Transact-SQL Statements (SQL Azure Database) - MSDN&lt;/a&gt;&lt;/p&gt;&#10;</description></item></channel></rss>