2021-04-12 17:00:36 +00:00
|
|
|
package compiler
|
2020-08-05 17:09:45 +00:00
|
|
|
|
|
|
|
import (
|
2021-04-12 17:00:36 +00:00
|
|
|
"fmt"
|
2020-08-05 17:09:45 +00:00
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
|
2021-04-12 17:00:36 +00:00
|
|
|
artifact "github.com/mudler/luet/pkg/compiler/types/artifact"
|
2021-01-24 18:04:57 +00:00
|
|
|
|
2021-04-12 17:00:36 +00:00
|
|
|
"github.com/mudler/luet/pkg/compiler/backend"
|
2020-08-05 17:09:45 +00:00
|
|
|
"github.com/mudler/luet/pkg/config"
|
|
|
|
"github.com/pkg/errors"
|
2021-04-12 17:00:36 +00:00
|
|
|
|
|
|
|
. "github.com/mudler/luet/pkg/logger"
|
2020-08-05 17:09:45 +00:00
|
|
|
)
|
|
|
|
|
2021-04-12 17:00:36 +00:00
|
|
|
func NewBackend(s string) (CompilerBackend, error) {
|
|
|
|
var compilerBackend CompilerBackend
|
|
|
|
|
|
|
|
switch s {
|
|
|
|
case backend.ImgBackend:
|
|
|
|
compilerBackend = backend.NewSimpleImgBackend()
|
|
|
|
case backend.DockerBackend:
|
|
|
|
compilerBackend = backend.NewSimpleDockerBackend()
|
|
|
|
default:
|
|
|
|
return nil, errors.New("invalid backend. Unsupported")
|
|
|
|
}
|
|
|
|
|
|
|
|
return compilerBackend, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type CompilerBackend interface {
|
|
|
|
BuildImage(backend.Options) error
|
|
|
|
ExportImage(backend.Options) error
|
|
|
|
RemoveImage(backend.Options) error
|
|
|
|
ImageDefinitionToTar(backend.Options) error
|
|
|
|
ExtractRootfs(opts backend.Options, keepPerms bool) error
|
|
|
|
|
|
|
|
CopyImage(string, string) error
|
|
|
|
DownloadImage(opts backend.Options) error
|
|
|
|
|
|
|
|
Push(opts backend.Options) error
|
|
|
|
ImageAvailable(string) bool
|
|
|
|
|
|
|
|
ImageExists(string) bool
|
|
|
|
}
|
|
|
|
|
2020-08-05 17:09:45 +00:00
|
|
|
// GenerateChanges generates changes between two images using a backend by leveraging export/extractrootfs methods
|
|
|
|
// example of json return: [
|
|
|
|
// {
|
|
|
|
// "Image1": "luet/base",
|
|
|
|
// "Image2": "alpine",
|
|
|
|
// "DiffType": "File",
|
|
|
|
// "Diff": {
|
|
|
|
// "Adds": null,
|
|
|
|
// "Dels": [
|
|
|
|
// {
|
|
|
|
// "Name": "/luetbuild",
|
|
|
|
// "Size": 5830706
|
|
|
|
// },
|
|
|
|
// {
|
|
|
|
// "Name": "/luetbuild/Dockerfile",
|
|
|
|
// "Size": 50
|
|
|
|
// },
|
|
|
|
// {
|
|
|
|
// "Name": "/luetbuild/output1",
|
|
|
|
// "Size": 5830656
|
|
|
|
// }
|
|
|
|
// ],
|
|
|
|
// "Mods": null
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// ]
|
2021-04-12 17:00:36 +00:00
|
|
|
func GenerateChanges(b CompilerBackend, fromImage, toImage backend.Options) ([]artifact.ArtifactLayer, error) {
|
2020-12-08 21:16:17 +00:00
|
|
|
|
2021-04-12 17:00:36 +00:00
|
|
|
res := artifact.ArtifactLayer{FromImage: fromImage.ImageName, ToImage: toImage.ImageName}
|
2020-08-05 17:09:45 +00:00
|
|
|
|
|
|
|
tmpdiffs, err := config.LuetCfg.GetSystem().TempDir("extraction")
|
|
|
|
if err != nil {
|
2021-04-12 17:00:36 +00:00
|
|
|
return []artifact.ArtifactLayer{}, errors.Wrap(err, "Error met while creating tempdir for rootfs")
|
2020-08-05 17:09:45 +00:00
|
|
|
}
|
|
|
|
defer os.RemoveAll(tmpdiffs) // clean up
|
|
|
|
|
|
|
|
srcRootFS, err := ioutil.TempDir(tmpdiffs, "src")
|
|
|
|
if err != nil {
|
2021-04-12 17:00:36 +00:00
|
|
|
return []artifact.ArtifactLayer{}, errors.Wrap(err, "Error met while creating tempdir for rootfs")
|
2020-08-05 17:09:45 +00:00
|
|
|
}
|
|
|
|
defer os.RemoveAll(srcRootFS) // clean up
|
|
|
|
|
|
|
|
dstRootFS, err := ioutil.TempDir(tmpdiffs, "dst")
|
|
|
|
if err != nil {
|
2021-04-12 17:00:36 +00:00
|
|
|
return []artifact.ArtifactLayer{}, errors.Wrap(err, "Error met while creating tempdir for rootfs")
|
2020-08-05 17:09:45 +00:00
|
|
|
}
|
|
|
|
defer os.RemoveAll(dstRootFS) // clean up
|
|
|
|
|
2021-04-12 17:00:36 +00:00
|
|
|
srcImageExtract := backend.Options{
|
2020-12-08 21:16:17 +00:00
|
|
|
ImageName: fromImage.ImageName,
|
2020-08-05 17:09:45 +00:00
|
|
|
Destination: srcRootFS,
|
|
|
|
}
|
2021-01-24 18:04:57 +00:00
|
|
|
Debug("Extracting source image", fromImage.ImageName)
|
2020-08-05 17:09:45 +00:00
|
|
|
err = b.ExtractRootfs(srcImageExtract, false) // No need to keep permissions as we just collect file diffs
|
|
|
|
if err != nil {
|
2021-04-12 17:00:36 +00:00
|
|
|
return []artifact.ArtifactLayer{}, errors.Wrap(err, "Error met while unpacking src image "+fromImage.ImageName)
|
2020-08-05 17:09:45 +00:00
|
|
|
}
|
|
|
|
|
2021-04-12 17:00:36 +00:00
|
|
|
dstImageExtract := backend.Options{
|
2020-12-08 21:16:17 +00:00
|
|
|
ImageName: toImage.ImageName,
|
2020-08-05 17:09:45 +00:00
|
|
|
Destination: dstRootFS,
|
|
|
|
}
|
2021-01-24 18:04:57 +00:00
|
|
|
Debug("Extracting destination image", toImage.ImageName)
|
2020-08-05 17:09:45 +00:00
|
|
|
err = b.ExtractRootfs(dstImageExtract, false)
|
|
|
|
if err != nil {
|
2021-04-12 17:00:36 +00:00
|
|
|
return []artifact.ArtifactLayer{}, errors.Wrap(err, "Error met while unpacking dst image "+toImage.ImageName)
|
2020-08-05 17:09:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get Additions/Changes. dst -> src
|
|
|
|
err = filepath.Walk(dstRootFS, func(path string, info os.FileInfo, err error) error {
|
|
|
|
if info.IsDir() {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
realpath := strings.Replace(path, dstRootFS, "", -1)
|
|
|
|
fileInfo, err := os.Lstat(filepath.Join(srcRootFS, realpath))
|
|
|
|
if err == nil {
|
|
|
|
var sizeA, sizeB int64
|
|
|
|
sizeA = fileInfo.Size()
|
|
|
|
|
|
|
|
if s, err := os.Lstat(filepath.Join(dstRootFS, realpath)); err == nil {
|
|
|
|
sizeB = s.Size()
|
|
|
|
}
|
|
|
|
|
|
|
|
if sizeA != sizeB {
|
|
|
|
// fmt.Println("File changed", path, filepath.Join(srcRootFS, realpath))
|
2021-04-12 17:00:36 +00:00
|
|
|
res.Diffs.Changes = append(res.Diffs.Changes, artifact.ArtifactNode{
|
2020-08-05 17:09:45 +00:00
|
|
|
Name: filepath.Join("/", realpath),
|
|
|
|
Size: int(sizeB),
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
// fmt.Println("File already exists", path, filepath.Join(srcRootFS, realpath))
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
var sizeB int64
|
|
|
|
|
|
|
|
if s, err := os.Lstat(filepath.Join(dstRootFS, realpath)); err == nil {
|
|
|
|
sizeB = s.Size()
|
|
|
|
}
|
2021-04-12 17:00:36 +00:00
|
|
|
res.Diffs.Additions = append(res.Diffs.Additions, artifact.ArtifactNode{
|
2020-08-05 17:09:45 +00:00
|
|
|
Name: filepath.Join("/", realpath),
|
|
|
|
Size: int(sizeB),
|
|
|
|
})
|
|
|
|
|
|
|
|
// fmt.Println("File created", path, filepath.Join(srcRootFS, realpath))
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
2021-04-12 17:00:36 +00:00
|
|
|
return []artifact.ArtifactLayer{}, errors.Wrap(err, "Error met while walking image destination")
|
2020-08-05 17:09:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get deletions. src -> dst
|
|
|
|
err = filepath.Walk(srcRootFS, func(path string, info os.FileInfo, err error) error {
|
|
|
|
if info.IsDir() {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
realpath := strings.Replace(path, srcRootFS, "", -1)
|
|
|
|
if _, err = os.Lstat(filepath.Join(dstRootFS, realpath)); err != nil {
|
|
|
|
// fmt.Println("File deleted", path, filepath.Join(srcRootFS, realpath))
|
2021-04-12 17:00:36 +00:00
|
|
|
res.Diffs.Deletions = append(res.Diffs.Deletions, artifact.ArtifactNode{
|
2020-08-05 17:09:45 +00:00
|
|
|
Name: filepath.Join("/", realpath),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
2021-04-12 17:00:36 +00:00
|
|
|
return []artifact.ArtifactLayer{}, errors.Wrap(err, "Error met while walking image source")
|
|
|
|
}
|
|
|
|
|
|
|
|
diffs := []artifact.ArtifactLayer{res}
|
|
|
|
|
|
|
|
if config.LuetCfg.GetGeneral().Debug {
|
|
|
|
summary := ComputeArtifactLayerSummary(diffs)
|
|
|
|
for _, l := range summary.Layers {
|
|
|
|
Debug(fmt.Sprintf("Diff %s -> %s: add %d (%d bytes), del %d (%d bytes), change %d (%d bytes)",
|
|
|
|
l.FromImage, l.ToImage,
|
|
|
|
l.AddFiles, l.AddSizes,
|
|
|
|
l.DelFiles, l.DelSizes,
|
|
|
|
l.ChangeFiles, l.ChangeSizes))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return diffs, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type ArtifactLayerSummary struct {
|
|
|
|
FromImage string `json:"image1"`
|
|
|
|
ToImage string `json:"image2"`
|
|
|
|
AddFiles int `json:"add_files"`
|
|
|
|
AddSizes int64 `json:"add_sizes"`
|
|
|
|
DelFiles int `json:"del_files"`
|
|
|
|
DelSizes int64 `json:"del_sizes"`
|
|
|
|
ChangeFiles int `json:"change_files"`
|
|
|
|
ChangeSizes int64 `json:"change_sizes"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type ArtifactLayersSummary struct {
|
|
|
|
Layers []ArtifactLayerSummary `json:"summary"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func ComputeArtifactLayerSummary(diffs []artifact.ArtifactLayer) ArtifactLayersSummary {
|
|
|
|
|
|
|
|
ans := ArtifactLayersSummary{
|
|
|
|
Layers: make([]ArtifactLayerSummary, 0),
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, layer := range diffs {
|
|
|
|
sum := ArtifactLayerSummary{
|
|
|
|
FromImage: layer.FromImage,
|
|
|
|
ToImage: layer.ToImage,
|
|
|
|
AddFiles: 0,
|
|
|
|
AddSizes: 0,
|
|
|
|
DelFiles: 0,
|
|
|
|
DelSizes: 0,
|
|
|
|
ChangeFiles: 0,
|
|
|
|
ChangeSizes: 0,
|
|
|
|
}
|
|
|
|
for _, a := range layer.Diffs.Additions {
|
|
|
|
sum.AddFiles++
|
|
|
|
sum.AddSizes += int64(a.Size)
|
|
|
|
}
|
|
|
|
for _, d := range layer.Diffs.Deletions {
|
|
|
|
sum.DelFiles++
|
|
|
|
sum.DelSizes += int64(d.Size)
|
|
|
|
}
|
|
|
|
for _, c := range layer.Diffs.Changes {
|
|
|
|
sum.ChangeFiles++
|
|
|
|
sum.ChangeSizes += int64(c.Size)
|
|
|
|
}
|
|
|
|
ans.Layers = append(ans.Layers, sum)
|
2020-08-05 17:09:45 +00:00
|
|
|
}
|
|
|
|
|
2021-04-12 17:00:36 +00:00
|
|
|
return ans
|
2020-08-05 17:09:45 +00:00
|
|
|
}
|