From 966557430824973c496a787762b657635ec1a954 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Motiejus=20Jak=C5=A1tys?= Date: Mon, 24 May 2021 00:11:58 +0300 Subject: [PATCH] wip leave only rootfs --- BUILD | 17 +++++++--- main.go | 76 ++++++++++++++++++++++++++++++++++-------- main_test.go | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 170 insertions(+), 17 deletions(-) create mode 100644 main_test.go diff --git a/BUILD b/BUILD index 8ea8dec..d1f3e0d 100644 --- a/BUILD +++ b/BUILD @@ -1,4 +1,4 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") +load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library", "go_test") load("@bazel_tools//tools/build_defs/pkg:pkg.bzl", "pkg_tar") load("@io_bazel_rules_docker//container:container.bzl", "container_bundle") @@ -8,9 +8,7 @@ go_library( importpath = "git.sr.ht/~motiejus/code/undocker", visibility = ["//visibility:private"], deps = [ - "//src/undocker/internal/cmdlxcconfig:go_default_library", - "//src/undocker/internal/cmdmanpage:go_default_library", - "//src/undocker/internal/cmdrootfs:go_default_library", + "//src/undocker/rootfs:go_default_library", "@com_github_jessevdk_go_flags//:go_default_library", ], ) @@ -20,3 +18,14 @@ go_binary( embed = [":go_default_library"], visibility = ["//visibility:public"], ) + +go_test( + name = "go_default_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/main.go b/main.go index 9cbea99..2732a12 100644 --- a/main.go +++ b/main.go @@ -1,28 +1,78 @@ package main import ( + "errors" + "fmt" + "io" "os" + "git.sr.ht/~motiejus/code/undocker/rootfs" goflags "github.com/jessevdk/go-flags" - "git.sr.ht/~motiejus/code/undocker/internal/cmdlxcconfig" - "git.sr.ht/~motiejus/code/undocker/internal/cmdmanpage" - "git.sr.ht/~motiejus/code/undocker/internal/cmdrootfs" ) +const _description = "Flatten a docker container image to a tarball" + func main() { - parser := goflags.NewParser(nil, goflags.Default) - - rootfs := cmdrootfs.NewCommand() - lxcconfig := cmdlxcconfig.NewCommand() - manpage := cmdmanpage.NewCommand(parser) - parser.AddCommand("rootfs", rootfs.ShortDesc(), rootfs.LongDesc(), rootfs) - parser.AddCommand("lxcconfig", lxcconfig.ShortDesc(), lxcconfig.LongDesc(), lxcconfig) - m, _ := parser.AddCommand("man-page", "", "", manpage) - m.Hidden = true - + parser := goflags.NewParser(newCommand(), goflags.Default) _, err := parser.Parse() if err != nil { 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)) + 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/main_test.go b/main_test.go new file mode 100644 index 0000000..bd1cdc8 --- /dev/null +++ b/main_test.go @@ -0,0 +1,94 @@ +package main + +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) { + assert.False(t, true) + 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 +}