2019-11-04 16:16:13 +00:00
// Copyright © 2019 Ettore Di Giacinto <mudler@gentoo.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package compiler
import (
2020-12-07 16:20:32 +00:00
"archive/tar"
2019-11-14 16:45:21 +00:00
"fmt"
2019-11-04 16:16:13 +00:00
"io/ioutil"
2019-11-10 09:48:07 +00:00
"os"
"path/filepath"
2020-10-04 17:16:01 +00:00
2019-12-03 16:26:53 +00:00
"regexp"
"strings"
2019-11-11 09:22:55 +00:00
"sync"
2020-07-12 13:27:50 +00:00
"time"
2019-11-04 16:16:13 +00:00
2020-11-13 17:25:44 +00:00
bus "github.com/mudler/luet/pkg/bus"
2020-11-27 21:22:20 +00:00
yaml "gopkg.in/yaml.v2"
2020-11-13 17:25:44 +00:00
2019-11-04 16:16:13 +00:00
"github.com/mudler/luet/pkg/helpers"
2019-12-06 15:28:42 +00:00
. "github.com/mudler/luet/pkg/logger"
2019-11-04 16:16:13 +00:00
pkg "github.com/mudler/luet/pkg/package"
2019-11-11 18:19:13 +00:00
"github.com/mudler/luet/pkg/solver"
2019-11-04 16:16:13 +00:00
"github.com/mudler/luet/pkg/tree"
2019-11-11 09:22:55 +00:00
"github.com/pkg/errors"
2019-11-04 16:16:13 +00:00
)
const BuildFile = "build.yaml"
2020-10-04 17:16:01 +00:00
const DefinitionFile = "definition.yaml"
2020-11-14 23:13:46 +00:00
const CollectionFile = "collection.yaml"
2019-11-04 16:16:13 +00:00
type LuetCompiler struct {
2019-11-05 16:36:22 +00:00
* tree . CompilerRecipe
2020-01-05 13:32:26 +00:00
Backend CompilerBackend
Database pkg . PackageDatabase
ImageRepository string
PullFirst , KeepImg , Clean bool
Concurrency int
CompressionType CompressionImplementation
2020-02-12 10:21:55 +00:00
Options CompilerOptions
2020-10-25 17:43:35 +00:00
SolverOptions solver . Options
2019-11-04 16:16:13 +00:00
}
2020-10-25 17:43:35 +00:00
func NewLuetCompiler ( backend CompilerBackend , db pkg . PackageDatabase , opt * CompilerOptions , solvopts solver . Options ) Compiler {
2019-11-05 16:36:22 +00:00
// The CompilerRecipe will gives us a tree with only build deps listed.
return & LuetCompiler {
Backend : backend ,
CompilerRecipe : & tree . CompilerRecipe {
2019-11-29 18:01:49 +00:00
tree . Recipe { Database : db } ,
2019-11-05 16:36:22 +00:00
} ,
2019-12-02 15:33:59 +00:00
Database : db ,
2020-01-05 13:32:26 +00:00
ImageRepository : opt . ImageRepository ,
PullFirst : opt . PullFirst ,
CompressionType : opt . CompressionType ,
KeepImg : opt . KeepImg ,
Concurrency : opt . Concurrency ,
2020-02-12 10:21:55 +00:00
Options : * opt ,
2020-10-25 17:43:35 +00:00
SolverOptions : solvopts ,
2019-11-05 16:36:22 +00:00
}
2019-11-04 16:16:13 +00:00
}
2019-12-30 11:53:32 +00:00
func ( cs * LuetCompiler ) SetConcurrency ( i int ) {
cs . Concurrency = i
}
2019-12-30 13:52:34 +00:00
func ( cs * LuetCompiler ) SetCompressionType ( t CompressionImplementation ) {
cs . CompressionType = t
}
2019-11-11 09:22:55 +00:00
func ( cs * LuetCompiler ) compilerWorker ( i int , wg * sync . WaitGroup , cspecs chan CompilationSpec , a * [ ] Artifact , m * sync . Mutex , concurrency int , keepPermissions bool , errors chan error ) {
defer wg . Done ( )
for s := range cspecs {
2019-11-15 23:38:07 +00:00
ar , err := cs . compile ( concurrency , keepPermissions , s )
2019-11-11 09:22:55 +00:00
if err != nil {
errors <- err
}
m . Lock ( )
* a = append ( * a , ar )
m . Unlock ( )
}
}
2019-12-30 11:53:32 +00:00
2019-12-30 13:46:45 +00:00
func ( cs * LuetCompiler ) CompileWithReverseDeps ( keepPermissions bool , ps CompilationSpecs ) ( [ ] Artifact , [ ] error ) {
artifacts , err := cs . CompileParallel ( keepPermissions , ps )
2019-11-15 17:11:26 +00:00
if len ( err ) != 0 {
return artifacts , err
}
2019-11-16 13:16:44 +00:00
Info ( ":ant: Resolving reverse dependencies" )
2019-11-15 17:11:26 +00:00
toCompile := NewLuetCompilationspecs ( )
for _ , a := range artifacts {
2019-11-29 18:01:49 +00:00
2019-12-06 15:28:42 +00:00
revdeps := a . GetCompileSpec ( ) . GetPackage ( ) . Revdeps ( cs . Database )
2019-11-15 17:11:26 +00:00
for _ , r := range revdeps {
spec , asserterr := cs . FromPackage ( r )
if err != nil {
return nil , append ( err , asserterr )
}
spec . SetOutputPath ( ps . All ( ) [ 0 ] . GetOutputPath ( ) )
toCompile . Add ( spec )
}
// for _, assertion := range a.GetSourceAssertion() {
// if assertion.Value && assertion.Package.Flagged() {
// spec, asserterr := cs.FromPackage(assertion.Package)
// if err != nil {
// return nil, append(err, asserterr)
// }
// w, asserterr := cs.Tree().World()
// if err != nil {
// return nil, append(err, asserterr)
// }
// revdeps := spec.GetPackage().Revdeps(&w)
// for _, r := range revdeps {
// spec, asserterr := cs.FromPackage(r)
// if asserterr != nil {
// return nil, append(err, asserterr)
// }
// spec.SetOutputPath(ps.All()[0].GetOutputPath())
// toCompile.Add(spec)
// }
// }
// }
}
uniques := toCompile . Unique ( ) . Remove ( ps )
for _ , u := range uniques . All ( ) {
2019-11-16 13:40:58 +00:00
Info ( " :arrow_right_hook:" , u . GetPackage ( ) . GetName ( ) , ":leaves:" , u . GetPackage ( ) . GetVersion ( ) , "(" , u . GetPackage ( ) . GetCategory ( ) , ")" )
2019-11-15 17:11:26 +00:00
}
2019-12-30 13:46:45 +00:00
artifacts2 , err := cs . CompileParallel ( keepPermissions , uniques )
2019-11-15 17:11:26 +00:00
return append ( artifacts , artifacts2 ... ) , err
}
2019-11-11 09:22:55 +00:00
2019-12-30 13:46:45 +00:00
func ( cs * LuetCompiler ) CompileParallel ( keepPermissions bool , ps CompilationSpecs ) ( [ ] Artifact , [ ] error ) {
2019-11-16 13:40:58 +00:00
Spinner ( 22 )
defer SpinnerStop ( )
2019-11-11 09:22:55 +00:00
all := make ( chan CompilationSpec )
artifacts := [ ] Artifact { }
mutex := & sync . Mutex { }
2019-11-15 17:11:26 +00:00
errors := make ( chan error , ps . Len ( ) )
2019-11-11 09:22:55 +00:00
var wg = new ( sync . WaitGroup )
2019-12-30 13:46:45 +00:00
for i := 0 ; i < cs . Concurrency ; i ++ {
2019-11-11 09:22:55 +00:00
wg . Add ( 1 )
2019-12-30 13:46:45 +00:00
go cs . compilerWorker ( i , wg , all , & artifacts , mutex , cs . Concurrency , keepPermissions , errors )
2019-11-11 09:22:55 +00:00
}
2019-11-15 17:11:26 +00:00
for _ , p := range ps . All ( ) {
2019-11-15 23:38:07 +00:00
asserts , err := cs . ComputeDepTree ( p )
if err != nil {
panic ( err )
}
p . SetSourceAssertion ( asserts )
2019-11-11 09:22:55 +00:00
all <- p
}
close ( all )
wg . Wait ( )
close ( errors )
var allErrors [ ] error
for e := range errors {
allErrors = append ( allErrors , e )
}
return artifacts , allErrors
}
2020-11-08 14:35:24 +00:00
func ( cs * LuetCompiler ) stripFromRootfs ( includes [ ] string , rootfs string , include bool ) error {
2019-12-03 16:26:53 +00:00
var includeRegexp [ ] * regexp . Regexp
for _ , i := range includes {
r , e := regexp . Compile ( i )
if e != nil {
return errors . Wrap ( e , "Could not compile regex in the include of the package" )
}
includeRegexp = append ( includeRegexp , r )
}
toRemove := [ ] string { }
// the function that handles each file or dir
var ff = func ( currentpath string , info os . FileInfo , err error ) error {
// if info.Name() != DefinitionFile {
// return nil // Skip with no errors
// }
if currentpath == rootfs {
return nil
}
abspath := strings . ReplaceAll ( currentpath , rootfs , "" )
match := false
for _ , i := range includeRegexp {
if i . MatchString ( abspath ) {
match = true
}
}
2020-11-08 14:35:24 +00:00
if include && ! match || ! include && match {
2019-12-03 16:26:53 +00:00
toRemove = append ( toRemove , currentpath )
}
return nil
}
err := filepath . Walk ( rootfs , ff )
if err != nil {
return err
}
for _ , s := range toRemove {
e := os . RemoveAll ( s )
if e != nil {
Warning ( "Failed removing" , s , e . Error ( ) )
return e
}
}
return nil
}
2020-11-10 17:14:18 +00:00
func ( cs * LuetCompiler ) unpackFs ( rootfs string , concurrency int , p CompilationSpec ) ( Artifact , error ) {
if p . GetPackageDir ( ) != "" {
Info ( ":tophat: Packing from output dir" , p . GetPackageDir ( ) )
rootfs = filepath . Join ( rootfs , p . GetPackageDir ( ) )
}
if len ( p . GetIncludes ( ) ) > 0 {
// strip from includes
cs . stripFromRootfs ( p . GetIncludes ( ) , rootfs , true )
}
if len ( p . GetExcludes ( ) ) > 0 {
// strip from includes
cs . stripFromRootfs ( p . GetExcludes ( ) , rootfs , false )
}
artifact := NewPackageArtifact ( p . Rel ( p . GetPackage ( ) . GetFingerPrint ( ) + ".package.tar" ) )
artifact . SetCompressionType ( cs . CompressionType )
if err := artifact . Compress ( rootfs , concurrency ) ; err != nil {
return nil , errors . Wrap ( err , "Error met while creating package archive" )
}
artifact . SetCompileSpec ( p )
return artifact , nil
}
func ( cs * LuetCompiler ) unpackDelta ( rootfs string , concurrency int , keepPermissions bool , p CompilationSpec , builderOpts , runnerOpts CompilerBackendOptions ) ( Artifact , error ) {
pkgTag := ":package: " + p . GetPackage ( ) . HumanReadableString ( )
if err := cs . Backend . ExportImage ( builderOpts ) ; err != nil {
return nil , errors . Wrap ( err , "Could not export image" )
}
if ! cs . Options . KeepImageExport {
defer os . Remove ( builderOpts . Destination )
}
Info ( pkgTag , ":hammer: Generating delta" )
2020-12-08 21:16:17 +00:00
diffs , err := cs . Backend . Changes ( builderOpts , runnerOpts )
2020-11-10 17:14:18 +00:00
if err != nil {
return nil , errors . Wrap ( err , "Could not generate changes from layers" )
}
artifact , err := ExtractArtifactFromDelta ( rootfs , p . Rel ( p . GetPackage ( ) . GetFingerPrint ( ) + ".package.tar" ) , diffs , concurrency , keepPermissions , p . GetIncludes ( ) , p . GetExcludes ( ) , cs . CompressionType )
if err != nil {
return nil , errors . Wrap ( err , "Could not generate deltas" )
}
artifact . SetCompileSpec ( p )
return artifact , nil
}
func ( cs * LuetCompiler ) buildPackageImage ( image , buildertaggedImage , packageImage string ,
2020-11-10 17:40:35 +00:00
concurrency int , keepPermissions bool ,
2020-11-10 17:14:18 +00:00
p CompilationSpec ) ( CompilerBackendOptions , CompilerBackendOptions , error ) {
var runnerOpts , builderOpts CompilerBackendOptions
2020-06-23 16:44:29 +00:00
2020-10-30 18:15:04 +00:00
pkgTag := ":package: " + p . GetPackage ( ) . HumanReadableString ( )
2020-06-23 16:44:29 +00:00
// Use packageImage as salt into the fp being used
// so the hash is unique also in cases where
// some package deps does have completely different
// depgraphs
// TODO: As the salt contains the packageImage ( in registry/organization/imagename:tag format)
// the images hashes are broken with registry mirrors.
// We should use the image tag, or pass by the package assertion hash which is unique
// and identifies the deptree of the package.
fp := p . GetPackage ( ) . HashFingerprint ( packageImage )
2020-06-03 19:00:30 +00:00
if buildertaggedImage == "" {
2020-12-02 20:18:12 +00:00
buildertaggedImage = cs . ImageRepository + ":builder-" + fp
2020-06-23 16:44:29 +00:00
Debug ( pkgTag , "Creating intermediary image" , buildertaggedImage , "from" , image )
2020-06-03 19:00:30 +00:00
}
2020-06-23 16:44:29 +00:00
// TODO: Cleanup, not actually hit
2020-06-03 19:00:30 +00:00
if packageImage == "" {
2020-12-02 20:18:12 +00:00
packageImage = cs . ImageRepository + ":builder-invalid" + fp
2020-06-03 19:00:30 +00:00
}
2020-06-23 16:44:29 +00:00
2019-11-11 18:19:13 +00:00
p . SetSeedImage ( image ) // In this case, we ignore the build deps as we suppose that the image has them - otherwise we recompose the tree with a solver,
// and we build all the images first.
2019-12-02 15:33:59 +00:00
2019-11-17 12:27:32 +00:00
err := os . MkdirAll ( p . Rel ( "build" ) , os . ModePerm )
if err != nil {
2020-11-10 17:14:18 +00:00
return builderOpts , runnerOpts , errors . Wrap ( err , "Error met while creating tempdir for building" )
2019-11-17 12:27:32 +00:00
}
buildDir , err := ioutil . TempDir ( p . Rel ( "build" ) , "pack" )
if err != nil {
2020-11-10 17:14:18 +00:00
return builderOpts , runnerOpts , errors . Wrap ( err , "Error met while creating tempdir for building" )
2019-11-17 12:27:32 +00:00
}
defer os . RemoveAll ( buildDir ) // clean up
2019-11-05 16:36:22 +00:00
2019-11-11 18:19:13 +00:00
// First we copy the source definitions into the output - we create a copy which the builds will need (we need to cache this phase somehow)
2019-11-17 12:27:32 +00:00
err = helpers . CopyDir ( p . GetPackage ( ) . GetPath ( ) , buildDir )
2019-11-11 18:19:13 +00:00
if err != nil {
2020-11-10 17:14:18 +00:00
return builderOpts , runnerOpts , errors . Wrap ( err , "Could not copy package sources" )
2019-11-11 18:19:13 +00:00
}
2020-02-13 13:15:43 +00:00
// Copy file into the build context, the compilespec might have requested to do so.
if len ( p . GetRetrieve ( ) ) > 0 {
err := p . CopyRetrieves ( buildDir )
if err != nil {
Warning ( "Failed copying retrieves" , err . Error ( ) )
}
}
2019-11-11 18:19:13 +00:00
// First we create the builder image
2020-11-10 17:40:35 +00:00
if err := p . WriteBuildImageDefinition ( filepath . Join ( buildDir , p . GetPackage ( ) . GetFingerPrint ( ) + "-builder.dockerfile" ) ) ; err != nil {
return builderOpts , runnerOpts , errors . Wrap ( err , "Could not generate image definition" )
}
2020-12-07 17:58:14 +00:00
if len ( p . GetPreBuildSteps ( ) ) == 0 {
buildertaggedImage = image
}
2020-11-10 17:14:18 +00:00
// Then we write the step image, which uses the builder one
2020-11-10 17:40:35 +00:00
if err := p . WriteStepImageDefinition ( buildertaggedImage , filepath . Join ( buildDir , p . GetPackage ( ) . GetFingerPrint ( ) + ".dockerfile" ) ) ; err != nil {
return builderOpts , runnerOpts , errors . Wrap ( err , "Could not generate image definition" )
}
2020-11-10 17:14:18 +00:00
builderOpts = CompilerBackendOptions {
2019-11-11 18:19:13 +00:00
ImageName : buildertaggedImage ,
SourcePath : buildDir ,
DockerFileName : p . GetPackage ( ) . GetFingerPrint ( ) + "-builder.dockerfile" ,
Destination : p . Rel ( p . GetPackage ( ) . GetFingerPrint ( ) + "-builder.image.tar" ) ,
}
2020-11-10 17:14:18 +00:00
runnerOpts = CompilerBackendOptions {
ImageName : packageImage ,
SourcePath : buildDir ,
DockerFileName : p . GetPackage ( ) . GetFingerPrint ( ) + ".dockerfile" ,
Destination : p . Rel ( p . GetPackage ( ) . GetFingerPrint ( ) + ".image.tar" ) ,
}
2019-11-10 09:48:07 +00:00
2020-11-10 17:40:35 +00:00
buildAndPush := func ( opts CompilerBackendOptions ) error {
buildImage := true
if cs . Options . PullFirst {
2020-12-02 17:24:32 +00:00
bus . Manager . Publish ( bus . EventImagePrePull , opts )
2020-11-28 17:03:43 +00:00
err := cs . Backend . DownloadImage ( opts )
if err == nil {
2020-11-10 17:40:35 +00:00
buildImage = false
2020-11-28 17:03:43 +00:00
} else {
2020-12-02 17:24:32 +00:00
Warning ( "Failed to download '" + opts . ImageName + "'. Will keep going and build the image unless you use --fatal" )
2020-11-28 17:03:43 +00:00
Warning ( err . Error ( ) )
2020-11-10 17:14:18 +00:00
}
2020-12-02 17:24:32 +00:00
bus . Manager . Publish ( bus . EventImagePostPull , opts )
2020-02-15 13:45:05 +00:00
}
2020-11-10 17:40:35 +00:00
if buildImage {
2020-12-02 17:24:32 +00:00
bus . Manager . Publish ( bus . EventImagePreBuild , opts )
2020-11-10 17:40:35 +00:00
if err := cs . Backend . BuildImage ( opts ) ; err != nil {
return errors . Wrap ( err , "Could not build image: " + image + " " + opts . DockerFileName )
2020-11-10 17:14:18 +00:00
}
2020-12-02 17:24:32 +00:00
bus . Manager . Publish ( bus . EventImagePostBuild , opts )
2020-11-10 17:40:35 +00:00
if cs . Options . Push {
2020-12-02 17:24:32 +00:00
bus . Manager . Publish ( bus . EventImagePrePush , opts )
2020-11-10 17:40:35 +00:00
if err = cs . Backend . Push ( opts ) ; err != nil {
return errors . Wrap ( err , "Could not push image: " + image + " " + opts . DockerFileName )
}
2020-12-02 17:24:32 +00:00
bus . Manager . Publish ( bus . EventImagePostPush , opts )
2020-11-10 17:14:18 +00:00
}
2020-11-10 17:40:35 +00:00
}
return nil
2019-11-11 18:19:13 +00:00
}
2020-12-06 22:50:51 +00:00
if len ( p . GetPreBuildSteps ( ) ) != 0 {
Info ( pkgTag , ":whale: Generating 'builder' image from" , image , "as" , buildertaggedImage , "with prelude steps" )
if err := buildAndPush ( builderOpts ) ; err != nil {
return builderOpts , runnerOpts , errors . Wrap ( err , "Could not push image: " + image + " " + builderOpts . DockerFileName )
}
2019-12-02 15:33:59 +00:00
}
2020-12-06 22:50:51 +00:00
2020-12-07 18:39:56 +00:00
// Even if we might not have any steps to build, we do that so we can tag the image used in this moment and use that to cache it in a registry, or in the system.
// acting as a docker tag.
Info ( pkgTag , ":whale: Generating 'package' image from" , buildertaggedImage , "as" , packageImage , "with build steps" )
if err := buildAndPush ( runnerOpts ) ; err != nil {
return builderOpts , runnerOpts , errors . Wrap ( err , "Could not push image: " + image + " " + builderOpts . DockerFileName )
2020-02-21 21:00:35 +00:00
}
2020-11-10 17:40:35 +00:00
2020-11-10 17:14:18 +00:00
return builderOpts , runnerOpts , nil
}
2020-02-21 21:00:35 +00:00
2020-11-10 17:14:18 +00:00
func ( cs * LuetCompiler ) genArtifact ( p CompilationSpec , builderOpts , runnerOpts CompilerBackendOptions , concurrency int , keepPermissions bool ) ( Artifact , error ) {
2019-11-10 09:48:07 +00:00
2020-11-10 17:14:18 +00:00
// generate Artifact
2019-11-17 14:45:55 +00:00
var artifact Artifact
2020-11-10 17:14:18 +00:00
var rootfs string
var err error
2020-05-16 19:34:27 +00:00
unpack := p . ImageUnpack ( )
2020-11-10 17:14:18 +00:00
pkgTag := ":package: " + p . GetPackage ( ) . HumanReadableString ( )
2019-11-17 14:45:55 +00:00
2020-05-16 19:34:27 +00:00
// If package_dir was specified in the spec, we want to treat the content of the directory
// as the root of our archive. ImageUnpack is implied to be true. override it
if p . GetPackageDir ( ) != "" {
unpack = true
}
2020-12-06 22:50:51 +00:00
if len ( p . BuildSteps ( ) ) == 0 && len ( p . GetPreBuildSteps ( ) ) == 0 && ! unpack {
fakePackage := p . Rel ( p . GetPackage ( ) . GetFingerPrint ( ) + ".package.tar" )
2020-12-07 16:20:32 +00:00
// We can't generate delta in this case. It implies the package is a virtual, and nothing has to be done really
2020-12-06 22:50:51 +00:00
file , err := os . Create ( fakePackage )
if err != nil {
return nil , errors . Wrap ( err , "Failed creating virtual package" )
}
2020-12-07 16:20:32 +00:00
defer file . Close ( )
tw := tar . NewWriter ( file )
defer tw . Close ( )
2020-12-06 22:50:51 +00:00
artifact := NewPackageArtifact ( fakePackage )
artifact . SetCompressionType ( cs . CompressionType )
return artifact , nil
}
2020-11-10 17:14:18 +00:00
// prepare folder content of the image with the package compiled inside
if err := cs . Backend . ExportImage ( runnerOpts ) ; err != nil {
return nil , errors . Wrap ( err , "Failed exporting image" )
}
2019-11-08 18:57:23 +00:00
2020-11-10 17:14:18 +00:00
if ! cs . Options . KeepImageExport {
defer os . Remove ( runnerOpts . Destination )
2019-11-11 18:19:13 +00:00
}
2019-11-17 14:45:55 +00:00
2020-11-10 17:14:18 +00:00
rootfs , err = ioutil . TempDir ( p . GetOutputPath ( ) , "rootfs" )
if err != nil {
return nil , errors . Wrap ( err , "Could not create tempdir" )
2019-12-02 15:33:59 +00:00
}
2020-11-10 17:14:18 +00:00
defer os . RemoveAll ( rootfs ) // clean up
2019-12-02 15:33:59 +00:00
2020-11-10 17:14:18 +00:00
// TODO: Compression and such
err = cs . Backend . ExtractRootfs ( CompilerBackendOptions {
ImageName : runnerOpts . ImageName ,
SourcePath : runnerOpts . Destination , Destination : rootfs } , keepPermissions )
if err != nil {
return nil , errors . Wrap ( err , "Could not extract rootfs" )
2020-10-06 15:57:57 +00:00
}
2020-05-16 19:34:27 +00:00
2020-10-06 15:57:57 +00:00
if unpack {
2020-11-10 17:14:18 +00:00
// Take content of container as a base for our package files
artifact , err = cs . unpackFs ( rootfs , concurrency , p )
2019-11-17 11:08:13 +00:00
if err != nil {
return nil , errors . Wrap ( err , "Error met while creating package archive" )
}
2019-11-17 14:45:55 +00:00
} else {
2020-11-10 17:14:18 +00:00
// Generate delta between the two images
artifact , err = cs . unpackDelta ( rootfs , concurrency , keepPermissions , p , builderOpts , runnerOpts )
2020-08-05 17:09:45 +00:00
if err != nil {
2020-11-10 17:14:18 +00:00
return nil , errors . Wrap ( err , "Error met while creating package archive" )
2019-11-17 14:45:55 +00:00
}
2019-11-11 18:19:13 +00:00
}
2019-11-17 14:45:55 +00:00
2020-04-13 08:52:41 +00:00
filelist , err := artifact . FileList ( )
if err != nil {
return artifact , errors . Wrap ( err , "Failed getting package list" )
}
artifact . SetFiles ( filelist )
2020-07-12 13:27:50 +00:00
artifact . GetCompileSpec ( ) . GetPackage ( ) . SetBuildTimestamp ( time . Now ( ) . String ( ) )
2019-11-22 20:01:29 +00:00
err = artifact . WriteYaml ( p . GetOutputPath ( ) )
if err != nil {
2020-04-13 08:52:41 +00:00
return artifact , errors . Wrap ( err , "Failed while writing metadata file" )
2019-11-22 20:01:29 +00:00
}
2019-11-26 19:11:51 +00:00
Info ( pkgTag , " :white_check_mark: Done" )
2019-11-11 18:19:13 +00:00
return artifact , nil
}
2019-11-17 11:08:13 +00:00
2020-11-10 17:14:18 +00:00
func ( cs * LuetCompiler ) compileWithImage ( image , buildertaggedImage , packageImage string ,
concurrency int ,
keepPermissions , keepImg bool ,
p CompilationSpec , generateArtifact bool ) ( Artifact , error ) {
2020-12-14 17:41:39 +00:00
if ! generateArtifact {
exists := cs . Backend . ImageExists ( packageImage )
2020-12-14 17:32:32 +00:00
if art , err := LoadArtifactFromYaml ( p ) ; err == nil && exists { // If YAML is correctly loaded, and both images exists, no reason to rebuild.
Debug ( "Artifact reloaded from YAML. Skipping build" )
2020-11-10 17:14:18 +00:00
return art , err
}
2020-12-14 17:41:39 +00:00
available := cs . Backend . ImageAvailable ( packageImage )
if exists || available {
2020-12-14 17:32:32 +00:00
return & PackageArtifact { } , nil
}
2020-11-10 17:14:18 +00:00
}
2020-11-10 17:40:35 +00:00
builderOpts , runnerOpts , err := cs . buildPackageImage ( image , buildertaggedImage , packageImage , concurrency , keepPermissions , p )
2020-11-10 17:14:18 +00:00
if err != nil {
return nil , errors . Wrap ( err , "failed building package image" )
}
2020-11-10 17:40:35 +00:00
if ! keepImg {
defer func ( ) {
// We keep them around, so to not reload them from the tar (which should be the "correct way") and we automatically share the same layers
if err := cs . Backend . RemoveImage ( builderOpts ) ; err != nil {
Warning ( "Could not remove image " , builderOpts . ImageName )
}
if err := cs . Backend . RemoveImage ( runnerOpts ) ; err != nil {
Warning ( "Could not remove image " , runnerOpts . ImageName )
}
} ( )
}
2020-11-10 17:14:18 +00:00
if ! generateArtifact {
return & PackageArtifact { } , nil
}
return cs . genArtifact ( p , builderOpts , runnerOpts , concurrency , keepPermissions )
}
2020-06-06 06:58:18 +00:00
func ( cs * LuetCompiler ) FromDatabase ( db pkg . PackageDatabase , minimum bool , dst string ) ( [ ] CompilationSpec , error ) {
compilerSpecs := NewLuetCompilationspecs ( )
w := db . World ( )
for _ , p := range w {
spec , err := cs . FromPackage ( p )
if err != nil {
return nil , err
}
if dst != "" {
spec . SetOutputPath ( dst )
}
compilerSpecs . Add ( spec )
}
switch minimum {
case true :
return cs . ComputeMinimumCompilableSet ( compilerSpecs . Unique ( ) . All ( ) ... )
default :
return compilerSpecs . Unique ( ) . All ( ) , nil
}
}
// ComputeMinimumCompilableSet strips specs that are eventually compiled by leafs
func ( cs * LuetCompiler ) ComputeMinimumCompilableSet ( p ... CompilationSpec ) ( [ ] CompilationSpec , error ) {
// Generate a set with all the deps of the provided specs
// we will use that set to remove the deps from the list of provided compilation specs
allDependencies := solver . PackagesAssertions { } // Get all packages that will be in deps
result := [ ] CompilationSpec { }
for _ , spec := range p {
ass , err := cs . ComputeDepTree ( spec )
if err != nil {
return result , errors . Wrap ( err , "computin specs deptree" )
}
allDependencies = append ( allDependencies , ass . Drop ( spec . GetPackage ( ) ) ... )
}
for _ , spec := range p {
if found := allDependencies . Search ( spec . GetPackage ( ) . GetFingerPrint ( ) ) ; found == nil {
result = append ( result , spec )
}
}
return result , nil
}
2019-11-15 23:38:07 +00:00
func ( cs * LuetCompiler ) ComputeDepTree ( p CompilationSpec ) ( solver . PackagesAssertions , error ) {
2019-11-11 23:13:03 +00:00
2020-10-25 17:43:35 +00:00
s := solver . NewResolver ( cs . SolverOptions , pkg . NewInMemoryDatabase ( false ) , cs . Database , pkg . NewInMemoryDatabase ( false ) , cs . Options . SolverOptions . Resolver ( ) )
2019-11-29 18:01:49 +00:00
2020-04-04 12:29:08 +00:00
solution , err := s . Install ( pkg . Packages { p . GetPackage ( ) } )
2019-11-15 23:38:07 +00:00
if err != nil {
2020-02-18 17:22:18 +00:00
return nil , errors . Wrap ( err , "While computing a solution for " + p . GetPackage ( ) . HumanReadableString ( ) )
2019-11-15 23:38:07 +00:00
}
2019-11-29 18:01:52 +00:00
2020-04-09 15:07:57 +00:00
dependencies , err := solution . Order ( cs . Database , p . GetPackage ( ) . GetFingerPrint ( ) )
if err != nil {
return nil , errors . Wrap ( err , "While order a solution for " + p . GetPackage ( ) . HumanReadableString ( ) )
}
2019-12-17 18:32:31 +00:00
2019-11-15 23:38:07 +00:00
assertions := solver . PackagesAssertions { }
for _ , assertion := range dependencies { //highly dependent on the order
2019-11-29 18:01:49 +00:00
if assertion . Value {
2019-12-14 14:00:16 +00:00
nthsolution := dependencies . Cut ( assertion . Package )
2019-11-15 23:38:07 +00:00
assertion . Hash = solver . PackageHash {
2020-05-18 17:10:38 +00:00
BuildHash : nthsolution . HashFrom ( assertion . Package ) ,
2019-12-14 14:00:16 +00:00
PackageHash : nthsolution . AssertionHash ( ) ,
2019-11-15 23:38:07 +00:00
}
assertions = append ( assertions , assertion )
}
}
p . SetSourceAssertion ( assertions )
return assertions , nil
}
// Compile is non-parallel
2019-12-30 13:46:45 +00:00
func ( cs * LuetCompiler ) Compile ( keepPermissions bool , p CompilationSpec ) ( Artifact , error ) {
2019-11-15 23:38:07 +00:00
asserts , err := cs . ComputeDepTree ( p )
if err != nil {
panic ( err )
}
p . SetSourceAssertion ( asserts )
2019-12-30 13:46:45 +00:00
return cs . compile ( cs . Concurrency , keepPermissions , p )
2019-11-15 23:38:07 +00:00
}
func ( cs * LuetCompiler ) compile ( concurrency int , keepPermissions bool , p CompilationSpec ) ( Artifact , error ) {
2020-02-18 17:22:18 +00:00
Info ( ":package: Compiling" , p . GetPackage ( ) . HumanReadableString ( ) , ".... :coffee:" )
2019-11-12 07:48:07 +00:00
2019-11-11 23:13:03 +00:00
if len ( p . GetPackage ( ) . GetRequires ( ) ) == 0 && p . GetImage ( ) == "" {
Error ( "Package with no deps and no seed image supplied, bailing out" )
2020-08-19 17:24:46 +00:00
return nil , errors . New ( "Package " + p . GetPackage ( ) . GetFingerPrint ( ) +
" with no deps and no seed image supplied, bailing out" )
2019-11-11 23:13:03 +00:00
}
2019-11-10 09:48:07 +00:00
2020-03-19 21:37:32 +00:00
targetAssertion := p . GetSourceAssertion ( ) . Search ( p . GetPackage ( ) . GetFingerPrint ( ) )
targetPackageHash := cs . ImageRepository + ":" + targetAssertion . Hash . PackageHash
2020-11-13 17:25:44 +00:00
bus . Manager . Publish ( bus . EventPackagePreBuild , struct {
CompileSpec CompilationSpec
Assert solver . PackageAssert
} {
CompileSpec : p ,
Assert : * targetAssertion ,
} )
2019-11-11 18:19:13 +00:00
// - If image is set we just generate a plain dockerfile
// Treat last case (easier) first. The image is provided and we just compute a plain dockerfile with the images listed as above
if p . GetImage ( ) != "" {
2020-10-06 15:57:57 +00:00
return cs . compileWithImage ( p . GetImage ( ) , "" , targetPackageHash , concurrency , keepPermissions , cs . KeepImg , p , true )
2019-11-11 18:19:13 +00:00
}
2019-11-12 16:31:50 +00:00
// - If image is not set, we read a base_image. Then we will build one image from it to kick-off our build based
// on how we compute the resolvable tree.
// This means to recursively build all the build-images needed to reach that tree part.
// - We later on compute an hash used to identify the image, so each similar deptree keeps the same build image.
2019-11-15 23:38:07 +00:00
dependencies := p . GetSourceAssertion ( ) . Drop ( p . GetPackage ( ) ) // at this point we should have a flattened list of deps to build, including all of them (with all constraints propagated already)
departifacts := [ ] Artifact { } // TODO: Return this somehow
2019-11-11 18:19:13 +00:00
var lastHash string
2019-11-14 16:45:21 +00:00
depsN := 0
currentN := 0
2020-10-06 15:57:57 +00:00
packageDeps := ! cs . Options . PackageTargetOnly
2020-02-18 17:22:18 +00:00
if ! cs . Options . NoDeps {
Info ( ":deciduous_tree: Build dependencies for " + p . GetPackage ( ) . HumanReadableString ( ) )
for _ , assertion := range dependencies { //highly dependent on the order
depsN ++
Info ( " :arrow_right_hook:" , assertion . Package . HumanReadableString ( ) , ":leaves:" )
2019-11-15 23:38:07 +00:00
}
2020-02-18 17:22:18 +00:00
for _ , assertion := range dependencies { //highly dependent on the order
currentN ++
2020-10-30 18:15:04 +00:00
pkgTag := fmt . Sprintf ( ":package: %d/%d %s ⤑ :hammer: build %s" , currentN , depsN , p . GetPackage ( ) . HumanReadableString ( ) , assertion . Package . HumanReadableString ( ) )
Info ( pkgTag , " starts" )
2020-02-18 17:22:18 +00:00
compileSpec , err := cs . FromPackage ( assertion . Package )
if err != nil {
return nil , errors . Wrap ( err , "Error while generating compilespec for " + assertion . Package . GetName ( ) )
}
compileSpec . SetOutputPath ( p . GetOutputPath ( ) )
buildImageHash := cs . ImageRepository + ":" + assertion . Hash . BuildHash
currentPackageImageHash := cs . ImageRepository + ":" + assertion . Hash . PackageHash
Debug ( pkgTag , " :arrow_right_hook: :whale: Builder image from" , buildImageHash )
Debug ( pkgTag , " :arrow_right_hook: :whale: Package image name" , currentPackageImageHash )
2020-11-13 17:25:44 +00:00
bus . Manager . Publish ( bus . EventPackagePreBuild , struct {
CompileSpec CompilationSpec
Assert solver . PackageAssert
} {
CompileSpec : compileSpec ,
Assert : assertion ,
} )
2020-02-18 17:22:18 +00:00
lastHash = currentPackageImageHash
if compileSpec . GetImage ( ) != "" {
Debug ( pkgTag , " :wrench: Compiling " + compileSpec . GetPackage ( ) . HumanReadableString ( ) + " from image" )
2020-10-06 15:57:57 +00:00
artifact , err := cs . compileWithImage ( compileSpec . GetImage ( ) , buildImageHash , currentPackageImageHash , concurrency , keepPermissions , cs . KeepImg , compileSpec , packageDeps )
2019-11-11 18:19:13 +00:00
if err != nil {
2020-02-18 17:22:18 +00:00
return nil , errors . Wrap ( err , "Failed compiling " + compileSpec . GetPackage ( ) . HumanReadableString ( ) )
2019-11-11 18:19:13 +00:00
}
departifacts = append ( departifacts , artifact )
2020-02-18 17:22:18 +00:00
Info ( pkgTag , ":white_check_mark: Done" )
2019-11-11 18:19:13 +00:00
continue
}
2020-02-18 17:22:18 +00:00
Debug ( pkgTag , " :wrench: Compiling " + compileSpec . GetPackage ( ) . HumanReadableString ( ) + " from tree" )
2020-10-06 15:57:57 +00:00
artifact , err := cs . compileWithImage ( buildImageHash , "" , currentPackageImageHash , concurrency , keepPermissions , cs . KeepImg , compileSpec , packageDeps )
2019-11-11 18:19:13 +00:00
if err != nil {
2020-02-18 17:22:18 +00:00
return nil , errors . Wrap ( err , "Failed compiling " + compileSpec . GetPackage ( ) . HumanReadableString ( ) )
// deperrs = append(deperrs, err)
// break // stop at first error
2019-11-11 18:19:13 +00:00
}
2020-11-13 17:25:44 +00:00
bus . Manager . Publish ( bus . EventPackagePostBuild , struct {
CompileSpec CompilationSpec
Artifact Artifact
} {
CompileSpec : compileSpec ,
Artifact : artifact ,
} )
2019-11-11 18:19:13 +00:00
departifacts = append ( departifacts , artifact )
2020-10-30 18:15:04 +00:00
Info ( pkgTag , ":white_check_mark: Done" )
2019-11-15 23:38:07 +00:00
}
2020-02-18 17:22:18 +00:00
} else if len ( dependencies ) > 0 {
2020-02-26 18:00:22 +00:00
lastHash = cs . ImageRepository + ":" + dependencies [ len ( dependencies ) - 1 ] . Hash . PackageHash
2020-02-18 17:22:18 +00:00
}
if ! cs . Options . OnlyDeps {
2020-10-30 18:15:04 +00:00
Info ( ":rocket: All dependencies are satisfied, building package requested by the user" , p . GetPackage ( ) . HumanReadableString ( ) )
Info ( ":package:" , p . GetPackage ( ) . HumanReadableString ( ) , " Using image: " , lastHash )
2020-10-06 15:57:57 +00:00
artifact , err := cs . compileWithImage ( lastHash , "" , targetPackageHash , concurrency , keepPermissions , cs . KeepImg , p , true )
2019-11-15 23:38:07 +00:00
if err != nil {
2020-02-18 17:22:18 +00:00
return artifact , err
2019-11-11 18:19:13 +00:00
}
2020-02-18 17:22:18 +00:00
artifact . SetDependencies ( departifacts )
artifact . SetSourceAssertion ( p . GetSourceAssertion ( ) )
2019-11-26 19:11:27 +00:00
2020-11-13 17:25:44 +00:00
bus . Manager . Publish ( bus . EventPackagePostBuild , struct {
CompileSpec CompilationSpec
Artifact Artifact
} {
CompileSpec : p ,
Artifact : artifact ,
} )
2019-11-15 17:11:26 +00:00
return artifact , err
2020-02-18 17:22:18 +00:00
} else {
return departifacts [ len ( departifacts ) - 1 ] , nil
2019-11-15 17:11:26 +00:00
}
2019-11-04 16:16:13 +00:00
}
2020-10-04 17:16:01 +00:00
type templatedata map [ string ] interface { }
2019-11-04 16:16:13 +00:00
func ( cs * LuetCompiler ) FromPackage ( p pkg . Package ) ( CompilationSpec , error ) {
2019-12-01 18:11:19 +00:00
pack , err := cs . Database . FindPackageCandidate ( p )
2019-11-04 16:16:13 +00:00
if err != nil {
return nil , err
}
2019-11-10 09:48:07 +00:00
2020-11-14 23:13:46 +00:00
var dataresult [ ] byte
val := pack . Rel ( DefinitionFile )
if _ , err := os . Stat ( pack . Rel ( CollectionFile ) ) ; err == nil {
val = pack . Rel ( CollectionFile )
data , err := ioutil . ReadFile ( val )
if err != nil {
return nil , errors . Wrap ( err , "rendering file " + val )
}
dataBuild , err := ioutil . ReadFile ( pack . Rel ( BuildFile ) )
if err != nil {
return nil , errors . Wrap ( err , "rendering file " + val )
}
packsRaw , err := pkg . GetRawPackages ( data )
2020-11-15 10:47:32 +00:00
raw := packsRaw . Find ( pack . GetName ( ) , pack . GetCategory ( ) , pack . GetVersion ( ) )
2020-11-14 23:13:46 +00:00
2020-11-27 21:22:20 +00:00
d := map [ string ] interface { } { }
if len ( cs . Options . BuildValuesFile ) > 0 {
defBuild , err := ioutil . ReadFile ( cs . Options . BuildValuesFile )
if err != nil {
return nil , errors . Wrap ( err , "rendering file " + val )
}
err = yaml . Unmarshal ( defBuild , & d )
if err != nil {
return nil , errors . Wrap ( err , "rendering file " + val )
}
}
dat , err := helpers . RenderHelm ( string ( dataBuild ) , raw , d )
2020-11-14 23:13:46 +00:00
if err != nil {
return nil , errors . Wrap ( err , "rendering file " + pack . Rel ( BuildFile ) )
}
dataresult = [ ] byte ( dat )
} else {
2020-11-27 21:22:20 +00:00
out , err := helpers . RenderFiles ( pack . Rel ( BuildFile ) , val , cs . Options . BuildValuesFile )
2020-11-14 23:13:46 +00:00
if err != nil {
return nil , errors . Wrap ( err , "rendering file " + pack . Rel ( BuildFile ) )
}
dataresult = [ ] byte ( out )
2020-10-04 17:16:01 +00:00
}
2019-11-04 16:16:13 +00:00
2020-11-14 23:13:46 +00:00
return NewLuetCompilationSpec ( dataresult , pack )
2019-11-04 16:16:13 +00:00
}
2019-11-04 16:20:00 +00:00
func ( cs * LuetCompiler ) GetBackend ( ) CompilerBackend {
return cs . Backend
}
func ( cs * LuetCompiler ) SetBackend ( b CompilerBackend ) {
cs . Backend = b
}