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
18 changes: 16 additions & 2 deletions app/cli/cmd/attestation_add.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ package cmd

import (
"errors"
"fmt"
"strings"

"github.com/spf13/cobra"
"github.com/spf13/viper"
Expand All @@ -28,6 +30,7 @@ import (
func newAttestationAddCmd() *cobra.Command {
var name, value string
var artifactCASConn *grpc.ClientConn
var annotationsFlag []string

cmd := &cobra.Command{
Use: "add",
Expand All @@ -44,7 +47,17 @@ func newAttestationAddCmd() *cobra.Command {
},
)

err := a.Run(name, value)
// Extract annotations
var annotations = make(map[string]string)
for _, annotation := range annotationsFlag {
kv := strings.SplitN(annotation, "=", 2)
if len(kv) != 2 {
return fmt.Errorf("invalid annotation %q, the format must be key=value", annotation)
}
annotations[kv[0]] = kv[1]
}

err := a.Run(name, value, annotations)
if err != nil {
if errors.Is(err, action.ErrAttestationNotInitialized) {
return err
Expand All @@ -67,11 +80,12 @@ func newAttestationAddCmd() *cobra.Command {
}

cmd.Flags().StringVar(&name, "name", "", "name of the material to be recorded")
cmd.Flags().StringVar(&value, "value", "", "value to be recorded")
err := cmd.MarkFlagRequired("name")
cobra.CheckErr(err)
cmd.Flags().StringVar(&value, "value", "", "value to be recorded")
err = cmd.MarkFlagRequired("value")
cobra.CheckErr(err)
cmd.Flags().StringSliceVar(&annotationsFlag, "annotation", nil, "additional annotation in the format of key=value")

return cmd
}
4 changes: 2 additions & 2 deletions app/cli/internal/action/attestation_add.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func NewAttestationAdd(cfg *AttestationAddOpts) *AttestationAdd {

var ErrAttestationNotInitialized = errors.New("attestation not yet initialized")

func (action *AttestationAdd) Run(k, v string) error {
func (action *AttestationAdd) Run(k, v string, annotations map[string]string) error {
if initialized := action.c.AlreadyInitialized(); !initialized {
return ErrAttestationNotInitialized
}
Expand Down Expand Up @@ -98,7 +98,7 @@ func (action *AttestationAdd) Run(k, v string) error {
casBackend.Uploader = casclient.New(artifactCASConn, casclient.WithLogger(action.Logger))
}

if err := action.c.AddMaterial(k, v, casBackend); err != nil {
if err := action.c.AddMaterial(k, v, casBackend, annotations); err != nil {
return fmt.Errorf("adding material: %w", err)
}

Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 10 additions & 13 deletions app/controlplane/api/workflowcontract/v1/crafting_schema.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ message CraftingSchema {

message Annotation {
string name = 1 [(validate.rules).string.pattern = "^[\\w]+$"]; // Single word optionally separated with _
// TODO: This value is required for now, but this behavior will change once we allow
// the user to set the value during attestation
string value = 2 [(validate.rules).string.min_len = 1];
// This value can be set in the contract or provided during the attestation
string value = 2;
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,6 @@ func TestValidateAnnotations(t *testing.T) {
value: "hi",
wantErr: true,
},
{
desc: "missing value",
name: "hi",
wantErr: true,
},
{
desc: "valid key underscore",
name: "hello_world",
Expand Down
35 changes: 32 additions & 3 deletions internal/attestation/crafter/crafter.go
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ func notResolvedVars(resolved map[string]string, wantList []string) []string {
}

// Inject material to attestation state
func (c *Crafter) AddMaterial(key, value string, casBackend *casclient.CASBackend) error {
func (c *Crafter) AddMaterial(key, value string, casBackend *casclient.CASBackend, runtimeAnnotations map[string]string) error {
if err := c.requireStateLoaded(); err != nil {
return err
}
Expand Down Expand Up @@ -354,19 +354,48 @@ func (c *Crafter) AddMaterial(key, value string, casBackend *casclient.CASBacken
return err
}

// 4 - Populate annotations from the ones provided at runtime
// a) we do not allow overriding values that come from the contract
// b) we do not allow adding annotations that are not defined in the contract
for kr, vr := range runtimeAnnotations {
// If the annotation is not defined in the material we fail
if v, found := mt.Annotations[kr]; !found {
return fmt.Errorf("annotation %q not found in material %q", kr, key)
} else if v == "" {
// Set it only if it's not set
mt.Annotations[kr] = vr
} else {
// NOTE: we do not allow overriding values that come from the contract
c.logger.Info().Str("key", key).Str("annotation", kr).Msg("annotation can't be changed, skipping")
}
}

// Make sure all the annotation values are now set
// This is in fact validated below but by manually checking we can provide a better error message
for k, v := range mt.Annotations {
var missingAnnotations []string
if v == "" {
missingAnnotations = append(missingAnnotations, k)
}

if len(missingAnnotations) > 0 {
return fmt.Errorf("annotations %q required for material %q", missingAnnotations, key)
}
}

if err := mt.Validate(); err != nil {
return fmt.Errorf("validation error: %w", err)
}

// 4 - Attach it to state
// 5 - Attach it to state
if mt != nil {
if c.CraftingState.Attestation.Materials == nil {
c.CraftingState.Attestation.Materials = map[string]*api.Attestation_Material{key: mt}
}
c.CraftingState.Attestation.Materials[key] = mt
}

// 5 - Persist state
// 6 - Persist state
if err := persistCraftingState(c.CraftingState, c.statePath); err != nil {
return err
}
Expand Down