The command-line interface, or the CLI, is a powerful tool for interacting with the computer. It allows us to quickly and easily run commands, manipulate files, and automate tasks. In this blog, we'll be building an IP Address tracker in Golang using Cobra, a popular package for building CLIs in the Go programming language. Cobra provides a simple and intuitive way to create powerful and user-friendly command-line applications. We will learn how to use cobra to create a basic CLI and how to add features like flags and subcommands. By the end of this blog, you will have a solid foundation for building your own CLIs using cobra.
We'll also learn to work with APIs in Golang.
Prerequisites: Go is installed locally and basic familiarity with Go syntax.
Initialize Go Module
The first thing you have to do when creating a Golang project is to initialize the Go module by typing go mod init iptracker
in the terminal by navigating to the project's root directory.
Note: You can replace
iptracker
in the above command with the project's github URL as well.
$ go mod init iptracker
Next, we'll import the cobra package, you can check the documentation here: https://github.com/spf13/cobra
The first thing we have to do is import the library, to do that we'll type the following command in the root directory of the project.
$ go get -u github.com/spf13/cobra@latest
The file structure for a Cobra-based application typically looks like this:
main.go
: This is the entry point for the application and contains the code for creating and executing the cobra command tree.cmd
directory : The directory contains the code for the different commands and subcommands within the application. Each command has it's own subdirectory within thecmd
directory, and each subdirectory contains a file named<command>.go
that contains the code for that command.pkg
directory : This directory contains any reusable packages that are used by the commands within thecmd
directory.
For example, suppose you have an application with two commands, serve
and migrate
, and the serve command has two subcommands, start
and stop
. The file structure for this application might look like this:
.
├── main.go
└── cmd
├── migrate
│ └── migrate.go
└── serve
├── start
│ └── start.go
└── stop
└── stop.go
Creating main.go file
From the cobra docs, we'll simply copy-paste the contents into the main.go
file as follows:
package main
import (
"iptracker/cmd"
)
func main() {
cmd.Execute()
}
Next, we'll create cmd
directory to store all the CLI commands, and then create a root.go
file inside the cmd
directory. Lastly, copy-paste the code from docs into root.go
.
The code structure should look like this:
.
├── main.go
├── go.mod
├── go.sum
└── cmd
├── root.go
Note: The above code is taken from Cobra package user guide with slight changes according to our requirements. You can check the docs here: https://github.com/spf13/cobra/blob/main/user_guide.md
package cmd
import (
"github.com/spf13/cobra"
)
var (
rootCmd = &cobra.Command{
Use: "iptracker",
Short: "CLI App to track IP Addresses.",
Long: `CLI App to track IP Addresses.`,
}
)
// Execute executes the root command.
func Execute() error {
return rootCmd.Execute()
}
Now there are three ways to run the above code.
Using
go run main.go
we can run the code.Secondly, using
go build
will create a binary in the root folder of the project, which we can then execute locally.Lastly, the most effective way is to use
go install
, this command will build the file and put the binary into the $GOPATH. So we'll first rungo install
in the root directory of the project and voila you can now use the commandiptracker
globally. Running these two commands should give a similar output:
How to add a new command?
To add a new command, we'll create a new file in the cmd
directory with the name of the command, in this case, I'll create a new file with the name track.go
. Alternatively, we can also use the COBRA-CLI generator which you can find in the link I shared above.
Folder Structure at this point:
.
├── main.go
├── go.mod
├── go.sum
└── cmd
├── root.go
└── track.go
In the track.go
we'll add the following content, along with the following packages that we'll require:
package cmd
import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"github.com/spf13/cobra"
)
var trackCmd = &cobra.Command{
Use: "track",
Short: "Track the IP with this command.",
Long: `Track the IP with this command.`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("track called")
},
}
func init() {
rootCmd.AddCommand(trackCmd)
}
Now, the cycle to build the code and test it repeats, first run go install
then iptracker track
to test if the newly added command is working. The output should be similar to this:
If you noticed here, the track command prints "track called". In this application, we want to pass an argument which is an IP Address. So we'll update the trackCmd
variable from the above code and instead of printing "track called", we'll instead call the argument. Along with that, we'll also add an if-else statement to check whether the IP Address is provided and a for loop because we might also receive multiple arguments.
var trackCmd = &cobra.Command{
Use: "track",
Short: "Track the IP with this command.",
Long: `Track the IP with this command.`,
// I updated the code here 👇
Run: func(cmd *cobra.Command, args []string) {
if (len(args) > 0) {
for _, ipAddress := range args {
fmt.Println(ipAddress)
}
} else {
fmt.Println("Please provide an IP Address to track.")
}
},
}
And it works!
How to get the IP Location?
To get the data of the given IP Address we'll use an Open Public API, namely https://ipinfo.io/
The format to get the IP data is as follows: https://ipinfo.io/<ip_address>/geo
. For example https://ipinfo.io/1.1.1.1/geo
Considering the response JSON. We'll map out the struct and add the following code to track.go
, keeping everything else the same as it was:
// In trackCmd variable 👇
var trackCmd = &cobra.Command{
Use: "track",
Short: "Track the IP with this command.",
Long: `Track the IP with this command.`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) > 0 {
for _, ipAddress := range args {
showData(ipAddress) // 👈 Updated
}
} else {
fmt.Println("Please provide an IP Address to track.")
}
},
}
// After the `func init()` function 👇
type Ip struct {
IP string `json::"ip"`
City string `json::"city"`
Loc string `json::loc`
Region string `json::"region"`
Country string `json::"country"`
Org string `json::"org"`
Postal string `json::"postal"`
Timezone string `json::"timezone"`
}
func showData(ipAddress string) {
url := "https://ipinfo.io/" + ipAddress + "/geo"
responseByte := getData(url)
data := Ip{}
err := json.Unmarshal(responseByte, &data)
if err != nil {
log.Println("Unable to unmarshal the response.")
}
fmt.Println("\nDATA FOUND :")
fmt.Printf("IP: %s\nLAT & LON: %s\nCITY: %s\nREGION: %s\nCOUNTRY: %s\nISP: %s\nPOSTAL: %s\nTIMEZONE: %s\n", data.IP, data.Loc, data.City, data.Region, data.Country, data.Org, data.Postal, data.Timezone)
fmt.Println("\n")
}
func getData(url string) []byte {
response, err := http.Get(url)
if err != nil {
log.Println("Unable to get the response.")
}
responseByte, err := io.ReadAll(response.Body)
if err != nil {
log.Println("Unable to read the response")
}
return responseByte
}
The function showData() gets the IP Address as a parameter and presents it in the terminal. On the other hand, the getData() function takes an URL as a parameter and returns the Response in byte format.
Adding the Version Flag
Cobra automatically creates a global flag --version
which users can invoke to determine the currently installed version of a particular CLI application. All we need to get this working with our IP Tracker is to set the Version field on the root command.
Let’s update cmd/root.go
and set rootCmd.Version to a new local variable. We use a variable - instead of hardcoding the version directly on rootCmd - so we can set the actual version number easily when invoking go install
.
Updating root.go:
var version = "1.0.0"
var (
rootCmd = &cobra.Command{
Use: "iptracker",
Version: version,
Short: "CLI App to track IP Addresses.",
Long: `CLI App to track IP Addresses.`,
}
)
Now, your application is ready. Run go install
for one final time to build the latest changes and you should be now able to track different IP Addresses and also use the --version
or -v
flag.
Tada!! You have successfully built a complete CLI Application. 🎉 Share this achievement on Twitter and with your friends. You should now have the foundational knowledge on how Cobra and CLI Apps in general work.
Conclusion
In conclusion, building a CLI application using Go and Cobra can be an effective and efficient way to create robust and user-friendly command-line tools. Go provides a simple and powerful programming language that is well-suited to building CLI applications, while Cobra offers a flexible and easy-to-use framework for organizing and managing the different commands and subcommands within an application.
By leveraging the strengths of both Go and Cobra, we can quickly create powerful and feature-rich CLI applications that provide valuable functionality to users. Whether you are a developer looking to create a new CLI application or a user looking for a powerful and easy-to-use command-line tool, Go and Cobra provide a powerful and effective solution.
Get the source code
You can find the entire source code on GitHub at https://github.com/hamees-sayed/ip-tracker