diff --git a/cmd/validate/vsa.go b/cmd/validate/vsa.go index d6171751e..7e6d4f489 100644 --- a/cmd/validate/vsa.go +++ b/cmd/validate/vsa.go @@ -452,14 +452,9 @@ func performVSAValidationForSingle(ctx context.Context, identifier string, data } // handleFallbackValidation handles the fallback validation logic -func handleFallbackValidation(ctx context.Context, identifier string, result *vsa.ValidationResult, data *validateVSAData, fs afero.Fs) error { +func handleFallbackValidation(ctx context.Context, imageRef string, result *vsa.ValidationResult, data *validateVSAData, fs afero.Fs) error { printVSAInfo(os.Stdout, "Falling back to image validation...") - - // Extract image reference from VSA identifier for fallback - imageRef, extractErr := vsa.ExtractImageFromVSAIdentifier(identifier) - if extractErr != nil { - return fmt.Errorf("fallback validation not supported for file paths: %s", identifier) - } + printVSAInfo(os.Stdout, fmt.Sprintf("Image reference: %s", imageRef)) // Create worker context for fallback validation workerFallbackContext, workerErr := vsa.CreateWorkerFallbackContext(ctx, data.fallbackContext.FallbackPolicy) @@ -467,17 +462,8 @@ func handleFallbackValidation(ctx context.Context, identifier string, result *vs return fmt.Errorf("failed to create fallback context: %w", workerErr) } - // Create fallback config - fallbackConfig := &vsa.FallbackConfig{ - FallbackToImageValidation: data.fallbackToImageValidation, - FallbackPublicKey: data.fallbackPublicKey, - PolicyConfig: data.policyConfig, - EffectiveTime: data.effectiveTime, - Info: data.info, - } - // Use the common fallback validation logic - fallbackResult := vsa.PerformFallbackValidation(ctx, fallbackConfig, data.fallbackContext, imageRef, "single-vsa-component", result, "", workerFallbackContext) + fallbackResult := vsa.PerformFallbackValidation(result, "") if fallbackResult.Error != nil { return fallbackResult.Error } @@ -831,6 +817,7 @@ func validateImageFallbackWithWorkerContext(ctx context.Context, data *validateV // Perform image validation using precomputed context and worker-specific evaluators log.Debugf("🔄 Fallback: Starting image validation...") + log.Debugf("🔄 Fallback: Using fallback policy: %s", data.fallbackContext.FallbackPolicy) result, err := image.ValidateImage(ctx, comp, spec, data.fallbackContext.FallbackPolicy, workerFallbackContext.Evaluators, data.info) if err != nil { log.Debugf("🔄 Fallback: Image validation failed with error: %v", err) @@ -910,17 +897,8 @@ func handleComponentFallback(ctx context.Context, component app.SnapshotComponen predicateStatus = result.PredicateOutcome } - // Create fallback config - fallbackConfig := &vsa.FallbackConfig{ - FallbackToImageValidation: data.fallbackToImageValidation, - FallbackPublicKey: data.fallbackPublicKey, - PolicyConfig: data.policyConfig, - EffectiveTime: data.effectiveTime, - Info: data.info, - } - // Use the common fallback validation logic - fallbackResult := vsa.PerformFallbackValidation(ctx, fallbackConfig, data.fallbackContext, imageRef, component.Name, result, predicateStatus, workerFallbackContext) + fallbackResult := vsa.PerformFallbackValidation(result, predicateStatus) if fallbackResult.Error != nil { return createErrorResult(component, fallbackResult.Error) } diff --git a/cmd/validate/vsa_test.go b/cmd/validate/vsa_test.go index 4ad47b5ad..8c7072921 100644 --- a/cmd/validate/vsa_test.go +++ b/cmd/validate/vsa_test.go @@ -35,7 +35,6 @@ import ( "github.com/spf13/cobra" "github.com/stretchr/testify/assert" - "github.com/conforma/cli/internal/evaluator" "github.com/conforma/cli/internal/output" "github.com/conforma/cli/internal/validate/vsa" ) @@ -1779,25 +1778,15 @@ func (m *mockOutputFormatter) PrintText(writer io.Writer) error { // TestPerformFallbackValidation tests the extracted fallback validation function func TestPerformFallbackValidation(t *testing.T) { - ctx := context.Background() - - // Create a mock worker fallback context - workerContext := &vsa.WorkerFallbackContext{ - Evaluators: []evaluator.Evaluator{}, // Empty for testing - } tests := []struct { name string - imageRef string - componentName string result *vsa.ValidationResult predicateStatus string expectError bool }{ { - name: "successful fallback with VSA result", - imageRef: "test-image:latest", - componentName: "test-component", + name: "successful fallback with VSA result", result: &vsa.ValidationResult{ Passed: false, Message: "VSA validation failed", @@ -1807,16 +1796,12 @@ func TestPerformFallbackValidation(t *testing.T) { }, { name: "successful fallback without VSA result", - imageRef: "test-image:latest", - componentName: "test-component", result: nil, predicateStatus: "failed", expectError: false, }, { name: "fallback with empty predicate status", - imageRef: "test-image:latest", - componentName: "test-component", result: nil, predicateStatus: "", expectError: false, @@ -1827,16 +1812,7 @@ func TestPerformFallbackValidation(t *testing.T) { t.Run(tt.name, func(t *testing.T) { // Note: This test will fail in practice because it requires actual // fallback context and evaluators, but it tests the function structure - // Create fallback config - fallbackConfig := &vsa.FallbackConfig{ - FallbackToImageValidation: true, - FallbackPublicKey: "test-key", - PolicyConfig: "test-policy", - EffectiveTime: "2023-01-01T00:00:00Z", - Info: false, - } - - fallbackResult := vsa.PerformFallbackValidation(ctx, fallbackConfig, nil, tt.imageRef, tt.componentName, tt.result, tt.predicateStatus, workerContext) + fallbackResult := vsa.PerformFallbackValidation(tt.result, tt.predicateStatus) if tt.expectError { assert.Error(t, fallbackResult.Error) @@ -1852,19 +1828,8 @@ func TestPerformFallbackValidation(t *testing.T) { // TestPerformFallbackValidation_ErrorHandling tests error handling in fallback validation func TestPerformFallbackValidation_ErrorHandling(t *testing.T) { - ctx := context.Background() - // Test with nil worker context - should not cause error as function only handles VSA result logic - // Create fallback config - fallbackConfig := &vsa.FallbackConfig{ - FallbackToImageValidation: true, - FallbackPublicKey: "test-key", - PolicyConfig: "test-policy", - EffectiveTime: "2023-01-01T00:00:00Z", - Info: false, - } - - fallbackResult := vsa.PerformFallbackValidation(ctx, fallbackConfig, nil, "test-image:latest", "test-component", nil, "failed", nil) + fallbackResult := vsa.PerformFallbackValidation(nil, "failed") // The function now only handles VSA result logic, so it should not return an error assert.NoError(t, fallbackResult.Error) assert.NotNil(t, fallbackResult.VSAResult) diff --git a/internal/validate/vsa/fallback.go b/internal/validate/vsa/fallback.go index e9f9233df..b505eefe3 100644 --- a/internal/validate/vsa/fallback.go +++ b/internal/validate/vsa/fallback.go @@ -83,7 +83,7 @@ func ShouldTriggerFallback(err error, result *ValidationResult) bool { // performFallbackValidation performs the common fallback validation logic // Note: This function now only handles the VSA result logic, image validation is handled in CLI layer -func PerformFallbackValidation(ctx context.Context, config *FallbackConfig, fallbackContext *FallbackValidationContext, imageRef string, componentName string, result *ValidationResult, predicateStatus string, workerFallbackContext *WorkerFallbackContext) *FallbackResult { +func PerformFallbackValidation(result *ValidationResult, predicateStatus string) *FallbackResult { // Use the actual VSA result for fallback case // If we have a result, use it; otherwise create a minimal result var vsaResult *ValidationResult diff --git a/internal/validate/vsa/fallback_test.go b/internal/validate/vsa/fallback_test.go index c7ad9855b..8cd0bdb32 100644 --- a/internal/validate/vsa/fallback_test.go +++ b/internal/validate/vsa/fallback_test.go @@ -113,34 +113,15 @@ func TestShouldTriggerFallback(t *testing.T) { } func TestPerformFallbackValidation(t *testing.T) { - ctx := context.Background() - tests := []struct { - name string - config *FallbackConfig - fallbackContext *FallbackValidationContext - imageRef string - componentName string - result *ValidationResult - predicateStatus string - workerFallbackContext *WorkerFallbackContext - expectedVSAResult *ValidationResult - expectedError error + name string + result *ValidationResult + predicateStatus string + expectedVSAResult *ValidationResult + expectedError error }{ { name: "With VSA result - should use provided result", - config: &FallbackConfig{ - FallbackToImageValidation: true, - FallbackPublicKey: "test-key", - PolicyConfig: "test-policy", - EffectiveTime: "2023-01-01T00:00:00Z", - Info: false, - }, - fallbackContext: &FallbackValidationContext{ - PolicyConfiguration: "test-policy", - }, - imageRef: "test-image:latest", - componentName: "test-component", result: &ValidationResult{ Passed: false, Message: "VSA validation failed", @@ -148,9 +129,6 @@ func TestPerformFallbackValidation(t *testing.T) { PredicateOutcome: "failed", }, predicateStatus: "failed", - workerFallbackContext: &WorkerFallbackContext{ - Evaluators: []evaluator.Evaluator{}, - }, expectedVSAResult: &ValidationResult{ Passed: false, Message: "VSA validation failed", @@ -160,24 +138,9 @@ func TestPerformFallbackValidation(t *testing.T) { expectedError: nil, }, { - name: "Without VSA result - should create minimal result", - config: &FallbackConfig{ - FallbackToImageValidation: true, - FallbackPublicKey: "test-key", - PolicyConfig: "test-policy", - EffectiveTime: "2023-01-01T00:00:00Z", - Info: false, - }, - fallbackContext: &FallbackValidationContext{ - PolicyConfiguration: "test-policy", - }, - imageRef: "test-image:latest", - componentName: "test-component", + name: "Without VSA result - should create minimal result", result: nil, // No VSA result predicateStatus: "failed", - workerFallbackContext: &WorkerFallbackContext{ - Evaluators: []evaluator.Evaluator{}, - }, expectedVSAResult: &ValidationResult{ Passed: false, Message: "VSA validation failed", @@ -188,18 +151,6 @@ func TestPerformFallbackValidation(t *testing.T) { }, { name: "With successful VSA result - should use provided result", - config: &FallbackConfig{ - FallbackToImageValidation: true, - FallbackPublicKey: "test-key", - PolicyConfig: "test-policy", - EffectiveTime: "2023-01-01T00:00:00Z", - Info: false, - }, - fallbackContext: &FallbackValidationContext{ - PolicyConfiguration: "test-policy", - }, - imageRef: "test-image:latest", - componentName: "test-component", result: &ValidationResult{ Passed: true, Message: "VSA validation passed", @@ -207,9 +158,6 @@ func TestPerformFallbackValidation(t *testing.T) { PredicateOutcome: "passed", }, predicateStatus: "passed", - workerFallbackContext: &WorkerFallbackContext{ - Evaluators: []evaluator.Evaluator{}, - }, expectedVSAResult: &ValidationResult{ Passed: true, Message: "VSA validation passed", @@ -223,14 +171,8 @@ func TestPerformFallbackValidation(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { fallbackResult := PerformFallbackValidation( - ctx, - tt.config, - tt.fallbackContext, - tt.imageRef, - tt.componentName, tt.result, tt.predicateStatus, - tt.workerFallbackContext, ) // Verify the result structure @@ -426,7 +368,6 @@ func TestShouldTriggerFallback_EdgeCases(t *testing.T) { // Test PerformFallbackValidation with different predicate statuses func TestPerformFallbackValidation_PredicateStatuses(t *testing.T) { - ctx := context.Background() tests := []struct { name string @@ -461,23 +402,9 @@ func TestPerformFallbackValidation_PredicateStatuses(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - config := &FallbackConfig{ - FallbackToImageValidation: true, - FallbackPublicKey: "test-key", - PolicyConfig: "test-policy", - EffectiveTime: "2023-01-01T00:00:00Z", - Info: false, - } - fallbackResult := PerformFallbackValidation( - ctx, - config, - nil, // fallbackContext - "test-image:latest", - "test-component", tt.result, tt.predicateStatus, - nil, // workerFallbackContext ) require.NotNil(t, fallbackResult) diff --git a/internal/validate/vsa/vsa.go b/internal/validate/vsa/vsa.go index 035e301d3..b85d56f78 100644 --- a/internal/validate/vsa/vsa.go +++ b/internal/validate/vsa/vsa.go @@ -931,36 +931,6 @@ func ExtractDigestFromImageRef(imageRef string) (string, error) { return imageRef, nil } -// ExtractImageFromVSAIdentifier extracts the image reference from VSA identifier -// This function is used for fallback validation when VSA validation fails -func ExtractImageFromVSAIdentifier(identifier string) (string, error) { - // Handle empty identifier specially - if identifier == "" { - return "", nil - } - - identifierType := DetectIdentifierType(identifier) - - switch identifierType { - case IdentifierImageDigest: - // Convert digest to image reference - // e.g., registry/image@sha256:abc123 -> registry/image:tag - return ConvertDigestToImageRef(identifier) - case IdentifierImageReference: - // Check if it's actually a file path that was incorrectly detected as image reference - if IsFilePathLike(identifier) { - return "", fmt.Errorf("fallback validation not supported for file paths: %s", identifier) - } - // Already an image reference - return identifier, nil - case IdentifierFile: - // Cannot extract image from file path - fallback not supported - return "", fmt.Errorf("fallback validation not supported for file paths: %s", identifier) - default: - return "", fmt.Errorf("fallback validation not supported for identifier type: %s", identifier) - } -} - // isFilePathLike checks if an identifier looks like a file path // This handles the case where name.ParseReference incorrectly accepts file paths as valid image references func IsFilePathLike(identifier string) bool { @@ -987,28 +957,3 @@ func IsFilePathLike(identifier string) bool { return false } - -// ConvertDigestToImageRef converts a digest to an image reference -// This is a simplified implementation that attempts to construct a reasonable image reference -func ConvertDigestToImageRef(digest string) (string, error) { - // If the digest is already in the format registry/image@sha256:abc123, - // we can extract the repository part and construct a tag reference - if strings.Contains(digest, "@") { - parts := strings.Split(digest, "@") - if len(parts) == 2 { - repository := parts[0] - // Try to construct a reasonable tag reference - // This is a best-effort approach since we don't have the original tag - imageRef := repository + ":latest" - - // Validate that the constructed reference is valid - if _, err := name.ParseReference(imageRef); err == nil { - return imageRef, nil - } - } - } - - // If we can't convert the digest to an image reference, - // return an error as fallback is not supported for pure digests - return "", fmt.Errorf("fallback validation: cannot convert digest to image reference: %s", digest) -} diff --git a/internal/validate/vsa/vsa_test.go b/internal/validate/vsa/vsa_test.go index 146a61b59..1ac7af9a6 100644 --- a/internal/validate/vsa/vsa_test.go +++ b/internal/validate/vsa/vsa_test.go @@ -759,243 +759,6 @@ func TestWriterErrorHandling(t *testing.T) { }) } -// TestExtractImageFromVSAIdentifier_Comprehensive tests the image extraction function comprehensively -func TestExtractImageFromVSAIdentifier_Comprehensive(t *testing.T) { - tests := []struct { - name string - identifier string - expectedResult string - expectedError string - }{ - // Valid image references (should return as-is) - { - name: "image reference with tag", - identifier: "registry.com/image:latest", - expectedResult: "registry.com/image:latest", - expectedError: "", - }, - { - name: "docker hub reference", - identifier: "nginx:1.21", - expectedResult: "nginx:1.21", - expectedError: "", - }, - { - name: "quay reference with tag", - identifier: "quay.io/redhat-user-workloads/rhtap-contract-tenant/golden-container:latest", - expectedResult: "quay.io/redhat-user-workloads/rhtap-contract-tenant/golden-container:latest", - expectedError: "", - }, - { - name: "image reference with digest", - identifier: "registry.com/image@sha256:abc123def456", - expectedResult: "registry.com/image:latest", - expectedError: "", - }, - { - name: "docker hub with digest", - identifier: "nginx@sha256:abc123def456", - expectedResult: "nginx:latest", - expectedError: "", - }, - // Valid image digests (should be converted to image references) - { - name: "image digest with repository", - identifier: "registry.com/image@sha256:abc123def456", - expectedResult: "registry.com/image:latest", - expectedError: "", - }, - { - name: "docker hub digest", - identifier: "nginx@sha256:abc123def456", - expectedResult: "nginx:latest", - expectedError: "", - }, - { - name: "quay digest", - identifier: "quay.io/redhat-user-workloads/rhtap-contract-tenant/golden-container@sha256:185f6c39e5544479863024565bb7e63c6f2f0547c3ab4ddf99ac9b5755075cc9", - expectedResult: "quay.io/redhat-user-workloads/rhtap-contract-tenant/golden-container:latest", - expectedError: "", - }, - { - name: "registry with port digest", - identifier: "registry.com:5000/image@sha256:abc123def456", - expectedResult: "registry.com:5000/image:latest", - expectedError: "", - }, - { - name: "registry with namespace digest", - identifier: "registry.com/namespace/image@sha256:abc123def456", - expectedResult: "registry.com/namespace/image:latest", - expectedError: "", - }, - // Invalid cases (should return errors) - { - name: "file path", - identifier: "./vsa-file.json", - expectedResult: "", - expectedError: "fallback validation not supported for file paths", - }, - { - name: "absolute file path", - identifier: "/path/to/vsa-file.json", - expectedResult: "", - expectedError: "fallback validation not supported for file paths", - }, - { - name: "pure digest without repository", - identifier: "sha256:abc123def456", - expectedResult: "", - expectedError: "fallback validation: cannot convert digest to image reference", - }, - { - name: "invalid identifier", - identifier: "invalid-identifier", - expectedResult: "invalid-identifier", - expectedError: "", - }, - { - name: "empty identifier", - identifier: "", - expectedResult: "", - expectedError: "", - }, - { - name: "file with extension", - identifier: "vsa-file.json", - expectedResult: "", - expectedError: "fallback validation not supported for file paths", - }, - { - name: "relative path with parent", - identifier: "../vsa-file.json", - expectedResult: "", - expectedError: "fallback validation not supported for file paths", - }, - { - name: "relative path with dot", - identifier: "./vsa-file.json", - expectedResult: "", - expectedError: "fallback validation not supported for file paths", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result, err := ExtractImageFromVSAIdentifier(tt.identifier) - - if tt.expectedError != "" { - assert.Error(t, err) - assert.Contains(t, err.Error(), tt.expectedError) - assert.Equal(t, "", result) - } else { - assert.NoError(t, err) - assert.Equal(t, tt.expectedResult, result) - } - }) - } -} - -// TestConvertDigestToImageRef_Comprehensive tests the digest conversion function comprehensively -func TestConvertDigestToImageRef_Comprehensive(t *testing.T) { - tests := []struct { - name string - digest string - expectedResult string - expectedError string - }{ - // Valid digests with repository - { - name: "valid digest with repository", - digest: "registry.com/image@sha256:abc123def456", - expectedResult: "registry.com/image:latest", - expectedError: "", - }, - { - name: "docker hub digest", - digest: "nginx@sha256:abc123def456", - expectedResult: "nginx:latest", - expectedError: "", - }, - { - name: "quay digest", - digest: "quay.io/redhat-user-workloads/rhtap-contract-tenant/golden-container@sha256:185f6c39e5544479863024565bb7e63c6f2f0547c3ab4ddf99ac9b5755075cc9", - expectedResult: "quay.io/redhat-user-workloads/rhtap-contract-tenant/golden-container:latest", - expectedError: "", - }, - { - name: "registry with port", - digest: "registry.com:5000/image@sha256:abc123def456", - expectedResult: "registry.com:5000/image:latest", - expectedError: "", - }, - { - name: "registry with namespace", - digest: "registry.com/namespace/image@sha256:abc123def456", - expectedResult: "registry.com/namespace/image:latest", - expectedError: "", - }, - { - name: "sha512 digest", - digest: "registry.com/image@sha512:abc123def456", - expectedResult: "registry.com/image:latest", - expectedError: "", - }, - // Invalid cases - { - name: "pure digest without repository", - digest: "sha256:abc123def456", - expectedResult: "", - expectedError: "fallback validation: cannot convert digest to image reference", - }, - { - name: "invalid digest format", - digest: "invalid-digest", - expectedResult: "", - expectedError: "fallback validation: cannot convert digest to image reference", - }, - { - name: "empty digest", - digest: "", - expectedResult: "", - expectedError: "fallback validation: cannot convert digest to image reference", - }, - { - name: "digest without algorithm", - digest: "abc123def456", - expectedResult: "", - expectedError: "fallback validation: cannot convert digest to image reference", - }, - { - name: "digest with invalid algorithm", - digest: "registry.com/image@md5:abc123def456", - expectedResult: "registry.com/image:latest", - expectedError: "", - }, - { - name: "digest with invalid hash", - digest: "registry.com/image@sha256:invalid-hash", - expectedResult: "registry.com/image:latest", - expectedError: "", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result, err := ConvertDigestToImageRef(tt.digest) - - if tt.expectedError != "" { - assert.Error(t, err) - assert.Contains(t, err.Error(), tt.expectedError) - assert.Equal(t, "", result) - } else { - assert.NoError(t, err) - assert.Equal(t, tt.expectedResult, result) - } - }) - } -} - // TestDetectIdentifierType_Comprehensive tests the identifier type detection comprehensively func TestDetectIdentifierType_Comprehensive(t *testing.T) { tests := []struct {