diff --git a/tools-image/enki/cmd/convert.go b/tools-image/enki/cmd/convert.go index 2f50d03..24400e2 100644 --- a/tools-image/enki/cmd/convert.go +++ b/tools-image/enki/cmd/convert.go @@ -1,14 +1,12 @@ 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 +// NewConvertCmd returns a new instance of the build-iso subcommand and appends it to // the root command. func NewConvertCmd() *cobra.Command { c := &cobra.Command{ @@ -17,7 +15,7 @@ func NewConvertCmd() *cobra.Command { 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), + Args: cobra.ExactArgs(2), PreRunE: func(cmd *cobra.Command, args []string) error { return CheckRoot() // TODO: Do we need root? }, @@ -26,13 +24,14 @@ func NewConvertCmd() *cobra.Command { 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) + rootfsDir := args[0] + resultPath := args[1] + imageName := args[2] logger := v1.NewLogger() - convertAction := action.NewConverterAction(rootfsDir) + convertAction := action.NewConverterAction(rootfsDir, resultPath, imageName) err := convertAction.Run() if err != nil { logger.Errorf(err.Error()) diff --git a/tools-image/enki/go.sum b/tools-image/enki/go.sum index f7d1002..04a8fb2 100644 --- a/tools-image/enki/go.sum +++ b/tools-image/enki/go.sum @@ -400,14 +400,10 @@ github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo/v2 v2.9.7 h1:06xGQy5www2oN160RtEZoTvnP2sPhEfePYmCDc2szss= -github.com/onsi/ginkgo/v2 v2.9.7/go.mod h1:cxrmXWykAwTwhQsJOPfdIDiJ+l2RYq7U8hFU+M/1uw0= github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= -github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc= -github.com/onsi/gomega v1.27.8/go.mod h1:2J8vzI/s+2shY9XHRApDkdgPo1TKT7P2u6fXeJKFnNQ= github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= @@ -594,8 +590,6 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= -golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -678,8 +672,6 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -853,8 +845,6 @@ golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= -golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM= golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/tools-image/enki/pkg/action/converter.go b/tools-image/enki/pkg/action/converter.go index 19014ec..5acb9c6 100644 --- a/tools-image/enki/pkg/action/converter.go +++ b/tools-image/enki/pkg/action/converter.go @@ -15,11 +15,15 @@ import ( // [TBD] The output is the same directory updated to be a Kairos image type ConverterAction struct { rootFSPath string + resultPath string + imageName string } -func NewConverterAction(rootfsPath string) *ConverterAction { +func NewConverterAction(rootfsPath, resultPath, imageName string) *ConverterAction { return &ConverterAction{ rootFSPath: rootfsPath, + resultPath: resultPath, + imageName: imageName, } } @@ -44,11 +48,10 @@ func (ca *ConverterAction) Run() (err error) { } defer ca.removeDockerIgnore() - out, err := ca.BuildWithKaniko(dockerfile) + out, err := ca.BuildWithKaniko(dockerfile, ca.resultPath) if err != nil { return fmt.Errorf("%w: %s", err, out) } - fmt.Printf("out = %+v\n", out) return } @@ -111,13 +114,12 @@ func (ca *ConverterAction) removeDockerIgnore() error { return os.RemoveAll(path.Join(ca.rootFSPath, ".dockerignore")) } -func (ca *ConverterAction) BuildWithKaniko(dockerfile string) (string, error) { - fmt.Printf("ca.rootFSPath = %+v\n", ca.rootFSPath) +func (ca *ConverterAction) BuildWithKaniko(dockerfile, resultPath string) (string, error) { cmd := exec.Command("executor", "--dockerfile", dockerfile, "--context", ca.rootFSPath, - "--destination", "whatever", - "--tar-path", "/build/image.tar", // TODO: Where do we write? Do we want this extracted to the rootFSPath? + "--destination", ca.imageName, // This is the name of the image when you: cat image.tar | docker load + "--tar-path", resultPath, // TODO: Do we want this extracted to the rootFSPath? "--no-push", ) diff --git a/tools-image/enki/pkg/action/converter_test.go b/tools-image/enki/pkg/action/converter_test.go index caedb78..0d75282 100644 --- a/tools-image/enki/pkg/action/converter_test.go +++ b/tools-image/enki/pkg/action/converter_test.go @@ -1,11 +1,89 @@ package action_test import ( + "fmt" + "math/rand" + "os" + "os/exec" + "path" + "time" + + . "github.com/kairos-io/enki/pkg/action" . "github.com/onsi/ginkgo/v2" - //. "github.com/onsi/gomega" + . "github.com/onsi/gomega" ) -var _ = Describe("BuildISOAction", func() { +var _ = FDescribe("ConverterAction", func() { + var rootfsPath, resultDir, imageName string + var action *ConverterAction + + BeforeEach(func() { + rootfsPath = prepareRootfs() + resultDir = prepareResultDir() + imageName = newImageName(10) + action = NewConverterAction(rootfsPath, path.Join(resultDir, "image.tar"), imageName) + }) + + AfterEach(func() { + cleanupDir(rootfsPath) + cleanupDir(resultDir) + removeImage(imageName) + }) + It("adds the framework bits", func() { + // TODO: Run enki next to kaniko (in an image?) + // CGO_ENABLED=0 go build -ldflags '-extldflags "-static"' -o build/enki && docker run -it -e PATH=/kaniko -v /tmp -v /home/dimitris/workspace/kairos/osbuilder/tmp/rootfs/:/context -v "$PWD/build/enki":/enki -v $PWD:/build --rm --entrypoint "/enki" gcr.io/kaniko-project/executor:latest convert /context + Expect(action.Run()).ToNot(HaveOccurred()) + + cmd := exec.Command("/bin/sh", "-c", fmt.Sprintf("cat %s/image.tar | docker load", resultDir)) + out, err := cmd.CombinedOutput() + Expect(err).ToNot(HaveOccurred(), string(out)) }) }) + +func prepareRootfs() string { + dir, err := os.MkdirTemp("", "kairos-temp") + Expect(err).ToNot(HaveOccurred()) + + cmd := exec.Command("/bin/sh", "-c", + fmt.Sprintf("docker run -v %s:/work quay.io/luet/base util unpack ubuntu:latest /work", dir)) + out, err := cmd.CombinedOutput() + Expect(err).ToNot(HaveOccurred(), string(out)) + + return dir +} + +func prepareResultDir() string { + dir, err := os.MkdirTemp("", "kairos-temp") + Expect(err).ToNot(HaveOccurred()) + + return dir +} + +func newImageName(n int) string { + rand.Seed(time.Now().UnixNano()) + var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + b := make([]rune, n) + for i := range b { + b[i] = letterRunes[rand.Intn(len(letterRunes))] + } + return string(b) +} + +func removeImage(image string) { + fmt.Printf("image = %+v\n", image) + cmd := exec.Command("/bin/sh", "-c", fmt.Sprintf("docker rmi %s:latest", image)) + out, err := cmd.CombinedOutput() + Expect(err).ToNot(HaveOccurred(), string(out)) +} + +// Cleanup in docker to use the same permissions as those when we created. +// This way we avoid sudo. +func cleanupDir(path string) { + fmt.Printf("path = %+v\n", path) + cmd := exec.Command("/bin/sh", "-c", + fmt.Sprintf("docker run --rm -v %[1]s:/work ubuntu /bin/bash -c 'rm -rf /work/*'", path)) + out, err := cmd.CombinedOutput() + Expect(err).ToNot(HaveOccurred(), string(out)) + Expect(os.RemoveAll(path)).ToNot(HaveOccurred()) +}