1 min read

Execute terminal scripts from Node.js and Go

Part of a series of Utterly Impractical Ideas — heavily inspired by Austin. Z. Henley

The problem:

We were working on building a Serverless application, storing a bunch of secrets in the AWS Parameter Store. Every time you add something new there, you need to make sure your changes are propagated to all the environments (we are currently operating on the 'per-developer deployments'). Everyone on our team has their own environment they can play around with and do proper e2e testing. If you don't add the new environments to all the envs you will likely cause a bunch of headaches.

The solution:

This is admittedly a workaround, I could have used aws-sdk to do all the things involved. It is good to refresh some rusty tools from your memory palace sometimes, and it was a fun exercise! There are other ways to do the same — ShellJs being the primary example. Of course, like with anything in JS there are other ways of running it using Node.js core library as well. Take spawn for example (mostly useful if you need variable flags and directory to run the commands in)

// index.mjs - to allow for top level awaits
import util from 'util'
import { exec } from 'child_process'
// Promisified to make it easier to use
const promisifiedExect = util.promisify(exec);

const stackName = "stack-name-used-by-sls"
const sourceStage = "sourceStage"

// Helper to get a bunch of commands executed easily
async function execCommand(cmd) {
    try {
    	const { stdout } = await promisifiedExect(cmd)
        return JSON.parse(stdout);
    } catch(err) {
      console.error(err); // This triggers if the command exits with anything but exit 0
    }
}

let params = await execCommand(`aws ssm get-parameters-by-path --path "/${stackName}-${sourceStage}/" --recursive`)


let commands = [];
const targets = [];
const suffix = "";

targets.forEach((target) => {
  for (const value of params.Parameters) {
    const { Name, Value, Type } = value;
    commands.push(
      `aws ssm put-parameter --name ${Name.replace(
        sourceStage,
        `${target}-${suffix}`
      )} --value ${Value} --type ${Type}`
    );
  }
});

const responses = await Promise.all(commands.map(execCommand))

console.log(responses)

Just the helper command translated into lands of go:

package main

import (
	"fmt"
	"os/exec"
)

func runCmd(toRun string) (output string, err error) {
	cmd, err := exec.Command(toRun).Output()
	if err != nil {
		fmt.Errorf("error %s", err)
	}
	output = string(cmd)
	return output, err
}

func main() {
	output, err := runCmd("ls")
	fmt.Println(output, err)
}