20 Commits

Author SHA1 Message Date
Motiejus Jakštys
6d24d0da7a replace io/ioutil with os 2023-01-03 16:00:34 +02:00
Motiejus Jakštys
d3efb410d0 make: fix clean target 2022-08-29 18:02:22 +03:00
1c46fb8617 release: a better changelog 2022-06-09 14:28:39 +03:00
4408a9e005 rootfs: remove ./ prefix from filenames
I've seen a container (private one) whose filenames start with ./, but
the layer name does not have the prefix, causing undocker to fail.

Let's always normalize the names to not have the prefix.
2022-06-09 14:22:21 +03:00
41baf180a3 [ci] merge test and lint steps to one 2021-09-29 14:45:53 +03:00
4754869190 [ci] replace tee with awk 2021-09-29 14:43:33 +03:00
ec62b65ecd [ci] add a usage step 2021-09-29 14:38:31 +03:00
0ff72b99d9 remove unnecessary support for prebuilt binaries
the users will know how to build the binaries themselves.
2021-09-29 14:30:18 +03:00
e4c0d4d72a remove references to 'official' binaries 2021-09-27 19:08:04 +03:00
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
7 changed files with 63 additions and 96 deletions

View File

@@ -9,12 +9,11 @@ sources:
tasks: tasks:
- setup: | - setup: |
go install honnef.co/go/tools/cmd/staticcheck@latest go install honnef.co/go/tools/cmd/staticcheck@latest
- test: | - built-test-lint: |
make -C undocker coverage.html make -C undocker -O -j$(nproc) undocker coverage.html lint
- lint: | - usage: |
make -C undocker -O -j$(nproc) lint # like 'grep -q', but prints output too.
- binaries: | # | tee /dev/stderr doesn't work on sourcehut workers; permission denied.
make -C undocker -O -j$(nproc) sha256sum.txt ./undocker/undocker |& awk 'BEGIN{c=1};/Built with /{c=0};{print};END{exit c}'
cat undocker/sha256sum.txt
artifacts: artifacts:
- undocker/coverage.html - undocker/coverage.html

1
.gitignore vendored
View File

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

View File

@@ -1,50 +1,29 @@
SCRIPTS = $(shell awk '/#!\/bin\/(ba)?sh/&&FNR==1{print FILENAME}' $(shell git ls-files)) SCRIPTS = $(shell awk '/#!\/bin\/(ba)?sh/&&FNR==1{print FILENAME}' $(shell git ls-files))
GODEPS = $(shell git ls-files '*.go' go.mod go.sum) GODEPS = $(shell git ls-files '*.go' go.mod go.sum)
GOBIN = $(shell go env GOPATH)/bin/
GOOSARCHS = $(sort darwin/amd64 linux/amd64)
VSN ?= $(shell git describe --dirty) VSN ?= $(shell git describe --dirty)
VSNHASH = $(shell git rev-parse --verify HEAD) VSNHASH = $(shell git rev-parse --verify HEAD)
LDFLAGS = -ldflags "-X main.Version=$(VSN) -X main.VersionHash=$(VSNHASH)" LDFLAGS = -ldflags "-X main.Version=$(VSN) -X main.VersionHash=$(VSNHASH)"
undocker: ## builds binary for the current architecture undocker: ## builds binary for the current architecture
CGO_ENABLED=0 go build $(LDFLAGS) -o $@ go build $(LDFLAGS) -o $@
.PHONY: test .PHONY: test
test: test: coverage.out
go test -race -cover ./...
define undockertarget
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))))))
.PHONY: all
all: $(UNDOCKERS)
.PHONY: lint .PHONY: lint
lint: lint:
go vet ./... go vet ./...
$(GOBIN)staticcheck -f stylish ./... $(shell go env GOPATH)/bin/staticcheck -f stylish ./...
shellcheck $(SCRIPTS) shellcheck $(SCRIPTS)
.INTERMEDIATE: coverage.out .INTERMEDIATE: coverage.out
coverage.out: $(GODEPS) coverage.out: $(GODEPS)
go test -coverprofile $@ ./... go test -race -cover -coverprofile $@ ./...
coverage.html: coverage.out coverage.html: coverage.out
go tool cover -html=$< -o $@ go tool cover -html=$< -o $@
sha256sum.txt: $(UNDOCKERS)
sha256sum $(UNDOCKERS) > $@
sha256sum.txt.asc: sha256sum.txt
gpg --clearsign $<
.PHONY: clean .PHONY: clean
clean: clean:
rm -f undocker-*-v* coverage.html sha256sum.txt sha256sum.txt.asc rm -f undocker coverage.html

View File

@@ -18,20 +18,19 @@ and application isolation ("container") runtimes: once the docker image is
extracted, it can be run with old-fashioned tools: lxc, systemd-nspawn, extracted, it can be run with old-fashioned tools: lxc, systemd-nspawn,
systemd, FreeBSD Jails, and many others. systemd, FreeBSD Jails, and many others.
Undocker has no dependencies outside Golang stdlib.
Installation Installation
------------ ------------
We recommend using [officially released binaries][3]. To build the project Build it like this for the "current" platform:
instead, run:
``` ```
$ make undocker $ make undocker
``` ```
The number of officially released binaries is quite limited. If you'd like me `make -B` will print the extra flags (`-X <...>`) for cross-compiling with
to expand a list, please contribute a patch to the Makefile. other archs. It's all `go build <...>` in the back, and depends only on Go's
compiler and stdlib.
Usage: convert docker image to rootfs Usage: convert docker image to rootfs
------------------------------------- -------------------------------------
@@ -40,50 +39,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 $ skopeo copy docker://docker.io/busybox:latest docker-archive:busybox.tar
$ undocker busybox.tar - | tar -tv | head -10 $ undocker busybox.tar - | tar -xv | sponge | head -10; echo '<...>'
drwxr-xr-x 0/0 0 2021-05-17 22:07 bin/ bin/
-rwxr-xr-x 0/0 1149184 2021-05-17 22:07 bin/[ bin/[
hrwxr-xr-x 0/0 0 2021-05-17 22:07 bin/[[ link to bin/[ bin/[[
hrwxr-xr-x 0/0 0 2021-05-17 22:07 bin/acpid link to bin/[ bin/acpid
hrwxr-xr-x 0/0 0 2021-05-17 22:07 bin/add-shell link to bin/[ bin/add-shell
hrwxr-xr-x 0/0 0 2021-05-17 22:07 bin/addgroup link to bin/[ bin/addgroup
hrwxr-xr-x 0/0 0 2021-05-17 22:07 bin/adduser link to bin/[ bin/adduser
hrwxr-xr-x 0/0 0 2021-05-17 22:07 bin/adjtimex link to bin/[ bin/adjtimex
hrwxr-xr-x 0/0 0 2021-05-17 22:07 bin/ar link to bin/[ bin/ar
hrwxr-xr-x 0/0 0 2021-05-17 22:07 bin/arch link to bin/[ bin/arch
<...>
``` ```
You can also refer [here][2] for other ways to download Docker images. There Refer [here][2] for other ways to download Docker images. There are many.
are many.
Converting a [1.1GB Docker image with 77 On author's laptop converting a [1.1GB Docker image with 77
layers](https://hub.docker.com/r/homeassistant/home-assistant) takes around 4 layers](https://hub.docker.com/r/homeassistant/home-assistant) takes around 3
seconds and on a reasonably powerful Intel laptop. seconds and uses ~65MB of residential memory.
Usage example: systemd-nspawn Usage example: systemd
----------------------------- ----------------------
Start with systemd-nspawn:
```
systemd-nspawn -D $PWD busybox httpd -vfp 8080
```
Usage example: plain old systemd
--------------------------------
``` ```
systemd-run \ systemd-run \
--wait --pty --collect --service-type=exec \ --wait --pty --collect --service-type=exec \
-p RootDirectory=$PWD \
-p ProtectProc=invisible \
-p PrivateUsers=true \ -p PrivateUsers=true \
-p DynamicUser=yes \ -p DynamicUser=yes \
-p ProtectProc=invisible \
-p RootDirectory=$PWD \
-- busybox httpd -vfp 8080 -- busybox httpd -vfp 8080
``` ```
Good things like `PrivateUsers`, `DynamicUser`, `ProtectProc` and other [Systemd protections][1] like `PrivateUsers`, `DynamicUser`, `ProtectProc` and
[systemd protections][1] are available, just like to any systemd unit. others are available, just like to any systemd unit.
Similar Projects Similar Projects
---------------- ----------------
@@ -102,7 +92,7 @@ Contributions
The following contributions may be accepted: The following contributions may be accepted:
- Pull requests (patchsets) with accompanying tests. - Patchsets, with accompanying tests.
- Regression reports. - Regression reports.
If you found a container that undocker cannot extract, or extracts incorrectly If you found a container that undocker cannot extract, or extracts incorrectly
@@ -113,6 +103,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 not work after". Issues without an accompanying patch will most likely be
rejected. rejected.
Communication
-------------
Use [~motiejus/undocker@lists.sr.ht](mailto:~motiejus/undocker@lists.sr.ht) for
questions or patches. Subscribe [here][4].
LICENSE LICENSE
------- -------
@@ -121,3 +117,4 @@ MIT
[1]: https://www.freedesktop.org/software/systemd/man/systemd.exec.html [1]: https://www.freedesktop.org/software/systemd/man/systemd.exec.html
[2]: https://fly.io/blog/docker-without-docker/ [2]: https://fly.io/blog/docker-without-docker/
[3]: http://git.sr.ht/~motiejus/undocker [3]: http://git.sr.ht/~motiejus/undocker
[4]: https://lists.sr.ht/~motiejus/undocker

View File

@@ -4,7 +4,6 @@ import (
"bytes" "bytes"
"errors" "errors"
"io" "io"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
@@ -28,7 +27,7 @@ func TestExecute(t *testing.T) {
infile: "t10-in.txt", infile: "t10-in.txt",
fixture: func(t *testing.T, dir string) { fixture: func(t *testing.T, dir string) {
fname := filepath.Join(dir, "t10-in.txt") fname := filepath.Join(dir, "t10-in.txt")
if err := ioutil.WriteFile(fname, _foo, 0644); err != nil { if err := os.WriteFile(fname, _foo, 0644); err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
}, },
@@ -39,7 +38,7 @@ func TestExecute(t *testing.T) {
infile: "t20-in.txt", infile: "t20-in.txt",
fixture: func(t *testing.T, dir string) { fixture: func(t *testing.T, dir string) {
fname := filepath.Join(dir, "t20-in.txt") fname := filepath.Join(dir, "t20-in.txt")
if err := ioutil.WriteFile(fname, _foo, 0644); err != nil { if err := os.WriteFile(fname, _foo, 0644); err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
}, },
@@ -50,7 +49,7 @@ func TestExecute(t *testing.T) {
infile: "t30-in.txt", infile: "t30-in.txt",
fixture: func(t *testing.T, dir string) { fixture: func(t *testing.T, dir string) {
fname := filepath.Join(dir, "t30-in.txt") fname := filepath.Join(dir, "t30-in.txt")
if err := ioutil.WriteFile(fname, _foo, 0644); err != nil { if err := os.WriteFile(fname, _foo, 0644); err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
}, },
@@ -122,7 +121,7 @@ func TestExecute(t *testing.T) {
if tt.outfile == "-" { if tt.outfile == "-" {
out = stdout.Bytes() out = stdout.Bytes()
} else { } else {
out, err = ioutil.ReadFile(tt.outfile) out, err = os.ReadFile(tt.outfile)
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }

25
release
View File

@@ -1,32 +1,25 @@
#!/bin/bash #!/bin/bash
set -euo pipefail set -euo pipefail
err() { _err(){ >&2 echo "ERROR: $*"; exit 1; }
>&2 echo "ERROR: $*"
exit 1
}
git status --porcelain | grep -q "" && git status --porcelain | grep -q "" &&
err "working tree is dirty, commit your changes first." _err "working tree is dirty, commit your changes first."
[[ ! "$1" =~ ^v([0-9]+)\.([0-9]+)(\.([0-9]+))?$ ]] && \ [[ "$1" =~ ^v([0-9]+)\.([0-9]+)(\.([0-9]+))?(-rc([0-9]+))?$ ]] || \
err "arg1 accepts the following formats: v1.0 v1.0.0" _err "arg1 accepts the following formats: v1.0 v1.0.0 v1.0-rc1 v1.0.1-rc1"
git tag | grep -q "^$1$" && git tag | grep -q "^$1$" &&
err "tag $1 already exists" _err "tag $1 already exists"
# sanity test: do the tests pass?
make -B -j"$(nproc)" test lint
last_tag=$(git tag | tail -1) last_tag=$(git tag | tail -1)
make -B -j"$(nproc)" VSN="$1" sha256sum.txt.asc
{ {
echo undocker "$1" echo undocker "$1"
echo echo
echo Changelog since "$last_tag": echo Changelog since "$last_tag":
git log --pretty=format:"- [%cn] %s" "$last_tag"..HEAD git log --pretty=format:"- [%an] %s" "$last_tag"..HEAD
echo
echo
echo sha256sums of released binaries:
cat sha256sum.txt
echo
} | git tag -u motiejus@jakstys.lt -F - "$1" } | git tag -u motiejus@jakstys.lt -F - "$1"

View File

@@ -69,7 +69,7 @@ func Flatten(rd io.ReadSeeker, w io.Writer) (_err error) {
if err != nil { if err != nil {
return err return err
} }
layerOffsets[hdr.Name] = here layerOffsets[strings.TrimPrefix(hdr.Name, "./")] = here
} }
} }
@@ -82,7 +82,7 @@ func Flatten(rd io.ReadSeeker, w io.Writer) (_err error) {
for i, name := range manifest[0].Layers { for i, name := range manifest[0].Layers {
layers[i] = nameOffset{ layers[i] = nameOffset{
name: name, name: name,
offset: layerOffsets[name], offset: layerOffsets[strings.TrimPrefix(name, "./")],
} }
} }
@@ -146,9 +146,8 @@ func Flatten(rd io.ReadSeeker, w io.Writer) (_err error) {
defer func() { defer func() {
// Avoiding use of multierr: if error is present, return // Avoiding use of multierr: if error is present, return
// that. Otherwise return whatever `Close` returns. // that. Otherwise return whatever `Close` returns.
err1 := tw.Close() if err := tw.Close(); err != nil && _err == nil {
if _err == nil { _err = err
_err = err1
} }
}() }()
// iterate through all layers, all files, and write files. // iterate through all layers, all files, and write files.