iTranslated by AI
You don't need to distribute binaries just because it's a compiled language—with Go
Happy 1st anniversary, Zenn! Here is a short story to celebrate. You think it'll be the same as always? Well, my apologies for that.
I posted on Twitter:
And now I'll reveal how that actually looks in practice.
Since my main machine is an Ubuntu box, the ratio of shell scripts to other code in my own operations is about 1:3. For example, if you have the Go compilation environment installed in /usr/local/go/, you can put a script like this in the /etc/profile.d/ directory:
# shellcheck shell=sh
# Expand $PATH to include the directory where golang applications go.
golang_bin_path="/usr/local/go/bin"
if [ -d "$golang_bin_path" -a -n "${PATH##*${golang_bin_path}}" -a -n "${PATH##*${golang_bin_path}:*}" ]; then
export PATH=$PATH:${golang_bin_path}
fi
A script like this will automatically add the path upon login (if the specified directory exists). I often write shell scripts when I want to describe such settings and the controls associated with them.
On the other hand, when I want to focus on "processing," I've started writing in Go more often.
For example, suppose you want to know the details of the CVSSv3 evaluation vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N for CVE-2021-33560.
Since I've created my own package for interpreting CVSS evaluation vectors:
Using this, if you write it like this:
package main
import (
"flag"
"fmt"
"io"
"os"
"github.com/spiegel-im-spiegel/go-cvss/v3/metric"
"github.com/spiegel-im-spiegel/go-cvss/v3/report"
"golang.org/x/text/language"
)
var template = "- `{{ .Vector }}`" + `
- {{ .SeverityName }}: {{ .SeverityValue }} (Score: {{ .BaseScore }})
| {{ .BaseMetrics }} | {{ .BaseMetricValue }} |
|--------|-------|
| {{ .AVName }} | {{ .AVValue }} |
| {{ .ACName }} | {{ .ACValue }} |
| {{ .PRName }} | {{ .PRValue }} |
| {{ .UIName }} | {{ .UIValue }} |
| {{ .SName }} | {{ .SValue }} |
| {{ .CName }} | {{ .CValue }} |
| {{ .IName }} | {{ .IValue }} |
| {{ .AName }} | {{ .AValue }} |
`
func main() {
flag.Parse()
if flag.NArg() < 1 {
fmt.Fprintln(os.Stderr, "Set CVSS vector")
return
}
bm, err := metric.NewBase().Decode(flag.Arg(0))
if err != nil {
fmt.Fprintf(os.Stderr, "%+v\n", err)
return
}
r, err := report.NewBase(bm, report.WithOptionsLanguage(language.Japanese)).ExportWithString(template)
if err != nil {
fmt.Fprintf(os.Stderr, "%+v\n", err)
return
}
if _, err := io.Copy(os.Stdout, r); err != nil {
fmt.Fprintf(os.Stderr, "%+v\n", err)
}
}
It will output something like this:
$ go run main.go CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
- `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N`
- Severity: High (Score: 7.5)
| Base Metrics | Value |
|--------|-------|
| Attack Vector | Network |
| Attack Complexity | Low |
| Privileges Required | None |
| User Interaction | None |
| Scope | Unchanged |
| Confidentiality Impact | High |
| Integrity Impact | None |
| Availability Impact | None |
Go's compilation is so fast it's almost instantaneous, and I often use it this way for small processing tasks that aren't worth the effort of building a binary.
One reason I’ve come to operate this way is that I’ve accumulated a fair amount of Go code resources (even though I’m a newcomer who started around 2015). While the same could be said for other languages regarding code resources, what I personally like about Go is that it is a language with a strong emphasis on refactoring, making it relatively easy to extract reusable logic into separate packages or even replace them entirely with other packages.
Furthermore, the code doesn't necessarily have to reside locally. For example, it's a common practice to fetch a shell script from the web and pipe it directly into sh like this:
$ curl https://sh.rustup.rs -sSf | sh -s -- --help
rustup-init 1.24.3 (c1c769109 2021-05-31)
The installer for rustup
USAGE:
rustup-init [FLAGS] [OPTIONS]
FLAGS:
-v, --verbose Enable verbose output
-q, --quiet Disable progress output
-y Disable confirmation prompt.
--no-modify-path Don't configure the PATH environment variable
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
--default-host <default-host> Choose a default host triple
--default-toolchain <default-toolchain> Choose a default toolchain to install
--default-toolchain none Do not install any toolchains
--profile [minimal|default|complete] Choose a profile
-c, --component <components>... Component name to also install
-t, --target <targets>... Target name to also install
Fetching it from the web and piping it directly to sh like that is a common method, and you can do something similar with Go.
For example, with entgo.io/ent, a famous ORM (Object Relational Mapper) framework for Go, many blog posts—even relatively recent ones—often instruct to go get github.com/facebook/ent/cmd/entc. However, you don't actually need to install it locally:
$ go run entgo.io/ent/cmd/ent@latest init TableName
This is sufficient (in the case of Go 1.17). The content of the generated ent/generate.go file also executes the ent module from the remote repository like this:
package ent
//go:generate go run -mod=mod entgo.io/ent/cmd/ent generate ./schema
By the way, my own depm tool works the same way. If you run:
go run github.com/spiegel-im-spiegel/depm@latest m --dot
it will output the dependencies of the Go code in the current directory in DOT language format. Give it a try! (lol)
Granted, it's obviously better to provide executable binaries for general users (who may not have the Go toolchain), and there are security risks involved in suddenly running unknown third-party code with go run path/to/package. Still, it's worth keeping in mind that you can operate on a codebase level just like a scripting language.
Discussion