diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..e13412b --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +examples/*/bindata.go filter=lfs diff=lfs merge=lfs -text diff --git a/Makefile b/Makefile index b4dcd0b..efc9b9e 100644 --- a/Makefile +++ b/Makefile @@ -9,19 +9,30 @@ GO_LDFLAGS_STATIC=-ldflags "-w -extldflags -static" all: clean build fmt lint test staticcheck vet ## Runs a clean, build, fmt, lint, test, staticcheck, and vet .PHONY: build -build: alpine busybox ## Builds a static executable or package +build: alpine busybox cl-k8s ## Builds a static executable or package .PHONY: alpine -alpine: generate +alpine: @echo "+ $@" + go generate ./examples/$@/... CGO_ENABLED=1 go build \ -tags "$(BUILDTAGS) static_build" \ ${GO_LDFLAGS_STATIC} -o $@ ./examples/$@/... @echo "Static container for $@ created at: ./$@" .PHONY: busybox -busybox: generate +busybox: @echo "+ $@" + go generate ./examples/$@/... + CGO_ENABLED=1 go build \ + -tags "$(BUILDTAGS) static_build" \ + ${GO_LDFLAGS_STATIC} -o $@ ./examples/$@/... + @echo "Static container for $@ created at: ./$@" + +.PHONY: cl-k8s +cl-k8s: + @echo "+ $@" + go generate ./examples/$@/... CGO_ENABLED=1 go build \ -tags "$(BUILDTAGS) static_build" \ ${GO_LDFLAGS_STATIC} -o $@ ./examples/$@/... @@ -81,14 +92,10 @@ tag: ## Create a new git tag to prepare to build a release git tag -sa $(VERSION) -m "$(VERSION)" @echo "Run git push origin $(VERSION) to push your new tag to GitHub and trigger a travis build." -.PHONY: generate -generate: - GOMAXPROCS=1 go generate ./... - .PHONY: clean clean: ## Cleanup any build binaries or packages @echo "+ $@" - $(RM) alpine busybox + $(RM) alpine busybox cl-k8s @sudo $(RM) -r rootfs .PHONY: help diff --git a/README.md b/README.md index ed5ee6e..64fe89b 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,12 @@ $ make alpine Static container created at: ./alpine # building the busybox example -$ make alpine +$ make busybox Static container created at: ./busybox + +# building the cl-k8s example +$ make cl-k8s +Static container created at: ./cl-k8s ``` ### Running @@ -39,6 +43,7 @@ Static container created at: ./busybox ```console $ ./alpine $ ./busybox +$ ./cl-k8s ``` ## Cool things diff --git a/container/spec.go b/container/spec.go index 34ce6b1..320d666 100644 --- a/container/spec.go +++ b/container/spec.go @@ -18,6 +18,7 @@ type SpecOpts struct { Readonly bool Terminal bool Args []string + Mounts []specs.Mount Hooks *specs.Hooks } @@ -55,5 +56,9 @@ func Spec(opts SpecOpts) *specs.Spec { spec.Process.Args = opts.Args } + if opts.Mounts != nil { + spec.Mounts = append(spec.Mounts, opts.Mounts...) + } + return spec } diff --git a/examples/cl-k8s/generate.go b/examples/cl-k8s/generate.go new file mode 100644 index 0000000..41e7425 --- /dev/null +++ b/examples/cl-k8s/generate.go @@ -0,0 +1,18 @@ +// +build ignore + +package main + +import ( + "fmt" + "os" + + "github.com/genuinetools/binctr/container" +) + +// Pulls an image and saves the binary data in the container package bindata.go. +func main() { + if err := container.EmbedImage("r.j3ss.co/clisp"); err != nil { + fmt.Fprintf(os.Stderr, "embed image failed: %v\n", err) + os.Exit(1) + } +} diff --git a/examples/cl-k8s/main.go b/examples/cl-k8s/main.go new file mode 100644 index 0000000..008ab32 --- /dev/null +++ b/examples/cl-k8s/main.go @@ -0,0 +1,157 @@ +package main + +import ( + "flag" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "runtime" + + "github.com/genuinetools/binctr/container" + "github.com/opencontainers/runc/libcontainer" + _ "github.com/opencontainers/runc/libcontainer/nsenter" + specs "github.com/opencontainers/runtime-spec/specs-go" + "github.com/sirupsen/logrus" +) + +const ( + defaultRoot = "/tmp/binctr-cl-k8s" + defaultRootfsDir = "rootfs" +) + +var ( + containerID string + root string + + file string +) + +func init() { + // Parse flags + flag.StringVar(&containerID, "id", "cl-k8s", "container ID") + flag.StringVar(&root, "root", defaultRoot, "root directory of container state, should be tmpfs") + + flag.Usage = func() { + flag.PrintDefaults() + } + + flag.Parse() + + if flag.NArg() < 1 { + logrus.Fatal("pass a file to run with cl-k8s") + } + + // Get the file args passed. + file := flag.Arg(0) + // Get the absolute path. + var err error + file, err = filepath.Abs(file) + if err != nil { + logrus.Fatal(err) + } + // Check if its directory. + fi, err := os.Stat(file) + if err != nil { + logrus.Fatal(err) + } + if !fi.Mode().IsDir() { + // Copy the file to a temporary directory. + file, err = copyFile(file) + if err != nil { + logrus.Fatal(err) + } + fmt.Printf("new file is %s\n", file) + } +} + +//go:generate go run generate.go +func main() { + if len(os.Args) > 1 && os.Args[1] == "init" { + runInit() + return + } + + // Create a new container spec with the following options. + opts := container.SpecOpts{ + Rootless: true, + Terminal: true, + Args: []string{"clisp", filepath.Join("/home/user/scripts", filepath.Base(file))}, + Mounts: []specs.Mount{ + { + Destination: "/home/user/scripts/", + Type: "bind", + Source: filepath.Dir(file), + Options: []string{"bind", "ro"}, + }, + }, + } + fmt.Printf("opts %#v\n", opts) + spec := container.Spec(opts) + + // Initialize the container object. + c := &container.Container{ + ID: containerID, + Spec: spec, + Root: root, + Rootless: true, + } + + // Unpack the rootfs. + if err := c.UnpackRootfs(defaultRootfsDir, Asset); err != nil { + logrus.Fatal(err) + } + + // Run the container. + status, err := c.Run() + if err != nil { + logrus.Fatal(err) + } + + // Remove the rootfs after the container has exited. + if err := os.RemoveAll(defaultRootfsDir); err != nil { + logrus.Warnf("removing rootfs failed: %v", err) + } + + // Exit with the container's exit status. + os.Exit(status) +} + +func runInit() { + runtime.GOMAXPROCS(1) + runtime.LockOSThread() + factory, _ := libcontainer.New("") + if err := factory.StartInitialization(); err != nil { + // as the error is sent back to the parent there is no need to log + // or write it to stderr because the parent process will handle this + os.Exit(1) + } + panic("libcontainer: container init failed to exec") +} + +// copyFile copies the src file to a temporary directory. +func copyFile(src string) (string, error) { + in, err := os.Open(src) + if err != nil { + return "", err + } + defer in.Close() + + tmpd, err := ioutil.TempDir("", "") + if err != nil { + return "", err + } + + out, err := os.Create(filepath.Join(tmpd, filepath.Base(src))) + if err != nil { + return "", err + } + defer out.Close() + + _, err = io.Copy(out, in) + if err != nil { + return "", err + } + return out.Name(), out.Close() +}