A remote program execution in Go
Tags: go, tutorial, api
Published: 2019-12-17 20:47:54

---
title: A remote program execution in Go
published: true
tags: #go #tutorial #api
cover_image: https://thepracticaldev.s3.amazonaws.com/i/rr0qtuw3thsl27jsznsm.jpg
---

This time, I'll be tackling the _Go_ programming language. Even though its syntax is not the most inspiring and it's missing some modern features like generics (but this is well requested by the Go community and it'll be probably part of the _Go 2_ umbrella project), I was ready to give a try. Being backed by Google is really a competitive advantage.

I was curious of how long it takes to write a decent WEB API project, as a complete beginner in Go. Of course being proficient at C/C++ and intermediate in Rust and D, helps.

# The project

As I'm currently dabbling in _Nagios_ those days, I discovered how the _Nagios Remote Plugin Execution_ works. But also how terse the source code was. 

So I decided to experiment a _Remote Execution Program_ in Go, by developing a REST simple WEB API mock-up. The interesting fact in Go resides in its speed and in the fact it's generating a standalone executable, which is a key element when distributing your app.

The general idea is this: execute a remote program or script in the remote server running the app, by sending a HTTP GET command with a path and optionally a query string. It's easy to test using _wget_ or even _postman_. On the server side, the list of available commands is read from a _TOML_ file and result is delivered to the client as a JSON string.

Of course, this could be done using Python _Flask_ or Ruby _Sinatra_ but the point here was to experiment Go and see how far I can land.

# The server side

Basically, the server app will:

* read the TOML file and marshal its data into a Go structure
* verify if commands defined in the TOML file are found locally
* define a route to handle GET request
* start the WEB server on port specified in the TOML file
* whenever a GET request is received, exec and return output data to the client, along with the command exit code

## Managing TOML data
First of all, the _TOML_ file could look like this:

```toml
# RPE configuration file sample
[general]
logfile = "/tmp/rpe.log"
var_prefix = "$"

# network specifics
[network]
address = "127.0.0.1"
port = 8080

[[command]]
name = "list_files"
path = "/bin/ls"
args = ["-l", "$DIR"]

[[command]]
name = "find_files"
path = "/usr/bin/find"
args = ["$ROOT", "-name", "$PATTERN"]
```

The _[[command]]_ section defines the command path and filename, its arguments with potential variable arguments and a name to refer to. That name will be used as the GET route, and its query string as variable arguments. The Go _TOML_ package is used to marshal data to a Go structure, with field names mapping TOML data and sections:

```go
// Global configuration
type tomlConfig struct {
	General generalConfig
	Network networkConfig
	Command []commandDetails
}

// General section 
type generalConfig struct {
	Logfile	string
	Var_Prefix string
}

// Topic/Item configuration
type networkConfig struct {
	Port	int16
	Address	string
}

// Commands as defined in the TOML file
type commandDetails struct {
	Name	string
	Path	string
	Args	[]string
}
```

Marshalling TOML data is straightforward:

```go
// now marshall toml data
if err := config.loadConfig(tomlFileName); err != nil {
    fmt.Println(err)
    os.Exit(2)
}
```

## WEB framework

It seems _mux_ is the _de facto_ standard as a HTTP WEB framework. It works similarly to what's usual in _Flask_ or _Sinatra_, binding routes to method handlers:

```go
// http loop and receive commands
router := mux.NewRouter()

// only handle GET requests
router.HandleFunc("/{command}", CommandHandler).Methods("GET")

// build address:port and listen HTTP incoming requests
addr := fmt.Sprintf("%s:%d", config.Network.Address, config.Network.Port)
http.ListenAndServe(addr, router)
```

It merely binds the _CommandHandler_ Go function, only for _GET_ requests and for the variable URL path _{command}_. The handler is then responsible for getting command characteristics from the TOML configuration, and if found, it spawns the command and return the results as JSON. 

## Using the app

Just use _wget_ like this:

```console
$ wget -O - http://127.0.0.1:8080/find_files\?ROOT\=/home\&PATTERN\=\*.go 2>/dev/null 
```

which gives a list of _*.go_ files from the _/home_ directory.

# Possible refinements

It's easy to add other security features:

* use https
* use authentication
* use host filter to allow connexion only from specific hosts
* implement a timer to kill a lengthy command
* write unit tests

# Conclusion

I was really surprised at how fast I learned Go. Being a C expert, it took only 2 days googling Go documentation and _stackoverflow_ posts to be productive. 

Of course, it's not as powerful as Rust, because it lacks modern features like generics, sum types or macros. And I found some constructs somewhat weird (like initializing a map) and error management is rather old-style.

Being a garbage collected language, it's not meant to be a true system's programming language, but rather targeted at **devops**. I think it has a great potential and a bright future when Go 2 will be rolled out with generics and additional bells and whistles. Its standard library is large and covers lots of features.

You can get the whole source code at: https://github.com/dandyvica/rpe

Hope this helps !

> Photo by Rowan Heuvel on Unsplash