Skip to content

Commit 0945ae9

Browse files
authored
feat(attestation): allow runtime annotations (#281)
Signed-off-by: Miguel Martinez Trivino <[email protected]>
1 parent e1672fa commit 0945ae9

File tree

8 files changed

+64
-44
lines changed

8 files changed

+64
-44
lines changed

app/cli/cmd/attestation_add.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ package cmd
1717

1818
import (
1919
"errors"
20+
"fmt"
21+
"strings"
2022

2123
"github.com/spf13/cobra"
2224
"github.com/spf13/viper"
@@ -28,6 +30,7 @@ import (
2830
func newAttestationAddCmd() *cobra.Command {
2931
var name, value string
3032
var artifactCASConn *grpc.ClientConn
33+
var annotationsFlag []string
3134

3235
cmd := &cobra.Command{
3336
Use: "add",
@@ -44,7 +47,17 @@ func newAttestationAddCmd() *cobra.Command {
4447
},
4548
)
4649

47-
err := a.Run(name, value)
50+
// Extract annotations
51+
var annotations = make(map[string]string)
52+
for _, annotation := range annotationsFlag {
53+
kv := strings.SplitN(annotation, "=", 2)
54+
if len(kv) != 2 {
55+
return fmt.Errorf("invalid annotation %q, the format must be key=value", annotation)
56+
}
57+
annotations[kv[0]] = kv[1]
58+
}
59+
60+
err := a.Run(name, value, annotations)
4861
if err != nil {
4962
if errors.Is(err, action.ErrAttestationNotInitialized) {
5063
return err
@@ -67,11 +80,12 @@ func newAttestationAddCmd() *cobra.Command {
6780
}
6881

6982
cmd.Flags().StringVar(&name, "name", "", "name of the material to be recorded")
70-
cmd.Flags().StringVar(&value, "value", "", "value to be recorded")
7183
err := cmd.MarkFlagRequired("name")
7284
cobra.CheckErr(err)
85+
cmd.Flags().StringVar(&value, "value", "", "value to be recorded")
7386
err = cmd.MarkFlagRequired("value")
7487
cobra.CheckErr(err)
88+
cmd.Flags().StringSliceVar(&annotationsFlag, "annotation", nil, "additional annotation in the format of key=value")
7589

7690
return cmd
7791
}

app/cli/internal/action/attestation_add.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ func NewAttestationAdd(cfg *AttestationAddOpts) *AttestationAdd {
5454

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

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

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

app/controlplane/api/gen/frontend/workflowcontract/v1/crafting_schema.ts

Lines changed: 1 addition & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/controlplane/api/workflowcontract/v1/crafting_schema.pb.go

Lines changed: 10 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/controlplane/api/workflowcontract/v1/crafting_schema.pb.validate.go

Lines changed: 1 addition & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/controlplane/api/workflowcontract/v1/crafting_schema.proto

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ message CraftingSchema {
6565

6666
message Annotation {
6767
string name = 1 [(validate.rules).string.pattern = "^[\\w]+$"]; // Single word optionally separated with _
68-
// TODO: This value is required for now, but this behavior will change once we allow
69-
// the user to set the value during attestation
70-
string value = 2 [(validate.rules).string.min_len = 1];
68+
// This value can be set in the contract or provided during the attestation
69+
string value = 2;
7170
}

app/controlplane/api/workflowcontract/v1/crafting_schema_test.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,6 @@ func TestValidateAnnotations(t *testing.T) {
4141
value: "hi",
4242
wantErr: true,
4343
},
44-
{
45-
desc: "missing value",
46-
name: "hi",
47-
wantErr: true,
48-
},
4944
{
5045
desc: "valid key underscore",
5146
name: "hello_world",

internal/attestation/crafter/crafter.go

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ func notResolvedVars(resolved map[string]string, wantList []string) []string {
326326
}
327327

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

357+
// 4 - Populate annotations from the ones provided at runtime
358+
// a) we do not allow overriding values that come from the contract
359+
// b) we do not allow adding annotations that are not defined in the contract
360+
for kr, vr := range runtimeAnnotations {
361+
// If the annotation is not defined in the material we fail
362+
if v, found := mt.Annotations[kr]; !found {
363+
return fmt.Errorf("annotation %q not found in material %q", kr, key)
364+
} else if v == "" {
365+
// Set it only if it's not set
366+
mt.Annotations[kr] = vr
367+
} else {
368+
// NOTE: we do not allow overriding values that come from the contract
369+
c.logger.Info().Str("key", key).Str("annotation", kr).Msg("annotation can't be changed, skipping")
370+
}
371+
}
372+
373+
// Make sure all the annotation values are now set
374+
// This is in fact validated below but by manually checking we can provide a better error message
375+
for k, v := range mt.Annotations {
376+
var missingAnnotations []string
377+
if v == "" {
378+
missingAnnotations = append(missingAnnotations, k)
379+
}
380+
381+
if len(missingAnnotations) > 0 {
382+
return fmt.Errorf("annotations %q required for material %q", missingAnnotations, key)
383+
}
384+
}
385+
357386
if err := mt.Validate(); err != nil {
358387
return fmt.Errorf("validation error: %w", err)
359388
}
360389

361-
// 4 - Attach it to state
390+
// 5 - Attach it to state
362391
if mt != nil {
363392
if c.CraftingState.Attestation.Materials == nil {
364393
c.CraftingState.Attestation.Materials = map[string]*api.Attestation_Material{key: mt}
365394
}
366395
c.CraftingState.Attestation.Materials[key] = mt
367396
}
368397

369-
// 5 - Persist state
398+
// 6 - Persist state
370399
if err := persistCraftingState(c.CraftingState, c.statePath); err != nil {
371400
return err
372401
}

0 commit comments

Comments
 (0)