How to not learn GoLang

, Mayer Eberhard

Scenario

I am a software engineer in a long term project. At the project we have a simple shell script for executing a build of our java and angular applications in my local environment. With continuation of the project and with more team members, the development environment has to run on different operation systems (OS). Like Windows, thats my choice, MacOS and some other Linux distributions.
So we need a switch between the windows commands and the linux ones. I thought: “Maybe it is possible to develop an os independent script,to execute the build and starting of the local environment, like the tool ‘kubectl'”. Kubectl is developed in the language Go, so I could give go a try as well.

Example

Let’s start an example. First its needed to download Go on the project homepage, or chocolately, or with winget. Than it is needed to initalize the project with a few commands:

  • First create a directory for the new project, like so: mkdir example
  • Then initalize the project with go mod init example/hello
  • Next create a file name hello.go
  • And insert the following code:
package main

import "fmt"

func main() {
    fmt.Println("Hello, World!")
}
  • At last you can now run the program with: go run .
    Really simple!

The next step is to create a cool command line interface (CLI). With programming with golang, i have still the question: “Have some one other person solved the problem of a nice CLI?” And yes someone have. The golang framework ist called “cobra” (https://cobra.dev/). Cobra ships a CLI, which can be configured, to add an implementation to this commands. To start with cobra you need to install it on your newly created hello world example. So for this example these are the steps:
First we have to install it via:

go install github.com/spf13/cobra-cli@latest

Then we navigate to our project and execute:

cobra-cli init
go run main.go

Now it is needed to add your own commands with the command. This example will add the command config for reading all needed environment parameters and show the configuration
on the system:

cobra-cli add config

In the explorer we will get now the new file config.go in the directoy cmd in the project folder.
This file will look like this:

/*
Copyright © 2023 NAME HERE <EMAIL ADDRESS>
*/
package cmd

import (
    "fmt"
    "os"

    "github.com/spf13/cobra"
)

// configCmd represents the config command
var configCmd = &cobra.Command{
    Use:   "config",
    Short: "A brief description of your command",
    Long: `A longer description that spans multiple lines and likely contains examples
and usage of using your command. For example:

Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
    Run: func(cmd *cobra.Command, args []string) {
        fmt.Println("Configured Environment variables")
        fmt.Println("ENV_1: " + os.Getenv("ENV_1"))
        fmt.Println("ENV_2: " + os.Getenv("ENV_2"))
        fmt.Println("ENV_3: " + os.Getenv("ENV_3"))
    },
}

func init() {
    rootCmd.AddCommand(configCmd)

    // Here you will define your flags and configuration settings.

    // Cobra supports Persistent Flags which will work for this command
    // and all subcommands, e.g.:
    // configCmd.PersistentFlags().String("foo", "", "A help for foo")

    // Cobra supports local flags which will only run when this command
    // is called directly, e.g.:
    // configCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

In the section var configCmd = &cobra.Command{ you can configure the documentation of the CLI help function. With the parameter Run: it is possible to add a function. This function will read the environment variables of the system and will show the content of it in the terminal. With repeating the latest steps you can generate your CLI and add the functionality as needed.

Problems

The main function of my CLI should be the interaction with non os specific shell commands to build an application via maven or npm. But that is not so easy, because i wanted to integrate besides a normal build function a start and stop command to this CLI. If you want to integrate this you have to handle os specific processes and tasks. Let’s start with the possibillity to build an application via maven:

  • First create a new command with cobra-cli add build.
  • Open the created file cmd/build.go
  • Add the import to the file:
    "os"
    "os/exec"
  • Insert the logic for building a java application:
func executeMavenBuild(path string) {
    executeCommands(path, exec.Command(os.Getenv("M2_HOME")+"mvn", "clean", "install"))
}
  • And insert the function to build a npm application:
func executeNodeBuild(path string) {
    executeCommands(path, exec.Command("npm", "install"))
    executeCommands(path, exec.Command("npm", "run", "format"))
    executeCommands(path, exec.Command("npm", "run", "lint"))
    executeCommands(path, exec.Command("npm", "run", "test"))
    executeCommands(path, exec.Command("npm", "run", "buildprod"))
}
  • Now it is needed to integrate the function executeCommands(...) for starting processes with golang:
func executeCommands(path string, command *exec.Cmd) {
    command.Dir = path

    var stdoutBuf, stderrBuf bytes.Buffer
    command.Stdout = io.MultiWriter(os.Stdout, &stdoutBuf)
    command.Stderr = io.MultiWriter(os.Stderr, &stderrBuf)

    err := command.Run()
    if err != nil {
        log.Fatalf("command.Run() failed with %s\n", err)
    }
    outStr, errStr := stdoutBuf.Bytes(), stderrBuf.Bytes()

    w := ansicolor.NewAnsiColorWriter(os.Stdout)

    fmt.Fprintf(w, "\nout:\n%s\nerr:\n%s\n", outStr, errStr)
    fmt.Print("####################")

    text := "%sforeground %sbold%s %sbackground%s\n"
    fmt.Fprintf(w, text, "\x1b[31m", "\x1b[1m", "\x1b[21m", "\x1b[41;32m", "\x1b[0m")
    fmt.Fprintf(w, text, "\x1b[32m", "\x1b[1m", "\x1b[21m", "\x1b[42;31m", "\x1b[0m")
    fmt.Fprintf(w, text, "\x1b[33m", "\x1b[1m", "\x1b[21m", "\x1b[43;34m", "\x1b[0m")
    fmt.Fprintf(w, text, "\x1b[34m", "\x1b[1m", "\x1b[21m", "\x1b[44;33m", "\x1b[0m")
    fmt.Fprintf(w, text, "\x1b[35m", "\x1b[1m", "\x1b[21m", "\x1b[45;36m", "\x1b[0m")
    fmt.Fprintf(w, text, "\x1b[36m", "\x1b[1m", "\x1b[21m", "\x1b[46;35m", "\x1b[0m")
    fmt.Fprintf(w, text, "\x1b[37m", "\x1b[1m", "\x1b[21m", "\x1b[47;30m", "\x1b[0m")

}

With this example you can execute external programs and show the result on the terminal. But there are several problems (on windows):

  • It is not possible to show the appropiate terminal colors. Thats the section with the code w := ansicolor.NewAnsiColorWriter(os.Stdout).
  • You do not get a process id, if you are start a new process. And it is not really esay to identify the right process, if there multiple node.exe or java.exe are running. The Id is required to start and stop the process itself.
  • On a windows system it is needed to handle all process operations with taskkill, but if you have no process id, it is really hard to find the right process. On the old shell script we have found the process id via the selected port. But for finding it, the application have to be started completly.

Result

Golang is a os independent language for a fast and efficent development. It delivers a CLI for generating a project and have the possibillity to add functionality via libraries. For my problem: Building a CLI for building an application is golang sufficiant. But if you want to handle processes and tasks, than you have to access the process handling via os functions. I whished golang have an answer for the multiple os process interaction. But it delivers only a cmd interface for interacting with os commands. And for my problem of serving an application it is not possible to use os independent process handling.

But i have learned a lot about golang.

Sources

General inquiries

We look forward to tackling your challenges together and discussing suitable solutions. Contact us - and get tailored solutions for your business. We look forward to your contact request!

Contact Us