<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Tesh.Digital]]></title><description><![CDATA[Tesh.Digital]]></description><link>https://tesh.digital</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1698003361115/Vgwe6sJlD.png</url><title>Tesh.Digital</title><link>https://tesh.digital</link></image><generator>RSS for Node</generator><lastBuildDate>Wed, 07 Oct 2026 00:16:30 GMT</lastBuildDate><atom:link href="https://tesh.digital/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Building a Serverless Video Generation Worker]]></title><description><![CDATA[Intro
I've been experimenting with AI video generation for a few personal projects, including music videos for my fictional band, Super Tankers. What started as wanting to make a few clips ended up wi]]></description><link>https://tesh.digital/building-a-serverless-video-generation-worker</link><guid isPermaLink="true">https://tesh.digital/building-a-serverless-video-generation-worker</guid><category><![CDATA[Runpod]]></category><category><![CDATA[ai video]]></category><category><![CDATA[storytelling]]></category><category><![CDATA[E2E]]></category><category><![CDATA[hybrid infrastructure]]></category><dc:creator><![CDATA[James]]></dc:creator><pubDate>Sun, 13 Sep 2026 10:44:42 GMT</pubDate><content:encoded><![CDATA[<h2>Intro</h2>
<p>I've been experimenting with AI video generation for a few personal projects, including music videos for my fictional band, Super Tankers. What started as wanting to make a few clips ended up with me building a serverless worker and a studio application to manage the process.</p>
<p>I've got an RTX 5080 in my PC, which is a very capable card, but it has 16 GB of VRAM. For plenty of use cases that's more than enough. For the video generation I wanted to do, though, it became a limitation. There’s only so far you can push things when the model and workflow need more memory than you have available.</p>
<p>I wasn't about to buy another GPU just for this, so I started looking at renting the hardware instead. Runpod gave me a way to experiment with GPUs with considerably more VRAM, without having to own one or keep a server running all the time.</p>
<p>I didn't want another monthly subscription to an AI generation service either. I wanted to pay for what I used, have more control over the workflow, and experiment with models I'd chosen myself, including open-source options.</p>
<p>I also wanted to submit jobs from my own application rather than manually run everything through ComfyUI. The idea was simple enough: send a request, have a worker run the workflow, and get a video back.</p>
<p>As usual, the idea was simpler than getting it all working.</p>
<h2>The Goal</h2>
<p>The goal was to make video generation something I could call from my own studio application.</p>
<p>ComfyUI would still do the generation, while the application would handle submitting jobs and keeping track of the results. I wanted to organise projects, develop prompts and iterate on clips without manually managing every run.</p>
<p>Serverless made sense for how I was using it. I might generate a batch of videos, then spend time reviewing them, editing or doing something else entirely. I didn't need a GPU server sitting there running throughout that.</p>
<p>I also wanted the option to use larger GPUs and run jobs concurrently. The approach I've settled on is to use my local card for drafts and experimentation, then use remote hardware for the more demanding generations once I'm happy with the direction.</p>
<p>The first milestone was getting one request to work from beginning to end: package the environment, submit the workflow, generate the video and retrieve the result. The studio application and worker now work together, and I've used them to generate coherent sequences for my projects.</p>
<h2>Issues Encountered Along the Way</h2>
<p>Getting a ComfyUI workflow running locally doesn't automatically mean it will work inside a remote worker.</p>
<p>The graph is only part of it. You also need the models, custom nodes and the right dependencies. Those all need to be available inside the container, with versions that work together. Otherwise, you've got a workflow that looks valid but can't actually execute.</p>
<p>I used a Runpod ComfyUI worker image as the starting point, then worked through getting my video workflow running within it. Having a base image helped, but there was still plenty to configure and debug.</p>
<h3>Where to put the models</h3>
<p>One decision I changed along the way was where to store the models.</p>
<p>Initially, I tried including them directly in the container image. That seemed straightforward: everything the worker needed would be packaged together. In practice, downloading the models during the build and then having to pull the resulting image became a limitation. These are large files, and it made iterating on the worker slower than I wanted.</p>
<p>I opted to store the models on a Runpod volume instead. That meant they could persist independently of the container, ready to be used whenever a worker needed them. I could rebuild the worker without repeatedly packaging the same model files into the image.</p>
<p>It also meant a change to the worker code didn't need to involve moving all the models around again. That made the setup much more practical while I was still experimenting and fixing things.</p>
<h3>Dependencies and optimisations</h3>
<p>Some of the friction came from trying to get the software environment into the right state. I experimented with ComfyUI versions and attempted to install SageAttention to improve performance.</p>
<p>That ran into problems around package availability and missing compiler tooling. I ended up falling back to PyTorch attention so I could keep moving.</p>
<p>At that point, getting a working generation mattered more than spending another evening trying to make an optimisation install. Once I had something running, I would have a better basis for deciding where to spend that effort.</p>
<p>It also made the importance of version pinning fairly clear. If a container rebuild pulls in different dependencies, you can end up debugging something that worked previously, even though you haven't changed the workflow.</p>
<h3>VRAM and generation time</h3>
<p>Moving to a cloud GPU didn't mean I could stop thinking about memory.</p>
<p>My initial experiments included a worker with 32 GB of VRAM. That gave me more room than my local card, but the video outputs I was exploring were still demanding. I started looking at GPUs with considerably more memory, including 96 GB options.</p>
<p>Resolution and clip duration both mattered, alongside the model and workflow settings. I had to work out what was practical rather than assume that renting a bigger GPU would make everything quick.</p>
<p>I eventually started seeing generations around five minutes with a LoRA in the workflow. That's useful as a rough indication of my experience, but it isn't a general benchmark. To make a proper comparison, I'd need to record the GPU, output settings and whether the worker was already warm.</p>
<p>There was also a distinction between the time spent generating and the time spent getting the worker ready. From the application's point of view, both contribute to how long you're waiting for a result.</p>
<h3>Waiting for a GPU</h3>
<p>One trade-off I've encountered with serverless is that the GPU isn't always available when I want it.</p>
<p>Sometimes the wait for capacity in my selected region has been long. Having the models persisted on a volume helps avoid repeatedly downloading them, but it doesn't remove the wait for hardware to become available.</p>
<p>That matters when I'm trying to iterate on an idea. If every small prompt change involves waiting for a remote worker, it slows the creative process down.</p>
<p>Using my local GPU for drafts makes this more workable. I can experiment locally, work out the direction I want to take, then submit the more demanding versions remotely. The remote generations still need reviewing and sometimes another attempt, but I'm doing more of the initial experimentation without waiting for cloud capacity.</p>
<p>It's a trade-off I'm happy to make for this use case, though it means I can't assume a submitted job will start immediately.</p>
<h3>Debugging remote failures</h3>
<p>When something failed, the error returned to my application wasn't always enough to explain why.</p>
<p>Some failures included a large amount of workflow information, but I still needed to inspect the worker logs to find the underlying issue. That made debugging more involved than running the graph locally and watching what happened.</p>
<p>It pushed me to think more about what the application should report. If a generation fails, I want enough information to work out whether the problem is with the request, the workflow or the worker environment.</p>
<p>That becomes more important once you start submitting multiple jobs. Manually digging through logs is manageable while experimenting, but it's something I'd like to reduce as the application develops.</p>
<h3>Concurrency</h3>
<p>Once individual requests were working, I started increasing the maximum number of workers to run more jobs at once.</p>
<p>That helps with completing a batch when capacity is available, but it doesn't make each individual video generate faster. A faster generation and more simultaneous generations are different improvements.</p>
<p>It also means more GPU work can be running at the same time, so I want better visibility into the cost before treating concurrency as something to keep increasing. The useful measure is how much it costs to get a clip I can actually use, including the attempts that don't make it into the final edit.</p>
<h2>The End Game and What I Got Out of It</h2>
<p>The studio application now exists, and I've used it in combination with the worker to generate meaningful, coherent video sequences for my projects.</p>
<img src="https://cdn.hashnode.com/uploads/covers/65353697ce07d3ba21aaadd1/b5ab9964-b70a-4e41-8e5e-15d26e96c3af.png" alt="" style="display:block;margin:0 auto" />

<p>That matters to me because getting individual clips back was only part of the goal. I wanted a process I could use to develop an idea across several shots and produce something that worked as a sequence.</p>
<p>The combination of local drafts and remote generation has made that practical. My RTX 5080 still has a useful role, while the remote workers give me access to the memory needed for the more demanding outputs.</p>
<p>The project also gave me a reason to explore the infrastructure properly. I wanted the videos for something, so successful runs could feed directly into the creative work.</p>
<p>The combination of local drafts and remote generation has made the process practical for me. My RTX 5080 still has a useful role, while the remote workers give me access to the memory needed for the more demanding outputs.</p>
<p>Building this gave me practical experience with packaging an AI workload, debugging it remotely and working through the trade-offs around GPU memory, generation time, availability and concurrency. More importantly, I've used the application and worker together to create coherent sequences for projects I actually wanted to make.</p>
<p>The <a href="https://github.com/JTSG1/comfyui-minimax-upscale">worker code is available on GitHub</a>, including the handler, Dockerfile and workflow configuration. My first priority was getting the generation process working from beginning to end. Clearer errors, more reproducible builds and better measurements are the next improvements I want to make.</p>
<p>I'll cover the full creative workflow in a separate post, including a closer look at the studio app and how I take an idea through local drafts, remote generation and into a finished sequence.</p>
]]></content:encoded></item><item><title><![CDATA[Using AI to generate repetitive code automatically with Python]]></title><description><![CDATA[https://github.com/JTSG1/GreyhoundDash
I’m building GreyhoundDash, a monitoring dashboard that can run either self-hosted or as SaaS.
The dashboard is intended as a tool to help users monitor their self-hosted applications from a single view. This is...]]></description><link>https://tesh.digital/using-ai-to-generate-repetitive-code-automatically</link><guid isPermaLink="true">https://tesh.digital/using-ai-to-generate-repetitive-code-automatically</guid><category><![CDATA[llm]]></category><category><![CDATA[AI]]></category><category><![CDATA[ci-cd]]></category><category><![CDATA[tooling]]></category><category><![CDATA[boilerplate]]></category><dc:creator><![CDATA[James]]></dc:creator><pubDate>Tue, 01 Jul 2025 20:54:42 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/UopUfxghnWo/upload/e387920592d49e8e8b53dc29687af678.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://github.com/JTSG1/GreyhoundDash">https://github.com/JTSG1/GreyhoundDash</a></p>
<p>I’m building <strong>GreyhoundDash</strong>, a monitoring dashboard that can run either self-hosted or as SaaS.</p>
<p>The dashboard is intended as a tool to help users monitor their self-hosted applications from a single view. This is definitely not a unique endeavour as there are many others like Heimdal. But it serves as an interesting platform to base a few different experiments from.</p>
<p>To recognise and health-check the long tail of open-source, self-hostable services, the dashboard needs a machine-readable definition for each one.</p>
<p>Hand-authoring those definitions doesn’t scale; the Awesome-Self-Hosted index alone lists hundreds of candidates.</p>
<p>Instead of grinding through them manually, I’m using large-language-model code generation to create the boilerplate classes automatically. This series documents the experiment—why I chose the approach, how the tooling is wired together, and what still needs tightening up.</p>
<h2 id="heading-requirements">Requirements</h2>
<p><strong>Core workflow</strong></p>
<ol>
<li><p>Detect new service</p>
<p> Scan the curated list (JSON produced from Awesome-Self-Hosted).</p>
</li>
<li><p>Generate code</p>
<p> Produce a Python subclass of <code>ServiceBase</code> that performs a simple “is-alive” check.</p>
</li>
<li><p>Create PR</p>
<p> Commit the file on a new branch, push, and open a pull-request back to <code>main</code>. Branch/PR names must be traceable (<code>feat/basic-svc-&lt;slug&gt;</code>, etc.).</p>
</li>
</ol>
<p><strong>Determinism &amp; safety</strong></p>
<ul>
<li><p>Same input → same output. If the model drifts, retry up to <em>n</em> times, then quarantine the service.</p>
</li>
<li><p>Validate the JSON schema with <em>Pydantic</em> before writing to disk; on failure, log and skip.</p>
</li>
</ul>
<p><strong>Cost &amp; noise controls</strong></p>
<ul>
<li><p><strong>Batching:</strong> group <em>10 services per PR</em> (<code>PR_FREQUENCY</code>) so reviewers aren’t flooded.</p>
</li>
<li><p><strong>Budget guard:</strong> stop once the estimated OpenAI spend or token quota for the run is hit.</p>
</li>
<li><p><strong>Checkpointing:</strong> persist a <code>completed_services.json</code> so reruns pick up where they left off.</p>
</li>
</ul>
<p>These constraints keep the process affordable and the repository maintainable while still letting the LLM do the grunt work. This article will focus on the first and third points.</p>
<h2 id="heading-harvesting-candidate-services">Harvesting candidate services</h2>
<p><strong>Source:</strong> <a target="_blank" href="https://awesome-selfhosted.net/#software">https://awesome-selfhosted.net/#software</a><br /><strong>Goal</strong>: Iterate over every entry and convert to a JSON format for easy parsing</p>
<p>This bit was relatively straight forwards, I decided to parse the content on this page <a target="_blank" href="https://awesome-selfhosted.net/#software">https://awesome-selfhosted.net/#software</a> (awesome project that I fully endorse) using the BS4 lib. I won’t go into specifics here but I will share the code:</p>
<p><strong>Extraction pipeline:</strong></p>
<p>1: Fetch the HTML</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">fetch_awesome_selfhosted</span>():</span>
    url = <span class="hljs-string">"https://awesome-selfhosted.net/#software"</span>
    response = requests.get(url)
    <span class="hljs-keyword">if</span> response.status_code != <span class="hljs-number">200</span>:
        <span class="hljs-keyword">raise</span> Exception(<span class="hljs-string">f"Failed to fetch the page: <span class="hljs-subst">{response.status_code}</span>"</span>)
    <span class="hljs-keyword">return</span> response.text
</code></pre>
<p>2: Parse with Beautiful Soup</p>
<p>BS is a great Python library used for parsing data from web pages, often used for web scraping. Parsing over HTML can be quite delicate, as any changes to the structure of the page can throw off the code.</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">extract_services</span>(<span class="hljs-params">html: str</span>) -&gt; dict[str, dict]:</span>
    soup = BeautifulSoup(html, <span class="hljs-string">"html.parser"</span>)
    services, skipped = {}, []

    <span class="hljs-keyword">for</span> s <span class="hljs-keyword">in</span> soup.select(<span class="hljs-string">"#software section"</span>):
        name = s.h3.get_text(strip=<span class="hljs-literal">True</span>)
        link = s.select_one(<span class="hljs-string">"a.external-link"</span>)
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> link:
            skipped.append(name); <span class="hljs-keyword">continue</span>
        services[to_camel(name)] = {
            <span class="hljs-string">"name"</span>: name,
            <span class="hljs-string">"description"</span>: (s.p <span class="hljs-keyword">or</span> <span class="hljs-string">""</span>).get_text(strip=<span class="hljs-literal">True</span>),
            <span class="hljs-string">"homepage"</span>: link[<span class="hljs-string">"href"</span>],
        }

    <span class="hljs-keyword">if</span> skipped:
        logging.warning(<span class="hljs-string">"Skipped %d services without a homepage: %s"</span>,
                        len(skipped), <span class="hljs-string">", "</span>.join(skipped))
    <span class="hljs-keyword">return</span> services
</code></pre>
<p>3: Persist</p>
<p>The list should be persisted so it can be used as input to the Language Model.</p>
<pre><code class="lang-python"><span class="hljs-keyword">with</span> open(<span class="hljs-string">"tooling/awesome_selfhosted_services.json"</span>, <span class="hljs-string">"w"</span>) <span class="hljs-keyword">as</span> f:
    <span class="hljs-keyword">import</span> json
    json.dump(result, f, indent=<span class="hljs-number">2</span>)
</code></pre>
<p>The scraper gives me a one-shot JSON cache with exactly the three fields the generator needs—<code>name</code>, <code>description</code>, <code>homepage</code>. Whenever Awesome-Self-Hosted changes, I just rerun the script and the new entries appear in the file.</p>
<p>It’s deliberately minimal:</p>
<ul>
<li><p><strong>No retry/back-off</strong> and no DOM diffing—acceptable for tooling, not for prod.</p>
</li>
<li><p>Relies on the current page structure; if the maintainers rename a class, the selector fails and I’ll fix it then.</p>
</li>
<li><p>Runs on demand, outside the dashboard’s runtime path, so a crash can’t hurt users.</p>
</li>
</ul>
<p>I also persist a <code>awesome_selfhosted_services_completed.json</code> checkpoint. Each code-gen run looks at that file first, skips anything already merged, and works on the remainder. Preventing duplicate processing and allows the code to pick what hasn’t been done only.</p>
<h2 id="heading-generating-the-service-stubs">Generating the service stubs</h2>
<p>The LLM must return something I can deserialize without guess-work, so the interaction is <em>schema-first</em>:</p>
<ol>
<li><p><strong>Schema contract</strong><br /> A single <code>ServiceResponseSchema</code> (Pydantic) drives both the OpenAI call and local validation:</p>
<pre><code class="lang-python"> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ServiceResponseSchema</span>(<span class="hljs-params">BaseModel</span>):</span>
     file_name: str      <span class="hljs-comment"># e.g. "service_grafana.py"</span>
     service: str        <span class="hljs-comment"># Human-readable name</span>
     code: str           <span class="hljs-comment"># The Python subclass itself</span>
     logo: str           <span class="hljs-comment"># URL or data-URI (future feature)</span>
     web_app: bool       <span class="hljs-comment"># True ⇢ include, False ⇢ skip</span>
</code></pre>
<p> Any field mismatch raises immediately; no fragile <code>json.loads()</code> spelunking.</p>
</li>
<li><p><strong>Prompt template</strong><br /> Stored in <code>tooling/service_generation_prompt.txt</code> with Jinja-like tokens:</p>
<pre><code class="lang-plaintext"> You are ChatGPT. Generate a subclass of {{ base_class_name }}.

 ``` python
 {{ base_class_file }}
 ```

 Sample implementations of this base class:

 ``` python
 {{ implementation_samples }}
 ```
 Response must comply with the below schema:

 ``` json
 {{ json_schema }}
 ```
</code></pre>
<p> At runtime the script simply <code>.replace()</code>s the placeholders—no template engine needed.</p>
</li>
</ol>
<p><em>Why JSON over code blocks?</em><br />Without a rigid envelope, the model may prepend helper text or miss imports, which breaks CI. JSON gives me a single thing to parse—and Pydantic enforces it at runtime.</p>
<p><em>Why a bare string replace instead of Jinja?</em><br />The prompt is <em>configuration</em>, not <em>application</em> code; simplicity beats another dependency.</p>
<p>Future cleanup: swap the naïve <code>.replace()</code> for <code>str.format_map()</code> so accidental <code>{{ }}</code> inside comments don’t expand.</p>
<h2 id="heading-wiring-the-pipeline">Wiring the pipeline</h2>
<p>The automation lives in <code>/tooling</code> and is split into two well-defined actors:</p>
<p>ServiceGenerator</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Actor</td><td>Responsibility</td><td>Why seperate</td></tr>
</thead>
<tbody>
<tr>
<td><code>ServiceGenerator</code></td><td>- Iterate over the JSON cache</td><td>Pure “business logic” &amp; prompt-handling; no Git side-effects.</td></tr>
<tr>
<td></td><td>- Call OpenAI, validate the <code>ServiceResponseSchema</code></td><td></td></tr>
<tr>
<td></td><td>- Write the new <code>service_*.py</code> files</td><td></td></tr>
<tr>
<td></td><td>- Update <code>awesome_selfhosted_services_completed.json</code></td><td></td></tr>
<tr>
<td><code>GithubPRCreator</code></td><td>- Spawn a branch</td><td>Keeps VCS concerns isolated; lets you swap GitHub for GitLab later.</td></tr>
<tr>
<td></td><td>- <code>git add/commit/push</code></td><td></td></tr>
<tr>
<td></td><td>- Open a pull-request via GitHub API</td></tr>
</tbody>
</table>
</div><h4 id="heading-githubprcreator-excerpt"><code>GithubPRCreator</code> (excerpt)</h4>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">create_branch_and_raise_pr</span>(<span class="hljs-params">self, branch, services</span>):</span>
    <span class="hljs-comment"># 1. git checkout -b &lt;branch&gt;</span>
    <span class="hljs-comment"># 2. git add -A &amp;&amp; git commit -m "feat: ..."</span>
    <span class="hljs-comment"># 3. git push -u origin &lt;branch&gt;</span>
    pr = self.repo.create_pull(
        title=<span class="hljs-string">f"gen: <span class="hljs-subst">{services[<span class="hljs-number">0</span>]}</span> → <span class="hljs-subst">{services[<span class="hljs-number">-1</span>]}</span>"</span>,
        body=<span class="hljs-string">"\n"</span>.join(<span class="hljs-string">f"<span class="hljs-subst">{n}</span>: <span class="hljs-subst">{ok}</span>"</span> <span class="hljs-keyword">for</span> n, ok <span class="hljs-keyword">in</span> services),
        head=branch,
        base=<span class="hljs-string">"main"</span>,
    )
</code></pre>
<p><em>Guard rails</em></p>
<ul>
<li><p>Refuses to reuse an existing branch (fast-fail).</p>
</li>
<li><p>Logs the PR URL so CI can post a comment or Slack alert later.</p>
</li>
</ul>
<h4 id="heading-servicegenerator-key-points"><code>ServiceGenerator</code> (key points)</h4>
<pre><code class="lang-python">PR_FREQUENCY = <span class="hljs-number">10</span>   <span class="hljs-comment"># services per PR</span>
BATCH_SIZE   = <span class="hljs-number">10</span>   <span class="hljs-comment"># services per run (keeps token spend predictable)</span>
GPT_MODEL    = <span class="hljs-string">"gpt-4o-mini"</span>
</code></pre>
<ol>
<li><p><strong>Prompt fill</strong> → deterministic JSON response (temperature = 0).</p>
</li>
<li><p><strong>Schema validation</strong> with Pydantic; on failure retry ≤ 5 then skip.</p>
</li>
<li><p><strong>Write file</strong> only when <code>web_app is True</code>.</p>
</li>
<li><p><strong>Book-keeping</strong> — append to <code>completed.json</code> so reruns resume cleanly.</p>
</li>
<li><p>Every <code>PR_FREQUENCY</code> successes → delegate to <code>GithubPRCreator</code>.</p>
</li>
</ol>
<h4 id="heading-entrypoint">Entrypoint</h4>
<pre><code class="lang-python"><span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    gh = GithubPRCreator(Github(os.getenv(<span class="hljs-string">"GH_TOKEN"</span>)), <span class="hljs-string">"JTSG1/GreyhoundDash"</span>)
    ServiceGenerator(openai_client=OpenAI(), github_pr_creator=gh).iterate_services()
</code></pre>
<p>The <code>main</code> block wires the two actors together; nothing else runs in global scope, which makes the module import-safe for future unit tests.</p>
<hr />
<p><em>Why this structure?</em><br />A tight, two-class design keeps the surface area small: one side talks to the LLM, the other to Git. Each part can be mocked in isolation, batteries included for retries, cost limits, and PR hygiene.Wrapping up</p>
<h3 id="heading-wrap-up">Wrap-up</h3>
<ul>
<li><p><strong>What’s solved?</strong><br />  GreyhoundDash can now turn a raw list of open-source services into ready-to-review Python stubs, batch-commit them, and open pull-requests—all without a human typing the same boilerplate thirty times.</p>
</li>
<li><p><strong>What’s still brittle?</strong></p>
<ul>
<li><p>Scraper depends on Awesome-Self-Hosted’s HTML staying stable.</p>
</li>
<li><p>Generated code is unit-tested only for syntax, not runtime behaviour.</p>
</li>
<li><p>Logo retrieval and licence compliance are TODOs.</p>
</li>
<li><p>OpenAI cost control is coarse (batch ceiling rather than per-token budgeting).</p>
</li>
</ul>
</li>
<li><p><strong>Immediate next steps</strong></p>
<ol>
<li><p>Add a CI job that lints each generated file and executes a dummy health-check.</p>
</li>
<li><p>Swap the HTML scrape for the project’s YAML source to decouple from DOM churn.</p>
</li>
<li><p>Enforce a spend cap via the OpenAI usage API and abort runs that hit it.</p>
</li>
<li><p>Store prompt + schema version in git so regressions are traceable.</p>
</li>
</ol>
</li>
<li><p><strong>Calling for help</strong></p>
<ul>
<li><p>Spot a service the scraper missed? Open an issue.</p>
</li>
<li><p>See a way to harden the GitHub automation (e.g., branch naming, PAT scope)? Let me know.</p>
</li>
</ul>
</li>
</ul>
<p>The repo is at <a target="_blank" href="http://github.com/JTSG1/GreyhoundDash"><strong>github.com/JTSG1/GreyhoundDash</strong></a>. Star it and keep up to date on the development!</p>
]]></content:encoded></item><item><title><![CDATA[Quick tip: using curl to pipe into docker compose]]></title><description><![CDATA[Ever needed to spin up a Docker Compose setup from a GitHub repo, but didn't want to clone the whole thing? I recently ran into this with Netflix Conductor. Instead of cloning or manually copying, try this:
Turns out we can use curl and pipe the cont...]]></description><link>https://tesh.digital/quick-tip-using-curl-to-pipe-into-docker-compose</link><guid isPermaLink="true">https://tesh.digital/quick-tip-using-curl-to-pipe-into-docker-compose</guid><category><![CDATA[Docker]]></category><category><![CDATA[Docker compose]]></category><category><![CDATA[GitHub]]></category><category><![CDATA[Devops]]></category><category><![CDATA[containers]]></category><category><![CDATA[quicktips]]></category><category><![CDATA[tips]]></category><dc:creator><![CDATA[James]]></dc:creator><pubDate>Mon, 16 Jun 2025 23:00:00 GMT</pubDate><content:encoded><![CDATA[<p>Ever needed to spin up a Docker Compose setup from a GitHub repo, but didn't want to clone the whole thing? I recently ran into this with Netflix Conductor. Instead of cloning or manually copying, try this:</p>
<p>Turns out we can use curl and pipe the contents into docker compose</p>
<pre><code class="lang-bash">curl https://raw.githubusercontent.com/Netflix/conductor/refs/heads/main/docker/docker-compose-postgres.yaml | docker compose -f - up -d
</code></pre>
<p>Just replace the URL with your desired <code>docker-compose.yml</code> location. Saves time and space!</p>
<p><em>The -f - flag tells Docker Compose to read the file from</em> <strong><em>stdin*</em></strong>, so nothing ever hits disk.*</p>
<p>Only run this against YAML you trust; you’re effectively ‘curl-piping’ commands that can start arbitrary containers</p>
]]></content:encoded></item><item><title><![CDATA[Taming the RX 7800 XT: Clean GPU Reset in Windows VMs Without a Reboot (QEMU)]]></title><description><![CDATA[Intro
I recently rebuilt my PC into a Fractal Mood — a minimalist case that blends beautifully with my office setup. That’s not the story though. This was the moment I decided to finally switch from Windows 11 to Linux.
I already spend most of my tim...]]></description><link>https://tesh.digital/taming-the-rx-7800-xt-clean-gpu-reset-in-windows-vms-without-a-reboot-qemu</link><guid isPermaLink="true">https://tesh.digital/taming-the-rx-7800-xt-clean-gpu-reset-in-windows-vms-without-a-reboot-qemu</guid><category><![CDATA[proxmox]]></category><category><![CDATA[passthrough]]></category><category><![CDATA[amd]]></category><category><![CDATA[fix]]></category><category><![CDATA[virtual machine]]></category><category><![CDATA[gaming]]></category><category><![CDATA[inference]]></category><category><![CDATA[AI]]></category><dc:creator><![CDATA[James]]></dc:creator><pubDate>Sun, 15 Jun 2025 21:27:35 GMT</pubDate><content:encoded><![CDATA[<h1 id="heading-intro">Intro</h1>
<p>I recently rebuilt my PC into a Fractal Mood — a minimalist case that blends beautifully with my office setup. That’s not the story though. This was the moment I decided to finally switch from Windows 11 to Linux.</p>
<p>I already spend most of my time in MacOS and GCP Linux environments, and I’ve been itching to remove Windows from my life entirely. Except for one thing: gaming.</p>
<p>Proton is amazing (cheers, Gabe!), but there are still games that just run better — or only — on Windows. My solution? A Proxmox-based homelab with GPU passthrough. Windows for gaming, Linux for everything else.</p>
<p>I was already aware of Proxmox, having used it in the past so I decided I really like the idea of turning my PC into more of a homelab underpinned by Proxmox goodness. My plan was to have a Windows VM with my RX 7800 XT passed through into it when required but also wanted to be able to shut that down and switch it over to a Linux VM for some light inference tasks.</p>
<h1 id="heading-the-problem">The Problem</h1>
<p>I was able to relatively easily setup the Windows guest with GPU passthrough, managed to install the operating system and also managed to pass my GPU through no problem. The issues started when I created a second Linux VM that I planned to run Ollama on. When I tried to shutdown the Windows VM and then start the Linux, the <strong>entire host</strong> would just go nope and crash. I had to reboot it to get back to where I needed to be.</p>
<p>I attempted many, many, many things to try and get to the behaviour that I wanted. One observation I did make was that the Linux guest (Fedora to be precise) released the graphics card properly. So if I shutdown the Linux guest and than booted up the Windows one, it worked fine. Not a problem the GPU switching to Windows. If I then shutdown Windows and tried to boot the Linux guest, it crashed. So I came to the conclusion that the problem actually lay with Windows.</p>
<p>Another observation I made, if I “stopped” the VM, as opposed to “shutdown” the GPU would release ok, even from Windows. But I wanted ACPI shutdown to avoid any data issues. It turns out, it’s Windows that wasn’t releasing the card properly.</p>
<p>Some of the things I tried without success:</p>
<ul>
<li><p>Installed QEMU drivers on guest</p>
</li>
<li><p>Installed QEMU agent</p>
</li>
<li><p>Attempted to pass the VBIOS into the VM via a rom file</p>
</li>
<li><p>Installed latest AMD drivers</p>
</li>
</ul>
<p>None of these things worked</p>
<h1 id="heading-the-fix">The Fix</h1>
<p>There is a utility in Windows called pnputil which can be used to interact with hardware. It can also be used to disable hardware via a command line. Using this tool I was able to get the Windows VM to shut down gracefully and release the GPU. The steps I followed were</p>
<ul>
<li><p>in CMD (elevated) run</p>
<pre><code class="lang-plaintext">  pnputil /enum-devices /class Display
</code></pre>
<ul>
<li><p>This will print out a load of information and your output may vary but look for the GPU you are interested in</p>
</li>
<li><pre><code class="lang-plaintext">    Instance ID:                PCI\VEN_1002&amp;DEV_747E&amp;SUBSYS_2427148C&amp;REV_C8\4&amp;23f93f77&amp;0&amp;00E0
    Device Description:         AMD Radeon RX 7800 XT
    Class Name:                 Display
    Class GUID:                 {4d36e968-e325-11ce-bfc1-08002be10318}
    Manufacturer Name:          Advanced Micro Devices, Inc.
    Status:                     Started
    Driver Name:                oem4.inf
</code></pre>
<p>  You want to make note of the Instance ID</p>
</li>
</ul>
</li>
<li><p>type “pnputil /disable-device "PCI\VEN_1002&amp;DEV_747E&amp;SUBSYS_2427148C&amp;REV_C8\4&amp;23f93f77&amp;0&amp;00E0" and you should get some positive response.</p>
</li>
<li><p><strong>Congratulations</strong> you can now shutdown the VM and release the GPU gracefully</p>
</li>
</ul>
<h1 id="heading-automate-it">Automate it</h1>
<p>Doing the above manually all the time would have been a trouble, but luckily we can easily automate this via gpedit.msc.</p>
<p>go here:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750022609117/ff1ed13d-5711-4a66-876d-6e3c0ec3f825.png" alt class="image--center mx-auto" /></p>
<p>create some scripts somewhere on your C: drive</p>
<h2 id="heading-cscriptsenable-gpubat">C:\scripts\enable-gpu.bat</h2>
<p>Create the below script, double click on Startup and browse to where you saved it</p>
<pre><code class="lang-plaintext">@echo off
pnputil /enable-device "PCI\VEN_1002&amp;DEV_747E&amp;SUBSYS_2427148C&amp;REV_C8\4&amp;23f93f77&amp;0&amp;00E0"
</code></pre>
<h2 id="heading-cscriptsdisable-gpubat">C:\scripts\disable-gpu.bat</h2>
<p>Same as above but for Shutdown</p>
<pre><code class="lang-plaintext">@echo off
pnputil /disable-device "PCI\VEN_1002&amp;DEV_747E&amp;SUBSYS_2427148C&amp;REV_C8\4&amp;23f93f77&amp;0&amp;00E0"
</code></pre>
<h1 id="heading-conclusion">Conclusion</h1>
<p>With this workaround, I can switch between Windows and Linux VMs without rebooting or crashing my host. No kernel patches, no risky hacks — just one native Windows command. If you’re fighting AMD GPU reset issues on Proxmox, this might just be your silver bullet.</p>
]]></content:encoded></item><item><title><![CDATA[K8s, Helm, and Container Orchestration]]></title><description><![CDATA[Introduction
Over the past year, my role has increasingly involved containerization technology, particularly Kubernetes. Kubernetes is a fantastic tool for deploying applications and enabling them to scale according to demand. Once you're comfortable...]]></description><link>https://tesh.digital/k8s-helm-and-container-orchestration</link><guid isPermaLink="true">https://tesh.digital/k8s-helm-and-container-orchestration</guid><category><![CDATA[Kubernetes]]></category><category><![CDATA[Helm]]></category><category><![CDATA[container orchestration]]></category><dc:creator><![CDATA[James]]></dc:creator><pubDate>Mon, 17 Mar 2025 20:02:06 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/GSiEeoHcNTQ/upload/7efd018704618a07e487765a57c0b835.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>Over the past year, my role has increasingly involved containerization technology, particularly Kubernetes. Kubernetes is a fantastic tool for deploying applications and enabling them to scale according to demand. Once you're comfortable with these technologies, you can really start to leverage the power of elastic computing.</p>
<p>A supporting repo can be found here: <a target="_blank" href="https://github.com/JTSG1/helm-kubernetes-tutorial">https://github.com/JTSG1/helm-kubernetes-tutorial</a></p>
<h2 id="heading-kubernetes">Kubernetes</h2>
<p>Kubernetes is a container orchestration platform. It allows you to take your Docker images and build production-ready workloads using YAML files. These files can be version-controlled and deployed through automated CI/CD pipelines. Sounds great, right?</p>
<p>Throughout this article, I'll provide some practical examples. Most of these will center around a service dashboard software called Homer. I've chosen Homer because it's relatively straightforward to get up and running, and I'm a big fan of the software for self-hosting.</p>
<h2 id="heading-helm">Helm</h2>
<p>Helm is essentially a templating system built on top of Kubernetes objects. It allows you to parameterize and manage releases centrally, offering greater control over your Kubernetes deployments. A great use case for Helm is the ability to tailor your application configuration to specific environments.</p>
<p>Imagine you've just developed the next big thing that's going to go viral and make you a fortune! If you're following good practices, you'll have several different environments set up to progress your deployment through various testing stages. Helm helps with this by allowing you to have a base configuration that you can then override for specific environments.</p>
<h2 id="heading-a-simple-kubernetes-deployment">A Simple Kubernetes Deployment</h2>
<p>We're going to look at a simplified set of Kubernetes manifests. Typically, in a production environment, you'd have more, but let's keep things simple for now. To deploy Homer, I want to define the following Kubernetes objects:</p>
<ul>
<li><p><strong>deployment</strong>: This contains the actual service.</p>
</li>
<li><p><strong>namespace</strong>: This is used to segregate objects, as Kubernetes clusters rarely host just one workload.</p>
</li>
<li><p><strong>service</strong>: This defines how we expose the application to the outside world.</p>
</li>
<li><p><strong>service account</strong>: This specifies the 'user' the service will run under, which is especially useful when deploying to services like GCP that offer workload federation.</p>
</li>
</ul>
<p>The file structure I'm aiming for will look like this:</p>
<pre><code class="lang-yaml"><span class="hljs-string">./homer-sample-no-helm</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">default-deployment.yaml</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">default-namespace.yaml</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">default-service.yaml</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">default-serviceaccount.yaml</span>
</code></pre>
<p>Here's the content of each of these files:</p>
<p><strong>default-deployment.yaml</strong></p>
<p>This object contains the actual container and its related settings.</p>
<p>YAML</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">apps/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Deployment</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">homer-deployment</span>
  <span class="hljs-attr">namespace:</span> <span class="hljs-string">ns-homer</span>
  <span class="hljs-attr">labels:</span>
    <span class="hljs-attr">app:</span> <span class="hljs-string">homer-deployment</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">strategy:</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">Recreate</span>
  <span class="hljs-attr">replicas:</span> <span class="hljs-number">1</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">matchLabels:</span>
      <span class="hljs-attr">app:</span> <span class="hljs-string">kafka-sample</span>
  <span class="hljs-attr">template:</span>
    <span class="hljs-attr">metadata:</span>
      <span class="hljs-attr">annotations:</span>
        <span class="hljs-attr">sampleAnnotation:</span> <span class="hljs-string">"a-sample-annotation"</span>
      <span class="hljs-attr">labels:</span>
        <span class="hljs-attr">app:</span> <span class="hljs-string">kafka-sample</span>
    <span class="hljs-attr">spec:</span>
      <span class="hljs-attr">serviceAccountName:</span> <span class="hljs-string">homer-service-account</span>
      <span class="hljs-attr">containers:</span>
      <span class="hljs-comment"># hello world container</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">hello-world</span>
        <span class="hljs-attr">image:</span> <span class="hljs-string">docker.io/b4bz/homer</span>
        <span class="hljs-attr">imagePullPolicy:</span> <span class="hljs-string">IfNotPresent</span>
        <span class="hljs-attr">ports:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">http</span>
          <span class="hljs-attr">containerPort:</span> <span class="hljs-number">8080</span>
          <span class="hljs-attr">protocol:</span> <span class="hljs-string">TCP</span>
        <span class="hljs-attr">resources:</span>
          <span class="hljs-attr">limits:</span>
            <span class="hljs-attr">cpu:</span> <span class="hljs-string">100m</span>
            <span class="hljs-attr">memory:</span> <span class="hljs-string">128Mi</span>
          <span class="hljs-attr">requests:</span>
            <span class="hljs-attr">cpu:</span> <span class="hljs-string">100m</span>
            <span class="hljs-attr">memory:</span> <span class="hljs-string">128Mi</span>
        <span class="hljs-attr">env:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">MY_ENV_VAR</span>
          <span class="hljs-attr">value:</span> <span class="hljs-string">"my-env-var-value"</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">MY_ENV_VAR_2</span>
          <span class="hljs-attr">value:</span> <span class="hljs-string">"my-env-var-value-2"</span>
      <span class="hljs-attr">restartPolicy:</span> <span class="hljs-string">Always</span>
</code></pre>
<p><strong>default-namespace.yaml</strong></p>
<p>This is the Kubernetes namespace definition for this deployment.</p>
<p>YAML</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Namespace</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">ns-homer</span>
  <span class="hljs-attr">labels:</span>
    <span class="hljs-attr">app:</span> <span class="hljs-string">ns-homer</span>
</code></pre>
<p><strong>default-service.yaml</strong></p>
<p>This service exposes the deployment externally using a LoadBalancer.</p>
<p>YAML</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Service</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">homer-deployment</span>
  <span class="hljs-attr">namespace:</span> <span class="hljs-string">ns-homer</span>
  <span class="hljs-attr">labels:</span>
    <span class="hljs-attr">app:</span> <span class="hljs-string">homer-deployment</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">ports:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">http</span>
    <span class="hljs-attr">port:</span> <span class="hljs-number">8080</span>
    <span class="hljs-attr">targetPort:</span> <span class="hljs-number">8080</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">app:</span> <span class="hljs-string">kafka-sample</span>
  <span class="hljs-attr">type:</span> <span class="hljs-string">LoadBalancer</span>
</code></pre>
<p><strong>default-serviceaccount.yaml</strong></p>
<p>This is the Kubernetes service account that the deployments will run under.</p>
<p>YAML</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">ServiceAccount</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">sa-homer-sample</span>
  <span class="hljs-attr">namespace:</span> <span class="hljs-string">ns-homer</span>
  <span class="hljs-attr">labels:</span>
    <span class="hljs-attr">app:</span> <span class="hljs-string">home-sample</span>
</code></pre>
<p>As you can already see, even for a relatively simple deployment, there are quite a few files. Maintaining these files separately for each of your RTL (Route to Live) environments would quickly become cumbersome. This is where Helm comes in handy.</p>
<p>Another thing you might notice is the amount of repetition across these files. A simple typo in one of these fields can lead to hours of debugging! Trust me, I've been there.</p>
<h2 id="heading-helm-to-the-rescue">Helm to the Rescue</h2>
<p>Now, let's look at the same set of files but within a Helm setup.</p>
<pre><code class="lang-yaml"><span class="hljs-string">./homer-sample</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">templates/</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">default-deployment.yaml</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">default-namespace.yaml</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">default-service.yaml</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">default-serviceaccount.yaml</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">Chart.yaml</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">values.yaml</span>
</code></pre>
<p>Inside the <code>templates</code> folder, we have the same set of files, but their content is different. We'll review that in a moment. Additionally, we also have two new files: <code>Chart.yaml</code> and <code>values.yaml</code>.</p>
<p><code>Chart.yaml</code> contains metadata about the Helm deployment.</p>
<p><code>values.yaml</code> is a central store for variables that we can reference in our Helm-templated Kubernetes manifests.</p>
<p>Now, let's re-examine the four manifest templates:</p>
<p><strong>default-deployment.yaml</strong></p>
<p>This file now contains references to <code>Values.*</code> variables, which are defined in the <code>values.yaml</code> file. You can immediately see the benefits of this approach to structuring these files. We are essentially templating the Helm files and injecting values defined elsewhere into them.</p>
<p>Also of note are the conditional statements included here.</p>
<p>YAML</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">apps/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Deployment</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> {{ <span class="hljs-string">.Values.deployment.name</span> }}
  <span class="hljs-attr">namespace:</span> {{ <span class="hljs-string">$.Values.namespace.name</span> }}
  <span class="hljs-attr">labels:</span>
    <span class="hljs-attr">app:</span> {{ <span class="hljs-string">.Values.deployment.name</span> }}
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">strategy:</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">Recreate</span>
  <span class="hljs-attr">replicas:</span> <span class="hljs-number">1</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">matchLabels:</span>
      <span class="hljs-attr">app:</span> <span class="hljs-string">kafka-sample</span>
  <span class="hljs-attr">template:</span>
    <span class="hljs-attr">metadata:</span>
      <span class="hljs-attr">annotations:</span>
        <span class="hljs-attr">sampleAnnotation:</span> <span class="hljs-string">"a-sample-annotation"</span>
        {{<span class="hljs-bullet">-</span> <span class="hljs-string">if</span> <span class="hljs-string">eq</span> <span class="hljs-string">.Values.conditionalAnnotations.enabled</span> <span class="hljs-literal">true</span>}}
        <span class="hljs-attr">conditionalAnnotation:</span> <span class="hljs-string">"a-conditional-annotation"</span>
        {{<span class="hljs-bullet">-</span> <span class="hljs-string">end</span> }}
      <span class="hljs-attr">labels:</span>
        <span class="hljs-attr">app:</span> <span class="hljs-string">kafka-sample</span>
    <span class="hljs-attr">spec:</span>
      <span class="hljs-attr">serviceAccountName:</span> {{ <span class="hljs-string">.Values.deployment.serviceAccountName</span> }}
      <span class="hljs-attr">containers:</span>
      <span class="hljs-comment"># hello world container</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">hello-world</span>
        <span class="hljs-attr">image:</span> <span class="hljs-string">docker.io/b4bz/homer</span>
        <span class="hljs-attr">imagePullPolicy:</span> <span class="hljs-string">IfNotPresent</span>
        <span class="hljs-attr">ports:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">http</span>
          <span class="hljs-attr">containerPort:</span> <span class="hljs-number">8080</span>
          <span class="hljs-attr">protocol:</span> <span class="hljs-string">TCP</span>
        <span class="hljs-attr">resources:</span>
          {{ <span class="hljs-string">toYaml</span> <span class="hljs-string">.Values.resources</span> <span class="hljs-string">|</span> <span class="hljs-string">nindent</span> <span class="hljs-number">10</span> }}
        <span class="hljs-attr">env:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">MY_ENV_VAR</span>
          <span class="hljs-attr">value:</span> <span class="hljs-string">"my-env-var-value"</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">MY_ENV_VAR_2</span>
          <span class="hljs-attr">value:</span> <span class="hljs-string">"my-env-var-value-2"</span>
      <span class="hljs-attr">restartPolicy:</span> <span class="hljs-string">Always</span>
</code></pre>
<p><strong>default-namespace.yaml</strong></p>
<p>Again, you can see the same values referenced in the namespace file, all defined in the <code>values.yaml</code> file.</p>
<p>YAML</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Namespace</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> {{ <span class="hljs-string">.Values.namespace.name</span> }}
  <span class="hljs-attr">labels:</span>
    <span class="hljs-attr">app:</span> {{ <span class="hljs-string">.Values.namespace.name</span> }}
</code></pre>
<p><strong>default-service.yaml</strong></p>
<p>Thinking about conditional elements, imagine a situation like the one below where we are defining an exposing service. What if your local environment didn't support <code>LoadBalancer</code> type services? Docker Desktop, for example, doesn't support it. You could create an overrides file and adjust your template accordingly to allow you to select a different service type.</p>
<p>YAML</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Service</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> {{ <span class="hljs-string">.Values.deployment.serviceName</span> }}
  <span class="hljs-attr">namespace:</span> {{ <span class="hljs-string">$.Values.namespace.name</span> }}
  <span class="hljs-attr">labels:</span>
    <span class="hljs-attr">app:</span> {{ <span class="hljs-string">.Values.deployment.serviceName</span> }}
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">ports:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">http</span>
    <span class="hljs-attr">port:</span> <span class="hljs-number">8080</span>
    <span class="hljs-attr">targetPort:</span> <span class="hljs-number">8080</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">app:</span> <span class="hljs-string">kafka-sample</span>
  <span class="hljs-attr">type:</span> <span class="hljs-string">LoadBalancer</span>
</code></pre>
<p><strong>default-serviceaccount.yaml</strong></p>
<p>YAML</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">ServiceAccount</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> {{ <span class="hljs-string">.Values.deployment.serviceAccountName</span> }}
  <span class="hljs-attr">namespace:</span> {{ <span class="hljs-string">$.Values.namespace.name</span> }}
  <span class="hljs-attr">labels:</span>
    <span class="hljs-attr">app:</span> {{ <span class="hljs-string">.Values.deployment.serviceAccountName</span> }}
</code></pre>
<p><strong>Values.yaml</strong></p>
<p>The <code>values.yaml</code> file contains the values for each of your defined variables.</p>
<p>YAML</p>
<pre><code class="lang-yaml"><span class="hljs-attr">deployment:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">homer-sample</span>
  <span class="hljs-attr">serviceName:</span> <span class="hljs-string">homer-sample-service</span>
  <span class="hljs-attr">serviceAccountName:</span> <span class="hljs-string">sa-homer-sample</span>

<span class="hljs-attr">namespace:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">homer-sample</span>

<span class="hljs-attr">conditionalAnnotations:</span>
  <span class="hljs-attr">enabled:</span> <span class="hljs-literal">true</span>

<span class="hljs-attr">resources:</span>
  <span class="hljs-attr">limits:</span>
    <span class="hljs-attr">cpu:</span> <span class="hljs-string">100m</span>
    <span class="hljs-attr">memory:</span> <span class="hljs-string">128Mi</span>
  <span class="hljs-attr">requests:</span>
    <span class="hljs-attr">cpu:</span> <span class="hljs-string">100m</span>
    <span class="hljs-attr">memory:</span> <span class="hljs-string">128Mi</span>
</code></pre>
<p>We mentioned overrides earlier. These are supplementary files to the <code>values.yaml</code> file. They can be defined elsewhere and injected into the Helm command like this:</p>
<p>Bash</p>
<pre><code class="lang-bash">helm install kafka-sample-release ./homer-sample -f ./homer-sample-overrides/bld-override.yaml
</code></pre>
<p>In the example above, we are injecting an overrides file into the Helm command. This will take the values defined in that file and apply them on top of the values in <code>values.yaml</code>. An example of an overrides file is shown below.</p>
<p>Here, we are altering resource constraints for a lower-provisioned development environment.</p>
<p>YAML</p>
<pre><code class="lang-yaml"><span class="hljs-attr">resources:</span>
  <span class="hljs-attr">limits:</span>
    <span class="hljs-attr">cpu:</span> <span class="hljs-string">50m</span>
    <span class="hljs-attr">memory:</span> <span class="hljs-string">64Mi</span>
  <span class="hljs-attr">requests:</span>
    <span class="hljs-attr">cpu:</span> <span class="hljs-string">50m</span>
    <span class="hljs-attr">memory:</span> <span class="hljs-string">64Mi</span>
</code></pre>
<h2 id="heading-and-thats-it">And That's It</h2>
<p>This covers everything I wanted to discuss in this post. I've found Kubernetes and Helm to be incredibly useful tools in my work. For smaller-scale applications, it might seem like overkill initially, but the effort invested in deploying an app this way can really pay off if it ever needs to scale.</p>
<p>How are you using Kubernetes and Helm in your hobby projects or even in your professional life? I'd love to hear more real-world examples of how people are using this fantastic technology.</p>
]]></content:encoded></item><item><title><![CDATA[Set up a CI/CD server and Configure for use with Private Github Repositories]]></title><description><![CDATA[Recently I have been exploring more Devops subjects and have been interested in setting up a CI/CD pipeline for a private project I have been working on.
This article covers the basics of setting up a private Jenkins instance (customised with Python)...]]></description><link>https://tesh.digital/set-up-a-cicd-server-and-configure-for-use-with-private-github-repositories</link><guid isPermaLink="true">https://tesh.digital/set-up-a-cicd-server-and-configure-for-use-with-private-github-repositories</guid><category><![CDATA[ci-cd]]></category><category><![CDATA[Jenkins]]></category><category><![CDATA[Pipeline]]></category><category><![CDATA[Django]]></category><category><![CDATA[Python]]></category><dc:creator><![CDATA[James]]></dc:creator><pubDate>Fri, 09 Aug 2024 21:00:42 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/VjWi56AWQ9k/upload/9a5f760c03d8f8c1b9cf7f245345d7a6.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Recently I have been exploring more Devops subjects and have been interested in setting up a CI/CD pipeline for a private project I have been working on.</p>
<p>This article covers the basics of setting up a private Jenkins instance (customised with Python) using Docker, setting up a Github PAT, registering that credential within Jenkins and configuring Jenkins to read a private repository.</p>
<h2 id="heading-setting-up-jenkins-on-docker">Setting up Jenkins on Docker</h2>
<p>I will use Docker Compose to set up the Jenkins environment.</p>
<p>I did these steps directly from the host that was running my 'prod' Docker instance, in my case its a Raspberry Pi 5 running Ubuntu.</p>
<p>I want to customise the Jenkins base image to include bash and python as my project is Django based. In order to achieve this I first create a folder on my Raspberry Pi called Jenkins and change directory into that folder.</p>
<pre><code class="lang-bash">mkdir jenkins
<span class="hljs-built_in">cd</span> jenkins
</code></pre>
<h3 id="heading-the-dockerfile">The Dockerfile</h3>
<p>Now I want to create a Dockerfile, I achieve this by running</p>
<pre><code class="lang-bash">vi Dockerfile
</code></pre>
<p>The contents of the that I created are below, as you can see I am creating this from the jenkins/jenkins:lts base image and then I am installing Python3 and associated tools.</p>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">FROM</span> jenkins/jenkins:lts-jdk17

<span class="hljs-keyword">USER</span> root

<span class="hljs-comment"># Install Python 3 and related tools</span>
<span class="hljs-keyword">RUN</span><span class="bash"> apt-get update &amp;&amp; \
    apt-get install -y python3 python3-pip python3-venv bash &amp;&amp; \
    apt-get clean</span>

<span class="hljs-comment"># Switch back to the Jenkins user</span>
<span class="hljs-keyword">USER</span> jenkins
</code></pre>
<p>Now create a file called docker-compose.yaml in the same folder as the above Dockerfile. and populate it like the below:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">'3.8'</span>
<span class="hljs-attr">services:</span>
  <span class="hljs-attr">jenkins:</span>
    <span class="hljs-attr">build:</span> <span class="hljs-string">.</span> 
    <span class="hljs-attr">user:</span> <span class="hljs-string">"1000:1000"</span>                  <span class="hljs-comment"># UID:GID that exists on the host</span>
    <span class="hljs-attr">group_add:</span> [<span class="hljs-string">"docker"</span>]              <span class="hljs-comment"># lets Jenkins run `docker build`</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">always</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-number">8090</span><span class="hljs-string">:8080</span>
      <span class="hljs-bullet">-</span> <span class="hljs-number">50000</span><span class="hljs-string">:50000</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">jenkins</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">/home/${myname}/jenkins_compose/jenkins_configuration:/var/jenkins_home</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">/var/run/docker.sock:/var/run/docker.sock</span>
</code></pre>
<p><strong><em>${myname}:</em></strong> <em>Create a .env file next to docker-compose.yaml with myname=&lt;your-linux-user&gt; or export myname before you run docker-compose. (Or simply replace this value in the docker-compose file before you run it with your Linux username)</em></p>
<p><strong>Security note</strong>: The Compose file above used to set privileged: true in an older revision of this article. This hands the Jenkins container full root-level control of the host. That’s convenient for personal experiments, but it is not recommended for production or shared hardware.</p>
<p>In serious environments:</p>
<ul>
<li>Run Jenkins as an unprivileged UID/GID that matches a real user on the host.</li>
</ul>
<ul>
<li>Give that user access to Docker only by adding it to the docker group (or by using a remote build agent), rather than running the container in --privileged mode.</li>
</ul>
<ul>
<li>Mount /var/run/docker.sock read-only if the jobs merely build images; use root-less buildkit or a dedicated DinD sidecar if you need full write access.</li>
</ul>
<p>Worth noting in this instance, I am using the build field rather than specifying a pre-created image. This means that when I run Docker Compose it will run the dockerfile that is in the same directory to build the image before deploying the image to a running container.</p>
<h3 id="heading-starting-jenkins">Starting Jenkins</h3>
<p>Once this file is created you can run the command below to run it. I have added the --build argument to ensure the image is built each time rather than using a cached copy and also the -d arg to detach from the process</p>
<pre><code class="lang-bash">docker-compose up -d --build
</code></pre>
<p>You can then navigate to http://%your-docker-host%:8090 to start the setup on the Jenkins instance. For the purpose of this activity all the defaults are fine during the setup wizard. Don't forget to set a memorable username and password!</p>
<p>Once that is setup we are good to proceed onto the next step...</p>
<h2 id="heading-configuring-a-pat-in-github-and-jenkins">Configuring a PAT in Github and Jenkins</h2>
<p>Next we need to create a Personal Access Token that will allow us to view our private repositories. I have opted to use a classic token for this purpose as I want my private Jenkins instance to be to see all my private repositories as I likely will expand the usage of this Jenkins instance.</p>
<p>In order to do this you follow the following steps on Github.com</p>
<ul>
<li><p>Log into your Github account</p>
</li>
<li><p>Click on your profile in the top right hand corner then click settings</p>
</li>
<li><p>Along the left hand menu, click 'developer settings'</p>
</li>
<li><p>Click "personal access token" drop down</p>
</li>
<li><p>Select "Tokens (Classic)"</p>
</li>
<li><p>Click "Generate New Token" and choose the classic option</p>
</li>
<li><p>Give it a name and also ensure "repo" is checked</p>
<ul>
<li>Set the expiry time to a value desired</li>
</ul>
</li>
<li><p>Press Generate Token</p>
</li>
<li><p>On the next screen ensure you copy the token and paste it somewhere safe for now. A text editor window is great for this</p>
</li>
</ul>
<p>Once you have the PAT it is time to configure the Credentials with your shiny new Jenkins instance</p>
<h3 id="heading-now-for-jenkins-config">Now for Jenkins config</h3>
<p>To add this PAT to Jenkins navigate to your instance and login in</p>
<p>From Dashboard, click Manage Jenkins and then select 'credentials' from within in there click "System" &gt; "Global Credentials" &gt; "+ Add Credentials" the default option should be "Username and Password" and this is what we want.</p>
<ul>
<li><p>Keep Scope as default value</p>
</li>
<li><p>username is your Github username</p>
</li>
<li><p>Password is your PAT that you copied earlier</p>
</li>
<li><p>ID can be anything to identify the credential</p>
</li>
<li><p>Description can be filled appropriately</p>
</li>
</ul>
<p>Now this is all setup for your first multibranch pipeline.</p>
<h2 id="heading-setting-up-a-multibranch-pipeline-for-a-private-repo">Setting up a multibranch pipeline for a private repo</h2>
<p>From Jenkins Dashboard select "+ New Item" when the page loads give it a name and select "Multibranch Pipeline" and then click "ok".</p>
<ul>
<li><p>Give the pipeline a descriptive name</p>
</li>
<li><p>Optionally give it a fitting description</p>
</li>
<li><p>Select "Add Source" and Choose "Github"</p>
</li>
<li><p>In credentials, in the drop down select the credential you just created</p>
</li>
<li><p>Get your Repositories HTTPS clone link and paste it in</p>
</li>
<li><p>As strategy I selected "Exclude branches that are filed as PRs"</p>
</li>
<li><p>Under Build Configuration I set mode to 'by jenkinsfile' and set script path to Jenkinsfile - this is configurable depending on your needs as you can embed the script directly in the pipeline. I would opt to include it in your repo as you can change behaviour on an individual branch level</p>
</li>
<li><p>I left the rest of the options as default</p>
</li>
</ul>
<p>Once this is done, we should have a working pipeline, in order to test navigate back to "Dashboard" and select the pipeline that should now be visible. Once the pipelines homepage loads you should be able to select "Scan Repository Now" and this should give you some positive responses and may even populate branches and PRs depending on the state of your repo.</p>
<p>This will not work if you don't currently have a jenkinsfile in the repo if you followed the above configuration.</p>
<p>A simple Jenkinsfile example for a python Django project:</p>
<pre><code class="lang-java">pipeline {
    agent any

    environment {
        <span class="hljs-comment">// Define environment variables here, like your Python version</span>
        VENV_DIR = <span class="hljs-string">'venv'</span>
        DJANGO_SETTINGS_MODULE = <span class="hljs-string">'my-django-project.settings'</span> <span class="hljs-comment">// Adjust this to your settings module</span>
    }

    stages {
        stage(<span class="hljs-string">'Setup'</span>) {
            steps {
                script {
                    <span class="hljs-comment">// Create a Python virtual environment</span>
                    <span class="hljs-keyword">if</span> (!fileExists(<span class="hljs-string">"${VENV_DIR}/bin/activate"</span>)) {
                        sh <span class="hljs-string">'python3 -m venv ${VENV_DIR}'</span>
                    }
                }
                <span class="hljs-comment">// Activate the virtual environment and install dependencies</span>
                sh <span class="hljs-string">''</span><span class="hljs-string">'
                    . ${VENV_DIR}/bin/activate
                    pip install -r src/requirements.txt
                '</span><span class="hljs-string">''</span>
            }
        }

        stage(<span class="hljs-string">'Run Tests'</span>) {
            steps {
                <span class="hljs-comment">// Run Django tests</span>
                sh <span class="hljs-string">''</span><span class="hljs-string">'
                    export $(grep -v '</span>^#<span class="hljs-string">' .env | xargs)
                    . ${VENV_DIR}/bin/activate
                    cd src
                    pytest --junitxml=../test-results.xml
                '</span><span class="hljs-string">''</span>
            }
        }
    }

    post {
        always {
            <span class="hljs-comment">// Archive test results and logs</span>
            archiveArtifacts artifacts: <span class="hljs-string">'test-results.xml'</span>, allowEmptyArchive: <span class="hljs-keyword">true</span>
            junit <span class="hljs-string">'test-results.xml'</span>
            cleanWs()
        }
        failure {
            <span class="hljs-comment">// Notify if the build fails</span>
            echo <span class="hljs-string">'The build failed.'</span>
        }
        success {
            <span class="hljs-comment">// Notify if the build succeeds</span>
            echo <span class="hljs-string">'The build was successful.'</span>
        }
    }
}
</code></pre>
<p>This concludes this post, I hope it was of some use to someone!</p>
]]></content:encoded></item><item><title><![CDATA[Including selective dependencies in maven project]]></title><description><![CDATA[I have been involved with writing applications for the public cloud recently, specifically GCP. There are a lot of helper libraries out there that are very useful for getting your application running in the cloud with less effort.
The problems I have...]]></description><link>https://tesh.digital/including-selective-dependencies-in-maven-project</link><guid isPermaLink="true">https://tesh.digital/including-selective-dependencies-in-maven-project</guid><category><![CDATA[profiles]]></category><category><![CDATA[Java]]></category><category><![CDATA[maven]]></category><category><![CDATA[GCP]]></category><category><![CDATA[Testing]]></category><dc:creator><![CDATA[James]]></dc:creator><pubDate>Thu, 21 Mar 2024 12:34:42 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1711024502340/ee59cbab-3fc8-4dae-a4b7-ee85303bc820.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I have been involved with writing applications for the public cloud recently, specifically GCP. There are a lot of helper libraries out there that are very useful for getting your application running in the cloud with less effort.</p>
<p>The problems I have encountered arise when I want to test these applications locally, certain dependencies when included change how for instance I connect to a database through the introduction of properties such as </p>
<pre><code class="lang-properties">spring.cloud.gcp.sql.database-name
spring.cloud.gcp.sql.instance-connection-name
spring.cloud.gcp.sql.enable-iam-auth
</code></pre>
<p>Merely including the dependency of:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
   <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.google.cloud<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-cloud-gcp-starter-sql-postgresql<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>5.0.4<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
   <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
</code></pre>
<p>Makes the aforementioned properties required. Which is great and not a problem when my target is GCP. What if I want to test my code locally with a local instance of PostgreSQL?</p>
<h3 id="heading-using-profiles">Using profiles</h3>
<p>We can make use of maven profiles to selectively include that dependency and also to ensure we choose an appropriate application.properties file for local running.</p>
<p>Initially I was looking at excluding a certain dependency for a certain profiles, but the answer appears to be to do the opposite.</p>
<p>It's fairly straight forward to set up in your pom.xml file</p>
<pre><code class="lang-xml">
...
<span class="hljs-comment">&lt;!-- remove the dependencies from your higher level &lt;dependencies&gt; --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">profiles</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">profile</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">id</span>&gt;</span>gcp<span class="hljs-tag">&lt;/<span class="hljs-name">id</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">properties</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">activatedProperties</span>&gt;</span>gcp<span class="hljs-tag">&lt;/<span class="hljs-name">activatedProperties</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">properties</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">dependencies</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.google.cloud<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-cloud-gcp-starter-sql-postgresql<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>5.0.4<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>
        &lt;/<span class="hljs-attr">dependencies</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">profile</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">profile</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">id</span>&gt;</span>local<span class="hljs-tag">&lt;/<span class="hljs-name">id</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">properties</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">activatedProperties</span>&gt;</span>local<span class="hljs-tag">&lt;/<span class="hljs-name">activatedProperties</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">properties</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">profile</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">profiles</span>&gt;</span>
...
</code></pre>
<p>In my src/main/resource folder I have three files defined:</p>
<ul>
<li>application.properties</li>
<li>application-gcp.properties</li>
<li>application-local.properties</li>
</ul>
<p>application.properties just contains a single line</p>
<pre><code>spring.profiles.active=@activatedProperties@
</code></pre><p>For me the main difference is the properties that are used to connect to the DB</p>
<p>For GCP I am needing to provide the following properties in my application-gcp.properties file:</p>
<pre><code class="lang-properties">spring.cloud.gcp.sql.databanse-name=${DB_NAME}
spring.cloud.gcp.sql.instance-connection-name=${GCP_CONNECTION_NAME}
spring.cloud.gcp.sql.enable-iam-auth=true
</code></pre>
<p>For my application-local.properties file I am using more direct properties:</p>
<pre><code class="lang-properties">spring.datasource.url=${DB_HOST}
</code></pre>
<p>To select each profile, you can run a command such as </p>
<pre><code class="lang-bash">mvn package -P <span class="hljs-built_in">local</span>
</code></pre>
<p>This was a useful way of leveraging maven profiles for me to be able to test my code locally and in GCP.  </p>
<h3 id="heading-conclusion">Conclusion</h3>
<p>This is a helpful mechanism to use when you are wanting to be able to test your applications locally but they are destined to be deployed in the cloud. Being able to test changes locally is important and can help avoid wasting time later by repeatedly needing to deploy your application code to the remote environment every time an issue is encountered.</p>
]]></content:encoded></item><item><title><![CDATA[Docker, so what is it anyway?]]></title><description><![CDATA[Docker has been around for a while, and my observations you are either exposed to it daily or haven't really touched it and its a bit a mystery. I was in the latter camp up until a few years ago when I had only briefly played with it. Now as someone ...]]></description><link>https://tesh.digital/docker-so-what-is-it-anyway</link><guid isPermaLink="true">https://tesh.digital/docker-so-what-is-it-anyway</guid><category><![CDATA[Windows Containers]]></category><category><![CDATA[Docker]]></category><category><![CDATA[containers]]></category><category><![CDATA[Microservices]]></category><category><![CDATA[LXC]]></category><dc:creator><![CDATA[James]]></dc:creator><pubDate>Sat, 09 Mar 2024 21:51:33 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1710021365352/709eb7c1-55f7-4fb5-8535-6b30c96d1545.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Docker has been around for a while, and my observations you are either exposed to it daily or haven't really touched it and its a bit a mystery. I was in the latter camp up until a few years ago when I had only briefly played with it. Now as someone who is a little more familiar with containerization, I would like to get my thoughts down on it, you know 'for the record'. My eureka moment with containers came when I realized how portable it can make your applications, especially when combined with orchestration technology like Kubernetes.</p>
<h3 id="heading-cool-and">Cool, and?</h3>
<p>Containers are a way to create a distinct operating environment for your applications by creating a sandbox that is lightweight and efficient. It shares the same kernel as the host system however, it runs in its own contained environment.</p>
<p>It was originally based on LXC containers for its runtime environment. LXC allows creating many different user space sessions using cgroups (control groups). These environments cannot interact directly with each other (they can use networking communication). Docker has moved on from this as a base technology however, the concepts remain the same.</p>
<p>This is how containers differ from virtualisation, they don't attempt to virtualise at the hardware level, instead, they do it at the user space level. This means a container is far lighter resource wise than a virtual machine. One knock on affect of this efficiency is containers are very quick to start up, very useful if you need to scale an application to answer a surge in demand!</p>
<h3 id="heading-why-should-i-care-about-it">Why should I care about it?</h3>
<p>Its a very useful technology, it allows us to create images that can be used to create containers. The behavior of these containers can be a lot more predictable as the environment is defined when the image is created. You can take the same image and run it on any host and expect the same results. (Images are created through the use of a Dockerfile).</p>
<p>Any application can be containerized, and Windows containers are also possible although, typically, Linux is the target platform when containerizing an app.</p>
<h3 id="heading-wait-windows-can-be-containerized">Wait, windows can be containerized?</h3>
<p>While I've not used it directly myself, I'm aware that Docker Engine - Enterprise allows the creation of Windows containers via namespace isolation technology allowing containers to share the same Windows Kernel.</p>
<p>From what I gather, Windows containers are less flexible than Linux containers; in the sense that they must be running the same OS version as the host. A Linux container can run any number of distributions of Linux. As a side note, you can run Linux containers via WSL.</p>
<h3 id="heading-how-does-this-benefit-in-the-real-world">How does this benefit in the real world</h3>
<p>Containerised workloads can be leveraged to offer many advantages, some examples include:</p>
<h4 id="heading-continuous-integration-and-continuous-deployment-cicd">Continuous Integration and Continuous Deployment (CI/CD)</h4>
<p>We can make use of CI/CD tools such as Jenkins or Github Actions to create automated deployment pipelines. Typically this might look something like this. A change is made to an application and committed to the repository, commit triggers a Jenkins pipeline that performs unit tests / vulnerability scanning and container image creation (as a basic example). The container is then deployed to a container registry where it can be used to deploy to a production environment.</p>
<h4 id="heading-microservice-architecture">Microservice Architecture</h4>
<p>Containers facilitate systems written with microservice architecture. Microservices ensure that functionality that makes up a larger system is decoupled, unlike monolithic applications. Some advantages of adopting a microservice pattern are:</p>
<ul>
<li><p>individual parts of the application can be updated without impacting other parts, reducing risk of introducing regression issues</p>
</li>
<li><p>Individual parts of the application can be scaled independently, if a sales system gets a sudden surge of orders, the part that handles processing those orders could be scaled whereas other parts of the system could be left. This has large cost benefits for companies as its more surgical and less brute force!</p>
</li>
</ul>
<h4 id="heading-legacy-applications-modernisation">Legacy applications modernisation</h4>
<p>Containers provide a convenient way to create environments that allow legacy applications to run on more modern infrastructures. Even if it is monolithic. Whilst I would advocate for looking into refactoring or recreating if this isn't possible, containerisation alone does offer benefits. Containerising an application in this way can be the first step in a large modernisation project.</p>
<h3 id="heading-a-brief-look-at-a-docker-file">A brief look at a docker file</h3>
<p>A Dockerfile can be as simple as:</p>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">FROM</span> python:<span class="hljs-number">3.10</span>.<span class="hljs-number">13</span>-slim-bullseye

<span class="hljs-comment"># Use apt-get to install system packages (we are using a Debian base image)</span>
<span class="hljs-keyword">RUN</span><span class="bash"> apt-get update &amp;&amp; apt-get install -y --no-install-recommends \
    gcc \
    libc-dev \
    curl \
    libpq-dev \
    &amp;&amp; rm -rf /var/lib/apt/lists/*</span>

<span class="hljs-comment"># Copy the application code and requirements file from the code repository to a location inside the image</span>
<span class="hljs-keyword">COPY</span><span class="bash"> ./app /app</span>
<span class="hljs-keyword">COPY</span><span class="bash"> ./requirements.txt /requirements.txt</span>

<span class="hljs-comment"># Acquire the rust setup script and run it as well as set up the env var required</span>
<span class="hljs-keyword">RUN</span><span class="bash"> curl --proto <span class="hljs-string">'=https'</span> --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y</span>
<span class="hljs-keyword">ENV</span> PATH=<span class="hljs-string">"/root/.cargo/bin:${PATH}"</span>

<span class="hljs-comment"># Install Python dependencies using pip</span>
<span class="hljs-keyword">RUN</span><span class="bash"> pip install --upgrade pip &amp;&amp; pip install -r /requirements.txt</span>

<span class="hljs-comment"># WORKDIR is analogous to cd command</span>
<span class="hljs-keyword">WORKDIR</span><span class="bash"> /app</span>

<span class="hljs-comment"># Run the app</span>
<span class="hljs-keyword">CMD</span><span class="bash"> [<span class="hljs-string">"uvicorn"</span>, <span class="hljs-string">"main:app"</span>, <span class="hljs-string">"--host"</span>, <span class="hljs-string">"0.0.0.0"</span>, <span class="hljs-string">"--port"</span>, <span class="hljs-string">"8000"</span>]</span>
</code></pre>
<p>This particular file is containerising a FastAPI (Python) application. It demonstrates how you can run commands to install system packages as well as using curl to install Rust runtime (required for one of the dependencies) and adding a path to the PATH env var. The CMD directive at the bottom of the file runs the uvicorn server and serves the application on part 8000. To create an image from this you would simply run:</p>
<pre><code class="lang-bash">docker build . -t name-of-image
</code></pre>
<p>And that's it, your application is containerized!</p>
<h3 id="heading-to-conclude">To Conclude</h3>
<p>This article was intended to be a brief introduction to containers to anyone who might have heard about the tech before but didn't really have chance to work directly with it or fully appreciate its implications on how we might build systems.</p>
<p>If it has stoked your interest definitely start checking Docker out, it is super-easy to install via Docker Desktop and you can get started with containerizing your applications simply by adding a Dockerfile to your project and adding a few lines to that.</p>
<p>Do you have use cases that can't use containers? What about some unique situations where containers have been used? I'd love to hear about them!</p>
]]></content:encoded></item><item><title><![CDATA[Using Alembic with SQLAlchemy]]></title><description><![CDATA[supporting code can be found here https://github.com/JTSG1/alembic-sqlalchemy-tutorial
Alembic is a database version control framework closely associated with SQLAlchemy, an independent Python ORM. I have used ORMs with Python in the past but more sp...]]></description><link>https://tesh.digital/using-alembic-with-sqlalchemy</link><guid isPermaLink="true">https://tesh.digital/using-alembic-with-sqlalchemy</guid><category><![CDATA[Python]]></category><category><![CDATA[sqlalchemy]]></category><category><![CDATA[Alembic]]></category><category><![CDATA[Databases]]></category><category><![CDATA[Programming Tips]]></category><category><![CDATA[coding journey]]></category><dc:creator><![CDATA[James]]></dc:creator><pubDate>Tue, 12 Dec 2023 19:52:07 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/npxXWgQ33ZQ/upload/c4fec3379a74e62dfe09ef32eeae2e35.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>supporting code can be found here https://github.com/JTSG1/alembic-sqlalchemy-tutorial</em></p>
<p>Alembic is a database version control framework closely associated with SQLAlchemy, an independent Python ORM. I have used ORMs with Python in the past but more specifically it has been the one that is built into Django. For recent projects I have made a point of exploring different frameworks, specifically those with less included out of the box.</p>
<p>This lead me to FastAPI, a RESTful API framework for python. A couple of stand out features that has encouraged me to explore it more are its speed and the build in support for OpenAPI Specifications. This lead to the very valid question of how to handle my database layer and that introduced me to SQLAlchemy, that doesn't include version control as part of its core offering. I am a big fan of DB Version Control for very obvious reasons and the desire to have the functionality lead me to Alembic.</p>
<h2 id="heading-project-setup">Project setup</h2>
<p>The set up is pretty straight forwards, the first thing to ensure is that we have the required dependencies installed. I like to keep these to Python Virtual Environments because it makes sense to scope dependencies to a project.</p>
<pre><code class="lang-bash">$ mkdir db-tutorial
$ <span class="hljs-built_in">cd</span> db-tutorial
$ python3 -m venv my-venv
$ <span class="hljs-built_in">source</span> my-venv/bin/activate
$ pip install sqlalchemy alembic psycopg2-binary
</code></pre>
<p>We are creating a new project directory above and then creating a virtual environment using the "venv" module, we are calling the within it then activating it. Once that is done, we are getting the required dependencies from pip.</p>
<p><strong>Hint:</strong> I tend to freeze the dependency list into a requirement.txt file when I install something new, this is important for collecting the correct dependencies down the line</p>
<pre><code class="lang-bash">$ pip freeze &gt; requirements.txt
</code></pre>
<p>We are now good to go.</p>
<h2 id="heading-initialising-sqlalchemy-and-creating-a-model">Initialising SQLAlchemy and Creating a Model</h2>
<h3 id="heading-initialise">Initialise</h3>
<p>First task is to do the require configuration for SQLAlchemy.</p>
<p>Lets create a module in at the root of our application that will hold our DB Connection Config and a model</p>
<pre><code class="lang-bash">$ mkdir persistence
$ <span class="hljs-built_in">cd</span> persistence
$ touch __init__.py
$ touch db.py
$ touch models.py
</code></pre>
<p>open db.py in your favourite editor:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> sqlalchemy <span class="hljs-keyword">import</span> Column, Integer, String, create_engine
<span class="hljs-keyword">from</span> sqlalchemy.ext.declarative <span class="hljs-keyword">import</span> declarative_base
<span class="hljs-keyword">from</span> sqlalchemy.orm <span class="hljs-keyword">import</span> sessionmaker
<span class="hljs-keyword">import</span> os

<span class="hljs-comment"># Saving connection details in environment variables</span>
<span class="hljs-comment"># note storing password in plain text in this way would be bad in a production</span>
<span class="hljs-comment"># system</span>
DB_USER = os.environ.get(<span class="hljs-string">"DB_USER"</span>)
DB_PASSWORD = os.environ.get(<span class="hljs-string">"DB_PASSWORD"</span>)
DB_HOST = os.environ.get(<span class="hljs-string">"DB_HOST"</span>)
DB_PORT = os.environ.get(<span class="hljs-string">"DB_PORT"</span>)
DB_NAME = os.environ.get(<span class="hljs-string">"DB_NAME"</span>)

<span class="hljs-comment"># Build the connection string</span>
SQLALCHEMY_DATABASE_URL = <span class="hljs-string">f"postgresql://<span class="hljs-subst">{DB_USER}</span>:<span class="hljs-subst">{DB_PASSWORD}</span>@<span class="hljs-subst">{DB_HOST}</span>:<span class="hljs-subst">{DB_PORT}</span>/<span class="hljs-subst">{DB_NAME}</span>"</span>

<span class="hljs-comment"># create the database connection</span>
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={})
SessionLocal = sessionmaker(autocommit=<span class="hljs-literal">False</span>, autoflush=<span class="hljs-literal">False</span>, bind=engine)

<span class="hljs-comment"># Get the base model class - we can use this to extend any models</span>
Base = declarative_base()

<span class="hljs-comment"># Provide a mechanism to retrieve the database from within the rest of the application</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_db</span>():</span>
    db = SessionLocal()
    <span class="hljs-keyword">try</span>:
        <span class="hljs-keyword">yield</span> db
    <span class="hljs-keyword">finally</span>:
        db.close()
</code></pre>
<p>The above code is essential boilerplate to configure SQLAlchemy. I have not hard coded any connection details in this snippet and instead would define them as Environment Variables in my run profile of VSCode (Or in a container).</p>
<h3 id="heading-create-a-basic-model">Create a basic model</h3>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> sqlalchemy <span class="hljs-keyword">import</span> Column, Integer, String
<span class="hljs-keyword">from</span> persistence.db <span class="hljs-keyword">import</span> Base
<span class="hljs-keyword">from</span> sqlalchemy <span class="hljs-keyword">import</span>  Column, Integer

<span class="hljs-comment"># a model to hold car data</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Car</span>(<span class="hljs-params">Base</span>):</span>

    __tablename__ = <span class="hljs-string">"cars"</span>
    id = Column(Integer, primary_key=<span class="hljs-literal">True</span>, index=<span class="hljs-literal">True</span>)
    make = Column(String)
    model = Column(String, unique=<span class="hljs-literal">True</span>, index=<span class="hljs-literal">True</span>)
    colour = Column(String)
</code></pre>
<p>The above is a basic model that will serve the purpose of this tutorial</p>
<p><strong>Note</strong> - <em>At this point it is worth noting that I will use a Postgres database running within WSL but you aren't restricted to that for your backend - if SQLAlchemy supports it you can use it.</em></p>
<p>We are now in a good place, we have created the code to initialise the database connection and created a model to apply to it.</p>
<h2 id="heading-init-alembic-and-config">Init alembic and config</h2>
<p>Now we want to init our version control system for our Database, lets start by opening a terminal and ensuring we are in the root of the project</p>
<pre><code class="lang-bash">$ alembic init alembic
</code></pre>
<p>This will initialise the alembic config, it creates a folder called "alembic" in the project root. It will also create "alembic.ini" in the same place.</p>
<p>alembic.ini contains a property that can be manipulated to provide the connection string of your DB to the framework however, I don't think this is the best way to go, we have already defined our connection string as part of our SQLAlchemy config and we are wanting to use the values stored in Environment Variables when the application is started. As such, we will defined our connection string in the generated /alembic/env.py file</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> logging.config <span class="hljs-keyword">import</span> fileConfig
<span class="hljs-keyword">from</span> sqlalchemy <span class="hljs-keyword">import</span> engine_from_config
<span class="hljs-keyword">from</span> sqlalchemy <span class="hljs-keyword">import</span> pool
<span class="hljs-keyword">from</span> alembic <span class="hljs-keyword">import</span> context
<span class="hljs-comment"># import your DeclarativeBase</span>
<span class="hljs-keyword">from</span> persistence.db <span class="hljs-keyword">import</span> Base  <span class="hljs-comment"># adjust this to fit your actual module path</span>
<span class="hljs-keyword">from</span> persistence.db <span class="hljs-keyword">import</span> SQLALCHEMY_DATABASE_URL

<span class="hljs-keyword">from</span> persistence.models <span class="hljs-keyword">import</span> Car

<span class="hljs-comment"># this is the Alembic Config object, which provides</span>
<span class="hljs-comment"># access to the values within the .ini file in use.</span>
config = context.config
<span class="hljs-comment"># set to connection string defined in the db_base.py file</span>
config.set_main_option(<span class="hljs-string">"sqlalchemy.url"</span>, SQLALCHEMY_DATABASE_URL)

target_metadata = Base.metadata  <span class="hljs-comment"># use your DeclarativeBase's metadata</span>
</code></pre>
<p>Mostly this file can stay how it is when generated however there are a couple of elements that need considering:</p>
<ul>
<li><p>As discussed above, we are setting the sqlalchemy.url property programmatically to ensure we don't repeat thing to that end, we are importing SQLALCHEMY_DATABASE_URL from from db.py</p>
</li>
<li><p>We are also importing our models to ensure Alembic can see them</p>
</li>
</ul>
<p>Once this is done we can try and make the initial commit to the versioning</p>
<pre><code class="lang-bash">$ alembic revision --autogenerate -m <span class="hljs-string">"initial"</span>
</code></pre>
<p>All being well, outputs with messages similar to</p>
<pre><code class="lang-bash">INFO  [alembic.runtime.migration] Context impl PostgresqlImpl.
INFO  [alembic.runtime.migration] Will assume transactional DDL.
INFO  [alembic.autogenerate.compare] Detected added table <span class="hljs-string">'cars'</span>
INFO  [alembic.autogenerate.compare] Detected added index <span class="hljs-string">''</span>ix_cars_id<span class="hljs-string">''</span> on <span class="hljs-string">'('</span>id<span class="hljs-string">',)'</span>
INFO  [alembic.autogenerate.compare] Detected added index <span class="hljs-string">''</span>ix_cars_model<span class="hljs-string">''</span> on <span class="hljs-string">'('</span>model<span class="hljs-string">',)'</span>
  Generating /home/james/python/db-tutorial/alembic/versions/0d25ea6c8183_initial.py ...  <span class="hljs-keyword">done</span>
</code></pre>
<p>This has not yet created the table in the database but it has committed it to an internal table that alembic uses to track versioning and has created the migrations under the alembic/versions folder. If you look into that file created in there you will see your models being described.</p>
<p>To push the changes to the database you can use the command</p>
<pre><code class="lang-bash">$ alembic upgrade head
</code></pre>
<p>If you look inside your database, you will see that a cars table has been created.</p>
<h2 id="heading-the-takeaway">The takeaway</h2>
<p>Your SQLAlchemy based database schema is now version controlled. Not only can you apply migrations as you make changes to your models but you can also go backwards too</p>
<pre><code class="lang-bash">$ alembic downgrade -1
</code></pre>
<p>would take you back one revision, if you put -2 it would go back two revisions and so on and so forth.</p>
<p>In conclusion, we've successfully navigated the landscape of database version control using Alembic, a robust framework closely entwined with the powerful SQLAlchemy ORM.</p>
<p>By venturing beyond the familiar confines of built-in Django ORM, we've embraced the flexibility of FastAPI and harnessed its speed and native support for OpenAPI Specifications. With a meticulous project setup, we crafted SQLAlchemy configurations, initialized database connections, and defined models for our journey.</p>
<p>The initiation of Alembic brought version control to our database schema, offering not only seamless migrations but also the ability to gracefully navigate backward when needed. As we delve deeper into the realm of SQLAlchemy and Alembic with FastAPI, this tutorial serves as a foundational guide, laying the groundwork for reliable, iterative application development. Stay tuned for more insights and explorations in the exciting intersection of Python, databases, and web frameworks.</p>
<p>Thanks for joining in, and I hope you found this journey into database version control both enlightening and useful!</p>
]]></content:encoded></item><item><title><![CDATA[GPT-4 is quite impressive (Free wallpapers!)]]></title><description><![CDATA[I've recently subscribed to the ChatGPT plus plan as I've been curious about the enhanced capabilities offered through that. So far I've been fairly impressed it seems to offer much more accuracy than GPT-3.5 and I appreciate the fact you can tailor ...]]></description><link>https://tesh.digital/gpt-4-is-quite-impressive-free-wallpapers</link><guid isPermaLink="true">https://tesh.digital/gpt-4-is-quite-impressive-free-wallpapers</guid><category><![CDATA[GPT 4]]></category><category><![CDATA[wallpapers]]></category><category><![CDATA[generated]]></category><dc:creator><![CDATA[James]]></dc:creator><pubDate>Tue, 07 Nov 2023 12:26:32 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1699359912831/dd5c6137-95c9-4c76-955d-63d011abd018.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I've recently subscribed to the ChatGPT plus plan as I've been curious about the enhanced capabilities offered through that. So far I've been fairly impressed it seems to offer much more accuracy than GPT-3.5 and I appreciate the fact you can tailor the answers by providing more information about yourself and how you would like it to communicate with you. I'm still yet to fully leverage the capabilities as I've only subscribed recently but I can see it will be a useful tool.</p>
<p>My favourite animal is the Cheetah and I also like birds of prey. I like a neon-themed desktop environment so I thought I'd test the Dall.E 3 capabilities and get it to generate some wallpapers for me. I'm quite satisfied with the results. Sharing them in case anyone else would like to use them. I do admit, it's a random combination!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1699359776209/a6d31405-b340-4b58-a343-93eed9742ad3.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1699359797693/30bc8ede-389b-4374-9625-10a4c8000000.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1699359801686/ab80dc90-8b0a-4ab7-811a-4f2f164c47f5.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1699359804648/21f972f6-a65d-4340-ba76-d157b5782863.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1699359809555/09ec250b-c69b-4210-857c-a03af0cabbf6.png" alt class="image--center mx-auto" /></p>
]]></content:encoded></item><item><title><![CDATA[Using Visual Studio Code for Java Dev]]></title><description><![CDATA[Traditionally, I've used Jetbrains IntelliJ for all my java development, in the past I have also made use of Pycharm for any of my Python activities. Let me preface this by saying I have absolutely nothing against these IDEs and thoroughly enjoy usin...]]></description><link>https://tesh.digital/using-visual-studio-code-for-java-dev</link><guid isPermaLink="true">https://tesh.digital/using-visual-studio-code-for-java-dev</guid><category><![CDATA[Java]]></category><category><![CDATA[code]]></category><category><![CDATA[vscode]]></category><category><![CDATA[intellij idea]]></category><category><![CDATA[Linux]]></category><dc:creator><![CDATA[James]]></dc:creator><pubDate>Mon, 06 Nov 2023 18:00:12 GMT</pubDate><content:encoded><![CDATA[<p>Traditionally, I've used Jetbrains IntelliJ for all my java development, in the past I have also made use of Pycharm for any of my Python activities. Let me preface this by saying I have absolutely nothing against these IDEs and thoroughly enjoy using them however, like all IDEs they can be somewhat resource-heavy.</p>
<p>I have recently been on an anti-Microsoft drive on all my computers with the ultimate goal of removing any MS operating system from my life as much as possible. My primary computer has been running POP!_OS for a little while now and I finally found the time to give my portable computer (a Microsoft Surface Pro 8) a similar software makeover.</p>
<p>Now, I recognise the irony that I own a device manufactured by Microsoft, but the truth is, as far as portable tablet hybrid devices. the Surface Pro suits my needs very well. That doesn't mean I have to be tied to their spyware-infested Windows 11.</p>
<p>Anyway I digress, I was setting up POP!_OS on my surface and after getting set up with the Linux Surface kernel to ensure everything worked correctly it was time to install the software I thought I might need. The first thing I usually install is VSCode followed by any other specific tools. On this occasion, I picked IntelliJ next. I went to install the community edition from the application marketplace but then I stopped. I asked myself why I didn't just use VSCode and keep all my dev activities to one application.</p>
<p>After a bit of research, it turns out there is an extensive set of extensions for Java development. I've installed the following extensions from the extension marketplace:</p>
<ul>
<li><p>Extension Pack for Java</p>
</li>
<li><p>Language Support for Java(TM) by Red Hat</p>
</li>
<li><p>Spring Initializr Java Support</p>
</li>
</ul>
<p>It turns out this works very well! I've set up a demo spring boot project with a couple of endpoints and some models and so far, the experience has been positive. I plan to further explore using VSCode for Java development going forwards and will report on failures and successes.</p>
<p>I recognise VSCode is overseen by the aforementioned Microsoft, which bring me to wondering whether considering my anti MS crusade that this is the right way to go. My main concern was getting MS out of my operating system so I had a lean system that worked exactly the way I wanted. My current thought process is their applications are ok to run especially if they can be sandboxed. Interested in hearing people thoughts on this!</p>
]]></content:encoded></item><item><title><![CDATA[Hello World!]]></title><description><![CDATA[Allow me to introduce myself, My name is James and I am a Full Stack Software Engineer and general IT enthusiast. I also try to be a bit of a creative at times but I'm not so good at that! Though I do enjoy it.
I have possessed this domain tesh.digit...]]></description><link>https://tesh.digital/hello-world</link><guid isPermaLink="true">https://tesh.digital/hello-world</guid><category><![CDATA[introduction]]></category><category><![CDATA[Blender]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[architecture]]></category><category><![CDATA[Linux]]></category><dc:creator><![CDATA[James]]></dc:creator><pubDate>Sun, 22 Oct 2023 19:59:42 GMT</pubDate><content:encoded><![CDATA[<p>Allow me to introduce myself, My name is James and I am a Full Stack Software Engineer and general IT enthusiast. I also try to be a bit of a creative at times but I'm not so good at that! Though I do enjoy it.</p>
<p>I have possessed this domain <a target="_blank" href="http://tesh.digital">tesh.digital</a> for a while now and always intended to do a bit of blog on topics I find interesting. they could be; engineering, systems architecture, gaming, 3D modelling and maybe even the odd bit of photography and my (poor) attempts at artistic endeavours.</p>
<p>Why did I want to start a blog? I just feel it's another creative outlet and I want to practice my writing more to increase my capabilities in that space. Should I find the time going forward I also intend this to be a showcase of any projects I start or get involved with.</p>
<p>Time is certainly a premium to me, I have a full-time job in the field mentioned above and also have two children! But I do want to start logging some of my initiatives.</p>
<p>For my tech-related content, I will be leaning heavily in the direction of open source, I am a big fan of the federation of development of software as it drives innovation and there is less corporate interest influencing decisions.</p>
<p>I am a big fan of GNU/Linux and my operating system of choice at the moment is Pop!_OS by system76. Professionally I have experience developing software in Python and Java and some more niche technologies focussed on customer communications management such as Doc1. Other things I have experience with include TypeScript and Vue.js</p>
<p>Gaming-wise, I really enjoy games that make me think, even if I'm not very good at them! One of my all-time favourites is Factorio and I'm looking forward to Cities Skylines 2.</p>
<p>As for my more creative endeavours, I have an interest in drawing and 3d modelling/animations. I'll confess, I'm not particularly creative but I do enjoy having a play about and have been learning Blender with great enthusiasm the last few weeks. The logo for this blog was an experiment with using Blender to create web content</p>
<p>My current plan is to write some articles on the following topics:</p>
<ol>
<li><p>Distributed Systems Architecture and an interesting pattern I've been digging into</p>
</li>
<li><p>Adventures in GNU/Linux - GPU passthrough and customisations</p>
</li>
<li><p>Share some Blender Renders and some of the behind-the-scenes elements</p>
</li>
<li><p>Containers and some Docker related content</p>
</li>
<li><p>Python, Java and a fight between the two on what languages should be used for your next project</p>
</li>
</ol>
<p>If anyone reads this, hope you find any content I might post of interest, if you do, don't forget to say hi! If you have any tips or want to share your blog with me in the comments, I'd be more than happy to check it out!</p>
]]></content:encoded></item></channel></rss>