@@ -17,25 +17,40 @@ limitations under the License.
17
17
package layouts
18
18
19
19
import (
20
+ "errors"
20
21
"fmt"
22
+ "os"
23
+ "time"
21
24
22
25
"github.com/google/go-containerregistry/pkg/authn"
23
26
"github.com/google/go-containerregistry/pkg/name"
27
+ v1 "github.com/google/go-containerregistry/pkg/v1"
24
28
"github.com/google/go-containerregistry/pkg/v1/layout"
25
29
"github.com/google/go-containerregistry/pkg/v1/remote"
30
+ "github.com/samber/lo"
31
+ "github.com/samber/lo/parallel"
26
32
27
33
"github.com/deckhouse/deckhouse-cli/pkg/libmirror/contexts"
28
34
"github.com/deckhouse/deckhouse-cli/pkg/libmirror/util/auth"
35
+ "github.com/deckhouse/deckhouse-cli/pkg/libmirror/util/errorutil"
36
+ "github.com/deckhouse/deckhouse-cli/pkg/libmirror/util/retry"
37
+ "github.com/deckhouse/deckhouse-cli/pkg/libmirror/util/retry/task"
29
38
)
30
39
40
+ var ErrEmptyLayout = errors .New ("No images in layout" )
41
+
31
42
func PushLayoutToRepo (
32
43
imagesLayout layout.Path ,
33
44
registryRepo string ,
34
45
authProvider authn.Authenticator ,
35
46
logger contexts.Logger ,
47
+ parallelismConfig contexts.ParallelismConfig ,
36
48
insecure , skipVerifyTLS bool ,
37
49
) error {
38
50
refOpts , remoteOpts := auth .MakeRemoteRegistryRequestOptions (authProvider , insecure , skipVerifyTLS )
51
+ if parallelismConfig .Blobs != 0 {
52
+ remoteOpts = append (remoteOpts , remote .WithJobs (parallelismConfig .Blobs ))
53
+ }
39
54
40
55
index , err := imagesLayout .ImageIndex ()
41
56
if err != nil {
@@ -46,27 +61,92 @@ func PushLayoutToRepo(
46
61
return fmt .Errorf ("Parse OCI Image Index Manifest: %w" , err )
47
62
}
48
63
49
- pushCount := 1
50
- for _ , imageDesc := range indexManifest .Manifests {
51
- tag := imageDesc .Annotations ["io.deckhouse.image.short_tag" ]
52
- imageRef := registryRepo + ":" + tag
64
+ if len (indexManifest .Manifests ) == 0 {
65
+ return fmt .Errorf ("%s: %w" , registryRepo , ErrEmptyLayout )
66
+ }
67
+
68
+ batches := lo .Chunk (indexManifest .Manifests , parallelismConfig .Images )
69
+ batchesCount , imagesCount := 1 , 1
70
+
71
+ for _ , manifestSet := range batches {
72
+ if parallelismConfig .Images == 1 {
73
+ tag := manifestSet [0 ].Annotations ["io.deckhouse.image.short_tag" ]
74
+ imageRef := registryRepo + ":" + tag
75
+ logger .InfoF ("[%d / %d] Pushing image %s" , imagesCount , len (indexManifest .Manifests ), imageRef )
76
+ pushImage (logger , registryRepo , index , imagesCount , refOpts , remoteOpts )(manifestSet [0 ], 0 )
77
+ imagesCount += 1
78
+ continue
79
+ }
53
80
54
- logger .InfoF ("[%d / %d] Pushing image %s " , pushCount , len (indexManifest .Manifests ), imageRef )
55
- img , err := index .Image (imageDesc .Digest )
81
+ err = logger .Process (fmt .Sprintf ("Pushing batch %d / %d" , batchesCount , len (batches )), func () error {
82
+ logger .InfoLn ("Images in batch:" )
83
+ for _ , manifest := range manifestSet {
84
+ logger .InfoF ("- %s" , registryRepo + ":" + manifest .Annotations ["io.deckhouse.image.short_tag" ])
85
+ }
86
+
87
+ parallel .ForEach (manifestSet , pushImage (logger , registryRepo , index , imagesCount , refOpts , remoteOpts ))
88
+
89
+ return nil
90
+ })
56
91
if err != nil {
57
- return fmt .Errorf ("Read image : %w" , err )
92
+ return fmt .Errorf ("Push batch of images : %w" , err )
58
93
}
94
+ batchesCount += 1
95
+ imagesCount += len (manifestSet )
96
+ }
97
+
98
+ return nil
99
+ }
59
100
101
+ func pushImage (
102
+ logger contexts.Logger ,
103
+ registryRepo string ,
104
+ index v1.ImageIndex ,
105
+ imagesCount int ,
106
+ refOpts []name.Option ,
107
+ remoteOpts []remote.Option ,
108
+ ) func (v1.Descriptor , int ) {
109
+ return func (manifest v1.Descriptor , _ int ) {
110
+ tag := manifest .Annotations ["io.deckhouse.image.short_tag" ]
111
+ imageRef := registryRepo + ":" + tag
112
+ img , err := index .Image (manifest .Digest )
113
+ if err != nil {
114
+ logger .WarnF ("Read image: %v" , err )
115
+ os .Exit (1 )
116
+ }
60
117
ref , err := name .ParseReference (imageRef , refOpts ... )
61
118
if err != nil {
62
- return fmt .Errorf ("Parse image reference: %w" , err )
119
+ logger .WarnF ("Parse image reference: %v" , err )
120
+ os .Exit (1 )
63
121
}
64
- if err = remote .Write (ref , img , remoteOpts ... ); err != nil {
65
- return fmt .Errorf ("Write %s to registry: %w" , ref .String (), err )
122
+
123
+ err = retry .RunTask (silentLogger {}, "" , task .WithConstantRetries (19 , 3 * time .Second , func () error {
124
+ if err = remote .Write (ref , img , remoteOpts ... ); err != nil {
125
+ if errorutil .IsTrivyMediaTypeNotAllowedError (err ) {
126
+ logger .WarnLn (errorutil .CustomTrivyMediaTypesWarning )
127
+ os .Exit (1 )
128
+ }
129
+ return fmt .Errorf ("Write %s to registry: %w" , ref .String (), err )
130
+ }
131
+ return nil
132
+ }))
133
+ if err != nil {
134
+ logger .WarnF ("Push image: %v" , err )
135
+ os .Exit (1 )
66
136
}
67
- logger .InfoLn ("✅" )
68
- pushCount += 1
69
- }
70
137
71
- return nil
138
+ imagesCount += 1
139
+ }
72
140
}
141
+
142
+ type silentLogger struct {}
143
+
144
+ var _ contexts.Logger = silentLogger {}
145
+
146
+ func (silentLogger ) DebugF (_ string , _ ... interface {}) {}
147
+ func (silentLogger ) DebugLn (_ ... interface {}) {}
148
+ func (silentLogger ) InfoF (_ string , _ ... interface {}) {}
149
+ func (silentLogger ) InfoLn (_ ... interface {}) {}
150
+ func (silentLogger ) WarnF (_ string , _ ... interface {}) {}
151
+ func (silentLogger ) WarnLn (_ ... interface {}) {}
152
+ func (silentLogger ) Process (_ string , _ func () error ) error { return nil }
0 commit comments