AWS MSK Create & List Topics

A close up of an old typewriter keyboard.

Problem

I needed to created topics in Amazon Web Services(AWS) Managed Streaming for Apache Kafka(MSK) and I wanted to list out the topics after they were created to verify.

Solution

This solution is written in python using the confluent-kafka package. It connects to the Kafka cluster and adds the new topics. Then it prints out all of the topics for verification

from confluent_kafka.admin import AdminClient, NewTopic
import os
a = AdminClient(
{
'bootstrap.servers': os.environ.get("AWS_KAFKA_BOOTSTRAP_SERVERS"),
'security.protocol': 'SASL_SSL',
'sasl.mechanisms': 'AWS_MSK_IAM',
'sasl.aws.access.key.id': os.environ.get("AWS_ACCESS_KEY_ID"),
'sasl.aws.secret.access.key': os.environ.get("AWS_SECRET_ACCESS_KEY"),
'sasl.aws.security.token': os.environ.get("AWS_SESSION_TOKEN"),
'sasl.aws.region': 'us-east-1',
'ssl.ca.location': '/etc/ssl/certs/ca-certificates.crt',
}
)
new_topics = [NewTopic(topic, num_partitions=3) for topic in ["topic1", "topic2"]]
# Note: In a multi-cluster production scenario, it is more typical to use a replication_factor of 3 for durability.
# Call create_topics to asynchronously create topics. A dict
# of <topic,future> is returned.
fs = a.create_topics(new_topics)
# Wait for each operation to finish.
for topic, f in fs.items():
try:
f.result() # The result itself is None
print("Topic {} created".format(topic))
except Exception as e:
print("Failed to create topic {}: {}".format(topic, e))
topics = a.list_topics().topics
print("There are currently {} topics. They are {}".format(len(topics), topics))
view raw add_topics.py hosted with ❤ by GitHub

References

  1. How to create topic in Kafka with Python-kafka admin client?
  2. Pip package confluent-kafka

Generate all PlantUML diagrams

Problem

You have many C4 PlantUML diagrams in nested folders that you would like to render without having to run the PlantUML cli on each one.

Solution

find . -type f -iname "*.puml" -exec echo "Generating plantuml diagram for {}" \; -exec plantuml {} \;
view raw command.bash hosted with ❤ by GitHub
  • The output will look like the below
┌─[user@laptop] – [~/my-project] – [Fri Feb 16, 16:58]
└─[$] <git:(feature-branch)> find . -type f -iname "*.puml" -exec echo "Generating plantuml diagram for {}" \; -exec plantuml {} \;
Generating plantuml diagram for ./project1/footer.puml
Generating plantuml diagram for ./project1/1 Context.puml
Generating plantuml diagram for ./project1/header.puml
Generating plantuml diagram for ./project1/2 Container.puml
Generating plantuml diagram for ./project2/footer.puml
Generating plantuml diagram for ./project2/1 Context.puml
Generating plantuml diagram for ./project2/header.puml
Generating plantuml diagram for ./project3/network.puml
Generating plantuml diagram for ./project4/1 Context.puml
Generating plantuml diagram for ./project4/2 Container.puml
Generating plantuml diagram for ./project5/Pilot/1 Context.puml
Generating plantuml diagram for ./project5/Pilot/2 Network Container.puml
Generating plantuml diagram for ./project5/Pilot/2 Container.puml
Generating plantuml diagram for ./project5/Pilot/definitions.puml
Generating plantuml diagram for ./project6/1 Context.puml
Generating plantuml diagram for ./project6/2 Container.puml
Generating plantuml diagram for ./project6/definitions.puml
find . -type f -iname "*.puml" -exec echo "Generating plantuml diagram for {}
Time: 33.93s user | 2.96s system | 112% cpu | 32.708 total | 336528 KiB max RSS
view raw output.bash hosted with ❤ by GitHub

References

  1. Manual Page for find
  2. Github: C4 Plant UML

Install PlantUML on Mac

Problem

You would like to create PlantUML diagrams on your Mac but don’t have the prerequisites or cli installed.

Solution

Steps

  1. Install java with brew install java
  2. Install PlantUML with brew install plantuml

Output for Java

  • Your output will look like the below if it is a new installation
┌─[user@laptop] – [~] – [Wed Feb 14, 15:13]
└─[$] <> brew install java
==> Downloading https://formulae.brew.sh/api/formula.jws.json
##O=- # #
==> Downloading https://formulae.brew.sh/api/cask.jws.json
############################################################################################################################################################################################################# 100.0%
Warning: Cask homebrew/cask/java was renamed to homebrew/core/java.
==> Downloading https://ghcr.io/v2/homebrew/core/openjdk/manifests/21.0.2
Already downloaded: /Users/user/Library/Caches/Homebrew/downloads/d437bb150fa297f0ee7f7f26594cb0a1e7aec55a45ec6570ed8660a033b7c3f9–openjdk-21.0.2.bottle_manifest.json
==> Fetching openjdk
==> Downloading https://ghcr.io/v2/homebrew/core/openjdk/blobs/sha256:9850be1875b9df8e9fa3510b6f2e947be2ff228d64a1c8e0daebc57a018ce2ef
Already downloaded: /Users/user/Library/Caches/Homebrew/downloads/ea1872f4761168acb4af2804e61d03cc5b31765cbc2ee364efcf7e10a67d2c8d–openjdk–21.0.2.arm64_sonoma.bottle.tar.gz
==> Pouring openjdk–21.0.2.arm64_sonoma.bottle.tar.gz
==> Caveats
For the system Java wrappers to find this JDK, symlink it with
sudo ln -sfn /opt/homebrew/opt/openjdk/libexec/openjdk.jdk /Library/Java/JavaVirtualMachines/openjdk.jdk
openjdk is keg-only, which means it was not symlinked into /opt/homebrew,
because macOS provides similar software and installing this software in
parallel can cause all kinds of trouble.
If you need to have openjdk first in your PATH, run:
echo 'export PATH="/opt/homebrew/opt/openjdk/bin:$PATH"' >> ~/.zshrc
For compilers to find openjdk you may need to set:
export CPPFLAGS="-I/opt/homebrew/opt/openjdk/include"
==> Summary
🍺 /opt/homebrew/Cellar/openjdk/21.0.2: 600 files, 331.2MB
==> Running `brew cleanup openjdk`…
Disable this behaviour by setting HOMEBREW_NO_INSTALL_CLEANUP.
Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`).
view raw output.sh hosted with ❤ by GitHub
  • Your output will look like the below if it is already installed
┌─[user@laptop] – [~] – [Wed Feb 14, 14:55]
└─[$] <> brew install java
==> Downloading https://formulae.brew.sh/api/formula.jws.json
############################################################################################################################################################################################################# 100.0%
==> Downloading https://formulae.brew.sh/api/cask.jws.json
############################################################################################################################################################################################################# 100.0%
Warning: Cask homebrew/cask/java was renamed to homebrew/core/java.
Warning: openjdk 21.0.2 is already installed and up-to-date.
To reinstall 21.0.2, run:
brew reinstall openjdk
view raw output.sh hosted with ❤ by GitHub

Output for PlantUML

  • Your output will look like the below if it is a new installation
┌─[user@laptop] – [~] – [Wed Feb 14, 15:16]
└─[$] <> brew install plantuml
==> Downloading https://ghcr.io/v2/homebrew/core/plantuml/manifests/1.2024.1
Already downloaded: /Users/user/Library/Caches/Homebrew/downloads/34e1194efa92f43fc1d37f47254e076fc6defb4330b5500d36e5685aed26e3e6–plantuml-1.2024.1.bottle_manifest.json
==> Fetching plantuml
==> Downloading https://ghcr.io/v2/homebrew/core/plantuml/blobs/sha256:3f9404a546ff9e549e1b640e77515b3815819a4b36239fc9b17effe8135c65e3
Already downloaded: /Users/user/Library/Caches/Homebrew/downloads/2d560846774dbc633917be48445efd06524e670aaf0b7299d210b7cff5c91afa–plantuml–1.2024.1.all.bottle.tar.gz
==> Pouring plantuml–1.2024.1.all.bottle.tar.gz
🍺 /opt/homebrew/Cellar/plantuml/1.2024.1: 4 files, 11.3MB
==> Running `brew cleanup plantuml`…
Disable this behaviour by setting HOMEBREW_NO_INSTALL_CLEANUP.
Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`).
view raw output.sh hosted with ❤ by GitHub
  • Your output will look like the below if it is already installed
┌─[user@laptop] – [~] – [Wed Feb 14, 15:19]
└─[$] <> brew install plantuml
Warning: plantuml 1.2024.1 is already installed and up-to-date.
To reinstall 1.2024.1, run:
brew reinstall plantuml
view raw output.sh hosted with ❤ by GitHub

References

  1. Install Homebrew
  2. Homebrew Java
  3. Homebrew PlantUML
  4. PlantUML homepage

Bash query json

A realistic painting of a nature scene with a pair of boulders and a bridge connecting them. A stream runs between the boulders under the bridge. The trees are green and there is orangish moss on the rocks.

Problem

You want to know all of the applications that your team supports across environments. You are working with an api and it gives you a distinct list of all the apps across all environments.

{
"env1":
{
"apps":
[
"app1",
"app2",
"app3",
"app4"
]
},
"env2":
{
"apps":
[
"app1",
"app2",
"app3",
"app4",
"app5",
"app6"
]
},
"env3":
{
"apps":
[
"app1",
"app2",
"app3",
"app4",
"app6"
]
},
"env4":
{
"apps":
[
"app1",
"app2",
"app3",
"app4",
"app6"
]
},
"env5":
{
"apps":
[
"app1",
"app2",
"app4",
"app6",
"app3"
]
},
"env6":
{
"apps":
[
"app1",
"app2",
"app4",
"app6",
"app3"
]
}
}
view raw output.json hosted with ❤ by GitHub

Solution

Fortunately you can easily read the values into an array in bash with jq and some piping.

read -a APP_NAMES < <(cat environments.json | jq -r '.[].apps' | jq -s 'add' | jq 'unique' | jq '.[]' | tr '\n' ' ' )
view raw solution.sh hosted with ❤ by GitHub

One caveat to note is that if you’re using zsh you will need to use read -A rather than read -a to avoid the following error.

read: bad option: -a

References

  1. StackOverflow: How to use ‘readarray’ in bash to read lines from a file into a 2D array – Stack Overflow
  2. jq

Vault CLI Create Policy

An old bluish teal fence with some of the paint peeling off in front of some bushes and a grassy area with a blurry table in the distance.

Problem

You need to grant permissions to secret paths in a uniform way for multiple users and/or roles. You may even need to explain the access to someone working in security or compliance. This becomes much more difficult if you are granting access separately to each user and/or role.

Solution

Fortunately HashiCorp Vault provides a policy. With a policy, you can define a set of paths and access to those paths. Here is an example below.

┌─[ryan@host] – [~] – [Thu Jun 22, 14:13]
└─[$] <> cat new-policy.hcl
path "secrets/data/env1/app1/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
path "secrets/data/env2/app2/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
┌─[ryan@host] – [~] – [Thu Jun 22, 14:13]
└─[$] <> cat new-policy.hcl | vault policy write new-policy –
Success! Uploaded policy: new-policy

Policies in HashiCorp Vault provide a convenient way to grant uniform access to multiple users and/or roles. This helps to provide consistent access to enable people to work and avoids over provisioning access.

References

  1. Vault CLI Documentation for vault policy write

Terraform staying current

A highly detailed and zoomed in black and white picture of a large snail on a branch with leaves

Problem

Depending on your CI/CD story for terraform and the sensitivity of your environments, you may need to apply terraform changes manually. The challenge that can happen is that changes in terraform may not be applied. This creates a challenge when you do try to apply a change but see historic changes as well that were never applied.

Solution

  1. The first step is to understand what changes may be outstanding across your different stacks.
  2. This solution does assume that you are using terraspace
IFS=$'\n' read -rd '' -A stacks <<<"$(terraspace list -t stack)"
for stack in $stacks; do
echo "Planning ${stack#$prefix}"
output=$(terraspace plan ${stack#$prefix} 2>&1)
echo $(echo $output | grep "Plan:")
done
view raw check_stacks.sh hosted with ❤ by GitHub
  1. This will provide the following output
Planning stack1
Plan: 1 to add, 2 to change, 3 to destroy.
Planning stack2
Plan: 4 to add, 5 to change, 6 to destroy.
view raw output.sh hosted with ❤ by GitHub

Then you can use this information to confirm the changes are expected and desired. Then you can apply them so that future changes will be easier and simpler to manage.

References

  1. terraspace plan
  2. terraspace list
  3. how to grep on a variable
  4. Split string on newline and write it into array using read
  5. Split bash string by newline characters

Pipenv Null Markers

Blank sign held up by two dark grey posts on either side. It is surrounded by a green bush and green tree foliage.

Problem

While trying to use pipenv to manage python dependencies, I noticed that I got the following error when trying to convert the Pipfile.lock to a requirements.txt file.

(my-app) ┌─[ryan@host] – [~/Documents/git/my-repo] – [Wed Oct 05, 14:29]
└─[$] pipfile2req Pipfile.lock > requirements.txt
Traceback (most recent call last):
File "/Users/ryan/.local/share/virtualenvs/my-app-g5SfM111/bin/pipfile2req", line 8, in
sys.exit(main())
File "/Users/ryan/.local/share/virtualenvs/my-app-g5SfM111/lib/python3.9/site-packages/pipfile2req/init.py", line 112, in main
lines = convert_pipfile_or_lock(
File "/Users/ryan/.local/share/virtualenvs/my-app-g5SfM111/lib/python3.9/site-packages/pipfile2req/init.py", line 96, in convert_pipfile_or_lock
lines = [
File "/Users/ryan/.local/share/virtualenvs/my-app-g5SfM111/lib/python3.9/site-packages/pipfile2req/init.py", line 97, in
requirement_from_pipfile(name, package, hashes)
File "/Users/ryan/.local/share/virtualenvs/my-app-g5SfM111/lib/python3.9/site-packages/pipfile2req/requirements.py", line 10, in requirement_from_pipfile
return Requirement.parse(name, package).as_line(include_hashes)
File "/Users/ryan/.local/share/virtualenvs/my-app-g5SfM111/lib/python3.9/site-packages/pipfile2req/requirements.py", line 97, in parse
kwargs["markers"] = _merge_markers(markers)
File "/Users/ryan/.local/share/virtualenvs/my-app-g5SfM111/lib/python3.9/site-packages/pipfile2req/requirements.py", line 14, in _merge_markers
result = Marker(markers[0])
File "/Users/ryan/.local/share/virtualenvs/my-app-g5SfM111/lib/python3.9/site-packages/packaging/markers.py", line 278, in init
self._markers = _coerce_parse_result(MARKER.parseString(marker))
File "/Users/ryan/.local/share/virtualenvs/my-app-g5SfM111/lib/python3.9/site-packages/pyparsing.py", line 1941, in parseString
instring = instring.expandtabs()
AttributeError: 'NoneType' object has no attribute 'expandtabs'
view raw output.sh hosted with ❤ by GitHub

Upon further inspection, I discovered that in some cases the marker value in the Pipfile.lock was null which caused the pipfile2req program to crash.

Solution

The issue had been pipenv was version 2022.9.20 which introduced the issue. I was able to uninstall using pip uninstall pipenv.

┌─[ryan@host] – [~] – [Wed Oct 05, 16:58]
└─[$] <> pip uninstall pipenv
Found existing installation: pipenv 2022.9.20
Uninstalling pipenv-2022.9.20:
Would remove:
/Users/ryan/.pyenv/versions/3.9.6/bin/pipenv
/Users/ryan/.pyenv/versions/3.9.6/bin/pipenv-resolver
/Users/ryan/.pyenv/versions/3.9.6/lib/python3.9/site-packages/pipenv-2022.9.20.dist-info/*
/Users/ryan/.pyenv/versions/3.9.6/lib/python3.9/site-packages/pipenv/*
Proceed (Y/n)? Y
Successfully uninstalled pipenv-2022.9.20

Then I re-installed pipenv with the latest version(2022.10.4 at the time of this writing).

┌─[ryan@host] – [~] – [Wed Oct 05, 16:58]
└─[$] <> pip install pipenv
Collecting pipenv
Downloading pipenv-2022.10.4-py2.py3-none-any.whl (3.3 MB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3.3/3.3 MB 8.9 MB/s eta 0:00:00
Requirement already satisfied: virtualenv-clone>=0.2.5 in ./.pyenv/versions/3.9.6/lib/python3.9/site-packages (from pipenv) (0.5.7)
Requirement already satisfied: setuptools>=36.2.1 in ./.pyenv/versions/3.9.6/lib/python3.9/site-packages (from pipenv) (57.5.0)
Requirement already satisfied: certifi in ./.pyenv/versions/3.9.6/lib/python3.9/site-packages (from pipenv) (2022.6.15)
Requirement already satisfied: virtualenv in ./.pyenv/versions/3.9.6/lib/python3.9/site-packages (from pipenv) (20.15.0)
Requirement already satisfied: filelock<4,>=3.2 in ./.pyenv/versions/3.9.6/lib/python3.9/site-packages (from virtualenv->pipenv) (3.7.1)
Requirement already satisfied: platformdirs<3,>=2 in ./.pyenv/versions/3.9.6/lib/python3.9/site-packages (from virtualenv->pipenv) (2.5.2)
Requirement already satisfied: distlib<1,>=0.3.1 in ./.pyenv/versions/3.9.6/lib/python3.9/site-packages (from virtualenv->pipenv) (0.3.4)
Requirement already satisfied: six<2,>=1.9.0 in ./.pyenv/versions/3.9.6/lib/python3.9/site-packages (from virtualenv->pipenv) (1.16.0)
Installing collected packages: pipenv
Successfully installed pipenv-2022.10.4

That resolved the issue of putting null markers in the Pipfile.lock file for packages.

ReferenceS

  1. Pipenv lock changes markers in a non deterministic way #4660
  2. package use specified marker in Pipfile may generates error logical condition in Pipfile.lock #3616
  3. pipenv

Chef Mount Network Drive

Fuji envisions 400TB tape drive

Problem

You need to mount a network drive onto your Linux server and you have a previously established pipeline using Chef and Ruby. There needs to be a way to do this in a straight forward, repeatable and way that is easy to debug.

Solution

mount "Mounting the drive //network/path to /mount_point as myUsername on domain myDomain.com" do
device /mount_point
fstype 'cifs –verbose'
mount_point MOUNT_PATH
options "username=myUsername,vers=3.0,password='myPassword',domain=myDomain.com"
retries 5
retry_delay 30
action :mount
end
view raw mount.rb hosted with ❤ by GitHub

You’ll notice the cifs --verbose portion under fstype. This is the only way that I’ve found how to use the mount resource while still passing in the --verbose flag to mount. This provides useful output in any case where the mount is unsuccessful.

References

  1. AskUbuntu: Mounting cifs drive gives: mount error(22): Invalid argument
  2. mount.cifs(8) – Linux man page
  3. Chef Docs: mount Resource

Chef Foodcritic exit code 3

Problem

I was running bundle exec foodcritic -f correctness and it was returning an exit code of 3. This causes builds to fail, like mine in TeamCity.

FC007: Ensure recipe dependencies are reflected in cookbook metadata: cookbooks/my_cookbook/recipes/backup.rb:1

I had the following line referencing a recipe in the same cookbook

include_recipe 'firewall'

Solution

The solution was to fully qualify the recipe like

include_recipe 'my_cookbook::firewall'

This was a relatively small change, but took me quite a while to figure out. I hope this helps others scratching their heads at this error.

References

  1. About Foodcritic
  2. GitHub Issue: return code should be different than zero when issues are detected

Removing Double Quotes from Token

Problem

You’re authenticating with an api and receiving a token that you need to use for subsequent calls. However the issue is that there are double quotes around response of your token. Without removing the double quotes, the future calls will fail due to an invalid token.

..[ryan@myserver] – [~]
..[$] <()>curl -X POST https://mysite.com/auth –data '{"userName":"guest","password":"guest"}' -H "Content-Type: application/json"
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Imd1ZXN0IiwiaWF0IjoxNTE2MjM5MDIyfQ.I1QCRPaYwSZ-VUqQWCd00g_JAmBEWRnSerZ2jUPFs3w"
view raw get-token.sh hosted with ❤ by GitHub

Solution

Fortunately it’s relatively simple to do by piping the output to tr. With tr, you can use the -d argument to remove the " (double quotes) from the token that is returned.

..[ryan@myserver] – [~]
..[$] <()> token=$(echo $(curl -X POST https://mysite.com/auth –data userName":"guest","password":"guest"}' -H "Content-Type: application/json") | tr -d '"')
..[ryan@myserver] – [~]
..[$] <()> echo $token
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Imd1ZXN0IiwiaWF0IjoxNTE2MjM5MDIyfQ.I1QCRPaYwSZ-VUqQWCd00g_JAmBEWRnSerZ2jUPFs3w
view raw get-token.sh hosted with ❤ by GitHub

References

  1. StackOverflow: Shell script – remove first and last quote (“) from a variable
  2. Tr command with examples