2021-04-07 13:54:38 +00:00
// Copyright © 2019-2021 Ettore Di Giacinto <mudler@sabayon.org>
2019-11-04 16:16:13 +00:00
//
// 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 (
2021-05-21 09:01:27 +00:00
"crypto/md5"
2019-11-14 16:45:21 +00:00
"fmt"
2021-05-21 09:01:27 +00:00
"io"
2019-11-04 16:16:13 +00:00
"io/ioutil"
2019-11-10 09:48:07 +00:00
"os"
2021-04-12 17:00:36 +00:00
"path"
2019-11-10 09:48:07 +00:00
"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
2021-10-24 15:43:33 +00:00
bus "github.com/mudler/luet/pkg/api/core/bus"
2021-12-17 14:21:03 +00:00
"github.com/mudler/luet/pkg/api/core/context"
2021-10-23 16:44:48 +00:00
"github.com/mudler/luet/pkg/api/core/image"
2022-01-27 15:37:42 +00:00
"github.com/mudler/luet/pkg/api/core/template"
2022-01-06 22:57:56 +00:00
"github.com/mudler/luet/pkg/api/core/types"
2021-10-19 15:06:48 +00:00
artifact "github.com/mudler/luet/pkg/api/core/types/artifact"
2021-04-12 17:00:36 +00:00
"github.com/mudler/luet/pkg/compiler/backend"
"github.com/mudler/luet/pkg/compiler/types/options"
compilerspec "github.com/mudler/luet/pkg/compiler/types/spec"
2022-01-06 22:57:56 +00:00
pkg "github.com/mudler/luet/pkg/database"
2019-11-04 16:16:13 +00:00
"github.com/mudler/luet/pkg/helpers"
2021-06-01 14:43:31 +00:00
fileHelper "github.com/mudler/luet/pkg/helpers/file"
2019-11-11 18:19:13 +00:00
"github.com/mudler/luet/pkg/solver"
2021-10-19 20:26:23 +00:00
"github.com/imdario/mergo"
2019-11-11 09:22:55 +00:00
"github.com/pkg/errors"
2021-04-14 14:49:43 +00:00
"gopkg.in/yaml.v2"
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
2021-04-12 17:00:36 +00:00
type ArtifactIndex [ ] * artifact . PackageArtifact
func ( i ArtifactIndex ) CleanPath ( ) ArtifactIndex {
newIndex := ArtifactIndex { }
for _ , art := range i {
2021-04-14 14:49:43 +00:00
copy := art . ShallowCopy ( )
copy . Path = path . Base ( art . Path )
newIndex = append ( newIndex , copy )
2021-04-12 17:00:36 +00:00
}
return newIndex
}
2019-11-04 16:16:13 +00:00
type LuetCompiler struct {
2021-04-12 17:00:36 +00:00
//*tree.CompilerRecipe
Backend CompilerBackend
2022-01-06 22:57:56 +00:00
Database types . PackageDatabase
2021-04-12 17:00:36 +00:00
Options options . Compiler
2019-11-04 16:16:13 +00:00
}
2021-04-12 17:00:36 +00:00
func NewCompiler ( p ... options . Option ) * LuetCompiler {
c := options . NewDefaultCompiler ( )
c . Apply ( p ... )
2021-04-07 13:54:38 +00:00
2021-04-12 17:00:36 +00:00
return & LuetCompiler { Options : * c }
2019-11-04 16:16:13 +00:00
}
2021-04-07 13:54:38 +00:00
2022-01-06 22:57:56 +00:00
func NewLuetCompiler ( backend CompilerBackend , db types . PackageDatabase , compilerOpts ... options . Option ) * LuetCompiler {
2021-04-12 17:00:36 +00:00
// The CompilerRecipe will gives us a tree with only build deps listed.
2021-04-07 13:54:38 +00:00
2021-04-12 17:00:36 +00:00
c := NewCompiler ( compilerOpts ... )
2021-10-20 22:13:02 +00:00
if c . Options . Context == nil {
2021-12-17 14:21:03 +00:00
c . Options . Context = context . NewContext ( )
2021-10-20 22:13:02 +00:00
}
2021-04-12 17:00:36 +00:00
// c.Options.BackendType
c . Backend = backend
c . Database = db
2019-12-30 11:53:32 +00:00
2021-04-12 17:00:36 +00:00
return c
2019-12-30 13:52:34 +00:00
}
2021-04-12 17:00:36 +00:00
func ( cs * LuetCompiler ) compilerWorker ( i int , wg * sync . WaitGroup , cspecs chan * compilerspec . LuetCompilationSpec , a * [ ] * artifact . PackageArtifact , m * sync . Mutex , concurrency int , keepPermissions bool , errors chan error ) {
2019-11-11 09:22:55 +00:00
defer wg . Done ( )
for s := range cspecs {
2021-05-25 11:27:50 +00:00
ar , err := cs . compile ( concurrency , keepPermissions , nil , nil , 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
2021-04-07 13:54:38 +00:00
// CompileWithReverseDeps compiles the supplied compilationspecs and their reverse dependencies
2021-04-12 17:00:36 +00:00
func ( cs * LuetCompiler ) CompileWithReverseDeps ( keepPermissions bool , ps * compilerspec . LuetCompilationspecs ) ( [ ] * artifact . PackageArtifact , [ ] error ) {
2019-12-30 13:46:45 +00:00
artifacts , err := cs . CompileParallel ( keepPermissions , ps )
2019-11-15 17:11:26 +00:00
if len ( err ) != 0 {
return artifacts , err
}
2021-10-20 22:13:02 +00:00
cs . Options . Context . Info ( ":ant: Resolving reverse dependencies" )
2021-04-12 17:00:36 +00:00
toCompile := compilerspec . NewLuetCompilationspecs ( )
2019-11-15 17:11:26 +00:00
for _ , a := range artifacts {
2019-11-29 18:01:49 +00:00
2021-04-12 17:00:36 +00:00
revdeps := a . CompileSpec . 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 )
}
}
uniques := toCompile . Unique ( ) . Remove ( ps )
for _ , u := range uniques . All ( ) {
2021-10-20 22:13:02 +00:00
cs . Options . Context . 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
2021-04-07 13:54:38 +00:00
// CompileParallel compiles the supplied compilationspecs in parallel
// to note, no specific heuristic is implemented, and the specs are run in parallel as they are.
2021-04-12 17:00:36 +00:00
func ( cs * LuetCompiler ) CompileParallel ( keepPermissions bool , ps * compilerspec . LuetCompilationspecs ) ( [ ] * artifact . PackageArtifact , [ ] error ) {
all := make ( chan * compilerspec . LuetCompilationSpec )
artifacts := [ ] * artifact . PackageArtifact { }
2019-11-11 09:22:55 +00:00
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 )
2021-04-12 17:00:36 +00:00
for i := 0 ; i < cs . Options . Concurrency ; i ++ {
2019-11-11 09:22:55 +00:00
wg . Add ( 1 )
2021-04-12 17:00:36 +00:00
go cs . compilerWorker ( i , wg , all , & artifacts , mutex , cs . Options . 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-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
2021-08-11 08:06:52 +00:00
break
2019-12-03 16:26:53 +00:00
}
}
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 )
2021-10-20 22:13:02 +00:00
cs . Options . Context . Debug ( ":scissors: Removing file" , currentpath )
2021-08-11 08:06:52 +00:00
} else {
2021-10-20 22:13:02 +00:00
cs . Options . Context . Debug ( ":sun: Matched file" , currentpath )
2019-12-03 16:26:53 +00:00
}
return nil
}
err := filepath . Walk ( rootfs , ff )
if err != nil {
return err
}
for _ , s := range toRemove {
e := os . RemoveAll ( s )
if e != nil {
2021-10-20 22:13:02 +00:00
cs . Options . Context . Warning ( "Failed removing" , s , e . Error ( ) )
2019-12-03 16:26:53 +00:00
return e
}
}
return nil
}
2021-04-12 17:00:36 +00:00
func ( cs * LuetCompiler ) unpackFs ( concurrency int , keepPermissions bool , p * compilerspec . LuetCompilationSpec , runnerOpts backend . Options ) ( * artifact . PackageArtifact , error ) {
2021-10-25 15:43:53 +00:00
if ! cs . Backend . ImageExists ( runnerOpts . ImageName ) {
if err := cs . Backend . DownloadImage ( runnerOpts ) ; err != nil {
return nil , errors . Wrap ( err , "failed pulling image " + runnerOpts . ImageName + " during extraction" )
}
}
2021-10-26 11:37:39 +00:00
img , err := cs . Backend . ImageReference ( runnerOpts . ImageName , true )
2021-01-24 11:27:07 +00:00
if err != nil {
2021-10-23 20:23:39 +00:00
return nil , err
2021-01-24 11:27:07 +00:00
}
2021-12-17 14:21:03 +00:00
ctx := cs . Options . Context . WithLoggingContext ( fmt . Sprintf ( "extract %s" , runnerOpts . ImageName ) )
2021-10-23 22:57:50 +00:00
_ , rootfs , err := image . Extract (
2021-12-17 14:21:03 +00:00
ctx ,
2021-10-23 20:23:39 +00:00
img ,
image . ExtractFiles (
cs . Options . Context ,
2021-10-23 21:12:04 +00:00
p . GetPackageDir ( ) ,
2021-10-23 20:23:39 +00:00
p . GetIncludes ( ) ,
p . GetExcludes ( ) ,
) ,
)
2021-01-24 11:27:07 +00:00
if err != nil {
2021-10-23 20:23:39 +00:00
return nil , err
2021-01-24 11:27:07 +00:00
}
2021-10-23 20:23:39 +00:00
defer os . RemoveAll ( rootfs ) // clean up
2021-01-24 11:27:07 +00:00
2021-10-23 20:23:39 +00:00
toUnpack := rootfs
2020-11-10 17:14:18 +00:00
2021-10-23 20:23:39 +00:00
if p . PackageDir != "" {
toUnpack = filepath . Join ( toUnpack , p . PackageDir )
2020-11-10 17:14:18 +00:00
}
2021-10-23 20:23:39 +00:00
2021-04-12 17:00:36 +00:00
a := artifact . NewPackageArtifact ( p . Rel ( p . GetPackage ( ) . GetFingerPrint ( ) + ".package.tar" ) )
a . CompressionType = cs . Options . CompressionType
2020-11-10 17:14:18 +00:00
2021-10-23 20:23:39 +00:00
if err := a . Compress ( toUnpack , concurrency ) ; err != nil {
2020-11-10 17:14:18 +00:00
return nil , errors . Wrap ( err , "Error met while creating package archive" )
}
2021-04-12 17:00:36 +00:00
a . CompileSpec = p
return a , nil
2020-11-10 17:14:18 +00:00
}
2021-04-12 17:00:36 +00:00
func ( cs * LuetCompiler ) unpackDelta ( concurrency int , keepPermissions bool , p * compilerspec . LuetCompilationSpec , builderOpts , runnerOpts backend . Options ) ( * artifact . PackageArtifact , error ) {
2021-01-24 11:27:07 +00:00
2021-12-17 14:21:03 +00:00
rootfs , err := cs . Options . Context . TempDir ( "rootfs" )
2021-01-24 11:27:07 +00:00
if err != nil {
return nil , errors . Wrap ( err , "Could not create tempdir" )
}
2021-12-04 20:48:43 +00:00
defer os . RemoveAll ( rootfs )
2021-01-24 11:27:07 +00:00
2020-11-10 17:14:18 +00:00
pkgTag := ":package: " + p . GetPackage ( ) . HumanReadableString ( )
2021-10-25 15:43:53 +00:00
if cs . Options . PullFirst {
if ! cs . Backend . ImageExists ( builderOpts . ImageName ) {
err := cs . Backend . DownloadImage ( builderOpts )
if err != nil {
return nil , errors . Wrap ( err , "Could not pull image" )
}
}
if ! cs . Backend . ImageExists ( runnerOpts . ImageName ) {
err := cs . Backend . DownloadImage ( runnerOpts )
if err != nil {
return nil , errors . Wrap ( err , "Could not pull image" )
}
2020-12-15 16:01:56 +00:00
}
}
2021-01-24 11:27:07 +00:00
2021-10-20 22:13:02 +00:00
cs . Options . Context . Info ( pkgTag , ":hammer: Generating delta" )
2021-10-23 16:44:48 +00:00
2021-10-26 13:44:35 +00:00
cs . Options . Context . Debug ( pkgTag , ":hammer: Retrieving reference for" , builderOpts . ImageName )
2021-10-25 22:46:45 +00:00
2021-10-26 10:14:01 +00:00
ref , err := cs . Backend . ImageReference ( builderOpts . ImageName , true )
2020-11-10 17:14:18 +00:00
if err != nil {
2021-10-23 16:44:48 +00:00
return nil , err
2020-11-10 17:14:18 +00:00
}
2021-01-24 11:41:56 +00:00
2021-10-26 13:44:35 +00:00
cs . Options . Context . Debug ( pkgTag , ":hammer: Retrieving reference for" , runnerOpts . ImageName )
2021-10-25 22:46:45 +00:00
2021-10-26 10:14:01 +00:00
ref2 , err := cs . Backend . ImageReference ( runnerOpts . ImageName , true )
2021-10-23 16:44:48 +00:00
if err != nil {
return nil , err
2021-01-24 11:41:56 +00:00
}
2021-10-23 16:44:48 +00:00
2021-10-26 13:44:35 +00:00
cs . Options . Context . Debug ( pkgTag , ":hammer: Generating filters for extraction" )
2021-10-25 22:46:45 +00:00
filter , err := image . ExtractDeltaAdditionsFiles ( cs . Options . Context , ref , p . GetIncludes ( ) , p . GetExcludes ( ) )
2020-11-10 17:14:18 +00:00
if err != nil {
2021-10-25 22:46:45 +00:00
return nil , errors . Wrap ( err , "failed generating filter for extraction" )
2020-11-10 17:14:18 +00:00
}
2021-10-25 22:46:45 +00:00
cs . Options . Context . Info ( pkgTag , ":hammer: Extracting artifact from image" , runnerOpts . ImageName )
2021-10-23 16:44:48 +00:00
a , err := artifact . ImageToArtifact (
cs . Options . Context ,
ref2 ,
cs . Options . CompressionType ,
p . Rel ( fmt . Sprintf ( "%s%s" , p . GetPackage ( ) . GetFingerPrint ( ) , ".package.tar" ) ) ,
2021-10-25 22:46:45 +00:00
filter ,
2021-10-23 16:44:48 +00:00
)
if err != nil {
return nil , err
}
a . CompileSpec = p
return a , nil
2020-11-10 17:14:18 +00:00
}
2021-04-22 20:50:50 +00:00
func ( cs * LuetCompiler ) buildPackageImage ( image , buildertaggedImage , packageImage string ,
concurrency int , keepPermissions bool ,
p * compilerspec . LuetCompilationSpec ) ( backend . Options , backend . Options , error ) {
var runnerOpts , builderOpts backend . Options
pkgTag := ":package: " + p . GetPackage ( ) . HumanReadableString ( )
2020-06-23 16:44:29 +00:00
// TODO: Cleanup, not actually hit
2020-06-03 19:00:30 +00:00
if packageImage == "" {
2021-04-07 13:54:38 +00:00
return runnerOpts , builderOpts , errors . New ( "no package image given" )
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
2021-12-17 14:21:03 +00:00
buildDir , err := cs . Options . Context . TempDir ( "build" )
2019-11-17 12:27:32 +00:00
if err != nil {
2021-10-24 10:44:14 +00:00
return builderOpts , runnerOpts , err
2019-11-17 12:27:32 +00:00
}
2021-10-24 10:44:14 +00:00
defer os . RemoveAll ( buildDir )
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)
2021-06-01 14:43:31 +00:00
err = fileHelper . 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 {
2021-10-20 22:13:02 +00:00
cs . Options . Context . Warning ( "Failed copying retrieves" , err . Error ( ) )
2020-02-13 13:15:43 +00:00
}
}
2019-11-11 18:19:13 +00:00
// First we create the builder image
2021-12-06 20:41:01 +00:00
if err := p . WriteBuildImageDefinition ( filepath . Join ( buildDir , p . GetPackage ( ) . ImageID ( ) + "-builder.dockerfile" ) ) ; err != nil {
2020-11-10 17:40:35 +00:00
return builderOpts , runnerOpts , errors . Wrap ( err , "Could not generate image definition" )
}
2021-04-22 20:50:50 +00:00
// Even if we don't have prelude steps, we want to push
// An intermediate image to tag images which are outside of the tree.
// Those don't have an hash otherwise, and thus makes build unreproducible
// see SKIPBUILD for the other logic
// if len(p.GetPreBuildSteps()) == 0 {
// buildertaggedImage = image
// }
// We might want to skip this phase but replacing with a tag that we push. But in case
// steps in prelude are == 0 those are equivalent.
2020-12-07 17:58:14 +00:00
2020-11-10 17:14:18 +00:00
// Then we write the step image, which uses the builder one
2021-12-06 20:41:01 +00:00
if err := p . WriteStepImageDefinition ( buildertaggedImage , filepath . Join ( buildDir , p . GetPackage ( ) . ImageID ( ) + ".dockerfile" ) ) ; err != nil {
2020-11-10 17:40:35 +00:00
return builderOpts , runnerOpts , errors . Wrap ( err , "Could not generate image definition" )
}
2020-11-10 17:14:18 +00:00
2021-04-12 17:00:36 +00:00
builderOpts = backend . Options {
2019-11-11 18:19:13 +00:00
ImageName : buildertaggedImage ,
SourcePath : buildDir ,
2021-12-06 20:41:01 +00:00
DockerFileName : p . GetPackage ( ) . ImageID ( ) + "-builder.dockerfile" ,
2019-11-11 18:19:13 +00:00
Destination : p . Rel ( p . GetPackage ( ) . GetFingerPrint ( ) + "-builder.image.tar" ) ,
2021-04-12 17:00:36 +00:00
BackendArgs : cs . Options . BackendArgs ,
2019-11-11 18:19:13 +00:00
}
2021-04-12 17:00:36 +00:00
runnerOpts = backend . Options {
2020-11-10 17:14:18 +00:00
ImageName : packageImage ,
SourcePath : buildDir ,
2021-12-06 20:41:01 +00:00
DockerFileName : p . GetPackage ( ) . ImageID ( ) + ".dockerfile" ,
2020-11-10 17:14:18 +00:00
Destination : p . Rel ( p . GetPackage ( ) . GetFingerPrint ( ) + ".image.tar" ) ,
2021-04-12 17:00:36 +00:00
BackendArgs : cs . Options . BackendArgs ,
2020-11-10 17:14:18 +00:00
}
2019-11-10 09:48:07 +00:00
2021-04-12 17:00:36 +00:00
buildAndPush := func ( opts backend . Options ) error {
2020-11-10 17:40:35 +00:00
buildImage := true
if cs . Options . PullFirst {
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 {
2021-10-20 22:13:02 +00:00
cs . Options . Context . Warning ( "Failed to download '" + opts . ImageName + "'. Will keep going and build the image unless you use --fatal" )
cs . Options . Context . Warning ( err . Error ( ) )
2020-11-10 17:14:18 +00:00
}
2020-02-15 13:45:05 +00:00
}
2020-11-10 17:40:35 +00:00
if buildImage {
if err := cs . Backend . BuildImage ( opts ) ; err != nil {
2021-04-07 13:54:38 +00:00
return errors . Wrapf ( err , "Could not build image: %s %s" , image , opts . DockerFileName )
2020-11-10 17:14:18 +00:00
}
2020-11-10 17:40:35 +00:00
if cs . Options . Push {
if err = cs . Backend . Push ( opts ) ; err != nil {
2021-04-07 13:54:38 +00:00
return errors . Wrapf ( err , "Could not push image: %s %s" , image , opts . DockerFileName )
2020-11-10 17:40:35 +00:00
}
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
}
2021-04-22 20:50:50 +00:00
// SKIPBUILD
// if len(p.GetPreBuildSteps()) != 0 {
2021-10-20 22:13:02 +00:00
cs . Options . Context . Info ( pkgTag , ":whale: Generating 'builder' image from" , image , "as" , buildertaggedImage , "with prelude steps" )
2021-04-22 20:50:50 +00:00
if err := buildAndPush ( builderOpts ) ; err != nil {
return builderOpts , runnerOpts , errors . Wrapf ( err , "Could not push image: %s %s" , image , builderOpts . DockerFileName )
2019-12-02 15:33:59 +00:00
}
2021-04-22 20:50:50 +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.
2021-10-20 22:13:02 +00:00
cs . Options . Context . Info ( pkgTag , ":whale: Generating 'package' image from" , buildertaggedImage , "as" , packageImage , "with build steps" )
2020-12-07 18:39:56 +00:00
if err := buildAndPush ( runnerOpts ) ; err != nil {
2021-04-07 13:54:38 +00:00
return builderOpts , runnerOpts , errors . Wrapf ( err , "Could not push image: %s %s" , image , runnerOpts . 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
2021-04-12 17:00:36 +00:00
func ( cs * LuetCompiler ) genArtifact ( p * compilerspec . LuetCompilationSpec , builderOpts , runnerOpts backend . Options , concurrency int , keepPermissions bool ) ( * artifact . PackageArtifact , error ) {
2019-11-10 09:48:07 +00:00
2021-04-12 17:00:36 +00:00
// generate *artifact.PackageArtifact
var a * artifact . PackageArtifact
2020-11-10 17:14:18 +00:00
var rootfs string
var err error
pkgTag := ":package: " + p . GetPackage ( ) . HumanReadableString ( )
2021-10-20 22:13:02 +00:00
cs . Options . Context . Debug ( pkgTag , "Generating artifact" )
2021-01-03 19:08:04 +00:00
// We can't generate delta in this case. It implies the package is a virtual, and nothing has to be done really
if p . EmptyPackage ( ) {
2020-12-06 22:50:51 +00:00
fakePackage := p . Rel ( p . GetPackage ( ) . GetFingerPrint ( ) + ".package.tar" )
2020-12-07 16:20:32 +00:00
2021-12-17 14:21:03 +00:00
rootfs , err = cs . Options . Context . TempDir ( "rootfs" )
2020-12-06 22:50:51 +00:00
if err != nil {
2021-01-03 19:08:04 +00:00
return nil , errors . Wrap ( err , "Could not create tempdir" )
2020-12-06 22:50:51 +00:00
}
2021-12-04 20:48:43 +00:00
defer os . RemoveAll ( rootfs )
2020-12-06 22:50:51 +00:00
2021-04-12 17:00:36 +00:00
a := artifact . NewPackageArtifact ( fakePackage )
a . CompressionType = cs . Options . CompressionType
2021-01-03 19:08:04 +00:00
2021-04-12 17:00:36 +00:00
if err := a . Compress ( rootfs , concurrency ) ; err != nil {
2021-01-03 19:08:04 +00:00
return nil , errors . Wrap ( err , "Error met while creating package archive" )
}
2021-04-12 17:00:36 +00:00
a . CompileSpec = p
a . CompileSpec . GetPackage ( ) . SetBuildTimestamp ( time . Now ( ) . String ( ) )
2021-09-17 12:46:32 +00:00
err = a . WriteYAML ( p . GetOutputPath ( ) )
2021-01-03 19:08:04 +00:00
if err != nil {
2021-04-12 17:00:36 +00:00
return a , errors . Wrap ( err , "Failed while writing metadata file" )
2021-01-03 19:08:04 +00:00
}
2021-10-22 15:55:38 +00:00
cs . Options . Context . Success ( pkgTag , " :white_check_mark: done (empty virtual package)" )
2022-04-13 16:04:50 +00:00
if err := cs . finalizeImages ( a , p , keepPermissions ) ; err != nil {
return nil , err
2021-11-04 21:31:03 +00:00
}
2022-04-13 16:04:50 +00:00
2021-04-12 17:00:36 +00:00
return a , nil
2020-12-06 22:50:51 +00:00
}
2021-01-03 19:08:04 +00:00
if p . UnpackedPackage ( ) {
2020-11-10 17:14:18 +00:00
// Take content of container as a base for our package files
2021-04-12 17:00:36 +00:00
a , err = cs . unpackFs ( concurrency , keepPermissions , p , runnerOpts )
2019-11-17 11:08:13 +00:00
if err != nil {
2020-12-15 16:01:56 +00:00
return nil , errors . Wrap ( err , "Error met while extracting image" )
2019-11-17 11:08:13 +00:00
}
2019-11-17 14:45:55 +00:00
} else {
2020-11-10 17:14:18 +00:00
// Generate delta between the two images
2021-04-12 17:00:36 +00:00
a , err = cs . unpackDelta ( concurrency , keepPermissions , p , builderOpts , runnerOpts )
2020-08-05 17:09:45 +00:00
if err != nil {
2020-12-15 16:01:56 +00:00
return nil , errors . Wrap ( err , "Error met while generating delta" )
2019-11-17 14:45:55 +00:00
}
2019-11-11 18:19:13 +00:00
}
2019-11-17 14:45:55 +00:00
2022-01-06 21:33:46 +00:00
if ! p . Package . Hidden {
filelist , err := a . FileList ( )
if err != nil {
return a , errors . Wrapf ( err , "Failed getting package list for '%s' '%s'" , a . Path , a . CompileSpec . Package . HumanReadableString ( ) )
}
a . Files = filelist
2020-04-13 08:52:41 +00:00
}
2021-04-12 17:00:36 +00:00
a . CompileSpec . GetPackage ( ) . SetBuildTimestamp ( time . Now ( ) . String ( ) )
2020-07-12 13:27:50 +00:00
2021-09-17 12:46:32 +00:00
err = a . WriteYAML ( p . GetOutputPath ( ) )
2019-11-22 20:01:29 +00:00
if err != nil {
2021-04-12 17:00:36 +00:00
return a , errors . Wrap ( err , "Failed while writing metadata file" )
2019-11-22 20:01:29 +00:00
}
2021-11-04 21:31:03 +00:00
cs . Options . Context . Success ( pkgTag , " :white_check_mark: Done building" )
2022-04-13 16:04:50 +00:00
if err := cs . finalizeImages ( a , p , keepPermissions ) ; err != nil {
return nil , err
2021-11-04 21:31:03 +00:00
}
2019-11-26 19:11:51 +00:00
2021-04-12 17:00:36 +00:00
return a , nil
2019-11-11 18:19:13 +00:00
}
2019-11-17 11:08:13 +00:00
2022-04-13 16:04:50 +00:00
// finalizeImages finalizes images and generates final artifacts (push them as well if necessary).
func ( cs * LuetCompiler ) finalizeImages ( a * artifact . PackageArtifact , p * compilerspec . LuetCompilationSpec , keepPermissions bool ) error {
// TODO: This is a small readaptation of repository_docker.go pushImageFromArtifact().
// Maybe can be moved to a common place.
// We either check if finalization is needed
// and push or generate final images here, anything else we just return successfully
if ! cs . Options . PushFinalImages && ! cs . Options . GenerateFinalImages {
return nil
}
2021-11-04 21:31:03 +00:00
imageID := fmt . Sprintf ( "%s:%s" , cs . Options . PushFinalImagesRepository , a . CompileSpec . Package . ImageID ( ) )
2022-04-13 16:04:50 +00:00
metadataImageID := fmt . Sprintf ( "%s:%s" , cs . Options . PushFinalImagesRepository , helpers . SanitizeImageString ( a . CompileSpec . GetPackage ( ) . GetMetadataFilePath ( ) ) )
// Do generate image only, might be required for local iteration without pushing to remote repository
if cs . Options . GenerateFinalImages && ! cs . Options . PushFinalImages {
cs . Options . Context . Info ( "Generating final image for" , a . CompileSpec . Package . HumanReadableString ( ) )
if err := a . GenerateFinalImage ( cs . Options . Context , imageID , cs . GetBackend ( ) , true ) ; err != nil {
return errors . Wrap ( err , "while creating final image" )
}
a := artifact . NewPackageArtifact ( filepath . Join ( p . GetOutputPath ( ) , a . CompileSpec . GetPackage ( ) . GetMetadataFilePath ( ) ) )
metadataArchive , err := artifact . CreateArtifactForFile ( cs . Options . Context , a . Path )
if err != nil {
return errors . Wrap ( err , "failed generating checksums for tree" )
}
if err := metadataArchive . GenerateFinalImage ( cs . Options . Context , metadataImageID , cs . Backend , keepPermissions ) ; err != nil {
return errors . Wrap ( err , "Failed generating metadata tree " + metadataImageID )
}
return nil
}
cs . Options . Context . Info ( "Pushing final image for" , a . CompileSpec . Package . HumanReadableString ( ) )
2021-11-04 21:31:03 +00:00
// First push the package image
if ! cs . Backend . ImageAvailable ( imageID ) || cs . Options . PushFinalImagesForce {
cs . Options . Context . Info ( "Generating and pushing final image for" , a . CompileSpec . Package . HumanReadableString ( ) , "as" , imageID )
if err := a . GenerateFinalImage ( cs . Options . Context , imageID , cs . GetBackend ( ) , true ) ; err != nil {
return errors . Wrap ( err , "while creating final image" )
}
if err := cs . Backend . Push ( backend . Options { ImageName : imageID } ) ; err != nil {
return errors . Wrapf ( err , "Could not push image: %s" , imageID )
}
}
// Then the image ID
if ! cs . Backend . ImageAvailable ( metadataImageID ) || cs . Options . PushFinalImagesForce {
cs . Options . Context . Info ( "Generating metadata image for" , a . CompileSpec . Package . HumanReadableString ( ) , metadataImageID )
a := artifact . NewPackageArtifact ( filepath . Join ( p . GetOutputPath ( ) , a . CompileSpec . GetPackage ( ) . GetMetadataFilePath ( ) ) )
metadataArchive , err := artifact . CreateArtifactForFile ( cs . Options . Context , a . Path )
if err != nil {
return errors . Wrap ( err , "failed generating checksums for tree" )
}
if err := metadataArchive . GenerateFinalImage ( cs . Options . Context , metadataImageID , cs . Backend , keepPermissions ) ; err != nil {
return errors . Wrap ( err , "Failed generating metadata tree " + metadataImageID )
}
if err = cs . Backend . Push ( backend . Options { ImageName : metadataImageID } ) ; err != nil {
return errors . Wrapf ( err , "Could not push image: %s" , metadataImageID )
}
}
return nil
}
2021-04-07 13:54:38 +00:00
func ( cs * LuetCompiler ) waitForImages ( images [ ] string ) {
if cs . Options . PullFirst && cs . Options . Wait {
available , _ := oneOfImagesAvailable ( images , cs . Backend )
if ! available {
2021-10-20 22:13:02 +00:00
cs . Options . Context . Info ( fmt . Sprintf ( "Waiting for image %s to be available... :zzz:" , images ) )
cs . Options . Context . Spinner ( )
defer cs . Options . Context . SpinnerStop ( )
2021-04-07 13:54:38 +00:00
for ! available {
available , _ = oneOfImagesAvailable ( images , cs . Backend )
2021-10-20 22:13:02 +00:00
cs . Options . Context . Info ( fmt . Sprintf ( "Image %s not available yet, sleeping" , images ) )
2021-04-07 13:54:38 +00:00
time . Sleep ( 5 * time . Second )
}
}
}
}
func oneOfImagesExists ( images [ ] string , b CompilerBackend ) ( bool , string ) {
for _ , i := range images {
if exists := b . ImageExists ( i ) ; exists {
return true , i
}
}
return false , ""
}
func oneOfImagesAvailable ( images [ ] string , b CompilerBackend ) ( bool , string ) {
for _ , i := range images {
if exists := b . ImageAvailable ( i ) ; exists {
return true , i
}
}
return false , ""
}
2021-05-21 09:01:27 +00:00
func ( cs * LuetCompiler ) findImageHash ( imageHash string , p * compilerspec . LuetCompilationSpec ) string {
2021-04-07 13:54:38 +00:00
var resolvedImage string
2021-10-20 22:13:02 +00:00
cs . Options . Context . Debug ( "Resolving image hash for" , p . Package . HumanReadableString ( ) , "hash" , imageHash , "Pull repositories" , p . BuildOptions . PullImageRepository )
2021-04-12 17:00:36 +00:00
toChecklist := append ( [ ] string { fmt . Sprintf ( "%s:%s" , cs . Options . PushImageRepository , imageHash ) } ,
2021-04-21 15:59:56 +00:00
genImageList ( p . BuildOptions . PullImageRepository , imageHash ) ... )
2022-04-13 16:04:50 +00:00
if cs . Options . PushFinalImagesRepository != "" {
toChecklist = append ( toChecklist , fmt . Sprintf ( "%s:%s" , cs . Options . PushFinalImagesRepository , imageHash ) )
}
2021-04-07 13:54:38 +00:00
if exists , which := oneOfImagesExists ( toChecklist , cs . Backend ) ; exists {
resolvedImage = which
}
if cs . Options . PullFirst {
if exists , which := oneOfImagesAvailable ( toChecklist , cs . Backend ) ; exists {
resolvedImage = which
2020-12-18 22:19:18 +00:00
}
}
2021-05-21 09:01:27 +00:00
return resolvedImage
}
func ( cs * LuetCompiler ) resolveExistingImageHash ( imageHash string , p * compilerspec . LuetCompilationSpec ) string {
resolvedImage := cs . findImageHash ( imageHash , p )
2021-04-07 13:54:38 +00:00
if resolvedImage == "" {
2021-04-12 17:00:36 +00:00
resolvedImage = fmt . Sprintf ( "%s:%s" , cs . Options . PushImageRepository , imageHash )
2021-04-07 13:54:38 +00:00
}
return resolvedImage
}
2021-04-12 17:00:36 +00:00
func LoadArtifactFromYaml ( spec * compilerspec . LuetCompilationSpec ) ( * artifact . PackageArtifact , error ) {
2021-04-14 14:49:43 +00:00
metaFile := spec . GetPackage ( ) . GetMetadataFilePath ( )
2021-04-12 17:00:36 +00:00
dat , err := ioutil . ReadFile ( spec . Rel ( metaFile ) )
if err != nil {
return nil , errors . Wrap ( err , "Error reading file " + metaFile )
}
art , err := artifact . NewPackageArtifactFromYaml ( dat )
if err != nil {
return nil , errors . Wrap ( err , "Error writing file " + metaFile )
}
// It is relative, set it back to abs
art . Path = spec . Rel ( art . Path )
return art , nil
}
func ( cs * LuetCompiler ) getImageArtifact ( hash string , p * compilerspec . LuetCompilationSpec ) ( * artifact . PackageArtifact , error ) {
2021-04-07 13:54:38 +00:00
// we check if there is an available image with the given hash and
// we return a full artifact if can be loaded locally.
2021-10-20 22:13:02 +00:00
cs . Options . Context . Debug ( "Get image artifact for" , p . Package . HumanReadableString ( ) , "hash" , hash , "Pull repositories" , p . BuildOptions . PullImageRepository )
2021-04-07 13:54:38 +00:00
2021-04-12 17:00:36 +00:00
toChecklist := append ( [ ] string { fmt . Sprintf ( "%s:%s" , cs . Options . PushImageRepository , hash ) } ,
2021-04-21 15:59:56 +00:00
genImageList ( p . BuildOptions . PullImageRepository , hash ) ... )
2021-04-07 13:54:38 +00:00
exists , _ := oneOfImagesExists ( toChecklist , cs . Backend )
if art , err := LoadArtifactFromYaml ( p ) ; err == nil && exists { // If YAML is correctly loaded, and both images exists, no reason to rebuild.
2021-10-20 22:13:02 +00:00
cs . Options . Context . Debug ( "Package reloaded from YAML. Skipping build" )
2021-04-07 13:54:38 +00:00
return art , nil
}
cs . waitForImages ( toChecklist )
available , _ := oneOfImagesAvailable ( toChecklist , cs . Backend )
if exists || ( cs . Options . PullFirst && available ) {
2021-10-20 22:13:02 +00:00
cs . Options . Context . Debug ( "Image available, returning empty artifact" )
2021-04-12 17:00:36 +00:00
return & artifact . PackageArtifact { } , nil
2021-04-07 13:54:38 +00:00
}
return nil , errors . New ( "artifact not found" )
2020-12-18 22:19:18 +00:00
}
2021-04-07 13:54:38 +00:00
// compileWithImage compiles a PackageTagHash image using the image source, and tagging an indermediate
// image buildertaggedImage.
// Images that can be resolved from repositories are prefered over the local ones if PullFirst is set to true
// avoiding to rebuild images as much as possible
2021-04-22 20:50:50 +00:00
func ( cs * LuetCompiler ) compileWithImage ( image , builderHash string , packageTagHash string ,
2020-11-10 17:14:18 +00:00
concurrency int ,
keepPermissions , keepImg bool ,
2021-04-12 17:00:36 +00:00
p * compilerspec . LuetCompilationSpec , generateArtifact bool ) ( * artifact . PackageArtifact , error ) {
2020-11-10 17:14:18 +00:00
2021-01-03 19:08:04 +00:00
// If it is a virtual, check if we have to generate an empty artifact or not.
2021-02-09 18:05:16 +00:00
if generateArtifact && p . IsVirtual ( ) {
2021-04-12 17:00:36 +00:00
return cs . genArtifact ( p , backend . Options { } , backend . Options { } , concurrency , keepPermissions )
2021-02-09 18:05:16 +00:00
} else if p . IsVirtual ( ) {
2021-04-12 17:00:36 +00:00
return & artifact . PackageArtifact { } , nil
2021-01-03 19:08:04 +00:00
}
2020-12-14 17:41:39 +00:00
if ! generateArtifact {
2021-04-07 13:54:38 +00:00
if art , err := cs . getImageArtifact ( packageTagHash , p ) ; err == nil {
2021-04-22 20:50:50 +00:00
// try to avoid regenerating the image if possible by checking the hash in the
// given repositories
// It is best effort. If we fail resolving, we will generate the images and keep going
2021-04-07 13:54:38 +00:00
return art , nil
2020-12-14 17:32:32 +00:00
}
2020-11-10 17:14:18 +00:00
}
2021-04-12 17:00:36 +00:00
packageImage := fmt . Sprintf ( "%s:%s" , cs . Options . PushImageRepository , packageTagHash )
2021-04-22 22:53:40 +00:00
remoteBuildertaggedImage := fmt . Sprintf ( "%s:%s" , cs . Options . PushImageRepository , builderHash )
builderResolved := cs . resolveExistingImageHash ( builderHash , p )
2021-04-22 20:50:50 +00:00
//generated := false
// if buildertaggedImage == "" {
// buildertaggedImage = fmt.Sprintf("%s:%s", cs.Options.PushImageRepository, buildertaggedImage)
// generated = true
2021-10-20 22:13:02 +00:00
// // cs.Options.Context.Debug(pkgTag, "Creating intermediary image", buildertaggedImage, "from", image)
2021-04-22 20:50:50 +00:00
// }
2021-04-22 22:53:40 +00:00
if cs . Options . PullFirst && ! cs . Options . Rebuild {
2021-10-20 22:13:02 +00:00
cs . Options . Context . Debug ( "Checking if an image is already available" )
2021-04-22 20:50:50 +00:00
// FIXUP here. If packageimage hash exists and pull is true, generate package
resolved := cs . resolveExistingImageHash ( packageTagHash , p )
2021-10-20 22:13:02 +00:00
cs . Options . Context . Debug ( "Resolved: " + resolved )
cs . Options . Context . Debug ( "Expected remote: " + resolved )
cs . Options . Context . Debug ( "Package image: " + packageImage )
cs . Options . Context . Debug ( "Resolved builder image: " + builderResolved )
2021-05-20 08:29:41 +00:00
// a remote image is there already
remoteImageAvailable := resolved != packageImage && remoteBuildertaggedImage != builderResolved
// or a local one is available
localImageAvailable := cs . Backend . ImageExists ( remoteBuildertaggedImage ) && cs . Backend . ImageExists ( packageImage )
switch {
case remoteImageAvailable :
2021-10-20 22:13:02 +00:00
cs . Options . Context . Debug ( "Images available remotely for" , p . Package . HumanReadableString ( ) , "generating artifact from remote images:" , resolved )
2021-04-22 20:50:50 +00:00
return cs . genArtifact ( p , backend . Options { ImageName : builderResolved } , backend . Options { ImageName : resolved } , concurrency , keepPermissions )
2021-05-20 08:29:41 +00:00
case localImageAvailable :
2021-10-20 22:13:02 +00:00
cs . Options . Context . Debug ( "Images locally available for" , p . Package . HumanReadableString ( ) , "generating artifact from image:" , resolved )
2021-05-20 08:29:41 +00:00
return cs . genArtifact ( p , backend . Options { ImageName : remoteBuildertaggedImage } , backend . Options { ImageName : packageImage } , concurrency , keepPermissions )
default :
2021-10-20 22:13:02 +00:00
cs . Options . Context . Debug ( "Images not available for" , p . Package . HumanReadableString ( ) )
2021-04-22 20:50:50 +00:00
}
}
// always going to point at the destination from the repo defined
2021-04-22 22:53:40 +00:00
builderOpts , runnerOpts , err := cs . buildPackageImage ( image , builderResolved , 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 {
2021-10-20 22:13:02 +00:00
cs . Options . Context . Warning ( "Could not remove image " , builderOpts . ImageName )
2020-11-10 17:40:35 +00:00
}
if err := cs . Backend . RemoveImage ( runnerOpts ) ; err != nil {
2021-10-20 22:13:02 +00:00
cs . Options . Context . Warning ( "Could not remove image " , runnerOpts . ImageName )
2020-11-10 17:40:35 +00:00
}
} ( )
}
2020-11-10 17:14:18 +00:00
if ! generateArtifact {
2021-04-12 17:00:36 +00:00
return & artifact . PackageArtifact { } , nil
2020-11-10 17:14:18 +00:00
}
return cs . genArtifact ( p , builderOpts , runnerOpts , concurrency , keepPermissions )
}
2021-04-07 13:54:38 +00:00
// FromDatabase returns all the available compilation specs from a database. If the minimum flag is returned
// it will be computed a minimal subset that will guarantees that all packages are built ( if not targeting a single package explictly )
2022-01-06 22:57:56 +00:00
func ( cs * LuetCompiler ) FromDatabase ( db types . PackageDatabase , minimum bool , dst string ) ( [ ] * compilerspec . LuetCompilationSpec , error ) {
2021-04-12 17:00:36 +00:00
compilerSpecs := compilerspec . NewLuetCompilationspecs ( )
2020-06-06 06:58:18 +00:00
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
}
}
2022-01-06 22:57:56 +00:00
func ( cs * LuetCompiler ) ComputeDepTree ( p * compilerspec . LuetCompilationSpec ) ( types . PackagesAssertions , error ) {
s := solver . NewResolver ( cs . Options . SolverOptions . SolverOptions , pkg . NewInMemoryDatabase ( false ) , cs . Database , pkg . NewInMemoryDatabase ( false ) , solver . NewSolverFromOptions ( cs . Options . SolverOptions ) )
2021-05-11 07:46:54 +00:00
2022-01-06 22:57:56 +00:00
solution , err := s . Install ( types . Packages { p . GetPackage ( ) } )
2021-05-11 07:46:54 +00:00
if err != nil {
return nil , errors . Wrap ( err , "While computing a solution for " + p . GetPackage ( ) . HumanReadableString ( ) )
}
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 ( ) )
}
return dependencies , nil
}
2021-10-15 19:14:48 +00:00
// BuildTree returns a BuildTree which represent the order in which specs should be compiled.
// It places specs into levels, and each level can be built in parallel. The root nodes starting from the top.
// A BuildTree can be marshaled into JSON or walked like:
// for _, l := range bt.AllLevels() {
// fmt.Println(strings.Join(bt.AllInLevel(l), " "))
// }
func ( cs * LuetCompiler ) BuildTree ( compilerSpecs compilerspec . LuetCompilationspecs ) ( * BuildTree , error ) {
compilationTree := map [ string ] map [ string ] interface { } { }
bt := & BuildTree { }
for _ , sp := range compilerSpecs . All ( ) {
ass , err := cs . ComputeDepTree ( sp )
if err != nil {
return nil , err
}
bt . Reset ( fmt . Sprintf ( "%s/%s" , sp . GetPackage ( ) . GetCategory ( ) , sp . GetPackage ( ) . GetName ( ) ) )
for _ , p := range ass {
bt . Reset ( fmt . Sprintf ( "%s/%s" , p . Package . GetCategory ( ) , p . Package . GetName ( ) ) )
spec , err := cs . FromPackage ( p . Package )
if err != nil {
return nil , err
}
ass , err := cs . ComputeDepTree ( spec )
if err != nil {
return nil , err
}
for _ , r := range ass {
if compilationTree [ fmt . Sprintf ( "%s/%s" , p . Package . GetCategory ( ) , p . Package . GetName ( ) ) ] == nil {
compilationTree [ fmt . Sprintf ( "%s/%s" , p . Package . GetCategory ( ) , p . Package . GetName ( ) ) ] = make ( map [ string ] interface { } )
}
compilationTree [ fmt . Sprintf ( "%s/%s" , p . Package . GetCategory ( ) , p . Package . GetName ( ) ) ] [ fmt . Sprintf ( "%s/%s" , r . Package . GetCategory ( ) , r . Package . GetName ( ) ) ] = nil
}
if compilationTree [ fmt . Sprintf ( "%s/%s" , sp . GetPackage ( ) . GetCategory ( ) , sp . GetPackage ( ) . GetName ( ) ) ] == nil {
compilationTree [ fmt . Sprintf ( "%s/%s" , sp . GetPackage ( ) . GetCategory ( ) , sp . GetPackage ( ) . GetName ( ) ) ] = make ( map [ string ] interface { } )
}
compilationTree [ fmt . Sprintf ( "%s/%s" , sp . GetPackage ( ) . GetCategory ( ) , sp . GetPackage ( ) . GetName ( ) ) ] [ fmt . Sprintf ( "%s/%s" , p . Package . GetCategory ( ) , p . Package . GetName ( ) ) ] = nil
}
}
bt . Order ( compilationTree )
return bt , nil
}
2020-06-06 06:58:18 +00:00
// ComputeMinimumCompilableSet strips specs that are eventually compiled by leafs
2021-04-12 17:00:36 +00:00
func ( cs * LuetCompiler ) ComputeMinimumCompilableSet ( p ... * compilerspec . LuetCompilationSpec ) ( [ ] * compilerspec . LuetCompilationSpec , error ) {
2020-06-06 06:58:18 +00:00
// 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
2022-01-06 22:57:56 +00:00
allDependencies := types . PackagesAssertions { } // Get all packages that will be in deps
2021-04-12 17:00:36 +00:00
result := [ ] * compilerspec . LuetCompilationSpec { }
2020-06-06 06:58:18 +00:00
for _ , spec := range p {
2021-05-11 07:46:54 +00:00
sol , err := cs . ComputeDepTree ( spec )
2020-06-06 06:58:18 +00:00
if err != nil {
2021-05-11 07:46:54 +00:00
return nil , errors . Wrap ( err , "failed querying hashtree" )
2020-06-06 06:58:18 +00:00
}
2021-05-11 07:46:54 +00:00
allDependencies = append ( allDependencies , sol . Drop ( spec . GetPackage ( ) ) ... )
2020-06-06 06:58:18 +00:00
}
for _ , spec := range p {
if found := allDependencies . Search ( spec . GetPackage ( ) . GetFingerPrint ( ) ) ; found == nil {
result = append ( result , spec )
}
}
return result , nil
}
2021-04-07 13:54:38 +00:00
// Compile is a non-parallel version of CompileParallel. It builds the compilation specs and generates
// an artifact
2021-04-12 17:00:36 +00:00
func ( cs * LuetCompiler ) Compile ( keepPermissions bool , p * compilerspec . LuetCompilationSpec ) ( * artifact . PackageArtifact , error ) {
2021-05-25 11:27:50 +00:00
return cs . compile ( cs . Options . Concurrency , keepPermissions , nil , nil , p )
2019-11-15 23:38:07 +00:00
}
2021-04-07 13:54:38 +00:00
func genImageList ( refs [ ] string , hash string ) [ ] string {
var res [ ] string
for _ , r := range refs {
res = append ( res , fmt . Sprintf ( "%s:%s" , r , hash ) )
}
return res
}
2021-04-19 15:11:37 +00:00
func ( cs * LuetCompiler ) inheritSpecBuildOptions ( p * compilerspec . LuetCompilationSpec ) {
2021-10-20 22:13:02 +00:00
cs . Options . Context . Debug ( p . GetPackage ( ) . HumanReadableString ( ) , "Build options before inherit" , p . BuildOptions )
2021-04-21 15:59:56 +00:00
// Append push repositories from buildpsec buildoptions as pull if found.
// This allows to resolve the hash automatically if we pulled the metadata from
// repositories that are advertizing their cache.
2021-04-19 15:11:37 +00:00
if len ( p . BuildOptions . PushImageRepository ) != 0 {
2021-04-21 15:59:56 +00:00
p . BuildOptions . PullImageRepository = append ( p . BuildOptions . PullImageRepository , p . BuildOptions . PushImageRepository )
2021-10-20 22:13:02 +00:00
cs . Options . Context . Debug ( "Inheriting pull repository from PushImageRepository buildoptions" , p . BuildOptions . PullImageRepository )
2021-04-14 14:49:43 +00:00
}
2021-04-21 15:59:56 +00:00
if len ( cs . Options . PullImageRepository ) != 0 {
p . BuildOptions . PullImageRepository = append ( p . BuildOptions . PullImageRepository , cs . Options . PullImageRepository ... )
2021-10-20 22:13:02 +00:00
cs . Options . Context . Debug ( "Inheriting pull repository from PullImageRepository buildoptions" , p . BuildOptions . PullImageRepository )
2021-04-21 15:59:56 +00:00
}
2021-08-04 14:16:54 +00:00
2021-10-20 22:13:02 +00:00
cs . Options . Context . Debug ( p . GetPackage ( ) . HumanReadableString ( ) , "Build options after inherit" , p . BuildOptions )
2021-04-19 15:11:37 +00:00
}
2022-01-06 22:57:56 +00:00
func ( cs * LuetCompiler ) getSpecHash ( pkgs types . Packages , salt string ) ( string , error ) {
2021-05-21 09:01:27 +00:00
ht := NewHashTree ( cs . Database )
overallFp := ""
for _ , p := range pkgs {
compileSpec , err := cs . FromPackage ( p )
if err != nil {
return "" , errors . Wrap ( err , "Error while generating compilespec for " + p . GetName ( ) )
}
packageHashTree , err := ht . Query ( cs , compileSpec )
if err != nil {
return "nil" , errors . Wrap ( err , "failed querying hashtree" )
}
overallFp = overallFp + packageHashTree . Target . Hash . PackageHash + p . GetFingerPrint ( )
}
h := md5 . New ( )
io . WriteString ( h , fmt . Sprintf ( "%s-%s" , overallFp , salt ) )
return fmt . Sprintf ( "%x" , h . Sum ( nil ) ) , nil
}
2021-07-09 07:31:37 +00:00
func ( cs * LuetCompiler ) resolveFinalImages ( concurrency int , keepPermissions bool , p * compilerspec . LuetCompilationSpec ) error {
2022-04-13 16:04:50 +00:00
if ! p . RequiresFinalImages {
return nil
}
2021-07-09 07:31:37 +00:00
joinTag := ">:loop: final images<"
2022-04-13 16:04:50 +00:00
2022-01-06 22:57:56 +00:00
var fromPackages types . Packages
2021-07-09 07:31:37 +00:00
2022-04-13 16:04:50 +00:00
cs . Options . Context . Info ( joinTag , "Generating a parent image from final packages" )
//fromPackages = p.Package.GetRequires() // (first level only)
pTarget := p
runtime , err := p . Package . GetRuntimePackage ( )
if err == nil {
spec , err := cs . FromPackage ( runtime )
if err == nil {
cs . Options . Context . Info ( joinTag , "Using runtime package for deptree computation" )
pTarget = spec
}
}
// resolve deptree of runtime of p and use it in fromPackages
t , err := cs . ComputeDepTree ( pTarget )
if err != nil {
return errors . Wrap ( err , "failed querying hashtree" )
}
for _ , a := range t {
if ! a . Value || a . Package . Matches ( p . Package ) {
continue
}
fromPackages = append ( fromPackages , a . Package )
cs . Options . Context . Infof ( "Adding dependency '%s'." , a . Package . HumanReadableString ( ) )
2021-05-21 09:01:27 +00:00
}
// First compute a hash and check if image is available. if it is, then directly consume that
2021-07-09 07:31:37 +00:00
overallFp , err := cs . getSpecHash ( fromPackages , "join" )
2021-05-21 09:01:27 +00:00
if err != nil {
return errors . Wrap ( err , "could not generate image hash" )
}
2021-05-24 17:40:17 +00:00
2021-10-20 22:13:02 +00:00
cs . Options . Context . Info ( joinTag , "Searching existing image with hash" , overallFp )
2021-05-24 17:40:17 +00:00
2022-04-13 16:04:50 +00:00
if img := cs . findImageHash ( overallFp , p ) ; img != "" {
cs . Options . Context . Info ( "Image already found" , img )
p . SetImage ( img )
2021-05-21 09:01:27 +00:00
return nil
}
2021-10-20 22:13:02 +00:00
cs . Options . Context . Info ( joinTag , "Image not found. Generating image join with hash " , overallFp )
2021-05-21 09:01:27 +00:00
2021-05-21 20:25:18 +00:00
// Make sure there is an output path
if err := os . MkdirAll ( p . GetOutputPath ( ) , os . ModePerm ) ; err != nil {
return errors . Wrap ( err , "while creating output path" )
}
2021-05-21 09:01:27 +00:00
// otherwise, generate it and push it aside
2021-12-17 14:21:03 +00:00
joinDir , err := cs . Options . Context . TempDir ( "join" )
2021-05-21 09:01:27 +00:00
if err != nil {
2021-05-21 20:25:18 +00:00
return errors . Wrap ( err , "could not create tempdir for joining images" )
2021-05-21 09:01:27 +00:00
}
2021-12-04 20:48:43 +00:00
defer os . RemoveAll ( joinDir )
2021-05-21 09:01:27 +00:00
2021-07-09 07:31:37 +00:00
for _ , p := range fromPackages {
2021-10-20 22:13:02 +00:00
cs . Options . Context . Info ( joinTag , ":arrow_right_hook:" , p . HumanReadableString ( ) , ":leaves:" )
2021-05-24 17:40:17 +00:00
}
current := 0
2021-07-09 07:31:37 +00:00
for _ , c := range fromPackages {
2021-05-24 17:40:17 +00:00
current ++
2021-05-21 09:01:27 +00:00
if c != nil && c . Name != "" && c . Version != "" {
2022-04-13 16:04:50 +00:00
joinTag2 := fmt . Sprintf ( "%s %d/%d ⤑ :hammer: build %s" , joinTag , current , len ( fromPackages ) , c . HumanReadableString ( ) )
// Search if we have already a final-image that was already pushed
// for this to work on the same repo, it is required to push final images during build
if img := cs . findImageHash ( c . ImageID ( ) , p ) ; cs . Options . PullFirst && img != "" {
cs . Options . Context . Info ( "Final image already found" , img )
if ! cs . Backend . ImageExists ( img ) {
if err := cs . Backend . DownloadImage ( backend . Options { ImageName : img } ) ; err != nil {
return errors . Wrap ( err , "failed pulling image " + img + " during extraction" )
}
}
2021-05-24 17:40:17 +00:00
2022-04-13 16:04:50 +00:00
imgRef , err := cs . Backend . ImageReference ( img , true )
if err != nil {
return err
}
ctx := cs . Options . Context . WithLoggingContext ( fmt . Sprintf ( "final image extract %s" , img ) )
_ , _ , err = image . ExtractTo (
ctx ,
imgRef ,
joinDir ,
nil ,
)
if err != nil {
return err
}
} else {
cs . Options . Context . Info ( "Final image not found for" , c . HumanReadableString ( ) )
2021-05-25 11:27:50 +00:00
2022-04-13 16:04:50 +00:00
// If no image was found, we have to build it from scratch
cs . Options . Context . Info ( joinTag2 , "compilation starts" )
spec , err := cs . FromPackage ( c )
if err != nil {
return errors . Wrap ( err , "while generating images to join from" )
}
wantsArtifact := true
genDepsArtifact := ! cs . Options . PackageTargetOnly
2021-05-23 15:36:09 +00:00
2022-04-13 16:04:50 +00:00
spec . SetOutputPath ( p . GetOutputPath ( ) )
2021-05-21 09:01:27 +00:00
2022-04-13 16:04:50 +00:00
artifact , err := cs . compile ( concurrency , keepPermissions , & wantsArtifact , & genDepsArtifact , spec )
if err != nil {
return errors . Wrap ( err , "failed building join image" )
}
err = artifact . Unpack ( cs . Options . Context , joinDir , keepPermissions )
if err != nil {
return errors . Wrap ( err , "failed building join image" )
}
cs . Options . Context . Info ( joinTag2 , ":white_check_mark: Done" )
2021-05-21 09:01:27 +00:00
}
}
}
2021-12-17 14:21:03 +00:00
artifactDir , err := cs . Options . Context . TempDir ( "join" )
2021-05-21 09:01:27 +00:00
if err != nil {
2021-05-21 20:25:18 +00:00
return errors . Wrap ( err , "could not create tempdir for final artifact" )
2021-05-21 09:01:27 +00:00
}
2021-12-04 20:48:43 +00:00
defer os . RemoveAll ( artifactDir )
2021-05-21 09:01:27 +00:00
2021-10-20 22:13:02 +00:00
cs . Options . Context . Info ( joinTag , ":droplet: generating artifact for source image of" , p . GetPackage ( ) . HumanReadableString ( ) )
2021-05-24 17:40:17 +00:00
2021-05-21 09:01:27 +00:00
// After unpack, create a new artifact and a new final image from it.
// no need to compress, as we are going to toss it away.
a := artifact . NewPackageArtifact ( filepath . Join ( artifactDir , p . GetPackage ( ) . GetFingerPrint ( ) + ".join.tar" ) )
if err := a . Compress ( joinDir , concurrency ) ; err != nil {
2021-05-21 20:25:18 +00:00
return errors . Wrap ( err , "error met while creating package archive" )
2021-05-21 09:01:27 +00:00
}
joinImageName := fmt . Sprintf ( "%s:%s" , cs . Options . PushImageRepository , overallFp )
2021-10-20 22:13:02 +00:00
cs . Options . Context . Info ( joinTag , ":droplet: generating image from artifact" , joinImageName )
2021-10-28 21:42:06 +00:00
err = a . GenerateFinalImage ( cs . Options . Context , joinImageName , cs . Backend , keepPermissions )
2021-05-21 09:01:27 +00:00
if err != nil {
2021-05-21 20:25:18 +00:00
return errors . Wrap ( err , "could not create final image" )
2021-05-21 09:01:27 +00:00
}
if cs . Options . Push {
2021-10-20 22:13:02 +00:00
cs . Options . Context . Info ( joinTag , ":droplet: pushing image from artifact" , joinImageName )
2021-10-28 21:42:06 +00:00
if err = cs . Backend . Push ( backend . Options { ImageName : joinImageName } ) ; err != nil {
return errors . Wrapf ( err , "Could not push image: %s" , joinImageName )
2021-05-21 09:01:27 +00:00
}
}
2021-10-20 22:13:02 +00:00
cs . Options . Context . Info ( joinTag , ":droplet: Consuming image" , joinImageName )
2021-05-21 09:01:27 +00:00
p . SetImage ( joinImageName )
return nil
}
2021-05-18 09:54:45 +00:00
func ( cs * LuetCompiler ) resolveMultiStageImages ( concurrency int , keepPermissions bool , p * compilerspec . LuetCompilationSpec ) error {
resolvedCopyFields := [ ] compilerspec . CopyField { }
2021-05-24 17:40:39 +00:00
copyTag := ">:droplet: copy<"
2021-05-18 09:54:45 +00:00
if len ( p . Copy ) != 0 {
2021-10-20 22:13:02 +00:00
cs . Options . Context . Info ( copyTag , "Package has multi-stage copy, generating required images" )
2021-05-18 09:54:45 +00:00
}
2021-05-24 17:40:39 +00:00
current := 0
// TODO: we should run this only if we are going to build the image
2021-05-18 09:54:45 +00:00
for _ , c := range p . Copy {
2021-05-24 17:40:39 +00:00
current ++
2021-05-18 09:54:45 +00:00
if c . Package != nil && c . Package . Name != "" && c . Package . Version != "" {
2021-05-24 19:35:22 +00:00
copyTag2 := fmt . Sprintf ( "%s %d/%d ⤑ :hammer: build %s" , copyTag , current , len ( p . Copy ) , c . Package . HumanReadableString ( ) )
2021-10-20 22:13:02 +00:00
cs . Options . Context . Info ( copyTag2 , "generating multi-stage images for" , c . Package . HumanReadableString ( ) )
2021-05-18 09:54:45 +00:00
spec , err := cs . FromPackage ( c . Package )
if err != nil {
return errors . Wrap ( err , "while generating images to copy from" )
}
2021-05-24 17:40:17 +00:00
// If we specify --only-target package, we don't want any artifact, otherwise we do
genArtifact := ! cs . Options . PackageTargetOnly
2021-05-23 15:36:09 +00:00
spec . SetOutputPath ( p . GetOutputPath ( ) )
2021-05-25 11:27:50 +00:00
artifact , err := cs . compile ( concurrency , keepPermissions , & genArtifact , & genArtifact , spec )
2021-05-18 09:54:45 +00:00
if err != nil {
return errors . Wrap ( err , "failed building multi-stage image" )
}
resolvedCopyFields = append ( resolvedCopyFields , compilerspec . CopyField {
Image : cs . resolveExistingImageHash ( artifact . PackageCacheImage , spec ) ,
Source : c . Source ,
Destination : c . Destination ,
} )
2021-10-22 15:55:38 +00:00
cs . Options . Context . Success ( copyTag2 , ":white_check_mark: Done" )
2021-05-18 09:54:45 +00:00
} else {
resolvedCopyFields = append ( resolvedCopyFields , c )
}
}
p . Copy = resolvedCopyFields
return nil
}
2021-12-15 17:38:44 +00:00
func CompilerFinalImages ( cs * LuetCompiler ) ( * LuetCompiler , error ) {
2021-11-24 20:59:43 +00:00
// When computing the hash tree, we need to take into consideration
// that packages that require final images have to be seen as packages without deps
// This is because we don't really want to calculate the deptree of them as
// as it is handled already when we are creating the images in resolveFinalImages().
c := * cs
copy := & c
memDB := pkg . NewInMemoryDatabase ( false )
// Create a copy to avoid races
dbCopy := pkg . NewInMemoryDatabase ( false )
err := cs . Database . Clone ( dbCopy )
if err != nil {
return nil , errors . Wrap ( err , "failed cloning db" )
}
for _ , p := range dbCopy . World ( ) {
copy := p . Clone ( )
2021-11-27 20:12:14 +00:00
spec , err := cs . FromPackage ( p )
if err != nil {
return nil , errors . Wrap ( err , "failed getting compile spec for package " + p . HumanReadableString ( ) )
}
2021-11-24 20:59:43 +00:00
if spec . RequiresFinalImages {
2022-01-06 22:57:56 +00:00
copy . Requires ( [ ] * types . Package { } )
2021-11-24 20:59:43 +00:00
}
memDB . CreatePackage ( copy )
}
copy . Database = memDB
2021-12-15 17:38:44 +00:00
return copy , nil
}
2021-11-24 20:59:43 +00:00
2021-12-15 17:38:44 +00:00
func ( cs * LuetCompiler ) compile ( concurrency int , keepPermissions bool , generateFinalArtifact * bool , generateDependenciesFinalArtifact * bool , p * compilerspec . LuetCompilationSpec ) ( * artifact . PackageArtifact , error ) {
cs . Options . Context . Info ( ":package: Compiling" , p . GetPackage ( ) . HumanReadableString ( ) , ".... :coffee:" )
//Before multistage : join - same as multistage, but keep artifacts, join them, create a new one and generate a final image.
// When the image is there, use it as a source here, in place of GetImage().
if err := cs . resolveFinalImages ( concurrency , keepPermissions , p ) ; err != nil {
return nil , errors . Wrap ( err , "while resolving join images" )
}
if err := cs . resolveMultiStageImages ( concurrency , keepPermissions , p ) ; err != nil {
return nil , errors . Wrap ( err , "while resolving multi-stage images" )
}
cs . Options . Context . Debug ( fmt . Sprintf ( "%s: has images %t, empty package: %t" , p . GetPackage ( ) . HumanReadableString ( ) , p . HasImageSource ( ) , p . EmptyPackage ( ) ) )
if ! p . HasImageSource ( ) && ! p . EmptyPackage ( ) {
return nil ,
fmt . Errorf (
"%s is invalid: package has no dependencies and no seed image supplied while it has steps defined" ,
p . GetPackage ( ) . GetFingerPrint ( ) ,
)
}
ht := NewHashTree ( cs . Database )
copy , err := CompilerFinalImages ( cs )
if err != nil {
return nil , err
}
2021-11-24 20:59:43 +00:00
packageHashTree , err := ht . Query ( copy , p )
2021-05-11 07:46:54 +00:00
if err != nil {
return nil , errors . Wrap ( err , "failed querying hashtree" )
}
// This is in order to have the metadata in the yaml
p . SetSourceAssertion ( packageHashTree . Solution )
targetAssertion := packageHashTree . Target
2020-03-19 21:37:32 +00:00
2020-11-13 17:25:44 +00:00
bus . Manager . Publish ( bus . EventPackagePreBuild , struct {
2021-05-11 07:46:54 +00:00
CompileSpec * compilerspec . LuetCompilationSpec
2022-01-06 22:57:56 +00:00
Assert types . PackageAssert
2021-05-11 07:46:54 +00:00
PackageHashTree * PackageImageHashTree
2020-11-13 17:25:44 +00:00
} {
2021-05-11 07:46:54 +00:00
CompileSpec : p ,
Assert : * targetAssertion ,
PackageHashTree : packageHashTree ,
2020-11-13 17:25:44 +00:00
} )
2021-04-15 10:25:34 +00:00
// Update compilespec build options - it will be then serialized into the compilation metadata file
2021-04-22 20:50:50 +00:00
p . BuildOptions . PushImageRepository = cs . Options . PushImageRepository
2021-04-12 17:00:36 +00:00
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 ( ) != "" {
2021-05-18 09:54:45 +00:00
localGenerateArtifact := true
2021-05-25 11:27:50 +00:00
if generateFinalArtifact != nil {
localGenerateArtifact = * generateFinalArtifact
2021-05-18 09:54:45 +00:00
}
2021-05-24 17:40:39 +00:00
2021-05-18 09:54:45 +00:00
a , err := cs . compileWithImage ( p . GetImage ( ) , packageHashTree . BuilderImageHash , targetAssertion . Hash . PackageHash , concurrency , keepPermissions , cs . Options . KeepImg , p , localGenerateArtifact )
if err != nil {
return nil , errors . Wrap ( err , "building direct image" )
}
a . SourceAssertion = p . GetSourceAssertion ( )
2021-05-24 17:40:39 +00:00
2021-05-18 09:54:45 +00:00
a . PackageCacheImage = targetAssertion . Hash . PackageHash
return a , nil
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.
2021-05-11 07:46:54 +00:00
dependencies := packageHashTree . Dependencies // at this point we should have a flattened list of deps to build, including all of them (with all constraints propagated already)
departifacts := [ ] * artifact . PackageArtifact { } // TODO: Return this somehow
2019-11-14 16:45:21 +00:00
depsN := 0
currentN := 0
2020-10-06 15:57:57 +00:00
packageDeps := ! cs . Options . PackageTargetOnly
2021-05-25 11:27:50 +00:00
if generateDependenciesFinalArtifact != nil {
packageDeps = * generateDependenciesFinalArtifact
2021-05-18 09:54:45 +00:00
}
2021-04-21 15:59:56 +00:00
buildDeps := ! cs . Options . NoDeps
buildTarget := ! cs . Options . OnlyDeps
if buildDeps {
2021-11-24 20:59:43 +00:00
2021-10-20 22:13:02 +00:00
cs . Options . Context . Info ( ":deciduous_tree: Build dependencies for " + p . GetPackage ( ) . HumanReadableString ( ) )
2020-02-18 17:22:18 +00:00
for _ , assertion := range dependencies { //highly dependent on the order
depsN ++
2021-10-20 22:13:02 +00:00
cs . Options . Context . 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 ( ) )
2021-10-20 22:13:02 +00:00
cs . Options . Context . 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 ( ) )
}
2021-04-21 15:59:56 +00:00
compileSpec . BuildOptions . PullImageRepository = append ( compileSpec . BuildOptions . PullImageRepository , p . BuildOptions . PullImageRepository ... )
2021-11-24 20:59:43 +00:00
2021-10-20 22:13:02 +00:00
cs . Options . Context . Debug ( "PullImage repos:" , compileSpec . BuildOptions . PullImageRepository )
2021-04-21 15:59:56 +00:00
2020-02-18 17:22:18 +00:00
compileSpec . SetOutputPath ( p . GetOutputPath ( ) )
2020-11-13 17:25:44 +00:00
bus . Manager . Publish ( bus . EventPackagePreBuild , struct {
2021-04-12 17:00:36 +00:00
CompileSpec * compilerspec . LuetCompilationSpec
2022-01-06 22:57:56 +00:00
Assert types . PackageAssert
2020-11-13 17:25:44 +00:00
} {
CompileSpec : compileSpec ,
Assert : assertion ,
} )
2021-07-09 07:31:37 +00:00
if err := cs . resolveFinalImages ( concurrency , keepPermissions , compileSpec ) ; err != nil {
2021-05-21 17:47:23 +00:00
return nil , errors . Wrap ( err , "while resolving join images" )
}
if err := cs . resolveMultiStageImages ( concurrency , keepPermissions , compileSpec ) ; err != nil {
return nil , errors . Wrap ( err , "while resolving multi-stage images" )
}
2021-05-11 07:46:54 +00:00
buildHash , err := packageHashTree . DependencyBuildImage ( assertion . Package )
if err != nil {
return nil , errors . Wrap ( err , "failed looking for dependency in hashtree" )
}
2021-10-20 22:13:02 +00:00
cs . Options . Context . Debug ( pkgTag , " :arrow_right_hook: :whale: Builder image from hash" , assertion . Hash . BuildHash )
cs . Options . Context . Debug ( pkgTag , " :arrow_right_hook: :whale: Package image from hash" , assertion . Hash . PackageHash )
2021-05-11 07:46:54 +00:00
var sourceImage string
2020-02-18 17:22:18 +00:00
if compileSpec . GetImage ( ) != "" {
2021-10-20 22:13:02 +00:00
cs . Options . Context . Debug ( pkgTag , " :wrench: Compiling " + compileSpec . GetPackage ( ) . HumanReadableString ( ) + " from image" )
2021-05-11 07:46:54 +00:00
sourceImage = compileSpec . GetImage ( )
} else {
// for the source instead, pick an image and a buildertaggedImage from hashes if they exists.
// otherways fallback to the pushed repo
// Resolve images from the hashtree
sourceImage = cs . resolveExistingImageHash ( assertion . Hash . BuildHash , compileSpec )
2021-10-20 22:13:02 +00:00
cs . Options . Context . Debug ( pkgTag , " :wrench: Compiling " + compileSpec . GetPackage ( ) . HumanReadableString ( ) + " from tree" )
2019-11-11 18:19:13 +00:00
}
2021-05-11 07:46:54 +00:00
a , err := cs . compileWithImage (
sourceImage ,
buildHash ,
assertion . Hash . PackageHash ,
concurrency ,
keepPermissions ,
cs . Options . 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
}
2020-11-13 17:25:44 +00:00
2021-05-18 09:54:45 +00:00
a . PackageCacheImage = assertion . Hash . PackageHash
2021-10-22 15:55:38 +00:00
cs . Options . Context . Success ( pkgTag , ":white_check_mark: Done" )
2021-05-11 07:46:54 +00:00
2020-11-13 17:25:44 +00:00
bus . Manager . Publish ( bus . EventPackagePostBuild , struct {
2021-04-12 17:00:36 +00:00
CompileSpec * compilerspec . LuetCompilationSpec
Artifact * artifact . PackageArtifact
2020-11-13 17:25:44 +00:00
} {
CompileSpec : compileSpec ,
2021-04-12 17:00:36 +00:00
Artifact : a ,
2020-11-13 17:25:44 +00:00
} )
2021-04-12 17:00:36 +00:00
departifacts = append ( departifacts , a )
2019-11-15 23:38:07 +00:00
}
2020-02-18 17:22:18 +00:00
}
2021-04-21 15:59:56 +00:00
if buildTarget {
2021-05-18 09:54:45 +00:00
localGenerateArtifact := true
2021-05-25 11:27:50 +00:00
if generateFinalArtifact != nil {
localGenerateArtifact = * generateFinalArtifact
2021-05-18 09:54:45 +00:00
}
2021-05-11 07:46:54 +00:00
resolvedSourceImage := cs . resolveExistingImageHash ( packageHashTree . SourceHash , p )
2021-10-20 22:13:02 +00:00
cs . Options . Context . Info ( ":rocket: All dependencies are satisfied, building package requested by the user" , p . GetPackage ( ) . HumanReadableString ( ) )
cs . Options . Context . Info ( ":package:" , p . GetPackage ( ) . HumanReadableString ( ) , " Using image: " , resolvedSourceImage )
2021-05-18 09:54:45 +00:00
a , err := cs . compileWithImage ( resolvedSourceImage , packageHashTree . BuilderImageHash , targetAssertion . Hash . PackageHash , concurrency , keepPermissions , cs . Options . KeepImg , p , localGenerateArtifact )
2019-11-15 23:38:07 +00:00
if err != nil {
2021-04-12 17:00:36 +00:00
return a , err
2019-11-11 18:19:13 +00:00
}
2021-04-12 17:00:36 +00:00
a . Dependencies = departifacts
a . SourceAssertion = p . GetSourceAssertion ( )
2021-05-18 09:54:45 +00:00
a . PackageCacheImage = targetAssertion . Hash . PackageHash
2020-11-13 17:25:44 +00:00
bus . Manager . Publish ( bus . EventPackagePostBuild , struct {
2021-04-12 17:00:36 +00:00
CompileSpec * compilerspec . LuetCompilationSpec
Artifact * artifact . PackageArtifact
2020-11-13 17:25:44 +00:00
} {
CompileSpec : p ,
2021-04-12 17:00:36 +00:00
Artifact : a ,
2020-11-13 17:25:44 +00:00
} )
2021-04-12 17:00:36 +00:00
return a , 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 { }
2022-01-06 22:57:56 +00:00
func ( cs * LuetCompiler ) templatePackage ( vals [ ] map [ string ] interface { } , pack * types . Package , dst templatedata ) ( [ ] byte , error ) {
2021-08-04 14:16:54 +00:00
// Grab shared templates first
2022-01-27 15:37:42 +00:00
var chartFiles [ ] string
2021-08-04 14:16:54 +00:00
if len ( cs . Options . TemplatesFolder ) != 0 {
2022-01-27 15:37:42 +00:00
c , err := template . FilesInDir ( cs . Options . TemplatesFolder )
2021-08-04 14:16:54 +00:00
if err == nil {
chartFiles = c
}
}
2019-11-10 09:48:07 +00:00
2020-11-14 23:13:46 +00:00
var dataresult [ ] byte
val := pack . Rel ( DefinitionFile )
2021-04-12 17:00:36 +00:00
2020-11-14 23:13:46 +00:00
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 )
}
2021-04-12 10:04:04 +00:00
2022-01-06 22:57:56 +00:00
packsRaw , err := types . GetRawPackages ( data )
2021-04-12 10:04:04 +00:00
if err != nil {
return nil , errors . Wrap ( err , "getting raw packages" )
}
2020-11-14 23:13:46 +00:00
2020-11-15 10:47:32 +00:00
raw := packsRaw . Find ( pack . GetName ( ) , pack . GetCategory ( ) , pack . GetVersion ( ) )
2021-04-14 14:49:43 +00:00
td := templatedata { }
if len ( vals ) > 0 {
for _ , bv := range vals {
current := templatedata ( bv )
if err := mergo . Merge ( & td , current ) ; err != nil {
return nil , errors . Wrap ( err , "merging values maps" )
}
}
}
2021-04-15 09:46:13 +00:00
if err := mergo . Merge ( & td , templatedata ( raw ) ) ; err != nil {
2021-04-14 14:49:43 +00:00
return nil , errors . Wrap ( err , "merging values maps" )
}
2020-11-14 23:13:46 +00:00
2022-01-27 15:37:42 +00:00
dat , err := template . Render ( append ( template . ReadFiles ( chartFiles ... ) , string ( dataBuild ) ) , td , dst )
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 {
2021-04-14 14:49:43 +00:00
bv := cs . Options . BuildValuesFile
if len ( vals ) > 0 {
2021-12-17 14:21:03 +00:00
valuesdir , err := cs . Options . Context . TempDir ( "genvalues" )
2021-04-14 14:49:43 +00:00
if err != nil {
return nil , errors . Wrap ( err , "Could not create tempdir" )
}
2021-12-04 20:48:43 +00:00
defer os . RemoveAll ( valuesdir )
2021-04-14 14:49:43 +00:00
for _ , b := range vals {
out , err := yaml . Marshal ( b )
if err != nil {
return nil , errors . Wrap ( err , "while marshalling values file" )
}
2021-06-01 14:43:31 +00:00
f := filepath . Join ( valuesdir , fileHelper . RandStringRunes ( 20 ) )
2021-04-14 14:49:43 +00:00
if err := ioutil . WriteFile ( f , out , os . ModePerm ) ; err != nil {
return nil , errors . Wrap ( err , "while writing temporary values file" )
}
bv = append ( [ ] string { f } , bv ... )
}
}
2021-08-04 14:16:54 +00:00
2022-01-27 15:37:42 +00:00
out , err := template . RenderWithValues ( append ( chartFiles , pack . Rel ( BuildFile ) ) , val , bv ... )
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
}
2021-04-12 10:04:04 +00:00
2021-04-12 17:00:36 +00:00
return dataresult , nil
2021-04-12 10:04:04 +00:00
}
// FromPackage returns a compilation spec from a package definition
2022-01-06 22:57:56 +00:00
func ( cs * LuetCompiler ) FromPackage ( p * types . Package ) ( * compilerspec . LuetCompilationSpec , error ) {
2021-04-12 10:04:04 +00:00
pack , err := cs . Database . FindPackageCandidate ( p )
if err != nil {
return nil , err
}
2021-04-14 14:49:43 +00:00
opts := options . Compiler { }
2021-04-21 15:59:56 +00:00
artifactMetadataFile := filepath . Join ( pack . GetTreeDir ( ) , ".." , pack . GetMetadataFilePath ( ) )
2021-10-20 22:13:02 +00:00
cs . Options . Context . Debug ( "Checking if metadata file is present" , artifactMetadataFile )
2021-04-15 15:35:35 +00:00
if _ , err := os . Stat ( artifactMetadataFile ) ; err == nil {
f , err := os . Open ( artifactMetadataFile )
2021-04-14 14:49:43 +00:00
if err != nil {
2021-04-15 15:35:35 +00:00
return nil , errors . Wrapf ( err , "could not open %s" , artifactMetadataFile )
2021-04-14 14:49:43 +00:00
}
dat , err := ioutil . ReadAll ( f )
if err != nil {
return nil , err
}
art , err := artifact . NewPackageArtifactFromYaml ( dat )
if err != nil {
return nil , errors . Wrap ( err , "could not decode package from yaml" )
}
2021-10-20 22:13:02 +00:00
cs . Options . Context . Debug ( "Read build options:" , art . CompileSpec . BuildOptions , "from" , artifactMetadataFile )
2021-04-22 20:50:50 +00:00
if art . CompileSpec . BuildOptions != nil {
opts = * art . CompileSpec . BuildOptions
}
2021-04-14 14:49:43 +00:00
} else if ! os . IsNotExist ( err ) {
2021-10-20 22:13:02 +00:00
cs . Options . Context . Debug ( "error reading artifact metadata file: " , err . Error ( ) )
2021-04-15 15:35:35 +00:00
} else if os . IsNotExist ( err ) {
2021-10-20 22:13:02 +00:00
cs . Options . Context . Debug ( "metadata file not present, skipping" , artifactMetadataFile )
2021-04-14 14:49:43 +00:00
}
2021-04-23 08:48:03 +00:00
// Update processed build values
2022-01-27 15:37:42 +00:00
dst , err := template . UnMarshalValues ( cs . Options . BuildValuesFile )
2021-04-23 08:48:03 +00:00
if err != nil {
return nil , errors . Wrap ( err , "unmarshalling values" )
}
opts . BuildValues = append ( opts . BuildValues , ( map [ string ] interface { } ) ( dst ) )
bytes , err := cs . templatePackage ( opts . BuildValues , pack , templatedata ( dst ) )
2021-04-12 10:04:04 +00:00
if err != nil {
return nil , errors . Wrap ( err , "while rendering package template" )
}
2019-11-04 16:16:13 +00:00
2021-04-14 14:49:43 +00:00
newSpec , err := compilerspec . NewLuetCompilationSpec ( bytes , pack )
if err != nil {
return nil , err
}
2021-04-21 15:59:56 +00:00
newSpec . BuildOptions = & opts
cs . inheritSpecBuildOptions ( newSpec )
2021-04-14 14:49:43 +00:00
2021-08-03 11:05:26 +00:00
// Update the package in the compiler database to catch updates from NewLuetCompilationSpec
if err := cs . Database . UpdatePackage ( newSpec . Package ) ; err != nil {
return nil , errors . Wrap ( err , "failed updating new package entry in compiler database" )
}
2021-04-14 14:49:43 +00:00
return newSpec , err
2019-11-04 16:16:13 +00:00
}
2019-11-04 16:20:00 +00:00
2021-04-07 13:54:38 +00:00
// GetBackend returns the current compilation backend
2019-11-04 16:20:00 +00:00
func ( cs * LuetCompiler ) GetBackend ( ) CompilerBackend {
return cs . Backend
}
2021-04-07 13:54:38 +00:00
// SetBackend sets the compilation backend
2019-11-04 16:20:00 +00:00
func ( cs * LuetCompiler ) SetBackend ( b CompilerBackend ) {
cs . Backend = b
}