@@ -20,6 +20,7 @@ import (
2020 "context"
2121 "errors"
2222 "fmt"
23+ "text/template"
2324
2425 schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1"
2526 "github.com/chainloop-dev/chainloop/app/controlplane/plugins/core/dependency-track/v1/client"
@@ -63,7 +64,7 @@ func New(l log.Logger) (sdk.FanOut, error) {
6364 base , err := sdk .NewFanOut (
6465 & sdk.NewParams {
6566 ID : "dependency-track" ,
66- Version : "1.2 " ,
67+ Version : "1.3 " ,
6768 Description : description ,
6869 Logger : l ,
6970 InputSchema : & sdk.InputSchema {
@@ -143,67 +144,117 @@ func (i *DependencyTrack) Attach(ctx context.Context, req *sdk.AttachmentRequest
143144 return & sdk.AttachmentResponse {Configuration : rawConfig }, nil
144145}
145146
146- // Send the SBOM to the configured Dependency Track instance
147+ // Send the SBOMs to the configured Dependency Track instance
147148func (i * DependencyTrack ) Execute (ctx context.Context , req * sdk.ExecutionRequest ) error {
148- i .Logger .Info ("execution requested" )
149-
149+ var errs error
150150 // Iterate over all SBOMs
151151 for _ , sbom := range req .Input .Materials {
152- // Make sure it's an SBOM and all the required configuration has been received
153- if err := validateExecuteOpts ( sbom , req . RegistrationInfo , req . AttachmentInfo ); err != nil {
154- return fmt . Errorf ( "running validation: %w" , err )
152+ if err := doExecute ( ctx , req , sbom , i . Logger ); err != nil {
153+ errs = errors . Join ( errs , err )
154+ continue
155155 }
156+ }
156157
157- // Extract registration configuration
158- var registrationConfig * registrationConfig
159- if err := sdk .FromConfig (req .RegistrationInfo .Configuration , & registrationConfig ); err != nil {
160- return errors .New ("invalid registration configuration" )
161- }
158+ if errs != nil {
159+ return fmt .Errorf ("executing: %w" , errs )
160+ }
162161
163- // Extract attachment configuration
164- var attachmentConfig * attachmentConfig
165- if err := sdk .FromConfig (req .AttachmentInfo .Configuration , & attachmentConfig ); err != nil {
166- return errors .New ("invalid attachment configuration" )
167- }
162+ return nil
163+ }
168164
169- i .Logger .Infow ("msg" , "Uploading SBOM" ,
170- "materialName" , sbom .Name ,
171- "host" , registrationConfig .Domain ,
172- "projectID" , attachmentConfig .ProjectID , "projectName" , attachmentConfig .ProjectName ,
173- "workflowID" , req .Workflow .ID ,
174- )
175-
176- // Create an SBOM client and perform validation and upload
177- d , err := client .NewSBOMUploader (registrationConfig .Domain ,
178- req .RegistrationInfo .Credentials .Password ,
179- bytes .NewReader (sbom .Content ),
180- attachmentConfig .ProjectID ,
181- attachmentConfig .ProjectName )
182- if err != nil {
183- return fmt .Errorf ("creating uploader: %w" , err )
184- }
165+ func doExecute (ctx context.Context , req * sdk.ExecutionRequest , sbom * sdk.ExecuteMaterial , l * log.Helper ) error {
166+ l .Info ("execution requested" )
185167
186- if err := d .Validate (ctx ); err != nil {
187- return fmt .Errorf ("validating uploader: %w" , err )
188- }
168+ // Make sure it's an SBOM and all the required configuration has been received
169+ if err := validateExecuteOpts (sbom , req .RegistrationInfo , req .AttachmentInfo ); err != nil {
170+ return fmt .Errorf ("running validation: %w" , err )
171+ }
189172
190- if err := d .Do (ctx ); err != nil {
191- return fmt .Errorf ("uploading SBOM: %w" , err )
192- }
173+ // Extract registration configuration
174+ var registrationConfig * registrationConfig
175+ if err := sdk .FromConfig (req .RegistrationInfo .Configuration , & registrationConfig ); err != nil {
176+ return errors .New ("invalid registration configuration" )
177+ }
193178
194- i .Logger .Infow ("msg" , "SBOM Uploaded" ,
195- "materialName" , sbom .Name ,
196- "host" , registrationConfig .Domain ,
197- "projectID" , attachmentConfig .ProjectID , "projectName" , attachmentConfig .ProjectName ,
198- "workflowID" , req .Workflow .ID ,
199- )
179+ // Extract attachment configuration
180+ var attachmentConfig * attachmentConfig
181+ if err := sdk .FromConfig (req .AttachmentInfo .Configuration , & attachmentConfig ); err != nil {
182+ return errors .New ("invalid attachment configuration" )
183+ }
184+
185+ projectName , err := resolveProjectName (attachmentConfig .ProjectName , sbom .Annotations )
186+ if err != nil {
187+ // If we can't find the annotation for example, we skip the SBOM
188+ l .Infow ("msg" , "failed to resolve project name, SKIPPING" , "err" , err , "materialName" , sbom .Name )
189+ return nil
200190 }
201191
202- i .Logger .Info ("execution finished" )
192+ l .Infow ("msg" , "Uploading SBOM" ,
193+ "materialName" , sbom .Name ,
194+ "host" , registrationConfig .Domain ,
195+ "projectID" , attachmentConfig .ProjectID , "projectName" , projectName ,
196+ "workflowID" , req .Workflow .ID ,
197+ )
198+
199+ // Create an SBOM client and perform validation and upload
200+ d , err := client .NewSBOMUploader (registrationConfig .Domain ,
201+ req .RegistrationInfo .Credentials .Password ,
202+ bytes .NewReader (sbom .Content ),
203+ attachmentConfig .ProjectID ,
204+ projectName )
205+ if err != nil {
206+ return fmt .Errorf ("creating uploader: %w" , err )
207+ }
208+
209+ if err := d .Validate (ctx ); err != nil {
210+ return fmt .Errorf ("validating uploader: %w" , err )
211+ }
212+
213+ if err := d .Do (ctx ); err != nil {
214+ return fmt .Errorf ("uploading SBOM: %w" , err )
215+ }
216+
217+ l .Infow ("msg" , "SBOM Uploaded" ,
218+ "materialName" , sbom .Name ,
219+ "host" , registrationConfig .Domain ,
220+ "projectID" , attachmentConfig .ProjectID , "projectName" , projectName ,
221+ "workflowID" , req .Workflow .ID ,
222+ )
223+
224+ l .Info ("execution finished" )
203225
204226 return nil
205227}
206228
229+ type interpolationContext struct {
230+ Material * interpolationContextMaterial
231+ }
232+ type interpolationContextMaterial struct {
233+ Annotations map [string ]string
234+ }
235+
236+ // Resolve the project name template.
237+ // We currently support the following template variables:
238+ // - material.annotations.<key>
239+ // For example, project-name => {{ material.annotations.my_annotation }}
240+ func resolveProjectName (projectNameTpl string , annotations map [string ]string ) (string , error ) {
241+ data := interpolationContext {& interpolationContextMaterial {annotations }}
242+
243+ // The project name can contain template variables, useful to include annotations for example
244+ // We do fail if the key can't be found
245+ tpl , err := template .New ("projectName" ).Option ("missingkey=error" ).Parse (projectNameTpl )
246+ if err != nil {
247+ return "" , fmt .Errorf ("invalid project name: %w" , err )
248+ }
249+
250+ buf := bytes .NewBuffer (nil )
251+ if err := tpl .Execute (buf , data ); err != nil {
252+ return "" , fmt .Errorf ("executing template: %w" , err )
253+ }
254+
255+ return buf .String (), nil
256+ }
257+
207258// i.e we want to attach to a dependency track integration and we are proving the right attachment options
208259// Not only syntactically but also semantically, i.e we can only request auto-creation of projects if the integration allows it
209260func validateAttachment (ctx context.Context , rc * registrationConfig , ac * attachmentRequest , credentials * sdk.Credentials ) error {
@@ -229,8 +280,15 @@ func validateAttachmentConfiguration(rc *registrationConfig, ac *attachmentReque
229280 return errors .New ("invalid configuration" )
230281 }
231282
232- if ac .ProjectName != "" && ! rc .AllowAutoCreate {
233- return errors .New ("auto creation of projects is not supported in this integration" )
283+ if ac .ProjectName != "" {
284+ if ! rc .AllowAutoCreate {
285+ return errors .New ("auto creation of projects is not supported in this integration" )
286+ }
287+
288+ // The project name can contain template variables, useful to include annotations for example
289+ if _ , err := template .New ("projectName" ).Parse (ac .ProjectName ); err != nil {
290+ return fmt .Errorf ("invalid project name: %w" , err )
291+ }
234292 }
235293
236294 if ac .ProjectID == "" && ac .ProjectName == "" {
0 commit comments