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.
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.
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
Find disk space hogs:
sudo du -h -d 3 / | grep "^[0-9.]*G"
… which helped me find the command in
yesterday’s post. You can add directories
after the slash to drill down to your heart’s content.
Copy this script into ~/bin/
:
#!/bin/bash
# Removes old revisions of snaps
# CLOSE ALL SNAPS BEFORE RUNNING THIS
set -ex
LANG=en_US.UTF-8 snap list --all | awk '/disabled/{print $1, $3}' |
while read snapname revision; do
sudo snap remove "$snapname" --revision="$revision"
done
Found on
https://www.linuxuprising.com/2019/04/how-to-remove-old-snap-versions-to-free.html and
slightly edited.
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).
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.
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
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.
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.