iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
💮

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:

https://twitter.com/spiegel_2007/status/1438343275237154817

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:

golang-bin-path.sh
# 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:

https://github.com/spiegel-im-spiegel/go-cvss

Using this, if you write it like this:

main.go
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:

generate.go
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.

GitHubで編集を提案

Discussion