-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
341 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
package ctr | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"io" | ||
|
||
"lesiw.io/cmdio" | ||
) | ||
|
||
type cmd struct { | ||
io.ReadWriter | ||
*cdr | ||
ctx context.Context | ||
env map[string]string | ||
arg []string | ||
} | ||
|
||
func newCmd( | ||
cdr *cdr, ctx context.Context, env map[string]string, args ...string, | ||
) io.ReadWriter { | ||
c := &cmd{ | ||
ctx: ctx, | ||
env: env, | ||
cdr: cdr, | ||
arg: args, | ||
} | ||
c.setCmd(false) | ||
return c | ||
} | ||
|
||
func (c *cmd) Attach() error { | ||
c.setCmd(true) | ||
if a, ok := c.ReadWriter.(cmdio.Attacher); ok { | ||
return a.Attach() | ||
} | ||
return nil | ||
} | ||
|
||
func (c *cmd) String() string { | ||
if s, ok := c.ReadWriter.(fmt.Stringer); ok { | ||
return s.String() | ||
} | ||
return fmt.Sprintf("<%T>", c) | ||
} | ||
|
||
func (c *cmd) setCmd(attach bool) { | ||
cmd := []string{"container", "exec"} | ||
if attach { | ||
cmd = append(cmd, "-ti") | ||
} | ||
for k, v := range c.env { | ||
cmd = append(cmd, "-e", k+"="+v) | ||
} | ||
cmd = append(cmd, c.ctrid) | ||
cmd = append(cmd, c.arg...) | ||
c.ReadWriter = c.rnr.Commander.Command(c.ctx, nil, cmd...) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
package ctr | ||
|
||
import ( | ||
"context" | ||
"crypto/sha1" | ||
"fmt" | ||
"io" | ||
"io/fs" | ||
"os" | ||
"path/filepath" | ||
"time" | ||
|
||
"lesiw.io/cmdio" | ||
"lesiw.io/cmdio/sub" | ||
"lesiw.io/cmdio/sys" | ||
) | ||
|
||
var clis = [...][]string{ | ||
{"docker"}, | ||
{"podman"}, | ||
{"nerdctl"}, | ||
{"lima", "nerdctl"}, | ||
} | ||
|
||
type cdr struct { | ||
rnr *cmdio.Runner | ||
ctrid string | ||
} | ||
|
||
func (c *cdr) Command( | ||
ctx context.Context, env map[string]string, args ...string, | ||
) io.ReadWriter { | ||
return newCmd(c, ctx, env, args...) | ||
} | ||
|
||
func (c *cdr) Close() error { | ||
return c.rnr.Run("container", "rm", "-f", c.ctrid) | ||
} | ||
|
||
// New instantiates a [cmdio.Runner] that runs commands in a container. | ||
func New(name string) (*cmdio.Runner, error) { | ||
return WithRunner(sys.Runner(), name) | ||
} | ||
|
||
// WithRunner instantiates a [cmdio.Runner] that runs commands in a container | ||
// using the given runner. | ||
func WithRunner(rnr *cmdio.Runner, name string) (*cmdio.Runner, error) { | ||
var ctrcli []string | ||
for _, cli := range clis { | ||
if _, err := rnr.Get("which", cli[0]); err == nil { | ||
ctrcli = cli | ||
break | ||
} | ||
} | ||
if len(ctrcli) == 0 { | ||
return nil, fmt.Errorf("failed to find container CLI") | ||
} | ||
rnr = sub.WithRunner(rnr, ctrcli...) | ||
|
||
if len(name) > 0 && (name[0] == '/' || name[0] == '.') { | ||
var err error | ||
if name, err = buildContainer(rnr, name); err != nil { | ||
return nil, fmt.Errorf("failed to build container: %w", err) | ||
} | ||
} | ||
r, err := rnr.Get("container", "run", "--rm", "-d", "-i", name, "cat") | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to start container: %w", err) | ||
} | ||
|
||
return cmdio.NewRunner( | ||
context.Background(), | ||
make(map[string]string), | ||
&cdr{rnr: rnr, ctrid: r.Out}, | ||
), nil | ||
} | ||
|
||
func buildContainer( | ||
rnr *cmdio.Runner, rpath string, | ||
) (image string, err error) { | ||
var path string | ||
if path, err = filepath.Abs(rpath); err != nil { | ||
err = fmt.Errorf("bad Containerfile path '%s': %w", rpath, err) | ||
return | ||
} | ||
imagehash := sha1.New() | ||
imagehash.Write([]byte(path)) | ||
image = fmt.Sprintf("%x", imagehash.Sum(nil)) | ||
insp, insperr := rnr.Get( | ||
"image", "inspect", | ||
"--format", "{{.Created}}", | ||
image, | ||
) | ||
mtime, err := getMtime(path) | ||
if err != nil { | ||
err = fmt.Errorf("bad Containerfile '%s': %w", path, err) | ||
return | ||
} | ||
if insperr == nil { | ||
var ctime time.Time | ||
ctime, err = time.Parse(time.RFC3339, insp.Out) | ||
if err != nil { | ||
err = fmt.Errorf( | ||
"failed to parse container created timestamp '%s': %s", | ||
insp.Out, err) | ||
return | ||
} | ||
if ctime.Unix() > mtime { | ||
return // Container is newer than Containerfile. | ||
} | ||
} | ||
_, err = rnr.Get( | ||
"image", "build", | ||
"--file", path, | ||
"--no-cache", | ||
"--tag", image, | ||
filepath.Dir(path), | ||
) | ||
if err != nil { | ||
err = fmt.Errorf("failed to build '%s': %w", path, err) | ||
} | ||
return | ||
} | ||
|
||
func getMtime(path string) (mtime int64, err error) { | ||
var info fs.FileInfo | ||
info, err = os.Lstat(path) | ||
if err != nil { | ||
return | ||
} | ||
mtime = info.ModTime().Unix() | ||
return | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package ctr | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
) | ||
|
||
func TestAlpine(t *testing.T) { | ||
rnr, err := New("alpine") | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
defer rnr.Close() | ||
|
||
r, err := rnr.Get("which", "apk") | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
if got, want := r.Out, "/sbin/apk"; got != want { | ||
t.Errorf("[which apk] = %q, want %q", got, want) | ||
} | ||
} | ||
|
||
func TestString(t *testing.T) { | ||
rnr, err := New("alpine") | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
defer rnr.Close() | ||
|
||
cmd := rnr.Command("echo", "hello world") | ||
str := fmt.Sprintf("docker container exec %s echo 'hello world'", | ||
rnr.Commander.(*cdr).ctrid) | ||
if got, want := fmt.Sprintf("%v", cmd), str; got != want { | ||
t.Errorf("Sprintf(cmd) = %q, want = %q", got, want) | ||
} | ||
} | ||
|
||
func TestBuild(t *testing.T) { | ||
rnr, err := New("./testdata/Dockerfile") | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
defer rnr.Close() | ||
|
||
cat, err := rnr.Get("cat", "/tmp/test") | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
if got, want := cat.Out, "hello world"; got != want { | ||
t.Errorf("[cat /tmp/test] = %q, want %q", got, want) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
FROM alpine | ||
|
||
RUN echo "hello world" > /tmp/test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.