Skip to content

Commit 14e2264

Browse files
authored
chore(attestations): Include attestation links on att push (#2670)
Signed-off-by: Javier Rodriguez <javier@chainloop.dev>
1 parent 7e8fccf commit 14e2264

32 files changed

+248
-28
lines changed

app/cli/cmd/attestation_status.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,12 @@ func attestationStatusTableOutput(status *action.AttestationStatusResult, w io.W
140140
gt.AppendRow(table.Row{"Policies", "------"})
141141
policiesTable(evs, gt)
142142
}
143+
144+
// Add the Attestation View URL if available
145+
if status.AttestationViewURL != "" {
146+
gt.AppendRow(table.Row{"Attestation View URL", status.AttestationViewURL})
147+
}
148+
143149
gt.Render()
144150

145151
if err := materialsTable(status, w, full); err != nil {

app/cli/cmd/workflow_workflow_run_describe.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,11 @@ func workflowRunDescribeTableOutput(run *action.WorkflowRunItemFull) error {
177177
gt.AppendRow(table.Row{"Policies", "------"})
178178
policiesTable(evs, gt)
179179
}
180+
181+
if run.Attestation.AttestationViewURL != "" {
182+
gt.AppendRow(table.Row{"Attestation View URL", run.Attestation.AttestationViewURL})
183+
}
184+
180185
gt.Render()
181186

182187
predicateV1Table(att)

app/cli/pkg/action/action.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"fmt"
2121
"os"
2222
"path/filepath"
23+
"strings"
2324
"time"
2425

2526
pb "github.com/chainloop-dev/chainloop/app/controlplane/api/controlplane/v1"
@@ -153,3 +154,34 @@ func getCASBackend(ctx context.Context, client pb.AttestationServiceClient, work
153154
casBackend.Uploader = casclient.New(artifactCASConn, casclient.WithLogger(logger))
154155
return casBackendInfo, artifactCASConn.Close, nil
155156
}
157+
158+
// fetchUIDashboardURL retrieves the UI Dashboard URL from the control plane
159+
// Returns empty string if not configured or if fetch fails
160+
func fetchUIDashboardURL(ctx context.Context, cpConnection *grpc.ClientConn) string {
161+
if cpConnection == nil {
162+
return ""
163+
}
164+
165+
tmoutCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
166+
defer cancel()
167+
168+
client := pb.NewStatusServiceClient(cpConnection)
169+
resp, err := client.Infoz(tmoutCtx, &pb.InfozRequest{})
170+
if err != nil {
171+
return ""
172+
}
173+
174+
return resp.UiDashboardUrl
175+
}
176+
177+
// buildAttestationViewURL constructs the attestation view URL
178+
// Returns empty string if platformURL is not configured
179+
func buildAttestationViewURL(uiDashboardURL, digest string) string {
180+
if uiDashboardURL == "" || digest == "" {
181+
return ""
182+
}
183+
184+
// Trim trailing slash from platform URL if present
185+
uiDashboardURL = strings.TrimRight(uiDashboardURL, "/")
186+
return fmt.Sprintf("%s/attestation/%s?tab=summary", uiDashboardURL, digest)
187+
}

app/cli/pkg/action/attestation_init.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ func (action *AttestationInit) Run(ctx context.Context, opts *AttestationInitRun
190190
policiesAllowedHostnames []string
191191
// Timestamp Authority URL for new attestations
192192
timestampAuthorityURL, signingCAName string
193+
uiDashboardURL string
193194
)
194195

195196
// Init in the control plane if needed including the runner context
@@ -220,6 +221,7 @@ func (action *AttestationInit) Run(ctx context.Context, opts *AttestationInitRun
220221
signingOpts := result.GetSigningOptions()
221222
timestampAuthorityURL = signingOpts.GetTimestampAuthorityUrl()
222223
signingCAName = signingOpts.GetSigningCa()
224+
uiDashboardURL = result.GetUiDashboardUrl()
223225

224226
if v := workflowMeta.Version; v != nil && workflowRun.GetVersion() != nil {
225227
v.Prerelease = workflowRun.GetVersion().GetPrerelease()
@@ -277,9 +279,10 @@ func (action *AttestationInit) Run(ctx context.Context, opts *AttestationInitRun
277279
TimestampAuthorityURL: timestampAuthorityURL,
278280
SigningCAName: signingCAName,
279281
},
280-
Auth: authInfo,
281-
CASBackend: casBackendInfo,
282-
Logger: &action.Logger,
282+
Auth: authInfo,
283+
CASBackend: casBackendInfo,
284+
Logger: &action.Logger,
285+
UIDashboardURL: uiDashboardURL,
283286
}
284287

285288
if err := action.c.Init(ctx, initOpts); err != nil {

app/cli/pkg/action/attestation_push.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,9 @@ func (action *AttestationPush) Run(ctx context.Context, attestationID string, ru
226226
return nil, fmt.Errorf("pushing to control plane: %w", err)
227227
}
228228

229+
// Build attestation view URL
230+
attestationResult.Status.AttestationViewURL = buildAttestationViewURL(crafter.CraftingState.UiDashboardUrl, attestationResult.Digest)
231+
229232
action.Logger.Info().Msg("push completed")
230233

231234
// Save bundle to disk

app/cli/pkg/action/attestation_status.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ type AttestationStatusResult struct {
5757
HasPolicyViolations bool `json:"has_policy_violations"`
5858
MustBlockOnPolicyViolations bool `json:"must_block_on_policy_violations"`
5959
TimestampAuthority string `json:"timestamp_authority"`
60+
AttestationViewURL string `json:"attestation_view_url"`
6061
// This might only be set if the attestation is pushed
6162
Digest string `json:"digest"`
6263
// This is the human readable output of the attestation status

app/cli/pkg/action/workflow_run_describe.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ type WorkflowRunAttestationItem struct {
6161
PolicyEvaluations map[string][]*PolicyEvaluation `json:"policy_evaluations,omitempty"`
6262
// Policy evaluation status
6363
PolicyEvaluationStatus *PolicyEvaluationStatus `json:"policy_evaluation_status,omitempty"`
64+
// URL to view the attestation in the UI
65+
AttestationViewURL string `json:"attestation_view_url"`
6466
}
6567

6668
type PolicyEvaluationStatus struct {
@@ -227,6 +229,12 @@ func (action *WorkflowRunDescribe) Run(ctx context.Context, opts *WorkflowRunDes
227229

228230
policyEvaluationStatus := att.GetPolicyEvaluationStatus()
229231

232+
var attestationViewURL string
233+
baseUIDashboardURL := fetchUIDashboardURL(ctx, action.cfg.CPConnection)
234+
if baseUIDashboardURL != "" {
235+
attestationViewURL = buildAttestationViewURL(baseUIDashboardURL, att.DigestInCasBackend)
236+
}
237+
230238
item.Attestation = &WorkflowRunAttestationItem{
231239
Envelope: envelope,
232240
Bundle: att.GetBundle(),
@@ -243,6 +251,7 @@ func (action *WorkflowRunDescribe) Run(ctx context.Context, opts *WorkflowRunDes
243251
HasViolations: policyEvaluationStatus.HasViolations,
244252
HasGatedViolations: policyEvaluationStatus.HasGatedViolations,
245253
},
254+
AttestationViewURL: attestationViewURL,
246255
}
247256

248257
return item, nil

app/controlplane/api/controlplane/v1/status.pb.go

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

app/controlplane/api/controlplane/v1/status.proto

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ message InfozResponse {
4646
string chart_version = 3;
4747
// Whether organization creation is restricted to admins
4848
bool restricted_org_creation = 4;
49+
// Link to the platform UI, if available
50+
string ui_dashboard_url = 5;
4951
}
5052

5153
message StatuszResponse {}

app/controlplane/api/controlplane/v1/workflow_run.pb.go

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

0 commit comments

Comments
 (0)