How to run a CLI command from Go? - command-line

How do I run a Gulp task from a Go program?
This is the command I run from a typical terminal:
gulp serv.dev
How could I run this simple line of code from golang:
package main
import (
"net/http"
"github.com/julienschmidt/httprouter"
"fmt"
)
func main() {
// What do I put here to open terminal in background and run `gulp serv.dev`?
}

What you're looking for is exec.Command
You'll pretty much want to spawn off a process that will run your gulp task.
This can be done like so:
package main
import (
"os/exec"
)
func main() {
cmd := exec.Command("gulp", "serv.dev")
if err := cmd.Run(); err != nil {
log.Fatal(err)
}
}

Take a look at exec. For your use case:
package main
import (
"net/http"
"github.com/julienschmidt/httprouter"
"fmt"
"os/exec"
"log"
)
func main() {
out, err := exec.Command("gulp", "serv.dev").Output()
if err != nil {
log.Fatal(err)
}
fmt.Printf("The date is %s\n", out)
}

Most probably you need exec package
cmd := exec.Command("gulp", "serv.dev")
err := cmd.Run()
Take a look at example at exec.Command. They explained how to pass parameters and read the output.

More generic, better output.
Use the exec.Command, along with buffers to record output and display it only when useful.
You can even make the function work with any command by using variadic arguments, aka arguments of an arbitrary quantity of elements.
Label unhanded errors appropriately, so if a command fails you are told which one and why.
Lastly note that Go, although expressive, is a quite raw language. It holds your hand for nothing. You will have to program plenty by yourself.
Example code:
package main
import (
"bytes"
"fmt"
"os"
"os/exec"
"runtime"
"strings"
)
func main() {
runCommand(currentFunction(), "ping", "-c1", "google.commm")
}
func commandErrorMessage(stderr bytes.Buffer, program string) string {
message := string(stderr.Bytes())
if len(message) == 0 {
message = "the command doesn't exist: " + program + "\n"
}
return message
}
func currentFunction() string {
counter, _, _, success := runtime.Caller(1)
if !success {
println("functionName: runtime.Caller: failed")
os.Exit(1)
}
return runtime.FuncForPC(counter).Name()
}
func printCommandError(stderr bytes.Buffer, callerFunc string, program string, args ...string) {
printCommandErrorUbication(callerFunc, program, args...)
fmt.Fprintf(os.Stderr, "%s", commandErrorMessage(stderr, program))
}
func printCommandErrorUbication(callerFunc string, program string, args ...string) {
format := "error at: %s: %s %s\n"
argsJoined := strings.Join(args, " ")
fmt.Fprintf(os.Stderr, format, callerFunc, program, argsJoined)
}
func runCommand(callerFunc string, program string, args ...string) {
command := exec.Command(program, args...)
var stderr bytes.Buffer
command.Stderr = &stderr
fail := command.Run()
if fail != nil {
printCommandError(stderr, callerFunc, program, args...)
os.Exit(1)
}
}
Example run:
$ go run test.go
error at: main.main: ping -c1 google.commm
ping: google.commm: Name or service not known
exit status 1

Related

How to use an optional query parameter in Go gorilla/mux?

Probably there is already a solution here to my problem, but I couldn't find it anywhere. I tried a bunch of stuff, but nothing worked so far.
I have something like this:
package main
import (
"fmt"
"net/http"
"github.com/gorilla/mux"
)
func HealthCheck(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, "Healthy")
// Also print the value of 'foo'
}
func main() {
r := mux.NewRouter()
r.HandleFunc("/health-check", HealthCheck).Methods("GET").Queries("foo", "{foo}").Name("HealthCheck")
r.HandleFunc("/health-check", HealthCheck).Methods("GET")
http.ListenAndServe(":8080", r)
}
What I'm trying to achieve:
curl http://localhost:8080/health-check
Should respond with: Healthy <foo> ( -> the default value of foo)
And also the following:
curl http://localhost:8080/health-check?foo=bar
Should respond with: Healthy bar
One solution if to simply handle the query params in your handler:
package main
import (
"net/http"
"github.com/gorilla/mux"
)
func HealthCheckHandler(w http.ResponseWriter, req *http.Request) {
values := req.URL.Query()
foo := values.Get("foo")
if foo != "" {
w.Write([]byte("Healthy " + foo))
} else {
w.Write([]byte("Healthy <foo>"))
}
w.WriteHeader(http.StatusOK)
}
func main() {
r := mux.NewRouter()
r.HandleFunc("/health-check", HealthCheckHandler)
http.ListenAndServe(":8080", r)
}
according to the gorilla/mux documentation, the Queries method is meant to match your handler to specific functions, akin to a regular expression.

Golang test of REST API dumps entire database

I wrote a small web service to learn unit testing. There is one endpoint to get data with a three-letter string. My code runs fine. The right query is http://localhost:8000/iata/thu, with the last bit thu being the three-letter string. I can get the correct data with it. I can also successfully get 404 with wrong ones. Then I wrote the test. It fails and dumps the entire database.
The SQLite3 database, main.go, and main_test.go are in the same directory.
Here's main_test.go:
package main
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestIata(t *testing.T) {
// "thu" is the three-letter code.
// I also tried "http://localhost:8000/iata/thu"
req, err := http.NewRequest("GET", "/iata/thu", nil)
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
handler := http.HandlerFunc(iata)
handler.ServeHTTP(rr, req)
if status := rr.Code; status != http.StatusOK {
t.Errorf("handler returned wrong status code: got %v want %v",
status, http.StatusOK)
}
expected := `[{"airport_id":"10","name":"Thule Air Base","city":"Thule","country":"Greenland","iata":"THU","icao":"BGTL","latitude":"76.5311965942","longitude":"-68.7032012939","altitude":"251","timezone":"-4","dst":"E","tz_db":"America/Thule","type":"airport","source":"OurAirports"}]`
if rr.Body.String() != expected {
t.Errorf("handler returned unexpected body: got %v want %v",
rr.Body.String(), expected)
}
}
Here's main.go:
package main
import (
"database/sql"
"encoding/json"
"log"
"net/http"
"github.com/gorilla/mux"
_ "github.com/mattn/go-sqlite3"
)
type datum struct {
AirportID string `json:"airport_id,omitempty"`
...
}
func check(err error) {
...
}
// Accesses the database and gets relevant rows.
func getRows(column string, searchTerm string) *sql.Rows {
db, err := sql.Open("sqlite3", "airports.db")
check(err)
stmt := `SELECT * FROM airports WHERE ` + column + ` LIKE ? COLLATE NOCASE;`
rows, err := db.Query(stmt, `%`+searchTerm+`%`)
check(err)
return rows
}
// Processes the data into a slice so it can be sent out as JSON.
func processData(rows *sql.Rows) []datum {
data := []datum{}
// For each row, insert data into a datum instance and then append to data slice.
for rows.Next() {
datum := datum{}
rows.Scan(&datum.AirportID,
...)
data = append(data, datum)
}
rows.Close()
return data
}
// Uses the above code to get data from the database, process it, and send it.
func getAndSendData(w http.ResponseWriter, r *http.Request, searchType string) {
params := mux.Vars(r)
searchTerm := params[searchType]
datum := getRows(searchType, searchTerm)
processed := processData(datum)
if len(processed) == 0 {
http.Error(w, "Data not found.", 404)
return
}
json.NewEncoder(w).Encode(processed)
}
func main() {
router := mux.NewRouter()
router.HandleFunc("/iata/{iata}", iata).Methods("GET")
log.Fatal(http.ListenAndServe(":8000", router))
}
func iata(w http.ResponseWriter, r *http.Request) {
searchType := "iata"
getAndSendData(w, r, searchType)
}
Running the test gets the entire database dumped in the result:
=== RUN TestIata
--- FAIL: TestIata (0.21s)
main_test.go:46: handler returned unexpected body: got [ENTIRE DATABASE DUMPED HERE] want [CORRECT DATA HERE]
FAIL
exit status 1
I've looked through a lot of tutorials, such as this one, which I feel is pretty clear. As far as I can tell, my test code is correct. I've also tried running main.go before doing the test. But that shouldn't matter, right?
What am I missing?

How do you parse date in the format:

It's not clear to me from the documentation how I would parse a date in this somewhat strange format. It seems like it might not be possible.
2016-07-08T08:34:24+00:00
The following does not work (go play link)
package main
import (
"fmt"
"time"
)
func main() {
date := "2016-07-08T08:34:24+00:00"
d, err := time.Parse("2006-01-02T15:04:05+07:00", date)
if err == nil {
fmt.Println(d)
} else {
fmt.Println(err)
}
}
Obviously a regexp could first check for this format and transform the + to a -, but that implies the standard library cant parse this date.
Go's reference layout uses -7 hours as the timezone offset, but you used +7 hours:
package main
import (
"fmt"
"time"
)
func main() {
date := "2016-07-08T08:34:24+00:00"
d, err := time.Parse("2006-01-02T15:04:05-07:00", date)
if err == nil {
fmt.Println(d)
} else {
fmt.Println(err)
}
}
https://play.golang.org/p/FNzx57R2jy

How can I return an encoded string in an http response body?

Adding an encoded string to an http resonse seems to replace some characters with !F(MISSING). How that that be prevented?
Output:
{"encodedText":"M6c8RqL61nMFy%!F(MISSING)hQmciSYrh9ZXgVFVjO"}
Code:
package main
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
)
type EncodeResult struct {
EncodedText string `json:"encodedText"`
}
func main() {
http.HandleFunc("/encodedString", encodedString)
_ = http.ListenAndServe(":8080", nil)
}
func encodedString(w http.ResponseWriter, r *http.Request) {
inputString := "M6c8RqL61nMFy/hQmciSYrh9ZXgVFVjO"
er := EncodeResult{url.QueryEscape(inputString)}
response, _ := json.Marshal(er)
w.Header().Set("Content-Type", "application/json")
fmt.Fprintf(w, string(response))
}
It appears to be escaping it normally, can you paste some code?
http://play.golang.org/p/rUEGn-KlTX
package main
import (
"fmt"
"net/url"
)
func main() {
escape := url.QueryEscape("M6c8RqL61nMFy/hQmciSYrh9ZXgVFVjO")
fmt.Println(escape)
}
You are using the escaped value "M6c8RqL61nMFy%2FhQmciSYrh9ZXgVFVjO " as a format string on this line:
fmt.Fprintf(w, string(response))
Fprintf attempts to format an argument for the verb "%2F". There is no argument, so Fprintf prints "%!F(MISSING)" for the verb.
The fix is to not use the output as a format string. Because you don't need any formatting when writing to the response, change the last line to:
w.Write(response)

Load file metadata with go

Does anyone know of a way to read the metadata and or properties of a file using the go language?
package main
import (
"fmt"
"os"
)
func main() {
fi, err := os.Stat("filename")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(fi.Name(), fi.Size())
}
Use below code, please change your path at place "filename or entire path".
package main
import (
"fmt"
"os"
)
func main() {
//The file has to be opened first
f, err := os.Open("filename or entire path")
// The file descriptor (File*) has to be used to get metadata
fi, err := f.Stat()
// The file can be closed
f.Close()
if err != nil {
fmt.Println(err)
return
}
// fi is a fileInfo interface returned by Stat
fmt.Println(fi.Name(), fi.Size())
}