diff --git a/app/cli/cmd/attestation_init.go b/app/cli/cmd/attestation_init.go index dfdd6955f..3648812a0 100644 --- a/app/cli/cmd/attestation_init.go +++ b/app/cli/cmd/attestation_init.go @@ -22,6 +22,7 @@ import ( "github.com/chainloop-dev/chainloop/app/cli/cmd/output" "github.com/chainloop-dev/chainloop/app/cli/pkg/action" "github.com/spf13/cobra" + "github.com/spf13/viper" ) func newAttestationInitCmd() *cobra.Command { @@ -77,11 +78,14 @@ func newAttestationInitCmd() *cobra.Command { RunE: func(cmd *cobra.Command, _ []string) error { a, err := action.NewAttestationInit( &action.AttestationInitOpts{ - ActionsOpts: ActionOpts, - DryRun: attestationDryRun, - Force: force, - UseRemoteState: useAttestationRemoteState, - LocalStatePath: attestationLocalStatePath, + ActionsOpts: ActionOpts, + DryRun: attestationDryRun, + Force: force, + UseRemoteState: useAttestationRemoteState, + LocalStatePath: attestationLocalStatePath, + CASURI: viper.GetString(confOptions.CASAPI.viperKey), + CASCAPath: viper.GetString(confOptions.CASCA.viperKey), + ConnectionInsecure: apiInsecure(), }, ) if err != nil { diff --git a/app/cli/documentation/cli-reference.mdx b/app/cli/documentation/cli-reference.mdx index 04ad83272..21c14c386 100755 --- a/app/cli/documentation/cli-reference.mdx +++ b/app/cli/documentation/cli-reference.mdx @@ -200,7 +200,7 @@ Options --annotation strings additional annotation in the format of key=value --attestation-id string Unique identifier of the in-progress attestation -h, --help help for add ---kind string kind of the material to be recorded: ["ARTIFACT" "ATTESTATION" "BLACKDUCK_SCA_JSON" "CHAINLOOP_RUNNER_CONTEXT" "CONTAINER_IMAGE" "CSAF_INFORMATIONAL_ADVISORY" "CSAF_SECURITY_ADVISORY" "CSAF_SECURITY_INCIDENT_RESPONSE" "CSAF_VEX" "EVIDENCE" "GHAS_CODE_SCAN" "GHAS_DEPENDENCY_SCAN" "GHAS_SECRET_SCAN" "GITLAB_SECURITY_REPORT" "HELM_CHART" "JACOCO_XML" "JUNIT_XML" "OPENVEX" "SARIF" "SBOM_CYCLONEDX_JSON" "SBOM_SPDX_JSON" "SLSA_PROVENANCE" "STRING" "TWISTCLI_SCAN_JSON" "ZAP_DAST_ZIP"] +--kind string kind of the material to be recorded: ["ARTIFACT" "ATTESTATION" "BLACKDUCK_SCA_JSON" "CHAINLOOP_PR_INFO" "CHAINLOOP_RUNNER_CONTEXT" "CONTAINER_IMAGE" "CSAF_INFORMATIONAL_ADVISORY" "CSAF_SECURITY_ADVISORY" "CSAF_SECURITY_INCIDENT_RESPONSE" "CSAF_VEX" "EVIDENCE" "GHAS_CODE_SCAN" "GHAS_DEPENDENCY_SCAN" "GHAS_SECRET_SCAN" "GITLAB_SECURITY_REPORT" "HELM_CHART" "JACOCO_XML" "JUNIT_XML" "OPENVEX" "SARIF" "SBOM_CYCLONEDX_JSON" "SBOM_SPDX_JSON" "SLSA_PROVENANCE" "STRING" "TWISTCLI_SCAN_JSON" "ZAP_DAST_ZIP"] --name string name of the material as shown in the contract --registry-password string registry password, ($CHAINLOOP_REGISTRY_PASSWORD) --registry-server string OCI repository server, ($CHAINLOOP_REGISTRY_SERVER) @@ -2871,7 +2871,7 @@ Options --annotation strings Key-value pairs of material annotations (key=value) -h, --help help for eval --input stringArray Key-value pairs of policy inputs (key=value) ---kind string Kind of the material: ["ARTIFACT" "ATTESTATION" "BLACKDUCK_SCA_JSON" "CHAINLOOP_RUNNER_CONTEXT" "CONTAINER_IMAGE" "CSAF_INFORMATIONAL_ADVISORY" "CSAF_SECURITY_ADVISORY" "CSAF_SECURITY_INCIDENT_RESPONSE" "CSAF_VEX" "EVIDENCE" "GHAS_CODE_SCAN" "GHAS_DEPENDENCY_SCAN" "GHAS_SECRET_SCAN" "GITLAB_SECURITY_REPORT" "HELM_CHART" "JACOCO_XML" "JUNIT_XML" "OPENVEX" "SARIF" "SBOM_CYCLONEDX_JSON" "SBOM_SPDX_JSON" "SLSA_PROVENANCE" "STRING" "TWISTCLI_SCAN_JSON" "ZAP_DAST_ZIP"] +--kind string Kind of the material: ["ARTIFACT" "ATTESTATION" "BLACKDUCK_SCA_JSON" "CHAINLOOP_PR_INFO" "CHAINLOOP_RUNNER_CONTEXT" "CONTAINER_IMAGE" "CSAF_INFORMATIONAL_ADVISORY" "CSAF_SECURITY_ADVISORY" "CSAF_SECURITY_INCIDENT_RESPONSE" "CSAF_VEX" "EVIDENCE" "GHAS_CODE_SCAN" "GHAS_DEPENDENCY_SCAN" "GHAS_SECRET_SCAN" "GITLAB_SECURITY_REPORT" "HELM_CHART" "JACOCO_XML" "JUNIT_XML" "OPENVEX" "SARIF" "SBOM_CYCLONEDX_JSON" "SBOM_SPDX_JSON" "SLSA_PROVENANCE" "STRING" "TWISTCLI_SCAN_JSON" "ZAP_DAST_ZIP"] --material string Path to material or attestation file -p, --policy string Policy reference (./my-policy.yaml, https://my-domain.com/my-policy.yaml, chainloop://my-stored-policy) (default "policy.yaml") ``` diff --git a/app/cli/pkg/action/action.go b/app/cli/pkg/action/action.go index 9f3e7f4b6..4f219606b 100644 --- a/app/cli/pkg/action/action.go +++ b/app/cli/pkg/action/action.go @@ -16,6 +16,7 @@ package action import ( + "context" "fmt" "os" "path/filepath" @@ -25,6 +26,9 @@ import ( "github.com/chainloop-dev/chainloop/pkg/attestation/crafter" "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/statemanager/filesystem" "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/statemanager/remote" + "github.com/chainloop-dev/chainloop/pkg/casclient" + "github.com/chainloop-dev/chainloop/pkg/grpcconn" + "github.com/rs/zerolog" "google.golang.org/grpc" ) @@ -94,3 +98,51 @@ func newCrafter(stateOpts *newCrafterStateOpts, conn *grpc.ClientConn, opts ...c return crafter.NewCrafter(stateManager, attClient, opts...) } + +// getCASBackend tries to get CAS upload credentials and set up a CAS client +func getCASBackend(ctx context.Context, client pb.AttestationServiceClient, workflowRunID, casCAPath, casURI string, casConnectionInsecure bool, logger zerolog.Logger, casBackend *casclient.CASBackend) (func() error, error) { + credsResp, err := client.GetUploadCreds(ctx, &pb.AttestationServiceGetUploadCredsRequest{ + WorkflowRunId: workflowRunID, + }) + if err != nil { + // Log warning but don't fail - will fall back to inline storage + logger.Warn().Err(err).Msg("failed to get CAS credentials for PR metadata, will store inline") + return nil, fmt.Errorf("getting upload creds: %w", err) + } + + if credsResp == nil || credsResp.GetResult() == nil { + logger.Debug().Msg("no upload creds result, will store inline") + return nil, fmt.Errorf("getting upload creds: %w", err) + } + + result := credsResp.GetResult() + backend := result.GetBackend() + if backend == nil { + logger.Debug().Msg("no backend info in upload creds, will store inline") + return nil, fmt.Errorf("no backend found in upload creds") + } + + casBackend.Name = backend.Provider + if backend.GetLimits() != nil { + casBackend.MaxSize = backend.GetLimits().MaxBytes + } + + // Only attempt to create a CAS connection when not inline and token is present + if backend.IsInline || result.Token == "" { + return nil, nil + } + + opts := []grpcconn.Option{grpcconn.WithInsecure(casConnectionInsecure)} + if casCAPath != "" { + opts = append(opts, grpcconn.WithCAFile(casCAPath)) + } + + artifactCASConn, err := grpcconn.New(casURI, result.Token, opts...) + if err != nil { + logger.Warn().Err(err).Msg("failed to create CAS connection, will store inline") + return nil, fmt.Errorf("creating CAS connection: %w", err) + } + + casBackend.Uploader = casclient.New(artifactCASConn, casclient.WithLogger(logger)) + return artifactCASConn.Close, nil +} diff --git a/app/cli/pkg/action/attestation_add.go b/app/cli/pkg/action/attestation_add.go index e4fe70889..39d41d56f 100644 --- a/app/cli/pkg/action/attestation_add.go +++ b/app/cli/pkg/action/attestation_add.go @@ -24,7 +24,6 @@ import ( "github.com/chainloop-dev/chainloop/pkg/attestation/crafter" api "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/api/attestation/v1" "github.com/chainloop-dev/chainloop/pkg/casclient" - "github.com/chainloop-dev/chainloop/pkg/grpcconn" "google.golang.org/grpc" ) @@ -98,40 +97,15 @@ func (action *AttestationAdd) Run(ctx context.Context, attestationID, materialNa // Define CASbackend information based on the API response if !crafter.CraftingState.GetDryRun() { - // Get upload creds and CASbackend for the current attestation and set up CAS client client := pb.NewAttestationServiceClient(action.CPConnection) - creds, err := client.GetUploadCreds(ctx, - &pb.AttestationServiceGetUploadCredsRequest{ - WorkflowRunId: crafter.CraftingState.GetAttestation().GetWorkflow().GetWorkflowRunId(), - }, - ) - if err != nil { - return nil, fmt.Errorf("getting upload creds: %w", err) - } - b := creds.GetResult().GetBackend() - if b == nil { - return nil, fmt.Errorf("no backend found in upload creds") + workflowRunID := crafter.CraftingState.GetAttestation().GetWorkflow().GetWorkflowRunId() + connectionCloserFn, getCASBackendErr := getCASBackend(ctx, client, workflowRunID, action.casCAPath, action.casURI, action.connectionInsecure, action.Logger, casBackend) + if getCASBackendErr != nil { + return nil, fmt.Errorf("failed to get CAS backend: %w", getCASBackendErr) } - casBackend.Name = b.Provider - casBackend.MaxSize = b.GetLimits().MaxBytes - // Some CASBackends will actually upload information to the CAS server - // in such case we need to set up a connection - if !b.IsInline && creds.Result.Token != "" { - var opts = []grpcconn.Option{ - grpcconn.WithInsecure(action.connectionInsecure), - } - - if action.casCAPath != "" { - opts = append(opts, grpcconn.WithCAFile(action.casCAPath)) - } - - artifactCASConn, err := grpcconn.New(action.casURI, creds.Result.Token, opts...) - if err != nil { - return nil, fmt.Errorf("creating CAS connection: %w", err) - } - defer artifactCASConn.Close() - - casBackend.Uploader = casclient.New(artifactCASConn, casclient.WithLogger(action.Logger)) + if connectionCloserFn != nil { + // nolint: errcheck + defer connectionCloserFn() } } diff --git a/app/cli/pkg/action/attestation_init.go b/app/cli/pkg/action/attestation_init.go index cc33877cd..f1bf1c097 100644 --- a/app/cli/pkg/action/attestation_init.go +++ b/app/cli/pkg/action/attestation_init.go @@ -27,6 +27,7 @@ import ( "github.com/chainloop-dev/chainloop/app/controlplane/pkg/unmarshal" "github.com/chainloop-dev/chainloop/pkg/attestation/crafter" clientAPI "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/api/attestation/v1" + "github.com/chainloop-dev/chainloop/pkg/casclient" "github.com/chainloop-dev/chainloop/pkg/policies" "github.com/rs/zerolog" ) @@ -37,16 +38,22 @@ type AttestationInitOpts struct { // Force the initialization and override any existing, in-progress ones. // Note that this is only useful when local-based attestation state is configured // since it's a protection to make sure you don't override the state by mistake - Force bool - UseRemoteState bool - LocalStatePath string + Force bool + UseRemoteState bool + LocalStatePath string + CASURI string + CASCAPath string // optional CA certificate for the CAS connection + ConnectionInsecure bool } type AttestationInit struct { *ActionsOpts - dryRun, force bool - c *crafter.Crafter - useRemoteState bool + dryRun, force bool + c *crafter.Crafter + useRemoteState bool + casURI string + casCAPath string + connectionInsecure bool } // ErrAttestationAlreadyExist means that there is an attestation in progress @@ -67,11 +74,14 @@ func NewAttestationInit(cfg *AttestationInitOpts) (*AttestationInit, error) { } return &AttestationInit{ - ActionsOpts: cfg.ActionsOpts, - c: c, - dryRun: cfg.DryRun, - force: cfg.Force, - useRemoteState: cfg.UseRemoteState, + ActionsOpts: cfg.ActionsOpts, + c: c, + dryRun: cfg.DryRun, + force: cfg.Force, + useRemoteState: cfg.UseRemoteState, + casURI: cfg.CASURI, + casCAPath: cfg.CASCAPath, + connectionInsecure: cfg.ConnectionInsecure, }, nil } @@ -219,6 +229,20 @@ func (action *AttestationInit) Run(ctx context.Context, opts *AttestationInitRun attestationID = workflowRun.GetId() } + // Get CAS credentials for PR metadata upload + var casBackend = &casclient.CASBackend{Name: "not-set"} + if !action.dryRun && attestationID != "" { + connectionCloserFn, err := getCASBackend(ctx, client, attestationID, action.casCAPath, action.casURI, action.connectionInsecure, action.Logger, casBackend) + if err != nil { + // We don't want to fail the attestation initialization if CAS setup fails, it's a best-effort feature for PR/MR metadata + action.Logger.Warn().Err(err).Msg("unexpected error getting CAS backend") + } + if connectionCloserFn != nil { + // nolint: errcheck + defer connectionCloserFn() + } + } + var authInfo *clientAPI.Attestation_Auth if action.AuthTokenRaw != "" { authInfo, err = extractAuthInfo(action.AuthTokenRaw) @@ -268,6 +292,12 @@ func (action *AttestationInit) Run(ctx context.Context, opts *AttestationInitRun return "", err } + // Auto-collect PR/MR metadata if in PR/MR context + if err := action.c.AutoCollectPRMetadata(ctx, attestationID, discoveredRunner, casBackend); err != nil { + action.Logger.Warn().Err(err).Msg("failed to auto-collect PR/MR metadata") + // Don't fail the init - this is best-effort + } + return attestationID, nil } diff --git a/app/controlplane/api/gen/frontend/workflowcontract/v1/crafting_schema.ts b/app/controlplane/api/gen/frontend/workflowcontract/v1/crafting_schema.ts index dfda40bfe..ac6398299 100644 --- a/app/controlplane/api/gen/frontend/workflowcontract/v1/crafting_schema.ts +++ b/app/controlplane/api/gen/frontend/workflowcontract/v1/crafting_schema.ts @@ -200,6 +200,8 @@ export enum CraftingSchema_Material_MaterialType { SLSA_PROVENANCE = 24, /** CHAINLOOP_RUNNER_CONTEXT - The Chainloop CLI plugin for runner context */ CHAINLOOP_RUNNER_CONTEXT = 25, + /** CHAINLOOP_PR_INFO - Pull Request / Merge Request metadata collected automatically during attestation */ + CHAINLOOP_PR_INFO = 26, UNRECOGNIZED = -1, } @@ -283,6 +285,9 @@ export function craftingSchema_Material_MaterialTypeFromJSON(object: any): Craft case 25: case "CHAINLOOP_RUNNER_CONTEXT": return CraftingSchema_Material_MaterialType.CHAINLOOP_RUNNER_CONTEXT; + case 26: + case "CHAINLOOP_PR_INFO": + return CraftingSchema_Material_MaterialType.CHAINLOOP_PR_INFO; case -1: case "UNRECOGNIZED": default: @@ -344,6 +349,8 @@ export function craftingSchema_Material_MaterialTypeToJSON(object: CraftingSchem return "SLSA_PROVENANCE"; case CraftingSchema_Material_MaterialType.CHAINLOOP_RUNNER_CONTEXT: return "CHAINLOOP_RUNNER_CONTEXT"; + case CraftingSchema_Material_MaterialType.CHAINLOOP_PR_INFO: + return "CHAINLOOP_PR_INFO"; case CraftingSchema_Material_MaterialType.UNRECOGNIZED: default: return "UNRECOGNIZED"; diff --git a/app/controlplane/api/gen/jsonschema/attestation.v1.Attestation.Material.jsonschema.json b/app/controlplane/api/gen/jsonschema/attestation.v1.Attestation.Material.jsonschema.json index 177be1fa5..fc46c4592 100644 --- a/app/controlplane/api/gen/jsonschema/attestation.v1.Attestation.Material.jsonschema.json +++ b/app/controlplane/api/gen/jsonschema/attestation.v1.Attestation.Material.jsonschema.json @@ -42,7 +42,8 @@ "GHAS_DEPENDENCY_SCAN", "JACOCO_XML", "SLSA_PROVENANCE", - "CHAINLOOP_RUNNER_CONTEXT" + "CHAINLOOP_RUNNER_CONTEXT", + "CHAINLOOP_PR_INFO" ], "title": "Material Type", "type": "string" @@ -120,7 +121,8 @@ "GHAS_DEPENDENCY_SCAN", "JACOCO_XML", "SLSA_PROVENANCE", - "CHAINLOOP_RUNNER_CONTEXT" + "CHAINLOOP_RUNNER_CONTEXT", + "CHAINLOOP_PR_INFO" ], "title": "Material Type", "type": "string" diff --git a/app/controlplane/api/gen/jsonschema/attestation.v1.Attestation.Material.schema.json b/app/controlplane/api/gen/jsonschema/attestation.v1.Attestation.Material.schema.json index 0d1227433..ea41e2842 100644 --- a/app/controlplane/api/gen/jsonschema/attestation.v1.Attestation.Material.schema.json +++ b/app/controlplane/api/gen/jsonschema/attestation.v1.Attestation.Material.schema.json @@ -42,7 +42,8 @@ "GHAS_DEPENDENCY_SCAN", "JACOCO_XML", "SLSA_PROVENANCE", - "CHAINLOOP_RUNNER_CONTEXT" + "CHAINLOOP_RUNNER_CONTEXT", + "CHAINLOOP_PR_INFO" ], "title": "Material Type", "type": "string" @@ -120,7 +121,8 @@ "GHAS_DEPENDENCY_SCAN", "JACOCO_XML", "SLSA_PROVENANCE", - "CHAINLOOP_RUNNER_CONTEXT" + "CHAINLOOP_RUNNER_CONTEXT", + "CHAINLOOP_PR_INFO" ], "title": "Material Type", "type": "string" diff --git a/app/controlplane/api/gen/jsonschema/attestation.v1.PolicyEvaluation.jsonschema.json b/app/controlplane/api/gen/jsonschema/attestation.v1.PolicyEvaluation.jsonschema.json index 670b6763f..f733a7ada 100644 --- a/app/controlplane/api/gen/jsonschema/attestation.v1.PolicyEvaluation.jsonschema.json +++ b/app/controlplane/api/gen/jsonschema/attestation.v1.PolicyEvaluation.jsonschema.json @@ -135,7 +135,8 @@ "GHAS_DEPENDENCY_SCAN", "JACOCO_XML", "SLSA_PROVENANCE", - "CHAINLOOP_RUNNER_CONTEXT" + "CHAINLOOP_RUNNER_CONTEXT", + "CHAINLOOP_PR_INFO" ], "title": "Material Type", "type": "string" diff --git a/app/controlplane/api/gen/jsonschema/attestation.v1.PolicyEvaluation.schema.json b/app/controlplane/api/gen/jsonschema/attestation.v1.PolicyEvaluation.schema.json index 28c95c6a7..ff6c44abb 100644 --- a/app/controlplane/api/gen/jsonschema/attestation.v1.PolicyEvaluation.schema.json +++ b/app/controlplane/api/gen/jsonschema/attestation.v1.PolicyEvaluation.schema.json @@ -135,7 +135,8 @@ "GHAS_DEPENDENCY_SCAN", "JACOCO_XML", "SLSA_PROVENANCE", - "CHAINLOOP_RUNNER_CONTEXT" + "CHAINLOOP_RUNNER_CONTEXT", + "CHAINLOOP_PR_INFO" ], "title": "Material Type", "type": "string" diff --git a/app/controlplane/api/gen/jsonschema/workflowcontract.v1.CraftingSchema.Material.jsonschema.json b/app/controlplane/api/gen/jsonschema/workflowcontract.v1.CraftingSchema.Material.jsonschema.json index f01e3c24c..7540ea618 100644 --- a/app/controlplane/api/gen/jsonschema/workflowcontract.v1.CraftingSchema.Material.jsonschema.json +++ b/app/controlplane/api/gen/jsonschema/workflowcontract.v1.CraftingSchema.Material.jsonschema.json @@ -59,7 +59,8 @@ "GHAS_DEPENDENCY_SCAN", "JACOCO_XML", "SLSA_PROVENANCE", - "CHAINLOOP_RUNNER_CONTEXT" + "CHAINLOOP_RUNNER_CONTEXT", + "CHAINLOOP_PR_INFO" ], "title": "Material Type", "type": "string" diff --git a/app/controlplane/api/gen/jsonschema/workflowcontract.v1.CraftingSchema.Material.schema.json b/app/controlplane/api/gen/jsonschema/workflowcontract.v1.CraftingSchema.Material.schema.json index 118d0908b..877ecd297 100644 --- a/app/controlplane/api/gen/jsonschema/workflowcontract.v1.CraftingSchema.Material.schema.json +++ b/app/controlplane/api/gen/jsonschema/workflowcontract.v1.CraftingSchema.Material.schema.json @@ -59,7 +59,8 @@ "GHAS_DEPENDENCY_SCAN", "JACOCO_XML", "SLSA_PROVENANCE", - "CHAINLOOP_RUNNER_CONTEXT" + "CHAINLOOP_RUNNER_CONTEXT", + "CHAINLOOP_PR_INFO" ], "title": "Material Type", "type": "string" diff --git a/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicyGroup.Material.jsonschema.json b/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicyGroup.Material.jsonschema.json index bcb597275..165bf7a7e 100644 --- a/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicyGroup.Material.jsonschema.json +++ b/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicyGroup.Material.jsonschema.json @@ -47,7 +47,8 @@ "GHAS_DEPENDENCY_SCAN", "JACOCO_XML", "SLSA_PROVENANCE", - "CHAINLOOP_RUNNER_CONTEXT" + "CHAINLOOP_RUNNER_CONTEXT", + "CHAINLOOP_PR_INFO" ], "title": "Material Type", "type": "string" diff --git a/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicyGroup.Material.schema.json b/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicyGroup.Material.schema.json index 919a4d3e7..f5c8e9046 100644 --- a/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicyGroup.Material.schema.json +++ b/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicyGroup.Material.schema.json @@ -47,7 +47,8 @@ "GHAS_DEPENDENCY_SCAN", "JACOCO_XML", "SLSA_PROVENANCE", - "CHAINLOOP_RUNNER_CONTEXT" + "CHAINLOOP_RUNNER_CONTEXT", + "CHAINLOOP_PR_INFO" ], "title": "Material Type", "type": "string" diff --git a/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicySpec.jsonschema.json b/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicySpec.jsonschema.json index 366110fef..0dcb1b02d 100644 --- a/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicySpec.jsonschema.json +++ b/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicySpec.jsonschema.json @@ -61,7 +61,8 @@ "GHAS_DEPENDENCY_SCAN", "JACOCO_XML", "SLSA_PROVENANCE", - "CHAINLOOP_RUNNER_CONTEXT" + "CHAINLOOP_RUNNER_CONTEXT", + "CHAINLOOP_PR_INFO" ], "title": "Material Type", "type": "string" diff --git a/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicySpec.schema.json b/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicySpec.schema.json index 3e4d82706..44f129907 100644 --- a/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicySpec.schema.json +++ b/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicySpec.schema.json @@ -61,7 +61,8 @@ "GHAS_DEPENDENCY_SCAN", "JACOCO_XML", "SLSA_PROVENANCE", - "CHAINLOOP_RUNNER_CONTEXT" + "CHAINLOOP_RUNNER_CONTEXT", + "CHAINLOOP_PR_INFO" ], "title": "Material Type", "type": "string" diff --git a/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicySpecV2.jsonschema.json b/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicySpecV2.jsonschema.json index 3274b96e7..462938725 100644 --- a/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicySpecV2.jsonschema.json +++ b/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicySpecV2.jsonschema.json @@ -36,7 +36,8 @@ "GHAS_DEPENDENCY_SCAN", "JACOCO_XML", "SLSA_PROVENANCE", - "CHAINLOOP_RUNNER_CONTEXT" + "CHAINLOOP_RUNNER_CONTEXT", + "CHAINLOOP_PR_INFO" ], "title": "Material Type", "type": "string" diff --git a/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicySpecV2.schema.json b/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicySpecV2.schema.json index 8c44566e4..e6e5f9af8 100644 --- a/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicySpecV2.schema.json +++ b/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicySpecV2.schema.json @@ -36,7 +36,8 @@ "GHAS_DEPENDENCY_SCAN", "JACOCO_XML", "SLSA_PROVENANCE", - "CHAINLOOP_RUNNER_CONTEXT" + "CHAINLOOP_RUNNER_CONTEXT", + "CHAINLOOP_PR_INFO" ], "title": "Material Type", "type": "string" diff --git a/app/controlplane/api/workflowcontract/v1/crafting_schema.pb.go b/app/controlplane/api/workflowcontract/v1/crafting_schema.pb.go index 9cf508e16..9086ff27e 100644 --- a/app/controlplane/api/workflowcontract/v1/crafting_schema.pb.go +++ b/app/controlplane/api/workflowcontract/v1/crafting_schema.pb.go @@ -147,6 +147,8 @@ const ( CraftingSchema_Material_SLSA_PROVENANCE CraftingSchema_Material_MaterialType = 24 // The Chainloop CLI plugin for runner context CraftingSchema_Material_CHAINLOOP_RUNNER_CONTEXT CraftingSchema_Material_MaterialType = 25 + // Pull Request / Merge Request metadata collected automatically during attestation + CraftingSchema_Material_CHAINLOOP_PR_INFO CraftingSchema_Material_MaterialType = 26 ) // Enum value maps for CraftingSchema_Material_MaterialType. @@ -178,6 +180,7 @@ var ( 23: "JACOCO_XML", 24: "SLSA_PROVENANCE", 25: "CHAINLOOP_RUNNER_CONTEXT", + 26: "CHAINLOOP_PR_INFO", } CraftingSchema_Material_MaterialType_value = map[string]int32{ "MATERIAL_TYPE_UNSPECIFIED": 0, @@ -206,6 +209,7 @@ var ( "JACOCO_XML": 23, "SLSA_PROVENANCE": 24, "CHAINLOOP_RUNNER_CONTEXT": 25, + "CHAINLOOP_PR_INFO": 26, } ) @@ -1812,7 +1816,7 @@ var file_workflowcontract_v1_crafting_schema_proto_rawDesc = []byte{ 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2e, 0x76, 0x31, 0x1a, 0x1b, 0x62, 0x75, 0x66, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2f, 0x76, - 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xed, 0x0d, + 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x84, 0x0e, 0x0a, 0x0e, 0x43, 0x72, 0x61, 0x66, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, 0x32, 0x0a, 0x0e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0b, 0xba, 0x48, 0x06, 0x72, 0x04, 0x0a, @@ -1861,7 +1865,7 @@ var file_workflowcontract_v1_crafting_schema_proto_rawDesc = []byte{ 0x44, 0x10, 0x05, 0x12, 0x13, 0x0a, 0x0f, 0x44, 0x41, 0x47, 0x47, 0x45, 0x52, 0x5f, 0x50, 0x49, 0x50, 0x45, 0x4c, 0x49, 0x4e, 0x45, 0x10, 0x06, 0x12, 0x15, 0x0a, 0x11, 0x54, 0x45, 0x41, 0x4d, 0x43, 0x49, 0x54, 0x59, 0x5f, 0x50, 0x49, 0x50, 0x45, 0x4c, 0x49, 0x4e, 0x45, 0x10, 0x07, 0x3a, - 0x02, 0x18, 0x01, 0x1a, 0xe3, 0x07, 0x0a, 0x08, 0x4d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, + 0x02, 0x18, 0x01, 0x1a, 0xfa, 0x07, 0x0a, 0x08, 0x4d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x12, 0x5c, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x39, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x61, 0x66, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x63, 0x68, @@ -1887,7 +1891,7 @@ var file_workflowcontract_v1_crafting_schema_proto_rawDesc = []byte{ 0x31, 0x2e, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0b, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x0a, 0x73, 0x6b, 0x69, 0x70, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0xb4, + 0x28, 0x08, 0x52, 0x0a, 0x73, 0x6b, 0x69, 0x70, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0xcb, 0x04, 0x0a, 0x0c, 0x4d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x19, 0x4d, 0x41, 0x54, 0x45, 0x52, 0x49, 0x41, 0x4c, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0a, @@ -1923,273 +1927,274 @@ var file_workflowcontract_v1_crafting_schema_proto_rawDesc = []byte{ 0x10, 0x17, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x4c, 0x53, 0x41, 0x5f, 0x50, 0x52, 0x4f, 0x56, 0x45, 0x4e, 0x41, 0x4e, 0x43, 0x45, 0x10, 0x18, 0x12, 0x1c, 0x0a, 0x18, 0x43, 0x48, 0x41, 0x49, 0x4e, 0x4c, 0x4f, 0x4f, 0x50, 0x5f, 0x52, 0x55, 0x4e, 0x4e, 0x45, 0x52, 0x5f, 0x43, 0x4f, 0x4e, 0x54, - 0x45, 0x58, 0x54, 0x10, 0x19, 0x3a, 0x02, 0x18, 0x01, 0x3a, 0x02, 0x18, 0x01, 0x22, 0xfb, 0x01, - 0x0a, 0x10, 0x43, 0x72, 0x61, 0x66, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, - 0x56, 0x32, 0x12, 0x38, 0x0a, 0x0b, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x17, 0xba, 0x48, 0x14, 0x72, 0x12, 0x0a, 0x10, - 0x63, 0x68, 0x61, 0x69, 0x6e, 0x6c, 0x6f, 0x6f, 0x70, 0x2e, 0x64, 0x65, 0x76, 0x2f, 0x76, 0x31, - 0x52, 0x0a, 0x61, 0x70, 0x69, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x04, - 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0f, 0xba, 0x48, 0x0c, 0x72, - 0x0a, 0x0a, 0x08, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x52, 0x04, 0x6b, 0x69, 0x6e, - 0x64, 0x12, 0x41, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, - 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0x12, 0x45, 0x0a, 0x04, 0x73, 0x70, 0x65, 0x63, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, 0x6e, - 0x74, 0x72, 0x61, 0x63, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x61, 0x66, 0x74, 0x69, 0x6e, - 0x67, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x56, 0x32, 0x53, 0x70, 0x65, 0x63, 0x42, 0x06, 0xba, - 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x04, 0x73, 0x70, 0x65, 0x63, 0x22, 0xd8, 0x02, 0x0a, 0x14, + 0x45, 0x58, 0x54, 0x10, 0x19, 0x12, 0x15, 0x0a, 0x11, 0x43, 0x48, 0x41, 0x49, 0x4e, 0x4c, 0x4f, + 0x4f, 0x50, 0x5f, 0x50, 0x52, 0x5f, 0x49, 0x4e, 0x46, 0x4f, 0x10, 0x1a, 0x3a, 0x02, 0x18, 0x01, + 0x3a, 0x02, 0x18, 0x01, 0x22, 0xfb, 0x01, 0x0a, 0x10, 0x43, 0x72, 0x61, 0x66, 0x74, 0x69, 0x6e, + 0x67, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x56, 0x32, 0x12, 0x38, 0x0a, 0x0b, 0x61, 0x70, 0x69, + 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x17, + 0xba, 0x48, 0x14, 0x72, 0x12, 0x0a, 0x10, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x6c, 0x6f, 0x6f, 0x70, + 0x2e, 0x64, 0x65, 0x76, 0x2f, 0x76, 0x31, 0x52, 0x0a, 0x61, 0x70, 0x69, 0x56, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x42, 0x0f, 0xba, 0x48, 0x0c, 0x72, 0x0a, 0x0a, 0x08, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x61, + 0x63, 0x74, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x41, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x77, 0x6f, 0x72, + 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2e, 0x76, 0x31, + 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, + 0x01, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x45, 0x0a, 0x04, 0x73, + 0x70, 0x65, 0x63, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x77, 0x6f, 0x72, 0x6b, + 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x61, 0x66, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x56, 0x32, - 0x53, 0x70, 0x65, 0x63, 0x12, 0x4a, 0x0a, 0x09, 0x6d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, - 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, - 0x6f, 0x77, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, - 0x61, 0x66, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4d, 0x61, 0x74, - 0x65, 0x72, 0x69, 0x61, 0x6c, 0x52, 0x09, 0x6d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x73, - 0x12, 0x24, 0x0a, 0x0e, 0x65, 0x6e, 0x76, 0x5f, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x6c, 0x69, - 0x73, 0x74, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x6e, 0x76, 0x41, 0x6c, 0x6c, - 0x6f, 0x77, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x42, 0x0a, 0x06, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, - 0x77, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x61, - 0x66, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x52, 0x75, 0x6e, 0x6e, - 0x65, 0x72, 0x52, 0x06, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x12, 0x39, 0x0a, 0x08, 0x70, 0x6f, - 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x77, - 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2e, - 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x08, 0x70, 0x6f, 0x6c, - 0x69, 0x63, 0x69, 0x65, 0x73, 0x12, 0x4f, 0x0a, 0x0d, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, - 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x77, - 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2e, - 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x41, 0x74, - 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x0c, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, - 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x22, 0x46, 0x0a, 0x0a, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x22, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x42, 0x0e, 0xba, 0x48, 0x0b, 0x72, 0x09, 0x32, 0x07, 0x5e, 0x5b, 0x5c, 0x77, 0x5d, - 0x2b, 0x24, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x98, - 0x01, 0x0a, 0x08, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x12, 0x43, 0x0a, 0x09, 0x6d, - 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, + 0x53, 0x70, 0x65, 0x63, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x04, 0x73, 0x70, + 0x65, 0x63, 0x22, 0xd8, 0x02, 0x0a, 0x14, 0x43, 0x72, 0x61, 0x66, 0x74, 0x69, 0x6e, 0x67, 0x53, + 0x63, 0x68, 0x65, 0x6d, 0x61, 0x56, 0x32, 0x53, 0x70, 0x65, 0x63, 0x12, 0x4a, 0x0a, 0x09, 0x6d, + 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, - 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x41, 0x74, 0x74, 0x61, 0x63, - 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x09, 0x6d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x73, - 0x12, 0x47, 0x0a, 0x0b, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, - 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, - 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x6c, 0x69, - 0x63, 0x79, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x0b, 0x61, 0x74, - 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xf6, 0x03, 0x0a, 0x10, 0x50, 0x6f, - 0x6c, 0x69, 0x63, 0x79, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1b, - 0x0a, 0x03, 0x72, 0x65, 0x66, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, - 0x72, 0x02, 0x10, 0x01, 0x48, 0x00, 0x52, 0x03, 0x72, 0x65, 0x66, 0x12, 0x39, 0x0a, 0x08, 0x65, - 0x6d, 0x62, 0x65, 0x64, 0x64, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, + 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x61, 0x66, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x63, 0x68, + 0x65, 0x6d, 0x61, 0x2e, 0x4d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x52, 0x09, 0x6d, 0x61, + 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x65, 0x6e, 0x76, 0x5f, 0x61, + 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x0c, 0x65, 0x6e, 0x76, 0x41, 0x6c, 0x6c, 0x6f, 0x77, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x42, 0x0a, + 0x06, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, - 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x48, 0x00, 0x52, 0x08, 0x65, 0x6d, - 0x62, 0x65, 0x64, 0x64, 0x65, 0x64, 0x12, 0x52, 0x0a, 0x08, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, - 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x36, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x66, - 0x6c, 0x6f, 0x77, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x50, - 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x2e, - 0x4d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, - 0x52, 0x08, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x69, - 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x64, 0x69, - 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x43, 0x0a, 0x04, 0x77, 0x69, 0x74, 0x68, 0x18, 0x05, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, - 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, - 0x79, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x57, 0x69, 0x74, 0x68, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x77, 0x69, 0x74, 0x68, 0x12, 0x63, 0x0a, 0x0c, 0x72, - 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, - 0x09, 0x42, 0x3f, 0xba, 0x48, 0x3c, 0x92, 0x01, 0x39, 0x22, 0x37, 0x72, 0x35, 0x32, 0x33, 0x5e, - 0x28, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x2d, 0x5d, 0x2b, 0x5c, 0x2f, 0x29, 0x3f, 0x28, - 0x5b, 0x5e, 0x5c, 0x73, 0x5c, 0x2f, 0x5d, 0x2b, 0x5c, 0x2f, 0x29, 0x28, 0x5b, 0x5e, 0x5c, 0x73, - 0x40, 0x5c, 0x2f, 0x5d, 0x2b, 0x29, 0x28, 0x40, 0x5b, 0x5e, 0x5c, 0x73, 0x40, 0x5d, 0x2b, 0x29, - 0x3f, 0x24, 0x52, 0x0c, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, - 0x1a, 0x37, 0x0a, 0x09, 0x57, 0x69, 0x74, 0x68, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, - 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, + 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x61, 0x66, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x63, 0x68, 0x65, + 0x6d, 0x61, 0x2e, 0x52, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x52, 0x06, 0x72, 0x75, 0x6e, 0x6e, 0x65, + 0x72, 0x12, 0x39, 0x0a, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, + 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, + 0x65, 0x73, 0x52, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x12, 0x4f, 0x0a, 0x0d, + 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x18, 0x05, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, + 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, + 0x47, 0x72, 0x6f, 0x75, 0x70, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x52, + 0x0c, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x22, 0x46, 0x0a, + 0x0a, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x22, 0x0a, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0e, 0xba, 0x48, 0x0b, 0x72, 0x09, + 0x32, 0x07, 0x5e, 0x5b, 0x5c, 0x77, 0x5d, 0x2b, 0x24, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x26, 0x0a, 0x10, 0x4d, 0x61, 0x74, - 0x65, 0x72, 0x69, 0x61, 0x6c, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x12, 0x0a, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0x42, 0x0f, 0x0a, 0x06, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x05, 0xba, 0x48, 0x02, - 0x08, 0x01, 0x22, 0xf6, 0x01, 0x0a, 0x06, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x49, 0x0a, - 0x0b, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x42, 0x28, 0xba, 0x48, 0x25, 0x72, 0x23, 0x0a, 0x21, 0x77, 0x6f, 0x72, 0x6b, 0x66, - 0x6c, 0x6f, 0x77, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2e, 0x63, 0x68, 0x61, 0x69, - 0x6e, 0x6c, 0x6f, 0x6f, 0x70, 0x2e, 0x64, 0x65, 0x76, 0x2f, 0x76, 0x31, 0x52, 0x0a, 0x61, 0x70, - 0x69, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0d, 0xba, 0x48, 0x0a, 0x72, 0x08, 0x0a, 0x06, 0x50, - 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x41, 0x0a, 0x08, 0x6d, - 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, - 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, - 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x42, 0x06, 0xba, 0x48, - 0x03, 0xc8, 0x01, 0x01, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x3b, - 0x0a, 0x04, 0x73, 0x70, 0x65, 0x63, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x77, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x98, 0x01, 0x0a, 0x08, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, + 0x65, 0x73, 0x12, 0x43, 0x0a, 0x09, 0x6d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, + 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x6c, 0x69, + 0x63, 0x79, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x09, 0x6d, 0x61, + 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x73, 0x12, 0x47, 0x0a, 0x0b, 0x61, 0x74, 0x74, 0x65, 0x73, + 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2e, - 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x53, 0x70, 0x65, 0x63, 0x42, 0x06, 0xba, - 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x04, 0x73, 0x70, 0x65, 0x63, 0x22, 0x92, 0x03, 0x0a, 0x08, - 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x97, 0x01, 0x0a, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x82, 0x01, 0xba, 0x48, 0x7f, 0xba, 0x01, 0x7c, - 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x2e, 0x64, 0x6e, 0x73, 0x2d, 0x31, 0x31, 0x32, 0x33, 0x12, + 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, + 0x65, 0x6e, 0x74, 0x52, 0x0b, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x22, 0xf6, 0x03, 0x0a, 0x10, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x41, 0x74, 0x74, 0x61, 0x63, + 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x03, 0x72, 0x65, 0x66, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, 0x01, 0x48, 0x00, 0x52, 0x03, 0x72, + 0x65, 0x66, 0x12, 0x39, 0x0a, 0x08, 0x65, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x65, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, + 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, + 0x79, 0x48, 0x00, 0x52, 0x08, 0x65, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x65, 0x64, 0x12, 0x52, 0x0a, + 0x08, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x36, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, + 0x63, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x41, 0x74, 0x74, 0x61, + 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x4d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x53, + 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x08, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, + 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x08, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x43, 0x0a, + 0x04, 0x77, 0x69, 0x74, 0x68, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x77, 0x6f, + 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2e, 0x76, + 0x31, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, + 0x6e, 0x74, 0x2e, 0x57, 0x69, 0x74, 0x68, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x77, 0x69, + 0x74, 0x68, 0x12, 0x63, 0x0a, 0x0c, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x6d, 0x65, 0x6e, + 0x74, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x42, 0x3f, 0xba, 0x48, 0x3c, 0x92, 0x01, 0x39, + 0x22, 0x37, 0x72, 0x35, 0x32, 0x33, 0x5e, 0x28, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x2d, + 0x5d, 0x2b, 0x5c, 0x2f, 0x29, 0x3f, 0x28, 0x5b, 0x5e, 0x5c, 0x73, 0x5c, 0x2f, 0x5d, 0x2b, 0x5c, + 0x2f, 0x29, 0x28, 0x5b, 0x5e, 0x5c, 0x73, 0x40, 0x5c, 0x2f, 0x5d, 0x2b, 0x29, 0x28, 0x40, 0x5b, + 0x5e, 0x5c, 0x73, 0x40, 0x5d, 0x2b, 0x29, 0x3f, 0x24, 0x52, 0x0c, 0x72, 0x65, 0x71, 0x75, 0x69, + 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x1a, 0x37, 0x0a, 0x09, 0x57, 0x69, 0x74, 0x68, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, + 0x1a, 0x26, 0x0a, 0x10, 0x4d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x53, 0x65, 0x6c, 0x65, + 0x63, 0x74, 0x6f, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x42, 0x0f, 0x0a, 0x06, 0x70, 0x6f, 0x6c, 0x69, + 0x63, 0x79, 0x12, 0x05, 0xba, 0x48, 0x02, 0x08, 0x01, 0x22, 0xf6, 0x01, 0x0a, 0x06, 0x50, 0x6f, + 0x6c, 0x69, 0x63, 0x79, 0x12, 0x49, 0x0a, 0x0b, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x28, 0xba, 0x48, 0x25, 0x72, 0x23, + 0x0a, 0x21, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, + 0x63, 0x74, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x6c, 0x6f, 0x6f, 0x70, 0x2e, 0x64, 0x65, 0x76, + 0x2f, 0x76, 0x31, 0x52, 0x0a, 0x61, 0x70, 0x69, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, + 0x21, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0d, 0xba, + 0x48, 0x0a, 0x72, 0x08, 0x0a, 0x06, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x04, 0x6b, 0x69, + 0x6e, 0x64, 0x12, 0x41, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, + 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x08, 0x6d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x3b, 0x0a, 0x04, 0x73, 0x70, 0x65, 0x63, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, + 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, + 0x53, 0x70, 0x65, 0x63, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x04, 0x73, 0x70, + 0x65, 0x63, 0x22, 0x92, 0x03, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, + 0x97, 0x01, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x82, + 0x01, 0xba, 0x48, 0x7f, 0xba, 0x01, 0x7c, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x2e, 0x64, 0x6e, + 0x73, 0x2d, 0x31, 0x31, 0x32, 0x33, 0x12, 0x3a, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x63, 0x6f, 0x6e, + 0x74, 0x61, 0x69, 0x6e, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x6c, 0x6f, 0x77, 0x65, 0x72, 0x63, + 0x61, 0x73, 0x65, 0x20, 0x6c, 0x65, 0x74, 0x74, 0x65, 0x72, 0x73, 0x2c, 0x20, 0x6e, 0x75, 0x6d, + 0x62, 0x65, 0x72, 0x73, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x68, 0x79, 0x70, 0x68, 0x65, 0x6e, + 0x73, 0x2e, 0x1a, 0x2f, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x73, + 0x28, 0x27, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x5d, 0x28, 0x5b, 0x2d, 0x61, 0x2d, + 0x7a, 0x30, 0x2d, 0x39, 0x5d, 0x2a, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x5d, 0x29, 0x3f, + 0x24, 0x27, 0x29, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, + 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x50, 0x0a, 0x0b, 0x61, + 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x2e, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, 0x6e, 0x74, 0x72, + 0x61, 0x63, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, + 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x52, 0x0b, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x27, 0x0a, + 0x0c, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0c, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x1a, 0x3e, 0x0a, 0x10, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x0f, 0x0a, 0x0d, 0x5f, 0x6f, 0x72, 0x67, 0x61, 0x6e, + 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xf7, 0x03, 0x0a, 0x0a, 0x50, 0x6f, 0x6c, 0x69, + 0x63, 0x79, 0x53, 0x70, 0x65, 0x63, 0x12, 0x18, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x48, 0x00, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, + 0x12, 0x20, 0x0a, 0x08, 0x65, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x48, 0x00, 0x52, 0x08, 0x65, 0x6d, 0x62, 0x65, 0x64, 0x64, + 0x65, 0x64, 0x12, 0x5c, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x39, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, 0x6e, 0x74, 0x72, + 0x61, 0x63, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x61, 0x66, 0x74, 0x69, 0x6e, 0x67, 0x53, + 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e, 0x4d, + 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x42, 0x0d, 0xba, 0x48, 0x08, + 0x82, 0x01, 0x05, 0x22, 0x03, 0x01, 0x03, 0x0b, 0x18, 0x01, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, + 0x12, 0x3d, 0x0a, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, 0x6e, + 0x74, 0x72, 0x61, 0x63, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x53, + 0x70, 0x65, 0x63, 0x56, 0x32, 0x52, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x12, + 0x38, 0x0a, 0x06, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x20, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, + 0x63, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x49, 0x6e, 0x70, 0x75, + 0x74, 0x52, 0x06, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x12, 0x3d, 0x0a, 0x0a, 0x61, 0x75, 0x74, + 0x6f, 0x5f, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, + 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, + 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x6f, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x52, 0x09, 0x61, + 0x75, 0x74, 0x6f, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x3a, 0x8c, 0x01, 0xba, 0x48, 0x88, 0x01, 0x1a, + 0x85, 0x01, 0x0a, 0x0a, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x73, 0x70, 0x65, 0x63, 0x12, 0x36, + 0x65, 0x69, 0x74, 0x68, 0x65, 0x72, 0x20, 0x73, 0x70, 0x65, 0x63, 0x20, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x20, 0x6f, 0x72, 0x20, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x20, 0x66, + 0x69, 0x65, 0x6c, 0x64, 0x73, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x62, 0x65, 0x20, 0x70, 0x72, + 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x1a, 0x3f, 0x68, 0x61, 0x73, 0x28, 0x74, 0x68, 0x69, 0x73, + 0x2e, 0x70, 0x61, 0x74, 0x68, 0x29, 0x20, 0x7c, 0x7c, 0x20, 0x68, 0x61, 0x73, 0x28, 0x74, 0x68, + 0x69, 0x73, 0x2e, 0x65, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x65, 0x64, 0x29, 0x20, 0x7c, 0x7c, 0x20, + 0x73, 0x69, 0x7a, 0x65, 0x28, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, + 0x65, 0x73, 0x29, 0x20, 0x3e, 0x20, 0x30, 0x42, 0x08, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x22, 0xfe, 0x01, 0x0a, 0x0b, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x49, 0x6e, 0x70, 0x75, + 0x74, 0x12, 0x96, 0x01, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x42, 0x81, 0x01, 0xba, 0x48, 0x7e, 0xba, 0x01, 0x7b, 0x0a, 0x14, 0x6e, 0x61, 0x6d, 0x65, 0x2e, + 0x67, 0x6f, 0x5f, 0x6d, 0x61, 0x70, 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x3a, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x6c, 0x6f, 0x77, 0x65, 0x72, 0x63, 0x61, 0x73, 0x65, 0x20, 0x6c, 0x65, 0x74, 0x74, 0x65, 0x72, 0x73, 0x2c, 0x20, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x2c, 0x20, 0x61, - 0x6e, 0x64, 0x20, 0x68, 0x79, 0x70, 0x68, 0x65, 0x6e, 0x73, 0x2e, 0x1a, 0x2f, 0x74, 0x68, 0x69, + 0x6e, 0x64, 0x20, 0x68, 0x79, 0x70, 0x68, 0x65, 0x6e, 0x73, 0x2e, 0x1a, 0x27, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x73, 0x28, 0x27, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, - 0x30, 0x2d, 0x39, 0x5d, 0x28, 0x5b, 0x2d, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x5d, 0x2a, 0x5b, - 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x5d, 0x29, 0x3f, 0x24, 0x27, 0x29, 0x52, 0x04, 0x6e, 0x61, - 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, - 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, - 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x50, 0x0a, 0x0b, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x77, 0x6f, 0x72, 0x6b, - 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2e, 0x76, 0x31, 0x2e, - 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0b, 0x61, 0x6e, 0x6e, 0x6f, 0x74, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x27, 0x0a, 0x0c, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, - 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0c, - 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x1a, - 0x3e, 0x0a, 0x10, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, - 0x0f, 0x0a, 0x0d, 0x5f, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x22, 0xf7, 0x03, 0x0a, 0x0a, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x53, 0x70, 0x65, 0x63, 0x12, - 0x18, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x02, 0x18, - 0x01, 0x48, 0x00, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x20, 0x0a, 0x08, 0x65, 0x6d, 0x62, - 0x65, 0x64, 0x64, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x48, - 0x00, 0x52, 0x08, 0x65, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x65, 0x64, 0x12, 0x5c, 0x0a, 0x04, 0x74, - 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x39, 0x2e, 0x77, 0x6f, 0x72, 0x6b, + 0x41, 0x2d, 0x5a, 0x5d, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x5f, 0x5d, + 0x2a, 0x24, 0x27, 0x29, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, + 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, + 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x66, 0x61, + 0x75, 0x6c, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x64, 0x65, 0x66, 0x61, 0x75, + 0x6c, 0x74, 0x22, 0xad, 0x01, 0x0a, 0x0c, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x53, 0x70, 0x65, + 0x63, 0x56, 0x32, 0x12, 0x14, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x48, 0x00, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x1c, 0x0a, 0x08, 0x65, 0x6d, 0x62, + 0x65, 0x64, 0x64, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x08, 0x65, + 0x6d, 0x62, 0x65, 0x64, 0x64, 0x65, 0x64, 0x12, 0x58, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x39, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, + 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x61, 0x66, + 0x74, 0x69, 0x6e, 0x67, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4d, 0x61, 0x74, 0x65, 0x72, + 0x69, 0x61, 0x6c, 0x2e, 0x4d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x54, 0x79, 0x70, 0x65, + 0x42, 0x09, 0xba, 0x48, 0x06, 0x82, 0x01, 0x03, 0x22, 0x01, 0x03, 0x52, 0x04, 0x6b, 0x69, 0x6e, + 0x64, 0x42, 0x0f, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x05, 0xba, 0x48, 0x02, + 0x08, 0x01, 0x22, 0x50, 0x0a, 0x09, 0x41, 0x75, 0x74, 0x6f, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x12, + 0x14, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, + 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x1c, 0x0a, 0x08, 0x65, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x65, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x08, 0x65, 0x6d, 0x62, 0x65, 0x64, + 0x64, 0x65, 0x64, 0x42, 0x0f, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x05, 0xba, + 0x48, 0x02, 0x08, 0x01, 0x22, 0xc9, 0x01, 0x0a, 0x15, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x47, + 0x72, 0x6f, 0x75, 0x70, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x19, + 0x0a, 0x03, 0x72, 0x65, 0x66, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, + 0x72, 0x02, 0x10, 0x01, 0x52, 0x03, 0x72, 0x65, 0x66, 0x12, 0x48, 0x0a, 0x04, 0x77, 0x69, 0x74, + 0x68, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, + 0x6f, 0x77, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, + 0x6c, 0x69, 0x63, 0x79, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, + 0x65, 0x6e, 0x74, 0x2e, 0x57, 0x69, 0x74, 0x68, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x77, + 0x69, 0x74, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x6b, 0x69, 0x70, 0x18, 0x03, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x04, 0x73, 0x6b, 0x69, 0x70, 0x1a, 0x37, 0x0a, 0x09, 0x57, 0x69, 0x74, 0x68, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, + 0x22, 0xb5, 0x07, 0x0a, 0x0b, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x47, 0x72, 0x6f, 0x75, 0x70, + 0x12, 0x49, 0x0a, 0x0b, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x28, 0xba, 0x48, 0x25, 0x72, 0x23, 0x0a, 0x21, 0x77, 0x6f, + 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2e, 0x63, + 0x68, 0x61, 0x69, 0x6e, 0x6c, 0x6f, 0x6f, 0x70, 0x2e, 0x64, 0x65, 0x76, 0x2f, 0x76, 0x31, 0x52, + 0x0a, 0x61, 0x70, 0x69, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x26, 0x0a, 0x04, 0x6b, + 0x69, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x12, 0xba, 0x48, 0x0f, 0x72, 0x0d, + 0x0a, 0x0b, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x04, 0x6b, + 0x69, 0x6e, 0x64, 0x12, 0x41, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, + 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x08, 0x6d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x4c, 0x0a, 0x04, 0x73, 0x70, 0x65, 0x63, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, + 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, + 0x79, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x47, 0x72, 0x6f, + 0x75, 0x70, 0x53, 0x70, 0x65, 0x63, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x04, + 0x73, 0x70, 0x65, 0x63, 0x1a, 0x9d, 0x01, 0x0a, 0x0f, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x47, + 0x72, 0x6f, 0x75, 0x70, 0x53, 0x70, 0x65, 0x63, 0x12, 0x50, 0x0a, 0x08, 0x70, 0x6f, 0x6c, 0x69, + 0x63, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x77, 0x6f, 0x72, + 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2e, 0x76, 0x31, + 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x2e, 0x50, 0x6f, 0x6c, + 0x69, 0x63, 0x79, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, + 0x52, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x12, 0x38, 0x0a, 0x06, 0x69, 0x6e, + 0x70, 0x75, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x77, 0x6f, 0x72, + 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2e, 0x76, 0x31, + 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x52, 0x06, 0x69, 0x6e, + 0x70, 0x75, 0x74, 0x73, 0x1a, 0xa7, 0x01, 0x0a, 0x13, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x47, + 0x72, 0x6f, 0x75, 0x70, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x12, 0x47, 0x0a, 0x09, + 0x6d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x29, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, + 0x63, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x47, 0x72, 0x6f, 0x75, + 0x70, 0x2e, 0x4d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x52, 0x09, 0x6d, 0x61, 0x74, 0x65, + 0x72, 0x69, 0x61, 0x6c, 0x73, 0x12, 0x47, 0x0a, 0x0b, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x77, 0x6f, 0x72, + 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2e, 0x76, 0x31, + 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, + 0x74, 0x52, 0x0b, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0xd7, + 0x02, 0x0a, 0x08, 0x4d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x12, 0x57, 0x0a, 0x04, 0x74, + 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x39, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x61, 0x66, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e, 0x4d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, - 0x54, 0x79, 0x70, 0x65, 0x42, 0x0d, 0xba, 0x48, 0x08, 0x82, 0x01, 0x05, 0x22, 0x03, 0x01, 0x03, - 0x0b, 0x18, 0x01, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x3d, 0x0a, 0x08, 0x70, 0x6f, 0x6c, - 0x69, 0x63, 0x69, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x77, 0x6f, - 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2e, 0x76, - 0x31, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x53, 0x70, 0x65, 0x63, 0x56, 0x32, 0x52, 0x08, - 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x12, 0x38, 0x0a, 0x06, 0x69, 0x6e, 0x70, 0x75, - 0x74, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x66, - 0x6c, 0x6f, 0x77, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x50, - 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x52, 0x06, 0x69, 0x6e, 0x70, 0x75, - 0x74, 0x73, 0x12, 0x3d, 0x0a, 0x0a, 0x61, 0x75, 0x74, 0x6f, 0x5f, 0x6d, 0x61, 0x74, 0x63, 0x68, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, - 0x77, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, - 0x6f, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x52, 0x09, 0x61, 0x75, 0x74, 0x6f, 0x4d, 0x61, 0x74, 0x63, - 0x68, 0x3a, 0x8c, 0x01, 0xba, 0x48, 0x88, 0x01, 0x1a, 0x85, 0x01, 0x0a, 0x0a, 0x70, 0x6f, 0x6c, - 0x69, 0x63, 0x79, 0x73, 0x70, 0x65, 0x63, 0x12, 0x36, 0x65, 0x69, 0x74, 0x68, 0x65, 0x72, 0x20, - 0x73, 0x70, 0x65, 0x63, 0x20, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x20, 0x6f, 0x72, 0x20, 0x70, - 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x20, 0x6d, - 0x75, 0x73, 0x74, 0x20, 0x62, 0x65, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x1a, - 0x3f, 0x68, 0x61, 0x73, 0x28, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x29, 0x20, - 0x7c, 0x7c, 0x20, 0x68, 0x61, 0x73, 0x28, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x65, 0x6d, 0x62, 0x65, - 0x64, 0x64, 0x65, 0x64, 0x29, 0x20, 0x7c, 0x7c, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x28, 0x74, 0x68, - 0x69, 0x73, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x29, 0x20, 0x3e, 0x20, 0x30, - 0x42, 0x08, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, 0xfe, 0x01, 0x0a, 0x0b, 0x50, - 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x12, 0x96, 0x01, 0x0a, 0x04, 0x6e, - 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x81, 0x01, 0xba, 0x48, 0x7e, 0xba, - 0x01, 0x7b, 0x0a, 0x14, 0x6e, 0x61, 0x6d, 0x65, 0x2e, 0x67, 0x6f, 0x5f, 0x6d, 0x61, 0x70, 0x5f, - 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x3a, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x63, - 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x6c, 0x6f, 0x77, 0x65, - 0x72, 0x63, 0x61, 0x73, 0x65, 0x20, 0x6c, 0x65, 0x74, 0x74, 0x65, 0x72, 0x73, 0x2c, 0x20, 0x6e, - 0x75, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x68, 0x79, 0x70, 0x68, - 0x65, 0x6e, 0x73, 0x2e, 0x1a, 0x27, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6d, 0x61, 0x74, 0x63, 0x68, - 0x65, 0x73, 0x28, 0x27, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x5d, 0x5b, 0x61, 0x2d, - 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x5f, 0x5d, 0x2a, 0x24, 0x27, 0x29, 0x52, 0x04, 0x6e, - 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, - 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, - 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, - 0x64, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x22, 0xad, 0x01, 0x0a, 0x0c, - 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x53, 0x70, 0x65, 0x63, 0x56, 0x32, 0x12, 0x14, 0x0a, 0x04, - 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x04, 0x70, 0x61, - 0x74, 0x68, 0x12, 0x1c, 0x0a, 0x08, 0x65, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x65, 0x64, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x08, 0x65, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x65, 0x64, - 0x12, 0x58, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x39, - 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, - 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x61, 0x66, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x63, 0x68, - 0x65, 0x6d, 0x61, 0x2e, 0x4d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e, 0x4d, 0x61, 0x74, - 0x65, 0x72, 0x69, 0x61, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x42, 0x09, 0xba, 0x48, 0x06, 0x82, 0x01, - 0x03, 0x22, 0x01, 0x03, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x42, 0x0f, 0x0a, 0x06, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x12, 0x05, 0xba, 0x48, 0x02, 0x08, 0x01, 0x22, 0x50, 0x0a, 0x09, 0x41, - 0x75, 0x74, 0x6f, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x12, 0x14, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x1c, - 0x0a, 0x08, 0x65, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x48, 0x00, 0x52, 0x08, 0x65, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x65, 0x64, 0x42, 0x0f, 0x0a, 0x06, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x05, 0xba, 0x48, 0x02, 0x08, 0x01, 0x22, 0xc9, 0x01, - 0x0a, 0x15, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x41, 0x74, 0x74, - 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x03, 0x72, 0x65, 0x66, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x03, 0x72, - 0x65, 0x66, 0x12, 0x48, 0x0a, 0x04, 0x77, 0x69, 0x74, 0x68, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x34, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, 0x6e, 0x74, 0x72, - 0x61, 0x63, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x47, 0x72, 0x6f, - 0x75, 0x70, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x57, 0x69, 0x74, - 0x68, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x77, 0x69, 0x74, 0x68, 0x12, 0x12, 0x0a, 0x04, - 0x73, 0x6b, 0x69, 0x70, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x73, 0x6b, 0x69, 0x70, - 0x1a, 0x37, 0x0a, 0x09, 0x57, 0x69, 0x74, 0x68, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, - 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, - 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xb5, 0x07, 0x0a, 0x0b, 0x50, 0x6f, - 0x6c, 0x69, 0x63, 0x79, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x49, 0x0a, 0x0b, 0x61, 0x70, 0x69, - 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x28, - 0xba, 0x48, 0x25, 0x72, 0x23, 0x0a, 0x21, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, - 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x6c, 0x6f, 0x6f, - 0x70, 0x2e, 0x64, 0x65, 0x76, 0x2f, 0x76, 0x31, 0x52, 0x0a, 0x61, 0x70, 0x69, 0x56, 0x65, 0x72, - 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x26, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x42, 0x12, 0xba, 0x48, 0x0f, 0x72, 0x0d, 0x0a, 0x0b, 0x50, 0x6f, 0x6c, 0x69, 0x63, - 0x79, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x41, 0x0a, 0x08, - 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, - 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, - 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x42, 0x06, 0xba, - 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, - 0x4c, 0x0a, 0x04, 0x73, 0x70, 0x65, 0x63, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, - 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, - 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x2e, - 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x53, 0x70, 0x65, 0x63, 0x42, - 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x04, 0x73, 0x70, 0x65, 0x63, 0x1a, 0x9d, 0x01, - 0x0a, 0x0f, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x53, 0x70, 0x65, - 0x63, 0x12, 0x50, 0x0a, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, - 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, - 0x47, 0x72, 0x6f, 0x75, 0x70, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x47, 0x72, 0x6f, 0x75, - 0x70, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, - 0x69, 0x65, 0x73, 0x12, 0x38, 0x0a, 0x06, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x18, 0x02, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, - 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, - 0x49, 0x6e, 0x70, 0x75, 0x74, 0x52, 0x06, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x1a, 0xa7, 0x01, - 0x0a, 0x13, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x50, 0x6f, 0x6c, - 0x69, 0x63, 0x69, 0x65, 0x73, 0x12, 0x47, 0x0a, 0x09, 0x6d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, - 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x66, - 0x6c, 0x6f, 0x77, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x50, - 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x2e, 0x4d, 0x61, 0x74, 0x65, 0x72, - 0x69, 0x61, 0x6c, 0x52, 0x09, 0x6d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x73, 0x12, 0x47, - 0x0a, 0x0b, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, - 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, - 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x0b, 0x61, 0x74, 0x74, 0x65, - 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0xd7, 0x02, 0x0a, 0x08, 0x4d, 0x61, 0x74, 0x65, - 0x72, 0x69, 0x61, 0x6c, 0x12, 0x57, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0e, 0x32, 0x39, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, 0x6e, - 0x74, 0x72, 0x61, 0x63, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x61, 0x66, 0x74, 0x69, 0x6e, - 0x67, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, - 0x2e, 0x4d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x42, 0x08, 0xba, - 0x48, 0x05, 0x82, 0x01, 0x02, 0x10, 0x01, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x08, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x12, 0x41, 0x0a, - 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x25, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, - 0x63, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x41, 0x74, 0x74, 0x61, - 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, - 0x3a, 0x7f, 0xba, 0x48, 0x7c, 0x1a, 0x7a, 0x0a, 0x0e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6d, - 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x12, 0x33, 0x69, 0x66, 0x20, 0x6e, 0x61, 0x6d, 0x65, - 0x20, 0x69, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x2c, 0x20, 0x74, 0x79, - 0x70, 0x65, 0x20, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, 0x61, - 0x20, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x33, 0x21, 0x68, - 0x61, 0x73, 0x28, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6e, 0x61, 0x6d, 0x65, 0x29, 0x20, 0x7c, 0x7c, - 0x20, 0x68, 0x61, 0x73, 0x28, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6e, 0x61, 0x6d, 0x65, 0x29, 0x20, - 0x26, 0x26, 0x20, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x20, 0x21, 0x3d, 0x20, - 0x30, 0x42, 0x4d, 0x5a, 0x4b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x63, 0x68, 0x61, 0x69, 0x6e, 0x6c, 0x6f, 0x6f, 0x70, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x63, 0x68, - 0x61, 0x69, 0x6e, 0x6c, 0x6f, 0x6f, 0x70, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x63, 0x6f, 0x6e, 0x74, - 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x77, 0x6f, 0x72, - 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2f, 0x76, 0x31, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x54, 0x79, 0x70, 0x65, 0x42, 0x08, 0xba, 0x48, 0x05, 0x82, 0x01, 0x02, 0x10, 0x01, 0x52, 0x04, + 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6f, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6f, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x61, 0x6c, 0x12, 0x41, 0x0a, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, + 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, + 0x77, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x6c, + 0x69, 0x63, 0x79, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x08, 0x70, + 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x3a, 0x7f, 0xba, 0x48, 0x7c, 0x1a, 0x7a, 0x0a, 0x0e, + 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x12, 0x33, + 0x69, 0x66, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x69, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, + 0x64, 0x65, 0x64, 0x2c, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, + 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, 0x61, 0x20, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x20, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x1a, 0x33, 0x21, 0x68, 0x61, 0x73, 0x28, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6e, + 0x61, 0x6d, 0x65, 0x29, 0x20, 0x7c, 0x7c, 0x20, 0x68, 0x61, 0x73, 0x28, 0x74, 0x68, 0x69, 0x73, + 0x2e, 0x6e, 0x61, 0x6d, 0x65, 0x29, 0x20, 0x26, 0x26, 0x20, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x74, + 0x79, 0x70, 0x65, 0x20, 0x21, 0x3d, 0x20, 0x30, 0x42, 0x4d, 0x5a, 0x4b, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x6c, 0x6f, 0x6f, 0x70, + 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x6c, 0x6f, 0x6f, 0x70, 0x2f, 0x61, + 0x70, 0x70, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2f, + 0x61, 0x70, 0x69, 0x2f, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, 0x6e, 0x74, + 0x72, 0x61, 0x63, 0x74, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/app/controlplane/api/workflowcontract/v1/crafting_schema.proto b/app/controlplane/api/workflowcontract/v1/crafting_schema.proto index f42b7f12b..e252cbc94 100644 --- a/app/controlplane/api/workflowcontract/v1/crafting_schema.proto +++ b/app/controlplane/api/workflowcontract/v1/crafting_schema.proto @@ -144,6 +144,8 @@ message CraftingSchema { SLSA_PROVENANCE = 24; // The Chainloop CLI plugin for runner context CHAINLOOP_RUNNER_CONTEXT = 25; + // Pull Request / Merge Request metadata collected automatically during attestation + CHAINLOOP_PR_INFO = 26; } } } diff --git a/internal/prinfo/generator.go b/internal/prinfo/generator.go new file mode 100644 index 000000000..713a04117 --- /dev/null +++ b/internal/prinfo/generator.go @@ -0,0 +1,69 @@ +// +// Copyright 2025 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 prinfo + +import ( + "encoding/json" + "fmt" + "os" + + "github.com/invopop/jsonschema" +) + +// Generator handles the generation of JSON schemas for PR info. +type Generator struct { +} + +// NewGenerator creates a new schema generator +func NewGenerator() *Generator { + return &Generator{} +} + +// GeneratePRInfoSchema generates a JSON schema for the PR/MR info data. +func (g *Generator) GeneratePRInfoSchema(version string) *jsonschema.Schema { + r := &jsonschema.Reflector{ + DoNotReference: true, + ExpandedStruct: true, + RequiredFromJSONSchemaTags: true, + // Set to false to allow additional properties by default + AllowAdditionalProperties: false, + } + + schema := r.Reflect(&Data{}) + + schema.ID = jsonschema.ID(fmt.Sprintf("https://schemas.chainloop.dev/prinfo/%s/pr-info.schema.json", version)) + schema.Title = "Pull Request / Merge Request Information" + schema.Description = "Schema for Pull Request or Merge Request metadata collected during attestation" + // we want to have a specific version of the schema to avoid compatibility issues + schema.Version = "http://json-schema.org/draft-07/schema#" + + return schema +} + +// Save writes the schema to a file +func (g *Generator) Save(schema *jsonschema.Schema, outputDir, version string) error { + schemaJSON, err := json.MarshalIndent(schema, "", " ") + if err != nil { + return fmt.Errorf("failed to marshal schema to JSON: %w", err) + } + + outputFile := fmt.Sprintf("%s/pr-info-%s.schema.json", outputDir, version) + if err := os.WriteFile(outputFile, schemaJSON, 0600); err != nil { + return fmt.Errorf("failed to write schema to file: %w", err) + } + + return nil +} diff --git a/internal/prinfo/prinfo.go b/internal/prinfo/prinfo.go new file mode 100644 index 000000000..64bf29142 --- /dev/null +++ b/internal/prinfo/prinfo.go @@ -0,0 +1,52 @@ +// +// Copyright 2025 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 prinfo + +const ( + // EvidenceID is the identifier for the PR/MR info material type + EvidenceID = "CHAINLOOP_PR_INFO" + // EvidenceSchemaURL is the URL to the JSON schema for PR/MR info + EvidenceSchemaURL = "https://schemas.chainloop.dev/prinfo/1.0/pr-info.schema.json" +) + +// Data represents the data payload of the PR/MR info evidence +type Data struct { + Platform string `json:"platform" jsonschema:"required,enum=github,enum=gitlab,description=The CI/CD platform"` + Type string `json:"type" jsonschema:"required,enum=pull_request,enum=merge_request,description=The type of change request"` + Number string `json:"number" jsonschema:"required,description=The PR/MR number or identifier"` + Title string `json:"title" jsonschema:"description=The PR/MR title"` + Description string `json:"description" jsonschema:"description=The PR/MR description or body"` + SourceBranch string `json:"source_branch" jsonschema:"description=The source branch name"` + TargetBranch string `json:"target_branch" jsonschema:"description=The target branch name"` + URL string `json:"url" jsonschema:"required,format=uri,description=Direct URL to the PR/MR"` + Author string `json:"author" jsonschema:"description=Username of the PR/MR author"` +} + +// Evidence represents the complete evidence structure for PR/MR metadata +type Evidence struct { + ID string `json:"chainloop.material.evidence.id"` + Schema string `json:"schema"` + Data Data `json:"data"` +} + +// NewEvidence creates a new Evidence instance +func NewEvidence(data Data) *Evidence { + return &Evidence{ + ID: EvidenceID, + Schema: EvidenceSchemaURL, + Data: data, + } +} diff --git a/internal/prinfo/prinfo_test.go b/internal/prinfo/prinfo_test.go new file mode 100644 index 000000000..e0a309ebb --- /dev/null +++ b/internal/prinfo/prinfo_test.go @@ -0,0 +1,135 @@ +// +// Copyright 2025 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 prinfo + +import ( + "encoding/json" + "testing" + + "github.com/chainloop-dev/chainloop/internal/schemavalidators" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestValidatePRInfo(t *testing.T) { + testCases := []struct { + name string + data string + wantErr bool + }{ + { + name: "valid GitHub PR", + data: `{ + "platform": "github", + "type": "pull_request", + "number": "123", + "url": "https://github.com/owner/repo/pull/123", + "title": "Add new feature", + "description": "This PR adds a new feature", + "source_branch": "feature-branch", + "target_branch": "main", + "author": "username" + }`, + wantErr: false, + }, + { + name: "valid GitLab MR minimal", + data: `{ + "platform": "gitlab", + "type": "merge_request", + "number": "456", + "url": "https://gitlab.com/owner/repo/-/merge_requests/456" + }`, + wantErr: false, + }, + { + name: "missing required field: platform", + data: `{ + "type": "pull_request", + "number": "123", + "url": "https://github.com/owner/repo/pull/123" + }`, + wantErr: true, + }, + { + name: "missing required field: url", + data: `{ + "platform": "github", + "type": "pull_request", + "number": "123" + }`, + wantErr: true, + }, + { + name: "invalid platform value", + data: `{ + "platform": "bitbucket", + "type": "pull_request", + "number": "123", + "url": "https://bitbucket.org/owner/repo/pull/123" + }`, + wantErr: true, + }, + { + name: "invalid type value", + data: `{ + "platform": "github", + "type": "issue", + "number": "123", + "url": "https://github.com/owner/repo/pull/123" + }`, + wantErr: true, + }, + { + name: "additional properties not allowed", + data: `{ + "platform": "github", + "type": "pull_request", + "number": "123", + "url": "https://github.com/owner/repo/pull/123", + "extra_field": "not allowed" + }`, + wantErr: true, + }, + { + name: "invalid JSON", + data: `{invalid json}`, + wantErr: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + var data interface{} + err := json.Unmarshal([]byte(tc.data), &data) + if err != nil { + // For invalid JSON test case + if tc.wantErr { + return + } + require.NoError(t, err) + } + + err = schemavalidators.ValidatePRInfo(data, schemavalidators.PRInfoVersion1_0) + if tc.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} diff --git a/internal/prinfo/schemas/generate.go b/internal/prinfo/schemas/generate.go new file mode 100644 index 000000000..e67b557d0 --- /dev/null +++ b/internal/prinfo/schemas/generate.go @@ -0,0 +1,45 @@ +// +// Copyright 2025 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 main + +import ( + "flag" + "fmt" + "os" + + "github.com/chainloop-dev/chainloop/internal/prinfo" +) + +//go:generate go run ./generate.go --output-dir ../../../internal/schemavalidators/internal_schemas/prinfo --version 1.0 +func main() { + var outputDir string + var version string + + flag.StringVar(&outputDir, "output-dir", "../../../internal/schemavalidators/internal_schemas/prinfo", "Directory to output the schema files") + flag.StringVar(&version, "version", "1.0", "Schema version") + flag.Parse() + + generator := prinfo.NewGenerator() + + fmt.Printf("Generating JSON schema for PR/MR Info\n") + sch := generator.GeneratePRInfoSchema(version) + if err := generator.Save(sch, outputDir, version); err != nil { + fmt.Fprintf(os.Stderr, "Error writing schema: %v\n", err) + os.Exit(1) + } + + fmt.Printf("JSON schema successfully generated at %s/pr-info-%s.schema.json\n", outputDir, version) +} diff --git a/internal/schemavalidators/internal_schemas/prinfo/pr-info-1.0.schema.json b/internal/schemavalidators/internal_schemas/prinfo/pr-info-1.0.schema.json new file mode 100644 index 000000000..abed97c5f --- /dev/null +++ b/internal/schemavalidators/internal_schemas/prinfo/pr-info-1.0.schema.json @@ -0,0 +1,61 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schemas.chainloop.dev/prinfo/1.0/pr-info.schema.json", + "properties": { + "platform": { + "type": "string", + "enum": [ + "github", + "gitlab" + ], + "description": "The CI/CD platform" + }, + "type": { + "type": "string", + "enum": [ + "pull_request", + "merge_request" + ], + "description": "The type of change request" + }, + "number": { + "type": "string", + "description": "The PR/MR number or identifier" + }, + "title": { + "type": "string", + "description": "The PR/MR title" + }, + "description": { + "type": "string", + "description": "The PR/MR description or body" + }, + "source_branch": { + "type": "string", + "description": "The source branch name" + }, + "target_branch": { + "type": "string", + "description": "The target branch name" + }, + "url": { + "type": "string", + "format": "uri", + "description": "Direct URL to the PR/MR" + }, + "author": { + "type": "string", + "description": "Username of the PR/MR author" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "platform", + "type", + "number", + "url" + ], + "title": "Pull Request / Merge Request Information", + "description": "Schema for Pull Request or Merge Request metadata collected during attestation" +} \ No newline at end of file diff --git a/internal/schemavalidators/external_schemas/runnercontext/runner-context-response-0.1.schema.json b/internal/schemavalidators/internal_schemas/runnercontext/runner-context-response-0.1.schema.json similarity index 100% rename from internal/schemavalidators/external_schemas/runnercontext/runner-context-response-0.1.schema.json rename to internal/schemavalidators/internal_schemas/runnercontext/runner-context-response-0.1.schema.json diff --git a/internal/schemavalidators/schemavalidators.go b/internal/schemavalidators/schemavalidators.go index 4f2734dcd..badf5a589 100644 --- a/internal/schemavalidators/schemavalidators.go +++ b/internal/schemavalidators/schemavalidators.go @@ -37,9 +37,14 @@ type CSAFVersion string // RunnerContextVersion represents the version of Runner Context schema. type RunnerContextVersion string +// PRInfoVersion represents the version of PR/MR Info schema. +type PRInfoVersion string + const ( // RunnerContextVersion0_1 represents Runner Context version 0.1 schema. RunnerContextVersion0_1 RunnerContextVersion = "0.1" + // PRInfoVersion1_0 represents PR/MR Info version 1.0 schema. + PRInfoVersion1_0 PRInfoVersion = "1.0" // CycloneDXVersion1_5 represents CycloneDX version 1.5 schema. CycloneDXVersion1_5 CycloneDXVersion = "1.5" // CycloneDXVersion1_6 represents CycloneDX version 1.6 schema. @@ -76,8 +81,12 @@ var ( cvssSpecVersion4_0 string // Runner Context schemas - //go:embed external_schemas/runnercontext/runner-context-response-0.1.schema.json + //go:embed internal_schemas/runnercontext/runner-context-response-0.1.schema.json runnerContextSpecVersion0_1 string + + // PR/MR Info schemas + //go:embed internal_schemas/prinfo/pr-info-1.0.schema.json + prInfoSpecVersion1_0 string ) // schemaURLMapping maps the schema URL to the schema content. This is used to compile the schema validators @@ -96,11 +105,13 @@ var schemaURLMapping = map[string]string{ "https://www.first.org/cvss/cvss-v3.1.json": cvssSpecVersion3_1, "https://www.first.org/cvss/cvss-v4.0.json": cvssSpecVersion4_0, "https://chainloop.dev/schemas/runner-context-response-0.1.schema.json": runnerContextSpecVersion0_1, + "https://schemas.chainloop.dev/prinfo/1.0/pr-info.schema.json": prInfoSpecVersion1_0, } var compiledCycloneDxSchemas map[CycloneDXVersion]*jsonschema.Schema var compiledCSAFSchemas map[CSAFVersion]*jsonschema.Schema var compiledRunnerContextSchemas map[RunnerContextVersion]*jsonschema.Schema +var compiledPRInfoSchemas map[PRInfoVersion]*jsonschema.Schema func init() { compiler := jsonschema.NewCompiler() @@ -118,6 +129,9 @@ func init() { compiledRunnerContextSchemas = make(map[RunnerContextVersion]*jsonschema.Schema) compiledRunnerContextSchemas[RunnerContextVersion0_1] = compiler.MustCompile("https://chainloop.dev/schemas/runner-context-response-0.1.schema.json") + + compiledPRInfoSchemas = make(map[PRInfoVersion]*jsonschema.Schema) + compiledPRInfoSchemas[PRInfoVersion1_0] = compiler.MustCompile("https://schemas.chainloop.dev/prinfo/1.0/pr-info.schema.json") } // ValidateCycloneDX validates the given object against the specified CycloneDX schema version. @@ -209,6 +223,28 @@ func ValidateChainloopRunnerContext(data interface{}, version RunnerContextVersi return nil } +// ValidatePRInfo validates the PR/MR info schema. +func ValidatePRInfo(data interface{}, version PRInfoVersion) error { + if version == "" { + version = PRInfoVersion1_0 + } + + schema, ok := compiledPRInfoSchemas[version] + if !ok { + return errors.New("invalid PR info schema version") + } + + if err := schema.Validate(data); err != nil { + var invalidJSONTypeError jsonschema.InvalidJSONTypeError + if errors.As(err, &invalidJSONTypeError) { + return ErrInvalidJSONPayload + } + return err + } + + return nil +} + // errorIsJSONFormat checks if the error is a JSON format error. func errorIsJSONFormat(err error) error { var invalidJSONTypeError jsonschema.InvalidJSONTypeError diff --git a/pkg/attestation/crafter/crafter.go b/pkg/attestation/crafter/crafter.go index daaa4a0bd..2e1fbb9b1 100644 --- a/pkg/attestation/crafter/crafter.go +++ b/pkg/attestation/crafter/crafter.go @@ -17,6 +17,7 @@ package crafter import ( "context" + "encoding/json" "errors" "fmt" "net/url" @@ -30,6 +31,7 @@ import ( v1 "github.com/chainloop-dev/chainloop/app/controlplane/api/controlplane/v1" schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1" "github.com/chainloop-dev/chainloop/internal/ociauth" + "github.com/chainloop-dev/chainloop/internal/prinfo" api "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/api/attestation/v1" "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/materials" "github.com/chainloop-dev/chainloop/pkg/casclient" @@ -472,6 +474,64 @@ func (c *Crafter) ResolveEnvVars(ctx context.Context, attestationID string) erro return nil } +// AutoCollectPRMetadata automatically collects PR/MR metadata if running in a PR/MR context +func (c *Crafter) AutoCollectPRMetadata(ctx context.Context, attestationID string, runner SupportedRunner, casBackend *casclient.CASBackend) error { + // Detect if we're in a PR/MR context + isPR, metadata, err := DetectPRContext(runner) + if err != nil { + return fmt.Errorf("failed to detect PR/MR context: %w", err) + } + + // If not in PR/MR context, nothing to do + if !isPR { + c.Logger.Debug().Msg("not in PR/MR context, skipping metadata collection") + return nil + } + + c.Logger.Info().Str("platform", metadata.Platform).Str("number", metadata.Number).Msg("detected PR/MR context") + + // Create the material + evidenceData := prinfo.NewEvidence(prinfo.Data{ + Platform: metadata.Platform, + Type: metadata.Type, + Number: metadata.Number, + Title: metadata.Title, + Description: metadata.Description, + SourceBranch: metadata.SourceBranch, + TargetBranch: metadata.TargetBranch, + URL: metadata.URL, + Author: metadata.Author, + }) + + // Marshal to JSON + jsonData, err := json.Marshal(evidenceData) + if err != nil { + return fmt.Errorf("failed to marshal PR/MR metadata: %w", err) + } + + // Create a temporary file for the metadata + tmpFile, err := os.CreateTemp("", "pr-metadata-*.json") + if err != nil { + return fmt.Errorf("failed to create temp file: %w", err) + } + defer os.Remove(tmpFile.Name()) + + // Write the JSON data to the temp file + if _, err := tmpFile.Write(jsonData); err != nil { + tmpFile.Close() + return fmt.Errorf("failed to write metadata to temp file: %w", err) + } + tmpFile.Close() + + // Add the material using the crafter with explicit CHAINLOOP_PR_INFO type + if _, err := c.AddMaterialContractFree(ctx, attestationID, schemaapi.CraftingSchema_Material_CHAINLOOP_PR_INFO.String(), "pr-metadata", tmpFile.Name(), casBackend, nil); err != nil { + return fmt.Errorf("failed to add PR/MR metadata material: %w", err) + } + + c.Logger.Info().Msg("successfully collected and attested PR/MR metadata") + return nil +} + func (c *Crafter) resolveRunnerInfo() { c.CraftingState.Attestation.RunnerEnvironment = &api.RunnerEnvironment{ Environment: c.Runner.Environment().String(), diff --git a/pkg/attestation/crafter/materials/chainloop_pr_info.go b/pkg/attestation/crafter/materials/chainloop_pr_info.go new file mode 100644 index 000000000..8abf702ce --- /dev/null +++ b/pkg/attestation/crafter/materials/chainloop_pr_info.go @@ -0,0 +1,90 @@ +// +// Copyright 2025 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 materials + +import ( + "context" + "encoding/json" + "fmt" + "os" + + schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1" + "github.com/chainloop-dev/chainloop/internal/prinfo" + "github.com/chainloop-dev/chainloop/internal/schemavalidators" + api "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/api/attestation/v1" + "github.com/chainloop-dev/chainloop/pkg/casclient" + + "github.com/rs/zerolog" +) + +type ChainloopPRInfoCrafter struct { + *crafterCommon + backend *casclient.CASBackend +} + +// NewChainloopPRInfoCrafter generates a new CHAINLOOP_PR_INFO material. +// This material type contains Pull Request or Merge Request metadata +// collected automatically during attestation in a PR/MR context. +func NewChainloopPRInfoCrafter(schema *schemaapi.CraftingSchema_Material, backend *casclient.CASBackend, l *zerolog.Logger) (*ChainloopPRInfoCrafter, error) { + if schema.Type != schemaapi.CraftingSchema_Material_CHAINLOOP_PR_INFO { + return nil, fmt.Errorf("material type is not chainloop_pr_info") + } + + craftCommon := &crafterCommon{logger: l, input: schema} + return &ChainloopPRInfoCrafter{backend: backend, crafterCommon: craftCommon}, nil +} + +// Craft will validate the PR info against the JSON schema, calculate the digest of the artifact, +// upload it and return the material definition. +func (i *ChainloopPRInfoCrafter) Craft(ctx context.Context, artifactPath string) (*api.Attestation_Material, error) { + // Read the file + f, err := os.ReadFile(artifactPath) + if err != nil { + return nil, fmt.Errorf("can't open the file: %w", err) + } + + // Unmarshal into typed structure first + var v prinfo.Evidence + if err := json.Unmarshal(f, &v); err != nil { + i.logger.Debug().Err(err).Msg("error decoding file") + return nil, fmt.Errorf("invalid JSON format: %w", err) + } + + // Marshal the data field to validate it + dataBytes, err := json.Marshal(v.Data) + if err != nil { + return nil, fmt.Errorf("failed to marshal data for validation: %w", err) + } + + var rawData interface{} + if err := json.Unmarshal(dataBytes, &rawData); err != nil { + return nil, fmt.Errorf("failed to unmarshal data for validation: %w", err) + } + + // Validate the data against JSON schema + if err := schemavalidators.ValidatePRInfo(rawData, schemavalidators.PRInfoVersion1_0); err != nil { + i.logger.Debug().Err(err).Msg("schema validation failed") + return nil, fmt.Errorf("PR info validation failed: %w", err) + } + + // Upload the artifact + material, err := uploadAndCraft(ctx, i.input, i.backend, artifactPath, i.logger) + if err != nil { + return nil, err + } + + return material, nil +} diff --git a/pkg/attestation/crafter/materials/chainloop_pr_info_test.go b/pkg/attestation/crafter/materials/chainloop_pr_info_test.go new file mode 100644 index 000000000..94e710bb1 --- /dev/null +++ b/pkg/attestation/crafter/materials/chainloop_pr_info_test.go @@ -0,0 +1,159 @@ +// +// Copyright 2025 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 materials + +import ( + "encoding/json" + "os" + "path/filepath" + "testing" + + schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1" + "github.com/chainloop-dev/chainloop/internal/prinfo" + "github.com/chainloop-dev/chainloop/internal/schemavalidators" + "github.com/rs/zerolog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestChainloopPRInfoCrafter_Validation(t *testing.T) { + testCases := []struct { + name string + data *prinfo.Evidence + wantErr bool + errMsg string + }{ + { + name: "valid GitHub PR", + data: prinfo.NewEvidence(prinfo.Data{ + Platform: "github", + Type: "pull_request", + Number: "123", + Title: "Test PR", + Description: "Test description", + SourceBranch: "feature", + TargetBranch: "main", + URL: "https://github.com/org/repo/pull/123", + Author: "testuser", + }), + wantErr: false, + }, + { + name: "valid GitLab MR minimal", + data: prinfo.NewEvidence(prinfo.Data{ + Platform: "gitlab", + Type: "merge_request", + Number: "456", + URL: "https://gitlab.com/org/repo/-/merge_requests/456", + }), + wantErr: false, + }, + { + name: "invalid platform", + data: prinfo.NewEvidence(prinfo.Data{ + Platform: "bitbucket", // Invalid platform + Type: "pull_request", + Number: "123", + URL: "https://bitbucket.org/org/repo/pull/123", + }), + wantErr: true, + }, + { + name: "missing required field: platform", + data: &prinfo.Evidence{ + ID: prinfo.EvidenceID, + Schema: prinfo.EvidenceSchemaURL, + Data: prinfo.Data{ + // Platform is missing + Type: "pull_request", + Number: "123", + URL: "https://github.com/org/repo/pull/123", + }, + }, + wantErr: true, + }, + { + name: "missing required field: url", + data: prinfo.NewEvidence(prinfo.Data{ + Platform: "github", + Type: "pull_request", + Number: "123", + // URL is missing + }), + wantErr: true, + }, + { + name: "invalid type value", + data: prinfo.NewEvidence(prinfo.Data{ + Platform: "github", + Type: "issue", // Invalid type + Number: "123", + URL: "https://github.com/org/repo/pull/123", + }), + wantErr: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Marshal the data field to validate it + dataBytes, err := json.Marshal(tc.data.Data) + require.NoError(t, err) + + var rawData interface{} + err = json.Unmarshal(dataBytes, &rawData) + require.NoError(t, err) + + // Validate the data against JSON schema + err = schemavalidators.ValidatePRInfo(rawData, schemavalidators.PRInfoVersion1_0) + + if tc.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestChainloopPRInfoCrafter_InvalidJSON(t *testing.T) { + tmpDir := t.TempDir() + tmpFile := filepath.Join(tmpDir, "invalid.json") + + // Write invalid JSON + err := os.WriteFile(tmpFile, []byte(`{invalid json}`), 0600) + require.NoError(t, err) + + // Read and try to unmarshal + f, err := os.ReadFile(tmpFile) + require.NoError(t, err) + + var v prinfo.Evidence + err = json.Unmarshal(f, &v) + require.Error(t, err) +} + +func TestChainloopPRInfoCrafter_WrongMaterialType(t *testing.T) { + logger := zerolog.Nop() + + schema := &schemaapi.CraftingSchema_Material{ + Type: schemaapi.CraftingSchema_Material_SBOM_CYCLONEDX_JSON, // Wrong type + } + + _, err := NewChainloopPRInfoCrafter(schema, nil, &logger) + require.Error(t, err) + assert.Contains(t, err.Error(), "material type is not chainloop_pr_info") +} diff --git a/pkg/attestation/crafter/materials/materials.go b/pkg/attestation/crafter/materials/materials.go index f3de17bc8..d88b0040e 100644 --- a/pkg/attestation/crafter/materials/materials.go +++ b/pkg/attestation/crafter/materials/materials.go @@ -269,6 +269,8 @@ func Craft(ctx context.Context, materialSchema *schemaapi.CraftingSchema_Materia crafter, err = NewSLSAProvenanceCrafter(materialSchema, casBackend, logger) case schemaapi.CraftingSchema_Material_CHAINLOOP_RUNNER_CONTEXT: crafter, err = NewRunnerContextCrafter(materialSchema, casBackend, logger) + case schemaapi.CraftingSchema_Material_CHAINLOOP_PR_INFO: + crafter, err = NewChainloopPRInfoCrafter(materialSchema, casBackend, logger) default: return nil, fmt.Errorf("material of type %q not supported yet", materialSchema.Type) } diff --git a/pkg/attestation/crafter/prmetadata.go b/pkg/attestation/crafter/prmetadata.go new file mode 100644 index 000000000..9a5f9651a --- /dev/null +++ b/pkg/attestation/crafter/prmetadata.go @@ -0,0 +1,145 @@ +// +// Copyright 2025 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 crafter + +import ( + "encoding/json" + "fmt" + "os" + + schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1" +) + +// PRMetadata holds extracted PR/MR information +type PRMetadata struct { + Platform string // "github" or "gitlab" + Type string // "pull_request" or "merge_request" + Number string + Title string + Description string + SourceBranch string + TargetBranch string + URL string + Author string +} + +// DetectPRContext checks if we're in a PR/MR context and extracts metadata +func DetectPRContext(runner SupportedRunner) (bool, *PRMetadata, error) { + if runner == nil { + return false, nil, fmt.Errorf("runner is nil") + } + + envVars, errs := runner.ResolveEnvVars() + if len(errs) > 0 { + var combinedErrs string + for _, err := range errs { + combinedErrs += (*err).Error() + "\n" + } + return false, nil, fmt.Errorf("failed to resolve env vars: %s", combinedErrs) + } + + switch runner.ID() { + case schemaapi.CraftingSchema_Runner_GITHUB_ACTION: + return extractGitHubPRMetadata(envVars) + case schemaapi.CraftingSchema_Runner_GITLAB_PIPELINE: + return extractGitLabMRMetadata(envVars) + default: + return false, nil, nil + } +} + +// extractGitHubPRMetadata reads GITHUB_EVENT_PATH JSON and extracts PR metadata +func extractGitHubPRMetadata(envVars map[string]string) (bool, *PRMetadata, error) { + eventName := envVars["GITHUB_EVENT_NAME"] + // Check if this is a pull request event + if eventName != "pull_request" && eventName != "pull_request_target" { + return false, nil, nil + } + + eventPath := envVars["GITHUB_EVENT_PATH"] + if eventPath == "" { + return false, nil, fmt.Errorf("GITHUB_EVENT_PATH not set") + } + + // Read the event payload file + data, err := os.ReadFile(eventPath) + if err != nil { + return false, nil, fmt.Errorf("failed to read event file: %w", err) + } + + // Parse the event JSON + var event struct { + PullRequest struct { + Number int `json:"number"` + Title string `json:"title"` + Body string `json:"body"` + HTMLURL string `json:"html_url"` + User struct { + Login string `json:"login"` + } `json:"user"` + } `json:"pull_request"` + } + + if err := json.Unmarshal(data, &event); err != nil { + return false, nil, fmt.Errorf("failed to parse event JSON: %w", err) + } + + metadata := &PRMetadata{ + Platform: "github", + Type: "pull_request", + Number: fmt.Sprintf("%d", event.PullRequest.Number), + Title: event.PullRequest.Title, + Description: event.PullRequest.Body, + SourceBranch: envVars["GITHUB_HEAD_REF"], + TargetBranch: envVars["GITHUB_BASE_REF"], + URL: event.PullRequest.HTMLURL, + Author: event.PullRequest.User.Login, + } + + return true, metadata, nil +} + +// extractGitLabMRMetadata extracts from GitLab environment variables +func extractGitLabMRMetadata(envVars map[string]string) (bool, *PRMetadata, error) { + pipelineSource := envVars["CI_PIPELINE_SOURCE"] + // Check if this is a merge request event + if pipelineSource != "merge_request_event" { + return false, nil, nil + } + + mrIID := envVars["CI_MERGE_REQUEST_IID"] + if mrIID == "" { + return false, nil, fmt.Errorf("CI_MERGE_REQUEST_IID not set") + } + + // Construct MR URL + projectURL := envVars["CI_MERGE_REQUEST_PROJECT_URL"] + mrURL := fmt.Sprintf("%s/-/merge_requests/%s", projectURL, mrIID) + + metadata := &PRMetadata{ + Platform: "gitlab", + Type: "merge_request", + Number: mrIID, + Title: envVars["CI_MERGE_REQUEST_TITLE"], + Description: envVars["CI_MERGE_REQUEST_DESCRIPTION"], + SourceBranch: envVars["CI_MERGE_REQUEST_SOURCE_BRANCH_NAME"], + TargetBranch: envVars["CI_MERGE_REQUEST_TARGET_BRANCH_NAME"], + URL: mrURL, + Author: envVars["GITLAB_USER_LOGIN"], + } + + return true, metadata, nil +} diff --git a/pkg/attestation/crafter/prmetadata_test.go b/pkg/attestation/crafter/prmetadata_test.go new file mode 100644 index 000000000..472234b2f --- /dev/null +++ b/pkg/attestation/crafter/prmetadata_test.go @@ -0,0 +1,175 @@ +// +// Copyright 2024-2025 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 crafter + +import ( + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestExtractGitHubPRMetadata(t *testing.T) { + testCases := []struct { + name string + envVars map[string]string + expectPR bool + expectError bool + validate func(t *testing.T, metadata *PRMetadata) + }{ + { + name: "valid pull_request event", + envVars: map[string]string{ + "GITHUB_EVENT_NAME": "pull_request", + "GITHUB_EVENT_PATH": filepath.Join("testdata", "github_pr_event.json"), + "GITHUB_HEAD_REF": "feature-branch", + "GITHUB_BASE_REF": "main", + }, + expectPR: true, + expectError: false, + validate: func(t *testing.T, metadata *PRMetadata) { + assert.Equal(t, "github", metadata.Platform) + assert.Equal(t, "pull_request", metadata.Type) + assert.Equal(t, "123", metadata.Number) + assert.Equal(t, "Add new feature", metadata.Title) + assert.Contains(t, metadata.Description, "This PR adds a new feature") + assert.Equal(t, "feature-branch", metadata.SourceBranch) + assert.Equal(t, "main", metadata.TargetBranch) + assert.Equal(t, "https://github.com/owner/repo/pull/123", metadata.URL) + assert.Equal(t, "johndoe", metadata.Author) + }, + }, + { + name: "pull_request_target event", + envVars: map[string]string{ + "GITHUB_EVENT_NAME": "pull_request_target", + "GITHUB_EVENT_PATH": filepath.Join("testdata", "github_pr_event.json"), + "GITHUB_HEAD_REF": "feature-branch", + "GITHUB_BASE_REF": "main", + }, + expectPR: true, + expectError: false, + }, + { + name: "not a PR event", + envVars: map[string]string{ + "GITHUB_EVENT_NAME": "push", + }, + expectPR: false, + expectError: false, + }, + { + name: "missing event path", + envVars: map[string]string{ + "GITHUB_EVENT_NAME": "pull_request", + }, + expectPR: false, + expectError: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + isPR, metadata, err := extractGitHubPRMetadata(tc.envVars) + + if tc.expectError { + require.Error(t, err) + return + } + + require.NoError(t, err) + assert.Equal(t, tc.expectPR, isPR) + + if tc.expectPR && tc.validate != nil { + require.NotNil(t, metadata) + tc.validate(t, metadata) + } + }) + } +} + +func TestExtractGitLabMRMetadata(t *testing.T) { + testCases := []struct { + name string + envVars map[string]string + expectMR bool + expectError bool + validate func(t *testing.T, metadata *PRMetadata) + }{ + { + name: "valid merge_request event", + envVars: map[string]string{ + "CI_PIPELINE_SOURCE": "merge_request_event", + "CI_MERGE_REQUEST_IID": "42", + "CI_MERGE_REQUEST_TITLE": "Test MR", + "CI_MERGE_REQUEST_DESCRIPTION": "This is a test MR", + "CI_MERGE_REQUEST_SOURCE_BRANCH_NAME": "feature-branch", + "CI_MERGE_REQUEST_TARGET_BRANCH_NAME": "main", + "CI_MERGE_REQUEST_PROJECT_URL": "https://gitlab.com/owner/repo", + "GITLAB_USER_LOGIN": "testuser", + }, + expectMR: true, + expectError: false, + validate: func(t *testing.T, metadata *PRMetadata) { + assert.Equal(t, "gitlab", metadata.Platform) + assert.Equal(t, "merge_request", metadata.Type) + assert.Equal(t, "42", metadata.Number) + assert.Equal(t, "Test MR", metadata.Title) + assert.Equal(t, "This is a test MR", metadata.Description) + assert.Equal(t, "feature-branch", metadata.SourceBranch) + assert.Equal(t, "main", metadata.TargetBranch) + assert.Equal(t, "https://gitlab.com/owner/repo/-/merge_requests/42", metadata.URL) + assert.Equal(t, "testuser", metadata.Author) + }, + }, + { + name: "not a merge request event", + envVars: map[string]string{ + "CI_PIPELINE_SOURCE": "push", + }, + expectMR: false, + expectError: false, + }, + { + name: "missing MR IID", + envVars: map[string]string{ + "CI_PIPELINE_SOURCE": "merge_request_event", + }, + expectMR: false, + expectError: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + isMR, metadata, err := extractGitLabMRMetadata(tc.envVars) + + if tc.expectError { + require.Error(t, err) + return + } + + require.NoError(t, err) + assert.Equal(t, tc.expectMR, isMR) + + if tc.expectMR && tc.validate != nil { + require.NotNil(t, metadata) + tc.validate(t, metadata) + } + }) + } +} diff --git a/pkg/attestation/crafter/runners/githubaction.go b/pkg/attestation/crafter/runners/githubaction.go index ba3976c7f..4bd998253 100644 --- a/pkg/attestation/crafter/runners/githubaction.go +++ b/pkg/attestation/crafter/runners/githubaction.go @@ -1,5 +1,5 @@ // -// Copyright 2023 The Chainloop Authors. +// Copyright 2024-2025 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. @@ -89,6 +89,11 @@ func (r *GitHubAction) ListEnvVars() []*EnvVarDefinition { {"GITHUB_SHA", false}, {"RUNNER_NAME", false}, {"RUNNER_OS", false}, + // PR-specific variables (optional - only present in PR contexts) + {"GITHUB_EVENT_NAME", true}, + {"GITHUB_HEAD_REF", true}, + {"GITHUB_BASE_REF", true}, + {"GITHUB_EVENT_PATH", true}, } } diff --git a/pkg/attestation/crafter/runners/githubaction_test.go b/pkg/attestation/crafter/runners/githubaction_test.go index 3943ceb2e..774b27f9f 100644 --- a/pkg/attestation/crafter/runners/githubaction_test.go +++ b/pkg/attestation/crafter/runners/githubaction_test.go @@ -1,5 +1,5 @@ // -// Copyright 2023 The Chainloop Authors. +// Copyright 2024-2025 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. @@ -101,6 +101,10 @@ func (s *githubActionSuite) TestListEnvVars() { {"GITHUB_SHA", false}, {"RUNNER_NAME", false}, {"RUNNER_OS", false}, + {"GITHUB_EVENT_NAME", true}, + {"GITHUB_HEAD_REF", true}, + {"GITHUB_BASE_REF", true}, + {"GITHUB_EVENT_PATH", true}, }, s.runner.ListEnvVars()) } @@ -138,6 +142,10 @@ var gitHubTestingEnvVars = map[string]string{ "GITHUB_SHA": "1234567890", "RUNNER_NAME": "chainloop-runner", "RUNNER_OS": "linux", + "GITHUB_EVENT_NAME": "pull_request", + "GITHUB_HEAD_REF": "feature-branch", + "GITHUB_BASE_REF": "main", + "GITHUB_EVENT_PATH": "/tmp/event.json", } // Run the tests diff --git a/pkg/attestation/crafter/runners/gitlabpipeline.go b/pkg/attestation/crafter/runners/gitlabpipeline.go index 6ceaecaa8..fcc7205b7 100644 --- a/pkg/attestation/crafter/runners/gitlabpipeline.go +++ b/pkg/attestation/crafter/runners/gitlabpipeline.go @@ -1,5 +1,5 @@ // -// Copyright 2023-2025 The Chainloop Authors. +// Copyright 2024-2025 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. @@ -70,6 +70,14 @@ func (r *GitlabPipeline) ListEnvVars() []*EnvVarDefinition { {"CI_RUNNER_VERSION", false}, {"CI_RUNNER_DESCRIPTION", true}, {"CI_COMMIT_REF_NAME", false}, + // MR-specific variables (optional - only present in MR contexts) + {"CI_PIPELINE_SOURCE", true}, + {"CI_MERGE_REQUEST_IID", true}, + {"CI_MERGE_REQUEST_TITLE", true}, + {"CI_MERGE_REQUEST_DESCRIPTION", true}, + {"CI_MERGE_REQUEST_SOURCE_BRANCH_NAME", true}, + {"CI_MERGE_REQUEST_TARGET_BRANCH_NAME", true}, + {"CI_MERGE_REQUEST_PROJECT_URL", true}, } } diff --git a/pkg/attestation/crafter/runners/gitlabpipeline_test.go b/pkg/attestation/crafter/runners/gitlabpipeline_test.go index 07e73e9d0..32e5ba742 100644 --- a/pkg/attestation/crafter/runners/gitlabpipeline_test.go +++ b/pkg/attestation/crafter/runners/gitlabpipeline_test.go @@ -91,6 +91,13 @@ func (s *gitlabPipelineSuite) TestListEnvVars() { {"CI_RUNNER_VERSION", false}, {"CI_RUNNER_DESCRIPTION", true}, {"CI_COMMIT_REF_NAME", false}, + {"CI_PIPELINE_SOURCE", true}, + {"CI_MERGE_REQUEST_IID", true}, + {"CI_MERGE_REQUEST_TITLE", true}, + {"CI_MERGE_REQUEST_DESCRIPTION", true}, + {"CI_MERGE_REQUEST_SOURCE_BRANCH_NAME", true}, + {"CI_MERGE_REQUEST_TARGET_BRANCH_NAME", true}, + {"CI_MERGE_REQUEST_PROJECT_URL", true}, }, s.runner.ListEnvVars()) } @@ -98,24 +105,39 @@ func (s *gitlabPipelineSuite) TestResolveEnvVars() { resolvedEnvVars, errors := s.runner.ResolveEnvVars() s.Empty(errors) s.Equal(map[string]string{ - "GITLAB_USER_EMAIL": "foo@foo.com", - "GITLAB_USER_LOGIN": "foo", - "CI_PROJECT_URL": "https://gitlab.com/chainloop/chainloop", - "CI_COMMIT_SHA": "1234567890", - "CI_JOB_URL": "https://gitlab.com/chainloop/chainloop/-/jobs/123", - "CI_PIPELINE_URL": "https://gitlab.com/chainloop/chainloop/-/pipelines/123", - "CI_RUNNER_VERSION": "13.10.0", - "CI_RUNNER_DESCRIPTION": "chainloop-runner", - "CI_COMMIT_REF_NAME": "main", - "CI_SERVER_URL": "https://gitlab.com", + "GITLAB_USER_EMAIL": "foo@foo.com", + "GITLAB_USER_LOGIN": "foo", + "CI_PROJECT_URL": "https://gitlab.com/chainloop/chainloop", + "CI_COMMIT_SHA": "1234567890", + "CI_JOB_URL": "https://gitlab.com/chainloop/chainloop/-/jobs/123", + "CI_PIPELINE_URL": "https://gitlab.com/chainloop/chainloop/-/pipelines/123", + "CI_RUNNER_VERSION": "13.10.0", + "CI_RUNNER_DESCRIPTION": "chainloop-runner", + "CI_COMMIT_REF_NAME": "main", + "CI_SERVER_URL": "https://gitlab.com", + "CI_PIPELINE_SOURCE": "merge_request_event", + "CI_MERGE_REQUEST_IID": "42", + "CI_MERGE_REQUEST_TITLE": "Add new feature", + "CI_MERGE_REQUEST_DESCRIPTION": "Implements awesome feature", + "CI_MERGE_REQUEST_SOURCE_BRANCH_NAME": "feature/awesome", + "CI_MERGE_REQUEST_TARGET_BRANCH_NAME": "main", + "CI_MERGE_REQUEST_PROJECT_URL": "https://gitlab.com/chainloop/chainloop/-/merge_requests/42", }, resolvedEnvVars) } func (s *gitlabPipelineSuite) TestResolveEnvVarsWithoutRunnerDescription() { + // Unset optional variables to test they can be missing s.T().Setenv("CI_RUNNER_DESCRIPTION", "") + s.T().Setenv("CI_PIPELINE_SOURCE", "") + s.T().Setenv("CI_MERGE_REQUEST_IID", "") + s.T().Setenv("CI_MERGE_REQUEST_TITLE", "") + s.T().Setenv("CI_MERGE_REQUEST_DESCRIPTION", "") + s.T().Setenv("CI_MERGE_REQUEST_SOURCE_BRANCH_NAME", "") + s.T().Setenv("CI_MERGE_REQUEST_TARGET_BRANCH_NAME", "") + s.T().Setenv("CI_MERGE_REQUEST_PROJECT_URL", "") resolvedEnvVars, errors := s.runner.ResolveEnvVars() - s.Empty(errors, "Should not error when CI_RUNNER_DESCRIPTION is missing") + s.Empty(errors, "Should not error when optional variables are missing") expected := map[string]string{ "GITLAB_USER_EMAIL": "foo@foo.com", @@ -155,6 +177,13 @@ func (s *gitlabPipelineSuite) SetupTest() { t.Setenv("CI_RUNNER_DESCRIPTION", "chainloop-runner") t.Setenv("CI_COMMIT_REF_NAME", "main") t.Setenv("CI_SERVER_URL", "https://gitlab.com") + t.Setenv("CI_PIPELINE_SOURCE", "merge_request_event") + t.Setenv("CI_MERGE_REQUEST_IID", "42") + t.Setenv("CI_MERGE_REQUEST_TITLE", "Add new feature") + t.Setenv("CI_MERGE_REQUEST_DESCRIPTION", "Implements awesome feature") + t.Setenv("CI_MERGE_REQUEST_SOURCE_BRANCH_NAME", "feature/awesome") + t.Setenv("CI_MERGE_REQUEST_TARGET_BRANCH_NAME", "main") + t.Setenv("CI_MERGE_REQUEST_PROJECT_URL", "https://gitlab.com/chainloop/chainloop/-/merge_requests/42") } // Run the tests diff --git a/pkg/attestation/crafter/runners/runners.go b/pkg/attestation/crafter/runners/runners.go index c7834a487..b5466d651 100644 --- a/pkg/attestation/crafter/runners/runners.go +++ b/pkg/attestation/crafter/runners/runners.go @@ -1,5 +1,5 @@ // -// Copyright 2023 The Chainloop Authors. +// Copyright 2024-2025 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. diff --git a/pkg/attestation/crafter/testdata/github_pr_event.json b/pkg/attestation/crafter/testdata/github_pr_event.json new file mode 100644 index 000000000..0275cbcb8 --- /dev/null +++ b/pkg/attestation/crafter/testdata/github_pr_event.json @@ -0,0 +1,11 @@ +{ + "pull_request": { + "number": 123, + "title": "Add new feature", + "body": "This PR adds a new feature to the system.\n\nSigned-off-by: John Doe ", + "html_url": "https://github.com/owner/repo/pull/123", + "user": { + "login": "johndoe" + } + } +}