Golang exec command create file and capture the file output in order to upload to storage object like S3

I'm trying to exec command create file and capture the file output in order to upload to storage object like S3 using golang but I difficulty capture the output file. For specific, I'm trying to create keystore (.jks) file using keytool.

Here's my code:

package main

import (
    "fmt"
    "os/exec"
)

func main() {
    cmd := exec.Command("keytool", "-genkeypair", "-noprompt", "-keystore", "~/SOME_PATH/USER_KEY.jks", "-keyalg", "RSA", "-keysize", "2048", "-validity", "10000", "-alias", "USER_ALIAS", "-storepass", "PASSWORD", "-keypass", "PASSWORD", "-dname", "CN=A, OU=A, O=A, L=A, S=A, C=A")
    out, err := cmd.CombinedOutput()
    if err != nil {
        fmt.Printf("failed with error: %s\n", err)
    }
    fmt.Printf("succed with output:\n%s\n", string(out))

    // Expected "out" is a .jks file not commandline output text.
    uploadToStorageObject(out)
}

func uploadToStorageObject(file []byte) {
    // Procedure upload the file to storage object
}

Or maybe there is another solution? Using java maybe? because keytool is created using java.


Solution 1:

There's a few things to address here.

Command output to stdout or stderr is not related to files the command may modify

I'm trying to exec command create file and capture the file output

The keytool utility manages keystore files. In your command you specify this file with the arguments "-keystore", "~/SOME_PATH/USER_KEY.jks".

You seem to expect the file to be in the command's CombinedOutput. But CombinedOutput

CombinedOutput runs the command and returns its combined standard output and standard error.

There is no connection between keytool's output to stdout or stderr, and the data it outputs to the key file. From what I can tell looking through keytool's man page (from openjdk, which might not be your version), this output is not useful to your program.

The file you want to upload is in "~/SOME_PATH/USER_KEY.jks".

~/ is a shell concept.

Since you're not invoking a shell, ~/ is not going to be replaced with the user's home directory; it will be interpreted as a directory literally named ~ under the current working directory. (That is, unless keytool itself takes ~/ to mean current user's home directory, which I highly doubt).

See Obtain user's home directory for how to obtain the user's home directory. Depending on that files' lifecycle, it might make more sense to put the file in /var/tmp or somewhere where the invoking user's home directory is irrelevant - but only you know whether that would make sense.

If you get an error, don't try to process the results

Here, if you get an error you output to stdout. It's good to catch the error and provide user feedback. However, the program continues regardless of whether you got an error. If err != nil your program shouldn't expect the command's intended results to have been achieve.

    out, err := cmd.CombinedOutput()
    if err != nil {
        fmt.Printf("failed with error: %s\n", err)
    }
    fmt.Printf("succed with output:\n%s\n", string(out))

In cases of error, this would print something like:

failed with error: <error message>
succed with output:
<stdout and stderr of keytool invocation>

Clearly it should be one or the other. If nothing else, I'd recommend a panic on the error which will result in an immediate abort of your program and a stack trace for your debugging (by default).

keytool and keystores are only relevant for java environment

Or maybe there is another solution? Using java maybe? because keytool is created using java

Just about every programming language can invoke an external utility like keyool. Java also probably has a library that allows you to interact with keystores without using an external keytool.

Hopefully you expect to consume your Keystore at the end of the day in a Java environment; I don't know of any other runtimes that would use a keystore (I could be naive). If you're just trying to generate an RSA key pair and aren't specifically wanting a keystore per se, you can do that with openssl or with Go directly.

If you really do want a keystore, it doesn't really matter how you invoke keytool to create it. The language choice isn't much relevant to keytool as you're simply asking the system to invoke an external program anyway. The reason to choose Go might be to make the executable easier to install (without requiring a JVM), or because you prefer or want to use Go. But you could run keytool and upload to s3 with just about anything, including a shell script (with supporting aws-cli tool, which pulls in an entire python layer - so again, Go gives you that nice executable all bundled up and self sufficient).