Skip to content

Commit 7dfb5c9

Browse files
authored
refactor(cli): store material info in crafting state (#1714)
Signed-off-by: Miguel Martinez <[email protected]>
1 parent 4042914 commit 7dfb5c9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+534
-532
lines changed

app/cli/cmd/attestation_add.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Copyright 2023 The Chainloop Authors.
2+
// Copyright 2024-2025 The Chainloop Authors.
33
//
44
// Licensed under the Apache License, Version 2.0 (the "License");
55
// you may not use this file except in compliance with the License.
@@ -62,7 +62,7 @@ func newAttestationAddCmd() *cobra.Command {
6262

6363
return nil
6464
},
65-
RunE: func(cmd *cobra.Command, args []string) error {
65+
RunE: func(cmd *cobra.Command, _ []string) error {
6666
a, err := action.NewAttestationAdd(
6767
&action.AttestationAddOpts{
6868
ActionsOpts: actionOpts,
@@ -89,7 +89,9 @@ func newAttestationAddCmd() *cobra.Command {
8989
// optimistic locking. We retry the operation if the state has changed since we last read it.
9090
return runWithBackoffRetry(
9191
func() error {
92-
if err := a.Run(cmd.Context(), attestationID, name, value, kind, annotations); err != nil {
92+
// TODO: take the material output and show render it
93+
_, err := a.Run(cmd.Context(), attestationID, name, value, kind, annotations)
94+
if err != nil {
9395
return err
9496
}
9597

app/cli/internal/action/attestation_add.go

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Copyright 2024 The Chainloop Authors.
2+
// Copyright 2024-2025 The Chainloop Authors.
33
//
44
// Licensed under the Apache License, Version 2.0 (the "License");
55
// you may not use this file except in compliance with the License.
@@ -21,10 +21,10 @@ import (
2121
"fmt"
2222

2323
pb "github.com/chainloop-dev/chainloop/app/controlplane/api/controlplane/v1"
24-
schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1"
2524
"github.com/chainloop-dev/chainloop/internal/casclient"
2625
"github.com/chainloop-dev/chainloop/internal/grpcconn"
2726
"github.com/chainloop-dev/chainloop/pkg/attestation/crafter"
27+
api "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/api/attestation/v1"
2828
"google.golang.org/grpc"
2929
)
3030

@@ -73,22 +73,22 @@ func NewAttestationAdd(cfg *AttestationAddOpts) (*AttestationAdd, error) {
7373

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

76-
func (action *AttestationAdd) Run(ctx context.Context, attestationID, materialName, materialValue, materialType string, annotations map[string]string) error {
76+
func (action *AttestationAdd) Run(ctx context.Context, attestationID, materialName, materialValue, materialType string, annotations map[string]string) (*AttestationStatusMaterial, error) {
7777
// initialize the crafter. If attestation-id is provided we assume the attestation is performed using remote state
7878
crafter, err := newCrafter(&newCrafterStateOpts{enableRemoteState: (attestationID != ""), localStatePath: action.localStatePath}, action.CPConnection, action.newCrafterOpts.opts...)
7979
if err != nil {
80-
return fmt.Errorf("failed to load crafter: %w", err)
80+
return nil, fmt.Errorf("failed to load crafter: %w", err)
8181
}
8282

8383
if initialized, err := crafter.AlreadyInitialized(ctx, attestationID); err != nil {
84-
return fmt.Errorf("checking if attestation is already initialized: %w", err)
84+
return nil, fmt.Errorf("checking if attestation is already initialized: %w", err)
8585
} else if !initialized {
86-
return ErrAttestationNotInitialized
86+
return nil, ErrAttestationNotInitialized
8787
}
8888

8989
if err := crafter.LoadCraftingState(ctx, attestationID); err != nil {
9090
action.Logger.Err(err).Msg("loading existing attestation")
91-
return err
91+
return nil, err
9292
}
9393

9494
// Default to inline CASBackend and override if we are not in dry-run mode
@@ -106,11 +106,11 @@ func (action *AttestationAdd) Run(ctx context.Context, attestationID, materialNa
106106
},
107107
)
108108
if err != nil {
109-
return err
109+
return nil, fmt.Errorf("getting upload creds: %w", err)
110110
}
111111
b := creds.GetResult().GetBackend()
112112
if b == nil {
113-
return fmt.Errorf("no backend found in upload creds")
113+
return nil, fmt.Errorf("no backend found in upload creds")
114114
}
115115
casBackend.Name = b.Provider
116116
casBackend.MaxSize = b.GetLimits().MaxBytes
@@ -127,7 +127,7 @@ func (action *AttestationAdd) Run(ctx context.Context, attestationID, materialNa
127127

128128
artifactCASConn, err := grpcconn.New(action.casURI, creds.Result.Token, opts...)
129129
if err != nil {
130-
return err
130+
return nil, fmt.Errorf("creating CAS connection: %w", err)
131131
}
132132
defer artifactCASConn.Close()
133133

@@ -141,37 +141,42 @@ func (action *AttestationAdd) Run(ctx context.Context, attestationID, materialNa
141141
// 2. If materialName is not empty, check if the material is in the contract. If it is, add material from contract
142142
// 2.1. If materialType is empty, try to guess the material kind with auto-detected kind and materialName
143143
// 3. If materialType is not empty, add material contract free with materialType and materialName
144-
var kind schemaapi.CraftingSchema_Material_MaterialType
144+
var mt *api.Attestation_Material
145145
switch {
146146
case materialName == "" && materialType == "":
147-
kind, err = crafter.AddMaterialContactFreeWithAutoDetectedKind(ctx, attestationID, "", materialValue, casBackend, annotations)
147+
mt, err = crafter.AddMaterialContactFreeWithAutoDetectedKind(ctx, attestationID, "", materialValue, casBackend, annotations)
148148
if err != nil {
149-
return fmt.Errorf("adding material: %w", err)
149+
return nil, fmt.Errorf("adding material: %w", err)
150150
}
151-
action.Logger.Info().Str("kind", kind.String()).Msg("material kind detected")
151+
action.Logger.Info().Str("kind", mt.MaterialType.String()).Msg("material kind detected")
152152
case materialName != "":
153153
switch {
154154
// If the material is in the contract, add it from the contract
155155
case crafter.IsMaterialInContract(materialName):
156-
err = crafter.AddMaterialFromContract(ctx, attestationID, materialName, materialValue, casBackend, annotations)
156+
mt, err = crafter.AddMaterialFromContract(ctx, attestationID, materialName, materialValue, casBackend, annotations)
157157
// If the material is not in the contract and the materialType is not provided, add material contract free with auto-detected kind, guessing the kind
158158
case materialType == "":
159-
kind, err = crafter.AddMaterialContactFreeWithAutoDetectedKind(ctx, attestationID, materialName, materialValue, casBackend, annotations)
159+
mt, err = crafter.AddMaterialContactFreeWithAutoDetectedKind(ctx, attestationID, materialName, materialValue, casBackend, annotations)
160160
if err != nil {
161-
return fmt.Errorf("adding material: %w", err)
161+
return nil, fmt.Errorf("adding material: %w", err)
162162
}
163-
action.Logger.Info().Str("kind", kind.String()).Msg("material kind detected")
163+
action.Logger.Info().Str("kind", mt.MaterialType.String()).Msg("material kind detected")
164164
// If the material is not in the contract and has a materialType, add material contract free with the provided materialType
165165
default:
166-
err = crafter.AddMaterialContractFree(ctx, attestationID, materialType, materialName, materialValue, casBackend, annotations)
166+
mt, err = crafter.AddMaterialContractFree(ctx, attestationID, materialType, materialName, materialValue, casBackend, annotations)
167167
}
168168
default:
169-
err = crafter.AddMaterialContractFree(ctx, attestationID, materialType, materialName, materialValue, casBackend, annotations)
169+
mt, err = crafter.AddMaterialContractFree(ctx, attestationID, materialType, materialName, materialValue, casBackend, annotations)
170170
}
171171

172172
if err != nil {
173-
return fmt.Errorf("adding material: %w", err)
173+
return nil, fmt.Errorf("adding material: %w", err)
174174
}
175175

176-
return nil
176+
materialResult, err := attMaterialToAction(mt)
177+
if err != nil {
178+
return nil, fmt.Errorf("converting material to action: %w", err)
179+
}
180+
181+
return materialResult, nil
177182
}

app/cli/internal/action/attestation_status.go

Lines changed: 39 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -45,18 +45,18 @@ type AttestationStatus struct {
4545
}
4646

4747
type AttestationStatusResult struct {
48-
AttestationID string `json:"attestationID"`
49-
InitializedAt *time.Time `json:"initializedAt"`
50-
WorkflowMeta *AttestationStatusWorkflowMeta `json:"workflowMeta"`
51-
Materials []AttestationStatusResultMaterial `json:"materials"`
52-
EnvVars map[string]string `json:"envVars"`
53-
RunnerContext *AttestationResultRunnerContext `json:"runnerContext"`
54-
DryRun bool `json:"dryRun"`
55-
Annotations []*Annotation `json:"annotations"`
56-
IsPushed bool `json:"isPushed"`
57-
PolicyEvaluations map[string][]*PolicyEvaluation `json:"policy_evaluations,omitempty"`
58-
HasPolicyViolations bool `json:"has_policy_violations"`
59-
MustBlockOnPolicyViolations bool `json:"must_block_on_policy_violations"`
48+
AttestationID string `json:"attestationID"`
49+
InitializedAt *time.Time `json:"initializedAt"`
50+
WorkflowMeta *AttestationStatusWorkflowMeta `json:"workflowMeta"`
51+
Materials []AttestationStatusMaterial `json:"materials"`
52+
EnvVars map[string]string `json:"envVars"`
53+
RunnerContext *AttestationResultRunnerContext `json:"runnerContext"`
54+
DryRun bool `json:"dryRun"`
55+
Annotations []*Annotation `json:"annotations"`
56+
IsPushed bool `json:"isPushed"`
57+
PolicyEvaluations map[string][]*PolicyEvaluation `json:"policy_evaluations,omitempty"`
58+
HasPolicyViolations bool `json:"has_policy_violations"`
59+
MustBlockOnPolicyViolations bool `json:"must_block_on_policy_violations"`
6060
// This might only be set if the attestation is pushed
6161
Digest string `json:"digest"`
6262
}
@@ -71,7 +71,7 @@ type AttestationStatusWorkflowMeta struct {
7171
ProjectVersion *ProjectVersion
7272
}
7373

74-
type AttestationStatusResultMaterial struct {
74+
type AttestationStatusMaterial struct {
7575
*Material
7676
Set, IsOutput, Required bool
7777
}
@@ -255,7 +255,8 @@ func populateMaterials(craftingState *v1.CraftingState, res *AttestationStatusRe
255255
// populateContractMaterials populates the materials that are defined in the contract schema
256256
func populateContractMaterials(inputSchemaMaterials []*pbc.CraftingSchema_Material, attsMaterial map[string]*v1.Attestation_Material, res *AttestationStatusResult, visitedMaterials map[string]struct{}) error {
257257
for _, m := range inputSchemaMaterials {
258-
materialResult := &AttestationStatusResultMaterial{
258+
// This one need to be crafter manually because it might not be in the attestation yet
259+
materialResult := &AttestationStatusMaterial{
259260
Material: &Material{
260261
Name: m.Name, Type: m.Type.String(),
261262
Annotations: pbAnnotationsToAction(m.Annotations),
@@ -284,26 +285,37 @@ func populateAdditionalMaterials(attsMaterials map[string]*v1.Attestation_Materi
284285

285286
// No need to check for name collisions, as it is not defined in the contract schema and it's
286287
// autogenerated by the crafter
287-
materialResult := &AttestationStatusResultMaterial{
288-
Material: &Material{
289-
Name: name,
290-
Type: m.GetMaterialType().String(),
291-
Annotations: stateAnnotationToAction(m.Annotations),
292-
},
293-
// No need to check if the material is optional or not, as it is not defined in the contract schema
294-
// TODO: Make IsOutput configurable
295-
IsOutput: false, Required: false,
296-
}
297-
298-
if err := setMaterialValue(m, materialResult); err != nil {
288+
materialResult, err := attMaterialToAction(m)
289+
if err != nil {
299290
return fmt.Errorf("setting material value: %w", err)
300291
}
301292

302293
res.Materials = append(res.Materials, *materialResult)
294+
visitedMaterials[name] = struct{}{}
303295
}
296+
304297
return nil
305298
}
306299

300+
// attMaterialToAction converts the attestation material to the action material
301+
func attMaterialToAction(m *v1.Attestation_Material) (*AttestationStatusMaterial, error) {
302+
res := &AttestationStatusMaterial{
303+
Material: &Material{
304+
Name: m.GetId(),
305+
Type: m.GetMaterialType().String(),
306+
Annotations: stateAnnotationToAction(m.Annotations),
307+
},
308+
IsOutput: m.Output,
309+
Required: m.Required,
310+
}
311+
312+
if err := setMaterialValue(m, res); err != nil {
313+
return nil, fmt.Errorf("setting material value: %w", err)
314+
}
315+
316+
return res, nil
317+
}
318+
307319
func pbAnnotationsToAction(in []*pbc.Annotation) []*Annotation {
308320
res := make([]*Annotation, 0, len(in))
309321

@@ -331,7 +343,7 @@ func stateAnnotationToAction(in map[string]string) []*Annotation {
331343
return res
332344
}
333345

334-
func setMaterialValue(w *v1.Attestation_Material, o *AttestationStatusResultMaterial) error {
346+
func setMaterialValue(w *v1.Attestation_Material, o *AttestationStatusMaterial) error {
335347
switch m := w.GetM().(type) {
336348
case *v1.Attestation_Material_String_:
337349
o.Value = m.String_.GetValue()

app/cli/internal/action/attestation_status_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Copyright 2024 The Chainloop Authors.
2+
// Copyright 2024-2025 The Chainloop Authors.
33
//
44
// Licensed under the Apache License, Version 2.0 (the "License");
55
// you may not use this file except in compliance with the License.
@@ -67,9 +67,9 @@ func TestPopulateContractMaterials(t *testing.T) {
6767
Attestation: &v1.Attestation{
6868
Materials: map[string]*v1.Attestation_Material{
6969
"vex-file": {
70+
Id: "random",
7071
M: &v1.Attestation_Material_Artifact_{
7172
Artifact: &v1.Attestation_Material_Artifact{
72-
Id: "random",
7373
Name: "vex-file",
7474
Digest: "random-digest",
7575
Content: []byte("random-content"),
@@ -79,9 +79,9 @@ func TestPopulateContractMaterials(t *testing.T) {
7979
UploadedToCas: true,
8080
},
8181
"other-file": {
82+
Id: "random",
8283
M: &v1.Attestation_Material_Artifact_{
8384
Artifact: &v1.Attestation_Material_Artifact{
84-
Id: "random",
8585
Name: "other-file",
8686
Digest: "random-digest-2",
8787
Content: []byte("random-content-2"),

0 commit comments

Comments
 (0)