From 427ccc341bfefe3e660693746c39c60f2c114fef Mon Sep 17 00:00:00 2001 From: Dimitris Karakasilis Date: Fri, 1 Sep 2023 17:01:05 +0300 Subject: [PATCH] [WIP] Run kaniko Signed-off-by: Dimitris Karakasilis --- tools-image/enki/Earthfile | 10 +++ tools-image/enki/cmd/convert.go | 51 ++++++++++++ tools-image/enki/pkg/action/converter.go | 99 +++++++++++++++++++++++- 3 files changed, 158 insertions(+), 2 deletions(-) create mode 100644 tools-image/enki/cmd/convert.go diff --git a/tools-image/enki/Earthfile b/tools-image/enki/Earthfile index 6f97b0c..2a95ee4 100644 --- a/tools-image/enki/Earthfile +++ b/tools-image/enki/Earthfile @@ -3,6 +3,16 @@ VERSION 0.7 # renovate: datasource=docker depName=golang ARG --global GO_VERSION=1.20-alpine3.18 +build: + FROM golang:$GO_VERSION + WORKDIR /build + COPY . . + + ENV CGO_ENABLED=0 + RUN go build -ldflags '-extldflags "-static"' + + SAVE ARTIFACT enki AS LOCAL build/enki + test: FROM golang:$GO_VERSION RUN apk add rsync gcc musl-dev docker jq diff --git a/tools-image/enki/cmd/convert.go b/tools-image/enki/cmd/convert.go new file mode 100644 index 0000000..2f50d03 --- /dev/null +++ b/tools-image/enki/cmd/convert.go @@ -0,0 +1,51 @@ +package cmd + +import ( + "fmt" + + "github.com/kairos-io/enki/pkg/action" + v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1" + "github.com/spf13/cobra" +) + +// NewBuildISOCmd returns a new instance of the build-iso subcommand and appends it to +// the root command. +func NewConvertCmd() *cobra.Command { + c := &cobra.Command{ + Use: "convert rootfs", + Short: "Convert a base image to a Kairos image", + Long: "Convert a base image to a Kairos image\n\n" + + "This is best effort. Enki will try to detect the distribution and add\n" + + "the necessary bits to convert it to a Kairos image", + Args: cobra.ExactArgs(1), + PreRunE: func(cmd *cobra.Command, args []string) error { + return CheckRoot() // TODO: Do we need root? + }, + RunE: func(cmd *cobra.Command, args []string) error { + // Set this after parsing of the flags, so it fails on parsing and prints usage properly + cmd.SilenceUsage = true + cmd.SilenceErrors = true // Do not propagate errors down the line, we control them + + rootfsDir := args[0] + // TODO: Check if this is really an existing dir (not a file) + fmt.Printf("rootfsDir = %+v\n", rootfsDir) + + logger := v1.NewLogger() + + convertAction := action.NewConverterAction(rootfsDir) + err := convertAction.Run() + if err != nil { + logger.Errorf(err.Error()) + return err + } + + return nil + }, + } + + return c +} + +func init() { + rootCmd.AddCommand(NewConvertCmd()) +} diff --git a/tools-image/enki/pkg/action/converter.go b/tools-image/enki/pkg/action/converter.go index aaea97d..6c3401b 100644 --- a/tools-image/enki/pkg/action/converter.go +++ b/tools-image/enki/pkg/action/converter.go @@ -1,13 +1,108 @@ package action +import ( + "fmt" + "os" + "os/exec" +) + // ConverterAction is the action that converts a non-kairos image to a Kairos one. // The conversion happens in a best-effort manner. It's not guaranteed that // any distribution will successfully be converted to a Kairos flavor. See // the Kairos releases for known-to-work flavors. +// The "input" of this action is a directory where the rootfs is extracted. +// [TBD] The output is the same directory updated to be a Kairos image type ConverterAction struct { rootFSPath string } -func NewConverterAction() *ConverterAction { - return &ConverterAction{} +func NewConverterAction(rootfsPath string) *ConverterAction { + return &ConverterAction{ + rootFSPath: rootfsPath, + } +} + +// https://github.com/GoogleContainerTools/kaniko/issues/1007 +// docker run -it -v $PWD:/work:rw --rm gcr.io/kaniko-project/executor:latest --dockerfile /work/Dockerfile --context dir:///work/rootfs --destination whatever --tar-path /work/image.tar --no-push + +// Run assumes the `kaniko` executable is in PATH as it shells out to it. +// The best way to do that is to spin up a container with the upstream +// image (gcr.io/kaniko-project/executor:latest) and mount enki in it. +// E.g. +// docker run -it -v "$PWD/enki":/enki --rm --entrypoint "/enki" gcr.io/kaniko-project/executor:latest +func (ca *ConverterAction) Run() (err error) { + dockerfile, err := ca.createDockerfile() + if err != nil { + return + } + defer os.Remove(dockerfile) + + err = ca.addDockerIgnore() + if err != nil { + return + } + defer ca.removeDockerIgnore() + + out, err := ca.BuildWithKaniko(dockerfile) + if err != nil { + return fmt.Errorf("%w: %s", err, out) + } + fmt.Printf("out = %+v\n", out) + + return +} + +func (ca *ConverterAction) createDockerfile() (string, error) { + f, err := os.CreateTemp("", "") + if err != nil { + return "", err + } + defer f.Close() + + // write data to the temporary file + data := []byte(` +FROM scratch as rootfs +COPY . . + +FROM rootfs + +# TODO: Do more clever things +RUN apt-get install -y curl +`) + + if _, err := f.Write(data); err != nil { + os.Remove(f.Name()) + return "", err + } + + return f.Name(), nil +} + +// https://github.com/GoogleContainerTools/kaniko/issues/1007 +// Create a .dockerignore in the rootfs directory to skip these: +// https://github.com/GoogleContainerTools/kaniko/pull/1724/files#diff-1e90758e2fb0f26bdbfe7a40aafc4b4796cbf808842703e52e16c1f36b8da7dcR89 +func (ca *ConverterAction) addDockerIgnore() error { + return nil +} + +func (ca *ConverterAction) removeDockerIgnore() error { + return nil +} + +func (ca *ConverterAction) BuildWithKaniko(dockerfile string) (string, error) { + fmt.Printf("ca.rootFSPath = %+v\n", ca.rootFSPath) + cmd := exec.Command("executor", + "--dockerfile", dockerfile, + "--context", ca.rootFSPath, + "--destination", "whatever", + "--tar-path", "image.tar", // TODO: Where do we write? Do we want this extracted to the rootFSPath? + "--no-push", + ) + + d, err := cmd.CombinedOutput() + if err != nil { + return "", fmt.Errorf("%w: %s", err, string(d)) + } + + return string(d), err }