Vinod Kurup

Hospitalist/programmer in search of the meaning of life

May 9, 2021 - 2 minute read - Comments - life

First Post of 2021

Hi loyal reader(s)! It is nearly halfway through 2021 and this is my first post of the year. And writing on this here blog was one of my 2021 resolutions, so I guess that was a complete failure. Although, maybe I can make up for it by actually writing more regularly over the next few months. It has been a whirlwind of a year for just about everyone, right? When the pandemic started, I remember making a list of the things that I might want to blog about and the only thing I remember from that list was that I wanted to blog about “not wanting to blog about the pandemic”. It was just too exhausting to think about. And I think I still feel that way. I have written some private thoughts about things, but I’ll leave them under wraps.

The biggest change in my life is that I am starting a new job tomorrow. I’ll be a Software Engineer at Kevel, programming in Clojure (among other languages). I’ve been infatuated with Clojure (and functional programming in general) for a long time, so I am beyond excited to be able to use it professionally. This is also the first job that I’ll be starting as a “remote-only” worker. Normally, I’d walk into the office and introduce myself to someone and then start to get oriented. It will be weird to do that via an online chat of some sort. Am I showing my age?

What else is going on? Well I’m not going to talk about the pandemic, but I do encourage people to go help out people in India (or wherever else people need help.) We are luckily healthy and vaccinated. The kids have survived remote schooling and two of them have returned to school partly in-person. I’ve been running and meditating and gardening, all with minimal success.

So, what is up with you all? Hope to write again before the end of the year.

Dec 12, 2020 - 2 minute read - Comments - programming elixir

Advent of Code Elixir Day 1

There’s this cool thing called Advent of Code, described best here. I found out about it while reading my favorite weekly weblog and so I’ve decided to try my fledgling (maybe flailing is a better word?) Elixir skills on it. There are 2 puzzles each day (day_1_0 and day_1_1 are my solutions). Here’s my code for Day 1:

  @doc """
  Find 2 numbers in file that sum to 2020 and return their product.

      iex> Aoc.day_1_0()
      633216
  """
  def day_1_0 do
    goal = 2020
    list_of_nums = get_list_of_nums()

    first_num = find_first_partner(list_of_nums, goal)
    first_num * (goal - first_num)
  end

  @doc """
  Find 3 numbers in file that sum to 2020 and return their product.

      iex> Aoc.day_1_1()
      68348924
  """
  def day_1_1 do
    goal = 2020
    list_of_nums = get_list_of_nums()

    # find first number where there exists 2 other numbers that sum to `goal - x`
    first_num = Enum.find(list_of_nums, fn x -> find_first_partner(list_of_nums, goal - x) end)
    second_num = find_first_partner(list_of_nums, goal - first_num)

    first_num * second_num * (goal - first_num - second_num)
  end

  @doc """
  Given a `list` of integers, return the first number in that list which has a
  partner in the list that sums to `goal`.
  """
  def find_first_partner(list, goal) do
    Enum.find(list, fn x -> Enum.member?(list, goal - x) end)
  end

  defp get_file do
    Path.join("data", "day1.txt") |> File.read!()
  end

  defp get_list_of_nums do
    # Split into lines, filter out empty lines, convert each to an integer
    get_file()
    |> String.split("\n")
    |> Enum.filter(fn x -> String.trim(x) != "" end)
    |> Enum.map(&String.to_integer/1)
  end

The code is also available in Github, which I’ll update if I do find time to keep going: https://github.com/vkurup/aoc-elixir/ I feel like this could have been done more efficiently with recursion, but I am not comfortable enough with recursion to know how…

A sincere thanks to Eric Wastl for spending the enormous time and energy to create cool puzzles like this.

Dec 11, 2020 - 2 minute read - Comments - cheatsheet python linux bash

Install multiple python versions using pyenv

pyenv is awesome and has changed the way that I manage my Python environments. One tiny annoyance is that it takes a looong time to install each Python version, which of course makes sense, since it has to download and compile each version on your machine. But it’s not a big deal, since you generally just do this once and then not again until you really need to upgrade to a different Python version.

Recently, though, I was working on a project that uses tox to make sure that the project runs on multiple Python versions, in this case 3.6, 3.7, 3.8, 3.9. The specific versions were included in the .python-version file, which looked like this:

3.9.0
3.8.6
3.7.9
3.6.12

I didn’t have all of these installed, so rather than doing pyenv install 3.9.0 and then waiting a few minutes, and then repeating the process for each version, I wrote up this little script, which I named pyinstall.sh:

#!/bin/bash
# Install all versions specified in .python-version
set -ex

while read version; do
    pyenv install -s "$version"
done <.python-version

Since pyenv is nice enough to include the -s flag which means “skip if already installed”, this can be run as many times as you want and it will only install the version if it’s not already installed

Aug 21, 2020 - 2 minute read - Comments - homelab phone

Using an OBi110 for VOIP service

I set up an OBi110 device to provide a landline at home. OK, it’s not a land line, it’s a VOIP (Voice Over Internet Protocol) line, but it performs a similar function. We’ve always had a landline. Our cell phone coverage is spotty at times and ever since Mala started working from home, she needs to have a reliable phone line for her global conference calls.

The problem is that Spectrum was charging us $44.95 per month plus taxes, which took it near $50 every month. I considered a few options including:

  • MagicJack
  • Ooma
  • I looked at a few others… can’t remember them all

In the end, I settled on buying an ATA device and then connecting it to a cheaper VOIP service. I chose voip.ms which has been great so far.

One Time Costs:

  • OBi110 ATA device: $19.90
  • voip.ms setup fee: $0.40
  • voip.ms e911 setup fee: $1.50

Monthly Costs:

  • voip.ms fee for an incoming phone number: $0.85
  • voip.ms e911 monthly fee: $1.50

Per-Minute Costs:

  • voip.ms incoming: $0.009 per minute

I’ll have to keep an eye on my monthly per-minute costs because there is an unlimited plan that is $4.25 per month, which I’ll switch to if I use more than that per month. In any case, this is MUCH cheaper than Spectrum.

Now, if you do any research about the OBi110 device, you’ll see everyone telling you not to buy it because it is out of service and doesn’t support Google Voice. This is true, ObiTalk doesn’t support the device anymore. When it was supported by the company, you could just dial a special code on your phone and it would do all the set up for you. Since it’s not supported, that procedure doesn’t work anymore. But you can manually configure the device with the excellent docs from voip.ms, and for a geek like me that was not bad. And yes, you can’t link a Google Voice number to the device, but I didn’t want to do that anyway. I think if you wanted to keep using a google voice number, you could probably get pretty close by putting a different number on your device (through voip.ms) and then pointing Google Voice at that number. Incoming calls would work fine in that way, but I’m not sure about outgoing calls. If you want to seamlessly use Google Voice on an ATA device, then OBi110 isn’t for you. You’ll need the OBI202 (which was out of stock when I was looking).

Aug 17, 2020 - 2 minute read - Comments - django programming

Django forms have 2 modes

I worked on adding tests to some Django forms today and I was reminded that Django forms operate in two modes. I think of them as the GET mode and the POST mode. The GET mode displays the form and the POST mode processes the data that you supply to the form. I don’t know why, but I often forget that. Instead, I sometimes treat them as a normal Python class, especially when I’m creating automated tests for them. I instantiate the class (form = MyForm()) and then just call any of its methods and make sure they return the right values or achieve the correct side effects. The form that I was working with today had a couple features. The first was that it provided some helper functions to the template so that the template could display the form properly. Those are in the GET mode. So I did something like:

user = create_my_special_user()
form = MyForm(user=user)
self.assertTrue(form.is_user_special())

And that worked as I expected. The second feature of my custom form was that it cleaned the data in a particular way. So I did:

form = MyForm(user=user)
cleaned_data = form.clean()
self.assertEqual(cleaned_data, expected_data)

Instead of passing, the test blew up:

AttributeError: 'MyForm' object has no attribute 'cleaned_data'

Why? Because the clean() method is called when you provide it with POST data (normally… there are also other ways to populate forms). If you don’t supply it data, then cleaned_data doesn’t get populated. The answer is to provide data to the form:

form = MyForm(user=user, data={})
cleaned_data = form.clean()
self.assertEqual(cleaned_data, expected_data)

And that works as expected. The reminder to myself is to always identify which mode of the form you are testing, GET or POST.

Jan 27, 2020 - 1 minute read - Comments - cheatsheet aws

AWS Xray brain dump

These are very rough notes on how I got AWS Xray working in a kubernetes deployment. Posting this mainly for my auxiliary brain.

First, set up an IAM role that allows nodes to access xray.

Following this blog post: https://aws.amazon.com/blogs/compute/application-tracing-on-kubernetes-with-aws-x-ray/

I cloned this repo: https://github.com/aws-samples/aws-xray-kubernetes

Build the AWS Xray image:

cd xray-daemon/
docker build -t xray-daemon:latest .

THIS OUT OF DATE! try this: https://docs.aws.amazon.com/xray/latest/devguide/xray-daemon-ecs.html#xray-daemon-ecs-image

Create an AWS ECR repo:

aws ecr create-repository --repository-name xray-daemon

Login via docker:

aws ecr get-login --no-include-email

That command spits out a command which you can run that will log you into docker ^

Tag and push the image there:

docker tag xray-daemon:latest AWS-ACCOUNT-ID.dkr.ecr.us-east-1.amazonaws.com/xray-daemon:latest
docker push AWS-ACCOUNT-ID.dkr.ecr.us-east-1.amazonaws.com/xray-daemon:latest

You should now be able to see the image in the AWS ECR console

Update aws-xray-kubernetes/xray-daemon/xray-k8s-daemonset.yaml:

Change the image value to the image you just pushed

deploy it:

kubectl apply -f xray-k8s-daemonset.yaml

Dec 11, 2019 - 3 minute read - Comments - book-review

We have always lived in the castle

At school, Kavi is reading “We have always lived in the castle”, by Shirley Jackson. I read her short story, “The Lottery”, in high school. I recall the main plot of the story, but I’m sure I have forgotten many details. What I haven’t forgotten is the sense of shock I felt as the plot finally became clear. As I’m typing this, I wonder if that was more because I was young and new to being shocked, or whether it was really shocking. In any case, that feeling of being shocked by that story is deeply embedded inside me.

So I checked this book from the library and finished it today. It was dark and disturbing, reminiscent of “The Lottery”. There were many elements of foreshadowing. From the start, you wonder what Mary Katherine or her family did to sow such discontent among the villagers, and the answer comes slowly, in bits and pieces. I wondered why they had library books that they would never be able to return. In the end, I was sucked in and couldn’t put the book down until I figured it all out. Thankfully, it’s a short book.

Some random thoughts that my brain generated while reading this book (and that I’d prefer to remember someday):

  • When Merrikat and Connie first enter their house after it was trashed by the village, Merrikat is not surprised that the library books are intact because it’s against the law to damage library property. I smiled.
  • The absurdity of Merrikat’s behavior at times struck me hard. The first time was when she threw a glass pitcher on the floor because she was upset by a visitor talking to her sister too much. I had to re-read that section a few times because it was written in an almost off-hand way, as if this was normal behavior. She continues to behave like that in bursts, and then the more absurd part becomes Connie’s calm reaction to these behaviors. Piece by piece, you get to understand more about how disturbed they are, and why.
  • Merrikat’s belief in magical powers that keep her safe are both hard to believe and comforting. I kept waiting for someone to say one of her three magic words, as if that was the only way that anything could go wrong.
  • I wanted Charles to be taught a lesson. I guess it happened, but a part of me wanted it to be more dramatic and more crushing. I’m a sucker for an evil character doing clearly evil things and then getting caught red-handed and punished. I know that’s simplistic, but I still like it and am a little disappointed when I don’t get that satisfaction.

I love the fact that I’m finding books to enjoy, thanks to my kids. Life is good.

Nov 27, 2019 - 1 minute read - Comments - book-review

Open Borders

I just finished Open Borders, a graphic novel about immigration policy. (Not 2 things that normally occupy the same sentence)

It was an excellent presentation of a position that I’ve always felt to be morally correct, in my heart of hearts. I am one of the few lucky ones who gets to be an American citizen just because of the fact that my parents took it on themselves to forge a better life for us. Why shouldn’t everyone have this opportunity? This book shatters many of the myths that allowing open borders makes things “worse” in any way for natives.