A Command-Line Interface (CLI) is a type of application that runs exclusively in the command-line terminal.
Typically, such programs are used to manage various tools related to developing and maintaining network infrastructure.
The interaction process is simple:
The user types the name of the CLI application, the command name, parameters, and sometimes additional flags into the terminal.
The CLI application performs the requested action and sends a text response to the terminal.
CLI applications may seem outdated due to the lack of a graphical user interface (GUI), but they are still considered the most versatile, fast, and convenient way for system administration.
To create a CLI in Go, you can use a special package called Cobra, which is developed by third-party developers. It is built on top of the flag
package (a command-line flag parser) from Go's standard library and provides a higher level of abstraction.
Cobra is a complete CLI platform for the Go language, consisting of two main components:
Cobra was originally developed by one of the Go team members, Steve Francia (spf13), for the Hugo project — a special framework for building websites. Over time, Cobra became one of the most popular packages in the Go community.
Cobra offers several simple features for building modern command-line interfaces. Additionally, Cobra includes a high-level controller to help organize the code for the CLI application being developed.
Cobra implements:
In fact, large projects such as Kubernetes, Hugo, and CockroachDB are built on Go and use the Cobra package to handle commands.
CLI commands follow a fairly standard pattern:
{application} {command code} [arguments] [--flags and their parameters]
For example, commands in real projects might look like this:
kubectl get all -n kube-system
Or like this:
etcdctl put first second
The working entities in Cobra can be divided into three types — each represents the structure of commands in the console terminal:
The POSIX standard defines a pattern (scheme) for organizing arguments and flags that CLI applications should follow. This is the classic format that most developers are familiar with — numerous Linux utility programs (such as ls
, cp
, useradd
) and third-party applications follow this convention.
It is important to remember that the command scheme is strictly formalized in the standard and looks as follows:
application_name [-a] [-b] [-c argument] [-d|-e]
Each application may have multiple versions of the same option — long and short forms. There is a clear rule that the short version must consist of only one character.
First, check whether the Go compiler is installed on your system. You can do this by running the version query command:
go version
If Go is installed, the console will display the Go version along with the operating system’s short name.
Next, create a separate directory for our Cobra project:
mkdir CobraProject
After that, navigate into it:
cd CobraProject
Golang has some peculiarities in its module system, which is necessary for connecting packages. Therefore, you need to initialize the project directory with a special command:
go mod init CobraProject
This will turn the directory into a full-fledged Go module, and the console will display a message about the creation of the module named CobraProject.
Starting from Go 1.18, Go includes a special command go install, which automatically installs remote modules. Therefore, we will use it to download the Cobra package from the official GitHub repository:
go install github.com/spf13/cobra-cli@latest
Note that with the @latest
tag we are installing the latest release.
After installation, the executable file cobra-cli
will be available in the terminal. We will use this tool to initialize the Cobra project in our working directory — at this point, you should already be in that directory:
cobra-cli init
Once executed, this will create several files in your working directory, containing the standard Cobra package code along with the project name CobraProject
.
The file structure will look like this:
CobraProject/
cmd/
root.go
main.go
go.mod
go.sum
The main.go
file is the entry point for the CLI application. Its default content will look something like this:
package main
import (
"CobraProject/cmd" // the path may vary depending on the location of the working directory
)
func main() {
cmd.Execute()
}
All commands are placed as separate files in the /cmd
directory.The root.go
file is the root command handler — essentially the base command for any command-line interface.
For example, consider the following command:
go get URL
Here, go
is the root command, which is handled by root.go
, and get
is a subcommand, whose handler is placed in a file different from root.go
.
To build the CLI application, you use the same command as for building any regular Go binary project:
go build
By default, the executable file will appear in the project’s working directory.
To make the built CLI application usable, you also need to install it:
go install
After this, the CLI application will be available for execution directly from the terminal. To use it, simply type the project name in the console:
CobraProject
If everything is set up correctly, the standard output for the command without any parameters will appear in the console. Of course, you can modify this standard output later in the root.go
file.
Each command entered in the terminal calls a corresponding Go function, which executes the logic for that command. Any parameters and flags specified in the terminal are passed into the function.
As a simple example, we will implement a small function that displays the time in the current time zone. To do this, we will use the time
package.
After initializing the CLI, the cmd
directory should have been created in your working directory. Let's go to it:
cd cmd
Now, let's create a file that will contain our function:
touch timefunc.go
The code inside the file will look like this:
package cmd // specify the name of our package
import "time" // import the standard Go time package
func getTimeFromZone(zone string) (string, error) {
loc, err := time.LoadLocation(zone) // get the current location
// check for error
if err != nil {
return "", err // return an empty result with error details
}
timeNow := time.Now().In(loc) // get the current time based on the location
return timeNow.Format(time.RFC1123), nil // return the formatted result without error details
}
As you can see, the function returns two values: the result and any error data. You can use it in the CLI to retrieve the time for a specified time zone.
Now that the functional part of our application is ready, we can "register" the command in the CLI application for external access.
There is a separate add
command for this:
cobra-cli add timefromzone
After running this command, a timefromzone.go
file will appear in the cmd
folder with the standard code inside.
In this same folder, you will also find the root.go
file, responsible for the "root" command processing, i.e., the command without any parameters.
It’s easy to guess that the handlers for console commands are formed in the file system as separate Go source files.
Let’s open the newly created file and populate it with the following code:
package cmd
import (
"fmt"
"log"
"github.com/spf13/cobra"
)
var timefromzoneCmd = &cobra.Command{
Use: "timefromzone",
Short: "Returns the time from a given geographical zone",
Long: `This command returns the time from a specified geographical zone. It accepts only one argument — the zone for which the time is required. The result is returned in the RFC1123 format.`,
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
timefromzone := args[0]
timeNow, err := getTimeFromZone(timefromzone)
if err != nil {
log.Fatalln("Invalid time zone")
}
fmt.Println(timeNow)
},
}
func init() {
rootCmd.AddCommand(timefromzoneCmd) // add the new command to the root command
}
Let’s break down what each field means in the command definition:
getTimeFromZone
function.In some cases, you could simplify the code by writing the logic directly inside the command handler function, like this:
import "time"
var timefromzoneCmd = &cobra.Command{
Use: "timefromzone",
Short: "Returns the time from a given geographical zone",
Long: `This command returns the time from a specified geographical zone. It accepts only one argument — the zone for which the time is required. The result is returned in RFC1123 format.`,
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
zone := args[0]
loc, err := time.LoadLocation(zone)
if err != nil {
log.Fatalln("Invalid time zone")
}
fmt.Println(time.Now().In(loc).Format(time.RFC1123))
},
}
In this case, we directly implemented the logic for retrieving the time inside the Run
function. If the time zone is invalid, an error message is logged.
Once the command is added, we just need to reinstall our CLI application:
go install
Now, we can use the application from the terminal by specifying the command name and passing the time zone code as an argument:
CobraProject timefromzone Europe/Nicosia
The console output will look something like this:
Sun, 10 Nov 2024 12:41:06 Europe/Nicosia
You can find a complete list of time zones and their codes in Wikipedia.
Typically, when running command-line applications, you can specify flags in addition to parameters. Flags are options that modify the behavior of a specific command. They are easily recognized by the preceding hyphen (or double hyphen).
The inclusion of flags in a CLI application adds variability and flexibility to the command behavior. Without flags, you would have to create many complex functions with a lot of redundant code. In this sense, flags help standardize the application.
Cobra has two types of flags:
Local flags: These only apply to the specific command.
Persistent flags: These can apply to all commands and subcommands.
Let’s return to the timefromzone.go
file and modify the initialization function to add a flag. The flag will specify the desired time format.
Here’s how you can add the flag to your command:
func init() {
rootCmd.AddCommand(timefromzoneCmd) // Add the defined command to the root command
timefromzoneCmd.Flags().String("format", "", "Outputs the time in the yyyy-mm-dd format") // Add a flag to the command
}
This adds a flag named --format
, which specifies the time format.
Here is the complete updated file with flag handling:
package cmd
import (
"fmt"
"time"
"github.com/spf13/cobra"
)
var timefromzoneCmd = &cobra.Command{
Use: "timefromzone",
Short: "Returns the time from a given geographical zone",
Long: `This command returns the time from a specified geographical zone. It accepts only one argument — the zone for which the time is required. The result is returned in RFC1123 format.`,
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
var date string
zone := args[0]
loc, _ := time.LoadLocation(zone) // Load the location from the zone argument
fla, _ := cmd.Flags().GetString("format") // Get the value of the 'format' flag
if fla != "" {
date = time.Now().In(loc).Format(fla) // If flag is provided, use custom format
} else {
date = time.Now().In(loc).Format(time.RFC1123) // Default format
}
fmt.Printf("Current time in timezone %v: %v\n", loc, date)
},
}
func init() {
rootCmd.AddCommand(timefromzoneCmd) // Add the timefromzone command to the root command
timefromzoneCmd.Flags().String("format", "", "Outputs the time in the yyyy-mm-dd format") // Add the 'format' flag
}
Now, let's reinstall the updated CLI application:
go install
To use the new flag, run the command with the --format
flag, like this:
CobraProject timefromzone Europe/Nicosia --format 2006-01-02
The output will be formatted according to the flag, like this:
Current time in timezone Europe/Nicosia: 2024-11-10
Here, the --format
flag explicitly tells the command to display the time in the yyyy-mm-dd
format, and the result will reflect this format.
The Cobra package for the Go programming language is an excellent solution that helps developers abstract away the complexities of low-level command-line parsing functions provided by the standard library.
Cobra is a kind of framework for CLI applications that alleviates the "headache" developers face when working with the command-line terminal, allowing them to focus more on business logic.
Each command is represented as a separate file in the /cmd
directory, and you can modify it using flags. This is convenient because you can explicitly build a hierarchy of commands and control the process of handling them by editing hook functions like init or run.
This feature gives Cobra CLI applications a more structured layout and less clutter, forming a solid framework.
It’s important to note that third-party developers created Cobra, so it is hosted in a separate GitHub repository and is not part of the Go standard library.
Additionally, on the official Cobra website, you can find installation instructions and details about using the command-line parser.
On our cloud app platform you can deploy Golang apps, such as Beego and Gin.