From 435ef4641b778aaf7a2df35d7334365d6732bf53 Mon Sep 17 00:00:00 2001 From: Daniel Liszka Date: Tue, 30 May 2023 18:38:24 +0200 Subject: [PATCH 01/10] feat: Add support fot JUnit XML material type (#120) Signed-off-by: Daniel Liszka --- .../workflowcontract/v1/crafting_schema.ts | 8 ++++- .../workflowcontract/v1/crafting_schema.pb.go | 24 ++++++++------- .../workflowcontract/v1/crafting_schema.proto | 1 + go.mod | 1 + go.sum | 2 ++ .../crafter/materials/materials.go | 30 +++++++++++++++++++ 6 files changed, 55 insertions(+), 11 deletions(-) diff --git a/app/controlplane/api/gen/frontend/workflowcontract/v1/crafting_schema.ts b/app/controlplane/api/gen/frontend/workflowcontract/v1/crafting_schema.ts index 670945c83..830ddabcf 100644 --- a/app/controlplane/api/gen/frontend/workflowcontract/v1/crafting_schema.ts +++ b/app/controlplane/api/gen/frontend/workflowcontract/v1/crafting_schema.ts @@ -73,8 +73,9 @@ export enum CraftingSchema_Material_MaterialType { CONTAINER_IMAGE = 2, ARTIFACT = 3, SBOM_CYCLONEDX_JSON = 4, - /** SBOM_SPDX_JSON - SARIF = 5; */ SBOM_SPDX_JSON = 5, + /** JUNIT_XML - SARIF = 5; */ + JUNIT_XML = 6, UNRECOGNIZED = -1, } @@ -98,6 +99,9 @@ export function craftingSchema_Material_MaterialTypeFromJSON(object: any): Craft case 5: case "SBOM_SPDX_JSON": return CraftingSchema_Material_MaterialType.SBOM_SPDX_JSON; + case 6: + case "JUNIT_XML": + return CraftingSchema_Material_MaterialType.JUNIT_XML; case -1: case "UNRECOGNIZED": default: @@ -119,6 +123,8 @@ export function craftingSchema_Material_MaterialTypeToJSON(object: CraftingSchem return "SBOM_CYCLONEDX_JSON"; case CraftingSchema_Material_MaterialType.SBOM_SPDX_JSON: return "SBOM_SPDX_JSON"; + case CraftingSchema_Material_MaterialType.JUNIT_XML: + return "JUNIT_XML"; case CraftingSchema_Material_MaterialType.UNRECOGNIZED: default: return "UNRECOGNIZED"; diff --git a/app/controlplane/api/workflowcontract/v1/crafting_schema.pb.go b/app/controlplane/api/workflowcontract/v1/crafting_schema.pb.go index b704c4e64..557b9ef45 100644 --- a/app/controlplane/api/workflowcontract/v1/crafting_schema.pb.go +++ b/app/controlplane/api/workflowcontract/v1/crafting_schema.pb.go @@ -93,7 +93,8 @@ const ( CraftingSchema_Material_CONTAINER_IMAGE CraftingSchema_Material_MaterialType = 2 CraftingSchema_Material_ARTIFACT CraftingSchema_Material_MaterialType = 3 CraftingSchema_Material_SBOM_CYCLONEDX_JSON CraftingSchema_Material_MaterialType = 4 - CraftingSchema_Material_SBOM_SPDX_JSON CraftingSchema_Material_MaterialType = 5 // SARIF = 5; + CraftingSchema_Material_SBOM_SPDX_JSON CraftingSchema_Material_MaterialType = 5 + CraftingSchema_Material_JUNIT_XML CraftingSchema_Material_MaterialType = 6 // SARIF = 5; ) // Enum value maps for CraftingSchema_Material_MaterialType. @@ -105,6 +106,7 @@ var ( 3: "ARTIFACT", 4: "SBOM_CYCLONEDX_JSON", 5: "SBOM_SPDX_JSON", + 6: "JUNIT_XML", } CraftingSchema_Material_MaterialType_value = map[string]int32{ "MATERIAL_TYPE_UNSPECIFIED": 0, @@ -113,6 +115,7 @@ var ( "ARTIFACT": 3, "SBOM_CYCLONEDX_JSON": 4, "SBOM_SPDX_JSON": 5, + "JUNIT_XML": 6, } ) @@ -344,7 +347,7 @@ var file_workflowcontract_v1_crafting_schema_proto_rawDesc = []byte{ 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2e, 0x76, 0x31, 0x1a, 0x17, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, - 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xf7, 0x05, 0x0a, 0x0e, 0x43, 0x72, + 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x86, 0x06, 0x0a, 0x0e, 0x43, 0x72, 0x61, 0x66, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, 0x30, 0x0a, 0x0e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x09, 0xfa, 0x42, 0x06, 0x72, 0x04, 0x0a, 0x02, 0x76, 0x31, 0x52, @@ -371,7 +374,7 @@ var file_workflowcontract_v1_crafting_schema_proto_rawDesc = []byte{ 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x47, 0x49, 0x54, 0x48, 0x55, 0x42, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x47, 0x49, 0x54, 0x4c, 0x41, 0x42, 0x5f, 0x50, 0x49, 0x50, - 0x45, 0x4c, 0x49, 0x4e, 0x45, 0x10, 0x02, 0x1a, 0xc9, 0x02, 0x0a, 0x08, 0x4d, 0x61, 0x74, 0x65, + 0x45, 0x4c, 0x49, 0x4e, 0x45, 0x10, 0x02, 0x1a, 0xd8, 0x02, 0x0a, 0x08, 0x4d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x12, 0x57, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x39, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x61, 0x66, 0x74, 0x69, 0x6e, @@ -383,7 +386,7 @@ var file_workflowcontract_v1_crafting_schema_proto_rawDesc = []byte{ 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x22, 0x89, 0x01, 0x0a, 0x0c, 0x4d, 0x61, 0x74, 0x65, + 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x22, 0x98, 0x01, 0x0a, 0x0c, 0x4d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x19, 0x4d, 0x41, 0x54, 0x45, 0x52, 0x49, 0x41, 0x4c, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x54, 0x52, 0x49, 0x4e, @@ -392,12 +395,13 @@ var file_workflowcontract_v1_crafting_schema_proto_rawDesc = []byte{ 0x46, 0x41, 0x43, 0x54, 0x10, 0x03, 0x12, 0x17, 0x0a, 0x13, 0x53, 0x42, 0x4f, 0x4d, 0x5f, 0x43, 0x59, 0x43, 0x4c, 0x4f, 0x4e, 0x45, 0x44, 0x58, 0x5f, 0x4a, 0x53, 0x4f, 0x4e, 0x10, 0x04, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x42, 0x4f, 0x4d, 0x5f, 0x53, 0x50, 0x44, 0x58, 0x5f, 0x4a, 0x53, 0x4f, - 0x4e, 0x10, 0x05, 0x42, 0x4d, 0x5a, 0x4b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, - 0x6d, 0x2f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x6c, 0x6f, 0x6f, 0x70, 0x2d, 0x64, 0x65, 0x76, 0x2f, - 0x63, 0x68, 0x61, 0x69, 0x6e, 0x6c, 0x6f, 0x6f, 0x70, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x63, 0x6f, - 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x77, - 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2f, - 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x4e, 0x10, 0x05, 0x12, 0x0d, 0x0a, 0x09, 0x4a, 0x55, 0x4e, 0x49, 0x54, 0x5f, 0x58, 0x4d, 0x4c, + 0x10, 0x06, 0x42, 0x4d, 0x5a, 0x4b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x6c, 0x6f, 0x6f, 0x70, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x63, + 0x68, 0x61, 0x69, 0x6e, 0x6c, 0x6f, 0x6f, 0x70, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x63, 0x6f, 0x6e, + 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x77, 0x6f, + 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2f, 0x76, + 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/app/controlplane/api/workflowcontract/v1/crafting_schema.proto b/app/controlplane/api/workflowcontract/v1/crafting_schema.proto index fbc13456e..cdfd227d6 100644 --- a/app/controlplane/api/workflowcontract/v1/crafting_schema.proto +++ b/app/controlplane/api/workflowcontract/v1/crafting_schema.proto @@ -54,6 +54,7 @@ message CraftingSchema { ARTIFACT = 3; SBOM_CYCLONEDX_JSON = 4; SBOM_SPDX_JSON = 5; + JUNIT_XML = 6; // SARIF = 5; } } diff --git a/go.mod b/go.mod index 8e1015487..f879da6d1 100644 --- a/go.mod +++ b/go.mod @@ -162,6 +162,7 @@ require ( github.com/jackc/pgtype v1.14.0 // indirect github.com/jedisct1/go-minisign v0.0.0-20211028175153-1c139d1cc84b // indirect github.com/josharian/intern v1.0.0 // indirect + github.com/joshdk/go-junit v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.16.0 // indirect github.com/leodido/go-urn v1.2.2 // indirect diff --git a/go.sum b/go.sum index e05df420e..c7a34c94d 100644 --- a/go.sum +++ b/go.sum @@ -744,6 +744,8 @@ github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqx github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE= +github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= diff --git a/internal/attestation/crafter/materials/materials.go b/internal/attestation/crafter/materials/materials.go index e46ec2875..47174e679 100644 --- a/internal/attestation/crafter/materials/materials.go +++ b/internal/attestation/crafter/materials/materials.go @@ -18,11 +18,13 @@ package materials import ( "context" "fmt" + "time" api "github.com/chainloop-dev/chainloop/app/cli/api/attestation/v1" schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1" "github.com/chainloop-dev/chainloop/internal/casclient" "github.com/rs/zerolog" + "google.golang.org/protobuf/types/known/timestamppb" ) // ErrInvalidMaterialType is returned when the provided material type @@ -34,6 +36,32 @@ type crafterCommon struct { input *schemaapi.CraftingSchema_Material } +type crafterUploader struct { + *crafterCommon + uploader casclient.Uploader +} + +// Craft will calculate the digest of the artifact, simulate an upload and return the material definition +func (i *crafterUploader) Craft(ctx context.Context, artifactPath string) (*api.Attestation_Material, error) { + result, err := i.uploader.UploadFile(ctx, artifactPath) + if err != nil { + i.logger.Debug().Err(err) + return nil, err + } + + res := &api.Attestation_Material{ + AddedAt: timestamppb.New(time.Now()), + MaterialType: i.input.Type, + M: &api.Attestation_Material_Artifact_{ + Artifact: &api.Attestation_Material_Artifact{ + Id: i.input.Name, Digest: result.Digest, Name: result.Filename, IsSubject: i.input.Output, + }, + }, + } + + return res, nil +} + type Craftable interface { Craft(ctx context.Context, value string) (*api.Attestation_Material, error) } @@ -53,6 +81,8 @@ func Craft(ctx context.Context, materialSchema *schemaapi.CraftingSchema_Materia crafter, err = NewCyclonedxJSONCrafter(materialSchema, uploader, logger) case schemaapi.CraftingSchema_Material_SBOM_SPDX_JSON: crafter, err = NewSPDXJSONCrafter(materialSchema, uploader, logger) + case schemaapi.CraftingSchema_Material_JUNIT_XML: + crafter, err = NewJUnitXMLCrafter(materialSchema, uploader, logger) default: return nil, fmt.Errorf("material of type %q not supported yet", materialSchema.Type) } From 4e7bd9015a72cfaca50aee12e5e017214ea5d460 Mon Sep 17 00:00:00 2001 From: Daniel Liszka Date: Tue, 30 May 2023 23:51:38 +0200 Subject: [PATCH 02/10] feat: Add support fot JUnit XML material type (#120) - added tests Signed-off-by: Daniel Liszka --- .../attestation/crafter/materials/artifact.go | 1 - .../crafter/materials/junit_xml.go | 74 ++++++++++ .../crafter/materials/junit_xml_test.go | 135 ++++++++++++++++++ .../crafter/materials/materials.go | 3 +- .../materials/testdata/junit-invalid.xml | 22 +++ .../crafter/materials/testdata/junit.xml | 22 +++ 6 files changed, 254 insertions(+), 3 deletions(-) create mode 100644 internal/attestation/crafter/materials/junit_xml.go create mode 100644 internal/attestation/crafter/materials/junit_xml_test.go create mode 100644 internal/attestation/crafter/materials/testdata/junit-invalid.xml create mode 100644 internal/attestation/crafter/materials/testdata/junit.xml diff --git a/internal/attestation/crafter/materials/artifact.go b/internal/attestation/crafter/materials/artifact.go index 8c37573f9..ddd04e9ce 100644 --- a/internal/attestation/crafter/materials/artifact.go +++ b/internal/attestation/crafter/materials/artifact.go @@ -41,7 +41,6 @@ func NewArtifactCrafter(schema *schemaapi.CraftingSchema_Material, uploader casc return &ArtifactCrafter{uploader: uploader, crafterCommon: craftCommon}, nil } -// Craft will calculate the digest of the artifact, simulate an upload and return the material definition func (i *ArtifactCrafter) Craft(ctx context.Context, artifactPath string) (*api.Attestation_Material, error) { result, err := i.uploader.UploadFile(ctx, artifactPath) if err != nil { diff --git a/internal/attestation/crafter/materials/junit_xml.go b/internal/attestation/crafter/materials/junit_xml.go new file mode 100644 index 000000000..1ea1008f4 --- /dev/null +++ b/internal/attestation/crafter/materials/junit_xml.go @@ -0,0 +1,74 @@ +// +// Copyright 2023 The Chainloop Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package materials + +import ( + "context" + "fmt" + "io" + "os" + + "encoding/xml" + + api "github.com/chainloop-dev/chainloop/app/cli/api/attestation/v1" + schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1" + "github.com/chainloop-dev/chainloop/internal/casclient" + junit "github.com/joshdk/go-junit" + "github.com/rs/zerolog" +) + +type JUnitXML struct { + *crafterUploader +} + +func NewJUnitXMLCrafter(materialSchema *schemaapi.CraftingSchema_Material, uploader casclient.Uploader, l *zerolog.Logger) (*JUnitXML, error) { + if materialSchema.Type != schemaapi.CraftingSchema_Material_JUNIT_XML { + return nil, fmt.Errorf("material type is not JUnit XML") + } + + return &JUnitXML{ + &crafterUploader{ + uploader: uploader, + crafterCommon: &crafterCommon{ + logger: l, + input: materialSchema, + }, + }, + }, nil +} + +func (i *JUnitXML) Craft(ctx context.Context, filePath string) (*api.Attestation_Material, error) { + f, err := os.Open(filePath) + if err != nil { + return nil, fmt.Errorf("can't open the file: %w", err) + } + defer f.Close() + + bytes, err := io.ReadAll(f) + if err != nil { + return nil, fmt.Errorf("can't read the file: %w", err) + } + if err := xml.Unmarshal(bytes, &junit.Suite{}); err != nil { + return nil, fmt.Errorf("invalid JUnit XML file: %w", ErrInvalidMaterialType) + } + _, err = junit.IngestReader(f) + if err != nil { + i.logger.Debug().Err(err).Msg("error decoding file: " + filePath) + return nil, fmt.Errorf("invalid JUnit XML file: %w", ErrInvalidMaterialType) + } + + return i.crafterUploader.craft(ctx, filePath) +} diff --git a/internal/attestation/crafter/materials/junit_xml_test.go b/internal/attestation/crafter/materials/junit_xml_test.go new file mode 100644 index 000000000..602c2af84 --- /dev/null +++ b/internal/attestation/crafter/materials/junit_xml_test.go @@ -0,0 +1,135 @@ +// +// Copyright 2023 The Chainloop Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package materials_test + +import ( + "context" + "testing" + "time" + + attestationApi "github.com/chainloop-dev/chainloop/app/cli/api/attestation/v1" + contractAPI "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1" + "github.com/chainloop-dev/chainloop/internal/attestation/crafter/materials" + "github.com/chainloop-dev/chainloop/internal/casclient" + mUploader "github.com/chainloop-dev/chainloop/internal/casclient/mocks" + "github.com/rs/zerolog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewJUnitXMLCrafter(t *testing.T) { + testCases := []struct { + name string + input *contractAPI.CraftingSchema_Material + wantErr bool + }{ + { + name: "happy path", + input: &contractAPI.CraftingSchema_Material{ + Type: contractAPI.CraftingSchema_Material_JUNIT_XML, + }, + }, + { + name: "wrong type", + input: &contractAPI.CraftingSchema_Material{ + Type: contractAPI.CraftingSchema_Material_CONTAINER_IMAGE, + }, + wantErr: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + _, err := materials.NewJUnitXMLCrafter(tc.input, nil, nil) + if tc.wantErr { + assert.Error(t, err) + return + } + + assert.NoError(t, err) + }) + } +} +func TestJUnitXMLCraft(t *testing.T) { + testCases := []struct { + name string + filePath string + wantErr string + }{ + { + name: "invalid path", + filePath: "./testdata/non-existing.json", + wantErr: "no such file or directory", + }, + { + name: "invalid JUnit XML format", + filePath: "./testdata/sbom.spdx.json", + wantErr: "unexpected material type", + }, + { + name: "invalid artifact type", + filePath: "./testdata/simple.txt", + wantErr: "unexpected material type", + }, + { + name: "invalid artifact type", + filePath: "./testdata/junit-invalid.xml", + wantErr: "unexpected material type", + }, + { + name: "valid artifact type", + filePath: "./testdata/junit.xml", + }, + } + + assert := assert.New(t) + schema := &contractAPI.CraftingSchema_Material{ + Name: "test", + Type: contractAPI.CraftingSchema_Material_JUNIT_XML, + } + l := zerolog.Nop() + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Mock uploader + uploader := mUploader.NewUploader(t) + if tc.wantErr == "" { + uploader.On("UploadFile", context.TODO(), tc.filePath). + Return(&casclient.UpDownStatus{ + Digest: "deadbeef", + Filename: "test.xml", + }, nil) + } + + crafter, err := materials.NewJUnitXMLCrafter(schema, uploader, &l) + require.NoError(t, err) + + got, err := crafter.Craft(context.TODO(), tc.filePath) + if tc.wantErr != "" { + assert.ErrorContains(err, tc.wantErr) + return + } + + require.NoError(t, err) + assert.Equal(contractAPI.CraftingSchema_Material_JUNIT_XML.String(), got.MaterialType.String()) + assert.WithinDuration(time.Now(), got.AddedAt.AsTime(), 5*time.Second) + + // The result includes the digest reference + assert.Equal(got.GetArtifact(), &attestationApi.Attestation_Material_Artifact{ + Id: "test", Digest: "deadbeef", Name: "test.xml", + }) + }) + } +} diff --git a/internal/attestation/crafter/materials/materials.go b/internal/attestation/crafter/materials/materials.go index 47174e679..112e05b48 100644 --- a/internal/attestation/crafter/materials/materials.go +++ b/internal/attestation/crafter/materials/materials.go @@ -41,8 +41,7 @@ type crafterUploader struct { uploader casclient.Uploader } -// Craft will calculate the digest of the artifact, simulate an upload and return the material definition -func (i *crafterUploader) Craft(ctx context.Context, artifactPath string) (*api.Attestation_Material, error) { +func (i *crafterUploader) craft(ctx context.Context, artifactPath string) (*api.Attestation_Material, error) { result, err := i.uploader.UploadFile(ctx, artifactPath) if err != nil { i.logger.Debug().Err(err) diff --git a/internal/attestation/crafter/materials/testdata/junit-invalid.xml b/internal/attestation/crafter/materials/testdata/junit-invalid.xml new file mode 100644 index 000000000..8aa990ea7 --- /dev/null +++ b/internal/attestation/crafter/materials/testdata/junit-invalid.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/internal/attestation/crafter/materials/testdata/junit.xml b/internal/attestation/crafter/materials/testdata/junit.xml new file mode 100644 index 000000000..b63ff78c0 --- /dev/null +++ b/internal/attestation/crafter/materials/testdata/junit.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + From 4b865fb77af5f6b9f0634247ca0c97f0efd77ff8 Mon Sep 17 00:00:00 2001 From: Daniel Liszka Date: Tue, 30 May 2023 23:59:09 +0200 Subject: [PATCH 03/10] feat: Add support fot JUnit XML material type (#120) - go mod tidy Signed-off-by: Daniel Liszka --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index f879da6d1..f2d3897fb 100644 --- a/go.mod +++ b/go.mod @@ -38,6 +38,7 @@ require ( github.com/in-toto/in-toto-golang v0.8.0 github.com/jackc/pgx/v4 v4.18.1 github.com/jedib0t/go-pretty/v6 v6.4.6 + github.com/joshdk/go-junit v1.0.0 github.com/lib/pq v1.10.7 github.com/moby/moby v23.0.1+incompatible github.com/opencontainers/image-spec v1.1.0-rc2 @@ -162,7 +163,6 @@ require ( github.com/jackc/pgtype v1.14.0 // indirect github.com/jedisct1/go-minisign v0.0.0-20211028175153-1c139d1cc84b // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/joshdk/go-junit v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.16.0 // indirect github.com/leodido/go-urn v1.2.2 // indirect From 07c26e9619b230610b29f70465f7bbe61712eea8 Mon Sep 17 00:00:00 2001 From: Daniel Liszka Date: Wed, 31 May 2023 12:48:12 +0200 Subject: [PATCH 04/10] feat: Add support fot JUnit XML material type (#120) - refactoring Signed-off-by: Daniel Liszka --- .../crafter/materials/junit_xml.go | 39 +++++++++---------- .../crafter/materials/materials.go | 19 +++++---- 2 files changed, 28 insertions(+), 30 deletions(-) diff --git a/internal/attestation/crafter/materials/junit_xml.go b/internal/attestation/crafter/materials/junit_xml.go index 1ea1008f4..f2ef1bafa 100644 --- a/internal/attestation/crafter/materials/junit_xml.go +++ b/internal/attestation/crafter/materials/junit_xml.go @@ -30,45 +30,44 @@ import ( "github.com/rs/zerolog" ) -type JUnitXML struct { - *crafterUploader +type JUnitXMLCrafter struct { + *crafterCommon + uploader casclient.Uploader } -func NewJUnitXMLCrafter(materialSchema *schemaapi.CraftingSchema_Material, uploader casclient.Uploader, l *zerolog.Logger) (*JUnitXML, error) { - if materialSchema.Type != schemaapi.CraftingSchema_Material_JUNIT_XML { +func NewJUnitXMLCrafter(schema *schemaapi.CraftingSchema_Material, uploader casclient.Uploader, l *zerolog.Logger) (*JUnitXMLCrafter, error) { + if schema.Type != schemaapi.CraftingSchema_Material_JUNIT_XML { return nil, fmt.Errorf("material type is not JUnit XML") } + craftCommon := &crafterCommon{logger: l, input: schema} + return &JUnitXMLCrafter{uploader: uploader, crafterCommon: craftCommon}, nil +} - return &JUnitXML{ - &crafterUploader{ - uploader: uploader, - crafterCommon: &crafterCommon{ - logger: l, - input: materialSchema, - }, - }, - }, nil +func (i *JUnitXMLCrafter) Craft(ctx context.Context, filePath string) (*api.Attestation_Material, error) { + if err := i.validate(ctx, filePath); err != nil { + return nil, err + } + return uploadAndCraft(ctx, i.input, i.uploader, filePath) } -func (i *JUnitXML) Craft(ctx context.Context, filePath string) (*api.Attestation_Material, error) { +func (i *JUnitXMLCrafter) validate(_ context.Context, filePath string) error { f, err := os.Open(filePath) if err != nil { - return nil, fmt.Errorf("can't open the file: %w", err) + return fmt.Errorf("can't open the file: %w", err) } defer f.Close() bytes, err := io.ReadAll(f) if err != nil { - return nil, fmt.Errorf("can't read the file: %w", err) + return fmt.Errorf("can't read the file: %w", err) } if err := xml.Unmarshal(bytes, &junit.Suite{}); err != nil { - return nil, fmt.Errorf("invalid JUnit XML file: %w", ErrInvalidMaterialType) + return fmt.Errorf("invalid JUnit XML file: %w", ErrInvalidMaterialType) } _, err = junit.IngestReader(f) if err != nil { i.logger.Debug().Err(err).Msg("error decoding file: " + filePath) - return nil, fmt.Errorf("invalid JUnit XML file: %w", ErrInvalidMaterialType) + return fmt.Errorf("invalid JUnit XML file: %w", ErrInvalidMaterialType) } - - return i.crafterUploader.craft(ctx, filePath) + return nil } diff --git a/internal/attestation/crafter/materials/materials.go b/internal/attestation/crafter/materials/materials.go index 112e05b48..6cc2b3c1e 100644 --- a/internal/attestation/crafter/materials/materials.go +++ b/internal/attestation/crafter/materials/materials.go @@ -36,24 +36,23 @@ type crafterCommon struct { input *schemaapi.CraftingSchema_Material } -type crafterUploader struct { - *crafterCommon - uploader casclient.Uploader -} - -func (i *crafterUploader) craft(ctx context.Context, artifactPath string) (*api.Attestation_Material, error) { - result, err := i.uploader.UploadFile(ctx, artifactPath) +// uploadAndCraft uploads the artifact to CAS and crafts the material +// this function is used by all the uploadable artifacts crafters (SBOMs, JUnit, and more in the future) +func uploadAndCraft(ctx context.Context, input *schemaapi.CraftingSchema_Material, uploader casclient.Uploader, artifactPath string) (*api.Attestation_Material, error) { + result, err := uploader.UploadFile(ctx, artifactPath) if err != nil { - i.logger.Debug().Err(err) return nil, err } res := &api.Attestation_Material{ AddedAt: timestamppb.New(time.Now()), - MaterialType: i.input.Type, + MaterialType: input.Type, M: &api.Attestation_Material_Artifact_{ Artifact: &api.Attestation_Material_Artifact{ - Id: i.input.Name, Digest: result.Digest, Name: result.Filename, IsSubject: i.input.Output, + Id: input.Name, + Name: result.Filename, + Digest: result.Digest, + IsSubject: input.Output, }, }, } From c5a9b43fc7af56c57859dad869f417cecd3456c6 Mon Sep 17 00:00:00 2001 From: Daniel Liszka Date: Wed, 31 May 2023 12:54:46 +0200 Subject: [PATCH 05/10] feat: Add support fot JUnit XML material type (#120) - revert the comment change Signed-off-by: Daniel Liszka --- internal/attestation/crafter/materials/artifact.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/attestation/crafter/materials/artifact.go b/internal/attestation/crafter/materials/artifact.go index ddd04e9ce..8c37573f9 100644 --- a/internal/attestation/crafter/materials/artifact.go +++ b/internal/attestation/crafter/materials/artifact.go @@ -41,6 +41,7 @@ func NewArtifactCrafter(schema *schemaapi.CraftingSchema_Material, uploader casc return &ArtifactCrafter{uploader: uploader, crafterCommon: craftCommon}, nil } +// Craft will calculate the digest of the artifact, simulate an upload and return the material definition func (i *ArtifactCrafter) Craft(ctx context.Context, artifactPath string) (*api.Attestation_Material, error) { result, err := i.uploader.UploadFile(ctx, artifactPath) if err != nil { From d555e0d8d4d1f4e3c85da5eac9ed43d0ed2b6f1d Mon Sep 17 00:00:00 2001 From: Daniel Liszka Date: Wed, 31 May 2023 15:09:56 +0200 Subject: [PATCH 06/10] feat: Add support fot JUnit XML material type (#120) - apply feedback from the PR review Signed-off-by: Daniel Liszka --- internal/attestation/crafter/materials/junit_xml.go | 5 +++-- internal/attestation/crafter/materials/materials.go | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/internal/attestation/crafter/materials/junit_xml.go b/internal/attestation/crafter/materials/junit_xml.go index f2ef1bafa..2b68981da 100644 --- a/internal/attestation/crafter/materials/junit_xml.go +++ b/internal/attestation/crafter/materials/junit_xml.go @@ -44,13 +44,14 @@ func NewJUnitXMLCrafter(schema *schemaapi.CraftingSchema_Material, uploader casc } func (i *JUnitXMLCrafter) Craft(ctx context.Context, filePath string) (*api.Attestation_Material, error) { - if err := i.validate(ctx, filePath); err != nil { + if err := i.validate(filePath); err != nil { return nil, err } + return uploadAndCraft(ctx, i.input, i.uploader, filePath) } -func (i *JUnitXMLCrafter) validate(_ context.Context, filePath string) error { +func (i *JUnitXMLCrafter) validate(filePath string) error { f, err := os.Open(filePath) if err != nil { return fmt.Errorf("can't open the file: %w", err) diff --git a/internal/attestation/crafter/materials/materials.go b/internal/attestation/crafter/materials/materials.go index 6cc2b3c1e..ba3082743 100644 --- a/internal/attestation/crafter/materials/materials.go +++ b/internal/attestation/crafter/materials/materials.go @@ -41,7 +41,7 @@ type crafterCommon struct { func uploadAndCraft(ctx context.Context, input *schemaapi.CraftingSchema_Material, uploader casclient.Uploader, artifactPath string) (*api.Attestation_Material, error) { result, err := uploader.UploadFile(ctx, artifactPath) if err != nil { - return nil, err + return nil, fmt.Errorf("uploading material: %w", err) } res := &api.Attestation_Material{ From 09e4779d9504fea2e3738c3466773c5eed0520e7 Mon Sep 17 00:00:00 2001 From: Daniel Liszka Date: Wed, 31 May 2023 15:12:02 +0200 Subject: [PATCH 07/10] feat: Add support fot JUnit XML material type (#120) - apply feedback from the PR review, remove unnecessary tests Signed-off-by: Daniel Liszka --- internal/attestation/crafter/materials/junit_xml_test.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/internal/attestation/crafter/materials/junit_xml_test.go b/internal/attestation/crafter/materials/junit_xml_test.go index 602c2af84..5038e3269 100644 --- a/internal/attestation/crafter/materials/junit_xml_test.go +++ b/internal/attestation/crafter/materials/junit_xml_test.go @@ -74,11 +74,6 @@ func TestJUnitXMLCraft(t *testing.T) { filePath: "./testdata/non-existing.json", wantErr: "no such file or directory", }, - { - name: "invalid JUnit XML format", - filePath: "./testdata/sbom.spdx.json", - wantErr: "unexpected material type", - }, { name: "invalid artifact type", filePath: "./testdata/simple.txt", From dc3800adc5176ce7ffe0c5857f5ee1c8cebe393b Mon Sep 17 00:00:00 2001 From: Daniel Liszka Date: Wed, 31 May 2023 15:12:44 +0200 Subject: [PATCH 08/10] feat: Add support fot JUnit XML material type (#120) - apply feedback from the PR review Signed-off-by: Daniel Liszka --- internal/attestation/crafter/materials/junit_xml.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/attestation/crafter/materials/junit_xml.go b/internal/attestation/crafter/materials/junit_xml.go index 2b68981da..5b0380f66 100644 --- a/internal/attestation/crafter/materials/junit_xml.go +++ b/internal/attestation/crafter/materials/junit_xml.go @@ -62,13 +62,16 @@ func (i *JUnitXMLCrafter) validate(filePath string) error { if err != nil { return fmt.Errorf("can't read the file: %w", err) } + if err := xml.Unmarshal(bytes, &junit.Suite{}); err != nil { return fmt.Errorf("invalid JUnit XML file: %w", ErrInvalidMaterialType) } + _, err = junit.IngestReader(f) if err != nil { i.logger.Debug().Err(err).Msg("error decoding file: " + filePath) return fmt.Errorf("invalid JUnit XML file: %w", ErrInvalidMaterialType) } + return nil } From a77e7cf1f1d2afc38829855b230d9dc0fcec1f85 Mon Sep 17 00:00:00 2001 From: Daniel Liszka Date: Wed, 31 May 2023 16:09:41 +0200 Subject: [PATCH 09/10] feat: Add support fot JUnit XML material type (#120) - apply feedback from the PR review Signed-off-by: Daniel Liszka --- internal/attestation/crafter/materials/junit_xml.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/attestation/crafter/materials/junit_xml.go b/internal/attestation/crafter/materials/junit_xml.go index 5b0380f66..9e1f67159 100644 --- a/internal/attestation/crafter/materials/junit_xml.go +++ b/internal/attestation/crafter/materials/junit_xml.go @@ -69,7 +69,8 @@ func (i *JUnitXMLCrafter) validate(filePath string) error { _, err = junit.IngestReader(f) if err != nil { - i.logger.Debug().Err(err).Msg("error decoding file: " + filePath) + s := fmt.Sprintf("error decoding file: %s", filePath) + i.logger.Debug().Err(err).Msg(s) return fmt.Errorf("invalid JUnit XML file: %w", ErrInvalidMaterialType) } From 101da0a9bfe1b1996d0ec5c2fc209f4e14d39d6a Mon Sep 17 00:00:00 2001 From: Daniel Liszka Date: Wed, 31 May 2023 19:21:45 +0200 Subject: [PATCH 10/10] feat: Add support fot JUnit XML material type (#120) - apply feedback from the PR review Signed-off-by: Daniel Liszka --- internal/attestation/crafter/materials/junit_xml.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/attestation/crafter/materials/junit_xml.go b/internal/attestation/crafter/materials/junit_xml.go index 9e1f67159..be2fe820a 100644 --- a/internal/attestation/crafter/materials/junit_xml.go +++ b/internal/attestation/crafter/materials/junit_xml.go @@ -69,8 +69,7 @@ func (i *JUnitXMLCrafter) validate(filePath string) error { _, err = junit.IngestReader(f) if err != nil { - s := fmt.Sprintf("error decoding file: %s", filePath) - i.logger.Debug().Err(err).Msg(s) + i.logger.Debug().Err(err).Msgf("error decoding file: %s", filePath) return fmt.Errorf("invalid JUnit XML file: %w", ErrInvalidMaterialType) }