helm Cheat Sheet

Development Tips and Tricks

Test Template Rendering

Run the following. Instead of it installing the chart it will render the template and display the output

helm install --debug --dry-run <release-name> <path-to-chart-dir>

To test with an overriding value

helm install <release-name> <path-to-chart-dir> --dry-run --debug --set k=v

Deployments

  • List releases: helm list
  • Get the manifest for a release: helm get manifest <release-name> [flags]

Implementing a Stack in Go

One of the key features in go is simplicity. As a result there are a number of utilities and data structures that are common in other high-level languages that do not come with the go stdlib.

One of them is a stack. Following is a very simple implementation of a stack that uses a slice to store the data.

The following is an implementation of a simple stack that takes any kind of pointer.

import "fmt"

type Stack[T any] struct {
	stack []*T
}

func (s *Stack[T]) IsEmpty() bool {
	return len(s.stack) == 0
}

func (s *Stack[T]) Push(q *T) {
	// Add the new value to the end of the slice, which acts as the "top" of the Stack
	s.stack = append(s.stack, q)
}

func (s *Stack[T]) Pop() *T {
	if s.IsEmpty() {
		return nil
	} else {
		// Get the index of the "top" of the Stack
		idx := len(s.stack) - 1
		// Retrieve the element from the stack that we are going to "pop" and return
		retval := s.stack[idx]
		// Remove it from the underlying slice by re-slicing the slice.
		s.stack = s.stack[:idx]
		return retval
	}
}

func main() {
	i := 1
	j := 2
	s1 := &Stack[int]{}
	s1.Push(&i)
	s1.Push(&j)

	fmt.Printf("Pop! = %d\n", *s1.Pop())
	fmt.Printf("Pop! = %d\n", *s1.Pop())
}

curl HTTPS Over an SSH Tunnel

If you want to execute curl commands on your local machine and connect to an HTTPS server that is only reachable from a bastion or other host through which you can only get to via SSH, the following is how you set up the SSH tunnel and execute the curl command.

The following will not work

# Create ssh tunnel
#
ssh -L localhost:8443:example.com:443 user@bastion.example.com

# Attempt to hit the endpoint otherwise accessible from bastion.example.com
# with curl -X GET https://example.com/v1/endpoint
#
curl -X GET https://localhost:8443/v1/endpoint

The reason that this does not work is that with the port forwarded ssh tunnel curl is unable to resolve the IP of the example.com HTTPS server on the other side of the connection on bastion.example.com and the connection fails.

If we execute curl -v we can see the details. Notice that curl is connecting to localhost:8443 and not resolving example.com

$ curl -v https://localhost:8443/subjects
*   Trying ::1:8443...
* Connected to localhost (::1) port 8443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: /etc/ssl/certs
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
* ALPN, server accepted to use h2
* Server certificate:
*  subject: CN=*.example.com
*  start date: Aug  3 00:00:00 2022 GMT
*  expire date: Sep  1 23:59:59 2023 GMT
*  issuer: C=US; O=Amazon; OU=Server CA 1B; CN=Amazon
*  SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x560827a1b2c0)
> GET /subjects HTTP/2
> Host: localhost:8443
> user-agent: curl/7.74.0
> accept: */*
> 
* Connection state changed (MAX_CONCURRENT_STREAMS == 128)!
< HTTP/2 404 
< server: awselb/2.0
< date: Wed, 08 Feb 2023 15:20:04 GMT
< content-type: text/plain; charset=utf-8
< content-length: 0

Instead of just forwarding TCP packets we need to tell ssh client to setup a SOCKS5 proxy through which we will tunnel traffic.

ssh -D 8443 -f -C -q -N bastion.example.com

The -D option creates a SOCKS5 proxy server listening on port 8443 which tunnels the traffic over ssh to bastion.example.com from which hostnames for the destination webserver can be resolved. This creates a proxy server that enables you to connect to “dynamic” destinations on the other side of the tunnel.

The other options

-D 8443- start a SOCKS server listening on port 8443 on the localhost
-f - fork the process, running it in the background
-C - compress data
-q - quite mode
-N - indicate to the ssh client that there are no commands to be sent over the tunnel

Once you create the proxy and tunnel you can then execute curl commands as follows on the localhost telling curl to use the SOCKS5 proxy listening on localhost:8443

curl -v -x socks5h://0:8443 https://example.com/v1/endpoint

The verbose output shows that the SOCKS5 proxy is the connecting to example.com:443 over the tunnel and remotely resolving the IP to the correct HTTPS server on the other side of the tunnel.

$ curl -v -x socks5h://0:8443 https://example.com/v1/endpoint
*   Trying 0.0.0.0:8443...
* SOCKS5 connect to example.com:443 (remotely resolved)
* SOCKS5 request granted.
* Connected to 0 (127.0.0.1) port 8443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: /etc/ssl/certs
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
* ALPN, server accepted to use h2
* Server certificate:
*  subject: CN=*.example.com
*  start date: Aug  3 00:00:00 2022 GMT
*  expire date: Sep  1 23:59:59 2023 GMT
*  subjectAltName: host "example.com" matched cert's "*.example.com"
*  issuer: C=US; O=Amazon; OU=Server CA 1B; CN=Amazon
*  SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x562ada1d52c0)
> GET /subjects HTTP/2
> Host: example.com
> user-agent: curl/7.74.0
> accept: */*
> 
* Connection state changed (MAX_CONCURRENT_STREAMS == 128)!
< HTTP/2 200 
< date: Wed, 08 Feb 2023 15:22:08 GMT
< content-type: application/vnd.schemaregistry.v1+json
< content-length: 1937
< vary: Accept-Encoding, User-Agent

Diffing the output of two commands

The GNU diff command on most Linux and UNIX systems will diff the contents of two files. With Bash, you can, using process substitution, take the output of any arbitrary command and process its input, or output, as a file descriptor. In this way, you can then use diff against the output of two commands as follows

diff <(cmd1) <(cmd2)

Both cmd1 and cmd2 will appear as a file name/file descriptor. The < character indicates that the file descriptor should be read to obtain the output.

Stuffed Eggplant

This is a really delicious Indian dish that can be enjoyed with either roti or rice.

Ingredients

  • 10 – 12 small round brinjal (mini eggplants)
  • 1/2 TSP salt
  • 4 TBSP oil
  • 1 medium white or red onion, chopped
  • 6 – 7 dried red chilies
  • 2 TBSP coriander seeds
  • 1 TBSP cumin seeds
  • 1 TSP fenugreek seeds
  • 2 TSP sesame seeds
  • 9 cloves garlic
  • 1/4 cup of peanuts, or if you have a peanut allergy you can substitute with sliced almonds
  • 3 TBS dried coconut
  • 2 TSP tamarind paste

Directions

  1. Heat 2 TBSP of oil in a large, flat skillet to medium-high heat
  2. Add the onion, chilies, coriander seeds, cumin seeds, fenugreek seeds, sesame seeds, garlic, and nuts and fry for about 7 or 8 minutes until the onions and garlic start to brown a little
  3. Turn off the heat and set aside for about 10 minutes to cool
  4. Add contents of the skillet, plus the coconut and tamarind to a blender. Add 1/2 cup of water and the salt and blend to a chutney consistency, not completely smooth, but should be a paste.
  5. Rinse the brinjal and pat dry
  6. Slit the brinjal from the opposite side of the stem about 3/4 of the way to the stem. Rotate 90 degrees and cut another slit in the brinjal the same way. If you hold the brinjal in your hand with the stem side away from you and look at the side that you cut there should be a plus sign cut in it now, such that all four pieces are still attached.
  7. Put the remaining oil in the bottom of your pressure cooker and cote the bottom.
  8. Stuff the ground masala into the slit eggplant trying not to break it apart and put each in the pressure cooker
  9. Pour the remaining masala into the pressure cooker and evenly spread it
  10. Add an extra 1 cup of water to the pressure cooker and secure the lid
  11. Cook on high. Once the pressure cooker starts whistling cook it for 5 minutes and then turn off trhe heat and let sit for 5 more minutes.
  12. Release pressure and then serve with rice or roti

Creating Custom Vagrant Boxes for Integration Testing

For some things, a docker container will not work. For example, I am working on a Python project to automate the deployment and configuration of VMs and bare-metal boxes. It involves installing packages and configuring the OS and to properly test it requires a full fledged VM and not just a container.

Use packer to define the box you want to build

Install packer. The description straight from apt show packer is “HashiCorp Packer – A tool for creating identical machine images for multiple platforms from a single source configuration” Then create your pkr.hcl file (I am using the newer HCL DSL). The following is for a box that extends Debian 11.

variable "version" {
  type    = string
  default = ""
}

locals { timestamp = regex_replace(timestamp(), "[- TZ:]", "") }

source "vagrant" "xfce4_debian_11" {
  add_force    = true
  communicator = "ssh"
  provider     = "virtualbox"
  source_path  = "debian/bullseye64"
}

build {
  sources = ["source.vagrant.xfce4_debian_11"]

  provisioner "shell" {
    execute_command = "echo 'vagrant' | {{.Vars}} sudo -S -E bash '{{.Path}}'"
    script          = "provision.sh"
  }
}

Build the box

packer build xfce4_debian_11.hcl

This will create an output-xfce4_debian_11 directory and build the box writing it into that directory. The contents of which, when finished will be

output-xfce4_debian_11/
|-- package.box
`-- Vagrantfile

Create a Vagrant file that uses the pre-built box

For your integration testing you will be spinning up an “instance” of the box you just built.

  1. Create a directory for your instance and cd into it: mkdir test-instance && cd test-instance
  2. Initialize the vagrant instance and then add the box that you built; vagrant init test-instance && vagrant box add test-instance <path-to-dir>/output-xfce4_debian_11/package.box
  3. Update the Vagrant file with any additional configs that you require and then run vagrant up to start it.

Using Environment Variables in a Vagrant File

An easy way to parameterize a Vagrant file is to use environment variables. You simply need to export a variable and then refer to it in the Vagrant file with the following syntax.

Export the variable:

export SSH_PORT=22222

Sample Vagrant file that reads the exported variable

# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|
  config.vm.box = "debian/bullseye64"
  config.vm.box_version = "11.20221219.1"

  config.vm.network "forwarded_port", guest: 22, host: ENV['SSH_PORT']
end

Declaring, Exporting, and Reading Dynamic Variables in Bash

If you want to dynamically define and export variable names in Bash here is the TLDR;

# Define the name of the variable
key="my-dynamic-var-name"

# Declare it and export it
declare -gx "$key"="some-value"

To then access that value via a dynamically generated variable name

# Create a variable that contains the variable name
var_to_access="my-dynamic-var-name"

# Read the value
my_var_value=${!var_to_access}

Read the man page for declare for more details and read this article for a really good explanation and further examples.

To read the declare man page

help declare

Chickpea and Rice Stew

Ingredients

  • 1.5 cups basmati rice
  • 1 TB olive oil
  • 1/4 cup olive oil
  • 1 cup yellow onion, diced
  • 2 (14 oz) cans chickpeas, drained
  • Salt & black pepper, to taste
  • 3 garlic cloves, minced
  • 1 tsp ground turmeric
  • 1/2 tsp crushed red pepper flakes
  • 1 lemon, juiced and zested
  • 1 (32 oz) box low sodium vegetable broth
  • 4 oz fresh baby spinach
  • 1/2 cup cilantro, chopped
  • 1/2 cup parsley, chopped
  • 1/4 cup dill, chopped
  • 1 TB chives, chopped
  • 4 Naan flatbread—To heat the Naan, preheat the oven to 400 degrees and place Naan on a baking sheet in the middle of oven and warm for 3 minutes

Directions

  1. Rinse the basmati rice in cold water. Combine rice with 2 1/4 cups water, 1 TB olive
    oil and a pinch of salt in a medium pot. Stir once and bring to a boil over high heat. Cover, reduce heat to low and simmer for 10 minutes. Remove pan from heat and leave covered for 5 minutes. Remove lid and fluff with fork before serving.
  2. Heat 1/4 cup olive oil in a large pan over medium heat. Add the garlic and diced onions and cook until soft, about 5-8 minutes.
  3. Add the 2 drained cans of chickpeas, season with salt & pepper to taste. Cook stirring occasionally until the chickpeas begin to crisp, about 5-8 minutes. Add the turmeric, crushed red pepper flakes (based on desired spice preference), and lemon zest, stir to combine, and cook about 2 minutes.
  4. Add 3 cups vegetable broth, the juice of 1/2 lemon and bring the mixture to a boil, then reduce heat to low. Stir in the baby spinach, 1/2 cup chopped cilantro, 1/2 cup chopped parsley, 1/4 cup chopped dill, and 1 TB chopped chives, and simmer for 10 minutes until the spinach is wilted. Add salt and black pepper, to taste, as needed. Add as much of the remaining 1 cup vegetable broth to reach desired soup consistency.
  5. To serve, divide the cooked basmati rice among bowls and ladle stew over. Top with reserved chickpeas. Enjoy with warmed Naan flatbread.

Bean and Barley Leek Soup

Make 3 cups of cooked barley ahead of time and have it on hand when cooking the soup.

Ingredients

  • 2 Tablespoons olive oil
  • 3 cloves garlic, minced
  • 2 leeks, diced
  • 3 carrots, diced
  • 1 bulb fennel, sliced thin (1 cup)
  • 1 (15 ounce) can no-salt added diced tomatoes
  • 1 teaspoon Herbes de Provence
  • 4 cups low-sodium vegetable broth
  • 2 (15 ounce) cans white beans,rinsed and drained
  • 1/2 teaspoon kosher salt
  • 1/4 teaspoon black pepper
  • 1 teaspoon lemon zest
  • 4 ounces spinach, chopped (2 cups)
  • 3 cups cooked barley
  • 1/2 cup chopped parsley
  • 1/4 cup crumbled feta, optional
  • 1 teaspoon of lemon zest

Instructions

  1. Heat a large sauté pan over medium heat.
  2. Add oil, garlic, leeks, carrots, cabbage, and fennel; sauté for
    about 3-4 minutes.
  3. Add tomatoes, Herbes, broth, beans, salt, and pepper. Bring to
    simmer and cook for 2 minutes.
  4. Add lemon zest, spinach, and barley; cook until heated
    through.
  5. Garnish with parsley and feta.