diff --git a/exporter.go b/exporter.go index fc5696c10..ab301a1c5 100644 --- a/exporter.go +++ b/exporter.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "io" + "io/ioutil" "os" "path/filepath" "strings" @@ -91,6 +92,12 @@ func (e *Exporter) Export(opts ExportOptions) (platform.ExportReport, error) { return platform.ExportReport{}, errors.Wrap(err, "read build metadata") } + // extender layers + // the new base is currently being referenced via run-image + // if err := e.addExtenderLayers(opts, &meta); err != nil { + // return platform.ExportReport{}, err + // } + // buildpack-provided layers if err := e.addBuildpackLayers(opts, &meta); err != nil { return platform.ExportReport{}, err @@ -284,6 +291,67 @@ func (e *Exporter) addAppLayers(opts ExportOptions, slices []layers.Slice, meta return nil } +func (e *Exporter) addExtenderLayers(opts ExportOptions, meta *platform.LayersMetadata) error { + e.Logger.Info("Adding extender layers") + + path := filepath.Join("/layers-run/kaniko") + dirs, err := ioutil.ReadDir(path) + if err != nil { + if os.IsNotExist(err) { + return nil + } + return err + } + + for _, dir := range dirs { + if !dir.IsDir() { + continue + } + + e.Logger.Infof("Adding extender layers for %s", dir.Name()) + manifestPath := filepath.Join(path, dir.Name(), "manifest.json") + var manifest []struct { + Layers []string `json:"Layers"` + } + + f, err := os.Open(manifestPath) + if err != nil { + if os.IsNotExist(err) { + return nil + } + + return err + } + + defer f.Close() + + contents, _ := ioutil.ReadAll(f) + e.Logger.Infof("kaniko manifest: %s", string(contents)) + f.Seek(0, 0) + + err = json.NewDecoder(f).Decode(&manifest) + if err != nil { + return err + } + + if len(manifest) == 0 || len(manifest[0].Layers) == 0 { + return nil + } + + // Iterate layers except first one (the base layer) + for _, layerName := range manifest[0].Layers[1:] { + tarPath := filepath.Join(path, dir.Name(), layerName) + e.Logger.Infof("Adding extended layer '%s'", tarPath) + err := opts.WorkingImage.AddLayer(tarPath) + if err != nil { + return errors.Wrapf(err, "creating ext layer") + } + } + } + + return nil +} + func (e *Exporter) setLabels(opts ExportOptions, meta platform.LayersMetadata, buildMD *platform.BuildMetadata) error { data, err := json.Marshal(meta) if err != nil { diff --git a/extender/main.go b/extender/main.go index 02618ed5a..10eea5681 100644 --- a/extender/main.go +++ b/extender/main.go @@ -3,15 +3,14 @@ package main import ( "encoding/json" "fmt" - "github.com/BurntSushi/toml" - "github.com/GoogleContainerTools/kaniko/pkg/config" - cfg "github.com/redhat-buildpacks/poc/kaniko/buildpackconfig" - "github.com/redhat-buildpacks/poc/kaniko/util" "os" "os/exec" - "path" "path/filepath" "syscall" + + "github.com/BurntSushi/toml" + "github.com/GoogleContainerTools/kaniko/pkg/config" + cfg "github.com/redhat-buildpacks/poc/kaniko/buildpackconfig" ) type BuildMetadata struct { @@ -61,6 +60,7 @@ func doKaniko(kind, baseimage string) { fmt.Println("Initialize the BuildPackConfig and set the defaults values ...") b := cfg.NewBuildPackConfig() b.InitDefaults() + b.ExtractLayers = false fmt.Printf("Kaniko dir: %s\n", b.KanikoDir) @@ -76,30 +76,91 @@ func doKaniko(kind, baseimage string) { panic(err) } - toMultiArg := func(args []DockerfileArg) []string { - var result []string - for _, arg := range args { - result = append(result, fmt.Sprintf("%s=%s", arg.Key, arg.Value)) + // start with the base image given to us by the builder + current_base_image := baseimage + + for _, d := range meta.Dockerfiles { + // skip the extensions not of this type + if d.Type != kind { + continue } - result = append(result, fmt.Sprintf(`base_image=%s`, baseimage)) - return result - } + toMultiArg := func(args []DockerfileArg) []string { + var result []string + for _, arg := range args { + result = append(result, fmt.Sprintf("%s=%s", arg.Key, arg.Value)) + } + + result = append(result, fmt.Sprintf(`base_image=%s`, current_base_image)) + return result + } + + if kind == "build" { + // TODO: execute all but the FROM clauses in the Dockerfile? + // Define opts + opts := config.KanikoOptions{ + BuildArgs: toMultiArg(d.Args), + DockerfilePath: d.Path, + // Cache: true, + // CacheOptions: config.CacheOptions{ + // CacheDir: b.CacheDir, + // }, + IgnoreVarRun: true, + NoPush: true, + SrcContext: b.WorkspaceDir, + SnapshotMode: "full", + // OCILayoutPath: "/layers/kaniko", + // ImageNameDigestFile: fmt.Sprintf("/layers/kaniko/idk%s", b.Destination), + RegistryOptions: config.RegistryOptions{ + SkipTLSVerify: true, + }, + // IgnorePaths: b.IgnorePaths, + // TODO: we can not output a tar for intermediate images, we only need the last one + // TarPath: "/layers/kaniko/new_base.tar", + // Destinations: []string{b.Destination}, + // ForceBuildMetadata: true, + Cleanup: false, + } + + // Build the Dockerfile + fmt.Printf("Building the %s\n", opts.DockerfilePath) + err := b.BuildDockerFile(opts) + if err != nil { + panic(err) + } + continue + } + + // run extensions + + // we need a registry right now, because kaniko is pulling the image to build on top of for subsequent Dockerfile exts + registryHost := os.Getenv("REGISTRY_HOST") + b.Destination = fmt.Sprintf("%s/extended/runimage", registryHost) + fmt.Printf("Destination Image: %s\n", b.Destination) - for _, d := range meta.Dockerfiles { // Define opts opts := config.KanikoOptions{ - BuildArgs: toMultiArg(d.Args), - DockerfilePath: d.Path, - CacheOptions: config.CacheOptions{CacheDir: b.CacheDir}, - IgnoreVarRun: true, - NoPush: true, - SrcContext: b.WorkspaceDir, - SnapshotMode: "full", - IgnorePaths: b.IgnorePaths, - TarPath: b.LayerTarFileName, - Destinations: []string{b.Destination}, - ForceBuildMetadata: true, + BuildArgs: toMultiArg(d.Args), + DockerfilePath: d.Path, + // Cache: true, + // CacheOptions: config.CacheOptions{ + // CacheDir: b.CacheDir, + // }, + IgnoreVarRun: true, + // NoPush: true, + SrcContext: b.WorkspaceDir, + SnapshotMode: "full", + // OCILayoutPath: "/layers/kaniko", + // ImageNameDigestFile: fmt.Sprintf("/layers/kaniko/idk%s", b.Destination), + // IgnorePaths: b.IgnorePaths, + // TODO: we can not output a tar for intermediate images, we only need the last one + // TarPath: "/layers/kaniko/new_base.tar", + Destinations: []string{b.Destination}, + // ForceBuildMetadata: true, + Cleanup: true, + RegistryOptions: config.RegistryOptions{ + SkipTLSVerify: true, + }, } // Build the Dockerfile @@ -108,53 +169,13 @@ func doKaniko(kind, baseimage string) { if err != nil { panic(err) } - } - - // Log the content of the Kaniko dir - fmt.Printf("Reading dir content of: %s\n", b.KanikoDir) - util.ReadFilesFromPath(b.KanikoDir) - - // TODO: caching doesn't seem to be working at the moment. Need to investigate... - // Copy the tgz layer file to the Cache dir - srcPath := path.Join("/", b.LayerTarFileName) - dstPath := path.Join(b.CacheDir, b.LayerTarFileName) - - // Ensure cache directory exists - fmt.Printf("Creating %s dir ...\n", b.CacheDir) - err = os.MkdirAll(b.CacheDir, os.ModePerm) - if err != nil { - panic(err) - } - fmt.Printf("Copy the %s file to the %s dir ...\n", srcPath, dstPath) - err = util.File(srcPath, dstPath) - if err != nil { - panic(err) - } - - fmt.Printf("Extract the content of the tarball file %s under the cache %s\n", b.Opts.TarPath, b.Opts.CacheDir) - err = untar(dstPath, b.CacheDir) - if err != nil { - panic(err) - } - - layerFiles, err := layerTarfiles() - if err != nil { - panic(err) + // TODO: next base image is the one we just built, we should use digest instead + current_base_image = b.Destination } + // run buildpacks now if kind == "build" { - // We're in "build" mode, untar layers to root filesystem: / - for _, layerFile := range layerFiles { - workingDirectory := "/" - tarPath := "/layers/kaniko/" + layerFile - - err = untar(tarPath, workingDirectory) - if err != nil { - panic(err) - } - } - // Run the build for buildpacks with lowered privileges. // We must assume that this extender is run as root. cmd := exec.Command("/cnb/lifecycle/builder", "-app", "/workspace", "-log-level", "debug") @@ -177,8 +198,8 @@ func untar(tarPath, workingDirectory string) error { return command.Run() } -func layerTarfiles() ([]string, error) { - manifestPath := "/layers/kaniko/manifest.json" +func layerTarfiles(manifestPath string) ([]string, error) { + // manifestPath := "/layers/kaniko/manifest.json" var manifest []struct { Layers []string `json:"Layers"` } diff --git a/test-extender.sh b/test-extender.sh index 189580ed3..86e25d798 100755 --- a/test-extender.sh +++ b/test-extender.sh @@ -1,8 +1,12 @@ +set -e + cd $LIFECYCLE_REPO_PATH echo ">>>>>>>>>> Building lifecycle..." -docker image rm test-builder --force +docker image rm $REGISTRY_HOST/test-builder --force +# remove the $REGISTRY_HOST/extended/runimage from any previous run +docker image rm $REGISTRY_HOST/extended/runimage --force make clean build-linux-amd64 @@ -13,7 +17,17 @@ FROM cnbs/sample-builder:bionic COPY ./lifecycle /cnb/lifecycle EOF -docker build -t test-builder . +docker build -t $REGISTRY_HOST/test-builder . +docker push $REGISTRY_HOST/test-builder + +cat << EOF > Dockerfile.extender +FROM scratch +COPY ./lifecycle /cnb/lifecycle +ENTRYPOINT /cnb/lifecycle/extender +EOF + +docker build -f Dockerfile.extender -t $REGISTRY_HOST/extender . +docker push $REGISTRY_HOST/extender cd $SAMPLES_REPO_PATH @@ -21,6 +35,10 @@ rm -rf $SAMPLES_REPO_PATH/kaniko mkdir -p $SAMPLES_REPO_PATH/kaniko rm -rf $SAMPLES_REPO_PATH/layers/kaniko mkdir -p $SAMPLES_REPO_PATH/layers/kaniko +rm -rf $SAMPLES_REPO_PATH/kaniko-run +mkdir -p $SAMPLES_REPO_PATH/kaniko-run +rm -rf $SAMPLES_REPO_PATH/layers-run/kaniko +mkdir -p $SAMPLES_REPO_PATH/layers-run/kaniko echo ">>>>>>>>>> Running detect..." @@ -30,7 +48,7 @@ docker run \ -v $PWD/platform/:/platform \ -v $PWD/cnb/ext/:/cnb/ext \ -v $PWD/cnb/buildpacks/:/cnb/buildpacks \ - test-builder \ + $REGISTRY_HOST/test-builder \ /cnb/lifecycle/detector -order /layers/order.toml -log-level debug echo ">>>>>>>>>> Running build for extensions..." @@ -41,9 +59,14 @@ docker run \ -v $PWD/platform/:/platform \ -v $PWD/cnb/ext/:/cnb/ext \ -v $PWD/cnb/buildpacks/:/cnb/buildpacks \ - test-builder \ + $REGISTRY_HOST/test-builder \ /cnb/lifecycle/builder -use-extensions -log-level debug +echo ">>>>>>>>>> Copy build extension layers for run extension..." + +# this needs to come along for the ride for extending run image +cp -R $PWD/layers/* $PWD/layers-run + echo ">>>>>>>>>> Running extend on build image followed by build for buildpacks..." docker run \ @@ -54,6 +77,37 @@ docker run \ -v $PWD/cnb/ext/:/cnb/ext \ -v $PWD/cnb/buildpacks/:/cnb/buildpacks \ -u root \ - test-builder \ - /cnb/lifecycle/extender kaniko build ubuntu:bionic + $REGISTRY_HOST/test-builder \ + /cnb/lifecycle/extender kaniko build "$REGISTRY_HOST/test-builder" + # args: + +echo ">>>>>>>>>> Running extend on run image..." + # TODO: we should probably not have to mount /cnb/ext? Can we copy the static Dockerfiles to layers? +docker run \ + -v $PWD/workspace/:/workspace \ + -v $PWD/kaniko-run/:/kaniko \ + -v $PWD/layers-run/:/layers \ + -v $PWD/cnb/ext/:/cnb/ext \ + -e REGISTRY_HOST=$REGISTRY_HOST \ + --entrypoint /cnb/lifecycle/extender \ + $REGISTRY_HOST/extender \ + kaniko run cnbs/sample-stack-run:bionic # args: + +echo ">>>>>>>>>> Exporting final app image..." + +docker run \ + -v $PWD/workspace/:/workspace \ + -v $PWD/layers-run/:/layers-run \ + -v $PWD/layers/:/layers \ + -v $PWD/platform/:/platform \ + -v $PWD/cnb/ext/:/cnb/ext \ + -v $PWD/cnb/buildpacks/:/cnb/buildpacks \ + -u root \ + $REGISTRY_HOST/test-builder \ + /cnb/lifecycle/exporter -log-level debug -run-image $REGISTRY_HOST/extended/runimage $REGISTRY_HOST/appimage + +# echo ">>>>>>>>>> Validate app image..." +docker pull $REGISTRY_HOST/appimage +docker run --rm --entrypoint cat -it $REGISTRY_HOST/appimage /opt/arg.txt +docker run --rm --entrypoint curl -it $REGISTRY_HOST/appimage google.com