From a1bdef44ca52a5837463b1ff8ce2b057bfe44d06 Mon Sep 17 00:00:00 2001 From: Javier Rodriguez Date: Fri, 16 Jan 2026 11:23:37 +0100 Subject: [PATCH 1/3] conflict Signed-off-by: Javier Rodriguez --- app/cli/cmd/attestation_status.go | 6 ++++ app/cli/cmd/workflow_workflow_run_describe.go | 5 +++ app/cli/pkg/action/action.go | 32 +++++++++++++++++++ app/cli/pkg/action/attestation_init.go | 9 ++++-- app/cli/pkg/action/attestation_push.go | 3 ++ app/cli/pkg/action/attestation_status.go | 1 + app/cli/pkg/action/workflow_run_describe.go | 9 ++++++ .../api/controlplane/v1/status.pb.go | 18 ++++++++--- .../api/controlplane/v1/status.proto | 2 ++ .../api/controlplane/v1/workflow_run.pb.go | 20 +++++++++--- .../api/controlplane/v1/workflow_run.proto | 2 ++ .../frontend/attestation/v1/crafting_state.ts | 17 +++++++++- .../gen/frontend/controlplane/v1/status.ts | 17 +++++++++- .../frontend/controlplane/v1/workflow_run.ts | 16 ++++++++++ ...testation.v1.CraftingState.jsonschema.json | 8 +++++ .../attestation.v1.CraftingState.schema.json | 8 +++++ ...ServiceInitResponse.Result.jsonschema.json | 8 +++++ ...tionServiceInitResponse.Result.schema.json | 8 +++++ ...trolplane.v1.InfozResponse.jsonschema.json | 8 +++++ .../controlplane.v1.InfozResponse.schema.json | 8 +++++ app/controlplane/cmd/wire_gen.go | 1 + app/controlplane/configs/config.devel.yaml | 2 ++ .../conf/controlplane/config/v1/conf.pb.go | 18 ++++++++--- .../conf/controlplane/config/v1/conf.proto | 3 ++ .../internal/service/attestation.go | 5 +++ app/controlplane/internal/service/status.go | 1 + deployment/chainloop/Chart.yaml | 2 +- .../templates/controlplane/configmap.yaml | 3 ++ deployment/chainloop/values.yaml | 5 ++- .../api/attestation/v1/crafting_state.pb.go | 24 ++++++++++---- .../api/attestation/v1/crafting_state.proto | 2 ++ pkg/attestation/crafter/crafter.go | 5 ++- 32 files changed, 248 insertions(+), 28 deletions(-) diff --git a/app/cli/cmd/attestation_status.go b/app/cli/cmd/attestation_status.go index b509c46c4..b6530a37e 100644 --- a/app/cli/cmd/attestation_status.go +++ b/app/cli/cmd/attestation_status.go @@ -140,6 +140,12 @@ func attestationStatusTableOutput(status *action.AttestationStatusResult, w io.W gt.AppendRow(table.Row{"Policies", "------"}) policiesTable(evs, gt) } + + // Add the Attestation View URL if available + if status.AttestationViewURL != "" { + gt.AppendRow(table.Row{"Attestation View URL", status.AttestationViewURL}) + } + gt.Render() if err := materialsTable(status, w, full); err != nil { diff --git a/app/cli/cmd/workflow_workflow_run_describe.go b/app/cli/cmd/workflow_workflow_run_describe.go index d2b183d7f..837d46006 100644 --- a/app/cli/cmd/workflow_workflow_run_describe.go +++ b/app/cli/cmd/workflow_workflow_run_describe.go @@ -177,6 +177,11 @@ func workflowRunDescribeTableOutput(run *action.WorkflowRunItemFull) error { gt.AppendRow(table.Row{"Policies", "------"}) policiesTable(evs, gt) } + + if run.Attestation.AttestationViewUrl != "" { + gt.AppendRow(table.Row{"Attestation View URL", run.Attestation.AttestationViewUrl}) + } + gt.Render() predicateV1Table(att) diff --git a/app/cli/pkg/action/action.go b/app/cli/pkg/action/action.go index 4158184b0..cd265fc22 100644 --- a/app/cli/pkg/action/action.go +++ b/app/cli/pkg/action/action.go @@ -20,6 +20,7 @@ import ( "fmt" "os" "path/filepath" + "strings" "time" pb "github.com/chainloop-dev/chainloop/app/controlplane/api/controlplane/v1" @@ -153,3 +154,34 @@ func getCASBackend(ctx context.Context, client pb.AttestationServiceClient, work casBackend.Uploader = casclient.New(artifactCASConn, casclient.WithLogger(logger)) return casBackendInfo, artifactCASConn.Close, nil } + +// fetchUiDashboardURL retrieves the UI Dashboard URL from the control plane +// Returns empty string if not configured or if fetch fails +func fetchUiDashboardURL(ctx context.Context, cpConnection *grpc.ClientConn) string { + if cpConnection == nil { + return "" + } + + tmoutCtx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + + client := pb.NewStatusServiceClient(cpConnection) + resp, err := client.Infoz(tmoutCtx, &pb.InfozRequest{}) + if err != nil { + return "" + } + + return resp.UiDashboardUrl +} + +// buildAttestationViewURL constructs the attestation view URL +// Returns empty string if platformURL is not configured +func buildAttestationViewURL(uiDashboardUrl, digest string) string { + if uiDashboardUrl == "" || digest == "" { + return "" + } + + // Trim trailing slash from platform URL if present + uiDashboardUrl = strings.TrimRight(uiDashboardUrl, "/") + return fmt.Sprintf("%s/attestation/%s?tab=summary", uiDashboardUrl, digest) +} diff --git a/app/cli/pkg/action/attestation_init.go b/app/cli/pkg/action/attestation_init.go index 602776ade..197d9b3a8 100644 --- a/app/cli/pkg/action/attestation_init.go +++ b/app/cli/pkg/action/attestation_init.go @@ -190,6 +190,7 @@ func (action *AttestationInit) Run(ctx context.Context, opts *AttestationInitRun policiesAllowedHostnames []string // Timestamp Authority URL for new attestations timestampAuthorityURL, signingCAName string + uiDashboardUrl string ) // Init in the control plane if needed including the runner context @@ -220,6 +221,7 @@ func (action *AttestationInit) Run(ctx context.Context, opts *AttestationInitRun signingOpts := result.GetSigningOptions() timestampAuthorityURL = signingOpts.GetTimestampAuthorityUrl() signingCAName = signingOpts.GetSigningCa() + uiDashboardUrl = result.GetUiDashboardUrl() if v := workflowMeta.Version; v != nil && workflowRun.GetVersion() != nil { v.Prerelease = workflowRun.GetVersion().GetPrerelease() @@ -277,9 +279,10 @@ func (action *AttestationInit) Run(ctx context.Context, opts *AttestationInitRun TimestampAuthorityURL: timestampAuthorityURL, SigningCAName: signingCAName, }, - Auth: authInfo, - CASBackend: casBackendInfo, - Logger: &action.Logger, + Auth: authInfo, + CASBackend: casBackendInfo, + Logger: &action.Logger, + UiDashboardUrl: uiDashboardUrl, } if err := action.c.Init(ctx, initOpts); err != nil { diff --git a/app/cli/pkg/action/attestation_push.go b/app/cli/pkg/action/attestation_push.go index 6af5a0562..fbb187ac4 100644 --- a/app/cli/pkg/action/attestation_push.go +++ b/app/cli/pkg/action/attestation_push.go @@ -226,6 +226,9 @@ func (action *AttestationPush) Run(ctx context.Context, attestationID string, ru return nil, fmt.Errorf("pushing to control plane: %w", err) } + // Build attestation view URL + attestationResult.Status.AttestationViewURL = buildAttestationViewURL(crafter.CraftingState.UiDashboardUrl, attestationResult.Digest) + action.Logger.Info().Msg("push completed") // Save bundle to disk diff --git a/app/cli/pkg/action/attestation_status.go b/app/cli/pkg/action/attestation_status.go index cbeb57e27..70bccb691 100644 --- a/app/cli/pkg/action/attestation_status.go +++ b/app/cli/pkg/action/attestation_status.go @@ -57,6 +57,7 @@ type AttestationStatusResult struct { HasPolicyViolations bool `json:"has_policy_violations"` MustBlockOnPolicyViolations bool `json:"must_block_on_policy_violations"` TimestampAuthority string `json:"timestamp_authority"` + AttestationViewURL string `json:"attestation_view_url"` // This might only be set if the attestation is pushed Digest string `json:"digest"` // This is the human readable output of the attestation status diff --git a/app/cli/pkg/action/workflow_run_describe.go b/app/cli/pkg/action/workflow_run_describe.go index a488c6c71..b71696d59 100644 --- a/app/cli/pkg/action/workflow_run_describe.go +++ b/app/cli/pkg/action/workflow_run_describe.go @@ -61,6 +61,8 @@ type WorkflowRunAttestationItem struct { PolicyEvaluations map[string][]*PolicyEvaluation `json:"policy_evaluations,omitempty"` // Policy evaluation status PolicyEvaluationStatus *PolicyEvaluationStatus `json:"policy_evaluation_status,omitempty"` + // URL to view the attestation in the UI + AttestationViewUrl string `json:"attestation_view_url"` } type PolicyEvaluationStatus struct { @@ -227,6 +229,12 @@ func (action *WorkflowRunDescribe) Run(ctx context.Context, opts *WorkflowRunDes policyEvaluationStatus := att.GetPolicyEvaluationStatus() + var attestationViewUrl string + baseUiDashboardUrl := fetchUiDashboardURL(ctx, action.cfg.CPConnection) + if baseUiDashboardUrl != "" { + attestationViewUrl = buildAttestationViewURL(baseUiDashboardUrl, att.DigestInCasBackend) + } + item.Attestation = &WorkflowRunAttestationItem{ Envelope: envelope, Bundle: att.GetBundle(), @@ -243,6 +251,7 @@ func (action *WorkflowRunDescribe) Run(ctx context.Context, opts *WorkflowRunDes HasViolations: policyEvaluationStatus.HasViolations, HasGatedViolations: policyEvaluationStatus.HasGatedViolations, }, + AttestationViewUrl: attestationViewUrl, } return item, nil diff --git a/app/controlplane/api/controlplane/v1/status.pb.go b/app/controlplane/api/controlplane/v1/status.pb.go index e7e69dd62..f9d481789 100644 --- a/app/controlplane/api/controlplane/v1/status.pb.go +++ b/app/controlplane/api/controlplane/v1/status.pb.go @@ -128,8 +128,10 @@ type InfozResponse struct { ChartVersion string `protobuf:"bytes,3,opt,name=chart_version,json=chartVersion,proto3" json:"chart_version,omitempty"` // Whether organization creation is restricted to admins RestrictedOrgCreation bool `protobuf:"varint,4,opt,name=restricted_org_creation,json=restrictedOrgCreation,proto3" json:"restricted_org_creation,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Link to the platform UI, if available + UiDashboardUrl string `protobuf:"bytes,5,opt,name=ui_dashboard_url,json=uiDashboardUrl,proto3" json:"ui_dashboard_url,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *InfozResponse) Reset() { @@ -190,6 +192,13 @@ func (x *InfozResponse) GetRestrictedOrgCreation() bool { return false } +func (x *InfozResponse) GetUiDashboardUrl() string { + if x != nil { + return x.UiDashboardUrl + } + return "" +} + type StatuszResponse struct { state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields @@ -233,12 +242,13 @@ const file_controlplane_v1_status_proto_rawDesc = "" + "\x1ccontrolplane/v1/status.proto\x12\x0fcontrolplane.v1\x1a\x1cgoogle/api/annotations.proto\"\x0e\n" + "\fInfozRequest\".\n" + "\x0eStatuszRequest\x12\x1c\n" + - "\treadiness\x18\x01 \x01(\bR\treadiness\"\xa3\x01\n" + + "\treadiness\x18\x01 \x01(\bR\treadiness\"\xcd\x01\n" + "\rInfozResponse\x12\x1b\n" + "\tlogin_url\x18\x01 \x01(\tR\bloginURL\x12\x18\n" + "\aversion\x18\x02 \x01(\tR\aversion\x12#\n" + "\rchart_version\x18\x03 \x01(\tR\fchartVersion\x126\n" + - "\x17restricted_org_creation\x18\x04 \x01(\bR\x15restrictedOrgCreation\"\x11\n" + + "\x17restricted_org_creation\x18\x04 \x01(\bR\x15restrictedOrgCreation\x12(\n" + + "\x10ui_dashboard_url\x18\x05 \x01(\tR\x0euiDashboardUrl\"\x11\n" + "\x0fStatuszResponse2\xc7\x01\n" + "\rStatusService\x12V\n" + "\x05Infoz\x12\x1d.controlplane.v1.InfozRequest\x1a\x1e.controlplane.v1.InfozResponse\"\x0e\x82\xd3\xe4\x93\x02\b\x12\x06/infoz\x12^\n" + diff --git a/app/controlplane/api/controlplane/v1/status.proto b/app/controlplane/api/controlplane/v1/status.proto index af2da742a..000c6ed71 100644 --- a/app/controlplane/api/controlplane/v1/status.proto +++ b/app/controlplane/api/controlplane/v1/status.proto @@ -46,6 +46,8 @@ message InfozResponse { string chart_version = 3; // Whether organization creation is restricted to admins bool restricted_org_creation = 4; + // Link to the platform UI, if available + string ui_dashboard_url = 5; } message StatuszResponse {} diff --git a/app/controlplane/api/controlplane/v1/workflow_run.pb.go b/app/controlplane/api/controlplane/v1/workflow_run.pb.go index 48d4f741f..97a4ce8c6 100644 --- a/app/controlplane/api/controlplane/v1/workflow_run.pb.go +++ b/app/controlplane/api/controlplane/v1/workflow_run.pb.go @@ -1384,8 +1384,10 @@ type AttestationServiceInitResponse_Result struct { SigningOptions *AttestationServiceInitResponse_SigningOptions `protobuf:"bytes,5,opt,name=signing_options,json=signingOptions,proto3" json:"signing_options,omitempty"` // array of hostnames that are allowed to be used in the policies PoliciesAllowedHostnames []string `protobuf:"bytes,6,rep,name=policies_allowed_hostnames,json=policiesAllowedHostnames,proto3" json:"policies_allowed_hostnames,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // URL pointing to the web dashboard. It might be empty if not available + UiDashboardUrl string `protobuf:"bytes,7,opt,name=ui_dashboard_url,json=uiDashboardUrl,proto3" json:"ui_dashboard_url,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *AttestationServiceInitResponse_Result) Reset() { @@ -1453,6 +1455,13 @@ func (x *AttestationServiceInitResponse_Result) GetPoliciesAllowedHostnames() [] return nil } +func (x *AttestationServiceInitResponse_Result) GetUiDashboardUrl() string { + if x != nil { + return x.UiDashboardUrl + } + return "" +} + type AttestationServiceInitResponse_SigningOptions struct { state protoimpl.MessageState `protogen:"open.v1"` // TSA service to be used for signing @@ -1766,15 +1775,16 @@ const file_controlplane_v1_workflow_run_proto_rawDesc = "" + "\rworkflow_name\x18\x04 \x01(\tB\a\xbaH\x04r\x02\x10\x01R\fworkflowName\x12*\n" + "\fproject_name\x18\x05 \x01(\tB\a\xbaH\x04r\x02\x10\x01R\vprojectName\x12'\n" + "\x0fproject_version\x18\x06 \x01(\tR\x0eprojectVersion\x128\n" + - "\x18require_existing_version\x18\a \x01(\bR\x16requireExistingVersion\"\xaf\x04\n" + + "\x18require_existing_version\x18\a \x01(\bR\x16requireExistingVersion\"\xd9\x04\n" + "\x1eAttestationServiceInitResponse\x12N\n" + - "\x06result\x18\x01 \x01(\v26.controlplane.v1.AttestationServiceInitResponse.ResultR\x06result\x1a\xd3\x02\n" + + "\x06result\x18\x01 \x01(\v26.controlplane.v1.AttestationServiceInitResponse.ResultR\x06result\x1a\xfd\x02\n" + "\x06Result\x12C\n" + "\fworkflow_run\x18\x02 \x01(\v2 .controlplane.v1.WorkflowRunItemR\vworkflowRun\x12\"\n" + "\forganization\x18\x03 \x01(\tR\forganization\x129\n" + "\x19block_on_policy_violation\x18\x04 \x01(\bR\x16blockOnPolicyViolation\x12g\n" + "\x0fsigning_options\x18\x05 \x01(\v2>.controlplane.v1.AttestationServiceInitResponse.SigningOptionsR\x0esigningOptions\x12<\n" + - "\x1apolicies_allowed_hostnames\x18\x06 \x03(\tR\x18policiesAllowedHostnames\x1ag\n" + + "\x1apolicies_allowed_hostnames\x18\x06 \x03(\tR\x18policiesAllowedHostnames\x12(\n" + + "\x10ui_dashboard_url\x18\a \x01(\tR\x0euiDashboardUrl\x1ag\n" + "\x0eSigningOptions\x126\n" + "\x17timestamp_authority_url\x18\x01 \x01(\tR\x15timestampAuthorityUrl\x12\x1d\n" + "\n" + diff --git a/app/controlplane/api/controlplane/v1/workflow_run.proto b/app/controlplane/api/controlplane/v1/workflow_run.proto index 4651b13d6..1386aa443 100644 --- a/app/controlplane/api/controlplane/v1/workflow_run.proto +++ b/app/controlplane/api/controlplane/v1/workflow_run.proto @@ -139,6 +139,8 @@ message AttestationServiceInitResponse { SigningOptions signing_options = 5; // array of hostnames that are allowed to be used in the policies repeated string policies_allowed_hostnames = 6; + // URL pointing to the web dashboard. It might be empty if not available + string ui_dashboard_url = 7; } message SigningOptions { diff --git a/app/controlplane/api/gen/frontend/attestation/v1/crafting_state.ts b/app/controlplane/api/gen/frontend/attestation/v1/crafting_state.ts index 7a58c760f..6f5514a9d 100644 --- a/app/controlplane/api/gen/frontend/attestation/v1/crafting_state.ts +++ b/app/controlplane/api/gen/frontend/attestation/v1/crafting_state.ts @@ -430,6 +430,8 @@ export interface CraftingState { schemaV2?: CraftingSchemaV2 | undefined; attestation?: Attestation; dryRun: boolean; + /** External URL of the platform UI, if available */ + uiDashboardUrl: string; } export interface WorkflowMetadata { @@ -3400,7 +3402,7 @@ export const Commit_CommitVerification = { }; function createBaseCraftingState(): CraftingState { - return { inputSchema: undefined, schemaV2: undefined, attestation: undefined, dryRun: false }; + return { inputSchema: undefined, schemaV2: undefined, attestation: undefined, dryRun: false, uiDashboardUrl: "" }; } export const CraftingState = { @@ -3417,6 +3419,9 @@ export const CraftingState = { if (message.dryRun === true) { writer.uint32(24).bool(message.dryRun); } + if (message.uiDashboardUrl !== "") { + writer.uint32(42).string(message.uiDashboardUrl); + } return writer; }, @@ -3455,6 +3460,13 @@ export const CraftingState = { message.dryRun = reader.bool(); continue; + case 5: + if (tag !== 42) { + break; + } + + message.uiDashboardUrl = reader.string(); + continue; } if ((tag & 7) === 4 || tag === 0) { break; @@ -3470,6 +3482,7 @@ export const CraftingState = { schemaV2: isSet(object.schemaV2) ? CraftingSchemaV2.fromJSON(object.schemaV2) : undefined, attestation: isSet(object.attestation) ? Attestation.fromJSON(object.attestation) : undefined, dryRun: isSet(object.dryRun) ? Boolean(object.dryRun) : false, + uiDashboardUrl: isSet(object.uiDashboardUrl) ? String(object.uiDashboardUrl) : "", }; }, @@ -3482,6 +3495,7 @@ export const CraftingState = { message.attestation !== undefined && (obj.attestation = message.attestation ? Attestation.toJSON(message.attestation) : undefined); message.dryRun !== undefined && (obj.dryRun = message.dryRun); + message.uiDashboardUrl !== undefined && (obj.uiDashboardUrl = message.uiDashboardUrl); return obj; }, @@ -3501,6 +3515,7 @@ export const CraftingState = { ? Attestation.fromPartial(object.attestation) : undefined; message.dryRun = object.dryRun ?? false; + message.uiDashboardUrl = object.uiDashboardUrl ?? ""; return message; }, }; diff --git a/app/controlplane/api/gen/frontend/controlplane/v1/status.ts b/app/controlplane/api/gen/frontend/controlplane/v1/status.ts index e448571a5..42472d414 100644 --- a/app/controlplane/api/gen/frontend/controlplane/v1/status.ts +++ b/app/controlplane/api/gen/frontend/controlplane/v1/status.ts @@ -24,6 +24,8 @@ export interface InfozResponse { chartVersion: string; /** Whether organization creation is restricted to admins */ restrictedOrgCreation: boolean; + /** Link to the platform UI, if available */ + uiDashboardUrl: string; } export interface StatuszResponse { @@ -130,7 +132,7 @@ export const StatuszRequest = { }; function createBaseInfozResponse(): InfozResponse { - return { loginUrl: "", version: "", chartVersion: "", restrictedOrgCreation: false }; + return { loginUrl: "", version: "", chartVersion: "", restrictedOrgCreation: false, uiDashboardUrl: "" }; } export const InfozResponse = { @@ -147,6 +149,9 @@ export const InfozResponse = { if (message.restrictedOrgCreation === true) { writer.uint32(32).bool(message.restrictedOrgCreation); } + if (message.uiDashboardUrl !== "") { + writer.uint32(42).string(message.uiDashboardUrl); + } return writer; }, @@ -185,6 +190,13 @@ export const InfozResponse = { message.restrictedOrgCreation = reader.bool(); continue; + case 5: + if (tag !== 42) { + break; + } + + message.uiDashboardUrl = reader.string(); + continue; } if ((tag & 7) === 4 || tag === 0) { break; @@ -200,6 +212,7 @@ export const InfozResponse = { version: isSet(object.version) ? String(object.version) : "", chartVersion: isSet(object.chartVersion) ? String(object.chartVersion) : "", restrictedOrgCreation: isSet(object.restrictedOrgCreation) ? Boolean(object.restrictedOrgCreation) : false, + uiDashboardUrl: isSet(object.uiDashboardUrl) ? String(object.uiDashboardUrl) : "", }; }, @@ -209,6 +222,7 @@ export const InfozResponse = { message.version !== undefined && (obj.version = message.version); message.chartVersion !== undefined && (obj.chartVersion = message.chartVersion); message.restrictedOrgCreation !== undefined && (obj.restrictedOrgCreation = message.restrictedOrgCreation); + message.uiDashboardUrl !== undefined && (obj.uiDashboardUrl = message.uiDashboardUrl); return obj; }, @@ -222,6 +236,7 @@ export const InfozResponse = { message.version = object.version ?? ""; message.chartVersion = object.chartVersion ?? ""; message.restrictedOrgCreation = object.restrictedOrgCreation ?? false; + message.uiDashboardUrl = object.uiDashboardUrl ?? ""; return message; }, }; diff --git a/app/controlplane/api/gen/frontend/controlplane/v1/workflow_run.ts b/app/controlplane/api/gen/frontend/controlplane/v1/workflow_run.ts index e5265fe84..fa19f6833 100644 --- a/app/controlplane/api/gen/frontend/controlplane/v1/workflow_run.ts +++ b/app/controlplane/api/gen/frontend/controlplane/v1/workflow_run.ts @@ -115,6 +115,8 @@ export interface AttestationServiceInitResponse_Result { signingOptions?: AttestationServiceInitResponse_SigningOptions; /** array of hostnames that are allowed to be used in the policies */ policiesAllowedHostnames: string[]; + /** URL pointing to the web dashboard. It might be empty if not available */ + uiDashboardUrl: string; } export interface AttestationServiceInitResponse_SigningOptions { @@ -1281,6 +1283,7 @@ function createBaseAttestationServiceInitResponse_Result(): AttestationServiceIn blockOnPolicyViolation: false, signingOptions: undefined, policiesAllowedHostnames: [], + uiDashboardUrl: "", }; } @@ -1301,6 +1304,9 @@ export const AttestationServiceInitResponse_Result = { for (const v of message.policiesAllowedHostnames) { writer.uint32(50).string(v!); } + if (message.uiDashboardUrl !== "") { + writer.uint32(58).string(message.uiDashboardUrl); + } return writer; }, @@ -1346,6 +1352,13 @@ export const AttestationServiceInitResponse_Result = { message.policiesAllowedHostnames.push(reader.string()); continue; + case 7: + if (tag !== 58) { + break; + } + + message.uiDashboardUrl = reader.string(); + continue; } if ((tag & 7) === 4 || tag === 0) { break; @@ -1366,6 +1379,7 @@ export const AttestationServiceInitResponse_Result = { policiesAllowedHostnames: Array.isArray(object?.policiesAllowedHostnames) ? object.policiesAllowedHostnames.map((e: any) => String(e)) : [], + uiDashboardUrl: isSet(object.uiDashboardUrl) ? String(object.uiDashboardUrl) : "", }; }, @@ -1383,6 +1397,7 @@ export const AttestationServiceInitResponse_Result = { } else { obj.policiesAllowedHostnames = []; } + message.uiDashboardUrl !== undefined && (obj.uiDashboardUrl = message.uiDashboardUrl); return obj; }, @@ -1405,6 +1420,7 @@ export const AttestationServiceInitResponse_Result = { ? AttestationServiceInitResponse_SigningOptions.fromPartial(object.signingOptions) : undefined; message.policiesAllowedHostnames = object.policiesAllowedHostnames?.map((e) => e) || []; + message.uiDashboardUrl = object.uiDashboardUrl ?? ""; return message; }, }; diff --git a/app/controlplane/api/gen/jsonschema/attestation.v1.CraftingState.jsonschema.json b/app/controlplane/api/gen/jsonschema/attestation.v1.CraftingState.jsonschema.json index b9fab1952..1f3feeb49 100644 --- a/app/controlplane/api/gen/jsonschema/attestation.v1.CraftingState.jsonschema.json +++ b/app/controlplane/api/gen/jsonschema/attestation.v1.CraftingState.jsonschema.json @@ -12,6 +12,10 @@ }, "^(schema_v2)$": { "$ref": "workflowcontract.v1.CraftingSchemaV2.jsonschema.json" + }, + "^(ui_dashboard_url)$": { + "description": "External URL of the platform UI, if available", + "type": "string" } }, "properties": { @@ -26,6 +30,10 @@ }, "schemaV2": { "$ref": "workflowcontract.v1.CraftingSchemaV2.jsonschema.json" + }, + "uiDashboardUrl": { + "description": "External URL of the platform UI, if available", + "type": "string" } }, "title": "Crafting State", diff --git a/app/controlplane/api/gen/jsonschema/attestation.v1.CraftingState.schema.json b/app/controlplane/api/gen/jsonschema/attestation.v1.CraftingState.schema.json index 6c101c2b1..850ae44c8 100644 --- a/app/controlplane/api/gen/jsonschema/attestation.v1.CraftingState.schema.json +++ b/app/controlplane/api/gen/jsonschema/attestation.v1.CraftingState.schema.json @@ -12,6 +12,10 @@ }, "^(schemaV2)$": { "$ref": "workflowcontract.v1.CraftingSchemaV2.schema.json" + }, + "^(uiDashboardUrl)$": { + "description": "External URL of the platform UI, if available", + "type": "string" } }, "properties": { @@ -26,6 +30,10 @@ }, "schema_v2": { "$ref": "workflowcontract.v1.CraftingSchemaV2.schema.json" + }, + "ui_dashboard_url": { + "description": "External URL of the platform UI, if available", + "type": "string" } }, "title": "Crafting State", diff --git a/app/controlplane/api/gen/jsonschema/controlplane.v1.AttestationServiceInitResponse.Result.jsonschema.json b/app/controlplane/api/gen/jsonschema/controlplane.v1.AttestationServiceInitResponse.Result.jsonschema.json index 574ea7495..d6421f5c6 100644 --- a/app/controlplane/api/gen/jsonschema/controlplane.v1.AttestationServiceInitResponse.Result.jsonschema.json +++ b/app/controlplane/api/gen/jsonschema/controlplane.v1.AttestationServiceInitResponse.Result.jsonschema.json @@ -18,6 +18,10 @@ "$ref": "controlplane.v1.AttestationServiceInitResponse.SigningOptions.jsonschema.json", "description": "Signing options" }, + "^(ui_dashboard_url)$": { + "description": "URL pointing to the web dashboard. It might be empty if not available", + "type": "string" + }, "^(workflow_run)$": { "$ref": "controlplane.v1.WorkflowRunItem.jsonschema.json" } @@ -42,6 +46,10 @@ "$ref": "controlplane.v1.AttestationServiceInitResponse.SigningOptions.jsonschema.json", "description": "Signing options" }, + "uiDashboardUrl": { + "description": "URL pointing to the web dashboard. It might be empty if not available", + "type": "string" + }, "workflowRun": { "$ref": "controlplane.v1.WorkflowRunItem.jsonschema.json" } diff --git a/app/controlplane/api/gen/jsonschema/controlplane.v1.AttestationServiceInitResponse.Result.schema.json b/app/controlplane/api/gen/jsonschema/controlplane.v1.AttestationServiceInitResponse.Result.schema.json index 6aae1ef92..1a86b4c67 100644 --- a/app/controlplane/api/gen/jsonschema/controlplane.v1.AttestationServiceInitResponse.Result.schema.json +++ b/app/controlplane/api/gen/jsonschema/controlplane.v1.AttestationServiceInitResponse.Result.schema.json @@ -18,6 +18,10 @@ "$ref": "controlplane.v1.AttestationServiceInitResponse.SigningOptions.schema.json", "description": "Signing options" }, + "^(uiDashboardUrl)$": { + "description": "URL pointing to the web dashboard. It might be empty if not available", + "type": "string" + }, "^(workflowRun)$": { "$ref": "controlplane.v1.WorkflowRunItem.schema.json" } @@ -42,6 +46,10 @@ "$ref": "controlplane.v1.AttestationServiceInitResponse.SigningOptions.schema.json", "description": "Signing options" }, + "ui_dashboard_url": { + "description": "URL pointing to the web dashboard. It might be empty if not available", + "type": "string" + }, "workflow_run": { "$ref": "controlplane.v1.WorkflowRunItem.schema.json" } diff --git a/app/controlplane/api/gen/jsonschema/controlplane.v1.InfozResponse.jsonschema.json b/app/controlplane/api/gen/jsonschema/controlplane.v1.InfozResponse.jsonschema.json index 3df08f5cd..bf18723c7 100644 --- a/app/controlplane/api/gen/jsonschema/controlplane.v1.InfozResponse.jsonschema.json +++ b/app/controlplane/api/gen/jsonschema/controlplane.v1.InfozResponse.jsonschema.json @@ -13,6 +13,10 @@ "^(restricted_org_creation)$": { "description": "Whether organization creation is restricted to admins", "type": "boolean" + }, + "^(ui_dashboard_url)$": { + "description": "Link to the platform UI, if available", + "type": "string" } }, "properties": { @@ -27,6 +31,10 @@ "description": "Whether organization creation is restricted to admins", "type": "boolean" }, + "uiDashboardUrl": { + "description": "Link to the platform UI, if available", + "type": "string" + }, "version": { "type": "string" } diff --git a/app/controlplane/api/gen/jsonschema/controlplane.v1.InfozResponse.schema.json b/app/controlplane/api/gen/jsonschema/controlplane.v1.InfozResponse.schema.json index 3423ead2c..0dad3dbac 100644 --- a/app/controlplane/api/gen/jsonschema/controlplane.v1.InfozResponse.schema.json +++ b/app/controlplane/api/gen/jsonschema/controlplane.v1.InfozResponse.schema.json @@ -13,6 +13,10 @@ "^(restrictedOrgCreation)$": { "description": "Whether organization creation is restricted to admins", "type": "boolean" + }, + "^(uiDashboardUrl)$": { + "description": "Link to the platform UI, if available", + "type": "string" } }, "properties": { @@ -27,6 +31,10 @@ "description": "Whether organization creation is restricted to admins", "type": "boolean" }, + "ui_dashboard_url": { + "description": "Link to the platform UI, if available", + "type": "string" + }, "version": { "type": "string" } diff --git a/app/controlplane/cmd/wire_gen.go b/app/controlplane/cmd/wire_gen.go index d29b34aab..f5c57c0b3 100644 --- a/app/controlplane/cmd/wire_gen.go +++ b/app/controlplane/cmd/wire_gen.go @@ -203,6 +203,7 @@ func wireApp(bootstrap *conf.Bootstrap, readerWriter credentials.ReaderWriter, l ProjectVersionUC: projectVersionUseCase, SigningUseCase: signingUseCase, UserUC: userUseCase, + BootstrapConfig: bootstrap, Opts: v5, } attestationService := service.NewAttestationService(newAttestationServiceOpts) diff --git a/app/controlplane/configs/config.devel.yaml b/app/controlplane/configs/config.devel.yaml index e7acbe0ec..dc546e062 100644 --- a/app/controlplane/configs/config.devel.yaml +++ b/app/controlplane/configs/config.devel.yaml @@ -107,3 +107,5 @@ enable_profiler: true # federated_authentication: # enabled: true # url: http://localhost:8002/machine-identity/verify-token + +ui_dashboard_url: http://localhost:3000 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 6f5d5d895..124e1302f 100644 --- a/app/controlplane/internal/conf/controlplane/config/v1/conf.pb.go +++ b/app/controlplane/internal/conf/controlplane/config/v1/conf.pb.go @@ -78,8 +78,10 @@ type Bootstrap struct { FederatedAuthentication *FederatedAuthentication `protobuf:"bytes,16,opt,name=federated_authentication,json=federatedAuthentication,proto3" json:"federated_authentication,omitempty"` // Restrict organization creation to admins RestrictOrgCreation bool `protobuf:"varint,18,opt,name=restrict_org_creation,json=restrictOrgCreation,proto3" json:"restrict_org_creation,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // External URL of the platform UI, if available + UiDashboardUrl string `protobuf:"bytes,19,opt,name=ui_dashboard_url,json=uiDashboardUrl,proto3" json:"ui_dashboard_url,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *Bootstrap) Reset() { @@ -239,6 +241,13 @@ func (x *Bootstrap) GetRestrictOrgCreation() bool { return false } +func (x *Bootstrap) GetUiDashboardUrl() string { + if x != nil { + return x.UiDashboardUrl + } + return "" +} + type FederatedAuthentication struct { state protoimpl.MessageState `protogen:"open.v1"` // URL of the federated verification endpoint @@ -1556,7 +1565,7 @@ var File_controlplane_config_v1_conf_proto protoreflect.FileDescriptor const file_controlplane_config_v1_conf_proto_rawDesc = "" + "\n" + - "!controlplane/config/v1/conf.proto\x12\x16controlplane.config.v1\x1a\x1bbuf/validate/validate.proto\x1a#controlplane/config/v1/config.proto\x1a\x1bcredentials/v1/config.proto\x1a\x1egoogle/protobuf/duration.proto\"\xf2\r\n" + + "!controlplane/config/v1/conf.proto\x12\x16controlplane.config.v1\x1a\x1bbuf/validate/validate.proto\x1a#controlplane/config/v1/config.proto\x1a\x1bcredentials/v1/config.proto\x1a\x1egoogle/protobuf/duration.proto\"\x9c\x0e\n" + "\tBootstrap\x126\n" + "\x06server\x18\x01 \x01(\v2\x1e.controlplane.config.v1.ServerR\x06server\x120\n" + "\x04data\x18\x02 \x01(\v2\x1c.controlplane.config.v1.DataR\x04data\x120\n" + @@ -1581,7 +1590,8 @@ const file_controlplane_config_v1_conf_proto_rawDesc = "" + "\vnats_server\x18\x0e \x01(\v2,.controlplane.config.v1.Bootstrap.NatsServerR\n" + "natsServer\x12j\n" + "\x18federated_authentication\x18\x10 \x01(\v2/.controlplane.config.v1.FederatedAuthenticationR\x17federatedAuthentication\x122\n" + - "\x15restrict_org_creation\x18\x12 \x01(\bR\x13restrictOrgCreation\x1a\x9d\x01\n" + + "\x15restrict_org_creation\x18\x12 \x01(\bR\x13restrictOrgCreation\x12(\n" + + "\x10ui_dashboard_url\x18\x13 \x01(\tR\x0euiDashboardUrl\x1a\x9d\x01\n" + "\rObservability\x12N\n" + "\x06sentry\x18\x01 \x01(\v26.controlplane.config.v1.Bootstrap.Observability.SentryR\x06sentry\x1a<\n" + "\x06Sentry\x12\x10\n" + diff --git a/app/controlplane/internal/conf/controlplane/config/v1/conf.proto b/app/controlplane/internal/conf/controlplane/config/v1/conf.proto index 254cdd15d..1cf70681d 100644 --- a/app/controlplane/internal/conf/controlplane/config/v1/conf.proto +++ b/app/controlplane/internal/conf/controlplane/config/v1/conf.proto @@ -102,6 +102,9 @@ message Bootstrap { string token = 2 [(buf.validate.field).string.min_len = 1]; } } + + // External URL of the platform UI, if available + string ui_dashboard_url = 19; } message FederatedAuthentication { diff --git a/app/controlplane/internal/service/attestation.go b/app/controlplane/internal/service/attestation.go index 4ca1a7423..585c6701a 100644 --- a/app/controlplane/internal/service/attestation.go +++ b/app/controlplane/internal/service/attestation.go @@ -24,6 +24,7 @@ import ( "github.com/cenkalti/backoff/v4" cpAPI "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/internal/dispatcher" "github.com/chainloop-dev/chainloop/app/controlplane/internal/usercontext" "github.com/chainloop-dev/chainloop/app/controlplane/internal/usercontext/attjwtmiddleware" @@ -60,6 +61,7 @@ type AttestationService struct { projectUseCase *biz.ProjectUseCase signingUseCase *biz.SigningUseCase userUseCase *biz.UserUseCase + bootstrapConfig *conf.Bootstrap } type NewAttestationServiceOpts struct { @@ -80,6 +82,7 @@ type NewAttestationServiceOpts struct { ProjectVersionUC *biz.ProjectVersionUseCase SigningUseCase *biz.SigningUseCase UserUC *biz.UserUseCase + BootstrapConfig *conf.Bootstrap Opts []NewOpt } @@ -103,6 +106,7 @@ func NewAttestationService(opts *NewAttestationServiceOpts) *AttestationService projectVersionUseCase: opts.ProjectVersionUC, signingUseCase: opts.SigningUseCase, userUseCase: opts.UserUC, + bootstrapConfig: opts.BootstrapConfig, } } @@ -208,6 +212,7 @@ func (s *AttestationService) Init(ctx context.Context, req *cpAPI.AttestationSer Organization: org.Name, BlockOnPolicyViolation: org.BlockOnPolicyViolation, PoliciesAllowedHostnames: org.PoliciesAllowedHostnames, + UiDashboardUrl: s.bootstrapConfig.UiDashboardUrl, } resp.SigningOptions = &cpAPI.AttestationServiceInitResponse_SigningOptions{} diff --git a/app/controlplane/internal/service/status.go b/app/controlplane/internal/service/status.go index 67d8ce115..95ef63113 100644 --- a/app/controlplane/internal/service/status.go +++ b/app/controlplane/internal/service/status.go @@ -53,6 +53,7 @@ func (s *StatusService) Statusz(ctx context.Context, r *pb.StatuszRequest) (*pb. func (s *StatusService) Infoz(_ context.Context, _ *pb.InfozRequest) (*pb.InfozResponse, error) { return &pb.InfozResponse{ LoginUrl: s.loginURL, + UiDashboardUrl: s.bootstrap.UiDashboardUrl, Version: s.version, ChartVersion: os.Getenv("CHART_VERSION"), RestrictedOrgCreation: s.bootstrap.RestrictOrgCreation, diff --git a/deployment/chainloop/Chart.yaml b/deployment/chainloop/Chart.yaml index b9a50e7ec..84ccfaccb 100644 --- a/deployment/chainloop/Chart.yaml +++ b/deployment/chainloop/Chart.yaml @@ -7,7 +7,7 @@ description: Chainloop is an open source software supply chain control plane, a type: application # Bump the patch (not minor, not major) version on each change in the Chart Source code -version: 1.322.0 +version: 1.322.1 # Do not update appVersion, this is handled automatically by the release process appVersion: v1.71.2 diff --git a/deployment/chainloop/templates/controlplane/configmap.yaml b/deployment/chainloop/templates/controlplane/configmap.yaml index e974809c3..388e23709 100644 --- a/deployment/chainloop/templates/controlplane/configmap.yaml +++ b/deployment/chainloop/templates/controlplane/configmap.yaml @@ -46,6 +46,9 @@ data: {{- end }} plugins_dir: {{ .Values.controlplane.pluginsDir }} restrict_org_creation: {{ .Values.controlplane.restrictOrgCreation }} + {{- if .Values.controlplane.uiDashboardURL }} + ui_dashboard_url: {{ .Values.controlplane.uiDashboardURL | quote }} + {{- end }} referrer_shared_index: {{- toYaml .Values.controlplane.referrerSharedIndex | nindent 6 }} {{ if .Values.controlplane.onboarding }} diff --git a/deployment/chainloop/values.yaml b/deployment/chainloop/values.yaml index 0b4c00c40..044cdb64c 100644 --- a/deployment/chainloop/values.yaml +++ b/deployment/chainloop/values.yaml @@ -179,7 +179,10 @@ controlplane: ## @skip controlplane.restrictOrgCreation Restrict organization creation to instance admins restrictOrgCreation: false - + + ## @param controlplane.uiDashboardURL Optional external URL for the dashboard UI. If set, CLI will display attestation view links after push + uiDashboardURL: "" + ## @extra controlplane.nats optional NATS configuration for events publishing. ## @param controlplane.nats.enabled Enable events publishing through a Nats stream ## @param controlplane.nats.host NATS Host diff --git a/pkg/attestation/crafter/api/attestation/v1/crafting_state.pb.go b/pkg/attestation/crafter/api/attestation/v1/crafting_state.pb.go index c2a4fd6da..51603b02f 100644 --- a/pkg/attestation/crafter/api/attestation/v1/crafting_state.pb.go +++ b/pkg/attestation/crafter/api/attestation/v1/crafting_state.pb.go @@ -737,11 +737,13 @@ type CraftingState struct { // // *CraftingState_InputSchema // *CraftingState_SchemaV2 - Schema isCraftingState_Schema `protobuf_oneof:"schema"` - Attestation *Attestation `protobuf:"bytes,2,opt,name=attestation,proto3" json:"attestation,omitempty"` - DryRun bool `protobuf:"varint,3,opt,name=dry_run,json=dryRun,proto3" json:"dry_run,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Schema isCraftingState_Schema `protobuf_oneof:"schema"` + Attestation *Attestation `protobuf:"bytes,2,opt,name=attestation,proto3" json:"attestation,omitempty"` + DryRun bool `protobuf:"varint,3,opt,name=dry_run,json=dryRun,proto3" json:"dry_run,omitempty"` + // External URL of the platform UI, if available + UiDashboardUrl string `protobuf:"bytes,5,opt,name=ui_dashboard_url,json=uiDashboardUrl,proto3" json:"ui_dashboard_url,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *CraftingState) Reset() { @@ -813,6 +815,13 @@ func (x *CraftingState) GetDryRun() bool { return false } +func (x *CraftingState) GetUiDashboardUrl() string { + if x != nil { + return x.UiDashboardUrl + } + return "" +} + type isCraftingState_Schema interface { isCraftingState_Schema() } @@ -2348,12 +2357,13 @@ const file_attestation_v1_crafting_state_proto_rawDesc = "" + "unverified\x10\x02\x12\x0f\n" + "\vunavailable\x10\x03\x12\x12\n" + "\x0enot_applicable\x10\x04B\x18\n" + - "\x16_platform_verification\"\x81\x02\n" + + "\x16_platform_verification\"\xab\x02\n" + "\rCraftingState\x12H\n" + "\finput_schema\x18\x01 \x01(\v2#.workflowcontract.v1.CraftingSchemaH\x00R\vinputSchema\x12D\n" + "\tschema_v2\x18\x04 \x01(\v2%.workflowcontract.v1.CraftingSchemaV2H\x00R\bschemaV2\x12=\n" + "\vattestation\x18\x02 \x01(\v2\x1b.attestation.v1.AttestationR\vattestation\x12\x17\n" + - "\adry_run\x18\x03 \x01(\bR\x06dryRunB\b\n" + + "\adry_run\x18\x03 \x01(\bR\x06dryRun\x12(\n" + + "\x10ui_dashboard_url\x18\x05 \x01(\tR\x0euiDashboardUrlB\b\n" + "\x06schema\"\xa3\x03\n" + "\x10WorkflowMetadata\x12\x1b\n" + "\x04name\x18\x01 \x01(\tB\a\xbaH\x04r\x02\x10\x01R\x04name\x12\x18\n" + diff --git a/pkg/attestation/crafter/api/attestation/v1/crafting_state.proto b/pkg/attestation/crafter/api/attestation/v1/crafting_state.proto index ea24063ad..ba3c9f89d 100644 --- a/pkg/attestation/crafter/api/attestation/v1/crafting_state.proto +++ b/pkg/attestation/crafter/api/attestation/v1/crafting_state.proto @@ -354,6 +354,8 @@ message CraftingState { } Attestation attestation = 2; bool dry_run = 3; + // External URL of the platform UI, if available + string ui_dashboard_url = 5; } message WorkflowMetadata { diff --git a/pkg/attestation/crafter/crafter.go b/pkg/attestation/crafter/crafter.go index d2c3277c7..1f98a9353 100644 --- a/pkg/attestation/crafter/crafter.go +++ b/pkg/attestation/crafter/crafter.go @@ -169,6 +169,8 @@ type InitOpts struct { PoliciesAllowedHostnames []string // CAS backend information CASBackend *api.Attestation_CASBackend + // UiDashboardUrl is the base URL to build the attestation view link + UiDashboardUrl string // Logger for verification logging Logger *zerolog.Logger } @@ -415,7 +417,8 @@ func initialCraftingState(cwd string, opts *InitOpts) (*api.CraftingState, error PoliciesAllowedHostnames: opts.PoliciesAllowedHostnames, CasBackend: opts.CASBackend, }, - DryRun: opts.DryRun, + DryRun: opts.DryRun, + UiDashboardUrl: opts.UiDashboardUrl, } // Set the appropriate schema From 30d2f75ecda57947fdbfbed2c2ae0fcf3ee22b12 Mon Sep 17 00:00:00 2001 From: Javier Rodriguez Date: Fri, 16 Jan 2026 11:41:43 +0100 Subject: [PATCH 2/3] fix linter Signed-off-by: Javier Rodriguez --- app/cli/cmd/workflow_workflow_run_describe.go | 4 ++-- app/cli/pkg/action/action.go | 12 ++++++------ app/cli/pkg/action/attestation_init.go | 2 +- app/cli/pkg/action/workflow_run_describe.go | 10 +++++----- pkg/attestation/crafter/crafter.go | 6 +++--- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/app/cli/cmd/workflow_workflow_run_describe.go b/app/cli/cmd/workflow_workflow_run_describe.go index 837d46006..0e2ede462 100644 --- a/app/cli/cmd/workflow_workflow_run_describe.go +++ b/app/cli/cmd/workflow_workflow_run_describe.go @@ -178,8 +178,8 @@ func workflowRunDescribeTableOutput(run *action.WorkflowRunItemFull) error { policiesTable(evs, gt) } - if run.Attestation.AttestationViewUrl != "" { - gt.AppendRow(table.Row{"Attestation View URL", run.Attestation.AttestationViewUrl}) + if run.Attestation.AttestationViewURL != "" { + gt.AppendRow(table.Row{"Attestation View URL", run.Attestation.AttestationViewURL}) } gt.Render() diff --git a/app/cli/pkg/action/action.go b/app/cli/pkg/action/action.go index cd265fc22..a51dbedce 100644 --- a/app/cli/pkg/action/action.go +++ b/app/cli/pkg/action/action.go @@ -155,9 +155,9 @@ func getCASBackend(ctx context.Context, client pb.AttestationServiceClient, work return casBackendInfo, artifactCASConn.Close, nil } -// fetchUiDashboardURL retrieves the UI Dashboard URL from the control plane +// fetchUIDashboardURL retrieves the UI Dashboard URL from the control plane // Returns empty string if not configured or if fetch fails -func fetchUiDashboardURL(ctx context.Context, cpConnection *grpc.ClientConn) string { +func fetchUIDashboardURL(ctx context.Context, cpConnection *grpc.ClientConn) string { if cpConnection == nil { return "" } @@ -176,12 +176,12 @@ func fetchUiDashboardURL(ctx context.Context, cpConnection *grpc.ClientConn) str // buildAttestationViewURL constructs the attestation view URL // Returns empty string if platformURL is not configured -func buildAttestationViewURL(uiDashboardUrl, digest string) string { - if uiDashboardUrl == "" || digest == "" { +func buildAttestationViewURL(uiDashboardURL, digest string) string { + if uiDashboardURL == "" || digest == "" { return "" } // Trim trailing slash from platform URL if present - uiDashboardUrl = strings.TrimRight(uiDashboardUrl, "/") - return fmt.Sprintf("%s/attestation/%s?tab=summary", uiDashboardUrl, digest) + uiDashboardURL = strings.TrimRight(uiDashboardURL, "/") + return fmt.Sprintf("%s/attestation/%s?tab=summary", uiDashboardURL, digest) } diff --git a/app/cli/pkg/action/attestation_init.go b/app/cli/pkg/action/attestation_init.go index 197d9b3a8..40e7d1c9f 100644 --- a/app/cli/pkg/action/attestation_init.go +++ b/app/cli/pkg/action/attestation_init.go @@ -282,7 +282,7 @@ func (action *AttestationInit) Run(ctx context.Context, opts *AttestationInitRun Auth: authInfo, CASBackend: casBackendInfo, Logger: &action.Logger, - UiDashboardUrl: uiDashboardUrl, + UIDashboardURL: uiDashboardUrl, } if err := action.c.Init(ctx, initOpts); err != nil { diff --git a/app/cli/pkg/action/workflow_run_describe.go b/app/cli/pkg/action/workflow_run_describe.go index b71696d59..191b02e01 100644 --- a/app/cli/pkg/action/workflow_run_describe.go +++ b/app/cli/pkg/action/workflow_run_describe.go @@ -62,7 +62,7 @@ type WorkflowRunAttestationItem struct { // Policy evaluation status PolicyEvaluationStatus *PolicyEvaluationStatus `json:"policy_evaluation_status,omitempty"` // URL to view the attestation in the UI - AttestationViewUrl string `json:"attestation_view_url"` + AttestationViewURL string `json:"attestation_view_url"` } type PolicyEvaluationStatus struct { @@ -230,9 +230,9 @@ func (action *WorkflowRunDescribe) Run(ctx context.Context, opts *WorkflowRunDes policyEvaluationStatus := att.GetPolicyEvaluationStatus() var attestationViewUrl string - baseUiDashboardUrl := fetchUiDashboardURL(ctx, action.cfg.CPConnection) - if baseUiDashboardUrl != "" { - attestationViewUrl = buildAttestationViewURL(baseUiDashboardUrl, att.DigestInCasBackend) + baseUIDashboardURL := fetchUIDashboardURL(ctx, action.cfg.CPConnection) + if baseUIDashboardURL != "" { + attestationViewUrl = buildAttestationViewURL(baseUIDashboardURL, att.DigestInCasBackend) } item.Attestation = &WorkflowRunAttestationItem{ @@ -251,7 +251,7 @@ func (action *WorkflowRunDescribe) Run(ctx context.Context, opts *WorkflowRunDes HasViolations: policyEvaluationStatus.HasViolations, HasGatedViolations: policyEvaluationStatus.HasGatedViolations, }, - AttestationViewUrl: attestationViewUrl, + AttestationViewURL: attestationViewUrl, } return item, nil diff --git a/pkg/attestation/crafter/crafter.go b/pkg/attestation/crafter/crafter.go index 1f98a9353..a83f9f080 100644 --- a/pkg/attestation/crafter/crafter.go +++ b/pkg/attestation/crafter/crafter.go @@ -169,8 +169,8 @@ type InitOpts struct { PoliciesAllowedHostnames []string // CAS backend information CASBackend *api.Attestation_CASBackend - // UiDashboardUrl is the base URL to build the attestation view link - UiDashboardUrl string + // UIDashboardURL is the base URL to build the attestation view link + UIDashboardURL string // Logger for verification logging Logger *zerolog.Logger } @@ -418,7 +418,7 @@ func initialCraftingState(cwd string, opts *InitOpts) (*api.CraftingState, error CasBackend: opts.CASBackend, }, DryRun: opts.DryRun, - UiDashboardUrl: opts.UiDashboardUrl, + UiDashboardUrl: opts.UIDashboardURL, } // Set the appropriate schema From 12f5f95e62add6570ce3fd1d6d6107c7e20996bf Mon Sep 17 00:00:00 2001 From: Javier Rodriguez Date: Fri, 16 Jan 2026 11:45:53 +0100 Subject: [PATCH 3/3] more linter fixes Signed-off-by: Javier Rodriguez --- app/cli/pkg/action/attestation_init.go | 6 +++--- app/cli/pkg/action/workflow_run_describe.go | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/cli/pkg/action/attestation_init.go b/app/cli/pkg/action/attestation_init.go index 40e7d1c9f..e6c0786db 100644 --- a/app/cli/pkg/action/attestation_init.go +++ b/app/cli/pkg/action/attestation_init.go @@ -190,7 +190,7 @@ func (action *AttestationInit) Run(ctx context.Context, opts *AttestationInitRun policiesAllowedHostnames []string // Timestamp Authority URL for new attestations timestampAuthorityURL, signingCAName string - uiDashboardUrl string + uiDashboardURL string ) // Init in the control plane if needed including the runner context @@ -221,7 +221,7 @@ func (action *AttestationInit) Run(ctx context.Context, opts *AttestationInitRun signingOpts := result.GetSigningOptions() timestampAuthorityURL = signingOpts.GetTimestampAuthorityUrl() signingCAName = signingOpts.GetSigningCa() - uiDashboardUrl = result.GetUiDashboardUrl() + uiDashboardURL = result.GetUiDashboardUrl() if v := workflowMeta.Version; v != nil && workflowRun.GetVersion() != nil { v.Prerelease = workflowRun.GetVersion().GetPrerelease() @@ -282,7 +282,7 @@ func (action *AttestationInit) Run(ctx context.Context, opts *AttestationInitRun Auth: authInfo, CASBackend: casBackendInfo, Logger: &action.Logger, - UIDashboardURL: uiDashboardUrl, + UIDashboardURL: uiDashboardURL, } if err := action.c.Init(ctx, initOpts); err != nil { diff --git a/app/cli/pkg/action/workflow_run_describe.go b/app/cli/pkg/action/workflow_run_describe.go index 191b02e01..abe1816ea 100644 --- a/app/cli/pkg/action/workflow_run_describe.go +++ b/app/cli/pkg/action/workflow_run_describe.go @@ -229,10 +229,10 @@ func (action *WorkflowRunDescribe) Run(ctx context.Context, opts *WorkflowRunDes policyEvaluationStatus := att.GetPolicyEvaluationStatus() - var attestationViewUrl string + var attestationViewURL string baseUIDashboardURL := fetchUIDashboardURL(ctx, action.cfg.CPConnection) if baseUIDashboardURL != "" { - attestationViewUrl = buildAttestationViewURL(baseUIDashboardURL, att.DigestInCasBackend) + attestationViewURL = buildAttestationViewURL(baseUIDashboardURL, att.DigestInCasBackend) } item.Attestation = &WorkflowRunAttestationItem{ @@ -251,7 +251,7 @@ func (action *WorkflowRunDescribe) Run(ctx context.Context, opts *WorkflowRunDes HasViolations: policyEvaluationStatus.HasViolations, HasGatedViolations: policyEvaluationStatus.HasGatedViolations, }, - AttestationViewURL: attestationViewUrl, + AttestationViewURL: attestationViewURL, } return item, nil