6
6
"errors"
7
7
"flag"
8
8
"fmt"
9
+ "net/http"
9
10
"os"
10
11
"os/exec"
11
12
"path/filepath"
@@ -15,7 +16,9 @@ import (
15
16
16
17
corev1 "k8s.io/api/core/v1"
17
18
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
19
+ "k8s.io/apimachinery/pkg/util/wait"
18
20
21
+ "github.com/containers/image/v5/docker"
19
22
"github.com/containers/image/v5/types"
20
23
"github.com/davecgh/go-spew/spew"
21
24
"github.com/distribution/reference"
@@ -141,23 +144,52 @@ func TestImagePrunerOnCluster(t *testing.T) {
141
144
certsDir := filepath .Join (t .TempDir ())
142
145
require .NoError (t , os .WriteFile (filepath .Join (certsDir , externalRegistryHostname + ".crt" ), ingressCert .Data ["tls.crt" ], 0o644 ))
143
146
144
- // Wait for the route to finish setting up. Not sure if there is an object we can poll for this instead.
145
- time .Sleep (time .Second * 5 )
147
+ // Wait for the route to finish setting up. We can determine that the route
148
+ // setup is complete when we get an image not found error when inspecting a
149
+ // nonexistent image.
150
+ err = wait .PollImmediate (time .Second , time .Minute , func () (bool , error ) {
151
+ imgPruner := imagepruner .NewImageInspectorDeleter ()
152
+ sysCtx := & types.SystemContext {DockerCertPath : certsDir , AuthFilePath : secretPath }
153
+
154
+ _ , _ , err = imgPruner .ImageInspect (ctx , sysCtx , pullspec )
155
+
156
+ // If we get an image not found error, that means the route is set up
157
+ // because we were able to authenticate to the image registry and make a
158
+ // query for a nonexistent image.
159
+ if imagepruner .IsImageNotFoundErr (err ) {
160
+ return true , nil
161
+ }
162
+
163
+ // If this is an HTTP 503 error, that means the route has not finished
164
+ // being set up, so we need to try again.
165
+ var unexpectedHTTPError docker.UnexpectedHTTPStatusError
166
+ if errors .As (err , & unexpectedHTTPError ) && unexpectedHTTPError .StatusCode == http .StatusServiceUnavailable {
167
+ return false , nil
168
+ }
169
+
170
+ // We were unable to identify this error, so return it.
171
+ return false , fmt .Errorf ("unknown registry error when polling: %w" , err )
172
+ })
173
+
174
+ require .NoError (t , err , unwrapAll (err ))
146
175
147
176
// Now we can run our test cases. All test cases use the
148
177
// ImageInspectorDeleter directly since we need to have a bit more control
149
178
// over the SystemContext given that we're running out-of-cluster.
150
179
t .Run ("Inspect without creds" , func (t * testing.T ) {
151
180
t .Parallel ()
181
+
152
182
imgPruner := imagepruner .NewImageInspectorDeleter ()
153
183
sysCtx := & types.SystemContext {DockerCertPath : certsDir }
184
+
154
185
_ , _ , err = imgPruner .ImageInspect (ctx , sysCtx , pullspec )
155
186
assert .Error (t , err )
156
187
assert .True (t , imagepruner .IsAccessDeniedErr (err ), "expected access denied err: %s" , unwrapAll (err ))
157
188
})
158
189
159
190
t .Run ("Inspect nonexistent image digest with creds" , func (t * testing.T ) {
160
191
t .Parallel ()
192
+
161
193
imgPruner := imagepruner .NewImageInspectorDeleter ()
162
194
sysCtx := & types.SystemContext {DockerCertPath : certsDir , AuthFilePath : secretPath }
163
195
@@ -172,6 +204,7 @@ func TestImagePrunerOnCluster(t *testing.T) {
172
204
173
205
t .Run ("Inspect nonexistent image tag with creds" , func (t * testing.T ) {
174
206
t .Parallel ()
207
+
175
208
imgPruner := imagepruner .NewImageInspectorDeleter ()
176
209
sysCtx := & types.SystemContext {DockerCertPath : certsDir , AuthFilePath : secretPath }
177
210
@@ -185,6 +218,7 @@ func TestImagePrunerOnCluster(t *testing.T) {
185
218
186
219
t .Run ("Inspect nonexistent image repo with creds" , func (t * testing.T ) {
187
220
t .Parallel ()
221
+
188
222
imgPruner := imagepruner .NewImageInspectorDeleter ()
189
223
sysCtx := & types.SystemContext {DockerCertPath : certsDir , AuthFilePath : secretPath }
190
224
@@ -198,9 +232,12 @@ func TestImagePrunerOnCluster(t *testing.T) {
198
232
199
233
t .Run ("Push image and inspect" , func (t * testing.T ) {
200
234
t .Parallel ()
235
+
201
236
require .NoError (t , createAndPushScratchImage (ctx , t , pullspec , secretPath , certsDir ))
237
+
202
238
imgPruner := imagepruner .NewImageInspectorDeleter ()
203
239
sysCtx := & types.SystemContext {DockerCertPath : certsDir , AuthFilePath : secretPath }
240
+
204
241
_ , _ , err := imgPruner .ImageInspect (ctx , sysCtx , pullspec )
205
242
assert .NoError (t , err )
206
243
@@ -779,6 +816,21 @@ func canTestOnInClusterRegistry(ctx context.Context, kubeconfig string) (bool, e
779
816
return false , nil
780
817
}
781
818
819
+ // Skopeo requires that a policy.json file be present. Usually, this file is
820
+ // placed in /etc/containers/policy.json when Skopeo is installed. Because we
821
+ // must install skopeo from source in CI, this file is missing. So what we do
822
+ // in this scenario is write our own policy.json file to a temp directory
823
+ // instead. The temp directory is managed by the Go test suite and will be
824
+ // removed after the test is finished.
825
+ func writePolicyFile (t * testing.T ) (string , error ) {
826
+ policyPath := filepath .Join (t .TempDir (), "policy.json" )
827
+
828
+ // Compacted contents of https://github.com/containers/skopeo/blob/main/default-policy.json
829
+ policyJSONBytes := []byte (`{"default":[{"type":"insecureAcceptAnything"}],"transports":{"docker-daemon":{"":[{"type":"insecureAcceptAnything"}]}}}` )
830
+
831
+ return policyPath , os .WriteFile (policyPath , policyJSONBytes , 0o755 )
832
+ }
833
+
782
834
// Creates an empty scratch image and pushes it to the given pullspec using the
783
835
// provided secret path. Accepts an optional certsDir parameter which is
784
836
// particularly useful for pushing internal image registries which have
@@ -792,9 +844,14 @@ func createAndPushScratchImage(ctx context.Context, t *testing.T, pullspec, secr
792
844
return err
793
845
}
794
846
795
- cmd := exec .Command ("skopeo" , "copy" , "--dest-authfile" , secretPath , "tarball://" + srcImage , "docker://" + pullspec )
847
+ policyPath , err := writePolicyFile (t )
848
+ if err != nil {
849
+ return fmt .Errorf ("could not write policy.json file: %w" , err )
850
+ }
851
+
852
+ cmd := exec .Command ("skopeo" , "--policy" , policyPath , "copy" , "--dest-authfile" , secretPath , "tarball://" + srcImage , "docker://" + pullspec )
796
853
if certsDir != "" {
797
- cmd = exec .Command ("skopeo" , "copy" , "--dest-cert-dir" , certsDir , "--dest-authfile" , secretPath , "tarball://" + srcImage , "docker://" + pullspec )
854
+ cmd = exec .Command ("skopeo" , "--policy" , policyPath , " copy" , "--dest-cert-dir" , certsDir , "--dest-authfile" , secretPath , "tarball://" + srcImage , "docker://" + pullspec )
798
855
}
799
856
800
857
t .Logf ("Copying %s to %s using skopeo" , srcImage , pullspec )
0 commit comments