diff --git a/app/controlplane/plugins/core/dependency-track/v1/extension.go b/app/controlplane/plugins/core/dependency-track/v1/extension.go index aab4fb7ad..8668aa68d 100644 --- a/app/controlplane/plugins/core/dependency-track/v1/extension.go +++ b/app/controlplane/plugins/core/dependency-track/v1/extension.go @@ -20,6 +20,7 @@ import ( "context" "errors" "fmt" + "strings" "text/template" schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1" @@ -182,7 +183,9 @@ func doExecute(ctx context.Context, req *sdk.ExecutionRequest, sbom *sdk.Execute return errors.New("invalid attachment configuration") } - projectName, err := resolveProjectName(attachmentConfig.ProjectName, sbom.Annotations) + // Calculate the project name based on the template + + projectName, err := resolveProjectName(attachmentConfig.ProjectName, req.Input.Attestation.Predicate.GetAnnotations(), sbom.Annotations) if err != nil { // If we can't find the annotation for example, we skip the SBOM l.Infow("msg", "failed to resolve project name, SKIPPING", "err", err, "materialName", sbom.Name) @@ -227,18 +230,33 @@ func doExecute(ctx context.Context, req *sdk.ExecutionRequest, sbom *sdk.Execute } type interpolationContext struct { - Material *interpolationContextMaterial + Material *annotations + Attestation *annotations } -type interpolationContextMaterial struct { +type annotations struct { Annotations map[string]string } +// Make annotations keys case insensitive +// that way you can define templates such as {{ material.annotations.myAnnotation }} or {{ material.annotations.MyAnnotation }} and they will both work +func toCaseInsensitive(in map[string]string) map[string]string { + for k, v := range in { + in[strings.Title(k)] = v + } + + return in +} + // Resolve the project name template. // We currently support the following template variables: -// - material.annotations. +// - {{ .Attestation.Annotations. }} for global annotations +// - {{ .Material.Annotations. }} for material annotations // For example, project-name => {{ material.annotations.my_annotation }} -func resolveProjectName(projectNameTpl string, annotations map[string]string) (string, error) { - data := interpolationContext{&interpolationContextMaterial{annotations}} +func resolveProjectName(projectNameTpl string, attAnnotations, sbomAnnotations map[string]string) (string, error) { + data := &interpolationContext{ + Material: &annotations{toCaseInsensitive(sbomAnnotations)}, + Attestation: &annotations{toCaseInsensitive(attAnnotations)}, + } // The project name can contain template variables, useful to include annotations for example // We do fail if the key can't be found diff --git a/app/controlplane/plugins/core/dependency-track/v1/extension_test.go b/app/controlplane/plugins/core/dependency-track/v1/extension_test.go index c597adc14..ec9de6b83 100644 --- a/app/controlplane/plugins/core/dependency-track/v1/extension_test.go +++ b/app/controlplane/plugins/core/dependency-track/v1/extension_test.go @@ -112,6 +112,11 @@ func TestResolveProjectName(t *testing.T) { projectName: "{{.Material.Annotations.Hello}}", want: "hola", }, + { + name: "lower case", + projectName: "{{.Material.Annotations.hello}}", + want: "hola", + }, { name: "interpolated string", projectName: "{{.Material.Annotations.Hello}}-project", @@ -124,21 +129,35 @@ func TestResolveProjectName(t *testing.T) { wantErr: true, }, { - name: "non-existing-case", - projectName: "{{.Material.Annotations.hello}}", - wantErr: true, + name: "interpolated string global", + projectName: "project-{{.Attestation.Annotations.version}}", + want: "project-1.2.3", }, + { + name: "interpolated combination global", + projectName: "project-{{.Material.Annotations.Hello}}-{{.Attestation.Annotations.version}}", + want: "project-hola-1.2.3", + }, + { + name: "uppercase global", + projectName: "{{.Attestation.Annotations.Version}}", + want: "1.2.3", + }, + } + + sbomAnnotation := map[string]string{ + "hello": "hola", + "world": "mundo", } - data := map[string]string{ - "Hello": "hola", - "World": "mundo", + attAnnotation := map[string]string{ + "version": "1.2.3", } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { var err error - got, err := resolveProjectName(tc.projectName, data) + got, err := resolveProjectName(tc.projectName, attAnnotation, sbomAnnotation) if tc.wantErr { assert.Error(t, err) return diff --git a/internal/attestation/renderer/chainloop/chainloop.go b/internal/attestation/renderer/chainloop/chainloop.go index 9f0002356..947c6bbe2 100644 --- a/internal/attestation/renderer/chainloop/chainloop.go +++ b/internal/attestation/renderer/chainloop/chainloop.go @@ -35,6 +35,7 @@ const builderIDFmt = "chainloop.dev/cli/%s@%s" // NormalizablePredicate represents a common interface of how to extract materials and env vars type NormalizablePredicate interface { + GetAnnotations() map[string]string GetEnvVars() map[string]string GetMaterials() []*NormalizedMaterial GetRunLink() string @@ -195,3 +196,7 @@ func (p *ProvenancePredicateCommon) GetEnvVars() map[string]string { func (p *ProvenancePredicateCommon) GetRunLink() string { return p.RunnerURL } + +func (p *ProvenancePredicateCommon) GetAnnotations() map[string]string { + return p.Annotations +}