117 lines
3.8 KiB
Go
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")}
|
|
}
|