Skip to content

Commit 8a78592

Browse files
mabulguclaude
andcommitted
Add tests for per-host OCI registry authentication
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: mabulgu <mabulgu@gmail.com>
1 parent 3406c96 commit 8a78592

5 files changed

Lines changed: 1072 additions & 6 deletions

File tree

internal/controller/metal3.io/baremetalhost_controller_test.go

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3351,3 +3351,205 @@ func TestComputeConditions(t *testing.T) {
33513351
})
33523352
}
33533353
}
3354+
3355+
// TestGetImageAuthSecret_OCIImageWithValidSecret tests that credentials are extracted
3356+
// successfully when an OCI image has a valid auth secret configured.
3357+
func TestGetImageAuthSecret_OCIImageWithValidSecret(t *testing.T) {
3358+
host := newDefaultHost(t)
3359+
host.Spec.Online = true
3360+
ociAuthSecretName := "oci-auth-secret"
3361+
host.Spec.Image = &metal3api.Image{
3362+
URL: "oci://registry.example.com/repo/image:tag",
3363+
OCIAuthSecretName: &ociAuthSecretName,
3364+
}
3365+
3366+
// Create the OCI auth secret
3367+
ociSecret := createDockerConfigJSONSecretForTest(t, ociAuthSecretName, namespace, map[string]map[string]string{
3368+
"registry.example.com": {
3369+
"username": "testuser",
3370+
"password": "testpass",
3371+
},
3372+
})
3373+
3374+
r := newTestReconciler(t, host, ociSecret)
3375+
3376+
// Manually call getImageAuthSecret to test the function directly
3377+
credentials, err := r.getImageAuthSecret(t.Context(), host, host.Spec.Image)
3378+
3379+
require.NoError(t, err)
3380+
require.NotEmpty(t, credentials, "expected non-empty credentials")
3381+
3382+
// Verify credentials are base64 encoded and in correct format
3383+
decoded, err := base64.StdEncoding.DecodeString(credentials)
3384+
require.NoError(t, err)
3385+
assert.Equal(t, "testuser:testpass", string(decoded))
3386+
}
3387+
3388+
// TestGetImageAuthSecret_OCIImageWithoutSecret tests that no error occurs
3389+
// when an OCI image does not have an auth secret configured.
3390+
func TestGetImageAuthSecret_OCIImageWithoutSecret(t *testing.T) {
3391+
host := newDefaultHost(t)
3392+
host.Spec.Online = true
3393+
host.Spec.Image = &metal3api.Image{
3394+
URL: "oci://registry.example.com/repo/image:tag",
3395+
// No OCIAuthSecretName set
3396+
}
3397+
3398+
r := newTestReconciler(t, host)
3399+
3400+
credentials, err := r.getImageAuthSecret(t.Context(), host, host.Spec.Image)
3401+
3402+
require.NoError(t, err)
3403+
assert.Empty(t, credentials, "expected empty credentials when no auth secret is configured")
3404+
}
3405+
3406+
// TestGetImageAuthSecret_OCIImageWithInvalidSecret tests the behavior when
3407+
// the configured auth secret cannot be parsed or doesn't have valid credentials.
3408+
func TestGetImageAuthSecret_OCIImageWithInvalidSecret(t *testing.T) {
3409+
host := newDefaultHost(t)
3410+
host.Spec.Online = true
3411+
ociAuthSecretName := "invalid-auth-secret"
3412+
host.Spec.Image = &metal3api.Image{
3413+
URL: "oci://registry.example.com/repo/image:tag",
3414+
OCIAuthSecretName: &ociAuthSecretName,
3415+
}
3416+
3417+
// Create an invalid secret (wrong format)
3418+
invalidSecret := &corev1.Secret{
3419+
ObjectMeta: metav1.ObjectMeta{
3420+
Name: ociAuthSecretName,
3421+
Namespace: namespace,
3422+
},
3423+
Type: corev1.SecretTypeDockerConfigJson,
3424+
Data: map[string][]byte{
3425+
// Missing proper dockerconfigjson format
3426+
corev1.DockerConfigJsonKey: []byte(`{"invalid": "json"}`),
3427+
},
3428+
}
3429+
3430+
r := newTestReconciler(t, host, invalidSecret)
3431+
3432+
credentials, err := r.getImageAuthSecret(t.Context(), host, host.Spec.Image)
3433+
3434+
require.Error(t, err, "expected error for invalid secret")
3435+
assert.Empty(t, credentials, "expected empty credentials for invalid secret")
3436+
}
3437+
3438+
// TestGetImageAuthSecret_OCIImageWithMissingSecret tests the behavior when
3439+
// the configured auth secret doesn't exist.
3440+
func TestGetImageAuthSecret_OCIImageWithMissingSecret(t *testing.T) {
3441+
host := newDefaultHost(t)
3442+
host.Spec.Online = true
3443+
ociAuthSecretName := "nonexistent-secret"
3444+
host.Spec.Image = &metal3api.Image{
3445+
URL: "oci://registry.example.com/repo/image:tag",
3446+
OCIAuthSecretName: &ociAuthSecretName,
3447+
}
3448+
3449+
r := newTestReconciler(t, host)
3450+
3451+
credentials, err := r.getImageAuthSecret(t.Context(), host, host.Spec.Image)
3452+
3453+
require.Error(t, err, "expected error for missing secret")
3454+
assert.Empty(t, credentials, "expected empty credentials for missing secret")
3455+
}
3456+
3457+
// TestGetImageAuthSecret_NonOCIImageWithAuthSecret tests that auth secrets
3458+
// are ignored for non-OCI images.
3459+
func TestGetImageAuthSecret_NonOCIImageWithAuthSecret(t *testing.T) {
3460+
host := newDefaultHost(t)
3461+
host.Spec.Online = true
3462+
ociAuthSecretName := "oci-auth-secret"
3463+
host.Spec.Image = &metal3api.Image{
3464+
URL: "http://example.com/image.qcow2", // Non-OCI URL
3465+
OCIAuthSecretName: &ociAuthSecretName,
3466+
}
3467+
3468+
// Create the auth secret even though it shouldn't be used
3469+
ociSecret := createDockerConfigJSONSecretForTest(t, ociAuthSecretName, namespace, map[string]map[string]string{
3470+
"registry.example.com": {
3471+
"username": "testuser",
3472+
"password": "testpass",
3473+
},
3474+
})
3475+
3476+
r := newTestReconciler(t, host, ociSecret)
3477+
3478+
credentials, err := r.getImageAuthSecret(t.Context(), host, host.Spec.Image)
3479+
3480+
require.NoError(t, err)
3481+
assert.Empty(t, credentials, "expected empty credentials for non-OCI image")
3482+
}
3483+
3484+
// TestGetImageAuthSecret_NilImage tests that the function handles nil image gracefully.
3485+
func TestGetImageAuthSecret_NilImage(t *testing.T) {
3486+
host := newDefaultHost(t)
3487+
host.Spec.Online = true
3488+
host.Spec.Image = nil
3489+
3490+
r := newTestReconciler(t, host)
3491+
3492+
credentials, err := r.getImageAuthSecret(t.Context(), host, host.Spec.Image)
3493+
3494+
require.NoError(t, err)
3495+
assert.Empty(t, credentials, "expected empty credentials for nil image")
3496+
}
3497+
3498+
// TestGetImageAuthSecret_RegistryMismatch tests the behavior when the auth secret
3499+
// doesn't contain credentials for the image's registry.
3500+
func TestGetImageAuthSecret_RegistryMismatch(t *testing.T) {
3501+
host := newDefaultHost(t)
3502+
host.Spec.Online = true
3503+
ociAuthSecretName := "oci-auth-secret"
3504+
host.Spec.Image = &metal3api.Image{
3505+
URL: "oci://registry.example.com/repo/image:tag",
3506+
OCIAuthSecretName: &ociAuthSecretName,
3507+
}
3508+
3509+
// Create secret with credentials for a different registry
3510+
ociSecret := createDockerConfigJSONSecretForTest(t, ociAuthSecretName, namespace, map[string]map[string]string{
3511+
"different-registry.com": {
3512+
"username": "testuser",
3513+
"password": "testpass",
3514+
},
3515+
})
3516+
3517+
r := newTestReconciler(t, host, ociSecret)
3518+
3519+
credentials, err := r.getImageAuthSecret(t.Context(), host, host.Spec.Image)
3520+
3521+
require.Error(t, err, "expected error when registry doesn't match")
3522+
assert.Empty(t, credentials, "expected empty credentials when registry doesn't match")
3523+
}
3524+
3525+
// Helper function to create a dockerconfigjson secret for testing.
3526+
func createDockerConfigJSONSecretForTest(t *testing.T, name, ns string, auths map[string]map[string]string) *corev1.Secret {
3527+
t.Helper()
3528+
dockerAuths := make(map[string]interface{})
3529+
for registry, creds := range auths {
3530+
username := creds["username"]
3531+
password := creds["password"]
3532+
// Encode credentials as base64("username:password") in the Auth field
3533+
auth := base64.StdEncoding.EncodeToString([]byte(username + ":" + password))
3534+
dockerAuths[registry] = map[string]string{
3535+
"auth": auth,
3536+
}
3537+
}
3538+
3539+
dockerConfig := map[string]interface{}{
3540+
"auths": dockerAuths,
3541+
}
3542+
dockerConfigJSON, err := json.Marshal(dockerConfig)
3543+
require.NoError(t, err)
3544+
3545+
return &corev1.Secret{
3546+
ObjectMeta: metav1.ObjectMeta{
3547+
Name: name,
3548+
Namespace: ns,
3549+
},
3550+
Type: corev1.SecretTypeDockerConfigJson,
3551+
Data: map[string][]byte{
3552+
corev1.DockerConfigJsonKey: dockerConfigJSON,
3553+
},
3554+
}
3555+
}

0 commit comments

Comments
 (0)