How to build a CLI App to track IP Address in Go

How to build a CLI App to track IP Address in Go

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 the cmd 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 the cmd 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.

  1. Using go run main.go we can run the code.

  2. Secondly, using go build will create a binary in the root folder of the project, which we can then execute locally.

  3. 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 run go install in the root directory of the project and voila you can now use the command iptracker globally. Running these two commands should give a similar output:

img

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:

img

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!

img

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.

img

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

Did you find this article valuable?

Support Hamees Sayed by becoming a sponsor. Any amount is appreciated!