diff --git a/app/cli/cmd/workflow_workflow_run_describe.go b/app/cli/cmd/workflow_workflow_run_describe.go index 38bd4ca3e..f6bb4f35c 100644 --- a/app/cli/cmd/workflow_workflow_run_describe.go +++ b/app/cli/cmd/workflow_workflow_run_describe.go @@ -25,6 +25,7 @@ import ( "strings" "time" + "code.cloudfoundry.org/bytefmt" "github.com/chainloop-dev/chainloop/app/cli/cmd/output" "github.com/chainloop-dev/chainloop/app/cli/pkg/action" attv1 "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/api/attestation/v1" @@ -174,11 +175,7 @@ func workflowRunDescribeTableOutput(run *action.WorkflowRunItemFull) error { gt.AppendRow(table.Row{"Policy enforcement bypassed", att.PolicyEvaluationStatus.Bypassed}) } - evs := att.PolicyEvaluations[chainloop.AttPolicyEvaluation] - if len(evs) > 0 { - gt.AppendRow(table.Row{"Policies", "------"}) - policiesTable(evs, gt, flagDebug) - } + appendPolicySection(att, gt, flagDebug) if run.Attestation.AttestationViewURL != "" { gt.AppendRow(table.Row{"Attestation View URL", run.Attestation.AttestationViewURL}) @@ -301,6 +298,72 @@ func policiesTable(evs []*action.PolicyEvaluation, mt table.Writer, debugMode bo } } +// appendPolicySection renders the attestation-level policies, either as the +// usual per-policy rows or, when the server declined to inline the +// evaluations, as a notice pointing at the bundle in the CAS backend. +func appendPolicySection(att *action.WorkflowRunAttestationItem, gt table.Writer, debugMode bool) { + if notice := policyEvaluationsRefNotice(att.PolicyEvaluationsRef, att.PolicyEvaluationStatus); notice != nil { + gt.AppendRow(table.Row{"Policies", "------"}) + for _, line := range notice { + gt.AppendRow(table.Row{"", line}) + } + + return + } + + evs := att.PolicyEvaluations[chainloop.AttPolicyEvaluation] + if len(evs) == 0 { + return + } + + gt.AppendRow(table.Row{"Policies", "------"}) + policiesTable(evs, gt, debugMode) +} + +// policyEvaluationsRefNotice renders the lines shown in place of the policy +// table when the server returned a reference instead of the evaluations. It +// leads with the counters, which stay accurate no matter how large the bundle +// is, and only offers a download when the bundle is known to be there. +func policyEvaluationsRefNotice(ref *action.PolicyEvaluationsRef, status *action.PolicyEvaluationStatus) []string { + if ref == nil { + return nil + } + + reason := "could not be retrieved from the CAS backend" + if ref.Reason == action.PolicyEvaluationsRefReasonTooLarge { + reason = "too large to include inline" + if ref.SizeBytes > 0 { + reason = fmt.Sprintf("%s (%s)", reason, bytefmt.ByteSize(uint64(ref.SizeBytes))) + } + } + + // Without counters there is nothing to summarize, so name the subject instead. + if status == nil { + lines := []string{fmt.Sprintf("policy evaluations %s", reason)} + if ref.Reason == action.PolicyEvaluationsRefReasonTooLarge { + lines = append(lines, downloadPolicyEvaluationsHint(ref)) + } + + return lines + } + + counters := fmt.Sprintf("%d evaluations, %d violations", status.Total, status.Violated) + if status.Suppressed > 0 { + counters = fmt.Sprintf("%s (%d suppressed)", counters, status.Suppressed) + } + + lines := []string{fmt.Sprintf("%s - %s", counters, reason)} + if ref.Reason == action.PolicyEvaluationsRefReasonTooLarge { + lines = append(lines, downloadPolicyEvaluationsHint(ref)) + } + + return lines +} + +func downloadPolicyEvaluationsHint(ref *action.PolicyEvaluationsRef) string { + return fmt.Sprintf("inspect with: chainloop artifact download --digest %s", ref.Digest) +} + // violationSummary builds a single-line description of a violation using the // structured finding when present (CVE id + severity + package + fix info, // or SAST rule + location, or license + component). Falls back to the first diff --git a/app/cli/cmd/workflow_workflow_run_describe_test.go b/app/cli/cmd/workflow_workflow_run_describe_test.go index fb76e11fe..04c886cfb 100644 --- a/app/cli/cmd/workflow_workflow_run_describe_test.go +++ b/app/cli/cmd/workflow_workflow_run_describe_test.go @@ -1,5 +1,5 @@ // -// Copyright 2024 The Chainloop Authors. +// Copyright 2024-2026 The Chainloop Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -22,6 +22,8 @@ import ( "github.com/chainloop-dev/chainloop/app/cli/pkg/action" attv1 "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/api/attestation/v1" + "github.com/chainloop-dev/chainloop/pkg/attestation/renderer/chainloop" + "github.com/jedib0t/go-pretty/v6/table" "github.com/secure-systems-lab/go-securesystemslib/dsse" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" @@ -208,3 +210,146 @@ func (s *workflowRunDescribeSuite) TestOutputTypePayload() { s.Require().NoError(err) s.Equal(expected, buf.String()) } + +const ( + testRefDigest = "sha256:abc123" + testRefDownloadHint = "inspect with: chainloop artifact download --digest " + testRefDigest + policiesRowLabel = "Policies" +) + +func TestPolicyEvaluationsRefNotice(t *testing.T) { + tests := []struct { + name string + ref *action.PolicyEvaluationsRef + status *action.PolicyEvaluationStatus + want []string + }{ + { + name: "no reference produces no notice", + }, + { + name: "oversized bundle reports counters, size and how to fetch it", + ref: &action.PolicyEvaluationsRef{ + Digest: testRefDigest, + SizeBytes: 64 * 1024 * 1024, + Reason: action.PolicyEvaluationsRefReasonTooLarge, + }, + status: &action.PolicyEvaluationStatus{Total: 12, Violated: 134112, Suppressed: 86321}, + want: []string{ + "12 evaluations, 134112 violations (86321 suppressed) - too large to include inline (64M)", + testRefDownloadHint, + }, + }, + { + name: "oversized bundle with nothing suppressed omits the suppressed count", + ref: &action.PolicyEvaluationsRef{ + Digest: testRefDigest, + SizeBytes: 3 * 1024 * 1024, + Reason: action.PolicyEvaluationsRefReasonTooLarge, + }, + status: &action.PolicyEvaluationStatus{Total: 2, Violated: 40}, + want: []string{ + "2 evaluations, 40 violations - too large to include inline (3M)", + testRefDownloadHint, + }, + }, + { + name: "unknown size omits the size", + ref: &action.PolicyEvaluationsRef{ + Digest: testRefDigest, + Reason: action.PolicyEvaluationsRefReasonTooLarge, + }, + status: &action.PolicyEvaluationStatus{Total: 2, Violated: 40}, + want: []string{ + "2 evaluations, 40 violations - too large to include inline", + testRefDownloadHint, + }, + }, + { + name: "unavailable bundle does not suggest a download", + ref: &action.PolicyEvaluationsRef{ + Digest: testRefDigest, + Reason: action.PolicyEvaluationsRefReasonUnavailable, + }, + status: &action.PolicyEvaluationStatus{Total: 3, Violated: 7}, + want: []string{ + "3 evaluations, 7 violations - could not be retrieved from the CAS backend", + }, + }, + { + name: "missing status still reports the reason", + ref: &action.PolicyEvaluationsRef{ + Digest: testRefDigest, + SizeBytes: 1024, + Reason: action.PolicyEvaluationsRefReasonTooLarge, + }, + want: []string{ + "policy evaluations too large to include inline (1K)", + testRefDownloadHint, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + assert.Equal(t, tc.want, policyEvaluationsRefNotice(tc.ref, tc.status)) + }) + } +} + +func TestAppendPolicySection(t *testing.T) { + tests := []struct { + name string + attestation *action.WorkflowRunAttestationItem + wantContain []string + wantAbsent []string + }{ + { + name: "inlined evaluations are rendered as policy rows", + attestation: &action.WorkflowRunAttestationItem{ + PolicyEvaluations: map[string][]*action.PolicyEvaluation{ + chainloop.AttPolicyEvaluation: { + {Name: "strong-acl", Violations: []*action.PolicyViolation{{Message: "weak ACL"}}}, + }, + }, + PolicyEvaluationStatus: &action.PolicyEvaluationStatus{Total: 1, Violated: 1}, + }, + wantContain: []string{policiesRowLabel, "strong-acl", "weak ACL"}, + wantAbsent: []string{"artifact download"}, + }, + { + name: "an oversized bundle is rendered as a notice instead", + attestation: &action.WorkflowRunAttestationItem{ + PolicyEvaluationStatus: &action.PolicyEvaluationStatus{Total: 12, Violated: 134112}, + PolicyEvaluationsRef: &action.PolicyEvaluationsRef{ + Digest: testRefDigest, + SizeBytes: 64 * 1024 * 1024, + Reason: action.PolicyEvaluationsRefReasonTooLarge, + }, + }, + wantContain: []string{policiesRowLabel, "134112 violations", "too large", "artifact download --digest " + testRefDigest}, + }, + { + name: "no policies and no reference renders nothing", + attestation: &action.WorkflowRunAttestationItem{ + PolicyEvaluationStatus: &action.PolicyEvaluationStatus{}, + }, + wantAbsent: []string{"Policies"}, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + tw := table.NewWriter() + appendPolicySection(tc.attestation, tw, false) + got := tw.Render() + + for _, want := range tc.wantContain { + assert.Contains(t, got, want) + } + for _, absent := range tc.wantAbsent { + assert.NotContains(t, got, absent) + } + }) + } +} diff --git a/app/cli/pkg/action/workflow_run_describe.go b/app/cli/pkg/action/workflow_run_describe.go index aae6448bb..8d36c55cb 100644 --- a/app/cli/pkg/action/workflow_run_describe.go +++ b/app/cli/pkg/action/workflow_run_describe.go @@ -63,6 +63,10 @@ type WorkflowRunAttestationItem struct { PolicyEvaluations map[string][]*PolicyEvaluation `json:"policy_evaluations,omitempty"` // Policy evaluation status PolicyEvaluationStatus *PolicyEvaluationStatus `json:"policy_evaluation_status,omitempty"` + // Set when the evaluations were not inlined above and must be fetched from + // the CAS backend instead. PolicyEvaluations is empty in that case, while + // PolicyEvaluationStatus stays complete. + PolicyEvaluationsRef *PolicyEvaluationsRef `json:"policy_evaluations_ref,omitempty"` // URL to view the attestation in the UI AttestationViewURL string `json:"attestation_view_url"` } @@ -73,6 +77,15 @@ type PolicyEvaluationStatus struct { Blocked bool `json:"blocked"` HasViolations bool `json:"has_violations"` HasGatedViolations bool `json:"has_gated_violations"` + // Canonical, server-computed status and counters. Status is empty for + // runs that predate the server materializing the summary. + Status string `json:"status,omitempty"` + Total int `json:"total"` + Passed int `json:"passed"` + Skipped int `json:"skipped"` + Violated int `json:"violated"` + Suppressed int `json:"suppressed"` + HasGates bool `json:"has_gates"` } type Material struct { @@ -98,6 +111,27 @@ type Annotation struct { Value string `json:"value"` } +// PolicyEvaluationsRefReason explains why the evaluations were not included +// in the response. +type PolicyEvaluationsRefReason string + +const ( + // The bundle is larger than the server is willing to inline + PolicyEvaluationsRefReasonTooLarge PolicyEvaluationsRefReason = "TOO_LARGE" + // The bundle could not be resolved from the CAS backend + PolicyEvaluationsRefReasonUnavailable PolicyEvaluationsRefReason = "UNAVAILABLE" +) + +// PolicyEvaluationsRef points at a policy-evaluation bundle stored in a CAS +// backend, returned in place of the evaluations themselves. +type PolicyEvaluationsRef struct { + Digest string `json:"digest"` + // Size of the bundle in bytes, zero when it could not be determined + SizeBytes int64 `json:"size_bytes,omitempty"` + MediaType string `json:"media_type,omitempty"` + Reason PolicyEvaluationsRefReason `json:"reason"` +} + type PolicyEvaluation struct { Name string `json:"name"` MaterialName string `json:"material_name,omitempty"` @@ -237,6 +271,14 @@ func (action *WorkflowRunDescribe) Run(ctx context.Context, opts *WorkflowRunDes } policyEvaluationStatus := att.GetPolicyEvaluationStatus() + summary := policyEvaluationStatus.GetSummary() + + // Left empty for runs that predate the server materializing the summary, + // so consumers can tell "no policies" apart from "not reported". + var summaryStatus string + if summary != nil { + summaryStatus = summary.GetStatus().String() + } var attestationViewURL string baseUIDashboardURL := fetchUIDashboardURL(ctx, action.cfg.CPConnection) @@ -259,8 +301,16 @@ func (action *WorkflowRunDescribe) Run(ctx context.Context, opts *WorkflowRunDes Blocked: policyEvaluationStatus.Blocked, HasViolations: policyEvaluationStatus.HasViolations, HasGatedViolations: policyEvaluationStatus.HasGatedViolations, + Status: summaryStatus, + Total: int(summary.GetTotal()), + Passed: int(summary.GetPassed()), + Skipped: int(summary.GetSkipped()), + Violated: int(summary.GetViolated()), + Suppressed: int(summary.GetSuppressed()), + HasGates: summary.GetHasGates(), }, - AttestationViewURL: attestationViewURL, + PolicyEvaluationsRef: pbPolicyEvaluationsRefToAction(att.GetPolicyEvaluationsRef()), + AttestationViewURL: attestationViewURL, } return item, nil @@ -289,6 +339,28 @@ func trustedRootPbToVerifier(resp *pb.GetTrustedRootResponse) (*verifier.Trusted return tr, nil } +// pbPolicyEvaluationsRefToAction maps the reference the server returns when it +// declines to inline the evaluations. An unspecified reason is treated as +// unavailable, which is the more conservative rendering: it does not promise +// the caller that a download would succeed. +func pbPolicyEvaluationsRefToAction(in *pb.PolicyEvaluationsRef) *PolicyEvaluationsRef { + if in == nil { + return nil + } + + reason := PolicyEvaluationsRefReasonUnavailable + if in.GetReason() == pb.PolicyEvaluationsRef_REASON_TOO_LARGE { + reason = PolicyEvaluationsRefReasonTooLarge + } + + return &PolicyEvaluationsRef{ + Digest: in.GetDigest(), + SizeBytes: in.GetSizeBytes(), + MediaType: in.GetMediaType(), + Reason: reason, + } +} + func policyEvaluationPBToAction(in *pb.PolicyEvaluation) *PolicyEvaluation { var pr *PolicyReference if in.PolicyReference != nil { diff --git a/app/controlplane/api/controlplane/v1/response_messages.pb.go b/app/controlplane/api/controlplane/v1/response_messages.pb.go index 1b236dac0..4bc706ee7 100644 --- a/app/controlplane/api/controlplane/v1/response_messages.pb.go +++ b/app/controlplane/api/controlplane/v1/response_messages.pb.go @@ -579,6 +579,57 @@ func (UserNotMemberOfOrgError) EnumDescriptor() ([]byte, []int) { return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{9} } +type PolicyEvaluationsRef_Reason int32 + +const ( + PolicyEvaluationsRef_REASON_UNSPECIFIED PolicyEvaluationsRef_Reason = 0 + // The bundle exceeds the maximum size the server inlines in a response + PolicyEvaluationsRef_REASON_TOO_LARGE PolicyEvaluationsRef_Reason = 1 + // The bundle could not be resolved from CAS + PolicyEvaluationsRef_REASON_UNAVAILABLE PolicyEvaluationsRef_Reason = 2 +) + +// Enum value maps for PolicyEvaluationsRef_Reason. +var ( + PolicyEvaluationsRef_Reason_name = map[int32]string{ + 0: "REASON_UNSPECIFIED", + 1: "REASON_TOO_LARGE", + 2: "REASON_UNAVAILABLE", + } + PolicyEvaluationsRef_Reason_value = map[string]int32{ + "REASON_UNSPECIFIED": 0, + "REASON_TOO_LARGE": 1, + "REASON_UNAVAILABLE": 2, + } +) + +func (x PolicyEvaluationsRef_Reason) Enum() *PolicyEvaluationsRef_Reason { + p := new(PolicyEvaluationsRef_Reason) + *p = x + return p +} + +func (x PolicyEvaluationsRef_Reason) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (PolicyEvaluationsRef_Reason) Descriptor() protoreflect.EnumDescriptor { + return file_controlplane_v1_response_messages_proto_enumTypes[10].Descriptor() +} + +func (PolicyEvaluationsRef_Reason) Type() protoreflect.EnumType { + return &file_controlplane_v1_response_messages_proto_enumTypes[10] +} + +func (x PolicyEvaluationsRef_Reason) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use PolicyEvaluationsRef_Reason.Descriptor instead. +func (PolicyEvaluationsRef_Reason) EnumDescriptor() ([]byte, []int) { + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{5, 0} +} + type WorkflowContractVersionItem_RawBody_Format int32 const ( @@ -615,11 +666,11 @@ func (x WorkflowContractVersionItem_RawBody_Format) String() string { } func (WorkflowContractVersionItem_RawBody_Format) Descriptor() protoreflect.EnumDescriptor { - return file_controlplane_v1_response_messages_proto_enumTypes[10].Descriptor() + return file_controlplane_v1_response_messages_proto_enumTypes[11].Descriptor() } func (WorkflowContractVersionItem_RawBody_Format) Type() protoreflect.EnumType { - return &file_controlplane_v1_response_messages_proto_enumTypes[10] + return &file_controlplane_v1_response_messages_proto_enumTypes[11] } func (x WorkflowContractVersionItem_RawBody_Format) Number() protoreflect.EnumNumber { @@ -628,7 +679,7 @@ func (x WorkflowContractVersionItem_RawBody_Format) Number() protoreflect.EnumNu // Deprecated: Use WorkflowContractVersionItem_RawBody_Format.Descriptor instead. func (WorkflowContractVersionItem_RawBody_Format) EnumDescriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{12, 0, 0} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{13, 0, 0} } type OrgItem_PolicyViolationBlockingStrategy int32 @@ -664,11 +715,11 @@ func (x OrgItem_PolicyViolationBlockingStrategy) String() string { } func (OrgItem_PolicyViolationBlockingStrategy) Descriptor() protoreflect.EnumDescriptor { - return file_controlplane_v1_response_messages_proto_enumTypes[11].Descriptor() + return file_controlplane_v1_response_messages_proto_enumTypes[12].Descriptor() } func (OrgItem_PolicyViolationBlockingStrategy) Type() protoreflect.EnumType { - return &file_controlplane_v1_response_messages_proto_enumTypes[11] + return &file_controlplane_v1_response_messages_proto_enumTypes[12] } func (x OrgItem_PolicyViolationBlockingStrategy) Number() protoreflect.EnumNumber { @@ -677,7 +728,7 @@ func (x OrgItem_PolicyViolationBlockingStrategy) Number() protoreflect.EnumNumbe // Deprecated: Use OrgItem_PolicyViolationBlockingStrategy.Descriptor instead. func (OrgItem_PolicyViolationBlockingStrategy) EnumDescriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{15, 0} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{16, 0} } type CASBackendItem_ValidationStatus int32 @@ -713,11 +764,11 @@ func (x CASBackendItem_ValidationStatus) String() string { } func (CASBackendItem_ValidationStatus) Descriptor() protoreflect.EnumDescriptor { - return file_controlplane_v1_response_messages_proto_enumTypes[12].Descriptor() + return file_controlplane_v1_response_messages_proto_enumTypes[13].Descriptor() } func (CASBackendItem_ValidationStatus) Type() protoreflect.EnumType { - return &file_controlplane_v1_response_messages_proto_enumTypes[12] + return &file_controlplane_v1_response_messages_proto_enumTypes[13] } func (x CASBackendItem_ValidationStatus) Number() protoreflect.EnumNumber { @@ -726,7 +777,7 @@ func (x CASBackendItem_ValidationStatus) Number() protoreflect.EnumNumber { // Deprecated: Use CASBackendItem_ValidationStatus.Descriptor instead. func (CASBackendItem_ValidationStatus) EnumDescriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{16, 0} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{17, 0} } type WorkflowItem struct { @@ -1259,8 +1310,14 @@ type AttestationItem struct { Annotations map[string]string `protobuf:"bytes,6,rep,name=annotations,proto3" json:"annotations,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` PolicyEvaluations map[string]*PolicyEvaluations `protobuf:"bytes,8,rep,name=policy_evaluations,json=policyEvaluations,proto3" json:"policy_evaluations,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` PolicyEvaluationStatus *AttestationItem_PolicyEvaluationStatus `protobuf:"bytes,9,opt,name=policy_evaluation_status,json=policyEvaluationStatus,proto3" json:"policy_evaluation_status,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Reference to the policy-evaluation bundle in the CAS backend. Populated + // only when the evaluations are NOT inlined in policy_evaluations, either + // because the bundle is larger than the server inlines or because it could + // not be resolved. Fetch the bundle by digest to inspect the violations. + // Counters and status in policy_evaluation_status remain complete either way. + PolicyEvaluationsRef *PolicyEvaluationsRef `protobuf:"bytes,11,opt,name=policy_evaluations_ref,json=policyEvaluationsRef,proto3" json:"policy_evaluations_ref,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *AttestationItem) Reset() { @@ -1350,6 +1407,87 @@ func (x *AttestationItem) GetPolicyEvaluationStatus() *AttestationItem_PolicyEva return nil } +func (x *AttestationItem) GetPolicyEvaluationsRef() *PolicyEvaluationsRef { + if x != nil { + return x.PolicyEvaluationsRef + } + return nil +} + +// Pointer to a policy-evaluation bundle held in a CAS backend, returned in +// place of the evaluations themselves. +type PolicyEvaluationsRef struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Digest of the bundle as stored in CAS, in "sha256:" form + Digest string `protobuf:"bytes,1,opt,name=digest,proto3" json:"digest,omitempty"` + // Size of the bundle in bytes. Zero when the size could not be determined. + SizeBytes int64 `protobuf:"varint,2,opt,name=size_bytes,json=sizeBytes,proto3" json:"size_bytes,omitempty"` + // Media type of the bundle, i.e application/vnd.chainloop.policy-evaluations.v1+json + MediaType string `protobuf:"bytes,3,opt,name=media_type,json=mediaType,proto3" json:"media_type,omitempty"` + // Why the evaluations were not inlined + Reason PolicyEvaluationsRef_Reason `protobuf:"varint,4,opt,name=reason,proto3,enum=controlplane.v1.PolicyEvaluationsRef_Reason" json:"reason,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PolicyEvaluationsRef) Reset() { + *x = PolicyEvaluationsRef{} + mi := &file_controlplane_v1_response_messages_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PolicyEvaluationsRef) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PolicyEvaluationsRef) ProtoMessage() {} + +func (x *PolicyEvaluationsRef) ProtoReflect() protoreflect.Message { + mi := &file_controlplane_v1_response_messages_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PolicyEvaluationsRef.ProtoReflect.Descriptor instead. +func (*PolicyEvaluationsRef) Descriptor() ([]byte, []int) { + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{5} +} + +func (x *PolicyEvaluationsRef) GetDigest() string { + if x != nil { + return x.Digest + } + return "" +} + +func (x *PolicyEvaluationsRef) GetSizeBytes() int64 { + if x != nil { + return x.SizeBytes + } + return 0 +} + +func (x *PolicyEvaluationsRef) GetMediaType() string { + if x != nil { + return x.MediaType + } + return "" +} + +func (x *PolicyEvaluationsRef) GetReason() PolicyEvaluationsRef_Reason { + if x != nil { + return x.Reason + } + return PolicyEvaluationsRef_REASON_UNSPECIFIED +} + type PolicyEvaluations struct { state protoimpl.MessageState `protogen:"open.v1"` Evaluations []*PolicyEvaluation `protobuf:"bytes,1,rep,name=evaluations,proto3" json:"evaluations,omitempty"` @@ -1359,7 +1497,7 @@ type PolicyEvaluations struct { func (x *PolicyEvaluations) Reset() { *x = PolicyEvaluations{} - mi := &file_controlplane_v1_response_messages_proto_msgTypes[5] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1371,7 +1509,7 @@ func (x *PolicyEvaluations) String() string { func (*PolicyEvaluations) ProtoMessage() {} func (x *PolicyEvaluations) ProtoReflect() protoreflect.Message { - mi := &file_controlplane_v1_response_messages_proto_msgTypes[5] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1384,7 +1522,7 @@ func (x *PolicyEvaluations) ProtoReflect() protoreflect.Message { // Deprecated: Use PolicyEvaluations.ProtoReflect.Descriptor instead. func (*PolicyEvaluations) Descriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{5} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{6} } func (x *PolicyEvaluations) GetEvaluations() []*PolicyEvaluation { @@ -1418,7 +1556,7 @@ type PolicyEvaluation struct { func (x *PolicyEvaluation) Reset() { *x = PolicyEvaluation{} - mi := &file_controlplane_v1_response_messages_proto_msgTypes[6] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1430,7 +1568,7 @@ func (x *PolicyEvaluation) String() string { func (*PolicyEvaluation) ProtoMessage() {} func (x *PolicyEvaluation) ProtoReflect() protoreflect.Message { - mi := &file_controlplane_v1_response_messages_proto_msgTypes[6] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1443,7 +1581,7 @@ func (x *PolicyEvaluation) ProtoReflect() protoreflect.Message { // Deprecated: Use PolicyEvaluation.ProtoReflect.Descriptor instead. func (*PolicyEvaluation) Descriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{6} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{7} } func (x *PolicyEvaluation) GetName() string { @@ -1574,7 +1712,7 @@ type PolicyViolation struct { func (x *PolicyViolation) Reset() { *x = PolicyViolation{} - mi := &file_controlplane_v1_response_messages_proto_msgTypes[7] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1586,7 +1724,7 @@ func (x *PolicyViolation) String() string { func (*PolicyViolation) ProtoMessage() {} func (x *PolicyViolation) ProtoReflect() protoreflect.Message { - mi := &file_controlplane_v1_response_messages_proto_msgTypes[7] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1599,7 +1737,7 @@ func (x *PolicyViolation) ProtoReflect() protoreflect.Message { // Deprecated: Use PolicyViolation.ProtoReflect.Descriptor instead. func (*PolicyViolation) Descriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{7} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{8} } func (x *PolicyViolation) GetSubject() string { @@ -1691,7 +1829,7 @@ type PolicyReference struct { func (x *PolicyReference) Reset() { *x = PolicyReference{} - mi := &file_controlplane_v1_response_messages_proto_msgTypes[8] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1703,7 +1841,7 @@ func (x *PolicyReference) String() string { func (*PolicyReference) ProtoMessage() {} func (x *PolicyReference) ProtoReflect() protoreflect.Message { - mi := &file_controlplane_v1_response_messages_proto_msgTypes[8] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1716,7 +1854,7 @@ func (x *PolicyReference) ProtoReflect() protoreflect.Message { // Deprecated: Use PolicyReference.ProtoReflect.Descriptor instead. func (*PolicyReference) Descriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{8} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{9} } func (x *PolicyReference) GetName() string { @@ -1772,7 +1910,7 @@ type WorkflowContractItem struct { func (x *WorkflowContractItem) Reset() { *x = WorkflowContractItem{} - mi := &file_controlplane_v1_response_messages_proto_msgTypes[9] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1784,7 +1922,7 @@ func (x *WorkflowContractItem) String() string { func (*WorkflowContractItem) ProtoMessage() {} func (x *WorkflowContractItem) ProtoReflect() protoreflect.Message { - mi := &file_controlplane_v1_response_messages_proto_msgTypes[9] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1797,7 +1935,7 @@ func (x *WorkflowContractItem) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkflowContractItem.ProtoReflect.Descriptor instead. func (*WorkflowContractItem) Descriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{9} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{10} } func (x *WorkflowContractItem) GetId() string { @@ -1886,7 +2024,7 @@ type ScopedEntity struct { func (x *ScopedEntity) Reset() { *x = ScopedEntity{} - mi := &file_controlplane_v1_response_messages_proto_msgTypes[10] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1898,7 +2036,7 @@ func (x *ScopedEntity) String() string { func (*ScopedEntity) ProtoMessage() {} func (x *ScopedEntity) ProtoReflect() protoreflect.Message { - mi := &file_controlplane_v1_response_messages_proto_msgTypes[10] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1911,7 +2049,7 @@ func (x *ScopedEntity) ProtoReflect() protoreflect.Message { // Deprecated: Use ScopedEntity.ProtoReflect.Descriptor instead. func (*ScopedEntity) Descriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{10} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{11} } func (x *ScopedEntity) GetType() string { @@ -1946,7 +2084,7 @@ type WorkflowRef struct { func (x *WorkflowRef) Reset() { *x = WorkflowRef{} - mi := &file_controlplane_v1_response_messages_proto_msgTypes[11] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1958,7 +2096,7 @@ func (x *WorkflowRef) String() string { func (*WorkflowRef) ProtoMessage() {} func (x *WorkflowRef) ProtoReflect() protoreflect.Message { - mi := &file_controlplane_v1_response_messages_proto_msgTypes[11] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1971,7 +2109,7 @@ func (x *WorkflowRef) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkflowRef.ProtoReflect.Descriptor instead. func (*WorkflowRef) Descriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{11} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{12} } func (x *WorkflowRef) GetId() string { @@ -2015,7 +2153,7 @@ type WorkflowContractVersionItem struct { func (x *WorkflowContractVersionItem) Reset() { *x = WorkflowContractVersionItem{} - mi := &file_controlplane_v1_response_messages_proto_msgTypes[12] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2027,7 +2165,7 @@ func (x *WorkflowContractVersionItem) String() string { func (*WorkflowContractVersionItem) ProtoMessage() {} func (x *WorkflowContractVersionItem) ProtoReflect() protoreflect.Message { - mi := &file_controlplane_v1_response_messages_proto_msgTypes[12] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2040,7 +2178,7 @@ func (x *WorkflowContractVersionItem) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkflowContractVersionItem.ProtoReflect.Descriptor instead. func (*WorkflowContractVersionItem) Descriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{12} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{13} } func (x *WorkflowContractVersionItem) GetId() string { @@ -2130,7 +2268,7 @@ type User struct { func (x *User) Reset() { *x = User{} - mi := &file_controlplane_v1_response_messages_proto_msgTypes[13] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2142,7 +2280,7 @@ func (x *User) String() string { func (*User) ProtoMessage() {} func (x *User) ProtoReflect() protoreflect.Message { - mi := &file_controlplane_v1_response_messages_proto_msgTypes[13] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2155,7 +2293,7 @@ func (x *User) ProtoReflect() protoreflect.Message { // Deprecated: Use User.ProtoReflect.Descriptor instead. func (*User) Descriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{13} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{14} } func (x *User) GetId() string { @@ -2222,7 +2360,7 @@ type OrgMembershipItem struct { func (x *OrgMembershipItem) Reset() { *x = OrgMembershipItem{} - mi := &file_controlplane_v1_response_messages_proto_msgTypes[14] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2234,7 +2372,7 @@ func (x *OrgMembershipItem) String() string { func (*OrgMembershipItem) ProtoMessage() {} func (x *OrgMembershipItem) ProtoReflect() protoreflect.Message { - mi := &file_controlplane_v1_response_messages_proto_msgTypes[14] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2247,7 +2385,7 @@ func (x *OrgMembershipItem) ProtoReflect() protoreflect.Message { // Deprecated: Use OrgMembershipItem.ProtoReflect.Descriptor instead. func (*OrgMembershipItem) Descriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{14} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{15} } func (x *OrgMembershipItem) GetId() string { @@ -2325,7 +2463,7 @@ type OrgItem struct { func (x *OrgItem) Reset() { *x = OrgItem{} - mi := &file_controlplane_v1_response_messages_proto_msgTypes[15] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2337,7 +2475,7 @@ func (x *OrgItem) String() string { func (*OrgItem) ProtoMessage() {} func (x *OrgItem) ProtoReflect() protoreflect.Message { - mi := &file_controlplane_v1_response_messages_proto_msgTypes[15] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2350,7 +2488,7 @@ func (x *OrgItem) ProtoReflect() protoreflect.Message { // Deprecated: Use OrgItem.ProtoReflect.Descriptor instead. func (*OrgItem) Descriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{15} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{16} } func (x *OrgItem) GetId() string { @@ -2469,7 +2607,7 @@ type CASBackendItem struct { func (x *CASBackendItem) Reset() { *x = CASBackendItem{} - mi := &file_controlplane_v1_response_messages_proto_msgTypes[16] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2481,7 +2619,7 @@ func (x *CASBackendItem) String() string { func (*CASBackendItem) ProtoMessage() {} func (x *CASBackendItem) ProtoReflect() protoreflect.Message { - mi := &file_controlplane_v1_response_messages_proto_msgTypes[16] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2494,7 +2632,7 @@ func (x *CASBackendItem) ProtoReflect() protoreflect.Message { // Deprecated: Use CASBackendItem.ProtoReflect.Descriptor instead. func (*CASBackendItem) Descriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{16} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{17} } func (x *CASBackendItem) GetId() string { @@ -2621,7 +2759,7 @@ type APITokenItem struct { func (x *APITokenItem) Reset() { *x = APITokenItem{} - mi := &file_controlplane_v1_response_messages_proto_msgTypes[17] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2633,7 +2771,7 @@ func (x *APITokenItem) String() string { func (*APITokenItem) ProtoMessage() {} func (x *APITokenItem) ProtoReflect() protoreflect.Message { - mi := &file_controlplane_v1_response_messages_proto_msgTypes[17] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2646,7 +2784,7 @@ func (x *APITokenItem) ProtoReflect() protoreflect.Message { // Deprecated: Use APITokenItem.ProtoReflect.Descriptor instead. func (*APITokenItem) Descriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{17} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{18} } func (x *APITokenItem) GetId() string { @@ -2744,7 +2882,7 @@ type AttestationItem_PolicyEvaluationStatus struct { func (x *AttestationItem_PolicyEvaluationStatus) Reset() { *x = AttestationItem_PolicyEvaluationStatus{} - mi := &file_controlplane_v1_response_messages_proto_msgTypes[20] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2756,7 +2894,7 @@ func (x *AttestationItem_PolicyEvaluationStatus) String() string { func (*AttestationItem_PolicyEvaluationStatus) ProtoMessage() {} func (x *AttestationItem_PolicyEvaluationStatus) ProtoReflect() protoreflect.Message { - mi := &file_controlplane_v1_response_messages_proto_msgTypes[20] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2840,7 +2978,7 @@ type AttestationItem_EnvVariable struct { func (x *AttestationItem_EnvVariable) Reset() { *x = AttestationItem_EnvVariable{} - mi := &file_controlplane_v1_response_messages_proto_msgTypes[21] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2852,7 +2990,7 @@ func (x *AttestationItem_EnvVariable) String() string { func (*AttestationItem_EnvVariable) ProtoMessage() {} func (x *AttestationItem_EnvVariable) ProtoReflect() protoreflect.Message { - mi := &file_controlplane_v1_response_messages_proto_msgTypes[21] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2913,7 +3051,7 @@ type AttestationItem_Material struct { func (x *AttestationItem_Material) Reset() { *x = AttestationItem_Material{} - mi := &file_controlplane_v1_response_messages_proto_msgTypes[22] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2925,7 +3063,7 @@ func (x *AttestationItem_Material) String() string { func (*AttestationItem_Material) ProtoMessage() {} func (x *AttestationItem_Material) ProtoReflect() protoreflect.Message { - mi := &file_controlplane_v1_response_messages_proto_msgTypes[22] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3022,7 +3160,7 @@ type WorkflowContractVersionItem_RawBody struct { func (x *WorkflowContractVersionItem_RawBody) Reset() { *x = WorkflowContractVersionItem_RawBody{} - mi := &file_controlplane_v1_response_messages_proto_msgTypes[27] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3034,7 +3172,7 @@ func (x *WorkflowContractVersionItem_RawBody) String() string { func (*WorkflowContractVersionItem_RawBody) ProtoMessage() {} func (x *WorkflowContractVersionItem_RawBody) ProtoReflect() protoreflect.Message { - mi := &file_controlplane_v1_response_messages_proto_msgTypes[27] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3047,7 +3185,7 @@ func (x *WorkflowContractVersionItem_RawBody) ProtoReflect() protoreflect.Messag // Deprecated: Use WorkflowContractVersionItem_RawBody.ProtoReflect.Descriptor instead. func (*WorkflowContractVersionItem_RawBody) Descriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{12, 0} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{13, 0} } func (x *WorkflowContractVersionItem_RawBody) GetBody() []byte { @@ -3074,7 +3212,7 @@ type CASBackendItem_Limits struct { func (x *CASBackendItem_Limits) Reset() { *x = CASBackendItem_Limits{} - mi := &file_controlplane_v1_response_messages_proto_msgTypes[28] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3086,7 +3224,7 @@ func (x *CASBackendItem_Limits) String() string { func (*CASBackendItem_Limits) ProtoMessage() {} func (x *CASBackendItem_Limits) ProtoReflect() protoreflect.Message { - mi := &file_controlplane_v1_response_messages_proto_msgTypes[28] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3099,7 +3237,7 @@ func (x *CASBackendItem_Limits) ProtoReflect() protoreflect.Message { // Deprecated: Use CASBackendItem_Limits.ProtoReflect.Descriptor instead. func (*CASBackendItem_Limits) Descriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{16, 0} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{17, 0} } func (x *CASBackendItem_Limits) GetMaxBytes() int64 { @@ -3173,7 +3311,7 @@ const file_controlplane_v1_response_messages_proto_rawDesc = "" + "\thas_gates\x18\x06 \x01(\bR\bhasGates\x12\x1e\n" + "\n" + "suppressed\x18\a \x01(\x05R\n" + - "suppressed\"\xa4\f\n" + + "suppressed\"\x81\r\n" + "\x0fAttestationItem\x12\x1e\n" + "\benvelope\x18\x03 \x01(\fB\x02\x18\x01R\benvelope\x12\x16\n" + "\x06bundle\x18\n" + @@ -3183,7 +3321,8 @@ const file_controlplane_v1_response_messages_proto_rawDesc = "" + "\tmaterials\x18\x05 \x03(\v2).controlplane.v1.AttestationItem.MaterialR\tmaterials\x12S\n" + "\vannotations\x18\x06 \x03(\v21.controlplane.v1.AttestationItem.AnnotationsEntryR\vannotations\x12f\n" + "\x12policy_evaluations\x18\b \x03(\v27.controlplane.v1.AttestationItem.PolicyEvaluationsEntryR\x11policyEvaluations\x12q\n" + - "\x18policy_evaluation_status\x18\t \x01(\v27.controlplane.v1.AttestationItem.PolicyEvaluationStatusR\x16policyEvaluationStatus\x1a>\n" + + "\x18policy_evaluation_status\x18\t \x01(\v27.controlplane.v1.AttestationItem.PolicyEvaluationStatusR\x16policyEvaluationStatus\x12[\n" + + "\x16policy_evaluations_ref\x18\v \x01(\v2%.controlplane.v1.PolicyEvaluationsRefR\x14policyEvaluationsRef\x1a>\n" + "\x10AnnotationsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1ah\n" + @@ -3216,7 +3355,18 @@ const file_controlplane_v1_response_messages_proto_rawDesc = "" + " \x01(\fR\brawValue\x1a>\n" + "\x10AnnotationsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"X\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x82\x02\n" + + "\x14PolicyEvaluationsRef\x12\x16\n" + + "\x06digest\x18\x01 \x01(\tR\x06digest\x12\x1d\n" + + "\n" + + "size_bytes\x18\x02 \x01(\x03R\tsizeBytes\x12\x1d\n" + + "\n" + + "media_type\x18\x03 \x01(\tR\tmediaType\x12D\n" + + "\x06reason\x18\x04 \x01(\x0e2,.controlplane.v1.PolicyEvaluationsRef.ReasonR\x06reason\"N\n" + + "\x06Reason\x12\x16\n" + + "\x12REASON_UNSPECIFIED\x10\x00\x12\x14\n" + + "\x10REASON_TOO_LARGE\x10\x01\x12\x16\n" + + "\x12REASON_UNAVAILABLE\x10\x02\"X\n" + "\x11PolicyEvaluations\x12C\n" + "\vevaluations\x18\x01 \x03(\v2!.controlplane.v1.PolicyEvaluationR\vevaluations\"\x92\x06\n" + "\x10PolicyEvaluation\x12\x12\n" + @@ -3451,8 +3601,8 @@ func file_controlplane_v1_response_messages_proto_rawDescGZIP() []byte { return file_controlplane_v1_response_messages_proto_rawDescData } -var file_controlplane_v1_response_messages_proto_enumTypes = make([]protoimpl.EnumInfo, 13) -var file_controlplane_v1_response_messages_proto_msgTypes = make([]protoimpl.MessageInfo, 29) +var file_controlplane_v1_response_messages_proto_enumTypes = make([]protoimpl.EnumInfo, 14) +var file_controlplane_v1_response_messages_proto_msgTypes = make([]protoimpl.MessageInfo, 30) var file_controlplane_v1_response_messages_proto_goTypes = []any{ (RunStatus)(0), // 0: controlplane.v1.RunStatus (PolicyViolationsFilter)(0), // 1: controlplane.v1.PolicyViolationsFilter @@ -3464,111 +3614,115 @@ var file_controlplane_v1_response_messages_proto_goTypes = []any{ (FederatedAuthError)(0), // 7: controlplane.v1.FederatedAuthError (UserWithNoMembershipError)(0), // 8: controlplane.v1.UserWithNoMembershipError (UserNotMemberOfOrgError)(0), // 9: controlplane.v1.UserNotMemberOfOrgError - (WorkflowContractVersionItem_RawBody_Format)(0), // 10: controlplane.v1.WorkflowContractVersionItem.RawBody.Format - (OrgItem_PolicyViolationBlockingStrategy)(0), // 11: controlplane.v1.OrgItem.PolicyViolationBlockingStrategy - (CASBackendItem_ValidationStatus)(0), // 12: controlplane.v1.CASBackendItem.ValidationStatus - (*WorkflowItem)(nil), // 13: controlplane.v1.WorkflowItem - (*WorkflowRunItem)(nil), // 14: controlplane.v1.WorkflowRunItem - (*ProjectVersion)(nil), // 15: controlplane.v1.ProjectVersion - (*PolicyStatusSummary)(nil), // 16: controlplane.v1.PolicyStatusSummary - (*AttestationItem)(nil), // 17: controlplane.v1.AttestationItem - (*PolicyEvaluations)(nil), // 18: controlplane.v1.PolicyEvaluations - (*PolicyEvaluation)(nil), // 19: controlplane.v1.PolicyEvaluation - (*PolicyViolation)(nil), // 20: controlplane.v1.PolicyViolation - (*PolicyReference)(nil), // 21: controlplane.v1.PolicyReference - (*WorkflowContractItem)(nil), // 22: controlplane.v1.WorkflowContractItem - (*ScopedEntity)(nil), // 23: controlplane.v1.ScopedEntity - (*WorkflowRef)(nil), // 24: controlplane.v1.WorkflowRef - (*WorkflowContractVersionItem)(nil), // 25: controlplane.v1.WorkflowContractVersionItem - (*User)(nil), // 26: controlplane.v1.User - (*OrgMembershipItem)(nil), // 27: controlplane.v1.OrgMembershipItem - (*OrgItem)(nil), // 28: controlplane.v1.OrgItem - (*CASBackendItem)(nil), // 29: controlplane.v1.CASBackendItem - (*APITokenItem)(nil), // 30: controlplane.v1.APITokenItem - nil, // 31: controlplane.v1.AttestationItem.AnnotationsEntry - nil, // 32: controlplane.v1.AttestationItem.PolicyEvaluationsEntry - (*AttestationItem_PolicyEvaluationStatus)(nil), // 33: controlplane.v1.AttestationItem.PolicyEvaluationStatus - (*AttestationItem_EnvVariable)(nil), // 34: controlplane.v1.AttestationItem.EnvVariable - (*AttestationItem_Material)(nil), // 35: controlplane.v1.AttestationItem.Material - nil, // 36: controlplane.v1.AttestationItem.Material.AnnotationsEntry - nil, // 37: controlplane.v1.PolicyEvaluation.AnnotationsEntry - nil, // 38: controlplane.v1.PolicyEvaluation.WithEntry - nil, // 39: controlplane.v1.PolicyReference.DigestEntry - (*WorkflowContractVersionItem_RawBody)(nil), // 40: controlplane.v1.WorkflowContractVersionItem.RawBody - (*CASBackendItem_Limits)(nil), // 41: controlplane.v1.CASBackendItem.Limits - (*timestamppb.Timestamp)(nil), // 42: google.protobuf.Timestamp - (v1.CraftingSchema_Runner_RunnerType)(0), // 43: workflowcontract.v1.CraftingSchema.Runner.RunnerType - (*v11.PolicyVulnerabilityFinding)(nil), // 44: attestation.v1.PolicyVulnerabilityFinding - (*v11.PolicySASTFinding)(nil), // 45: attestation.v1.PolicySASTFinding - (*v11.PolicyLicenseViolationFinding)(nil), // 46: attestation.v1.PolicyLicenseViolationFinding - (*v1.CraftingSchema)(nil), // 47: workflowcontract.v1.CraftingSchema + (PolicyEvaluationsRef_Reason)(0), // 10: controlplane.v1.PolicyEvaluationsRef.Reason + (WorkflowContractVersionItem_RawBody_Format)(0), // 11: controlplane.v1.WorkflowContractVersionItem.RawBody.Format + (OrgItem_PolicyViolationBlockingStrategy)(0), // 12: controlplane.v1.OrgItem.PolicyViolationBlockingStrategy + (CASBackendItem_ValidationStatus)(0), // 13: controlplane.v1.CASBackendItem.ValidationStatus + (*WorkflowItem)(nil), // 14: controlplane.v1.WorkflowItem + (*WorkflowRunItem)(nil), // 15: controlplane.v1.WorkflowRunItem + (*ProjectVersion)(nil), // 16: controlplane.v1.ProjectVersion + (*PolicyStatusSummary)(nil), // 17: controlplane.v1.PolicyStatusSummary + (*AttestationItem)(nil), // 18: controlplane.v1.AttestationItem + (*PolicyEvaluationsRef)(nil), // 19: controlplane.v1.PolicyEvaluationsRef + (*PolicyEvaluations)(nil), // 20: controlplane.v1.PolicyEvaluations + (*PolicyEvaluation)(nil), // 21: controlplane.v1.PolicyEvaluation + (*PolicyViolation)(nil), // 22: controlplane.v1.PolicyViolation + (*PolicyReference)(nil), // 23: controlplane.v1.PolicyReference + (*WorkflowContractItem)(nil), // 24: controlplane.v1.WorkflowContractItem + (*ScopedEntity)(nil), // 25: controlplane.v1.ScopedEntity + (*WorkflowRef)(nil), // 26: controlplane.v1.WorkflowRef + (*WorkflowContractVersionItem)(nil), // 27: controlplane.v1.WorkflowContractVersionItem + (*User)(nil), // 28: controlplane.v1.User + (*OrgMembershipItem)(nil), // 29: controlplane.v1.OrgMembershipItem + (*OrgItem)(nil), // 30: controlplane.v1.OrgItem + (*CASBackendItem)(nil), // 31: controlplane.v1.CASBackendItem + (*APITokenItem)(nil), // 32: controlplane.v1.APITokenItem + nil, // 33: controlplane.v1.AttestationItem.AnnotationsEntry + nil, // 34: controlplane.v1.AttestationItem.PolicyEvaluationsEntry + (*AttestationItem_PolicyEvaluationStatus)(nil), // 35: controlplane.v1.AttestationItem.PolicyEvaluationStatus + (*AttestationItem_EnvVariable)(nil), // 36: controlplane.v1.AttestationItem.EnvVariable + (*AttestationItem_Material)(nil), // 37: controlplane.v1.AttestationItem.Material + nil, // 38: controlplane.v1.AttestationItem.Material.AnnotationsEntry + nil, // 39: controlplane.v1.PolicyEvaluation.AnnotationsEntry + nil, // 40: controlplane.v1.PolicyEvaluation.WithEntry + nil, // 41: controlplane.v1.PolicyReference.DigestEntry + (*WorkflowContractVersionItem_RawBody)(nil), // 42: controlplane.v1.WorkflowContractVersionItem.RawBody + (*CASBackendItem_Limits)(nil), // 43: controlplane.v1.CASBackendItem.Limits + (*timestamppb.Timestamp)(nil), // 44: google.protobuf.Timestamp + (v1.CraftingSchema_Runner_RunnerType)(0), // 45: workflowcontract.v1.CraftingSchema.Runner.RunnerType + (*v11.PolicyVulnerabilityFinding)(nil), // 46: attestation.v1.PolicyVulnerabilityFinding + (*v11.PolicySASTFinding)(nil), // 47: attestation.v1.PolicySASTFinding + (*v11.PolicyLicenseViolationFinding)(nil), // 48: attestation.v1.PolicyLicenseViolationFinding + (*v1.CraftingSchema)(nil), // 49: workflowcontract.v1.CraftingSchema } var file_controlplane_v1_response_messages_proto_depIdxs = []int32{ - 42, // 0: controlplane.v1.WorkflowItem.created_at:type_name -> google.protobuf.Timestamp - 14, // 1: controlplane.v1.WorkflowItem.last_run:type_name -> controlplane.v1.WorkflowRunItem - 42, // 2: controlplane.v1.WorkflowRunItem.created_at:type_name -> google.protobuf.Timestamp - 42, // 3: controlplane.v1.WorkflowRunItem.finished_at:type_name -> google.protobuf.Timestamp + 44, // 0: controlplane.v1.WorkflowItem.created_at:type_name -> google.protobuf.Timestamp + 15, // 1: controlplane.v1.WorkflowItem.last_run:type_name -> controlplane.v1.WorkflowRunItem + 44, // 2: controlplane.v1.WorkflowRunItem.created_at:type_name -> google.protobuf.Timestamp + 44, // 3: controlplane.v1.WorkflowRunItem.finished_at:type_name -> google.protobuf.Timestamp 0, // 4: controlplane.v1.WorkflowRunItem.status:type_name -> controlplane.v1.RunStatus - 13, // 5: controlplane.v1.WorkflowRunItem.workflow:type_name -> controlplane.v1.WorkflowItem - 43, // 6: controlplane.v1.WorkflowRunItem.runner_type:type_name -> workflowcontract.v1.CraftingSchema.Runner.RunnerType - 25, // 7: controlplane.v1.WorkflowRunItem.contract_version:type_name -> controlplane.v1.WorkflowContractVersionItem - 15, // 8: controlplane.v1.WorkflowRunItem.version:type_name -> controlplane.v1.ProjectVersion - 16, // 9: controlplane.v1.WorkflowRunItem.policy_summary:type_name -> controlplane.v1.PolicyStatusSummary - 42, // 10: controlplane.v1.ProjectVersion.created_at:type_name -> google.protobuf.Timestamp - 42, // 11: controlplane.v1.ProjectVersion.released_at:type_name -> google.protobuf.Timestamp + 14, // 5: controlplane.v1.WorkflowRunItem.workflow:type_name -> controlplane.v1.WorkflowItem + 45, // 6: controlplane.v1.WorkflowRunItem.runner_type:type_name -> workflowcontract.v1.CraftingSchema.Runner.RunnerType + 27, // 7: controlplane.v1.WorkflowRunItem.contract_version:type_name -> controlplane.v1.WorkflowContractVersionItem + 16, // 8: controlplane.v1.WorkflowRunItem.version:type_name -> controlplane.v1.ProjectVersion + 17, // 9: controlplane.v1.WorkflowRunItem.policy_summary:type_name -> controlplane.v1.PolicyStatusSummary + 44, // 10: controlplane.v1.ProjectVersion.created_at:type_name -> google.protobuf.Timestamp + 44, // 11: controlplane.v1.ProjectVersion.released_at:type_name -> google.protobuf.Timestamp 2, // 12: controlplane.v1.PolicyStatusSummary.status:type_name -> controlplane.v1.PolicyStatus - 34, // 13: controlplane.v1.AttestationItem.env_vars:type_name -> controlplane.v1.AttestationItem.EnvVariable - 35, // 14: controlplane.v1.AttestationItem.materials:type_name -> controlplane.v1.AttestationItem.Material - 31, // 15: controlplane.v1.AttestationItem.annotations:type_name -> controlplane.v1.AttestationItem.AnnotationsEntry - 32, // 16: controlplane.v1.AttestationItem.policy_evaluations:type_name -> controlplane.v1.AttestationItem.PolicyEvaluationsEntry - 33, // 17: controlplane.v1.AttestationItem.policy_evaluation_status:type_name -> controlplane.v1.AttestationItem.PolicyEvaluationStatus - 19, // 18: controlplane.v1.PolicyEvaluations.evaluations:type_name -> controlplane.v1.PolicyEvaluation - 37, // 19: controlplane.v1.PolicyEvaluation.annotations:type_name -> controlplane.v1.PolicyEvaluation.AnnotationsEntry - 38, // 20: controlplane.v1.PolicyEvaluation.with:type_name -> controlplane.v1.PolicyEvaluation.WithEntry - 20, // 21: controlplane.v1.PolicyEvaluation.violations:type_name -> controlplane.v1.PolicyViolation - 21, // 22: controlplane.v1.PolicyEvaluation.policy_reference:type_name -> controlplane.v1.PolicyReference - 21, // 23: controlplane.v1.PolicyEvaluation.group_reference:type_name -> controlplane.v1.PolicyReference - 44, // 24: controlplane.v1.PolicyViolation.vulnerability:type_name -> attestation.v1.PolicyVulnerabilityFinding - 45, // 25: controlplane.v1.PolicyViolation.sast:type_name -> attestation.v1.PolicySASTFinding - 46, // 26: controlplane.v1.PolicyViolation.license_violation:type_name -> attestation.v1.PolicyLicenseViolationFinding - 39, // 27: controlplane.v1.PolicyReference.digest:type_name -> controlplane.v1.PolicyReference.DigestEntry - 42, // 28: controlplane.v1.WorkflowContractItem.created_at:type_name -> google.protobuf.Timestamp - 42, // 29: controlplane.v1.WorkflowContractItem.updated_at:type_name -> google.protobuf.Timestamp - 42, // 30: controlplane.v1.WorkflowContractItem.latest_revision_created_at:type_name -> google.protobuf.Timestamp - 24, // 31: controlplane.v1.WorkflowContractItem.workflow_refs:type_name -> controlplane.v1.WorkflowRef - 23, // 32: controlplane.v1.WorkflowContractItem.scoped_entity:type_name -> controlplane.v1.ScopedEntity - 42, // 33: controlplane.v1.WorkflowContractVersionItem.created_at:type_name -> google.protobuf.Timestamp - 47, // 34: controlplane.v1.WorkflowContractVersionItem.v1:type_name -> workflowcontract.v1.CraftingSchema - 40, // 35: controlplane.v1.WorkflowContractVersionItem.raw_contract:type_name -> controlplane.v1.WorkflowContractVersionItem.RawBody - 42, // 36: controlplane.v1.User.created_at:type_name -> google.protobuf.Timestamp - 42, // 37: controlplane.v1.User.updated_at:type_name -> google.protobuf.Timestamp - 28, // 38: controlplane.v1.OrgMembershipItem.org:type_name -> controlplane.v1.OrgItem - 26, // 39: controlplane.v1.OrgMembershipItem.user:type_name -> controlplane.v1.User - 42, // 40: controlplane.v1.OrgMembershipItem.created_at:type_name -> google.protobuf.Timestamp - 42, // 41: controlplane.v1.OrgMembershipItem.updated_at:type_name -> google.protobuf.Timestamp - 5, // 42: controlplane.v1.OrgMembershipItem.role:type_name -> controlplane.v1.MembershipRole - 42, // 43: controlplane.v1.OrgItem.created_at:type_name -> google.protobuf.Timestamp - 42, // 44: controlplane.v1.OrgItem.updated_at:type_name -> google.protobuf.Timestamp - 11, // 45: controlplane.v1.OrgItem.default_policy_violation_strategy:type_name -> controlplane.v1.OrgItem.PolicyViolationBlockingStrategy - 42, // 46: controlplane.v1.CASBackendItem.created_at:type_name -> google.protobuf.Timestamp - 42, // 47: controlplane.v1.CASBackendItem.validated_at:type_name -> google.protobuf.Timestamp - 12, // 48: controlplane.v1.CASBackendItem.validation_status:type_name -> controlplane.v1.CASBackendItem.ValidationStatus - 41, // 49: controlplane.v1.CASBackendItem.limits:type_name -> controlplane.v1.CASBackendItem.Limits - 42, // 50: controlplane.v1.CASBackendItem.updated_at:type_name -> google.protobuf.Timestamp - 23, // 51: controlplane.v1.APITokenItem.scoped_entity:type_name -> controlplane.v1.ScopedEntity - 42, // 52: controlplane.v1.APITokenItem.created_at:type_name -> google.protobuf.Timestamp - 42, // 53: controlplane.v1.APITokenItem.revoked_at:type_name -> google.protobuf.Timestamp - 42, // 54: controlplane.v1.APITokenItem.expires_at:type_name -> google.protobuf.Timestamp - 42, // 55: controlplane.v1.APITokenItem.last_used_at:type_name -> google.protobuf.Timestamp - 18, // 56: controlplane.v1.AttestationItem.PolicyEvaluationsEntry.value:type_name -> controlplane.v1.PolicyEvaluations - 16, // 57: controlplane.v1.AttestationItem.PolicyEvaluationStatus.summary:type_name -> controlplane.v1.PolicyStatusSummary - 36, // 58: controlplane.v1.AttestationItem.Material.annotations:type_name -> controlplane.v1.AttestationItem.Material.AnnotationsEntry - 10, // 59: controlplane.v1.WorkflowContractVersionItem.RawBody.format:type_name -> controlplane.v1.WorkflowContractVersionItem.RawBody.Format - 60, // [60:60] is the sub-list for method output_type - 60, // [60:60] is the sub-list for method input_type - 60, // [60:60] is the sub-list for extension type_name - 60, // [60:60] is the sub-list for extension extendee - 0, // [0:60] is the sub-list for field type_name + 36, // 13: controlplane.v1.AttestationItem.env_vars:type_name -> controlplane.v1.AttestationItem.EnvVariable + 37, // 14: controlplane.v1.AttestationItem.materials:type_name -> controlplane.v1.AttestationItem.Material + 33, // 15: controlplane.v1.AttestationItem.annotations:type_name -> controlplane.v1.AttestationItem.AnnotationsEntry + 34, // 16: controlplane.v1.AttestationItem.policy_evaluations:type_name -> controlplane.v1.AttestationItem.PolicyEvaluationsEntry + 35, // 17: controlplane.v1.AttestationItem.policy_evaluation_status:type_name -> controlplane.v1.AttestationItem.PolicyEvaluationStatus + 19, // 18: controlplane.v1.AttestationItem.policy_evaluations_ref:type_name -> controlplane.v1.PolicyEvaluationsRef + 10, // 19: controlplane.v1.PolicyEvaluationsRef.reason:type_name -> controlplane.v1.PolicyEvaluationsRef.Reason + 21, // 20: controlplane.v1.PolicyEvaluations.evaluations:type_name -> controlplane.v1.PolicyEvaluation + 39, // 21: controlplane.v1.PolicyEvaluation.annotations:type_name -> controlplane.v1.PolicyEvaluation.AnnotationsEntry + 40, // 22: controlplane.v1.PolicyEvaluation.with:type_name -> controlplane.v1.PolicyEvaluation.WithEntry + 22, // 23: controlplane.v1.PolicyEvaluation.violations:type_name -> controlplane.v1.PolicyViolation + 23, // 24: controlplane.v1.PolicyEvaluation.policy_reference:type_name -> controlplane.v1.PolicyReference + 23, // 25: controlplane.v1.PolicyEvaluation.group_reference:type_name -> controlplane.v1.PolicyReference + 46, // 26: controlplane.v1.PolicyViolation.vulnerability:type_name -> attestation.v1.PolicyVulnerabilityFinding + 47, // 27: controlplane.v1.PolicyViolation.sast:type_name -> attestation.v1.PolicySASTFinding + 48, // 28: controlplane.v1.PolicyViolation.license_violation:type_name -> attestation.v1.PolicyLicenseViolationFinding + 41, // 29: controlplane.v1.PolicyReference.digest:type_name -> controlplane.v1.PolicyReference.DigestEntry + 44, // 30: controlplane.v1.WorkflowContractItem.created_at:type_name -> google.protobuf.Timestamp + 44, // 31: controlplane.v1.WorkflowContractItem.updated_at:type_name -> google.protobuf.Timestamp + 44, // 32: controlplane.v1.WorkflowContractItem.latest_revision_created_at:type_name -> google.protobuf.Timestamp + 26, // 33: controlplane.v1.WorkflowContractItem.workflow_refs:type_name -> controlplane.v1.WorkflowRef + 25, // 34: controlplane.v1.WorkflowContractItem.scoped_entity:type_name -> controlplane.v1.ScopedEntity + 44, // 35: controlplane.v1.WorkflowContractVersionItem.created_at:type_name -> google.protobuf.Timestamp + 49, // 36: controlplane.v1.WorkflowContractVersionItem.v1:type_name -> workflowcontract.v1.CraftingSchema + 42, // 37: controlplane.v1.WorkflowContractVersionItem.raw_contract:type_name -> controlplane.v1.WorkflowContractVersionItem.RawBody + 44, // 38: controlplane.v1.User.created_at:type_name -> google.protobuf.Timestamp + 44, // 39: controlplane.v1.User.updated_at:type_name -> google.protobuf.Timestamp + 30, // 40: controlplane.v1.OrgMembershipItem.org:type_name -> controlplane.v1.OrgItem + 28, // 41: controlplane.v1.OrgMembershipItem.user:type_name -> controlplane.v1.User + 44, // 42: controlplane.v1.OrgMembershipItem.created_at:type_name -> google.protobuf.Timestamp + 44, // 43: controlplane.v1.OrgMembershipItem.updated_at:type_name -> google.protobuf.Timestamp + 5, // 44: controlplane.v1.OrgMembershipItem.role:type_name -> controlplane.v1.MembershipRole + 44, // 45: controlplane.v1.OrgItem.created_at:type_name -> google.protobuf.Timestamp + 44, // 46: controlplane.v1.OrgItem.updated_at:type_name -> google.protobuf.Timestamp + 12, // 47: controlplane.v1.OrgItem.default_policy_violation_strategy:type_name -> controlplane.v1.OrgItem.PolicyViolationBlockingStrategy + 44, // 48: controlplane.v1.CASBackendItem.created_at:type_name -> google.protobuf.Timestamp + 44, // 49: controlplane.v1.CASBackendItem.validated_at:type_name -> google.protobuf.Timestamp + 13, // 50: controlplane.v1.CASBackendItem.validation_status:type_name -> controlplane.v1.CASBackendItem.ValidationStatus + 43, // 51: controlplane.v1.CASBackendItem.limits:type_name -> controlplane.v1.CASBackendItem.Limits + 44, // 52: controlplane.v1.CASBackendItem.updated_at:type_name -> google.protobuf.Timestamp + 25, // 53: controlplane.v1.APITokenItem.scoped_entity:type_name -> controlplane.v1.ScopedEntity + 44, // 54: controlplane.v1.APITokenItem.created_at:type_name -> google.protobuf.Timestamp + 44, // 55: controlplane.v1.APITokenItem.revoked_at:type_name -> google.protobuf.Timestamp + 44, // 56: controlplane.v1.APITokenItem.expires_at:type_name -> google.protobuf.Timestamp + 44, // 57: controlplane.v1.APITokenItem.last_used_at:type_name -> google.protobuf.Timestamp + 20, // 58: controlplane.v1.AttestationItem.PolicyEvaluationsEntry.value:type_name -> controlplane.v1.PolicyEvaluations + 17, // 59: controlplane.v1.AttestationItem.PolicyEvaluationStatus.summary:type_name -> controlplane.v1.PolicyStatusSummary + 38, // 60: controlplane.v1.AttestationItem.Material.annotations:type_name -> controlplane.v1.AttestationItem.Material.AnnotationsEntry + 11, // 61: controlplane.v1.WorkflowContractVersionItem.RawBody.format:type_name -> controlplane.v1.WorkflowContractVersionItem.RawBody.Format + 62, // [62:62] is the sub-list for method output_type + 62, // [62:62] is the sub-list for method input_type + 62, // [62:62] is the sub-list for extension type_name + 62, // [62:62] is the sub-list for extension extendee + 0, // [0:62] is the sub-list for field type_name } func init() { file_controlplane_v1_response_messages_proto_init() } @@ -3577,23 +3731,23 @@ func file_controlplane_v1_response_messages_proto_init() { return } file_controlplane_v1_response_messages_proto_msgTypes[1].OneofWrappers = []any{} - file_controlplane_v1_response_messages_proto_msgTypes[7].OneofWrappers = []any{ + file_controlplane_v1_response_messages_proto_msgTypes[8].OneofWrappers = []any{ (*PolicyViolation_Vulnerability)(nil), (*PolicyViolation_Sast)(nil), (*PolicyViolation_LicenseViolation)(nil), } - file_controlplane_v1_response_messages_proto_msgTypes[12].OneofWrappers = []any{ + file_controlplane_v1_response_messages_proto_msgTypes[13].OneofWrappers = []any{ (*WorkflowContractVersionItem_V1)(nil), } - file_controlplane_v1_response_messages_proto_msgTypes[15].OneofWrappers = []any{} file_controlplane_v1_response_messages_proto_msgTypes[16].OneofWrappers = []any{} + file_controlplane_v1_response_messages_proto_msgTypes[17].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_controlplane_v1_response_messages_proto_rawDesc), len(file_controlplane_v1_response_messages_proto_rawDesc)), - NumEnums: 13, - NumMessages: 29, + NumEnums: 14, + NumMessages: 30, NumExtensions: 0, NumServices: 0, }, diff --git a/app/controlplane/api/controlplane/v1/response_messages.proto b/app/controlplane/api/controlplane/v1/response_messages.proto index 990390745..68b64c789 100644 --- a/app/controlplane/api/controlplane/v1/response_messages.proto +++ b/app/controlplane/api/controlplane/v1/response_messages.proto @@ -189,6 +189,12 @@ message AttestationItem { map annotations = 6; map policy_evaluations = 8; PolicyEvaluationStatus policy_evaluation_status = 9; + // Reference to the policy-evaluation bundle in the CAS backend. Populated + // only when the evaluations are NOT inlined in policy_evaluations, either + // because the bundle is larger than the server inlines or because it could + // not be resolved. Fetch the bundle by digest to inspect the violations. + // Counters and status in policy_evaluation_status remain complete either way. + PolicyEvaluationsRef policy_evaluations_ref = 11; message PolicyEvaluationStatus { string strategy = 1; @@ -236,6 +242,27 @@ message AttestationItem { } } +// Pointer to a policy-evaluation bundle held in a CAS backend, returned in +// place of the evaluations themselves. +message PolicyEvaluationsRef { + // Digest of the bundle as stored in CAS, in "sha256:" form + string digest = 1; + // Size of the bundle in bytes. Zero when the size could not be determined. + int64 size_bytes = 2; + // Media type of the bundle, i.e application/vnd.chainloop.policy-evaluations.v1+json + string media_type = 3; + // Why the evaluations were not inlined + Reason reason = 4; + + enum Reason { + REASON_UNSPECIFIED = 0; + // The bundle exceeds the maximum size the server inlines in a response + REASON_TOO_LARGE = 1; + // The bundle could not be resolved from CAS + REASON_UNAVAILABLE = 2; + } +} + message PolicyEvaluations { repeated PolicyEvaluation evaluations = 1; } diff --git a/app/controlplane/api/gen/frontend/controlplane/v1/response_messages.ts b/app/controlplane/api/gen/frontend/controlplane/v1/response_messages.ts index 63f7d9084..03b9b80c3 100644 --- a/app/controlplane/api/gen/frontend/controlplane/v1/response_messages.ts +++ b/app/controlplane/api/gen/frontend/controlplane/v1/response_messages.ts @@ -617,6 +617,14 @@ export interface AttestationItem { annotations: { [key: string]: string }; policyEvaluations: { [key: string]: PolicyEvaluations }; policyEvaluationStatus?: AttestationItem_PolicyEvaluationStatus; + /** + * Reference to the policy-evaluation bundle in the CAS backend. Populated + * only when the evaluations are NOT inlined in policy_evaluations, either + * because the bundle is larger than the server inlines or because it could + * not be resolved. Fetch the bundle by digest to inspect the violations. + * Counters and status in policy_evaluation_status remain complete either way. + */ + policyEvaluationsRef?: PolicyEvaluationsRef; } export interface AttestationItem_AnnotationsEntry { @@ -695,6 +703,62 @@ export interface AttestationItem_Material_AnnotationsEntry { value: string; } +/** + * Pointer to a policy-evaluation bundle held in a CAS backend, returned in + * place of the evaluations themselves. + */ +export interface PolicyEvaluationsRef { + /** Digest of the bundle as stored in CAS, in "sha256:" form */ + digest: string; + /** Size of the bundle in bytes. Zero when the size could not be determined. */ + sizeBytes: number; + /** Media type of the bundle, i.e application/vnd.chainloop.policy-evaluations.v1+json */ + mediaType: string; + /** Why the evaluations were not inlined */ + reason: PolicyEvaluationsRef_Reason; +} + +export enum PolicyEvaluationsRef_Reason { + REASON_UNSPECIFIED = 0, + /** REASON_TOO_LARGE - The bundle exceeds the maximum size the server inlines in a response */ + REASON_TOO_LARGE = 1, + /** REASON_UNAVAILABLE - The bundle could not be resolved from CAS */ + REASON_UNAVAILABLE = 2, + UNRECOGNIZED = -1, +} + +export function policyEvaluationsRef_ReasonFromJSON(object: any): PolicyEvaluationsRef_Reason { + switch (object) { + case 0: + case "REASON_UNSPECIFIED": + return PolicyEvaluationsRef_Reason.REASON_UNSPECIFIED; + case 1: + case "REASON_TOO_LARGE": + return PolicyEvaluationsRef_Reason.REASON_TOO_LARGE; + case 2: + case "REASON_UNAVAILABLE": + return PolicyEvaluationsRef_Reason.REASON_UNAVAILABLE; + case -1: + case "UNRECOGNIZED": + default: + return PolicyEvaluationsRef_Reason.UNRECOGNIZED; + } +} + +export function policyEvaluationsRef_ReasonToJSON(object: PolicyEvaluationsRef_Reason): string { + switch (object) { + case PolicyEvaluationsRef_Reason.REASON_UNSPECIFIED: + return "REASON_UNSPECIFIED"; + case PolicyEvaluationsRef_Reason.REASON_TOO_LARGE: + return "REASON_TOO_LARGE"; + case PolicyEvaluationsRef_Reason.REASON_UNAVAILABLE: + return "REASON_UNAVAILABLE"; + case PolicyEvaluationsRef_Reason.UNRECOGNIZED: + default: + return "UNRECOGNIZED"; + } +} + export interface PolicyEvaluations { evaluations: PolicyEvaluation[]; } @@ -1805,6 +1869,7 @@ function createBaseAttestationItem(): AttestationItem { annotations: {}, policyEvaluations: {}, policyEvaluationStatus: undefined, + policyEvaluationsRef: undefined, }; } @@ -1834,6 +1899,9 @@ export const AttestationItem = { if (message.policyEvaluationStatus !== undefined) { AttestationItem_PolicyEvaluationStatus.encode(message.policyEvaluationStatus, writer.uint32(74).fork()).ldelim(); } + if (message.policyEvaluationsRef !== undefined) { + PolicyEvaluationsRef.encode(message.policyEvaluationsRef, writer.uint32(90).fork()).ldelim(); + } return writer; }, @@ -1906,6 +1974,13 @@ export const AttestationItem = { message.policyEvaluationStatus = AttestationItem_PolicyEvaluationStatus.decode(reader, reader.uint32()); continue; + case 11: + if (tag !== 90) { + break; + } + + message.policyEvaluationsRef = PolicyEvaluationsRef.decode(reader, reader.uint32()); + continue; } if ((tag & 7) === 4 || tag === 0) { break; @@ -1941,6 +2016,9 @@ export const AttestationItem = { policyEvaluationStatus: isSet(object.policyEvaluationStatus) ? AttestationItem_PolicyEvaluationStatus.fromJSON(object.policyEvaluationStatus) : undefined, + policyEvaluationsRef: isSet(object.policyEvaluationsRef) + ? PolicyEvaluationsRef.fromJSON(object.policyEvaluationsRef) + : undefined, }; }, @@ -1976,6 +2054,9 @@ export const AttestationItem = { message.policyEvaluationStatus !== undefined && (obj.policyEvaluationStatus = message.policyEvaluationStatus ? AttestationItem_PolicyEvaluationStatus.toJSON(message.policyEvaluationStatus) : undefined); + message.policyEvaluationsRef !== undefined && (obj.policyEvaluationsRef = message.policyEvaluationsRef + ? PolicyEvaluationsRef.toJSON(message.policyEvaluationsRef) + : undefined); return obj; }, @@ -2011,6 +2092,9 @@ export const AttestationItem = { (object.policyEvaluationStatus !== undefined && object.policyEvaluationStatus !== null) ? AttestationItem_PolicyEvaluationStatus.fromPartial(object.policyEvaluationStatus) : undefined; + message.policyEvaluationsRef = (object.policyEvaluationsRef !== undefined && object.policyEvaluationsRef !== null) + ? PolicyEvaluationsRef.fromPartial(object.policyEvaluationsRef) + : undefined; return message; }, }; @@ -2680,6 +2764,103 @@ export const AttestationItem_Material_AnnotationsEntry = { }, }; +function createBasePolicyEvaluationsRef(): PolicyEvaluationsRef { + return { digest: "", sizeBytes: 0, mediaType: "", reason: 0 }; +} + +export const PolicyEvaluationsRef = { + encode(message: PolicyEvaluationsRef, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.digest !== "") { + writer.uint32(10).string(message.digest); + } + if (message.sizeBytes !== 0) { + writer.uint32(16).int64(message.sizeBytes); + } + if (message.mediaType !== "") { + writer.uint32(26).string(message.mediaType); + } + if (message.reason !== 0) { + writer.uint32(32).int32(message.reason); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): PolicyEvaluationsRef { + const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBasePolicyEvaluationsRef(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 10) { + break; + } + + message.digest = reader.string(); + continue; + case 2: + if (tag !== 16) { + break; + } + + message.sizeBytes = longToNumber(reader.int64() as Long); + continue; + case 3: + if (tag !== 26) { + break; + } + + message.mediaType = reader.string(); + continue; + case 4: + if (tag !== 32) { + break; + } + + message.reason = reader.int32() as any; + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + fromJSON(object: any): PolicyEvaluationsRef { + return { + digest: isSet(object.digest) ? String(object.digest) : "", + sizeBytes: isSet(object.sizeBytes) ? Number(object.sizeBytes) : 0, + mediaType: isSet(object.mediaType) ? String(object.mediaType) : "", + reason: isSet(object.reason) ? policyEvaluationsRef_ReasonFromJSON(object.reason) : 0, + }; + }, + + toJSON(message: PolicyEvaluationsRef): unknown { + const obj: any = {}; + message.digest !== undefined && (obj.digest = message.digest); + message.sizeBytes !== undefined && (obj.sizeBytes = Math.round(message.sizeBytes)); + message.mediaType !== undefined && (obj.mediaType = message.mediaType); + message.reason !== undefined && (obj.reason = policyEvaluationsRef_ReasonToJSON(message.reason)); + return obj; + }, + + create, I>>(base?: I): PolicyEvaluationsRef { + return PolicyEvaluationsRef.fromPartial(base ?? {}); + }, + + fromPartial, I>>(object: I): PolicyEvaluationsRef { + const message = createBasePolicyEvaluationsRef(); + message.digest = object.digest ?? ""; + message.sizeBytes = object.sizeBytes ?? 0; + message.mediaType = object.mediaType ?? ""; + message.reason = object.reason ?? 0; + return message; + }, +}; + function createBasePolicyEvaluations(): PolicyEvaluations { return { evaluations: [] }; } diff --git a/app/controlplane/api/gen/jsonschema/controlplane.v1.AttestationItem.jsonschema.json b/app/controlplane/api/gen/jsonschema/controlplane.v1.AttestationItem.jsonschema.json index 1bda15d71..81ea09c69 100644 --- a/app/controlplane/api/gen/jsonschema/controlplane.v1.AttestationItem.jsonschema.json +++ b/app/controlplane/api/gen/jsonschema/controlplane.v1.AttestationItem.jsonschema.json @@ -25,6 +25,10 @@ "type": "string" }, "type": "object" + }, + "^(policy_evaluations_ref)$": { + "$ref": "controlplane.v1.PolicyEvaluationsRef.jsonschema.json", + "description": "Reference to the policy-evaluation bundle in the CAS backend. Populated\n only when the evaluations are NOT inlined in policy_evaluations, either\n because the bundle is larger than the server inlines or because it could\n not be resolved. Fetch the bundle by digest to inspect the violations.\n Counters and status in policy_evaluation_status remain complete either way." } }, "properties": { @@ -75,6 +79,10 @@ "type": "string" }, "type": "object" + }, + "policyEvaluationsRef": { + "$ref": "controlplane.v1.PolicyEvaluationsRef.jsonschema.json", + "description": "Reference to the policy-evaluation bundle in the CAS backend. Populated\n only when the evaluations are NOT inlined in policy_evaluations, either\n because the bundle is larger than the server inlines or because it could\n not be resolved. Fetch the bundle by digest to inspect the violations.\n Counters and status in policy_evaluation_status remain complete either way." } }, "title": "Attestation Item", diff --git a/app/controlplane/api/gen/jsonschema/controlplane.v1.AttestationItem.schema.json b/app/controlplane/api/gen/jsonschema/controlplane.v1.AttestationItem.schema.json index ec1f044f2..5e5da3a00 100644 --- a/app/controlplane/api/gen/jsonschema/controlplane.v1.AttestationItem.schema.json +++ b/app/controlplane/api/gen/jsonschema/controlplane.v1.AttestationItem.schema.json @@ -25,6 +25,10 @@ "type": "string" }, "type": "object" + }, + "^(policyEvaluationsRef)$": { + "$ref": "controlplane.v1.PolicyEvaluationsRef.schema.json", + "description": "Reference to the policy-evaluation bundle in the CAS backend. Populated\n only when the evaluations are NOT inlined in policy_evaluations, either\n because the bundle is larger than the server inlines or because it could\n not be resolved. Fetch the bundle by digest to inspect the violations.\n Counters and status in policy_evaluation_status remain complete either way." } }, "properties": { @@ -75,6 +79,10 @@ "type": "string" }, "type": "object" + }, + "policy_evaluations_ref": { + "$ref": "controlplane.v1.PolicyEvaluationsRef.schema.json", + "description": "Reference to the policy-evaluation bundle in the CAS backend. Populated\n only when the evaluations are NOT inlined in policy_evaluations, either\n because the bundle is larger than the server inlines or because it could\n not be resolved. Fetch the bundle by digest to inspect the violations.\n Counters and status in policy_evaluation_status remain complete either way." } }, "title": "Attestation Item", diff --git a/app/controlplane/api/gen/jsonschema/controlplane.v1.PolicyEvaluationsRef.jsonschema.json b/app/controlplane/api/gen/jsonschema/controlplane.v1.PolicyEvaluationsRef.jsonschema.json new file mode 100644 index 000000000..6206057e7 --- /dev/null +++ b/app/controlplane/api/gen/jsonschema/controlplane.v1.PolicyEvaluationsRef.jsonschema.json @@ -0,0 +1,71 @@ +{ + "$id": "controlplane.v1.PolicyEvaluationsRef.jsonschema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": false, + "description": "Pointer to a policy-evaluation bundle held in a CAS backend, returned in\n place of the evaluations themselves.", + "patternProperties": { + "^(media_type)$": { + "description": "Media type of the bundle, i.e application/vnd.chainloop.policy-evaluations.v1+json", + "type": "string" + }, + "^(size_bytes)$": { + "anyOf": [ + { + "exclusiveMaximum": 9223372036854776000, + "minimum": -9223372036854775808, + "type": "integer" + }, + { + "pattern": "^-?[0-9]+$", + "type": "string" + } + ], + "description": "Size of the bundle in bytes. Zero when the size could not be determined." + } + }, + "properties": { + "digest": { + "description": "Digest of the bundle as stored in CAS, in \"sha256:\u003chex\u003e\" form", + "type": "string" + }, + "mediaType": { + "description": "Media type of the bundle, i.e application/vnd.chainloop.policy-evaluations.v1+json", + "type": "string" + }, + "reason": { + "anyOf": [ + { + "enum": [ + "REASON_UNSPECIFIED", + "REASON_TOO_LARGE", + "REASON_UNAVAILABLE" + ], + "title": "Reason", + "type": "string" + }, + { + "maximum": 2147483647, + "minimum": -2147483648, + "type": "integer" + } + ], + "description": "Why the evaluations were not inlined" + }, + "sizeBytes": { + "anyOf": [ + { + "exclusiveMaximum": 9223372036854776000, + "minimum": -9223372036854775808, + "type": "integer" + }, + { + "pattern": "^-?[0-9]+$", + "type": "string" + } + ], + "description": "Size of the bundle in bytes. Zero when the size could not be determined." + } + }, + "title": "Policy Evaluations Ref", + "type": "object" +} diff --git a/app/controlplane/api/gen/jsonschema/controlplane.v1.PolicyEvaluationsRef.schema.json b/app/controlplane/api/gen/jsonschema/controlplane.v1.PolicyEvaluationsRef.schema.json new file mode 100644 index 000000000..6ab18bfbe --- /dev/null +++ b/app/controlplane/api/gen/jsonschema/controlplane.v1.PolicyEvaluationsRef.schema.json @@ -0,0 +1,71 @@ +{ + "$id": "controlplane.v1.PolicyEvaluationsRef.schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": false, + "description": "Pointer to a policy-evaluation bundle held in a CAS backend, returned in\n place of the evaluations themselves.", + "patternProperties": { + "^(mediaType)$": { + "description": "Media type of the bundle, i.e application/vnd.chainloop.policy-evaluations.v1+json", + "type": "string" + }, + "^(sizeBytes)$": { + "anyOf": [ + { + "exclusiveMaximum": 9223372036854776000, + "minimum": -9223372036854775808, + "type": "integer" + }, + { + "pattern": "^-?[0-9]+$", + "type": "string" + } + ], + "description": "Size of the bundle in bytes. Zero when the size could not be determined." + } + }, + "properties": { + "digest": { + "description": "Digest of the bundle as stored in CAS, in \"sha256:\u003chex\u003e\" form", + "type": "string" + }, + "media_type": { + "description": "Media type of the bundle, i.e application/vnd.chainloop.policy-evaluations.v1+json", + "type": "string" + }, + "reason": { + "anyOf": [ + { + "enum": [ + "REASON_UNSPECIFIED", + "REASON_TOO_LARGE", + "REASON_UNAVAILABLE" + ], + "title": "Reason", + "type": "string" + }, + { + "maximum": 2147483647, + "minimum": -2147483648, + "type": "integer" + } + ], + "description": "Why the evaluations were not inlined" + }, + "size_bytes": { + "anyOf": [ + { + "exclusiveMaximum": 9223372036854776000, + "minimum": -9223372036854775808, + "type": "integer" + }, + { + "pattern": "^-?[0-9]+$", + "type": "string" + } + ], + "description": "Size of the bundle in bytes. Zero when the size could not be determined." + } + }, + "title": "Policy Evaluations Ref", + "type": "object" +} diff --git a/app/controlplane/cmd/wire_gen.go b/app/controlplane/cmd/wire_gen.go index 7446ac5fc..a06ddc2e2 100644 --- a/app/controlplane/cmd/wire_gen.go +++ b/app/controlplane/cmd/wire_gen.go @@ -240,6 +240,7 @@ func wireApp(contextContext context.Context, bootstrap *conf.Bootstrap, readerWr CASClient: casClientUseCase, CASMappingUC: casMappingUseCase, PolicyEvalCache: policyevalbundleCache, + BootstrapConfig: bootstrap, Opts: v5, } workflowRunService := service.NewWorkflowRunService(newWorkflowRunServiceOpts) diff --git a/app/controlplane/internal/conf/controlplane/config/v1/conf.pb.go b/app/controlplane/internal/conf/controlplane/config/v1/conf.pb.go index aeccaa51e..e265b8016 100644 --- a/app/controlplane/internal/conf/controlplane/config/v1/conf.pb.go +++ b/app/controlplane/internal/conf/controlplane/config/v1/conf.pb.go @@ -266,8 +266,15 @@ type Attestations struct { // when the workflow run's CAS backend is inline, since inline backends // do not store attestation bundles externally. SkipDbStorage bool `protobuf:"varint,1,opt,name=skip_db_storage,json=skipDbStorage,proto3" json:"skip_db_storage,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Maximum size in bytes of a policy-evaluation bundle that the workflow-run + // View API downloads from CAS and inlines in its response. Bundles above + // this size are returned as a reference for the caller to fetch directly, + // which keeps a single attestation with a very large number of violations + // from exhausting the control plane's memory. Values <= 0 select the + // built-in default. + PolicyEvaluationsMaxInlineBytes int64 `protobuf:"varint,2,opt,name=policy_evaluations_max_inline_bytes,json=policyEvaluationsMaxInlineBytes,proto3" json:"policy_evaluations_max_inline_bytes,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *Attestations) Reset() { @@ -307,6 +314,13 @@ func (x *Attestations) GetSkipDbStorage() bool { return false } +func (x *Attestations) GetPolicyEvaluationsMaxInlineBytes() int64 { + if x != nil { + return x.PolicyEvaluationsMaxInlineBytes + } + return 0 +} + type OperationAuthorizationProvider struct { state protoimpl.MessageState `protogen:"open.v1"` // URL of the authorization endpoint @@ -1427,7 +1441,7 @@ type Data_Database struct { state protoimpl.MessageState `protogen:"open.v1"` Driver string `protobuf:"bytes,1,opt,name=driver,proto3" json:"driver,omitempty"` Source string `protobuf:"bytes,2,opt,name=source,proto3" json:"source,omitempty"` - // default 0 + // default 0 MinOpenConns int32 `protobuf:"varint,3,opt,name=min_open_conns,json=minOpenConns,proto3" json:"min_open_conns,omitempty"` // default max(4, runtime.NumCPU()) MaxOpenConns int32 `protobuf:"varint,4,opt,name=max_open_conns,json=maxOpenConns,proto3" json:"max_open_conns,omitempty"` @@ -1780,9 +1794,10 @@ const file_controlplane_config_v1_conf_proto_rawDesc = "" + "\x03uri\x18\x01 \x01(\tB\a\xbaH\x04r\x02\x10\x01R\x03uri\x12\x1f\n" + "\x05token\x18\x02 \x01(\tB\a\xbaH\x04r\x02\x10\x01H\x00R\x05token\x12\x1a\n" + "\breplicas\x18\x03 \x01(\x05R\breplicasB\x10\n" + - "\x0eauthenticationJ\x04\b\b\x10\tR\x15referrer_shared_index\"6\n" + + "\x0eauthenticationJ\x04\b\b\x10\tR\x15referrer_shared_index\"\x84\x01\n" + "\fAttestations\x12&\n" + - "\x0fskip_db_storage\x18\x01 \x01(\bR\rskipDbStorage\"v\n" + + "\x0fskip_db_storage\x18\x01 \x01(\bR\rskipDbStorage\x12L\n" + + "#policy_evaluations_max_inline_bytes\x18\x02 \x01(\x03R\x1fpolicyEvaluationsMaxInlineBytes\"v\n" + "\x1eOperationAuthorizationProvider\x12\x1a\n" + "\x03url\x18\x01 \x01(\tB\b\xbaH\x05r\x03\x88\x01\x01R\x03url\x12\x18\n" + "\aenabled\x18\x02 \x01(\bR\aenabled\x12\x1e\n" + diff --git a/app/controlplane/internal/conf/controlplane/config/v1/conf.proto b/app/controlplane/internal/conf/controlplane/config/v1/conf.proto index 1a04c8881..4665594f7 100644 --- a/app/controlplane/internal/conf/controlplane/config/v1/conf.proto +++ b/app/controlplane/internal/conf/controlplane/config/v1/conf.proto @@ -136,6 +136,14 @@ message Attestations { // when the workflow run's CAS backend is inline, since inline backends // do not store attestation bundles externally. bool skip_db_storage = 1; + + // Maximum size in bytes of a policy-evaluation bundle that the workflow-run + // View API downloads from CAS and inlines in its response. Bundles above + // this size are returned as a reference for the caller to fetch directly, + // which keeps a single attestation with a very large number of violations + // from exhausting the control plane's memory. Values <= 0 select the + // built-in default. + int64 policy_evaluations_max_inline_bytes = 2; } message OperationAuthorizationProvider { diff --git a/app/controlplane/internal/service/workflowrun.go b/app/controlplane/internal/service/workflowrun.go index 973722218..4e8cab18d 100644 --- a/app/controlplane/internal/service/workflowrun.go +++ b/app/controlplane/internal/service/workflowrun.go @@ -23,6 +23,7 @@ import ( pb "github.com/chainloop-dev/chainloop/app/controlplane/api/controlplane/v1" craftingpb "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1" + conf "github.com/chainloop-dev/chainloop/app/controlplane/internal/conf/controlplane/config/v1" "github.com/chainloop-dev/chainloop/app/controlplane/pkg/authz" "github.com/chainloop-dev/chainloop/app/controlplane/pkg/biz" "github.com/chainloop-dev/chainloop/app/controlplane/pkg/pagination" @@ -48,6 +49,7 @@ type WorkflowRunService struct { casClient biz.CASClient casMappingUC *biz.CASMappingUseCase policyEvalCache *policyevalbundle.Cache + bootstrapConfig *conf.Bootstrap } type NewWorkflowRunServiceOpts struct { @@ -60,6 +62,7 @@ type NewWorkflowRunServiceOpts struct { CASClient biz.CASClient CASMappingUC *biz.CASMappingUseCase PolicyEvalCache *policyevalbundle.Cache + BootstrapConfig *conf.Bootstrap Opts []NewOpt } @@ -75,6 +78,7 @@ func NewWorkflowRunService(opts *NewWorkflowRunServiceOpts) *WorkflowRunService casClient: opts.CASClient, casMappingUC: opts.CASMappingUC, policyEvalCache: opts.PolicyEvalCache, + bootstrapConfig: opts.BootstrapConfig, } } @@ -87,39 +91,174 @@ func (p *casResolvedPredicate) GetPolicyEvaluations() map[string][]*chainloop.Po return p.evals } +// defaultPolicyEvaluationsMaxInlineBytes bounds the policy-evaluation bundle +// the View API is willing to download and inline in a response. A single +// attestation can carry a six-figure number of violations, and inlining one +// holds the payload in memory several times over (download buffer, decoded +// bundle, regrouped evaluations, response protos), which is enough to exhaust +// the control plane. Bundles above the cap are returned as a reference so the +// caller can fetch them directly from the CAS. +const defaultPolicyEvaluationsMaxInlineBytes = 10 << 20 // 10MiB + +// resolvedPolicyEvaluations carries either the inlined evaluations or, when +// they were deliberately left out, the reference the caller needs to fetch +// them. Exactly one of the two fields is set. +type resolvedPolicyEvaluations struct { + evaluations map[string][]*chainloop.PolicyEvaluation + ref *pb.PolicyEvaluationsRef +} + +func (s *WorkflowRunService) policyEvaluationsMaxInlineBytes() int64 { + if configured := s.bootstrapConfig.GetAttestations().GetPolicyEvaluationsMaxInlineBytes(); configured > 0 { + return configured + } + + return defaultPolicyEvaluationsMaxInlineBytes +} + +// resolvePolicyEvaluations resolves the policy-evaluation bundle referenced by +// an attestation predicate. It returns nil when the predicate carries no +// reference, meaning the caller should keep whatever the predicate itself +// holds. +// +// Anything that prevents inlining the bundle with confidence -- an oversized +// bundle, an unknown size, an unreachable or undecodable object -- yields a +// reference rather than a download attempt. func (s *WorkflowRunService) resolvePolicyEvaluations( ctx context.Context, - ref *intoto.ResourceDescriptor, + descriptor *intoto.ResourceDescriptor, orgID uuid.UUID, -) (map[string][]*chainloop.PolicyEvaluation, error) { - if ref == nil { - return nil, nil +) *resolvedPolicyEvaluations { + if descriptor == nil { + return nil } - hexDigest, ok := ref.Digest["sha256"] + mediaType := descriptor.GetMediaType() + + hexDigest, ok := descriptor.GetDigest()["sha256"] if !ok { - return nil, fmt.Errorf("no sha256 digest in policy evaluations ref") + s.log.Warnw("msg", "policy evaluations reference has no sha256 digest") + return unavailablePolicyEvaluations("", 0, mediaType) } digest := fmt.Sprintf("sha256:%s", hexDigest) + maxInlineBytes := s.policyEvaluationsMaxInlineBytes() + + // A cached bundle costs no CAS round trip. Only under-cap bundles are + // cached below, so the size check here just keeps the cap honest for + // entries written before it existed. if cached, found, err := s.policyEvalCache.Get(ctx, digest); err == nil && found { - return chainloop.PolicyEvaluationsFromBundle(cached) + if int64(len(cached)) > maxInlineBytes { + return tooLargePolicyEvaluations(digest, int64(len(cached)), mediaType) + } + + return s.decodePolicyEvaluations(cached, digest, int64(len(cached)), mediaType) } mapping, err := s.casMappingUC.FindCASMappingForDownloadByOrg(ctx, digest, []uuid.UUID{orgID}, nil) if err != nil { - return nil, fmt.Errorf("finding CAS mapping: %w", err) + s.log.Warnw("msg", "finding CAS mapping for policy evaluations", "digest", digest, "err", err) + return unavailablePolicyEvaluations(digest, 0, mediaType) + } + + // Ask for the size before paying for the transfer. + info, err := s.casClient.Describe(ctx, string(mapping.CASBackend.Provider), mapping.CASBackend.SecretName, mapping.CASBackend.OrganizationID, digest) + if err != nil { + s.log.Warnw("msg", "describing policy evaluations bundle", "digest", digest, "err", err) + return unavailablePolicyEvaluations(digest, 0, mediaType) + } + + if info.Size > maxInlineBytes { + s.log.Infow("msg", "policy evaluations bundle too large to inline", "digest", digest, "size", info.Size, "max", maxInlineBytes) + return tooLargePolicyEvaluations(digest, info.Size, mediaType) } - var buf bytes.Buffer - if err := s.casClient.Download(ctx, string(mapping.CASBackend.Provider), mapping.CASBackend.SecretName, mapping.CASBackend.OrganizationID, &buf, digest); err != nil { - return nil, fmt.Errorf("downloading policy eval bundle: %w", err) + // A size of zero means the backend did not report one, not that the object + // is empty: some backends omit the content length and the proto getter then + // yields zero. Downloading on that basis would be downloading blind. + if info.Size <= 0 { + s.log.Warnw("msg", "policy evaluations bundle has no reported size", "digest", digest) + return unavailablePolicyEvaluations(digest, 0, mediaType) + } + + // The reported size is metadata, so bound the transfer itself as well. + // A backend that under-reports cannot then push us past the cap. + buf := &boundedBuffer{limit: maxInlineBytes} + err = s.casClient.Download(ctx, string(mapping.CASBackend.Provider), mapping.CASBackend.SecretName, mapping.CASBackend.OrganizationID, buf, digest) + + // Checked before the error because a writer refusing to grow surfaces as a + // download failure, and because the bound must hold even if an + // implementation swallows the write error. + if buf.exceeded { + s.log.Warnw("msg", "policy evaluations bundle exceeded the cap while downloading", "digest", digest, "reportedSize", info.Size, "max", maxInlineBytes) + // The reported size is known to be wrong, so no size is reported at all. + return tooLargePolicyEvaluations(digest, 0, mediaType) + } + + if err != nil { + s.log.Warnw("msg", "downloading policy evaluations bundle", "digest", digest, "err", err) + return unavailablePolicyEvaluations(digest, info.Size, mediaType) } data := buf.Bytes() _ = s.policyEvalCache.Set(ctx, digest, data) - return chainloop.PolicyEvaluationsFromBundle(data) + return s.decodePolicyEvaluations(data, digest, info.Size, mediaType) +} + +// boundedBuffer accumulates bytes in memory up to a limit and refuses the write +// that would exceed it, recording that it did. It exists so the policy +// evaluations cap is enforced against the bytes actually received rather than +// against the size the CAS backend claims. +type boundedBuffer struct { + buf bytes.Buffer + limit int64 + written int64 + exceeded bool +} + +func (b *boundedBuffer) Write(p []byte) (int, error) { + if b.written+int64(len(p)) > b.limit { + b.exceeded = true + return 0, fmt.Errorf("content exceeds the maximum of %d bytes", b.limit) + } + + n, err := b.buf.Write(p) + b.written += int64(n) + + return n, err +} + +func (b *boundedBuffer) Bytes() []byte { + return b.buf.Bytes() +} + +func (s *WorkflowRunService) decodePolicyEvaluations(data []byte, digest string, size int64, mediaType string) *resolvedPolicyEvaluations { + evaluations, err := chainloop.PolicyEvaluationsFromBundle(data) + if err != nil { + s.log.Warnw("msg", "decoding policy evaluations bundle", "digest", digest, "err", err) + return unavailablePolicyEvaluations(digest, size, mediaType) + } + + return &resolvedPolicyEvaluations{evaluations: evaluations} +} + +func tooLargePolicyEvaluations(digest string, size int64, mediaType string) *resolvedPolicyEvaluations { + return &resolvedPolicyEvaluations{ref: &pb.PolicyEvaluationsRef{ + Digest: digest, + SizeBytes: size, + MediaType: mediaType, + Reason: pb.PolicyEvaluationsRef_REASON_TOO_LARGE, + }} +} + +func unavailablePolicyEvaluations(digest string, size int64, mediaType string) *resolvedPolicyEvaluations { + return &resolvedPolicyEvaluations{ref: &pb.PolicyEvaluationsRef{ + Digest: digest, + SizeBytes: size, + MediaType: mediaType, + Reason: pb.PolicyEvaluationsRef_REASON_UNAVAILABLE, + }} } func (s *WorkflowRunService) List(ctx context.Context, req *pb.WorkflowRunServiceListRequest) (*pb.WorkflowRunServiceListResponse, error) { @@ -283,18 +422,20 @@ func (s *WorkflowRunService) View(ctx context.Context, req *pb.WorkflowRunServic } var predicate chainloop.NormalizablePredicate + var policyEvaluationsRef *pb.PolicyEvaluationsRef if run.Attestation != nil && run.Attestation.Envelope != nil { predicate, err = chainloop.ExtractPredicate(run.Attestation.Envelope) if err != nil { return nil, handleUseCaseErr(err, s.log) } - if ref := predicate.GetPolicyEvaluationsRef(); ref != nil { - resolved, resolveErr := s.resolvePolicyEvaluations(ctx, ref, run.Workflow.OrgID) - if resolveErr != nil { - s.log.Warnw("msg", "failed to resolve policy evaluations from CAS, using inline", "err", resolveErr) - } else if resolved != nil { - predicate = &casResolvedPredicate{NormalizablePredicate: predicate, evals: resolved} + if resolved := s.resolvePolicyEvaluations(ctx, predicate.GetPolicyEvaluationsRef(), run.Workflow.OrgID); resolved != nil { + // Either the evaluations are inlined, or the caller is handed the + // reference to fetch them from the CAS itself. + if resolved.ref != nil { + policyEvaluationsRef = resolved.ref + } else { + predicate = &casResolvedPredicate{NormalizablePredicate: predicate, evals: resolved.evaluations} } } } @@ -304,6 +445,10 @@ func (s *WorkflowRunService) View(ctx context.Context, req *pb.WorkflowRunServic return nil, handleUseCaseErr(err, s.log) } + if attestation != nil { + attestation.PolicyEvaluationsRef = policyEvaluationsRef + } + contractAndVersion, err := s.workflowContractUseCase.FindVersionByID(ctx, run.ContractVersionID.String()) if err != nil { return nil, handleUseCaseErr(err, s.log) diff --git a/app/controlplane/internal/service/workflowrun_test.go b/app/controlplane/internal/service/workflowrun_test.go new file mode 100644 index 000000000..ac2377b52 --- /dev/null +++ b/app/controlplane/internal/service/workflowrun_test.go @@ -0,0 +1,279 @@ +// +// Copyright 2026 The Chainloop Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package service + +import ( + "bytes" + "context" + "errors" + "io" + "testing" + + pb "github.com/chainloop-dev/chainloop/app/controlplane/api/controlplane/v1" + conf "github.com/chainloop-dev/chainloop/app/controlplane/internal/conf/controlplane/config/v1" + "github.com/chainloop-dev/chainloop/app/controlplane/pkg/biz" + bizMocks "github.com/chainloop-dev/chainloop/app/controlplane/pkg/biz/mocks" + attestationpb "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/api/attestation/v1" + "github.com/chainloop-dev/chainloop/pkg/attestation/renderer/chainloop" + "github.com/chainloop-dev/chainloop/pkg/cache/policyevalbundle" + "github.com/chainloop-dev/chainloop/pkg/casclient" + "github.com/google/uuid" + intoto "github.com/in-toto/attestation/go/v1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/encoding/protojson" +) + +const ( + sha256Alg = "sha256" + testBundleHexDigest = "cf4c9c8b7b1b4f4d0b4e3f4a5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d" + testBundleDigest = sha256Alg + ":" + testBundleHexDigest +) + +// policyEvaluationsBundle builds a valid protojson-encoded bundle carrying a +// single evaluation with one violation. +func policyEvaluationsBundle(t *testing.T) []byte { + t.Helper() + + bundle := &attestationpb.PolicyEvaluationBundle{ + Evaluations: []*attestationpb.PolicyEvaluation{ + { + Name: "strong-acl", + MaterialName: "registry-report", + Violations: []*attestationpb.PolicyEvaluation_Violation{ + {Subject: "HKLM\\Software", Message: "weak ACL"}, + }, + }, + }, + } + + data, err := protojson.Marshal(bundle) + require.NoError(t, err) + + return data +} + +func testResourceDescriptor() *intoto.ResourceDescriptor { + return &intoto.ResourceDescriptor{ + Name: "policy-evaluations", + Digest: map[string]string{sha256Alg: testBundleHexDigest}, + MediaType: chainloop.PolicyEvaluationsBundleMediaType, + } +} + +func TestResolvePolicyEvaluations(t *testing.T) { + orgID := uuid.New() + bundle := policyEvaluationsBundle(t) + + testCases := []struct { + name string + // descriptor defaults to a valid one when nil and useNilDescriptor is false + descriptor *intoto.ResourceDescriptor + useNilDescriptor bool + // maxInlineBytes 0 selects the built-in default + maxInlineBytes int64 + // seedCache pre-populates the bundle cache with these bytes + seedCache []byte + // mappingErr makes the CAS mapping lookup fail + mappingErr error + // describeSize is reported by the CAS when describeErr is nil + describeSize int64 + describeErr error + // downloadBody is what the CAS download writes out + downloadBody []byte + + wantNilResolution bool + wantEvaluations bool + wantRefReason pb.PolicyEvaluationsRef_Reason + wantRefSize int64 + wantDescribeCall bool + wantDownloadCall bool + }{ + { + name: "no descriptor resolves to nothing", + useNilDescriptor: true, + wantNilResolution: true, + }, + { + name: "bundle under the cap is inlined", + describeSize: int64(len(bundle)), + downloadBody: bundle, + wantEvaluations: true, + wantDescribeCall: true, + wantDownloadCall: true, + }, + { + name: "bundle over the cap is never downloaded", + maxInlineBytes: 16, + describeSize: 64 * 1024 * 1024, + wantRefReason: pb.PolicyEvaluationsRef_REASON_TOO_LARGE, + wantRefSize: 64 * 1024 * 1024, + wantDescribeCall: true, + }, + { + name: "size exactly at the cap is inlined", + maxInlineBytes: int64(len(bundle)), + describeSize: int64(len(bundle)), + downloadBody: bundle, + wantEvaluations: true, + wantDescribeCall: true, + wantDownloadCall: true, + }, + { + name: "unknown size is not downloaded", + describeErr: errors.New("cas unreachable"), + wantRefReason: pb.PolicyEvaluationsRef_REASON_UNAVAILABLE, + wantDescribeCall: true, + }, + { + name: "a size of zero is unknown, not empty, so it is not downloaded", + describeSize: 0, + wantRefReason: pb.PolicyEvaluationsRef_REASON_UNAVAILABLE, + wantDescribeCall: true, + }, + { + name: "a body larger than its reported size stops at the cap", + maxInlineBytes: 16, + describeSize: 8, + downloadBody: bytes.Repeat([]byte("x"), 64), + wantRefReason: pb.PolicyEvaluationsRef_REASON_TOO_LARGE, + wantRefSize: 0, + wantDescribeCall: true, + wantDownloadCall: true, + }, + { + name: "missing CAS mapping is not downloaded", + mappingErr: biz.NewErrNotFound("digest"), + wantRefReason: pb.PolicyEvaluationsRef_REASON_UNAVAILABLE, + }, + { + name: "cached bundle under the cap skips the CAS entirely", + seedCache: bundle, + wantEvaluations: true, + }, + { + name: "cached bundle over the cap is discarded", + seedCache: bundle, + maxInlineBytes: 4, + wantRefReason: pb.PolicyEvaluationsRef_REASON_TOO_LARGE, + wantRefSize: int64(len(bundle)), + }, + { + name: "undecodable bundle reports unavailable", + describeSize: 16, + downloadBody: []byte("this is not protojson"), + wantRefReason: pb.PolicyEvaluationsRef_REASON_UNAVAILABLE, + wantRefSize: 16, + wantDescribeCall: true, + wantDownloadCall: true, + }, + { + name: "descriptor without a sha256 digest reports unavailable", + descriptor: &intoto.ResourceDescriptor{ + Name: "policy-evaluations", + Digest: map[string]string{"sha512": "abc"}, + }, + wantRefReason: pb.PolicyEvaluationsRef_REASON_UNAVAILABLE, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + + casClient := bizMocks.NewCASClient(t) + if tc.wantDescribeCall { + casClient.On("Describe", mock.Anything, mock.Anything, mock.Anything, orgID, testBundleDigest). + Return(&casclient.ResourceInfo{Digest: testBundleDigest, Size: tc.describeSize}, tc.describeErr) + } + if tc.wantDownloadCall { + casClient.On("Download", mock.Anything, mock.Anything, mock.Anything, orgID, mock.Anything, testBundleDigest). + Run(func(args mock.Arguments) { + w, ok := args.Get(4).(io.Writer) + require.True(t, ok) + // The error is deliberately ignored: a bounded writer + // rejects a body past the cap and the production code, + // not the test, decides what that means. + _, _ = w.Write(tc.downloadBody) + }).Return(nil) + } + + mappingRepo := bizMocks.NewCASMappingRepo(t) + if !tc.useNilDescriptor && tc.descriptor == nil { + mapping := &biz.CASMapping{CASBackend: &biz.CASBackend{ + Provider: "OCI_REPOSITORY", + SecretName: "secret-name", + OrganizationID: orgID, + }} + if tc.mappingErr != nil { + mappingRepo.On("FindByDigestInOrgs", mock.Anything, testBundleDigest, mock.Anything, mock.Anything). + Return(nil, tc.mappingErr) + } else if tc.seedCache == nil { + mappingRepo.On("FindByDigestInOrgs", mock.Anything, testBundleDigest, mock.Anything, mock.Anything). + Return(mapping, nil) + } + } + + cache, err := policyevalbundle.New(ctx, nil, nil) + require.NoError(t, err) + if tc.seedCache != nil { + require.NoError(t, cache.Set(ctx, testBundleDigest, tc.seedCache)) + } + + svc := NewWorkflowRunService(&NewWorkflowRunServiceOpts{ + CASClient: casClient, + CASMappingUC: biz.NewCASMappingUseCase(mappingRepo, nil, nil), + PolicyEvalCache: cache, + BootstrapConfig: &conf.Bootstrap{ + Attestations: &conf.Attestations{ + PolicyEvaluationsMaxInlineBytes: tc.maxInlineBytes, + }, + }, + }) + + descriptor := tc.descriptor + if !tc.useNilDescriptor && descriptor == nil { + descriptor = testResourceDescriptor() + } + + got := svc.resolvePolicyEvaluations(ctx, descriptor, orgID) + + if tc.wantNilResolution { + assert.Nil(t, got) + return + } + + require.NotNil(t, got) + + if tc.wantEvaluations { + assert.Nil(t, got.ref, "evaluations were inlined so no ref is expected") + require.NotEmpty(t, got.evaluations) + assert.Len(t, got.evaluations["registry-report"], 1) + return + } + + assert.Empty(t, got.evaluations) + require.NotNil(t, got.ref) + assert.Equal(t, tc.wantRefReason, got.ref.GetReason()) + assert.Equal(t, tc.wantRefSize, got.ref.GetSizeBytes()) + + if !tc.wantDownloadCall { + casClient.AssertNotCalled(t, "Download", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything) + } + }) + } +} diff --git a/app/controlplane/pkg/biz/.mockery.yml b/app/controlplane/pkg/biz/.mockery.yml index 93dda23d7..b717cccb1 100644 --- a/app/controlplane/pkg/biz/.mockery.yml +++ b/app/controlplane/pkg/biz/.mockery.yml @@ -16,6 +16,7 @@ packages: interfaces: APITokenRepo: CASBackendRepo: + CASClient: CASMappingRepo: OrganizationRepo: WorkflowRunRepo: diff --git a/app/controlplane/pkg/biz/casclient.go b/app/controlplane/pkg/biz/casclient.go index f09a3f0f7..ba9800de3 100644 --- a/app/controlplane/pkg/biz/casclient.go +++ b/app/controlplane/pkg/biz/casclient.go @@ -51,6 +51,10 @@ type CASUploader interface { type CASDownloader interface { Download(ctx context.Context, backendType, secretID string, orgID uuid.UUID, w io.Writer, digest string) error + // Describe reports the metadata of a stored resource, including its size in + // bytes, without transferring its content. Callers use it to decide whether + // downloading is worthwhile before paying for the transfer. + Describe(ctx context.Context, backendType, secretID string, orgID uuid.UUID, digest string) (*casclient.ResourceInfo, error) } type CASClient interface { @@ -149,6 +153,25 @@ func (uc *CASClientUseCase) Download(ctx context.Context, backendType, secretID return nil } +func (uc *CASClientUseCase) Describe(ctx context.Context, backendType, secretID string, orgID uuid.UUID, digest string) (*casclient.ResourceInfo, error) { + ctx, span := otelx.Start(ctx, casClientTracer, "CASClientUseCase.Describe") + defer span.End() + + // SourceInternal flags this as the control plane's own traffic so the CAS doesn't emit audit events for it + client, closeFn, err := uc.casAPIClient(&CASCredsOpts{BackendType: backendType, SecretPath: secretID, Role: casJWT.Downloader, OrgID: orgID, SourceInternal: true}) + if err != nil { + return nil, fmt.Errorf("failed to create cas client: %w", err) + } + defer closeFn() + + info, err := client.Describe(ctx, digest) + if err != nil { + return nil, fmt.Errorf("failed to describe content: %w", err) + } + + return info, nil +} + // create a client with a temporary set of credentials for a specific operation func (uc *CASClientUseCase) casAPIClient(backendRef *CASCredsOpts) (casclient.DownloaderUploader, func(), error) { token, err := uc.credsProvider.GenerateTemporaryCredentials(backendRef) diff --git a/app/controlplane/pkg/biz/casclient_test.go b/app/controlplane/pkg/biz/casclient_test.go index 01b5c2655..867f915ab 100644 --- a/app/controlplane/pkg/biz/casclient_test.go +++ b/app/controlplane/pkg/biz/casclient_test.go @@ -1,5 +1,5 @@ // -// Copyright 2023-2025 The Chainloop Authors. +// Copyright 2023-2026 The Chainloop Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -17,14 +17,17 @@ package biz_test import ( "context" + "errors" "testing" conf "github.com/chainloop-dev/chainloop/app/controlplane/internal/conf/controlplane/config/v1" "github.com/chainloop-dev/chainloop/app/controlplane/pkg/biz" "github.com/chainloop-dev/chainloop/pkg/casclient" "github.com/chainloop-dev/chainloop/pkg/casclient/mocks" + "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" ) func TestIsReady(t *testing.T) { @@ -82,3 +85,71 @@ func TestIsReady(t *testing.T) { }) } } + +func TestDescribe(t *testing.T) { + const digest = "sha256:cf4c9c8b7b1b4f4d0b4e3f4a5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d" + + validConf := &conf.Bootstrap_CASServer{ + Grpc: &conf.Server_GRPC{Addr: "localhost:1111"}, + } + + testCases := []struct { + name string + orgID uuid.UUID + casInfo *casclient.ResourceInfo + casErr error + want *casclient.ResourceInfo + wantErr bool + wantCall bool + }{ + { + name: "returns the resource metadata reported by the CAS", + orgID: uuid.New(), + casInfo: &casclient.ResourceInfo{Digest: digest, Filename: "policy-evaluations.json", Size: 2048}, + want: &casclient.ResourceInfo{Digest: digest, Filename: "policy-evaluations.json", Size: 2048}, + wantCall: true, + }, + { + name: "propagates the CAS error", + orgID: uuid.New(), + casErr: errors.New("not found"), + wantErr: true, + wantCall: true, + }, + { + name: "fails without reaching the CAS when the org is missing", + orgID: uuid.Nil, + wantErr: true, + }, + } + + credsProvider, err := biz.NewCASCredentialsUseCase(&conf.Auth{ + CasRobotAccountPrivateKeyPath: "./testdata/test-key.ec.pem", + }) + require.NoError(t, err) + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + c := mocks.NewDownloaderUploader(t) + if tc.wantCall { + c.On("Describe", mock.Anything, digest).Return(tc.casInfo, tc.casErr) + } + + clientProvider := func(_ *conf.Bootstrap_CASServer, _ string) (casclient.DownloaderUploader, func(), error) { + return c, func() {}, nil + } + + uc := biz.NewCASClientUseCase(credsProvider, validConf, nil, biz.WithClientFactory(clientProvider)) + + got, err := uc.Describe(context.Background(), "OCI_REPOSITORY", "secret-name", tc.orgID, digest) + if tc.wantErr { + assert.Error(t, err) + assert.Nil(t, got) + return + } + + assert.NoError(t, err) + assert.Equal(t, tc.want, got) + }) + } +} diff --git a/app/controlplane/pkg/biz/mocks/CASClient.go b/app/controlplane/pkg/biz/mocks/CASClient.go index 66ebacc70..0d29cb5d2 100644 --- a/app/controlplane/pkg/biz/mocks/CASClient.go +++ b/app/controlplane/pkg/biz/mocks/CASClient.go @@ -1,66 +1,295 @@ -// Code generated by mockery v2.53.4. DO NOT EDIT. +// Code generated by mockery; DO NOT EDIT. +// github.com/vektra/mockery +// template: testify package mocks import ( - context "context" - io "io" + "context" + "io" + "github.com/chainloop-dev/chainloop/pkg/casclient" "github.com/google/uuid" mock "github.com/stretchr/testify/mock" ) +// NewCASClient creates a new instance of CASClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewCASClient(t interface { + mock.TestingT + Cleanup(func()) +}) *CASClient { + mock := &CASClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + // CASClient is an autogenerated mock type for the CASClient type type CASClient struct { mock.Mock } -// Download provides a mock function with given fields: ctx, backendType, secretID, orgID, w, digest -func (_m *CASClient) Download(ctx context.Context, backendType string, secretID string, orgID uuid.UUID, w io.Writer, digest string) error { - ret := _m.Called(ctx, backendType, secretID, orgID, w, digest) +type CASClient_Expecter struct { + mock *mock.Mock +} + +func (_m *CASClient) EXPECT() *CASClient_Expecter { + return &CASClient_Expecter{mock: &_m.Mock} +} + +// Describe provides a mock function for the type CASClient +func (_mock *CASClient) Describe(ctx context.Context, backendType string, secretID string, orgID uuid.UUID, digest string) (*casclient.ResourceInfo, error) { + ret := _mock.Called(ctx, backendType, secretID, orgID, digest) + + if len(ret) == 0 { + panic("no return value specified for Describe") + } + + var r0 *casclient.ResourceInfo + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, uuid.UUID, string) (*casclient.ResourceInfo, error)); ok { + return returnFunc(ctx, backendType, secretID, orgID, digest) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, uuid.UUID, string) *casclient.ResourceInfo); ok { + r0 = returnFunc(ctx, backendType, secretID, orgID, digest) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*casclient.ResourceInfo) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, string, string, uuid.UUID, string) error); ok { + r1 = returnFunc(ctx, backendType, secretID, orgID, digest) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// CASClient_Describe_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Describe' +type CASClient_Describe_Call struct { + *mock.Call +} + +// Describe is a helper method to define mock.On call +// - ctx context.Context +// - backendType string +// - secretID string +// - orgID uuid.UUID +// - digest string +func (_e *CASClient_Expecter) Describe(ctx any, backendType any, secretID any, orgID any, digest any) *CASClient_Describe_Call { + return &CASClient_Describe_Call{Call: _e.mock.On("Describe", ctx, backendType, secretID, orgID, digest)} +} + +func (_c *CASClient_Describe_Call) Run(run func(ctx context.Context, backendType string, secretID string, orgID uuid.UUID, digest string)) *CASClient_Describe_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + var arg3 uuid.UUID + if args[3] != nil { + arg3 = args[3].(uuid.UUID) + } + var arg4 string + if args[4] != nil { + arg4 = args[4].(string) + } + run( + arg0, + arg1, + arg2, + arg3, + arg4, + ) + }) + return _c +} + +func (_c *CASClient_Describe_Call) Return(resourceInfo *casclient.ResourceInfo, err error) *CASClient_Describe_Call { + _c.Call.Return(resourceInfo, err) + return _c +} + +func (_c *CASClient_Describe_Call) RunAndReturn(run func(ctx context.Context, backendType string, secretID string, orgID uuid.UUID, digest string) (*casclient.ResourceInfo, error)) *CASClient_Describe_Call { + _c.Call.Return(run) + return _c +} + +// Download provides a mock function for the type CASClient +func (_mock *CASClient) Download(ctx context.Context, backendType string, secretID string, orgID uuid.UUID, w io.Writer, digest string) error { + ret := _mock.Called(ctx, backendType, secretID, orgID, w, digest) if len(ret) == 0 { panic("no return value specified for Download") } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, string, uuid.UUID, io.Writer, string) error); ok { - r0 = rf(ctx, backendType, secretID, orgID, w, digest) + if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, uuid.UUID, io.Writer, string) error); ok { + r0 = returnFunc(ctx, backendType, secretID, orgID, w, digest) } else { r0 = ret.Error(0) } - return r0 } -// Upload provides a mock function with given fields: ctx, backendType, secretID, orgID, content, filename, digest -func (_m *CASClient) Upload(ctx context.Context, backendType string, secretID string, orgID uuid.UUID, content io.Reader, filename string, digest string) error { - ret := _m.Called(ctx, backendType, secretID, orgID, content, filename, digest) +// CASClient_Download_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Download' +type CASClient_Download_Call struct { + *mock.Call +} + +// Download is a helper method to define mock.On call +// - ctx context.Context +// - backendType string +// - secretID string +// - orgID uuid.UUID +// - w io.Writer +// - digest string +func (_e *CASClient_Expecter) Download(ctx any, backendType any, secretID any, orgID any, w any, digest any) *CASClient_Download_Call { + return &CASClient_Download_Call{Call: _e.mock.On("Download", ctx, backendType, secretID, orgID, w, digest)} +} + +func (_c *CASClient_Download_Call) Run(run func(ctx context.Context, backendType string, secretID string, orgID uuid.UUID, w io.Writer, digest string)) *CASClient_Download_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + var arg3 uuid.UUID + if args[3] != nil { + arg3 = args[3].(uuid.UUID) + } + var arg4 io.Writer + if args[4] != nil { + arg4 = args[4].(io.Writer) + } + var arg5 string + if args[5] != nil { + arg5 = args[5].(string) + } + run( + arg0, + arg1, + arg2, + arg3, + arg4, + arg5, + ) + }) + return _c +} + +func (_c *CASClient_Download_Call) Return(err error) *CASClient_Download_Call { + _c.Call.Return(err) + return _c +} + +func (_c *CASClient_Download_Call) RunAndReturn(run func(ctx context.Context, backendType string, secretID string, orgID uuid.UUID, w io.Writer, digest string) error) *CASClient_Download_Call { + _c.Call.Return(run) + return _c +} + +// Upload provides a mock function for the type CASClient +func (_mock *CASClient) Upload(ctx context.Context, backendType string, secretID string, orgID uuid.UUID, content io.Reader, filename string, digest string) error { + ret := _mock.Called(ctx, backendType, secretID, orgID, content, filename, digest) if len(ret) == 0 { panic("no return value specified for Upload") } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, string, uuid.UUID, io.Reader, string, string) error); ok { - r0 = rf(ctx, backendType, secretID, orgID, content, filename, digest) + if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, uuid.UUID, io.Reader, string, string) error); ok { + r0 = returnFunc(ctx, backendType, secretID, orgID, content, filename, digest) } else { r0 = ret.Error(0) } - return r0 } -// NewCASClient creates a new instance of CASClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewCASClient(t interface { - mock.TestingT - Cleanup(func()) -}) *CASClient { - mock := &CASClient{} - mock.Mock.Test(t) +// CASClient_Upload_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Upload' +type CASClient_Upload_Call struct { + *mock.Call +} - t.Cleanup(func() { mock.AssertExpectations(t) }) +// Upload is a helper method to define mock.On call +// - ctx context.Context +// - backendType string +// - secretID string +// - orgID uuid.UUID +// - content io.Reader +// - filename string +// - digest string +func (_e *CASClient_Expecter) Upload(ctx any, backendType any, secretID any, orgID any, content any, filename any, digest any) *CASClient_Upload_Call { + return &CASClient_Upload_Call{Call: _e.mock.On("Upload", ctx, backendType, secretID, orgID, content, filename, digest)} +} - return mock +func (_c *CASClient_Upload_Call) Run(run func(ctx context.Context, backendType string, secretID string, orgID uuid.UUID, content io.Reader, filename string, digest string)) *CASClient_Upload_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + var arg3 uuid.UUID + if args[3] != nil { + arg3 = args[3].(uuid.UUID) + } + var arg4 io.Reader + if args[4] != nil { + arg4 = args[4].(io.Reader) + } + var arg5 string + if args[5] != nil { + arg5 = args[5].(string) + } + var arg6 string + if args[6] != nil { + arg6 = args[6].(string) + } + run( + arg0, + arg1, + arg2, + arg3, + arg4, + arg5, + arg6, + ) + }) + return _c +} + +func (_c *CASClient_Upload_Call) Return(err error) *CASClient_Upload_Call { + _c.Call.Return(err) + return _c +} + +func (_c *CASClient_Upload_Call) RunAndReturn(run func(ctx context.Context, backendType string, secretID string, orgID uuid.UUID, content io.Reader, filename string, digest string) error) *CASClient_Upload_Call { + _c.Call.Return(run) + return _c } diff --git a/pkg/casclient/.mockery.yml b/pkg/casclient/.mockery.yml new file mode 100644 index 000000000..85bcd20ba --- /dev/null +++ b/pkg/casclient/.mockery.yml @@ -0,0 +1,18 @@ +all: false +formatter: goimports +include-auto-generated: false +log-level: info +recursive: false +require-template-schema-exists: true +template: testify +template-schema: "{{.Template}}.schema.json" +packages: + github.com/chainloop-dev/chainloop/pkg/casclient: + config: + dir: "{{.InterfaceDir}}/mocks" + filename: "{{.InterfaceName}}.go" + pkgname: mocks + structname: "{{.InterfaceName}}" + interfaces: + Downloader: + DownloaderUploader: diff --git a/pkg/casclient/casclient.go b/pkg/casclient/casclient.go index 0ae391760..a28708e2e 100644 --- a/pkg/casclient/casclient.go +++ b/pkg/casclient/casclient.go @@ -1,5 +1,5 @@ // -// Copyright 2023-2025 The Chainloop Authors. +// Copyright 2023-2026 The Chainloop Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -43,6 +43,9 @@ type Uploader interface { type Downloader interface { Download(ctx context.Context, w io.Writer, digest string) error + // Describe reports the metadata of a stored resource, including its size in + // bytes, without transferring its content + Describe(ctx context.Context, digest string) (*ResourceInfo, error) // Whether the CAS is ready to accept downloads IsReady(ctx context.Context) (bool, error) } diff --git a/pkg/casclient/mocks/Downloader.go b/pkg/casclient/mocks/Downloader.go index 04a1ff680..3b553589a 100644 --- a/pkg/casclient/mocks/Downloader.go +++ b/pkg/casclient/mocks/Downloader.go @@ -1,68 +1,231 @@ -// Code generated by mockery v2.20.0. DO NOT EDIT. +// Code generated by mockery; DO NOT EDIT. +// github.com/vektra/mockery +// template: testify package mocks import ( - context "context" - io "io" + "context" + "io" + "github.com/chainloop-dev/chainloop/pkg/casclient" mock "github.com/stretchr/testify/mock" ) +// NewDownloader creates a new instance of Downloader. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewDownloader(t interface { + mock.TestingT + Cleanup(func()) +}) *Downloader { + mock := &Downloader{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + // Downloader is an autogenerated mock type for the Downloader type type Downloader struct { mock.Mock } -// Download provides a mock function with given fields: ctx, w, digest -func (_m *Downloader) Download(ctx context.Context, w io.Writer, digest string) error { - ret := _m.Called(ctx, w, digest) +type Downloader_Expecter struct { + mock *mock.Mock +} + +func (_m *Downloader) EXPECT() *Downloader_Expecter { + return &Downloader_Expecter{mock: &_m.Mock} +} + +// Describe provides a mock function for the type Downloader +func (_mock *Downloader) Describe(ctx context.Context, digest string) (*casclient.ResourceInfo, error) { + ret := _mock.Called(ctx, digest) + + if len(ret) == 0 { + panic("no return value specified for Describe") + } + + var r0 *casclient.ResourceInfo + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, string) (*casclient.ResourceInfo, error)); ok { + return returnFunc(ctx, digest) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, string) *casclient.ResourceInfo); ok { + r0 = returnFunc(ctx, digest) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*casclient.ResourceInfo) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = returnFunc(ctx, digest) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// Downloader_Describe_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Describe' +type Downloader_Describe_Call struct { + *mock.Call +} + +// Describe is a helper method to define mock.On call +// - ctx context.Context +// - digest string +func (_e *Downloader_Expecter) Describe(ctx any, digest any) *Downloader_Describe_Call { + return &Downloader_Describe_Call{Call: _e.mock.On("Describe", ctx, digest)} +} + +func (_c *Downloader_Describe_Call) Run(run func(ctx context.Context, digest string)) *Downloader_Describe_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *Downloader_Describe_Call) Return(resourceInfo *casclient.ResourceInfo, err error) *Downloader_Describe_Call { + _c.Call.Return(resourceInfo, err) + return _c +} + +func (_c *Downloader_Describe_Call) RunAndReturn(run func(ctx context.Context, digest string) (*casclient.ResourceInfo, error)) *Downloader_Describe_Call { + _c.Call.Return(run) + return _c +} + +// Download provides a mock function for the type Downloader +func (_mock *Downloader) Download(ctx context.Context, w io.Writer, digest string) error { + ret := _mock.Called(ctx, w, digest) + + if len(ret) == 0 { + panic("no return value specified for Download") + } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, io.Writer, string) error); ok { - r0 = rf(ctx, w, digest) + if returnFunc, ok := ret.Get(0).(func(context.Context, io.Writer, string) error); ok { + r0 = returnFunc(ctx, w, digest) } else { r0 = ret.Error(0) } - return r0 } -// IsReady provides a mock function with given fields: ctx -func (_m *Downloader) IsReady(ctx context.Context) (bool, error) { - ret := _m.Called(ctx) +// Downloader_Download_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Download' +type Downloader_Download_Call struct { + *mock.Call +} + +// Download is a helper method to define mock.On call +// - ctx context.Context +// - w io.Writer +// - digest string +func (_e *Downloader_Expecter) Download(ctx any, w any, digest any) *Downloader_Download_Call { + return &Downloader_Download_Call{Call: _e.mock.On("Download", ctx, w, digest)} +} + +func (_c *Downloader_Download_Call) Run(run func(ctx context.Context, w io.Writer, digest string)) *Downloader_Download_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 io.Writer + if args[1] != nil { + arg1 = args[1].(io.Writer) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + run( + arg0, + arg1, + arg2, + ) + }) + return _c +} + +func (_c *Downloader_Download_Call) Return(err error) *Downloader_Download_Call { + _c.Call.Return(err) + return _c +} + +func (_c *Downloader_Download_Call) RunAndReturn(run func(ctx context.Context, w io.Writer, digest string) error) *Downloader_Download_Call { + _c.Call.Return(run) + return _c +} + +// IsReady provides a mock function for the type Downloader +func (_mock *Downloader) IsReady(ctx context.Context) (bool, error) { + ret := _mock.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for IsReady") + } var r0 bool var r1 error - if rf, ok := ret.Get(0).(func(context.Context) (bool, error)); ok { - return rf(ctx) + if returnFunc, ok := ret.Get(0).(func(context.Context) (bool, error)); ok { + return returnFunc(ctx) } - if rf, ok := ret.Get(0).(func(context.Context) bool); ok { - r0 = rf(ctx) + if returnFunc, ok := ret.Get(0).(func(context.Context) bool); ok { + r0 = returnFunc(ctx) } else { r0 = ret.Get(0).(bool) } - - if rf, ok := ret.Get(1).(func(context.Context) error); ok { - r1 = rf(ctx) + if returnFunc, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = returnFunc(ctx) } else { r1 = ret.Error(1) } - return r0, r1 } -type mockConstructorTestingTNewDownloader interface { - mock.TestingT - Cleanup(func()) +// Downloader_IsReady_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsReady' +type Downloader_IsReady_Call struct { + *mock.Call } -// NewDownloader creates a new instance of Downloader. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewDownloader(t mockConstructorTestingTNewDownloader) *Downloader { - mock := &Downloader{} - mock.Mock.Test(t) +// IsReady is a helper method to define mock.On call +// - ctx context.Context +func (_e *Downloader_Expecter) IsReady(ctx any) *Downloader_IsReady_Call { + return &Downloader_IsReady_Call{Call: _e.mock.On("IsReady", ctx)} +} - t.Cleanup(func() { mock.AssertExpectations(t) }) +func (_c *Downloader_IsReady_Call) Run(run func(ctx context.Context)) *Downloader_IsReady_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + run( + arg0, + ) + }) + return _c +} - return mock +func (_c *Downloader_IsReady_Call) Return(b bool, err error) *Downloader_IsReady_Call { + _c.Call.Return(b, err) + return _c +} + +func (_c *Downloader_IsReady_Call) RunAndReturn(run func(ctx context.Context) (bool, error)) *Downloader_IsReady_Call { + _c.Call.Return(run) + return _c } diff --git a/pkg/casclient/mocks/DownloaderUploader.go b/pkg/casclient/mocks/DownloaderUploader.go index 10237e195..ff112989f 100644 --- a/pkg/casclient/mocks/DownloaderUploader.go +++ b/pkg/casclient/mocks/DownloaderUploader.go @@ -1,123 +1,379 @@ -// Code generated by mockery v2.20.0. DO NOT EDIT. +// Code generated by mockery; DO NOT EDIT. +// github.com/vektra/mockery +// template: testify package mocks import ( - context "context" - - casclient "github.com/chainloop-dev/chainloop/pkg/casclient" - - io "io" + "context" + "io" + "github.com/chainloop-dev/chainloop/pkg/casclient" mock "github.com/stretchr/testify/mock" ) +// NewDownloaderUploader creates a new instance of DownloaderUploader. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewDownloaderUploader(t interface { + mock.TestingT + Cleanup(func()) +}) *DownloaderUploader { + mock := &DownloaderUploader{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + // DownloaderUploader is an autogenerated mock type for the DownloaderUploader type type DownloaderUploader struct { mock.Mock } -// Download provides a mock function with given fields: ctx, w, digest -func (_m *DownloaderUploader) Download(ctx context.Context, w io.Writer, digest string) error { - ret := _m.Called(ctx, w, digest) +type DownloaderUploader_Expecter struct { + mock *mock.Mock +} + +func (_m *DownloaderUploader) EXPECT() *DownloaderUploader_Expecter { + return &DownloaderUploader_Expecter{mock: &_m.Mock} +} + +// Describe provides a mock function for the type DownloaderUploader +func (_mock *DownloaderUploader) Describe(ctx context.Context, digest string) (*casclient.ResourceInfo, error) { + ret := _mock.Called(ctx, digest) + + if len(ret) == 0 { + panic("no return value specified for Describe") + } + + var r0 *casclient.ResourceInfo + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, string) (*casclient.ResourceInfo, error)); ok { + return returnFunc(ctx, digest) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, string) *casclient.ResourceInfo); ok { + r0 = returnFunc(ctx, digest) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*casclient.ResourceInfo) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = returnFunc(ctx, digest) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// DownloaderUploader_Describe_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Describe' +type DownloaderUploader_Describe_Call struct { + *mock.Call +} + +// Describe is a helper method to define mock.On call +// - ctx context.Context +// - digest string +func (_e *DownloaderUploader_Expecter) Describe(ctx any, digest any) *DownloaderUploader_Describe_Call { + return &DownloaderUploader_Describe_Call{Call: _e.mock.On("Describe", ctx, digest)} +} + +func (_c *DownloaderUploader_Describe_Call) Run(run func(ctx context.Context, digest string)) *DownloaderUploader_Describe_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *DownloaderUploader_Describe_Call) Return(resourceInfo *casclient.ResourceInfo, err error) *DownloaderUploader_Describe_Call { + _c.Call.Return(resourceInfo, err) + return _c +} + +func (_c *DownloaderUploader_Describe_Call) RunAndReturn(run func(ctx context.Context, digest string) (*casclient.ResourceInfo, error)) *DownloaderUploader_Describe_Call { + _c.Call.Return(run) + return _c +} + +// Download provides a mock function for the type DownloaderUploader +func (_mock *DownloaderUploader) Download(ctx context.Context, w io.Writer, digest string) error { + ret := _mock.Called(ctx, w, digest) + + if len(ret) == 0 { + panic("no return value specified for Download") + } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, io.Writer, string) error); ok { - r0 = rf(ctx, w, digest) + if returnFunc, ok := ret.Get(0).(func(context.Context, io.Writer, string) error); ok { + r0 = returnFunc(ctx, w, digest) } else { r0 = ret.Error(0) } - return r0 } -// IsReady provides a mock function with given fields: ctx -func (_m *DownloaderUploader) IsReady(ctx context.Context) (bool, error) { - ret := _m.Called(ctx) +// DownloaderUploader_Download_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Download' +type DownloaderUploader_Download_Call struct { + *mock.Call +} + +// Download is a helper method to define mock.On call +// - ctx context.Context +// - w io.Writer +// - digest string +func (_e *DownloaderUploader_Expecter) Download(ctx any, w any, digest any) *DownloaderUploader_Download_Call { + return &DownloaderUploader_Download_Call{Call: _e.mock.On("Download", ctx, w, digest)} +} + +func (_c *DownloaderUploader_Download_Call) Run(run func(ctx context.Context, w io.Writer, digest string)) *DownloaderUploader_Download_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 io.Writer + if args[1] != nil { + arg1 = args[1].(io.Writer) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + run( + arg0, + arg1, + arg2, + ) + }) + return _c +} + +func (_c *DownloaderUploader_Download_Call) Return(err error) *DownloaderUploader_Download_Call { + _c.Call.Return(err) + return _c +} + +func (_c *DownloaderUploader_Download_Call) RunAndReturn(run func(ctx context.Context, w io.Writer, digest string) error) *DownloaderUploader_Download_Call { + _c.Call.Return(run) + return _c +} + +// IsReady provides a mock function for the type DownloaderUploader +func (_mock *DownloaderUploader) IsReady(ctx context.Context) (bool, error) { + ret := _mock.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for IsReady") + } var r0 bool var r1 error - if rf, ok := ret.Get(0).(func(context.Context) (bool, error)); ok { - return rf(ctx) + if returnFunc, ok := ret.Get(0).(func(context.Context) (bool, error)); ok { + return returnFunc(ctx) } - if rf, ok := ret.Get(0).(func(context.Context) bool); ok { - r0 = rf(ctx) + if returnFunc, ok := ret.Get(0).(func(context.Context) bool); ok { + r0 = returnFunc(ctx) } else { r0 = ret.Get(0).(bool) } - - if rf, ok := ret.Get(1).(func(context.Context) error); ok { - r1 = rf(ctx) + if returnFunc, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = returnFunc(ctx) } else { r1 = ret.Error(1) } - return r0, r1 } -// Upload provides a mock function with given fields: ctx, r, digest, fileName -func (_m *DownloaderUploader) Upload(ctx context.Context, r io.Reader, digest string, fileName string) (*casclient.UpDownStatus, error) { - ret := _m.Called(ctx, r, digest, fileName) +// DownloaderUploader_IsReady_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsReady' +type DownloaderUploader_IsReady_Call struct { + *mock.Call +} + +// IsReady is a helper method to define mock.On call +// - ctx context.Context +func (_e *DownloaderUploader_Expecter) IsReady(ctx any) *DownloaderUploader_IsReady_Call { + return &DownloaderUploader_IsReady_Call{Call: _e.mock.On("IsReady", ctx)} +} + +func (_c *DownloaderUploader_IsReady_Call) Run(run func(ctx context.Context)) *DownloaderUploader_IsReady_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *DownloaderUploader_IsReady_Call) Return(b bool, err error) *DownloaderUploader_IsReady_Call { + _c.Call.Return(b, err) + return _c +} + +func (_c *DownloaderUploader_IsReady_Call) RunAndReturn(run func(ctx context.Context) (bool, error)) *DownloaderUploader_IsReady_Call { + _c.Call.Return(run) + return _c +} + +// Upload provides a mock function for the type DownloaderUploader +func (_mock *DownloaderUploader) Upload(ctx context.Context, r io.Reader, filename string, digest string) (*casclient.UpDownStatus, error) { + ret := _mock.Called(ctx, r, filename, digest) + + if len(ret) == 0 { + panic("no return value specified for Upload") + } var r0 *casclient.UpDownStatus var r1 error - if rf, ok := ret.Get(0).(func(context.Context, io.Reader, string, string) (*casclient.UpDownStatus, error)); ok { - return rf(ctx, r, digest, fileName) + if returnFunc, ok := ret.Get(0).(func(context.Context, io.Reader, string, string) (*casclient.UpDownStatus, error)); ok { + return returnFunc(ctx, r, filename, digest) } - if rf, ok := ret.Get(0).(func(context.Context, io.Reader, string, string) *casclient.UpDownStatus); ok { - r0 = rf(ctx, r, digest, fileName) + if returnFunc, ok := ret.Get(0).(func(context.Context, io.Reader, string, string) *casclient.UpDownStatus); ok { + r0 = returnFunc(ctx, r, filename, digest) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*casclient.UpDownStatus) } } - - if rf, ok := ret.Get(1).(func(context.Context, io.Reader, string, string) error); ok { - r1 = rf(ctx, r, digest, fileName) + if returnFunc, ok := ret.Get(1).(func(context.Context, io.Reader, string, string) error); ok { + r1 = returnFunc(ctx, r, filename, digest) } else { r1 = ret.Error(1) } - return r0, r1 } -// UploadFile provides a mock function with given fields: ctx, filepath -func (_m *DownloaderUploader) UploadFile(ctx context.Context, filepath string) (*casclient.UpDownStatus, error) { - ret := _m.Called(ctx, filepath) +// DownloaderUploader_Upload_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Upload' +type DownloaderUploader_Upload_Call struct { + *mock.Call +} + +// Upload is a helper method to define mock.On call +// - ctx context.Context +// - r io.Reader +// - filename string +// - digest string +func (_e *DownloaderUploader_Expecter) Upload(ctx any, r any, filename any, digest any) *DownloaderUploader_Upload_Call { + return &DownloaderUploader_Upload_Call{Call: _e.mock.On("Upload", ctx, r, filename, digest)} +} + +func (_c *DownloaderUploader_Upload_Call) Run(run func(ctx context.Context, r io.Reader, filename string, digest string)) *DownloaderUploader_Upload_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 io.Reader + if args[1] != nil { + arg1 = args[1].(io.Reader) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + var arg3 string + if args[3] != nil { + arg3 = args[3].(string) + } + run( + arg0, + arg1, + arg2, + arg3, + ) + }) + return _c +} + +func (_c *DownloaderUploader_Upload_Call) Return(upDownStatus *casclient.UpDownStatus, err error) *DownloaderUploader_Upload_Call { + _c.Call.Return(upDownStatus, err) + return _c +} + +func (_c *DownloaderUploader_Upload_Call) RunAndReturn(run func(ctx context.Context, r io.Reader, filename string, digest string) (*casclient.UpDownStatus, error)) *DownloaderUploader_Upload_Call { + _c.Call.Return(run) + return _c +} + +// UploadFile provides a mock function for the type DownloaderUploader +func (_mock *DownloaderUploader) UploadFile(ctx context.Context, filepath string) (*casclient.UpDownStatus, error) { + ret := _mock.Called(ctx, filepath) + + if len(ret) == 0 { + panic("no return value specified for UploadFile") + } var r0 *casclient.UpDownStatus var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string) (*casclient.UpDownStatus, error)); ok { - return rf(ctx, filepath) + if returnFunc, ok := ret.Get(0).(func(context.Context, string) (*casclient.UpDownStatus, error)); ok { + return returnFunc(ctx, filepath) } - if rf, ok := ret.Get(0).(func(context.Context, string) *casclient.UpDownStatus); ok { - r0 = rf(ctx, filepath) + if returnFunc, ok := ret.Get(0).(func(context.Context, string) *casclient.UpDownStatus); ok { + r0 = returnFunc(ctx, filepath) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*casclient.UpDownStatus) } } - - if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { - r1 = rf(ctx, filepath) + if returnFunc, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = returnFunc(ctx, filepath) } else { r1 = ret.Error(1) } - return r0, r1 } -type mockConstructorTestingTNewDownloaderUploader interface { - mock.TestingT - Cleanup(func()) +// DownloaderUploader_UploadFile_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UploadFile' +type DownloaderUploader_UploadFile_Call struct { + *mock.Call } -// NewDownloaderUploader creates a new instance of DownloaderUploader. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewDownloaderUploader(t mockConstructorTestingTNewDownloaderUploader) *DownloaderUploader { - mock := &DownloaderUploader{} - mock.Mock.Test(t) +// UploadFile is a helper method to define mock.On call +// - ctx context.Context +// - filepath string +func (_e *DownloaderUploader_Expecter) UploadFile(ctx any, filepath any) *DownloaderUploader_UploadFile_Call { + return &DownloaderUploader_UploadFile_Call{Call: _e.mock.On("UploadFile", ctx, filepath)} +} - t.Cleanup(func() { mock.AssertExpectations(t) }) +func (_c *DownloaderUploader_UploadFile_Call) Run(run func(ctx context.Context, filepath string)) *DownloaderUploader_UploadFile_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + run( + arg0, + arg1, + ) + }) + return _c +} - return mock +func (_c *DownloaderUploader_UploadFile_Call) Return(upDownStatus *casclient.UpDownStatus, err error) *DownloaderUploader_UploadFile_Call { + _c.Call.Return(upDownStatus, err) + return _c +} + +func (_c *DownloaderUploader_UploadFile_Call) RunAndReturn(run func(ctx context.Context, filepath string) (*casclient.UpDownStatus, error)) *DownloaderUploader_UploadFile_Call { + _c.Call.Return(run) + return _c }