diff --git a/BUILD b/BUILD index d1f3e0d..c8f0a10 100644 --- a/BUILD +++ b/BUILD @@ -7,10 +7,7 @@ go_library( srcs = ["main.go"], importpath = "git.sr.ht/~motiejus/code/undocker", visibility = ["//visibility:private"], - deps = [ - "//src/undocker/rootfs:go_default_library", - "@com_github_jessevdk_go_flags//:go_default_library", - ], + deps = ["//src/undocker/rootfs:go_default_library"], ) go_binary( @@ -24,7 +21,6 @@ go_test( srcs = ["main_test.go"], embed = [":go_default_library"], deps = [ - "@com_github_jessevdk_go_flags//:go_default_library", "@com_github_stretchr_testify//assert:go_default_library", "@com_github_stretchr_testify//require:go_default_library", ], diff --git a/internal/cmdlxcconfig/BUILD b/internal/cmdlxcconfig/BUILD deleted file mode 100644 index 5840180..0000000 --- a/internal/cmdlxcconfig/BUILD +++ /dev/null @@ -1,23 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") - -go_library( - name = "go_default_library", - srcs = ["cmdlxcconfig.go"], - importpath = "git.sr.ht/~motiejus/code/undocker/internal/cmdlxcconfig", - visibility = ["//src/undocker:__subpackages__"], - deps = [ - "//src/undocker/lxcconfig:go_default_library", - "@com_github_jessevdk_go_flags//:go_default_library", - ], -) - -go_test( - name = "go_default_test", - srcs = ["cmdlxcconfig_test.go"], - embed = [":go_default_library"], - deps = [ - "@com_github_jessevdk_go_flags//:go_default_library", - "@com_github_stretchr_testify//assert:go_default_library", - "@com_github_stretchr_testify//require:go_default_library", - ], -) diff --git a/internal/cmdlxcconfig/cmdlxcconfig.go b/internal/cmdlxcconfig/cmdlxcconfig.go deleted file mode 100644 index 40bdd4c..0000000 --- a/internal/cmdlxcconfig/cmdlxcconfig.go +++ /dev/null @@ -1,76 +0,0 @@ -package cmdlxcconfig - -import ( - "errors" - "fmt" - "io" - "os" - - goflags "github.com/jessevdk/go-flags" - "git.sr.ht/~motiejus/code/undocker/lxcconfig" -) - -const _description = "Create an LXC-compatible container configuration" - -// Command is an implementation of go-flags.Command -type Command struct { - configer func(io.ReadSeeker, io.Writer) error - Stdout io.Writer - - PositionalArgs struct { - Infile goflags.Filename `long:"infile" description:"Input tarball"` - Outfile string `long:"outfile" description:"Output path, stdout is '-'"` - } `positional-args:"yes" required:"yes"` -} - -// NewCommand creates a new Command struct -func NewCommand() *Command { - return &Command{ - configer: lxcconfig.LXCConfig, - Stdout: os.Stdout, - } -} - -// ShortDesc returns the command's short description -func (*Command) ShortDesc() string { return _description } - -// LongDesc returns the command's long description -func (*Command) LongDesc() string { return _description } - -// Execute executes lxcconfig Command -func (c *Command) Execute(args []string) (err error) { - if len(args) != 0 { - return errors.New("too many args") - } - - rd, err := os.Open(string(c.PositionalArgs.Infile)) - if err != nil { - return err - } - defer func() { - err1 := rd.Close() - if err == nil { - err = err1 - } - }() - - var out io.Writer - outf := string(c.PositionalArgs.Outfile) - if fname := string(c.PositionalArgs.Outfile); fname == "-" { - out = c.Stdout - } else { - outf, err := os.Create(outf) - if err != nil { - return fmt.Errorf("create: %w", err) - } - defer func() { - err1 := outf.Close() - if err == nil { - err = err1 - } - }() - out = outf - } - - return c.configer(rd, out) -} diff --git a/internal/cmdlxcconfig/cmdlxcconfig_test.go b/internal/cmdlxcconfig/cmdlxcconfig_test.go deleted file mode 100644 index c0f12de..0000000 --- a/internal/cmdlxcconfig/cmdlxcconfig_test.go +++ /dev/null @@ -1,93 +0,0 @@ -package cmdlxcconfig - -import ( - "bytes" - "io" - "io/ioutil" - "path/filepath" - "testing" - - goflags "github.com/jessevdk/go-flags" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestExecute(t *testing.T) { - var _foo = []byte("foo foo") - - tests := []struct { - name string - fixture func(*testing.T, string) - infile string - outfile string - wantErr string - }{ - { - name: "ok passthrough via stdout", - infile: "t10-in.txt", - fixture: func(t *testing.T, dir string) { - fname := filepath.Join(dir, "t10-in.txt") - require.NoError(t, ioutil.WriteFile(fname, _foo, 0644)) - }, - outfile: "-", - }, - { - name: "ok passthrough via file", - infile: "t20-in.txt", - fixture: func(t *testing.T, dir string) { - fname := filepath.Join(dir, "t20-in.txt") - require.NoError(t, ioutil.WriteFile(fname, _foo, 0644)) - }, - outfile: "t20-out.txt", - }, - { - name: "infile does not exist", - infile: "t3-does-not-exist.txt", - wantErr: "^open .*t3-does-not-exist.txt: no such file or directory$", - }, - { - name: "outpath dir not writable", - outfile: filepath.Join("t4", "does", "not", "exist"), - wantErr: "^create: open .*/t4/does/not/exist: no such file or directory", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - dir := t.TempDir() - var stdout bytes.Buffer - c := &Command{Stdout: &stdout} - if tt.fixture != nil { - tt.fixture(t, dir) - } - if tt.outfile != "-" { - tt.outfile = filepath.Join(dir, tt.outfile) - } - inf := filepath.Join(dir, tt.infile) - c.PositionalArgs.Infile = goflags.Filename(inf) - c.PositionalArgs.Outfile = tt.outfile - c.configer = configerPassthrough - - err := c.Execute(nil) - if tt.wantErr != "" { - require.Error(t, err) - assert.Regexp(t, tt.wantErr, err.Error()) - return - } - var out []byte - require.NoError(t, err) - if tt.outfile == "-" { - out = stdout.Bytes() - } else { - out, err = ioutil.ReadFile(tt.outfile) - require.NoError(t, err) - } - assert.Equal(t, []byte("foo foo"), out) - }) - } -} - -func configerPassthrough(r io.ReadSeeker, w io.Writer) error { - _, err := io.Copy(w, r) - return err -} diff --git a/internal/cmdmanpage/BUILD b/internal/cmdmanpage/BUILD deleted file mode 100644 index 73c9003..0000000 --- a/internal/cmdmanpage/BUILD +++ /dev/null @@ -1,9 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") - -go_library( - name = "go_default_library", - srcs = ["cmdmanpage.go"], - importpath = "git.sr.ht/~motiejus/code/undocker/internal/cmdmanpage", - visibility = ["//src/undocker:__subpackages__"], - deps = ["@com_github_jessevdk_go_flags//:go_default_library"], -) diff --git a/internal/cmdmanpage/cmdmanpage.go b/internal/cmdmanpage/cmdmanpage.go deleted file mode 100644 index 3788480..0000000 --- a/internal/cmdmanpage/cmdmanpage.go +++ /dev/null @@ -1,25 +0,0 @@ -package cmdmanpage - -import ( - "io" - "os" - - goflags "github.com/jessevdk/go-flags" -) - -type Command struct { - parser *goflags.Parser - stdout io.Writer -} - -func NewCommand(parser *goflags.Parser) *Command { - return &Command{ - parser: parser, - stdout: os.Stdout, - } -} - -func (c *Command) Execute(args []string) error { - c.parser.WriteManPage(c.stdout) - return nil -} diff --git a/internal/cmdrootfs/BUILD b/internal/cmdrootfs/BUILD deleted file mode 100644 index 159b1fd..0000000 --- a/internal/cmdrootfs/BUILD +++ /dev/null @@ -1,23 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") - -go_library( - name = "go_default_library", - srcs = ["cmdrootfs.go"], - importpath = "git.sr.ht/~motiejus/code/undocker/internal/cmdrootfs", - visibility = ["//src/undocker:__subpackages__"], - deps = [ - "//src/undocker/rootfs:go_default_library", - "@com_github_jessevdk_go_flags//:go_default_library", - ], -) - -go_test( - name = "go_default_test", - srcs = ["cmdrootfs_test.go"], - embed = [":go_default_library"], - deps = [ - "@com_github_jessevdk_go_flags//:go_default_library", - "@com_github_stretchr_testify//assert:go_default_library", - "@com_github_stretchr_testify//require:go_default_library", - ], -) diff --git a/internal/cmdrootfs/cmdrootfs.go b/internal/cmdrootfs/cmdrootfs.go deleted file mode 100644 index bb0b04e..0000000 --- a/internal/cmdrootfs/cmdrootfs.go +++ /dev/null @@ -1,75 +0,0 @@ -package cmdrootfs - -import ( - "errors" - "fmt" - "io" - "os" - - "git.sr.ht/~motiejus/code/undocker/rootfs" - goflags "github.com/jessevdk/go-flags" -) - -const _description = "Flatten a docker container image to a tarball" - -// Command is an implementation of go-flags.Command -type Command struct { - flattener func(io.ReadSeeker, io.Writer) error - Stdout io.Writer - - PositionalArgs struct { - Infile goflags.Filename `long:"infile" description:"Input tarball"` - Outfile string `long:"outfile" description:"Output path, stdout is '-'"` - } `positional-args:"yes" required:"yes"` -} - -// NewCommand creates a new Command struct -func NewCommand() *Command { - return &Command{ - flattener: rootfs.Flatten, - Stdout: os.Stdout, - } -} - -// ShortDesc returns the command's short description -func (*Command) ShortDesc() string { return _description } - -// LongDesc returns the command's long description -func (*Command) LongDesc() string { return _description } - -// Execute executes rootfs Command -func (c *Command) Execute(args []string) (err error) { - if len(args) != 0 { - return errors.New("too many args") - } - - rd, err := os.Open(string(c.PositionalArgs.Infile)) - if err != nil { - return err - } - defer func() { - err1 := rd.Close() - if err == nil { - err = err1 - } - }() - - var out io.Writer - if fname := string(c.PositionalArgs.Outfile); fname == "-" { - out = c.Stdout - } else { - outf, err := os.Create(fname) - if err != nil { - return fmt.Errorf("create: %w", err) - } - defer func() { - err1 := outf.Close() - if err == nil { - err = err1 - } - }() - out = outf - } - - return c.flattener(rd, out) -} diff --git a/internal/cmdrootfs/cmdrootfs_test.go b/internal/cmdrootfs/cmdrootfs_test.go deleted file mode 100644 index a1b2df9..0000000 --- a/internal/cmdrootfs/cmdrootfs_test.go +++ /dev/null @@ -1,93 +0,0 @@ -package cmdrootfs - -import ( - "bytes" - "io" - "io/ioutil" - "path/filepath" - "testing" - - goflags "github.com/jessevdk/go-flags" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestExecute(t *testing.T) { - var _foo = []byte("foo foo") - - tests := []struct { - name string - fixture func(*testing.T, string) - infile string - outfile string - wantErr string - }{ - { - name: "ok passthrough via stdout", - infile: "t10-in.txt", - fixture: func(t *testing.T, dir string) { - fname := filepath.Join(dir, "t10-in.txt") - require.NoError(t, ioutil.WriteFile(fname, _foo, 0644)) - }, - outfile: "-", - }, - { - name: "ok passthrough via file", - infile: "t20-in.txt", - fixture: func(t *testing.T, dir string) { - fname := filepath.Join(dir, "t20-in.txt") - require.NoError(t, ioutil.WriteFile(fname, _foo, 0644)) - }, - outfile: "t20-out.txt", - }, - { - name: "infile does not exist", - infile: "t3-does-not-exist.txt", - wantErr: "^open .*t3-does-not-exist.txt: no such file or directory$", - }, - { - name: "outpath dir not writable", - outfile: filepath.Join("t4", "does", "not", "exist"), - wantErr: "^create: open .*/t4/does/not/exist: no such file or directory", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - dir := t.TempDir() - var stdout bytes.Buffer - c := &Command{Stdout: &stdout} - if tt.fixture != nil { - tt.fixture(t, dir) - } - if tt.outfile != "-" { - tt.outfile = filepath.Join(dir, tt.outfile) - } - inf := filepath.Join(dir, tt.infile) - c.PositionalArgs.Infile = goflags.Filename(inf) - c.PositionalArgs.Outfile = tt.outfile - c.flattener = flattenPassthrough - - err := c.Execute(nil) - if tt.wantErr != "" { - require.Error(t, err) - assert.Regexp(t, tt.wantErr, err.Error()) - return - } - var out []byte - require.NoError(t, err) - if tt.outfile == "-" { - out = stdout.Bytes() - } else { - out, err = ioutil.ReadFile(tt.outfile) - require.NoError(t, err) - } - assert.Equal(t, []byte("foo foo"), out) - }) - } -} - -func flattenPassthrough(r io.ReadSeeker, w io.Writer) error { - _, err := io.Copy(w, r) - return err -} diff --git a/lxcconfig/BUILD b/lxcconfig/BUILD deleted file mode 100644 index 894464b..0000000 --- a/lxcconfig/BUILD +++ /dev/null @@ -1,19 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") - -go_library( - name = "go_default_library", - srcs = ["lxcconfig.go"], - importpath = "git.sr.ht/~motiejus/code/undocker/lxcconfig", - visibility = ["//visibility:public"], -) - -go_test( - name = "go_default_test", - srcs = ["lxcconfig_test.go"], - embed = [":go_default_library"], - deps = [ - "//src/undocker/internal/tartest:go_default_library", - "@com_github_stretchr_testify//assert:go_default_library", - "@com_github_stretchr_testify//require:go_default_library", - ], -) diff --git a/lxcconfig/lxcconfig.go b/lxcconfig/lxcconfig.go deleted file mode 100644 index baa5722..0000000 --- a/lxcconfig/lxcconfig.go +++ /dev/null @@ -1,169 +0,0 @@ -package lxcconfig - -import ( - "archive/tar" - "encoding/json" - "errors" - "fmt" - "io" - "strings" - "text/template" -) - -const ( - _json = ".json" - _manifestJSON = "manifest.json" -) - -var ( - _lxcTemplate = template.Must( - template.New("lxcconfig").Parse("" + - "lxc.include = LXC_TEMPLATE_CONFIG/common.conf\n" + - "lxc.architecture = {{ .Architecture }}\n" + - "lxc.init.cmd = '{{ .Cmd }}'\n" + - "{{ if .Cwd }}lxc.init.cwd = {{ .Cwd }}\n{{ end }}" + - "{{ range .Env }}lxc.environment = {{ . }}\n{{ end }}")) - errBadManifest = errors.New("bad or missing manifest.json") -) - -type ( - // lxcConfig is passed to _lxcTemplate - lxcConfig struct { - Architecture string - Cmd string - Cwd string - Env []string - } - - // offsetSize is a tuple which stores file offset and size - offsetSize struct { - offset int64 - size int64 - } - - // dockerManifest is manifest.json - dockerManifest []struct { - Config string `json:"Config"` - } - - // dockerConfig returns interesting configs for the container. user/group are - // skipped, since Docker allows specifying them by name, which would require - // peeking into the container image's /etc/passwd to resolve the names to ints. - dockerConfig struct { - Architecture string `json:"architecture"` - Config dockerConfigConfig `json:"config"` - } - - dockerConfigConfig struct { - Entrypoint []string `json:"Entrypoint"` - Cmd []string `json:"Cmd"` - WorkingDir string `json:"WorkingDir"` - Env []string `json:"Env"` - } -) - -// LXCConfig accepts a Docker container image and returns lxc configuration. -func LXCConfig(rd io.ReadSeeker, wr io.Writer) error { - dockerCfg, err := getDockerConfig(rd) - if err != nil { - return err - } - lxcCfg := docker2lxc(dockerCfg) - return lxcCfg.WriteTo(wr) -} - -func docker2lxc(d dockerConfig) lxcConfig { - // cmd/entrypoint logic is copied from lxc-oci template and adapted - // for simple double-argument quoting. - ep := quoted(d.Config.Entrypoint) - cmd := quoted(d.Config.Cmd) - if len(ep) == 0 { - ep = cmd - if len(ep) == 0 { - ep = "/bin/sh" - } - } else if len(cmd) != 0 { - ep = ep + " " + cmd - } - return lxcConfig{ - Architecture: d.Architecture, - Cmd: ep, - Env: d.Config.Env, - Cwd: d.Config.WorkingDir, - } -} - -func (l lxcConfig) WriteTo(wr io.Writer) error { - return _lxcTemplate.Execute(wr, l) -} - -func getDockerConfig(rd io.ReadSeeker) (dockerConfig, error) { - tr := tar.NewReader(rd) - // get offsets to all json files rd the archive - jsonOffsets := map[string]offsetSize{} - for { - hdr, err := tr.Next() - if err == io.EOF { - break - } - if hdr.Typeflag != tar.TypeReg { - continue - } - if !strings.HasSuffix(hdr.Name, _json) { - continue - } - here, err := rd.Seek(0, io.SeekCurrent) - if err != nil { - return dockerConfig{}, err - } - jsonOffsets[hdr.Name] = offsetSize{ - offset: here, - size: hdr.Size, - } - } - - // manifest is the docker manifest rd the image - var manifest dockerManifest - if err := parseJSON(rd, jsonOffsets, _manifestJSON, &manifest); err != nil { - return dockerConfig{}, err - } - if len(manifest) == 0 { - return dockerConfig{}, errBadManifest - } - - var config dockerConfig - if err := parseJSON(rd, jsonOffsets, manifest[0].Config, &config); err != nil { - return dockerConfig{}, err - } - - return config, nil -} - -func parseJSON(rd io.ReadSeeker, offsets map[string]offsetSize, fname string, c interface{}) error { - osize, ok := offsets[fname] - if !ok { - return fmt.Errorf("file %s not found", fname) - } - if _, err := rd.Seek(osize.offset, io.SeekStart); err != nil { - return fmt.Errorf("seek to %s: %w", fname, err) - } - - lrd := io.LimitReader(rd, osize.size) - dec := json.NewDecoder(lrd) - if err := dec.Decode(c); err != nil { - return fmt.Errorf("decode %s: %w", fname, err) - } - return nil -} - -func quoted(cmds []string) string { - ret := make([]string, len(cmds)) - for i, cmd := range cmds { - if cmd == "" || strings.ContainsRune(cmd, ' ') { - ret[i] = `"` + cmd + `"` - } else { - ret[i] = cmd - } - } - return strings.Join(ret, " ") -} diff --git a/lxcconfig/lxcconfig_test.go b/lxcconfig/lxcconfig_test.go deleted file mode 100644 index becf672..0000000 --- a/lxcconfig/lxcconfig_test.go +++ /dev/null @@ -1,87 +0,0 @@ -package lxcconfig - -import ( - "archive/tar" - "bytes" - "encoding/json" - "strings" - "testing" - - "git.sr.ht/~motiejus/code/undocker/internal/tartest" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestLXCConfig(t *testing.T) { - tests := []struct { - name string - docker dockerConfig - want string - }{ - { - name: "just architecture", - docker: dockerConfig{ - Architecture: "amd64", - }, - want: strings.Join([]string{ - `lxc.include = LXC_TEMPLATE_CONFIG/common.conf`, - `lxc.architecture = amd64`, - `lxc.init.cmd = '/bin/sh'`, - ``, - }, "\n"), - }, - { - name: "all fields", - docker: dockerConfig{ - Architecture: "amd64", - Config: dockerConfigConfig{ - Entrypoint: []string{"/entrypoint.sh"}, - Cmd: []string{"/bin/sh", "-c", "echo foo"}, - WorkingDir: "/x", - Env: []string{ - `LONGNAME="Foo Bar"`, - "SHELL=/bin/tcsh", - }, - }, - }, - want: strings.Join([]string{ - `lxc.include = LXC_TEMPLATE_CONFIG/common.conf`, - `lxc.architecture = amd64`, - `lxc.init.cmd = '/entrypoint.sh /bin/sh -c "echo foo"'`, - `lxc.init.cwd = /x`, - `lxc.environment = LONGNAME="Foo Bar"`, - `lxc.environment = SHELL=/bin/tcsh`, - ``, - }, "\n"), - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - manifest := tartest.File{ - Name: "manifest.json", - Contents: bytes.NewBufferString(`[{"Config":"config.json"}]`), - } - archive := tartest.Tarball{ - manifest, - tt.docker, - } - in := bytes.NewReader(archive.Buffer().Bytes()) - var buf bytes.Buffer - require.NoError(t, LXCConfig(in, &buf)) - assert.Equal(t, tt.want, string(buf.Bytes())) - }) - } -} - -// Helpers -func (c dockerConfig) Tar(tw *tar.Writer) error { - configJSON, err := json.MarshalIndent(c, "", " ") - if err != nil { - return err - } - return tartest.File{ - Name: "config.json", - Contents: bytes.NewBuffer(configJSON), - }.Tar(tw) -} diff --git a/main.go b/main.go index 2732a12..712bfc1 100644 --- a/main.go +++ b/main.go @@ -1,52 +1,48 @@ package main import ( - "errors" "fmt" "io" "os" + "path/filepath" + "runtime" "git.sr.ht/~motiejus/code/undocker/rootfs" - goflags "github.com/jessevdk/go-flags" ) -const _description = "Flatten a docker container image to a tarball" +const _usage = `Usage: + %s + +Flatten a Docker container image to a root file system. + +Arguments: + : Input Docker container. Tarball. + : Output tarball, the root file system. '-' is stdout. +` func main() { - parser := goflags.NewParser(newCommand(), goflags.Default) - _, err := parser.Parse() - if err != nil { + runtime.GOMAXPROCS(1) // no need to create that many threads + + if len(os.Args) != 3 { + fmt.Fprintf(os.Stderr, _usage, filepath.Base(os.Args[0])) + os.Exit(1) + } + + c := &command{flattener: rootfs.Flatten, Stdout: os.Stdout} + if err := c.execute(os.Args[1], os.Args[2]); err != nil { + fmt.Errorf("Error: %v", err) os.Exit(1) } os.Exit(0) } -// command implements go-flags.Command type command struct { flattener func(io.ReadSeeker, io.Writer) error Stdout io.Writer - - PositionalArgs struct { - Infile goflags.Filename `long:"infile" description:"Input tarball"` - Outfile string `long:"outfile" description:"Output path, stdout is '-'"` - } `positional-args:"yes" required:"yes"` } -// newCommand creates a new Command struct -func newCommand() *command { - return &command{ - flattener: rootfs.Flatten, - Stdout: os.Stdout, - } -} - -// Execute executes rootfs Command -func (c *command) Execute(args []string) (err error) { - if len(args) != 0 { - return errors.New("too many args") - } - - rd, err := os.Open(string(c.PositionalArgs.Infile)) +func (c *command) execute(infile string, outfile string) (err error) { + rd, err := os.Open(infile) if err != nil { return err } @@ -58,10 +54,10 @@ func (c *command) Execute(args []string) (err error) { }() var out io.Writer - if fname := string(c.PositionalArgs.Outfile); fname == "-" { + if outfile == "-" { out = c.Stdout } else { - outf, err := os.Create(fname) + outf, err := os.Create(outfile) if err != nil { return fmt.Errorf("create: %w", err) } diff --git a/main_test.go b/main_test.go index bd1cdc8..92c4399 100644 --- a/main_test.go +++ b/main_test.go @@ -7,13 +7,11 @@ import ( "path/filepath" "testing" - goflags "github.com/jessevdk/go-flags" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestExecute(t *testing.T) { - assert.False(t, true) var _foo = []byte("foo foo") tests := []struct { @@ -65,11 +63,9 @@ func TestExecute(t *testing.T) { tt.outfile = filepath.Join(dir, tt.outfile) } inf := filepath.Join(dir, tt.infile) - c.PositionalArgs.Infile = goflags.Filename(inf) - c.PositionalArgs.Outfile = tt.outfile c.flattener = flattenPassthrough - err := c.Execute(nil) + err := c.execute(inf, tt.outfile) if tt.wantErr != "" { require.Error(t, err) assert.Regexp(t, tt.wantErr, err.Error())