@@ -2,6 +2,7 @@ package imagebuildah
2
2
3
3
import (
4
4
"context"
5
+ "crypto/sha256"
5
6
"fmt"
6
7
"io"
7
8
"os"
@@ -22,6 +23,7 @@ import (
22
23
"github.com/containers/buildah/util"
23
24
config "github.com/containers/common/pkg/config"
24
25
cp "github.com/containers/image/v5/copy"
26
+ imagedocker "github.com/containers/image/v5/docker"
25
27
"github.com/containers/image/v5/docker/reference"
26
28
"github.com/containers/image/v5/manifest"
27
29
is "github.com/containers/image/v5/storage"
@@ -961,6 +963,22 @@ func (s *StageExecutor) Execute(ctx context.Context, base string) (imgID string,
961
963
s .log (commitMessage )
962
964
}
963
965
}
966
+ // logCachePulled produces build log for cases when `--cache-from`
967
+ // is used and a valid intermediate image is pulled from remote source.
968
+ logCachePulled := func (cacheKey string ) {
969
+ if ! s .executor .quiet {
970
+ cacheHitMessage := "--> Cache pulled from remote"
971
+ fmt .Fprintf (s .executor .out , "%s %s\n " , cacheHitMessage , fmt .Sprintf ("%s:%s" , s .executor .cacheFrom , cacheKey ))
972
+ }
973
+ }
974
+ // logCachePush produces build log for cases when `--cache-to`
975
+ // is used and a valid intermediate image is pushed tp remote source.
976
+ logCachePush := func (cacheKey string ) {
977
+ if ! s .executor .quiet {
978
+ cacheHitMessage := "--> Pushing cache"
979
+ fmt .Fprintf (s .executor .out , "%s %s\n " , cacheHitMessage , fmt .Sprintf ("%s:%s" , s .executor .cacheTo , cacheKey ))
980
+ }
981
+ }
964
982
logCacheHit := func (cacheID string ) {
965
983
if ! s .executor .quiet {
966
984
cacheHitMessage := "--> Using cache"
@@ -1153,18 +1171,31 @@ func (s *StageExecutor) Execute(ctx context.Context, base string) (imgID string,
1153
1171
var (
1154
1172
commitName string
1155
1173
cacheID string
1174
+ cacheKey string
1175
+ pulledAndUsedCacheImage bool
1156
1176
err error
1157
1177
rebase bool
1158
1178
addedContentSummary string
1159
1179
canMatchCacheOnlyAfterRun bool
1160
1180
)
1161
1181
1182
+ needsCacheKey := (s .executor .cacheFrom != nil || s .executor .cacheTo != nil )
1183
+
1162
1184
// If we have to commit for this instruction, only assign the
1163
1185
// stage's configured output name to the last layer.
1164
1186
if lastInstruction {
1165
1187
commitName = s .output
1166
1188
}
1167
1189
1190
+ // If --cache-from or --cache-to is specified make sure to populate
1191
+ // cacheKey since it will be used either while pulling or pushing the
1192
+ // cache images.
1193
+ if needsCacheKey {
1194
+ cacheKey , err = s .generateCacheKey (ctx , node , addedContentSummary , s .stepRequiresLayer (step ))
1195
+ if err != nil {
1196
+ return "" , nil , fmt .Errorf ("failed while generating cache key: %w" , err )
1197
+ }
1198
+ }
1168
1199
// Check if there's already an image based on our parent that
1169
1200
// has the same change that we're about to make, so far as we
1170
1201
// can tell.
@@ -1186,11 +1217,35 @@ func (s *StageExecutor) Execute(ctx context.Context, base string) (imgID string,
1186
1217
// Retrieve the digest info for the content that we just copied
1187
1218
// into the rootfs.
1188
1219
addedContentSummary = s .getContentSummaryAfterAddingContent ()
1220
+ // regenerate cache key with updated content summary
1221
+ if needsCacheKey {
1222
+ cacheKey , err = s .generateCacheKey (ctx , node , addedContentSummary , s .stepRequiresLayer (step ))
1223
+ if err != nil {
1224
+ return "" , nil , fmt .Errorf ("failed while generating cache key: %w" , err )
1225
+ }
1226
+ }
1189
1227
}
1190
1228
cacheID , err = s .intermediateImageExists (ctx , node , addedContentSummary , s .stepRequiresLayer (step ))
1191
1229
if err != nil {
1192
1230
return "" , nil , fmt .Errorf ("error checking if cached image exists from a previous build: %w" , err )
1193
1231
}
1232
+ // All the best effort to find cache on localstorage have failed try pulling
1233
+ // cache from remote repo if `--cache-from` was configured.
1234
+ if cacheID == "" && s .executor .cacheFrom != nil {
1235
+ // only attempt to use cache again if pulling was successful
1236
+ // otherwise do nothing and attempt to run the step, err != nil
1237
+ // is ignored and will be automatically logged for --log-level debug
1238
+ if id , err := s .pullCache (ctx , cacheKey ); id != "" && err == nil {
1239
+ logCachePulled (cacheKey )
1240
+ cacheID , err = s .intermediateImageExists (ctx , node , addedContentSummary , s .stepRequiresLayer (step ))
1241
+ if err != nil {
1242
+ return "" , nil , fmt .Errorf ("error checking if cached image exists from a previous build: %w" , err )
1243
+ }
1244
+ if cacheID != "" {
1245
+ pulledAndUsedCacheImage = true
1246
+ }
1247
+ }
1248
+ }
1194
1249
}
1195
1250
1196
1251
// If we didn't find a cache entry, or we need to add content
@@ -1208,6 +1263,13 @@ func (s *StageExecutor) Execute(ctx context.Context, base string) (imgID string,
1208
1263
1209
1264
// In case we added content, retrieve its digest.
1210
1265
addedContentSummary = s .getContentSummaryAfterAddingContent ()
1266
+ // regenerate cache key with updated content summary
1267
+ if needsCacheKey {
1268
+ cacheKey , err = s .generateCacheKey (ctx , node , addedContentSummary , s .stepRequiresLayer (step ))
1269
+ if err != nil {
1270
+ return "" , nil , fmt .Errorf ("failed while generating cache key: %w" , err )
1271
+ }
1272
+ }
1211
1273
1212
1274
// Check if there's already an image based on our parent that
1213
1275
// has the same change that we just made.
@@ -1269,6 +1331,23 @@ func (s *StageExecutor) Execute(ctx context.Context, base string) (imgID string,
1269
1331
}
1270
1332
}
1271
1333
1334
+ // Following step is just built and was not used from
1335
+ // cache so check if --cache-to was specified if yes
1336
+ // then attempt pushing this cache to remote repo and
1337
+ // fail accordingly.
1338
+ //
1339
+ // Or
1340
+ //
1341
+ // Try to push this cache to remote repository only
1342
+ // if cache was present on local storage and not
1343
+ // pulled from remote source while processing this
1344
+ if s .executor .cacheTo != nil && (! pulledAndUsedCacheImage || cacheID == "" ) {
1345
+ logCachePush (cacheKey )
1346
+ if err = s .pushCache (ctx , imgID , cacheKey ); err != nil {
1347
+ return "" , nil , err
1348
+ }
1349
+ }
1350
+
1272
1351
// Create a squashed version of this image
1273
1352
// if we're supposed to create one and this
1274
1353
// is the last instruction of the last stage.
@@ -1542,6 +1621,114 @@ func (s *StageExecutor) tagExistingImage(ctx context.Context, cacheID, output st
1542
1621
return img .ID , ref , nil
1543
1622
}
1544
1623
1624
+ // generateCacheKey returns a computed digest for the current STEP
1625
+ // running its history and diff against a hash algorithm and this
1626
+ // generated CacheKey is further used by buildah to lock and decide
1627
+ // tag for the intermeidate image which can be pushed and pulled to/from
1628
+ // the remote repository.
1629
+ func (s * StageExecutor ) generateCacheKey (ctx context.Context , currNode * parser.Node , addedContentDigest string , buildAddsLayer bool ) (string , error ) {
1630
+ hash := sha256 .New ()
1631
+ var baseHistory []v1.History
1632
+ var diffIDs []digest.Digest
1633
+ var manifestType string
1634
+ var err error
1635
+ if s .builder .FromImageID != "" {
1636
+ manifestType , baseHistory , diffIDs , err = s .executor .getImageTypeAndHistoryAndDiffIDs (ctx , s .builder .FromImageID )
1637
+ if err != nil {
1638
+ return "" , fmt .Errorf ("error getting history of base image %q: %w" , s .builder .FromImageID , err )
1639
+ }
1640
+ for i := 0 ; i < len (diffIDs ); i ++ {
1641
+ fmt .Fprintln (hash , diffIDs [i ].String ())
1642
+ }
1643
+ }
1644
+ createdBy := s .getCreatedBy (currNode , addedContentDigest )
1645
+ fmt .Fprintf (hash , "%t" , buildAddsLayer )
1646
+ fmt .Fprintln (hash , createdBy )
1647
+ fmt .Fprintln (hash , manifestType )
1648
+ for _ , element := range baseHistory {
1649
+ fmt .Fprintln (hash , element .CreatedBy )
1650
+ fmt .Fprintln (hash , element .Author )
1651
+ fmt .Fprintln (hash , element .Comment )
1652
+ fmt .Fprintln (hash , element .Created )
1653
+ fmt .Fprintf (hash , "%t" , element .EmptyLayer )
1654
+ fmt .Fprintln (hash )
1655
+ }
1656
+ return fmt .Sprintf ("%x" , hash .Sum (nil )), nil
1657
+ }
1658
+
1659
+ // cacheImageReference is internal function which generates ImageReference from Named repo sources
1660
+ // and a tag.
1661
+ func cacheImageReference (repo reference.Named , cachekey string ) (types.ImageReference , error ) {
1662
+ tagged , err := reference .WithTag (repo , cachekey )
1663
+ if err != nil {
1664
+ return nil , fmt .Errorf ("failed generating tagged reference for %q: %w" , repo , err )
1665
+ }
1666
+ dest , err := imagedocker .NewReference (tagged )
1667
+ if err != nil {
1668
+ return nil , fmt .Errorf ("failed generating docker reference for %q: %w" , tagged , err )
1669
+ }
1670
+ return dest , nil
1671
+ }
1672
+
1673
+ // pushCache takes the image id of intermediate image and attempts
1674
+ // to perform push at the remote repository with cacheKey as the tag.
1675
+ // Returns error if fails otherwise returns nil.
1676
+ func (s * StageExecutor ) pushCache (ctx context.Context , src , cacheKey string ) error {
1677
+ dest , err := cacheImageReference (s .executor .cacheTo , cacheKey )
1678
+ if err != nil {
1679
+ return err
1680
+ }
1681
+ logrus .Debugf ("trying to push cache to dest: %+v from src:%+v" , dest , src )
1682
+ options := buildah.PushOptions {
1683
+ Compression : s .executor .compression ,
1684
+ SignaturePolicyPath : s .executor .signaturePolicyPath ,
1685
+ Store : s .executor .store ,
1686
+ SystemContext : s .executor .systemContext ,
1687
+ BlobDirectory : s .executor .blobDirectory ,
1688
+ SignBy : s .executor .signBy ,
1689
+ MaxRetries : s .executor .maxPullPushRetries ,
1690
+ RetryDelay : s .executor .retryPullPushDelay ,
1691
+ }
1692
+ ref , digest , err := buildah .Push (ctx , src , dest , options )
1693
+ if err != nil {
1694
+ return fmt .Errorf ("failed pushing cache to %q: %w" , dest , err )
1695
+ }
1696
+ logrus .Debugf ("successfully pushed cache to dest: %+v with ref:%+v and digest: %v" , dest , ref , digest )
1697
+ return nil
1698
+ }
1699
+
1700
+ // pullCache takes the image source of the cache assuming tag
1701
+ // already points to the valid cacheKey and pulls the image to
1702
+ // local storage only if it was not already present on local storage
1703
+ // or a newer version of cache was found in the upstream repo. If new
1704
+ // image was pulled function returns image id otherwise returns empty
1705
+ // string "" or error if any error was encontered while pulling the cache.
1706
+ func (s * StageExecutor ) pullCache (ctx context.Context , cacheKey string ) (string , error ) {
1707
+ src , err := cacheImageReference (s .executor .cacheFrom , cacheKey )
1708
+ if err != nil {
1709
+ return "" , err
1710
+ }
1711
+ logrus .Debugf ("trying to pull cache from remote repo: %+v" , src .DockerReference ())
1712
+ options := buildah.PullOptions {
1713
+ SignaturePolicyPath : s .executor .signaturePolicyPath ,
1714
+ Store : s .executor .store ,
1715
+ SystemContext : s .executor .systemContext ,
1716
+ BlobDirectory : s .executor .blobDirectory ,
1717
+ MaxRetries : s .executor .maxPullPushRetries ,
1718
+ RetryDelay : s .executor .retryPullPushDelay ,
1719
+ AllTags : false ,
1720
+ ReportWriter : nil ,
1721
+ PullPolicy : define .PullIfNewer ,
1722
+ }
1723
+ id , err := buildah .Pull (ctx , src .DockerReference ().String (), options )
1724
+ if err != nil {
1725
+ logrus .Debugf ("failed pulling cache from source %s: %v" , src , err )
1726
+ return "" , fmt .Errorf ("failed while pulling cache from %q: %w" , src , err )
1727
+ }
1728
+ logrus .Debugf ("successfully pulled cache from repo %s: %s" , src , id )
1729
+ return id , nil
1730
+ }
1731
+
1545
1732
// intermediateImageExists returns true if an intermediate image of currNode exists in the image store from a previous build.
1546
1733
// It verifies this by checking the parent of the top layer of the image and the history.
1547
1734
func (s * StageExecutor ) intermediateImageExists (ctx context.Context , currNode * parser.Node , addedContentDigest string , buildAddsLayer bool ) (string , error ) {
0 commit comments