Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 24 additions & 6 deletions app/controlplane/plugins/core/dependency-track/v1/extension.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"context"
"errors"
"fmt"
"strings"
"text/template"

schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1"
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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.<key>
// - {{ .Attestation.Annotations.<key> }} for global annotations
// - {{ .Material.Annotations.<key> }} 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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
Expand Down
5 changes: 5 additions & 0 deletions internal/attestation/renderer/chainloop/chainloop.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}