Archive for the ‘Coding’ Category

The Passing of a Legend

Sunday, August 6th, 2023

vim.jpg

This morning, a friend sent an email with a link mentioning the passing of Bram Moolenaar, the initial creator of Vim. There aren't many folks who have impacted my professional life as much as the creators of Vi, and then Bram and Vim.

I remember first using Vi at Purdue in my final year of Undergrad on the ADM terminals, and then making sure I could find termcap entries for all the terminals I could get my hands on in the days when you had terminals hooked by serial lines to the Dual VAX 11/780 system at the Electrical Engineering Department. After that, it was Auburn, and Vim on my Amiga, and then Mac System 6, and on virtually every system I had from 1985 on.

The only tool that even comples close to that longevity is GCC.

I know nothing lasts forever, and I know people pass on, but I honestly didn't expect to be so surprised by this news. I read it again, a couple of hours later on one of the RSS Feeds I read on my iPad, where, again, I have Vim. Still the same sense of sadness.

Ask not for whom the bell tolls...

Nice Postgresql Trick

Thursday, June 29th, 2023

PostgreSQL.jpg

This morning I really wanted to be able to set the psql prompt in my sessions because the system we have for creating databases doesn't really create nicely human-readable name, and even so, I'd like to have the name in the prompt match the branch of the code I'm working on... it just works out better.

So I started digging, and the -c parameter is OK, but the psql session terminates after that - so that's not going to work. And piping in the \set commands seemed to be problematic, and then I found this:

  $ psql --set=PROMPT1="${br}%R%#%x "

where br is the name of the branch I'm working on. This could then be obtained from git easily, and then put into a function, and it works great!

  #
  # "platter psql (postgres command line tool)" - start a psql session on
  # the provided branch in the PLATTER_INSTANCE in the .env file in the
  # current directory (a Node project repo). If no branch is provided, then
  # the current git branch will be used.
  #
  function ppsql() {
    if [ -f .env ]; then
      set -o allexport; source .env; set +o allexport
      local br=$1
      if [ ${#br} -eq 0 ]; then
        br=`cat .git/HEAD | sed -e 's:^.*/::'`
      fi
      local url="`npx platter postgres branch url ${br} \
           --instance $PLATTER_INSTANCE | tr -d '\n'`?sslmode=require"
      psql --set=PROMPT1="${br}%R%#%x " --set=PROMPT2="${br}%R%#%x " $url
    else
      echo "Not in a Node project directory!"
    fi
  }

With this, it's so easy now to be able to keep track of the database (branch) I'm on with Platter, and that makes a really big different to my peace of mind. 🙂

Node, Docker, Google Cloud, and Environment Variables

Monday, November 14th, 2022

GoogleCloud

At The Shop, we're using Google Cloud Run for a containerized API written in Node, and it's a fine solution - really. But one of the issues we have run into is that of environment variables. We have a lot of them. The configuration for dev versus prod versus local development is all being held in environment variables, and the standard way for these to be passed in the cloudbuild.yaml file in the Build step:


steps:
  - name: gcr.io/cloud-builders/docker
    entrypoint: '/bin/bash'
    args:
      - '-c'
      - >-
        docker build --no-cache
        --build-arg BRANCH_NAME=$BRANCH_NAME
        --build-arg THESHOP_ENV=$_THESHOP_ENV
        --build-arg BASE_API_URL=$_BASE_API_URL
        -t $_GCR_HOSTNAME/$PROJECT_ID/$REPO_NAME/$_SERVICE_NAME:$COMMIT_SHA
        . -f Dockerfile
    id: Build

and then in the Dockerfile, you have:

ARG BRANCH_NAME
RUN test -n "$BRANCH_NAME" || (echo 'please pass in --build-arg BRANCH_NAME' && exit 1)
ENV BRANCH_NAME=${BRANCH_NAME}
 
ARG THESHOP_ENV
RUN test -n "$THESHOP_ENV" || (echo 'please pass in --build-arg THESHOP_ENV' && exit 1)
ENV THESHOP_ENV=${THESHOP_ENV}
 
ARG BASE_API_URL
RUN test -n "$BASE_API_URL" || (echo 'please pass in --build-arg BASE_API_URL' && exit 1)
ENV BASE_API_URL=${BASE_API_URL}

While will place them in the environment of the built container. And all this is fine, until you start to hit the limits.

The cloudbuild.yaml command has a limit of 4000 characters, and if you have large, or sufficient number, of environment variables then you can exceed this, and we have. There is also a limit of 20 arguments to the docker build command, so again, we run into trouble if the number of environment variables gets more than that. So what can be done?

Well... since we are using Google Cloud Secrets, we could write something to scan those secrets, and pull them all into the running process, and stuff them into the process.env map for Node. But therein lies another problem: Node is asynchronous, so if we have top-level definitions that use these environment variables, like, say clients to Vendor services, then it's quite possible that they will need those variables before we have had the chance to load them.

So what can we do?

The solution that seems to work is to have a separate app that will be run in the Dockerfile, and will generate a .env file resides only in the container, and is built at the time the container is built, and contains all the environment variables we need. Then, the Node app can just use these with the dotenv library.

To make this file, we have the end of the Dockerfile look like:

# now copy everything over to the container to be made...
COPY . .
# run the node script to generate the .env file
RUN THESHOP_ENV=${THESHOP_ENV} \
  GCP_SECRETS_API_EMAIL=${GCP_SECRETS_API_EMAIL} \
  GCP_SECRETS_API_KEY=${GCP_SECRETS_API_KEY} \
  GCP_BUILD_PROJECT=${GCP_BUILD_PROJECT} \
  npm run create-env
# run the migrations for the database to keep things up to date
RUN npx migrate up --store='@platter/migrate-store'
EXPOSE 8080
CMD [ "node", "-r", "dotenv/config", "./bin/www" ]

So that we give the create-env script the few key environment variables it needs to read the Google Cloud Secrets, and then it generates the file. The create-env script is defined in the package.json as:

{
  "scripts": {
    "create-env": "node -r dotenv/config tools/make-env"
  }
}

and then the script itself is:

const arg = require('arg')
const { execSync } = require('child_process')
const { addSecretsToEnv } = require('../secrets')
const { log } = require('../logging')
 
const _help = `Help on command usage:
  npm run create-env -- --help         - show this message
  npm run create-env -- --file <name>  - where to write the env [.env]
  npm run create-env -- --verbose      - be noisy about it
 
  Nothing is required other than the FLEXBASE_ENV and some GCP env variables
  that can be specified on the command line.`;
 
/*
 * This is the main entry point for the script. We will simply read in all
 * the secrets for the THESHOP_ENV defined environment from the Cloud
 * Secrets, and then write them all to the '.env' file, as the default.
 * This will allow us to set up this environment nicely in a Dockerfile.
 */
(async () => {
  // only do this if we are run directly from 'npm run'...
  if (!module.parent) {
    // let's process the arguments and then do what they are asking
    const args = arg({
      '--help': Boolean,
      '--verbose': Boolean,
      '--file': String,
    })
    // break it into what we need
    const verbose = args['--verbose']
    const where = args['--file'] ?? '.env'
 
    // ... now let's pull in all the appropriate Secrets to the local env...
    log.info(`[makeEnv] loading the Secrets for ${process.env.THESHOP_ENV} into
        this environment...`)
    const resp = await addSecretsToEnv()
    if (verbose) {
      console.log(resp)
    }
    // ...and now we can write them out to a suitable file
    log.info(`[makeEnv] writing the environment to ${where}...`)
    const ans = execSync(`printenv > ${where}`).toString()
    if (verbose) {
      console.log(ans)
    }
    return
  }
})()

The addSecretsToEnv() is where we use the Google Secrets Node Client to read all the Secrets in our account, and one by one, pull them down and put them into process.env. The fact that this runs before the app starts is how we get around the asynchronous nature of Node, and by having it be an .env variable, we can use all the normal tools to read and process it, and we no longer need to worry about the top-level Vendor clients trying to define themselves with environment variables that haven't been defined.

Now if Node had a way to force an async function to finish before moving on, then this wouldn't be necessary, as we'd simply call the addSecretsToEnv() in the Node start-up script, well ahead of the loading of the other files. But alas... that's not how it works.

This has turned out to be a very workable solution, and we get past the limitations of the cloudbuild.yaml file, which is a great relief.

Flushing DNS Cache on macOS 13 Ventura

Saturday, November 12th, 2022

Yosemite

This morning I needed to flush the DNS cache on my MacBook Pro, and so I looked it up, and wanted to keep it around, so here we are. 🙂 The problem was that a service I use had to change DNS mapping due to a change in Google Cloud, and the nature of DNS caching is to try and minimize the hits on the DNS Servers, but this makes it hard to "forget" a DNS entry... unless you flush the cache.

It's really not all that hard:

  $ sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder

and after this, the DNS cache is empty, and all services will hit the DNS server for the IP addresses, and everything will detect the "move".

Upgraded Sublime Text to Build 4142

Thursday, November 10th, 2022

Sublime Text 2

With the update to macOS 13.0.1, I thought it would be a good time to check and see if Sublime Text had an update - because I thought I got notifications, but I guessed I missed this one. Still, it's nice to see that they are making updates to the editor.

I was chatting with a friend the other day, and with all the IDEs, and Super Editors out there, why Sublime? And it was interesting to me to realize it's the simplest, fastest, editor with the minimum chrome that reminds me of the old ProjectBuilder days on NeXT. The editor window is just that - a simple window, and there's no need for all the fancy ornamentation on the window... I know what's happening, and if I need it, I can make it visible. But 99% of the time, I just don't need it - so why clutter the screen?

With this update, there are a host of updates, fixes, and additions, and it's always nice to read the release notes, and realize this is a lot more powerful than I need. It's nice to have the ability - should I need it.

So all in all, a nice day of upgrades. 🙂

Upgraded to macOS Ventura

Wednesday, October 26th, 2022

Yosemite

This morning I took the time to get macOS Ventura 13.0 up and running on my main laptop, and I'm glad I did. Lots of nice things are coming, and I'd updated my iPhone and iPad Pro to 16.1 yesterday, so today was the day for the laptop.

I did notice that Sublime Text launched much faster on macOS 13.0, and that the only wrinkle was that Safari Technology Preview wouldn't run, of course, so I had to download the latest version from Apple, and now it's back.

All in all, a successful morning.

Found a Nice Async Batching Library

Tuesday, October 18th, 2022

NodeJS

Yesterday, I was doing a little work, and noticed that I was getting a lot of connection resets on a service that has been flawless for more than 18 months, but to be fair, the load has been rising, and after digging into the cause, it appeared that the issue was overloading the Client with so many requests, it just failed.

Typically, a client will apply back-pressure on the caller to make sure that things don't get to this point, or they will queue the requests in memory so that they will be processed, in turn, as they arrived. I'm not exactly sure what's happening, the developers of the Client are looking at this, but I needed to find something to ease the load, and so I found asyncBatch().

Let's say I had the following code:

  const balances = (await Promise.all(companies
    .map(async c => {
      const bal = await minimumDueForCompany(user, c)
      if (bal?.success && !isNil(bal?.interestDue) && bal.billDate === today) {
        bal.company = c
        return bal
      }
      return undefined
    })))
    .filter(bal => bal !== undefined)

we're running through all the items in the companies array, and for each, we are calling minimumDueForCompany() and then checking a few things, and then filtering on those that we want to see. Simple.

But if we have more than 200 elements in the companies array, and the minimumDueForCompany() employs several database queries, we could get to the point of launching more than a thousand hits at nearly the same time. If this is a background task, this might be able to starve some more important tasks with all the database aork.

A batching solution was needed. And so I went looking.

asyncBatch() follows much the same style as the Promise.all(), it just takes the values as arguments: the array, the function, and the batch size:

  const asyncBatch = require('async-batch').default
 
  const balances = (await asyncBatch(companies,
    async c => {
      const bal = await minimumDueForCompany(user, c)
      if (bal?.success && !isNil(bal?.interestDue) && bal.billDate === today) {
        bal.company = c
        return bal
      }
      return undefined
    }, 2))
    .filter(bal => bal !== undefined)

With a batch size of 2, we'll start simply, and let the background task take a little longer, while preserving the more immediate user-facing calls can have priority access.

Put this in and things are working better. It's not a perfect solution, and we still need to have the Client improved, but it gets around the two problems: Flooding the database when the use-case doesn't require it... and Failures on the Client to handle the flood. We can fine-tune the batch size later.

UPDATE: it turned out that the library launched all the work in an initial Promise.all() so it really wasn't batching the work as I'd expected. So I wrote my own using the chunk library:

  const chunk = require('chunk')
 
  /*
   * We need a function that will batch the equivalent of:
   *
   *   const resp = await Promise.all(arr.map(itm => fcn(itm)))
   *
   * but do it in batches, so that when we get a large workload, we don't
   * overwhelm the system. This is that function. The first argument is the
   * array to process, the second is the async function, that takes one
   * argument, and the last is the batch size that defaults to a reasonable
   * value.
   */
  const asyncBatch = async (arr, fcn, batchSize = 4) => {
    const ans = []
    for (const b of chunk(arr, batchSize)) {
      const blk = await Promise.all(b.map(async itm => await fcn.apply(null, [itm])))
      ans.push(...blk)
    }
    return ans
  }

This works exactly as expected, working on n of the elements at a time, and then moving to the next batch. Much cleaner.

Adding Let’s Encrypt Certs to Nginx

Thursday, October 13th, 2022

Linode

This morning I had some time and wanted to finish up the work of getting my Cloud VM running Ubuntu 22.04 working just fine as a development box - including inbound webhooks from vendors, and calls from apps like HTTPbot on my iPad Pro. The key was that I needed to be able to install and configure nginx to forward all port 443 traffic to port 6543, and that also meant getting the nginx server to be listening on port 443 with a legit certificate.

Turns out, it wasn't as bad as I thought it might be. 🙂

Starting with my Ubuntu 22.04 install, I added the packages I was going to need, based on this blog post on the nginx site.

  $ sudo apt-get -y install --no-install-recommends nginx certbot python3-certbot-nginx

Once these are installed, we could set the server_name in the nginx config:

  $ sudo /etc/nginx/sites-enabled/default

and update the server_name line to be:

  server_name mybox.mydomain.com;

and then we can get the initial certificate from Let's Encrypt and register a new email account with them with:

  $ sudo certbot --nginx -d mybox.mydomain.com -d mydomain.com

and the second -d argument is for an additional domain for the certificate. I didn't need it, so I just had the one -d pair on my certbot command.

After this, we edit the config file again, updating the port 443 section's location specification with:

  location / {
    # forward all HTTPS traffic to port 6543
    proxy_set_header  X-Forward-For $remote_addr;
    proxy_set_header  Host $http_host;
    proxy_pass        "http://127.0.0.1:6543";
  }

and then verify the nginx config with:

  $ sudo nginx -t

and then tell nginx to reload the config with:

  $ sudo nginx -s reload

At this point, the box is answering HTTPS traffic, and forwarding it on to the Node service at port 6543. Excellent. 🙂

In order to refresh the Let's Encrypt Certificate on time, let's add a simple crontab entry:

  $ crontab -e

and then have the entries:

  # run all the commands on Bash not Bourne Shell
  SHELL=/bin/bash
  # send all the mail to my main account
  MAILTO=bob@mydomain.com
 
  # check the Let's Encrypt certificate each dat at noon UTC
  0 12 * * *   sudo /usr/bin/certbot renew --quiet

And that should do it.

Setting up iPad Pro Development

Tuesday, October 11th, 2022

IPadPro

I have been doing some manual tasks that I decided today I really wanted to automate. The problem was, it was updating a few rows in a database, and so I didn't really want to expose an endpoint that achieve this, just on the very slim chance that the service might get hacked. I know, we protect these things carefully with Google Cloud Tasks, but I just felt these tasks needed to be done outside the scope of the running service, and it was possible, but tiring, to do it manually. First, with psql, and then with a bash script which took command-line options, and then called psql, and made the updates.

In all, it wasn't a bad idea. And it was working just fine - except for the tired part. 🙂 So while I didn't want to set up a crontab job on my laptop, I could set up one on a Cloud VM that is only accessible by me based on an RSA key. That is secure, and it's out on the network, so I don't have to worry about my internet service going down.

So I needed to have some way to replicate my entire development environment onto my iPad Pro, and then from there use the tools I've pulled together to try and make this happen, and decided to push it that last little bit.

Starting with Working Copy on the iPad, I have a complete, stand-alone Git client that has created local directories on my iPad so that an editor like Textastic on the iPad can access these files, save to them, and Working Copy will detect the changes. Additionally, Textastic can upload the files to a hope using scp, so I can edit and save, upload and test, and then make a PR, and push up to GitHub.

I needed to be able to run Node, and a few more watching commands on the box, so I had to get everything up on the Ubuntu 22.04 box in the Cloud, and then wrangle everything to get it all going. The most troublesome thing is that mosh, the Mobile Shell that I use with Blink on my iPad, doesn't allow for SSH Key Forwarding. It's something about mosh, and I understand the reason, but I also know the mosh folks are working to fix this - but it is annoying not to be able to use git within mosh because the SSH keys aren't carried up in the communication stack.

Still, with conventional ssh, they are carried, and I can use that, as needed. Someday soon, there will be a way to use something like Guardian Angel for the SSH key forwarding in mosh, but for now, this works.

At this point, I can edit locally... push to the cloud, have nodemon restart the server on any change... and hit the service on the standard Node port 6543. But then I needed to get nginx forwarding HTTPS to port 6543, and that's another challenge for another day.

What I can do is to run the bash scripts on the Cloud VM, and then crontab the runs so that I can never again have to worry about being so tired from running these commands at odd hours. 🙂

Upgraded Postgres to 14.4 on Homebrew

Thursday, August 11th, 2022

PostgreSQL.jpg

Nothing major, but I did notice that 14.4 was the stable release of Postgres, and so I decided it was time to upgrade to the latest from Homebrew. And it really is very easy (again):

  $ brew upgrade postgresql

and then after all the downloading and installing, we have:

  $ psql --version
  psql (PostgreSQL) 14.4

Then we are good to go! 🙂

Interestingly, this time I didn't have to run:

  $ brew services restart postgresql

for when I did, it told me it was already running. Nice - it restarted on it's own. 🙂