Skip to content

Commit

Permalink
uimage template configs
Browse files Browse the repository at this point in the history
Signed-off-by: Chris Koch <[email protected]>
  • Loading branch information
hugelgupf committed Feb 23, 2024
1 parent e098f5c commit bb049b6
Show file tree
Hide file tree
Showing 4 changed files with 284 additions and 0 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ require (
golang.org/x/sync v0.6.0
golang.org/x/sys v0.16.0
golang.org/x/tools v0.17.0
gopkg.in/yaml.v3 v3.0.0
)

require (
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
src.elv.sh v0.16.0-rc1.0.20220116211855-fda62502ad7f h1:pjVeIo9Ba6K1Wy+rlwX91zT7A+xGEmxiNRBdN04gDTQ=
Expand Down
137 changes: 137 additions & 0 deletions uimage/templates/templates.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// Copyright 2024 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package templates defines a uimage template configuration file parser.
package templates

import (
"errors"
"fmt"
"os"
"path/filepath"

"github.com/u-root/gobusybox/src/pkg/golang"
"github.com/u-root/mkuimage/uimage"
"gopkg.in/yaml.v3"
)

// ErrTemplateNotExist is returned when the given config name did not exist.
var ErrTemplateNotExist = errors.New("config template does not exist")

func findConfigFile(name string) (string, error) {
dir, err := os.Getwd()
if err != nil {
return "", err
}
for dir != "/" {
p := filepath.Join(dir, name)
if _, err := os.Stat(p); err == nil {
return p, nil
}
dir = filepath.Dir(dir)
}
return "", fmt.Errorf("%w: could not find %s in current directory or any parent", os.ErrNotExist, name)
}

// Command represents commands to build.
type Command struct {
// Builder is bb, gbb, or binary.
//
// Defaults to bb if not given.
Builder string

// Commands are commands or template names.
Commands []string
}

// Config is a mkuimage build configuration.
type Config struct {
GOOS string
GOARCH string
BuildTags []string `yaml:"build_tags"`
Commands []Command
Files []string
Init string
Uinit string
Shell string
}

// Templates are a set of mkuimage build configs and command templates.
type Templates struct {
Configs map[string]Config

// Commands defines a set of command template name -> commands to expand.
Commands map[string][]string
}

// Uimage returns the uimage modifiers for the given templated config name.
func (t *Templates) Uimage(config string) ([]uimage.Modifier, error) {
c, ok := t.Configs[config]
if !ok {
return nil, fmt.Errorf("%w: %q", ErrTemplateNotExist, config)
}
m := []uimage.Modifier{
uimage.WithFiles(c.Files...),
uimage.WithInit(c.Init),
uimage.WithUinitCommand(c.Uinit),
uimage.WithShell(c.Shell),
uimage.WithEnv(
golang.WithGOOS(c.GOOS),
golang.WithGOARCH(c.GOARCH),
golang.WithBuildTag(c.BuildTags...),
),
}
for _, cmds := range c.Commands {
switch cmds.Builder {
case "binary":
m = append(m, uimage.WithBinaryCommands(t.CommandsFor(cmds.Commands...)...))
case "bb", "gbb":
fallthrough
default:
m = append(m, uimage.WithBusyboxCommands(t.CommandsFor(cmds.Commands...)...))
}
}
return m, nil
}

// CommandsFor expands commands according to command templates.
func (t *Templates) CommandsFor(names ...string) []string {
var c []string
for _, name := range names {
cmds, ok := t.Commands[name]
if ok {
c = append(c, cmds...)
} else {
c = append(c, name)
}
}
return c
}

// TemplateFrom parses a template from bytes.
func TemplateFrom(b []byte) (*Templates, error) {
var tpl Templates
if err := yaml.Unmarshal(b, &tpl); err != nil {
return nil, err
}
return &tpl, nil
}

// Template parses the first file named .mkuimage.yaml in the current directory or any of its parents.
func Template() (*Templates, error) {
p, err := findConfigFile(".mkuimage.yaml")
if err != nil {
return nil, fmt.Errorf("%w: no templates found", os.ErrNotExist)
}
return TemplateFromFile(p)
}

// TemplateFromFile parses a template from the given file.
func TemplateFromFile(p string) (*Templates, error) {
b, err := os.ReadFile(p)
if err != nil {
return nil, err
}
return TemplateFrom(b)
}
144 changes: 144 additions & 0 deletions uimage/templates/templates_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// Copyright 2024 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package templates

import (
"errors"
"os"
"path/filepath"
"reflect"
"testing"

"github.com/u-root/gobusybox/src/pkg/golang"
"github.com/u-root/mkuimage/uimage"
"github.com/u-root/mkuimage/uimage/builder"
)

func TestMods(t *testing.T) {
for _, tt := range []struct {
name string
tpl string
config string
want *uimage.Opts
err error
}{
{
name: "ok",
tpl: `
commands:
core:
- github.com/u-root/u-root/cmds/core/ip
- github.com/u-root/u-root/cmds/core/init
- github.com/u-root/u-root/cmds/core/gosh
minimal:
- github.com/u-root/u-root/cmds/core/ls
- github.com/u-root/u-root/cmds/core/init
configs:
plan9:
goarch: amd64
goos: plan9
build_tags: [grpcnotrace]
files:
- /bin/bash
init: init
uinit: gosh script.sh
shell: gosh
commands:
- builder: bb
commands: [core, minimal]
- builder: bb
commands: [./u-bmc/cmd/foo]
- builder: binary
commands: [./u-bmc/cmd/bar]
- builder: binary
commands: [cmd/test2json]
`,
config: "plan9",
want: &uimage.Opts{
Env: golang.Default(golang.WithGOARCH("amd64"), golang.WithGOOS("plan9"), golang.WithBuildTag("grpcnotrace")),
ExtraFiles: []string{"/bin/bash"},
InitCmd: "init",
UinitCmd: "gosh",
UinitArgs: []string{"script.sh"},
DefaultShell: "gosh",
Commands: []uimage.Commands{
{
Builder: builder.Busybox,
Packages: []string{
"github.com/u-root/u-root/cmds/core/ip",
"github.com/u-root/u-root/cmds/core/init",
"github.com/u-root/u-root/cmds/core/gosh",
"github.com/u-root/u-root/cmds/core/ls",
"github.com/u-root/u-root/cmds/core/init",
"./u-bmc/cmd/foo",
},
},
{
Builder: builder.Binary,
Packages: []string{"./u-bmc/cmd/bar"},
},
{
Builder: builder.Binary,
Packages: []string{"cmd/test2json"},
},
},
},
},
{
name: "missing_config",
tpl: `
configs:
plan9:
goarch: amd64
goos: plan9
`,
config: "plan10",
err: ErrTemplateNotExist,
},
} {
t.Run(tt.name, func(t *testing.T) {
tpl, err := TemplateFrom([]byte(tt.tpl))
if err != nil {
t.Fatal(err)
}
mods, err := tpl.Uimage(tt.config)
if !errors.Is(err, tt.err) {
t.Fatalf("UimageMods = %v, want %v", err, tt.err)
}
if len(mods) == 0 {
return
}
got, err := uimage.OptionsFor(mods...)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(got, tt.want) {
t.Logf("got: %#v", got)
t.Logf("want: %#v", tt.want)
t.Errorf("not equal")
}
})
}
}

func TestTemplateErr(t *testing.T) {
if _, err := TemplateFrom([]byte("\t")); err == nil {
t.Fatal("Expected error")
}

d := t.TempDir()
wd, _ := os.Getwd()
_ = os.Chdir(d)
defer func() { _ = os.Chdir(wd) }()

if _, err := Template(); !errors.Is(err, os.ErrNotExist) {
t.Fatalf("Template = %v, want ErrNotExist", err)
}
if _, err := TemplateFromFile(filepath.Join(d, "foobar")); !os.IsNotExist(err) {
t.Fatalf("Template = %v, want not exist", err)
}
}

0 comments on commit bb049b6

Please sign in to comment.