Statocyst Development Part 2

isNftInstalled?

Now that we have the runCommand function from our our last post, we'll create a folder to container our nftables module and make a file to store out types:

mkdir nftables
touch nftables/types.ts

Usually I would create my types first, but in this case I want my types to match the json output from nftables. So instead I'll build a way to run the commands I'll want statocyst to be able to run, then I'll build types from the output.

Let's make nftables/command.ts

touch nftables/command.ts

The first thing we want to do is build functions that will check if nft is ever installed on this system. while newer versions of Debian-based Linux distributions use nftables, they often start with just a compatibility layer that use the old iptables CLI. We want to use nft because we're fancy and like modern features (such as json formated output built-in).

So we'll write a function called isNftInstalled in which we'll run the command which nft and if it returns an empty string then we'll return false, otherwise we'll return true:

/* nftables/command.ts */
import { runCommand } from "../command.ts";
import { Result } from "@joyautomation/dark-matter";
import { createSuccess, isSuccess } from "@joyautomation/dark-matter";

export const isNftInstalled = ():Promise<Result<boolean>> => runCommand( 'which', { args: ['nft'] })
.then((result) => {
  if(isSuccess(result)) {
    return createSuccess(result.output !== '')
  } else {
    return result
  }
})

after that we'll need another function called installNft that we can use to install nft if we detect that it's missing:

export const installNft = ():Promise<Result<void>> => runCommand('sudo', { args: ['apt', 'install', 'nftables', '-y'] })
.then((result) => {
  if(isSuccess(result)) {
    return createSuccess(void 0)
  } else {
    return result
  }
})

In this case as long as apt install doesn't return stderr, we'll call that a success.

Now we can already see this pattern emerging where in .then we are checking if the result is a success, returning some manipulated value based on the result if it is a success, and returning the result itself if it's a failure. So at this point it's probably a good idea for us to make that functionality re-usable. I think it's probably a good idea to just incorporate straight into the runCommand functoin since we'll likely need to do this every time we use it.

Right now our runCommand just returns a string, but if we're going to process the output when successful, we should probably pass it a generic type that defaults to string. Then we'll also want to pass it an optional function that takes in the stdout string and returns our generic type. We'll just have the function pass through the output by default.

See Part 3 for how we'll accomplish this!