@@ -22,35 +22,69 @@ import (
2222 "fmt"
2323 "os"
2424 "path/filepath"
25+ "time"
2526
27+ ecc "github.com/enterprise-contract/enterprise-contract-controller/api/v1alpha1"
2628 log "github.com/sirupsen/logrus"
2729 "github.com/spf13/afero"
2830)
2931
30- // SnapshotVSAWriter handles writing application snapshot VSA predicates to files
31- type SnapshotVSAWriter struct {
32+ // SnapshotComponentDetail represents detailed information about a component in the snapshot summary
33+ type SnapshotComponentDetail struct {
34+ Name string `json:"name"`
35+ ContainerImage string `json:"containerImage"`
36+ Success bool `json:"success"`
37+ Violations int `json:"violations"`
38+ Warnings int `json:"warnings"`
39+ Successes int `json:"successes"`
40+ }
41+
42+ // SnapshotSummary represents the summary information for a snapshot predicate
43+ type SnapshotSummary struct {
44+ Snapshot string `json:"snapshot"`
45+ Components int `json:"components"`
46+ Success bool `json:"success"`
47+ Key string `json:"key"`
48+ EcVersion string `json:"ec_version"`
49+ ComponentDetails []SnapshotComponentDetail `json:"component_details"`
50+ Violations int `json:"Violations"`
51+ Warnings int `json:"Warnings"`
52+ }
53+
54+ // SnapshotPredicate represents a predicate for an entire application snapshot
55+ type SnapshotPredicate struct {
56+ Policy ecc.EnterpriseContractPolicySpec `json:"policy"`
57+ ImageRefs []string `json:"imageRefs"`
58+ Timestamp string `json:"timestamp"`
59+ Status string `json:"status"`
60+ Verifier string `json:"verifier"`
61+ Summary SnapshotSummary `json:"summary"`
62+ }
63+
64+ // SnapshotPredicateWriter handles writing application snapshot predicates to files
65+ type SnapshotPredicateWriter struct {
3266 FS afero.Fs // defaults to afero.NewOsFs()
33- TempDirPrefix string // defaults to "snapshot-vsa -"
67+ TempDirPrefix string // defaults to "snapshot-predicate -"
3468 FilePerm os.FileMode // defaults to 0600
3569}
3670
37- // NewSnapshotVSAWriter creates a new application snapshot VSA file writer
38- func NewSnapshotVSAWriter () * SnapshotVSAWriter {
39- return & SnapshotVSAWriter {
71+ // NewSnapshotPredicateWriter creates a new application snapshot predicate file writer
72+ func NewSnapshotPredicateWriter () * SnapshotPredicateWriter {
73+ return & SnapshotPredicateWriter {
4074 FS : afero .NewOsFs (),
41- TempDirPrefix : "snapshot-vsa -" ,
75+ TempDirPrefix : "snapshot-predicate -" ,
4276 FilePerm : 0o600 ,
4377 }
4478}
4579
46- // WritePredicate writes the Report as a VSA predicate to a file
47- func (s * SnapshotVSAWriter ) WritePredicate (report Report ) (string , error ) {
80+ // WritePredicate writes the SnapshotPredicate as a JSON file to a temp directory and returns the path
81+ func (s * SnapshotPredicateWriter ) WritePredicate (predicate * SnapshotPredicate ) (string , error ) {
4882 log .Infof ("Writing application snapshot VSA" )
4983
5084 // Serialize with indent
51- data , err := json .MarshalIndent (report , "" , " " )
85+ data , err := json .MarshalIndent (predicate , "" , " " )
5286 if err != nil {
53- return "" , fmt .Errorf ("failed to marshal application snapshot VSA: %w" , err )
87+ return "" , fmt .Errorf ("failed to marshal application snapshot VSA predicate : %w" , err )
5488 }
5589
5690 // Create temp directory
@@ -59,32 +93,144 @@ func (s *SnapshotVSAWriter) WritePredicate(report Report) (string, error) {
5993 return "" , fmt .Errorf ("failed to create temp directory: %w" , err )
6094 }
6195
62- // Write to file
96+ // Write to file with same naming convention as old VSA
6397 filename := "application-snapshot-vsa.json"
6498 filepath := filepath .Join (tempDir , filename )
6599 err = afero .WriteFile (s .FS , filepath , data , s .FilePerm )
66100 if err != nil {
67- return "" , fmt .Errorf ("failed to write application snapshot VSA to file: %w" , err )
101+ return "" , fmt .Errorf ("failed to write application snapshot VSA predicate to file: %w" , err )
68102 }
69103
70104 log .Infof ("Application snapshot VSA written to: %s" , filepath )
71105 return filepath , nil
72106}
73107
74- type SnapshotVSAGenerator struct {
108+ // SnapshotPredicateGenerator generates predicates for application snapshots
109+ type SnapshotPredicateGenerator struct {
75110 Report Report
76111}
77112
78- // NewSnapshotVSAGenerator creates a new VSA predicate generator for application snapshots
79- func NewSnapshotVSAGenerator (report Report ) * SnapshotVSAGenerator {
80- return & SnapshotVSAGenerator {
113+ // NewSnapshotPredicateGenerator creates a new predicate generator for application snapshots
114+ func NewSnapshotPredicateGenerator (report Report ) * SnapshotPredicateGenerator {
115+ return & SnapshotPredicateGenerator {
81116 Report : report ,
82117 }
83118}
84119
85- // GeneratePredicate creates a VSA predicate for the entire application snapshot
86- func (s * SnapshotVSAGenerator ) GeneratePredicate (ctx context.Context ) (Report , error ) {
87- log .Infof ("Generating application snapshot VSA predicate with %d components" , len (s .Report .Components ))
120+ // GeneratePredicate creates a predicate for the entire application snapshot
121+ func (s * SnapshotPredicateGenerator ) GeneratePredicate (ctx context.Context ) (* SnapshotPredicate , error ) {
122+ log .Infof ("Generating application snapshot EC predicate with %d components" , len (s .Report .Components ))
123+
124+ // Collect all image references from all components
125+ imageRefs := s .getAllImageRefs ()
126+
127+ // Determine overall status
128+ status := "failed"
129+ if s .Report .Success {
130+ status = "passed"
131+ }
132+
133+ // Add detailed component breakdown and calculate totals
134+ components := make ([]SnapshotComponentDetail , 0 , len (s .Report .Components ))
135+ totalViolations := 0
136+ totalWarnings := 0
137+
138+ for _ , comp := range s .Report .Components {
139+ compViolations := len (comp .Violations )
140+ compWarnings := len (comp .Warnings )
141+
142+ compDetails := SnapshotComponentDetail {
143+ Name : comp .Name ,
144+ ContainerImage : comp .ContainerImage ,
145+ Success : comp .Success ,
146+ Violations : compViolations ,
147+ Warnings : compWarnings ,
148+ Successes : len (comp .Successes ),
149+ }
150+ components = append (components , compDetails )
151+
152+ // Add to totals
153+ totalViolations += compViolations
154+ totalWarnings += compWarnings
155+ }
156+
157+ // Create summary with component information
158+ summary := SnapshotSummary {
159+ Snapshot : s .Report .Snapshot ,
160+ Components : len (s .Report .Components ),
161+ Success : s .Report .Success ,
162+ Key : s .Report .Key ,
163+ EcVersion : s .Report .EcVersion ,
164+ ComponentDetails : components ,
165+ Violations : totalViolations ,
166+ Warnings : totalWarnings ,
167+ }
168+
169+ return & SnapshotPredicate {
170+ Policy : s .Report .Policy , // This contains the resolved policy with pinned URLs
171+ ImageRefs : imageRefs ,
172+ Timestamp : time .Now ().UTC ().Format (time .RFC3339 ),
173+ Status : status ,
174+ Verifier : "conforma" ,
175+ Summary : summary ,
176+ }, nil
177+ }
178+
179+ // getAllImageRefs returns all image references from all components including expansion info
180+ func (s * SnapshotPredicateGenerator ) getAllImageRefs () []string {
181+ var allImageRefs []string
182+
183+ for _ , comp := range s .Report .Components {
184+ // Add the main component image reference
185+ allImageRefs = append (allImageRefs , comp .ContainerImage )
186+
187+ // If we have expansion info, add the index and all architecture-specific images
188+ if s .Report .Expansion != nil {
189+ // Get the normalized index reference
190+ normalizedRef := normalizeIndexRef (comp .ContainerImage , s .Report .Expansion )
191+
192+ // Add the index reference if it's different from the component image
193+ if normalizedRef != comp .ContainerImage {
194+ allImageRefs = append (allImageRefs , normalizedRef )
195+ }
196+
197+ // Get all child images for this index
198+ if children , ok := s .Report .Expansion .GetChildrenByIndex (normalizedRef ); ok {
199+ allImageRefs = append (allImageRefs , children ... )
200+ }
201+ }
202+ }
203+
204+ // Remove duplicates and return
205+ return removeDuplicateStrings (allImageRefs )
206+ }
207+
208+ // normalizeIndexRef normalizes an image reference to its pinned digest form if it's an index
209+ func normalizeIndexRef (ref string , exp * ExpansionInfo ) string {
210+ if exp == nil {
211+ return ref
212+ }
213+ if pinned , ok := exp .GetIndexAlias (ref ); ok {
214+ return pinned
215+ }
216+ return ref
217+ }
218+
219+ // removeDuplicateStrings removes duplicate strings from a slice
220+ func removeDuplicateStrings (slice []string ) []string {
221+ if len (slice ) == 0 {
222+ return []string {}
223+ }
224+
225+ keys := make (map [string ]bool )
226+ var result []string
227+
228+ for _ , item := range slice {
229+ if ! keys [item ] {
230+ keys [item ] = true
231+ result = append (result , item )
232+ }
233+ }
88234
89- return s . Report , nil
235+ return result
90236}
0 commit comments