@@ -7,13 +7,15 @@ package images
7
7
import (
8
8
"context"
9
9
"fmt"
10
+ "time"
10
11
12
+ "github.com/avast/retry-go/v4"
11
13
"oras.land/oras-go/v2"
12
14
"oras.land/oras-go/v2/content/oci"
13
15
"oras.land/oras-go/v2/registry"
14
16
orasRemote "oras.land/oras-go/v2/registry/remote"
15
17
"oras.land/oras-go/v2/registry/remote/auth"
16
- "oras.land/oras-go/v2/registry/remote/retry"
18
+ orasRetry "oras.land/oras-go/v2/registry/remote/retry"
17
19
18
20
"github.com/defenseunicorns/pkg/helpers/v2"
19
21
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
@@ -23,41 +25,20 @@ import (
23
25
"github.com/zarf-dev/zarf/src/pkg/transform"
24
26
)
25
27
28
+ const defaultRetries = 3
29
+
26
30
// Push pushes images to a registry.
27
31
func Push (ctx context.Context , cfg PushConfig ) error {
32
+ if cfg .Retries < 1 {
33
+ cfg .Retries = defaultRetries
34
+ }
28
35
cfg .ImageList = helpers .Unique (cfg .ImageList )
36
+ toPush := map [string ]struct {}{}
37
+ for _ , img := range cfg .ImageList {
38
+ toPush [img .Reference ] = struct {}{}
39
+ }
29
40
l := logger .From (ctx )
30
41
registryURL := cfg .RegistryInfo .Address
31
- var tunnel * cluster.Tunnel
32
- c , _ := cluster .NewCluster ()
33
- if c != nil {
34
- var err error
35
- registryURL , tunnel , err = c .ConnectToZarfRegistryEndpoint (ctx , cfg .RegistryInfo )
36
- if err != nil {
37
- return err
38
- }
39
- if tunnel != nil {
40
- defer tunnel .Close ()
41
- }
42
- }
43
- client := & auth.Client {
44
- Client : retry .DefaultClient ,
45
- Cache : auth .NewCache (),
46
- Credential : auth .StaticCredential (registryURL , auth.Credential {
47
- Username : cfg .RegistryInfo .PushUsername ,
48
- Password : cfg .RegistryInfo .PushPassword ,
49
- }),
50
- }
51
-
52
- plainHTTP := cfg .PlainHTTP
53
-
54
- if dns .IsLocalhost (registryURL ) && ! cfg .PlainHTTP {
55
- var err error
56
- plainHTTP , err = shouldUsePlainHTTP (ctx , registryURL , client )
57
- if err != nil {
58
- return err
59
- }
60
- }
61
42
err := addRefNameAnnotationToImages (cfg .SourceDirectory )
62
43
if err != nil {
63
44
return err
@@ -68,53 +49,98 @@ func Push(ctx context.Context, cfg PushConfig) error {
68
49
return fmt .Errorf ("failed to instantiate oci directory: %w" , err )
69
50
}
70
51
71
- pushImage := func (srcName , dstName string ) error {
72
- remoteRepo := & orasRemote.Repository {
73
- PlainHTTP : plainHTTP ,
74
- Client : client ,
75
- }
76
- remoteRepo .Reference , err = registry .ParseReference (dstName )
77
- if err != nil {
78
- return fmt .Errorf ("failed to parse ref %s: %w" , dstName , err )
79
- }
80
- defaultPlatform := & ocispec.Platform {
81
- Architecture : cfg .Arch ,
82
- OS : "linux" ,
52
+ // The user may or may not have a cluster available, if it's available then use it to connect to the registry
53
+ c , _ := cluster .NewCluster ()
54
+ err = retry .Do (func () error {
55
+ // Include tunnel connection in case the port forward breaks, for example, a registry pod could spin down / restart
56
+ var tunnel * cluster.Tunnel
57
+ if c != nil {
58
+ var err error
59
+ registryURL , tunnel , err = c .ConnectToZarfRegistryEndpoint (ctx , cfg .RegistryInfo )
60
+ if err != nil {
61
+ return err
62
+ }
63
+ if tunnel != nil {
64
+ defer tunnel .Close ()
65
+ }
83
66
}
84
- if tunnel != nil {
85
- return tunnel .Wrap (func () error {
86
- return copyImage (ctx , src , remoteRepo , srcName , dstName , cfg .OCIConcurrency , defaultPlatform )
87
- })
67
+ client := & auth.Client {
68
+ Client : orasRetry .DefaultClient ,
69
+ Cache : auth .NewCache (),
70
+ Credential : auth .StaticCredential (registryURL , auth.Credential {
71
+ Username : cfg .RegistryInfo .PushUsername ,
72
+ Password : cfg .RegistryInfo .PushPassword ,
73
+ }),
88
74
}
89
- return copyImage (ctx , src , remoteRepo , srcName , dstName , cfg .OCIConcurrency , defaultPlatform )
90
- }
91
75
92
- for _ , img := range cfg .ImageList {
93
- l . Info ( "pushing image" , "name" , img . Reference )
94
- // If this is not a no checksum image push it for use with the Zarf agent
95
- if ! cfg . NoChecksum {
96
- offlineNameCRC , err := transform . ImageTransformHost ( registryURL , img . Reference )
76
+ plainHTTP := cfg .PlainHTTP
77
+
78
+ if dns . IsLocalhost ( registryURL ) && ! cfg . PlainHTTP {
79
+ var err error
80
+ plainHTTP , err = shouldUsePlainHTTP ( ctx , registryURL , client )
97
81
if err != nil {
98
82
return err
99
83
}
84
+ }
100
85
101
- if err = pushImage (img .Reference , offlineNameCRC ); err != nil {
102
- return err
86
+ pushImage := func (srcName , dstName string ) error {
87
+ remoteRepo := & orasRemote.Repository {
88
+ PlainHTTP : plainHTTP ,
89
+ Client : client ,
103
90
}
91
+ remoteRepo .Reference , err = registry .ParseReference (dstName )
92
+ if err != nil {
93
+ return fmt .Errorf ("failed to parse ref %s: %w" , dstName , err )
94
+ }
95
+ defaultPlatform := & ocispec.Platform {
96
+ Architecture : cfg .Arch ,
97
+ OS : "linux" ,
98
+ }
99
+ if tunnel != nil {
100
+ return tunnel .Wrap (func () error {
101
+ return copyImage (ctx , src , remoteRepo , srcName , dstName , cfg .OCIConcurrency , defaultPlatform )
102
+ })
103
+ }
104
+ return copyImage (ctx , src , remoteRepo , srcName , dstName , cfg .OCIConcurrency , defaultPlatform )
104
105
}
106
+ pushed := []string {}
107
+ // Delete the images that were already successfully pushed so that they aren't attempted on the next retry
108
+ defer func () {
109
+ for _ , refInfo := range pushed {
110
+ delete (toPush , refInfo )
111
+ }
112
+ }()
113
+ for img := range toPush {
114
+ l .Info ("pushing image" , "name" , img )
115
+ // If this is not a no checksum image push it for use with the Zarf agent
116
+ if ! cfg .NoChecksum {
117
+ offlineNameCRC , err := transform .ImageTransformHost (registryURL , img )
118
+ if err != nil {
119
+ return err
120
+ }
121
+
122
+ if err = pushImage (img , offlineNameCRC ); err != nil {
123
+ return err
124
+ }
125
+ }
105
126
106
- // To allow for other non-zarf workloads to easily see the images upload a non-checksum version
107
- // (this may result in collisions but this is acceptable for this use case)
108
- offlineName , err := transform .ImageTransformHostWithoutChecksum (registryURL , img . Reference )
109
- if err != nil {
110
- return err
111
- }
127
+ // To allow for other non-zarf workloads to easily see the images upload a non-checksum version
128
+ // (this may result in collisions but this is acceptable for this use case)
129
+ offlineName , err := transform .ImageTransformHostWithoutChecksum (registryURL , img )
130
+ if err != nil {
131
+ return err
132
+ }
112
133
113
- if err = pushImage (img .Reference , offlineName ); err != nil {
114
- return err
134
+ if err = pushImage (img , offlineName ); err != nil {
135
+ return err
136
+ }
137
+ pushed = append (pushed , img )
115
138
}
139
+ return nil
140
+ }, retry .Context (ctx ), retry .Attempts (uint (cfg .Retries )), retry .Delay (500 * time .Millisecond ))
141
+ if err != nil {
142
+ return err
116
143
}
117
-
118
144
return nil
119
145
}
120
146
0 commit comments