16 Commits
v1.0 ... v1.0.2

Author SHA1 Message Date
eab7c3c1ef [release] fix nullglob issue 2021-09-06 15:36:50 +03:00
ec61d24fd4 [nit] Makefile style 2021-09-06 09:01:05 +03:00
d3933efd74 Update README
- add communication section
- remove systemd-nspawn section
2021-09-06 08:25:11 +03:00
a265915b21 Shellcheck release 2021-09-01 09:41:00 +03:00
8c00e74df9 Update release script
- fix paths of sha256sum
- check for old artifacts before doing the release
2021-09-01 09:38:13 +03:00
5e297827d3 update README with more actionable examples 2021-09-01 09:32:26 +03:00
dc03b29e6e Revert "add WithFilePrefix"
This reverts commit 436a866f5d.

This is not a good place for such auxiliary functionality; will be moved
elsewhere.
2021-08-29 18:40:11 +03:00
f02af98ac5 nit: shorter defer body 2021-08-29 18:39:43 +03:00
cca579d218 update Makefile and default target
Adds a `sha256sum-$(VSN).txt`. For easier CI.
2021-08-29 18:23:49 +03:00
436a866f5d add WithFilePrefix
This adds an option to prefix every file path with a given string.
2021-08-29 16:55:32 +03:00
71aab65b75 version sha256sum files 2021-08-29 07:49:32 +03:00
984752b812 remove unused archs
I am not using nor need arm64 and windows. Removing these from the
Makefile. If you see this message and feel like they should be added
back (or different archs), ping me.
2021-08-25 08:57:10 +03:00
4568429a69 main.go: remove file if on Flatten(..) failure
When a Flatten() fails, the main program used to leave an incomplete
file (usually zero-length). Now it will clean itself up on failure.
2021-08-25 08:49:30 +03:00
6f40bc91be simplify linter 2021-08-24 17:23:31 +03:00
63d171007e shellcheck 2021-08-24 16:44:58 +03:00
5d4d6410da remove windows/arm64
not available on go 1.16
2021-08-24 14:50:30 +03:00
8 changed files with 139 additions and 85 deletions

View File

@@ -3,6 +3,7 @@ packages:
- go
- git
- make
- shellcheck
sources:
- https://git.sr.ht/~motiejus/undocker
tasks:
@@ -13,7 +14,7 @@ tasks:
- lint: |
make -C undocker -O -j$(nproc) lint
- binaries: |
make -C undocker -O -j$(nproc) sha256sum.txt
cat undocker/sha256sum.txt
make -C undocker -O -j$(nproc) all
cat undocker/sha256sum-*.txt
artifacts:
- undocker/coverage.html

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
/undocker
/undocker-*
coverage.html
sha256sum.txt*

View File

@@ -1,44 +1,38 @@
SCRIPTS = $(shell awk '/#!\/bin\/(ba)?sh/&&FNR==1{print FILENAME}' $(shell git ls-files))
GODEPS = $(shell git ls-files '*.go' go.mod go.sum)
GOBIN = $(shell go env GOPATH)/bin/
GOOSARCHS = $(sort \
darwin/amd64 \
darwin/arm64 \
linux/amd64 \
linux/arm64 \
windows/amd64/.exe \
windows/arm64/.exe)
GOOSARCHS = $(sort darwin/amd64 linux/amd64)
VSN ?= $(shell git describe --dirty)
VSNHASH = $(shell git rev-parse --verify HEAD)
LDFLAGS = -ldflags "-X main.Version=$(VSN) -X main.VersionHash=$(VSNHASH)"
undocker: ## builds binary for the current architecture
CGO_ENABLED=0 go build $(LDFLAGS) -o $@
.PHONY: test
test:
go test -race -cover ./...
define undockertarget
UNDOCKERS += undocker-$(1)-$(2)-$(VSN)$(firstword $(3))
undocker-$(1)-$(2)-$(VSN)$(firstword $(3)): $(GODEPS)
UNDOCKERS += undocker-$(1)-$(2)-$(VSN)
undocker-$(1)-$(2)-$(VSN): $(GODEPS)
CGO_ENABLED=0 GOOS=$(1) GOARCH=$(2) go build $(LDFLAGS) -o $$@
endef
$(foreach goosarch,$(GOOSARCHS),\
$(eval $(call undockertarget,$(word 1,$(subst /, ,$(goosarch))),$(word 2,$(subst /, ,$(goosarch))),$(word 3,$(subst /, ,$(goosarch))))))
$(eval $(call undockertarget,$(word 1,$(subst /, ,$(goosarch))),$(word 2,$(subst /, ,$(goosarch))))))
.PHONY: all
all: $(UNDOCKERS)
all: $(UNDOCKERS) sha256sum-$(VSN).txt
.PHONY: sha256sum-asc
sha256sum-asc: sha256sum-$(VSN).txt.asc
.PHONY: lint
lint: vet staticcheck
.PHONY: vet
vet:
lint:
go vet ./...
.PHONY: staticcheck
staticcheck:
$(GOBIN)staticcheck -f stylish ./...
$(shell go env GOPATH)/bin/staticcheck -f stylish ./...
shellcheck $(SCRIPTS)
.INTERMEDIATE: coverage.out
coverage.out: $(GODEPS)
@@ -47,12 +41,12 @@ coverage.out: $(GODEPS)
coverage.html: coverage.out
go tool cover -html=$< -o $@
sha256sum.txt: $(UNDOCKERS)
sha256sum-$(VSN).txt: $(UNDOCKERS)
sha256sum $(UNDOCKERS) > $@
sha256sum.txt.asc: sha256sum.txt
sha256sum-$(VSN).txt.asc: sha256sum-$(VSN).txt
gpg --clearsign $<
.PHONY: clean
clean:
rm -f undocker-*-v* coverage.html sha256sum.txt sha256sum.txt.asc
rm -f undocker-*-v* coverage.html sha256sum*.txt sha256sum*.txt.asc

View File

@@ -20,6 +20,15 @@ systemd, FreeBSD Jails, and many others.
Undocker has no dependencies outside Golang stdlib.
Installation
------------
You may use [officially released binaries][3], or build it:
```
$ make undocker
```
Usage: convert docker image to rootfs
-------------------------------------
@@ -27,50 +36,41 @@ Download `busybox` docker image from docker hub and convert it to a rootfs:
```
$ skopeo copy docker://docker.io/busybox:latest docker-archive:busybox.tar
$ undocker busybox.tar - | tar -tv | head -10
drwxr-xr-x 0/0 0 2021-05-17 22:07 bin/
-rwxr-xr-x 0/0 1149184 2021-05-17 22:07 bin/[
hrwxr-xr-x 0/0 0 2021-05-17 22:07 bin/[[ link to bin/[
hrwxr-xr-x 0/0 0 2021-05-17 22:07 bin/acpid link to bin/[
hrwxr-xr-x 0/0 0 2021-05-17 22:07 bin/add-shell link to bin/[
hrwxr-xr-x 0/0 0 2021-05-17 22:07 bin/addgroup link to bin/[
hrwxr-xr-x 0/0 0 2021-05-17 22:07 bin/adduser link to bin/[
hrwxr-xr-x 0/0 0 2021-05-17 22:07 bin/adjtimex link to bin/[
hrwxr-xr-x 0/0 0 2021-05-17 22:07 bin/ar link to bin/[
hrwxr-xr-x 0/0 0 2021-05-17 22:07 bin/arch link to bin/[
$ undocker busybox.tar - | tar -xv | sponge | head -10; echo '<...>'
bin/
bin/[
bin/[[
bin/acpid
bin/add-shell
bin/addgroup
bin/adduser
bin/adjtimex
bin/ar
bin/arch
<...>
```
You can also refer [here][2] for other ways to download Docker images. There
are many.
Refer [here][2] for other ways to download Docker images. There are many.
Converting a [1.1GB Docker image with 77
layers](https://hub.docker.com/r/homeassistant/home-assistant) takes around 4
seconds and on a reasonably powerful Intel laptop.
On author's laptop converting a [1.1GB Docker image with 77
layers](https://hub.docker.com/r/homeassistant/home-assistant) takes around 3
seconds and uses ~65MB of residential memory.
Usage example: systemd-nspawn
-----------------------------
Start with systemd-nspawn:
```
systemd-nspawn -D $PWD busybox httpd -vfp 8080
```
Usage example: plain old systemd
--------------------------------
Usage example: systemd
----------------------
```
systemd-run \
--wait --pty --collect --service-type=exec \
-p RootDirectory=$PWD \
-p ProtectProc=invisible \
-p PrivateUsers=true \
-p DynamicUser=yes \
-p ProtectProc=invisible \
-p RootDirectory=$PWD \
-- busybox httpd -vfp 8080
```
Good things like `PrivateUsers`, `DynamicUser`, `ProtectProc` and other
[systemd protections][1] are available, just like to any systemd unit.
[Systemd protections][1] like `PrivateUsers`, `DynamicUser`, `ProtectProc` and
others are available, just like to any systemd unit.
Similar Projects
----------------
@@ -89,7 +89,7 @@ Contributions
The following contributions may be accepted:
- Pull requests (patchsets) with accompanying tests.
- Patchsets, with accompanying tests.
- Regression reports.
If you found a container that undocker cannot extract, or extracts incorrectly
@@ -100,6 +100,12 @@ Reports of regression reports must provide examples of "works before" and "does
not work after". Issues without an accompanying patch will most likely be
rejected.
Communication
-------------
Use [~motiejus/undocker@lists.sr.ht](mailto:~motiejus/undocker@lists.sr.ht) for
questions or patches. Subscribe [here][4].
LICENSE
-------
@@ -107,3 +113,5 @@ MIT
[1]: https://www.freedesktop.org/software/systemd/man/systemd.exec.html
[2]: https://fly.io/blog/docker-without-docker/
[3]: http://git.sr.ht/~motiejus/undocker
[4]: https://lists.sr.ht/~motiejus/undocker

16
main.go
View File

@@ -53,15 +53,15 @@ type command struct {
Stdout io.Writer
}
func (c *command) execute(infile string, outfile string) (err error) {
func (c *command) execute(infile string, outfile string) (_err error) {
rd, err := os.Open(infile)
if err != nil {
return err
}
defer func() {
err1 := rd.Close()
if err == nil {
err = err1
err := rd.Close()
if _err == nil {
_err = err
}
}()
@@ -74,9 +74,11 @@ func (c *command) execute(infile string, outfile string) (err error) {
return fmt.Errorf("create: %w", err)
}
defer func() {
err1 := outf.Close()
if err == nil {
err = err1
err := outf.Close()
if _err != nil {
os.Remove(outfile)
} else {
_err = err
}
}()
out = outf

View File

@@ -2,8 +2,10 @@ package main
import (
"bytes"
"errors"
"io"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"testing"
@@ -13,11 +15,13 @@ 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 string
fixture func(*testing.T, string)
flattener func(io.ReadSeeker, io.Writer) error
infile string
outfile string
wantErr string
assertion func(*testing.T, string)
}{
{
name: "ok passthrough via stdout",
@@ -41,6 +45,31 @@ func TestExecute(t *testing.T) {
},
outfile: "t20-out.txt",
},
{
name: "bad flattener should remove the file",
infile: "t30-in.txt",
fixture: func(t *testing.T, dir string) {
fname := filepath.Join(dir, "t30-in.txt")
if err := ioutil.WriteFile(fname, _foo, 0644); err != nil {
t.Fatalf("unexpected error: %v", err)
}
},
flattener: flattenBad,
outfile: "t30-out.txt",
wantErr: "some error",
assertion: func(t *testing.T, dir string) {
d, err := os.ReadDir(dir)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(d) != 1 {
t.Fatalf("expected 1 entry, got %d", len(d))
}
if d[0].Name() != "t30-in.txt" {
t.Fatalf("expected to find only t30-in.txt, got %s", d[0].Name())
}
},
},
{
name: "infile does not exist",
infile: "t3-does-not-exist.txt",
@@ -57,7 +86,10 @@ func TestExecute(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
dir := t.TempDir()
var stdout bytes.Buffer
c := &command{Stdout: &stdout}
if tt.flattener == nil {
tt.flattener = flattenPassthrough
}
if tt.fixture != nil {
tt.fixture(t, dir)
}
@@ -65,9 +97,14 @@ func TestExecute(t *testing.T) {
tt.outfile = filepath.Join(dir, tt.outfile)
}
inf := filepath.Join(dir, tt.infile)
c.flattener = flattenPassthrough
c := &command{Stdout: &stdout, flattener: tt.flattener}
err := c.execute(inf, tt.outfile)
if tt.assertion != nil {
tt.assertion(t, dir)
}
if tt.wantErr != "" {
if err == nil {
t.Fatal("expected error, got nil")
@@ -78,10 +115,10 @@ func TestExecute(t *testing.T) {
}
return
}
var out []byte
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
var out []byte
if tt.outfile == "-" {
out = stdout.Bytes()
} else {
@@ -91,8 +128,9 @@ func TestExecute(t *testing.T) {
}
}
if !bytes.Equal([]byte("foo foo"), out) {
t.Errorf("out != foo foo: %s", string(out))
t.Errorf("out != foo foo: %q", string(out))
}
})
}
}
@@ -101,3 +139,7 @@ func flattenPassthrough(r io.ReadSeeker, w io.Writer) error {
_, err := io.Copy(w, r)
return err
}
func flattenBad(_ io.ReadSeeker, _ io.Writer) error {
return errors.New("some error")
}

23
release
View File

@@ -6,27 +6,34 @@ err() {
exit 1
}
[[ -n "$(git status --porcelain)" ]] && \
git status --porcelain | grep -q "" &&
err "working tree is dirty, commit your changes first."
[[ ! "$1" =~ ^v([0-9]+)\.([0-9]+)(\.([0-9]+))?$ ]] && \
# https://raimue.blog/2010/09/09/bash-for-loop-with-glob-patterns/
shopt -s nullglob
for file in sha256sum-*; do
err "found $file from previous release, delete it first"
done
shopt -u nullglob
[[ "$1" =~ ^v([0-9]+)\.([0-9]+)(\.([0-9]+))?$ ]] || \
err "arg1 accepts the following formats: v1.0 v1.0.0"
[[ -n "$(git tag | grep "^$1$")" ]] && \
git tag | grep -q "^$1$" &&
err "tag $1 already exists"
last_tag=$(git tag | tail -1)
make -B -j$(nproc) VSN=$1 sha256sum.txt.asc
make -B -j"$(nproc)" VSN="$1" sha256sum-asc
{
echo undocker $1
echo undocker "$1"
echo
echo Changelog since $last_tag:
git log --pretty=format:"- [%cn] %s" $last_tag..HEAD
echo Changelog since "$last_tag":
git log --pretty=format:"- [%cn] %s" "$last_tag"..HEAD
echo
echo
echo sha256sums of released binaries:
cat sha256sum.txt
cat sha256sum-*.txt
echo
} | git tag -u motiejus@jakstys.lt -F - "$1"

View File

@@ -146,9 +146,8 @@ func Flatten(rd io.ReadSeeker, w io.Writer) (_err error) {
defer func() {
// Avoiding use of multierr: if error is present, return
// that. Otherwise return whatever `Close` returns.
err1 := tw.Close()
if _err == nil {
_err = err1
if err := tw.Close(); err != nil && _err == nil {
_err = err
}
}()
// iterate through all layers, all files, and write files.