Files
mesh-catalog/modules/mesh-build-image/cmd/mesh-build-image/probe.go
T

117 lines
3.8 KiB
Go

package main
// What the module proves about its own machine before it builds (novox/hq ADR 0487 §2, issue 1703a): buildah runs,
// the account holds subordinate user and group ranges (the node-engine's useradd allocates them for every `user`
// account, so nothing declares them; this reads them), and a user namespace can be made.
import (
"bufio"
"context"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"time"
)
// ProbeTimeout bounds each probe: they read the machine and fetch nothing.
const ProbeTimeout = 20 * time.Second
// Default subordinate id files.
const (
SubUIDFile = "/etc/subuid"
SubGIDFile = "/etc/subgid"
)
// buildahVersion runs `buildah --version` and answers the version, the third word of "buildah version 1.45.1 (...)".
func (m *Module) buildahVersion(ctx context.Context) (string, error) {
ctx, cancel := context.WithTimeout(ctx, ProbeTimeout)
defer cancel()
r := m.Run(ctx, m.env(), "buildah", "--version")
if r.Status != 0 || r.Err != "" {
return "", fmt.Errorf("`buildah --version` failed (is the module buildah assigned to this machine?): %s", firstLine(r.Err+"\n"+r.Stderr))
}
f := strings.Fields(r.Stdout)
if len(f) < 3 || f[0] != "buildah" || f[1] != "version" {
return "", fmt.Errorf("`buildah --version` answered %q", strings.TrimSpace(r.Stdout))
}
return f[2], nil
}
// userNamespace makes a user namespace as the account does for a build and runs `true` in it.
func (m *Module) userNamespace(ctx context.Context) error {
ctx, cancel := context.WithTimeout(ctx, ProbeTimeout)
defer cancel()
r := m.Run(ctx, m.env(), "buildah", append(m.storeFlags(), "unshare", "true")...)
if r.Status != 0 || r.Err != "" {
return fmt.Errorf("a user namespace cannot be made: %s", firstLine(r.Err+"\n"+r.Stderr))
}
return nil
}
// SubRange is how many subordinate ids file lists for the account (by name or by numeric uid): the sum of its lines'
// counts, zero when it has none.
func SubRange(file, account, uid string) (int, error) {
f, err := os.Open(file)
if err != nil {
return 0, err
}
defer f.Close()
total := 0
sc := bufio.NewScanner(f)
for sc.Scan() {
parts := strings.Split(strings.TrimSpace(sc.Text()), ":")
if len(parts) != 3 || (parts[0] != account && (uid == "" || parts[0] != uid)) {
continue
}
if n, err := strconv.Atoi(parts[2]); err == nil && n > 0 {
total += n
}
}
return total, sc.Err()
}
func (m *Module) subFiles() (uid, gid string) {
uid, gid = m.SubUIDFile, m.SubGIDFile
if uid == "" {
uid = SubUIDFile
}
if gid == "" {
gid = SubGIDFile
}
return
}
// ranges reads the account's subordinate ranges, or says which file lacks one.
func (m *Module) ranges() (uids, gids int, err error) {
uf, gf := m.subFiles()
for _, c := range []struct {
file string
n *int
}{{uf, &uids}, {gf, &gids}} {
n, rerr := SubRange(c.file, m.Account, m.UID)
if rerr != nil {
return 0, 0, fmt.Errorf("%s cannot be read: %v", c.file, rerr)
}
if n == 0 {
return 0, 0, fmt.Errorf("%s holds no subordinate range for the account %s: a build in a user namespace has no ids to map (the node-engine's useradd allocates them when it makes a user account)", c.file, m.Account)
}
*c.n = n
}
return uids, gids, nil
}
// storeFlags are buildah's global flags that put its image store and its runtime state in the module's store
// directory, outside the account's home.
func (m *Module) storeFlags() []string {
return []string{"--root", filepath.Join(m.Store, "root"), "--runroot", filepath.Join(m.Store, "run")}
}
// env is what buildah is given beyond the base environment: a runtime directory of its own inside the store, so
// nothing is read from the account's runtime directory, and no registry credential file anywhere (the module has none
// and REGISTRY_AUTH_FILE is never set).
func (m *Module) env() []string {
return []string{"XDG_RUNTIME_DIR=" + filepath.Join(m.Store, "xdg")}
}