@@ -17,19 +17,38 @@ package materials
1717
1818import (
1919 "context"
20+ "encoding/json"
2021 "fmt"
22+ "os"
2123
2224 schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1"
2325 api "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/api/attestation/v1"
2426 "github.com/chainloop-dev/chainloop/pkg/casclient"
27+
2528 "github.com/rs/zerolog"
2629)
2730
31+ const (
32+ // Annotations for evidence metadata that will be extracted if the evidence is in JSON format
33+ annotationEvidenceID = "chainloop.material.evidence.id"
34+ annotationEvidenceSchema = "chainloop.material.evidence.schema"
35+ )
36+
2837type EvidenceCrafter struct {
2938 * crafterCommon
3039 backend * casclient.CASBackend
3140}
3241
42+ // customEvidence represents the expected structure of a custom Evidence JSON file
43+ type customEvidence struct {
44+ // ID is a unique identifier for the evidence
45+ ID string `json:"id"`
46+ // Schema is an optional schema reference for the evidence validation
47+ Schema string `json:"schema"`
48+ // Data contains the actual evidence content
49+ Data json.RawMessage `json:"data"`
50+ }
51+
3352// NewEvidenceCrafter generates a new Evidence material.
3453// Pieces of evidences represent generic, additional context that don't fit
3554// into one of the well known material types. For example, a custom approval report (in json), ...
@@ -43,6 +62,53 @@ func NewEvidenceCrafter(schema *schemaapi.CraftingSchema_Material, backend *casc
4362}
4463
4564// Craft will calculate the digest of the artifact, simulate an upload and return the material definition
65+ // If the evidence is in JSON format with id, data (and optionally schema) fields,
66+ // it will extract those as annotations
4667func (i * EvidenceCrafter ) Craft (ctx context.Context , artifactPath string ) (* api.Attestation_Material , error ) {
47- return uploadAndCraft (ctx , i .input , i .backend , artifactPath , i .logger )
68+ material , err := uploadAndCraft (ctx , i .input , i .backend , artifactPath , i .logger )
69+ if err != nil {
70+ return nil , err
71+ }
72+
73+ // Try to parse as JSON and extract annotations
74+ i .tryExtractAnnotations (material , artifactPath )
75+
76+ return material , nil
77+ }
78+
79+ // tryExtractAnnotations attempts to parse the evidence as JSON and extract id/schema fields as annotations
80+ func (i * EvidenceCrafter ) tryExtractAnnotations (m * api.Attestation_Material , artifactPath string ) {
81+ // Read the file content
82+ content , err := os .ReadFile (artifactPath )
83+ if err != nil {
84+ i .logger .Debug ().Err (err ).Msg ("failed to read evidence file for annotation extraction" )
85+ return
86+ }
87+
88+ // Try to parse as JSON
89+ var evidence customEvidence
90+
91+ if err := json .Unmarshal (content , & evidence ); err != nil {
92+ i .logger .Debug ().Err (err ).Msg ("evidence is not valid JSON, skipping annotation extraction" )
93+ return
94+ }
95+
96+ // Check if it has the required structure (id and data fields)
97+ if evidence .ID == "" || len (evidence .Data ) == 0 {
98+ i .logger .Debug ().Msg ("evidence JSON does not have required id and data fields, skipping annotation extraction" )
99+ return
100+ }
101+
102+ // Initialize annotations map if needed
103+ if m .Annotations == nil {
104+ m .Annotations = make (map [string ]string )
105+ }
106+
107+ // Extract id and schema as annotations
108+ m .Annotations [annotationEvidenceID ] = evidence .ID
109+ if evidence .Schema != "" {
110+ m .Annotations [annotationEvidenceSchema ] = evidence .Schema
111+ }
112+
113+ i .logger .Debug ().Str ("id" , evidence .ID ).Str ("schema" , evidence .Schema ).Msg ("extracted evidence annotations" )
48114}
0 commit comments