diff --git a/app/controlplane/Makefile b/app/controlplane/Makefile index ba9037f6a..41c5fb6a5 100644 --- a/app/controlplane/Makefile +++ b/app/controlplane/Makefile @@ -8,6 +8,7 @@ config: check-buf-tool .PHONY: api # generate api proto bindings api: check-buf-tool + cd ./plugins/sdk/v1/plugin/api && buf generate cd ./api && buf generate .PHONY: build diff --git a/app/controlplane/cmd/wire_gen.go b/app/controlplane/cmd/wire_gen.go index b95f8746e..1c96f3ae5 100644 --- a/app/controlplane/cmd/wire_gen.go +++ b/app/controlplane/cmd/wire_gen.go @@ -89,7 +89,7 @@ func wireApp(bootstrap *conf.Bootstrap, readerWriter credentials.ReaderWriter, l Opts: v2, } workflowRunService := service.NewWorkflowRunService(newWorkflowRunServiceOpts) - fanOutDispatcher := dispatcher.New(integrationUseCase, workflowUseCase, readerWriter, casClientUseCase, availablePlugins, logger) + fanOutDispatcher := dispatcher.New(integrationUseCase, workflowUseCase, workflowRunUseCase, readerWriter, casClientUseCase, availablePlugins, logger) newAttestationServiceOpts := &service.NewAttestationServiceOpts{ WorkflowRunUC: workflowRunUseCase, WorkflowUC: workflowUseCase, diff --git a/app/controlplane/internal/dispatcher/dispatcher.go b/app/controlplane/internal/dispatcher/dispatcher.go index 24982eef6..8e9a50b74 100644 --- a/app/controlplane/internal/dispatcher/dispatcher.go +++ b/app/controlplane/internal/dispatcher/dispatcher.go @@ -39,6 +39,7 @@ import ( type FanOutDispatcher struct { integrationUC *biz.IntegrationUseCase wfUC *biz.WorkflowUseCase + wfRunUC *biz.WorkflowRunUseCase credentialsProvider credentials.ReaderWriter casClient biz.CASClient log *log.Helper @@ -46,8 +47,8 @@ type FanOutDispatcher struct { loaded sdk.AvailablePlugins } -func New(integrationUC *biz.IntegrationUseCase, wfUC *biz.WorkflowUseCase, creds credentials.ReaderWriter, c biz.CASClient, registered sdk.AvailablePlugins, l log.Logger) *FanOutDispatcher { - return &FanOutDispatcher{integrationUC, wfUC, creds, c, servicelogger.ScopedHelper(l, "fanout-dispatcher"), l, registered} +func New(integrationUC *biz.IntegrationUseCase, wfUC *biz.WorkflowUseCase, wfRunUC *biz.WorkflowRunUseCase, creds credentials.ReaderWriter, c biz.CASClient, registered sdk.AvailablePlugins, l log.Logger) *FanOutDispatcher { + return &FanOutDispatcher{integrationUC, wfUC, wfRunUC, creds, c, servicelogger.ScopedHelper(l, "fanout-dispatcher"), l, registered} } // Dispatch item is a plugin instance + resolved inputs that gets hydrated @@ -101,11 +102,28 @@ func (d *FanOutDispatcher) Run(ctx context.Context, opts *RunOpts) error { return fmt.Errorf("workflow not found") } + wfRun, err := d.wfRunUC.View(ctx, opts.OrgID, opts.WorkflowRunID) + if err != nil { + return fmt.Errorf("finding workflow: %w", err) + } else if wfRun == nil { + return fmt.Errorf("workflowRun not found") + } + workflowMetadata := &sdk.ChainloopMetadata{ - WorkflowID: opts.WorkflowID, - WorkflowRunID: opts.WorkflowRunID, - WorkflowName: wf.Name, - WorkflowProject: wf.Project, + Workflow: &sdk.ChainloopMetadataWorkflow{ + ID: opts.WorkflowID, + Name: wf.Name, + Project: wf.Project, + Team: wf.Team, + }, + WorkflowRun: &sdk.ChainloopMetadataWorkflowRun{ + ID: opts.WorkflowRunID, + State: wfRun.State, + StartedAt: *wfRun.CreatedAt, + FinishedAt: *wfRun.FinishedAt, + RunnerType: wfRun.RunnerType, + RunURL: wfRun.RunURL, + }, } // Dispatch the integrations diff --git a/app/controlplane/internal/dispatcher/dispatcher_test.go b/app/controlplane/internal/dispatcher/dispatcher_test.go index 91f83ae83..31268f6b3 100644 --- a/app/controlplane/internal/dispatcher/dispatcher_test.go +++ b/app/controlplane/internal/dispatcher/dispatcher_test.go @@ -313,7 +313,7 @@ func (s *dispatcherTestSuite) SetupTest() { l := log.NewStdLogger(io.Discard) s.casClient = mocks.NewCASClient(s.T()) - s.dispatcher = New(s.Integration, nil, nil, s.casClient, registeredIntegrations, l) + s.dispatcher = New(s.Integration, nil, nil, nil, s.casClient, registeredIntegrations, l) } type mockedIntegration struct { diff --git a/app/controlplane/plugins/core/dependency-track/v1/extension.go b/app/controlplane/plugins/core/dependency-track/v1/extension.go index 46e541a3c..fd6eb0af8 100644 --- a/app/controlplane/plugins/core/dependency-track/v1/extension.go +++ b/app/controlplane/plugins/core/dependency-track/v1/extension.go @@ -170,7 +170,7 @@ func (i *DependencyTrack) Execute(ctx context.Context, req *sdk.ExecutionRequest "materialName", sbom.Name, "host", registrationConfig.Domain, "projectID", attachmentConfig.ProjectID, "projectName", attachmentConfig.ProjectName, - "workflowID", req.WorkflowID, + "workflowID", req.Workflow.ID, ) // Create an SBOM client and perform validation and upload @@ -195,7 +195,7 @@ func (i *DependencyTrack) Execute(ctx context.Context, req *sdk.ExecutionRequest "materialName", sbom.Name, "host", registrationConfig.Domain, "projectID", attachmentConfig.ProjectID, "projectName", attachmentConfig.ProjectName, - "workflowID", req.WorkflowID, + "workflowID", req.Workflow.ID, ) } diff --git a/app/controlplane/plugins/core/discord-webhook/v1/discord.go b/app/controlplane/plugins/core/discord-webhook/v1/discord.go index a9a37a9e7..bc7ecf4f8 100644 --- a/app/controlplane/plugins/core/discord-webhook/v1/discord.go +++ b/app/controlplane/plugins/core/discord-webhook/v1/discord.go @@ -24,8 +24,6 @@ import ( "io" "mime/multipart" "net/http" - "strings" - "text/template" "github.com/chainloop-dev/chainloop/app/controlplane/plugins/sdk/v1" "github.com/go-kratos/kratos/v2/log" @@ -142,22 +140,13 @@ func (i *Integration) Execute(_ context.Context, req *sdk.ExecutionRequest) erro return fmt.Errorf("invalid registration config: %w", err) } - attestationJSON, err := json.MarshalIndent(req.Input.Attestation.Statement, "", " ") + summary, err := sdk.SummaryTable(req) if err != nil { - return fmt.Errorf("error marshaling JSON: %w", err) - } - - metadata := req.ChainloopMetadata - tplData := &templateContent{ - WorkflowID: metadata.WorkflowID, - WorkflowName: metadata.WorkflowName, - WorkflowRunID: metadata.WorkflowRunID, - WorkflowProject: metadata.WorkflowProject, - RunnerLink: req.Input.Attestation.Predicate.GetRunLink(), + return fmt.Errorf("generating summary table: %w", err) } webhookURL := req.RegistrationInfo.Credentials.Password - if err := executeWebhook(webhookURL, config.Username, attestationJSON, renderContent(tplData)); err != nil { + if err := executeWebhook(webhookURL, config.Username, []byte(summary), "New Attestation Received"); err != nil { return fmt.Errorf("error executing webhook: %w", err) } @@ -183,7 +172,7 @@ func (i *Integration) Execute(_ context.Context, req *sdk.ExecutionRequest) erro // --boundary // Content-Disposition: form-data; name="files[0]"; filename="statement.json" // --boundary -func executeWebhook(webhookURL, usernameOverride string, jsonStatement []byte, msgContent string) error { +func executeWebhook(webhookURL, usernameOverride string, statement []byte, msgContent string) error { var b bytes.Buffer multipartWriter := multipart.NewWriter(&b) @@ -194,7 +183,7 @@ func executeWebhook(webhookURL, usernameOverride string, jsonStatement []byte, m Attachments: []payloadAttachment{ { ID: 0, - Filename: "attestation.json", + Filename: "statement.txt", }, }, } @@ -214,12 +203,12 @@ func executeWebhook(webhookURL, usernameOverride string, jsonStatement []byte, m } // attach attestation JSON - attachmentWriter, err := multipartWriter.CreateFormFile("files[0]", "statement.json") + attachmentWriter, err := multipartWriter.CreateFormFile("files[0]", "statement.txt") if err != nil { return fmt.Errorf("creating attachment form field: %w", err) } - if _, err := attachmentWriter.Write(jsonStatement); err != nil { + if _, err := attachmentWriter.Write(statement); err != nil { return fmt.Errorf("writing attachment form field: %w", err) } @@ -271,27 +260,3 @@ func validateExecuteRequest(req *sdk.ExecutionRequest) error { return nil } - -type templateContent struct { - WorkflowID, WorkflowName, WorkflowProject, WorkflowRunID, RunnerLink string -} - -func renderContent(metadata *templateContent) string { - t := template.Must(template.New("content").Parse(msgTemplate)) - - var b bytes.Buffer - if err := t.Execute(&b, metadata); err != nil { - return "" - } - - return strings.Trim(b.String(), "\n") -} - -const msgTemplate = ` -New attestation received! -- Workflow: {{.WorkflowProject}}/{{.WorkflowName}} -- Workflow Run: {{.WorkflowRunID}} -{{- if .RunnerLink }} -- Link to runner: {{.RunnerLink}} -{{end}} -` diff --git a/app/controlplane/plugins/core/discord-webhook/v1/discord_test.go b/app/controlplane/plugins/core/discord-webhook/v1/discord_test.go index eb514ba12..341c106e7 100644 --- a/app/controlplane/plugins/core/discord-webhook/v1/discord_test.go +++ b/app/controlplane/plugins/core/discord-webhook/v1/discord_test.go @@ -86,46 +86,6 @@ func TestValidateRegistrationInput(t *testing.T) { } } -func TestRenderContent(t *testing.T) { - testCases := []struct { - name string - input *templateContent - expected string - }{ - { - name: "all fields", - input: &templateContent{ - WorkflowRunID: "deadbeef", - WorkflowName: "test", - WorkflowProject: "project", - RunnerLink: "http://runner.io", - }, - expected: `New attestation received! -- Workflow: project/test -- Workflow Run: deadbeef -- Link to runner: http://runner.io`, - }, - { - name: "no runner link", - input: &templateContent{ - WorkflowRunID: "deadbeef", - WorkflowName: "test", - WorkflowProject: "project", - }, - expected: `New attestation received! -- Workflow: project/test -- Workflow Run: deadbeef`, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - actual := renderContent(tc.input) - assert.Equal(t, tc.expected, actual) - }) - } -} - func TestNewIntegration(t *testing.T) { _, err := New(nil) assert.NoError(t, err) diff --git a/app/controlplane/plugins/core/guac/v1/guac.go b/app/controlplane/plugins/core/guac/v1/guac.go index 72ac6b372..d4ba3d388 100644 --- a/app/controlplane/plugins/core/guac/v1/guac.go +++ b/app/controlplane/plugins/core/guac/v1/guac.go @@ -210,10 +210,10 @@ func uploadToBucket(ctx context.Context, bucket *storage.BucketHandle, filename w.ObjectAttrs.Metadata = map[string]string{ "author": "chainloop", - "workflowID": md.WorkflowID, - "workflowName": md.WorkflowName, - "workflowProject": md.WorkflowProject, - "workflowRunID": md.WorkflowRunID, + "workflowID": md.Workflow.ID, + "workflowName": md.Workflow.Name, + "workflowProject": md.Workflow.Project, + "workflowRunID": md.WorkflowRun.ID, "filename": filename, } diff --git a/app/controlplane/plugins/core/guac/v1/guac_test.go b/app/controlplane/plugins/core/guac/v1/guac_test.go index 2039bc593..d8e3b4ee4 100644 --- a/app/controlplane/plugins/core/guac/v1/guac_test.go +++ b/app/controlplane/plugins/core/guac/v1/guac_test.go @@ -56,10 +56,14 @@ func (s *testSuite) TestUpload() { var content = []byte("test") const fileName = "sbom.json" metadata := &sdk.ChainloopMetadata{ - WorkflowID: "wid", - WorkflowRunID: "wid", - WorkflowName: "name", - WorkflowProject: "project", + Workflow: &sdk.ChainloopMetadataWorkflow{ + ID: "wid", + Name: "name", + Project: "project", + }, + WorkflowRun: &sdk.ChainloopMetadataWorkflowRun{ + ID: "wid", + }, } // Perform the upload diff --git a/app/controlplane/plugins/core/oci-registry/v1/ociregistry.go b/app/controlplane/plugins/core/oci-registry/v1/ociregistry.go index f4cfaf283..913097b1e 100644 --- a/app/controlplane/plugins/core/oci-registry/v1/ociregistry.go +++ b/app/controlplane/plugins/core/oci-registry/v1/ociregistry.go @@ -175,7 +175,7 @@ func (i *Integration) Execute(ctx context.Context, req *sdk.ExecutionRequest) er return fmt.Errorf("creating OCI backend %w", err) } - i.Logger.Infow("msg", "Uploading attestation", "repo", registrationConfig.Repository, "workflowID", req.WorkflowID) + i.Logger.Infow("msg", "Uploading attestation", "repo", registrationConfig.Repository, "workflowID", req.Workflow.ID) // Perform the upload of the json marshalled attestation jsonContent, err := json.Marshal(req.Input.Attestation.Envelope) @@ -193,7 +193,7 @@ func (i *Integration) Execute(ctx context.Context, req *sdk.ExecutionRequest) er return fmt.Errorf("uploading the attestation: %w", err) } - i.Logger.Infow("msg", "Attestation uploaded", "repo", registrationConfig.Repository, "workflowID", req.WorkflowID) + i.Logger.Infow("msg", "Attestation uploaded", "repo", registrationConfig.Repository, "workflowID", req.Workflow.ID) return nil } diff --git a/app/controlplane/plugins/core/slack-webhook/v1/slack_webhook.go b/app/controlplane/plugins/core/slack-webhook/v1/slack_webhook.go index 162809b90..3a5dd5bd5 100644 --- a/app/controlplane/plugins/core/slack-webhook/v1/slack_webhook.go +++ b/app/controlplane/plugins/core/slack-webhook/v1/slack_webhook.go @@ -96,25 +96,15 @@ func (i *Integration) Execute(_ context.Context, req *sdk.ExecutionRequest) erro return fmt.Errorf("running validation: %w", err) } - attestationJSON, err := json.MarshalIndent(req.Input.Attestation.Statement, "", " ") + summary, err := sdk.SummaryTable(req) if err != nil { - return fmt.Errorf("error marshaling JSON: %w", err) - } - - metadata := req.ChainloopMetadata - // I was not able to make backticks work in the template - a := fmt.Sprintf("\n```\n%s\n```\n", string(attestationJSON)) - tplData := &templateContent{ - WorkflowID: metadata.WorkflowID, - WorkflowName: metadata.WorkflowName, - WorkflowRunID: metadata.WorkflowRunID, - WorkflowProject: metadata.WorkflowProject, - RunnerLink: req.Input.Attestation.Predicate.GetRunLink(), - Attestation: a, + return fmt.Errorf("error summarizing the request: %w", err) } + msg := fmt.Sprintf("\nNew attestation received!```\n%s\n```\n", summary) webhookURL := req.RegistrationInfo.Credentials.Password - if err := executeWebhook(webhookURL, renderContent(tplData)); err != nil { + + if err := executeWebhook(webhookURL, msg); err != nil { return fmt.Errorf("error executing webhook: %w", err) } diff --git a/app/controlplane/plugins/core/smtp/v1/extension.go b/app/controlplane/plugins/core/smtp/v1/extension.go index 0c4be8a4b..65a70d1af 100644 --- a/app/controlplane/plugins/core/smtp/v1/extension.go +++ b/app/controlplane/plugins/core/smtp/v1/extension.go @@ -17,7 +17,6 @@ package smtp import ( "context" - "encoding/json" "errors" "fmt" nsmtp "net/smtp" @@ -145,7 +144,7 @@ func (i *Integration) Execute(_ context.Context, req *sdk.ExecutionRequest) erro i.Logger.Info("execution requested") if err := validateExecuteRequest(req); err != nil { - return fmt.Errorf("running validation for workflow id %s: %w", req.WorkflowID, err) + return fmt.Errorf("running validation for workflow id %s: %w", req.Workflow.ID, err) } var rc *registrationState @@ -158,27 +157,16 @@ func (i *Integration) Execute(_ context.Context, req *sdk.ExecutionRequest) erro return errors.New("invalid attachment configuration") } - // marshal the statement - jsonBytes, err := json.MarshalIndent(req.Input.Attestation.Statement, "", " ") + summary, err := sdk.SummaryTable(req) if err != nil { - return fmt.Errorf("error marshaling JSON: %w", err) + return fmt.Errorf("generating summary table: %w", err) } // send the email to, from, user, password, host, port := rc.To, rc.From, rc.User, req.RegistrationInfo.Credentials.Password, rc.Host, rc.Port subject := "[chainloop] New workflow run finished successfully!" - tpl := `A new workflow run finished successfully! - -# Workflow: %s - -# in-toto statement: - %s - -This email has been delivered via integration %s version %s. - ` - body := fmt.Sprintf(tpl, req.WorkflowID, jsonBytes, i.Describe().ID, i.Describe().Version) - err = sendEmail(host, port, user, password, from, to, ac.CC, subject, body) - if err != nil { + if err := sendEmail(host, port, user, password, from, to, ac.CC, subject, + "
"+summary+"
"); err != nil { return fmt.Errorf("sending an email: %w", err) } @@ -209,6 +197,7 @@ func sendEmail(host string, port string, user, password, from, to, cc, subject, message := "From: " + from + "\n" + "To: " + to + "\n" + "CC: " + cc + "\n" + + "MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n" + "Subject: " + subject + "\n\n" + body diff --git a/app/controlplane/plugins/sdk/v1/fanout.go b/app/controlplane/plugins/sdk/v1/fanout.go index 8c2f0b8cf..ccc7d24ea 100644 --- a/app/controlplane/plugins/sdk/v1/fanout.go +++ b/app/controlplane/plugins/sdk/v1/fanout.go @@ -23,6 +23,7 @@ import ( "fmt" "io" "sort" + "time" crv1 "github.com/google/go-containerregistry/pkg/v1" @@ -114,11 +115,21 @@ type AttachmentResponse struct { } type ChainloopMetadata struct { - WorkflowID string - WorkflowName string - WorkflowProject string + Workflow *ChainloopMetadataWorkflow + WorkflowRun *ChainloopMetadataWorkflowRun +} + +type ChainloopMetadataWorkflowRun struct { + ID string + State string + StartedAt time.Time + FinishedAt time.Time + RunnerType string + RunURL string +} - WorkflowRunID string +type ChainloopMetadataWorkflow struct { + ID, Name, Team, Project string } // ExecutionRequest is the request to execute the integration diff --git a/app/controlplane/plugins/sdk/v1/helpers.go b/app/controlplane/plugins/sdk/v1/helpers.go new file mode 100644 index 000000000..3b9df7e03 --- /dev/null +++ b/app/controlplane/plugins/sdk/v1/helpers.go @@ -0,0 +1,175 @@ +// +// Copyright 2023 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 sdk + +import ( + "fmt" + "sort" + "time" + + "github.com/chainloop-dev/chainloop/internal/attestation/renderer/chainloop" + "github.com/jedib0t/go-pretty/v6/table" +) + +type renderer struct { + render func(t table.Writer) string + format string +} + +type RenderOpt func(r *renderer) error + +func WithFormat(format string) RenderOpt { + return func(r *renderer) error { + switch format { + case "text": + r.render = func(t table.Writer) string { + return t.Render() + } + case "markdown": + r.render = func(t table.Writer) string { + return t.RenderMarkdown() + } + case "html": + r.render = func(t table.Writer) string { + return t.RenderHTML() + } + default: + return fmt.Errorf("unsupported format %s", format) + } + + r.format = format + return nil + } +} + +func newRenderer(opts ...RenderOpt) (*renderer, error) { + r := &renderer{ + render: func(t table.Writer) string { + return t.Render() + }, format: "text", + } + + for _, opt := range opts { + if err := opt(r); err != nil { + return nil, err + } + } + + return r, nil +} + +func (r *renderer) summaryTable(m *ChainloopMetadata, predicate chainloop.NormalizablePredicate) (string, error) { + tw := table.NewWriter() + tw.SetStyle(table.StyleLight) + + if m == nil || m.Workflow == nil { + return "", fmt.Errorf("workflow metadata is missing") + } + + if predicate == nil { + return "", fmt.Errorf("predicate is nil") + } + + tw.SetTitle("Workflow") + tw.AppendRow(table.Row{"ID", m.Workflow.ID}) + tw.AppendRow(table.Row{"Name", m.Workflow.Name}) + tw.AppendRow(table.Row{"Team", m.Workflow.Team}) + tw.AppendRow(table.Row{"Project", m.Workflow.Project}) + tw.AppendSeparator() + + wr := m.WorkflowRun + if wr == nil { + return "", fmt.Errorf("workflow run metadata is missing") + } + + tw.AppendRow(table.Row{"Workflow Run"}) + tw.AppendSeparator() + tw.AppendRow(table.Row{"ID", wr.ID}) + tw.AppendRow(table.Row{"Started At", wr.StartedAt.Format(time.RFC822)}) + tw.AppendRow(table.Row{"Finished At", wr.FinishedAt.Format(time.RFC822)}) + tw.AppendRow(table.Row{"State", wr.State}) + tw.AppendRow(table.Row{"Runner Link", wr.RunURL}) + + var result = r.render(tw) + + // Materials + materials := predicate.GetMaterials() + if len(materials) > 0 { + mt := table.NewWriter() + mt.SetStyle(table.StyleLight) + + mt.SetTitle("Materials") + mt.AppendHeader(table.Row{"Name", "Type", "Value"}) + + for _, m := range materials { + // Initialize simply with the value + displayValue := m.Value + // Override if there is a hash attached + if m.Hash != nil { + name := m.Value + if m.EmbeddedInline || m.UploadedToCAS { + name = m.Filename + } + + displayValue = fmt.Sprintf("%s@%s", name, m.Hash) + } + + row := table.Row{m.Name, m.Type, displayValue} + mt.AppendRow(row) + } + + result += "\n" + r.render(mt) + } + + // Env variables + envVars := predicate.GetEnvVars() + if len(envVars) > 0 { + mt := table.NewWriter() + mt.SetStyle(table.StyleLight) + mt.SetTitle("Environment Variables") + + header := table.Row{"Name", "Value"} + mt.AppendHeader(header) + + // sort env vars by name + var keys []string + for k := range envVars { + keys = append(keys, k) + } + + sort.Strings(keys) + + for _, k := range keys { + v := envVars[k] + mt.AppendRow(table.Row{k, v}) + } + + result += "\n" + r.render(mt) + } + + result += fmt.Sprintf("\n\nGet Full Attestation\n\n$ chainloop workflow run describe --id %s -o statement", wr.ID) + + return result, nil +} + +func SummaryTable(req *ExecutionRequest, opts ...RenderOpt) (string, error) { + renderer, err := newRenderer(opts...) + if err != nil { + return "", err + } + + return renderer.summaryTable(req.ChainloopMetadata, req.Input.Attestation.Predicate) +} diff --git a/app/controlplane/plugins/sdk/v1/helpers_test.go b/app/controlplane/plugins/sdk/v1/helpers_test.go new file mode 100644 index 000000000..33c401b56 --- /dev/null +++ b/app/controlplane/plugins/sdk/v1/helpers_test.go @@ -0,0 +1,127 @@ +// +// Copyright 2023 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 sdk + +import ( + "encoding/json" + "os" + "testing" + "time" + + "github.com/chainloop-dev/chainloop/internal/attestation/renderer/chainloop" + "github.com/secure-systems-lab/go-securesystemslib/dsse" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +func (s *helperTestSuite) TestSummaryTable() { + testCases := []struct { + name string + inputPath string + renderOpts []RenderOpt + outputPath string + hasError bool + }{ + { + name: "full text", + inputPath: "testdata/attestations/full.json", + outputPath: "testdata/attestations/full.txt", + }, + { + name: "full markdown", + inputPath: "testdata/attestations/full.json", + renderOpts: []RenderOpt{WithFormat("markdown")}, + outputPath: "testdata/attestations/full.md", + }, + { + name: "invalid format", + inputPath: "testdata/attestations/full.json", + renderOpts: []RenderOpt{WithFormat("invalid")}, + outputPath: "testdata/attestations/full.md", + hasError: true, + }, + } + + for _, tc := range testCases { + s.T().Run(tc.name, func(t *testing.T) { + renderer, err := newRenderer(tc.renderOpts...) + if tc.hasError { + require.Error(t, err) + return + } + + require.NoError(t, err) + want, err := os.ReadFile(tc.outputPath) + require.NoError(t, err) + + predicate, err := testPredicate(tc.inputPath) + require.NoError(t, err) + got, err := renderer.summaryTable(s.m, predicate) + if tc.hasError { + require.Error(t, err) + return + } + + require.NoError(t, err) + assert.Equal(s.T(), got, string(want)) + }) + } +} + +func TestHelpers(t *testing.T) { + suite.Run(t, new(helperTestSuite)) +} + +func (s *helperTestSuite) SetupTest() { + date, _ := time.Parse("2006-01-02", "2021-11-22") + s.m = &ChainloopMetadata{ + Workflow: &ChainloopMetadataWorkflow{ + ID: "deadbeef", + Name: "test-workflow", + Project: "test-project", + Team: "test-team", + }, + WorkflowRun: &ChainloopMetadataWorkflowRun{ + ID: "beefdead", + State: "success", + StartedAt: date, + FinishedAt: date.Add(10 * time.Minute), + RunnerType: "github-actions", + RunURL: "chainloop.dev/runner", + }, + } +} + +type helperTestSuite struct { + suite.Suite + m *ChainloopMetadata +} + +func testPredicate(filePath string) (chainloop.NormalizablePredicate, error) { + var envelope dsse.Envelope + content, err := os.ReadFile(filePath) + if err != nil { + return nil, err + } + + err = json.Unmarshal(content, &envelope) + if err != nil { + return nil, err + } + + return chainloop.ExtractPredicate(&envelope) +} diff --git a/app/controlplane/plugins/sdk/v1/plugin/api/fanout.pb.go b/app/controlplane/plugins/sdk/v1/plugin/api/fanout.pb.go index 4717762ec..04fe4b45b 100644 --- a/app/controlplane/plugins/sdk/v1/plugin/api/fanout.pb.go +++ b/app/controlplane/plugins/sdk/v1/plugin/api/fanout.pb.go @@ -24,6 +24,7 @@ package api import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" reflect "reflect" sync "sync" ) @@ -1030,10 +1031,8 @@ type ExecuteRequest_Metadata struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - WorkflowId string `protobuf:"bytes,1,opt,name=workflow_id,json=workflowId,proto3" json:"workflow_id,omitempty"` - WorkflowName string `protobuf:"bytes,2,opt,name=workflow_name,json=workflowName,proto3" json:"workflow_name,omitempty"` - WorkflowProject string `protobuf:"bytes,3,opt,name=workflow_project,json=workflowProject,proto3" json:"workflow_project,omitempty"` - WorkflowRunId string `protobuf:"bytes,4,opt,name=workflow_run_id,json=workflowRunId,proto3" json:"workflow_run_id,omitempty"` + Workflow *ExecuteRequest_Metadata_Workflow `protobuf:"bytes,1,opt,name=workflow,proto3" json:"workflow,omitempty"` + WorkflowRun *ExecuteRequest_Metadata_WorkflowRun `protobuf:"bytes,2,opt,name=workflow_run,json=workflowRun,proto3" json:"workflow_run,omitempty"` } func (x *ExecuteRequest_Metadata) Reset() { @@ -1068,194 +1067,360 @@ func (*ExecuteRequest_Metadata) Descriptor() ([]byte, []int) { return file_fanout_proto_rawDescGZIP(), []int{12, 1} } -func (x *ExecuteRequest_Metadata) GetWorkflowId() string { +func (x *ExecuteRequest_Metadata) GetWorkflow() *ExecuteRequest_Metadata_Workflow { if x != nil { - return x.WorkflowId + return x.Workflow + } + return nil +} + +func (x *ExecuteRequest_Metadata) GetWorkflowRun() *ExecuteRequest_Metadata_WorkflowRun { + if x != nil { + return x.WorkflowRun + } + return nil +} + +type ExecuteRequest_Metadata_Workflow struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Team string `protobuf:"bytes,3,opt,name=team,proto3" json:"team,omitempty"` + Project string `protobuf:"bytes,4,opt,name=project,proto3" json:"project,omitempty"` +} + +func (x *ExecuteRequest_Metadata_Workflow) Reset() { + *x = ExecuteRequest_Metadata_Workflow{} + if protoimpl.UnsafeEnabled { + mi := &file_fanout_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ExecuteRequest_Metadata_Workflow) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExecuteRequest_Metadata_Workflow) ProtoMessage() {} + +func (x *ExecuteRequest_Metadata_Workflow) ProtoReflect() protoreflect.Message { + mi := &file_fanout_proto_msgTypes[19] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExecuteRequest_Metadata_Workflow.ProtoReflect.Descriptor instead. +func (*ExecuteRequest_Metadata_Workflow) Descriptor() ([]byte, []int) { + return file_fanout_proto_rawDescGZIP(), []int{12, 1, 0} +} + +func (x *ExecuteRequest_Metadata_Workflow) GetId() string { + if x != nil { + return x.Id } return "" } -func (x *ExecuteRequest_Metadata) GetWorkflowName() string { +func (x *ExecuteRequest_Metadata_Workflow) GetName() string { if x != nil { - return x.WorkflowName + return x.Name } return "" } -func (x *ExecuteRequest_Metadata) GetWorkflowProject() string { +func (x *ExecuteRequest_Metadata_Workflow) GetTeam() string { if x != nil { - return x.WorkflowProject + return x.Team } return "" } -func (x *ExecuteRequest_Metadata) GetWorkflowRunId() string { +func (x *ExecuteRequest_Metadata_Workflow) GetProject() string { if x != nil { - return x.WorkflowRunId + return x.Project } return "" } +type ExecuteRequest_Metadata_WorkflowRun struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + State string `protobuf:"bytes,2,opt,name=state,proto3" json:"state,omitempty"` + RunnerType string `protobuf:"bytes,3,opt,name=runner_type,json=runnerType,proto3" json:"runner_type,omitempty"` + RunUrl string `protobuf:"bytes,4,opt,name=run_url,json=runUrl,proto3" json:"run_url,omitempty"` + StartedAt *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=started_at,json=startedAt,proto3" json:"started_at,omitempty"` + FinishedAt *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=finished_at,json=finishedAt,proto3" json:"finished_at,omitempty"` +} + +func (x *ExecuteRequest_Metadata_WorkflowRun) Reset() { + *x = ExecuteRequest_Metadata_WorkflowRun{} + if protoimpl.UnsafeEnabled { + mi := &file_fanout_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ExecuteRequest_Metadata_WorkflowRun) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExecuteRequest_Metadata_WorkflowRun) ProtoMessage() {} + +func (x *ExecuteRequest_Metadata_WorkflowRun) ProtoReflect() protoreflect.Message { + mi := &file_fanout_proto_msgTypes[20] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExecuteRequest_Metadata_WorkflowRun.ProtoReflect.Descriptor instead. +func (*ExecuteRequest_Metadata_WorkflowRun) Descriptor() ([]byte, []int) { + return file_fanout_proto_rawDescGZIP(), []int{12, 1, 1} +} + +func (x *ExecuteRequest_Metadata_WorkflowRun) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *ExecuteRequest_Metadata_WorkflowRun) GetState() string { + if x != nil { + return x.State + } + return "" +} + +func (x *ExecuteRequest_Metadata_WorkflowRun) GetRunnerType() string { + if x != nil { + return x.RunnerType + } + return "" +} + +func (x *ExecuteRequest_Metadata_WorkflowRun) GetRunUrl() string { + if x != nil { + return x.RunUrl + } + return "" +} + +func (x *ExecuteRequest_Metadata_WorkflowRun) GetStartedAt() *timestamppb.Timestamp { + if x != nil { + return x.StartedAt + } + return nil +} + +func (x *ExecuteRequest_Metadata_WorkflowRun) GetFinishedAt() *timestamppb.Timestamp { + if x != nil { + return x.FinishedAt + } + return nil +} + var File_fanout_proto protoreflect.FileDescriptor var file_fanout_proto_rawDesc = []byte{ 0x0a, 0x0c, 0x66, 0x61, 0x6e, 0x6f, 0x75, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03, - 0x61, 0x70, 0x69, 0x22, 0x11, 0x0a, 0x0f, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x81, 0x02, 0x0a, 0x10, 0x44, 0x65, 0x73, 0x63, 0x72, - 0x69, 0x62, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, - 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, - 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x38, 0x0a, 0x18, 0x72, 0x65, 0x67, 0x69, 0x73, - 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x5f, 0x73, 0x63, 0x68, - 0x65, 0x6d, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x16, 0x72, 0x65, 0x67, 0x69, 0x73, - 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x73, 0x6f, 0x6e, 0x53, 0x63, 0x68, 0x65, 0x6d, - 0x61, 0x12, 0x34, 0x0a, 0x16, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x5f, - 0x6a, 0x73, 0x6f, 0x6e, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x14, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x4a, 0x73, 0x6f, - 0x6e, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, 0x31, 0x0a, 0x14, 0x73, 0x75, 0x62, 0x73, 0x63, - 0x72, 0x69, 0x62, 0x65, 0x64, 0x5f, 0x6d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x73, 0x18, - 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x13, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, - 0x64, 0x4d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x73, 0x22, 0x40, 0x0a, 0x1b, 0x56, 0x61, - 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x6a, 0x73, 0x6f, - 0x6e, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x0b, 0x6a, 0x73, 0x6f, 0x6e, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0x4a, 0x0a, 0x1c, - 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, - 0x76, 0x61, 0x6c, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x76, 0x61, 0x6c, - 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x3e, 0x0a, 0x19, 0x56, 0x61, 0x6c, 0x69, - 0x64, 0x61, 0x74, 0x65, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x6a, 0x73, 0x6f, 0x6e, 0x5f, 0x70, 0x61, - 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x6a, 0x73, 0x6f, - 0x6e, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0x48, 0x0a, 0x1a, 0x56, 0x61, 0x6c, 0x69, - 0x64, 0x61, 0x74, 0x65, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, - 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, - 0x6f, 0x72, 0x22, 0x0f, 0x0a, 0x0d, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x22, 0x26, 0x0a, 0x0e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x2b, 0x0a, 0x0f, 0x52, - 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, - 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0xd6, 0x01, 0x0a, 0x10, 0x52, 0x65, 0x67, - 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, - 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x43, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, - 0x6c, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, - 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, - 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x52, 0x0b, 0x63, 0x72, 0x65, - 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x1a, 0x57, 0x0a, 0x0b, 0x43, 0x72, 0x65, 0x64, - 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, - 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, - 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, - 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, - 0x64, 0x22, 0x6d, 0x0a, 0x0d, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x42, 0x0a, 0x11, + 0x61, 0x70, 0x69, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x11, 0x0a, 0x0f, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x81, 0x02, 0x0a, 0x10, 0x44, 0x65, 0x73, 0x63, + 0x72, 0x69, 0x62, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x38, 0x0a, 0x18, 0x72, 0x65, 0x67, 0x69, + 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x5f, 0x73, 0x63, + 0x68, 0x65, 0x6d, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x16, 0x72, 0x65, 0x67, 0x69, + 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x73, 0x6f, 0x6e, 0x53, 0x63, 0x68, 0x65, + 0x6d, 0x61, 0x12, 0x34, 0x0a, 0x16, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, + 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x14, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x4a, 0x73, + 0x6f, 0x6e, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, 0x31, 0x0a, 0x14, 0x73, 0x75, 0x62, 0x73, + 0x63, 0x72, 0x69, 0x62, 0x65, 0x64, 0x5f, 0x6d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x73, + 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x13, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, + 0x65, 0x64, 0x4d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x73, 0x22, 0x40, 0x0a, 0x1b, 0x56, + 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x6a, 0x73, + 0x6f, 0x6e, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x0b, 0x6a, 0x73, 0x6f, 0x6e, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0x4a, 0x0a, + 0x1c, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, + 0x05, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x76, 0x61, + 0x6c, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x3e, 0x0a, 0x19, 0x56, 0x61, 0x6c, + 0x69, 0x64, 0x61, 0x74, 0x65, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x6a, 0x73, 0x6f, 0x6e, 0x5f, 0x70, + 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x6a, 0x73, + 0x6f, 0x6e, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0x48, 0x0a, 0x1a, 0x56, 0x61, 0x6c, + 0x69, 0x64, 0x61, 0x74, 0x65, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x12, 0x14, 0x0a, + 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, + 0x72, 0x6f, 0x72, 0x22, 0x0f, 0x0a, 0x0d, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x22, 0x26, 0x0a, 0x0e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x2b, 0x0a, 0x0f, + 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x18, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0xd6, 0x01, 0x0a, 0x10, 0x52, 0x65, + 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, + 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x43, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, + 0x61, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x2e, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x52, 0x0b, 0x63, 0x72, + 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x1a, 0x57, 0x0a, 0x0b, 0x43, 0x72, 0x65, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, + 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, + 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, + 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, + 0x72, 0x64, 0x22, 0x6d, 0x0a, 0x0d, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x42, 0x0a, + 0x11, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, + 0x66, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, + 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, + 0x10, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x66, + 0x6f, 0x22, 0x36, 0x0a, 0x0e, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xd9, 0x07, 0x0a, 0x0e, 0x45, 0x78, + 0x65, 0x63, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x42, 0x0a, 0x11, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x66, - 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, + 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x10, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x66, 0x6f, - 0x22, 0x36, 0x0a, 0x0e, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x9c, 0x05, 0x0a, 0x0e, 0x45, 0x78, 0x65, - 0x63, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x42, 0x0a, 0x11, 0x72, - 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x66, 0x6f, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x67, - 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x10, 0x72, - 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x12, - 0x3c, 0x0a, 0x0f, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x6e, - 0x66, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x41, - 0x74, 0x74, 0x61, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x0e, 0x61, - 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1a, 0x0a, - 0x08, 0x65, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x08, 0x65, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x12, 0x44, 0x0a, 0x09, 0x6d, 0x61, 0x74, - 0x65, 0x72, 0x69, 0x61, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x61, - 0x70, 0x69, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x2e, 0x4e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x4d, 0x61, 0x74, 0x65, - 0x72, 0x69, 0x61, 0x6c, 0x52, 0x09, 0x6d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x73, 0x12, - 0x38, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, - 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x1a, 0xc5, 0x01, 0x0a, 0x12, 0x4e, 0x6f, - 0x72, 0x6d, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x4d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, - 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, - 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, - 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, - 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x26, 0x0a, 0x0f, - 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x64, 0x5f, 0x74, 0x6f, 0x5f, 0x63, 0x61, 0x73, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x64, 0x54, - 0x6f, 0x43, 0x61, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, - 0x65, 0x1a, 0xa3, 0x01, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1f, - 0x0a, 0x0b, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0a, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x49, 0x64, 0x12, - 0x23, 0x0a, 0x0d, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, - 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, - 0x5f, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, - 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x12, - 0x26, 0x0a, 0x0f, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x72, 0x75, 0x6e, 0x5f, - 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, - 0x6f, 0x77, 0x52, 0x75, 0x6e, 0x49, 0x64, 0x22, 0x11, 0x0a, 0x0f, 0x45, 0x78, 0x65, 0x63, 0x75, - 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3c, 0x0a, 0x15, 0x49, 0x73, - 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x64, 0x54, 0x6f, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x6d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x5f, - 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6d, 0x61, 0x74, 0x65, - 0x72, 0x69, 0x61, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x22, 0x38, 0x0a, 0x16, 0x49, 0x73, 0x53, 0x75, - 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x64, 0x54, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, - 0x65, 0x64, 0x32, 0x9c, 0x04, 0x0a, 0x0d, 0x46, 0x61, 0x6e, 0x6f, 0x75, 0x74, 0x53, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x12, 0x37, 0x0a, 0x08, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, - 0x12, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x73, - 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5b, 0x0a, - 0x14, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x56, 0x61, 0x6c, 0x69, + 0x12, 0x3c, 0x0a, 0x0f, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, + 0x6e, 0x66, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x0e, + 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1a, + 0x0a, 0x08, 0x65, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x08, 0x65, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x12, 0x44, 0x0a, 0x09, 0x6d, 0x61, + 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x2e, 0x4e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x4d, 0x61, 0x74, + 0x65, 0x72, 0x69, 0x61, 0x6c, 0x52, 0x09, 0x6d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x73, + 0x12, 0x38, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x1a, 0xc5, 0x01, 0x0a, 0x12, 0x4e, + 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x4d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, + 0x6c, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, + 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, + 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, + 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x26, 0x0a, + 0x0f, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x64, 0x5f, 0x74, 0x6f, 0x5f, 0x63, 0x61, 0x73, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x64, + 0x54, 0x6f, 0x43, 0x61, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x4e, 0x61, + 0x6d, 0x65, 0x1a, 0xe0, 0x03, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, + 0x41, 0x0a, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x25, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, + 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, + 0x6f, 0x77, 0x12, 0x4b, 0x0a, 0x0c, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x72, + 0x75, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x45, + 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, + 0x75, 0x6e, 0x52, 0x0b, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x75, 0x6e, 0x1a, + 0x5c, 0x0a, 0x08, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x0e, 0x0a, 0x02, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, + 0x12, 0x0a, 0x04, 0x74, 0x65, 0x61, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, + 0x65, 0x61, 0x6d, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x1a, 0xe5, 0x01, + 0x0a, 0x0b, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x75, 0x6e, 0x12, 0x0e, 0x0a, + 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, + 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, + 0x61, 0x74, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x5f, 0x74, 0x79, + 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, + 0x54, 0x79, 0x70, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x75, 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x75, 0x6e, 0x55, 0x72, 0x6c, 0x12, 0x39, 0x0a, + 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x73, + 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x3b, 0x0a, 0x0b, 0x66, 0x69, 0x6e, 0x69, + 0x73, 0x68, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x66, 0x69, 0x6e, 0x69, 0x73, + 0x68, 0x65, 0x64, 0x41, 0x74, 0x22, 0x11, 0x0a, 0x0f, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3c, 0x0a, 0x15, 0x49, 0x73, 0x53, 0x75, + 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x64, 0x54, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x6d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x5f, 0x74, 0x79, + 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6d, 0x61, 0x74, 0x65, 0x72, 0x69, + 0x61, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x22, 0x38, 0x0a, 0x16, 0x49, 0x73, 0x53, 0x75, 0x62, 0x73, + 0x63, 0x72, 0x69, 0x62, 0x65, 0x64, 0x54, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x64, + 0x32, 0x9c, 0x04, 0x0a, 0x0d, 0x46, 0x61, 0x6e, 0x6f, 0x75, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x12, 0x37, 0x0a, 0x08, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x12, 0x14, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x62, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5b, 0x0a, 0x14, 0x56, + 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, + 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x56, 0x61, - 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x55, 0x0a, 0x12, 0x56, 0x61, - 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, - 0x12, 0x1e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x41, - 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x41, - 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x31, 0x0a, 0x06, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x12, 0x2e, 0x61, 0x70, - 0x69, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x13, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0e, 0x49, 0x73, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, - 0x69, 0x62, 0x65, 0x64, 0x54, 0x6f, 0x12, 0x1a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x73, 0x53, - 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x64, 0x54, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x73, 0x53, 0x75, 0x62, 0x73, 0x63, - 0x72, 0x69, 0x62, 0x65, 0x64, 0x54, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x37, 0x0a, 0x08, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x14, 0x2e, 0x61, 0x70, - 0x69, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x31, 0x0a, 0x06, 0x41, 0x74, 0x74, 0x61, - 0x63, 0x68, 0x12, 0x12, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x74, 0x74, - 0x61, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x07, 0x45, - 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x12, 0x13, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x45, 0x78, 0x65, - 0x63, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x61, 0x70, - 0x69, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x42, 0x53, 0x5a, 0x51, 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, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, - 0x2f, 0x73, 0x64, 0x6b, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2f, 0x61, - 0x70, 0x69, 0x3b, 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x55, 0x0a, 0x12, 0x56, 0x61, 0x6c, 0x69, + 0x64, 0x61, 0x74, 0x65, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1e, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x41, 0x74, 0x74, + 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x41, 0x74, 0x74, + 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x31, 0x0a, 0x06, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x12, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0e, 0x49, 0x73, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, + 0x65, 0x64, 0x54, 0x6f, 0x12, 0x1a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x73, 0x53, 0x75, 0x62, + 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x64, 0x54, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x73, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, + 0x62, 0x65, 0x64, 0x54, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, + 0x08, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x31, 0x0a, 0x06, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, + 0x12, 0x12, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x74, 0x74, 0x61, 0x63, + 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x07, 0x45, 0x78, 0x65, + 0x63, 0x75, 0x74, 0x65, 0x12, 0x13, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, + 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, + 0x53, 0x5a, 0x51, 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, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x2f, 0x73, + 0x64, 0x6b, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2f, 0x61, 0x70, 0x69, + 0x3b, 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1270,27 +1435,30 @@ func file_fanout_proto_rawDescGZIP() []byte { return file_fanout_proto_rawDescData } -var file_fanout_proto_msgTypes = make([]protoimpl.MessageInfo, 19) +var file_fanout_proto_msgTypes = make([]protoimpl.MessageInfo, 21) var file_fanout_proto_goTypes = []interface{}{ - (*DescribeRequest)(nil), // 0: api.DescribeRequest - (*DescribeResponse)(nil), // 1: api.DescribeResponse - (*ValidateRegistrationRequest)(nil), // 2: api.ValidateRegistrationRequest - (*ValidateRegistrationResponse)(nil), // 3: api.ValidateRegistrationResponse - (*ValidateAttachmentRequest)(nil), // 4: api.ValidateAttachmentRequest - (*ValidateAttachmentResponse)(nil), // 5: api.ValidateAttachmentResponse - (*StringRequest)(nil), // 6: api.StringRequest - (*StringResponse)(nil), // 7: api.StringResponse - (*RegisterRequest)(nil), // 8: api.RegisterRequest - (*RegisterResponse)(nil), // 9: api.RegisterResponse - (*AttachRequest)(nil), // 10: api.AttachRequest - (*AttachResponse)(nil), // 11: api.AttachResponse - (*ExecuteRequest)(nil), // 12: api.ExecuteRequest - (*ExecuteResponse)(nil), // 13: api.ExecuteResponse - (*IsSubscribedToRequest)(nil), // 14: api.IsSubscribedToRequest - (*IsSubscribedToResponse)(nil), // 15: api.IsSubscribedToResponse - (*RegisterResponse_Credentials)(nil), // 16: api.RegisterResponse.Credentials - (*ExecuteRequest_NormalizedMaterial)(nil), // 17: api.ExecuteRequest.NormalizedMaterial - (*ExecuteRequest_Metadata)(nil), // 18: api.ExecuteRequest.Metadata + (*DescribeRequest)(nil), // 0: api.DescribeRequest + (*DescribeResponse)(nil), // 1: api.DescribeResponse + (*ValidateRegistrationRequest)(nil), // 2: api.ValidateRegistrationRequest + (*ValidateRegistrationResponse)(nil), // 3: api.ValidateRegistrationResponse + (*ValidateAttachmentRequest)(nil), // 4: api.ValidateAttachmentRequest + (*ValidateAttachmentResponse)(nil), // 5: api.ValidateAttachmentResponse + (*StringRequest)(nil), // 6: api.StringRequest + (*StringResponse)(nil), // 7: api.StringResponse + (*RegisterRequest)(nil), // 8: api.RegisterRequest + (*RegisterResponse)(nil), // 9: api.RegisterResponse + (*AttachRequest)(nil), // 10: api.AttachRequest + (*AttachResponse)(nil), // 11: api.AttachResponse + (*ExecuteRequest)(nil), // 12: api.ExecuteRequest + (*ExecuteResponse)(nil), // 13: api.ExecuteResponse + (*IsSubscribedToRequest)(nil), // 14: api.IsSubscribedToRequest + (*IsSubscribedToResponse)(nil), // 15: api.IsSubscribedToResponse + (*RegisterResponse_Credentials)(nil), // 16: api.RegisterResponse.Credentials + (*ExecuteRequest_NormalizedMaterial)(nil), // 17: api.ExecuteRequest.NormalizedMaterial + (*ExecuteRequest_Metadata)(nil), // 18: api.ExecuteRequest.Metadata + (*ExecuteRequest_Metadata_Workflow)(nil), // 19: api.ExecuteRequest.Metadata.Workflow + (*ExecuteRequest_Metadata_WorkflowRun)(nil), // 20: api.ExecuteRequest.Metadata.WorkflowRun + (*timestamppb.Timestamp)(nil), // 21: google.protobuf.Timestamp } var file_fanout_proto_depIdxs = []int32{ 16, // 0: api.RegisterResponse.credentials:type_name -> api.RegisterResponse.Credentials @@ -1299,27 +1467,31 @@ var file_fanout_proto_depIdxs = []int32{ 11, // 3: api.ExecuteRequest.attachment_info:type_name -> api.AttachResponse 17, // 4: api.ExecuteRequest.materials:type_name -> api.ExecuteRequest.NormalizedMaterial 18, // 5: api.ExecuteRequest.metadata:type_name -> api.ExecuteRequest.Metadata - 0, // 6: api.FanoutService.Describe:input_type -> api.DescribeRequest - 2, // 7: api.FanoutService.ValidateRegistration:input_type -> api.ValidateRegistrationRequest - 4, // 8: api.FanoutService.ValidateAttachment:input_type -> api.ValidateAttachmentRequest - 6, // 9: api.FanoutService.String:input_type -> api.StringRequest - 14, // 10: api.FanoutService.IsSubscribedTo:input_type -> api.IsSubscribedToRequest - 8, // 11: api.FanoutService.Register:input_type -> api.RegisterRequest - 10, // 12: api.FanoutService.Attach:input_type -> api.AttachRequest - 12, // 13: api.FanoutService.Execute:input_type -> api.ExecuteRequest - 1, // 14: api.FanoutService.Describe:output_type -> api.DescribeResponse - 3, // 15: api.FanoutService.ValidateRegistration:output_type -> api.ValidateRegistrationResponse - 5, // 16: api.FanoutService.ValidateAttachment:output_type -> api.ValidateAttachmentResponse - 7, // 17: api.FanoutService.String:output_type -> api.StringResponse - 15, // 18: api.FanoutService.IsSubscribedTo:output_type -> api.IsSubscribedToResponse - 9, // 19: api.FanoutService.Register:output_type -> api.RegisterResponse - 11, // 20: api.FanoutService.Attach:output_type -> api.AttachResponse - 13, // 21: api.FanoutService.Execute:output_type -> api.ExecuteResponse - 14, // [14:22] is the sub-list for method output_type - 6, // [6:14] is the sub-list for method input_type - 6, // [6:6] is the sub-list for extension type_name - 6, // [6:6] is the sub-list for extension extendee - 0, // [0:6] is the sub-list for field type_name + 19, // 6: api.ExecuteRequest.Metadata.workflow:type_name -> api.ExecuteRequest.Metadata.Workflow + 20, // 7: api.ExecuteRequest.Metadata.workflow_run:type_name -> api.ExecuteRequest.Metadata.WorkflowRun + 21, // 8: api.ExecuteRequest.Metadata.WorkflowRun.started_at:type_name -> google.protobuf.Timestamp + 21, // 9: api.ExecuteRequest.Metadata.WorkflowRun.finished_at:type_name -> google.protobuf.Timestamp + 0, // 10: api.FanoutService.Describe:input_type -> api.DescribeRequest + 2, // 11: api.FanoutService.ValidateRegistration:input_type -> api.ValidateRegistrationRequest + 4, // 12: api.FanoutService.ValidateAttachment:input_type -> api.ValidateAttachmentRequest + 6, // 13: api.FanoutService.String:input_type -> api.StringRequest + 14, // 14: api.FanoutService.IsSubscribedTo:input_type -> api.IsSubscribedToRequest + 8, // 15: api.FanoutService.Register:input_type -> api.RegisterRequest + 10, // 16: api.FanoutService.Attach:input_type -> api.AttachRequest + 12, // 17: api.FanoutService.Execute:input_type -> api.ExecuteRequest + 1, // 18: api.FanoutService.Describe:output_type -> api.DescribeResponse + 3, // 19: api.FanoutService.ValidateRegistration:output_type -> api.ValidateRegistrationResponse + 5, // 20: api.FanoutService.ValidateAttachment:output_type -> api.ValidateAttachmentResponse + 7, // 21: api.FanoutService.String:output_type -> api.StringResponse + 15, // 22: api.FanoutService.IsSubscribedTo:output_type -> api.IsSubscribedToResponse + 9, // 23: api.FanoutService.Register:output_type -> api.RegisterResponse + 11, // 24: api.FanoutService.Attach:output_type -> api.AttachResponse + 13, // 25: api.FanoutService.Execute:output_type -> api.ExecuteResponse + 18, // [18:26] is the sub-list for method output_type + 10, // [10:18] is the sub-list for method input_type + 10, // [10:10] is the sub-list for extension type_name + 10, // [10:10] is the sub-list for extension extendee + 0, // [0:10] is the sub-list for field type_name } func init() { file_fanout_proto_init() } @@ -1556,6 +1728,30 @@ func file_fanout_proto_init() { return nil } } + file_fanout_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ExecuteRequest_Metadata_Workflow); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_fanout_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ExecuteRequest_Metadata_WorkflowRun); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -1563,7 +1759,7 @@ func file_fanout_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_fanout_proto_rawDesc, NumEnums: 0, - NumMessages: 19, + NumMessages: 21, NumExtensions: 0, NumServices: 1, }, diff --git a/app/controlplane/plugins/sdk/v1/plugin/api/fanout.proto b/app/controlplane/plugins/sdk/v1/plugin/api/fanout.proto index c55924296..bc069032d 100644 --- a/app/controlplane/plugins/sdk/v1/plugin/api/fanout.proto +++ b/app/controlplane/plugins/sdk/v1/plugin/api/fanout.proto @@ -19,6 +19,8 @@ package api; option go_package = "github.com/chainloop-dev/chainloop/app/controlplane/plugins/sdk/v1/plugin/api;api"; +import "google/protobuf/timestamp.proto"; + service FanoutService { // Core / Shared rpc Describe(DescribeRequest) returns (DescribeResponse); @@ -114,10 +116,24 @@ message ExecuteRequest { } message Metadata { - string workflow_id = 1; - string workflow_name = 2; - string workflow_project = 3; - string workflow_run_id = 4; + Workflow workflow = 1; + WorkflowRun workflow_run = 2; + + message Workflow { + string id = 1; + string name = 2; + string team = 3; + string project = 4; + } + + message WorkflowRun { + string id = 1; + string state = 2; + string runner_type = 3; + string run_url = 4; + google.protobuf.Timestamp started_at = 5; + google.protobuf.Timestamp finished_at = 6; + } } } diff --git a/app/controlplane/plugins/sdk/v1/plugin/api/translation.go b/app/controlplane/plugins/sdk/v1/plugin/api/translation.go index a170aca8e..63f3a8e77 100644 --- a/app/controlplane/plugins/sdk/v1/plugin/api/translation.go +++ b/app/controlplane/plugins/sdk/v1/plugin/api/translation.go @@ -24,6 +24,7 @@ import ( "github.com/chainloop-dev/chainloop/internal/attestation/renderer/chainloop" cr_v1 "github.com/google/go-containerregistry/pkg/v1" status "google.golang.org/grpc/status" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" ) func DescribeProtoToSDK(pd *DescribeResponse) (*sdk.IntegrationInfo, error) { @@ -109,21 +110,51 @@ func AttachProtoToSDK(in *AttachResponse) (*sdk.AttachmentResponse, error) { } func MetadataSDKToProto(in *sdk.ChainloopMetadata) *ExecuteRequest_Metadata { - return &ExecuteRequest_Metadata{ - WorkflowId: in.WorkflowID, - WorkflowName: in.WorkflowName, - WorkflowProject: in.WorkflowProject, - WorkflowRunId: in.WorkflowRunID, + res := &ExecuteRequest_Metadata{ + Workflow: &ExecuteRequest_Metadata_Workflow{ + Id: in.Workflow.ID, + Name: in.Workflow.Name, + Project: in.Workflow.Project, + Team: in.Workflow.Team, + }, + WorkflowRun: &ExecuteRequest_Metadata_WorkflowRun{ + Id: in.WorkflowRun.ID, + State: in.WorkflowRun.State, + RunnerType: in.WorkflowRun.RunnerType, + RunUrl: in.WorkflowRun.RunURL, + StartedAt: timestamppb.New(in.WorkflowRun.StartedAt), + }, } + + if !in.WorkflowRun.FinishedAt.IsZero() { + res.WorkflowRun.FinishedAt = timestamppb.New(in.WorkflowRun.FinishedAt) + } + + return res } func MetadataProtoToSDK(in *ExecuteRequest_Metadata) *sdk.ChainloopMetadata { - return &sdk.ChainloopMetadata{ - WorkflowID: in.WorkflowId, - WorkflowName: in.WorkflowName, - WorkflowProject: in.WorkflowProject, - WorkflowRunID: in.WorkflowRunId, + res := &sdk.ChainloopMetadata{ + Workflow: &sdk.ChainloopMetadataWorkflow{ + ID: in.Workflow.Id, + Name: in.Workflow.Name, + Project: in.Workflow.Project, + Team: in.Workflow.Team, + }, + WorkflowRun: &sdk.ChainloopMetadataWorkflowRun{ + ID: in.WorkflowRun.Id, + State: in.WorkflowRun.State, + RunnerType: in.WorkflowRun.RunnerType, + RunURL: in.WorkflowRun.RunUrl, + StartedAt: in.WorkflowRun.StartedAt.AsTime(), + }, } + + if in.WorkflowRun.FinishedAt != nil { + res.WorkflowRun.FinishedAt = in.WorkflowRun.FinishedAt.AsTime() + } + + return res } func MaterialSDKToProto(in *sdk.ExecuteMaterial) *ExecuteRequest_NormalizedMaterial { diff --git a/app/controlplane/plugins/sdk/v1/testdata/attestations/full.json b/app/controlplane/plugins/sdk/v1/testdata/attestations/full.json new file mode 100644 index 000000000..bb2e6a42b --- /dev/null +++ b/app/controlplane/plugins/sdk/v1/testdata/attestations/full.json @@ -0,0 +1,10 @@ +{ + "payloadType": "application/vnd.in-toto+json", + "payload": "ewogICJfdHlwZSI6ICJodHRwczovL2luLXRvdG8uaW8vU3RhdGVtZW50L3YwLjEiLAogICJwcmVkaWNhdGVUeXBlIjogImNoYWlubG9vcC5kZXYvYXR0ZXN0YXRpb24vdjAuMiIsCiAgInN1YmplY3QiOiBbCiAgICB7CiAgICAgICJuYW1lIjogImNoYWlubG9vcC5kZXYvd29ya2Zsb3cvb25seS1zYm9tIiwKICAgICAgImRpZ2VzdCI6IHsKICAgICAgICAic2hhMjU2IjogIjMwMzZmMmU1ZDcwOWEyMzgwOGVhYTA1NTcwZjE3MThhZmZmOTJkNGFkMzVlMjMyYzEzODczODM3MmFjOTNkZmIiCiAgICAgIH0KICAgIH0KICBdLAogICJwcmVkaWNhdGUiOiB7CiAgICAibWV0YWRhdGEiOiB7CiAgICAgICJuYW1lIjogIm9ubHktc2JvbSIsCiAgICAgICJwcm9qZWN0IjogImZvbyIsCiAgICAgICJ0ZWFtIjogIiIsCiAgICAgICJpbml0aWFsaXplZEF0IjogIjIwMjMtMDYtMjNUMTM6MDA6MjkuMTgzNjE4OTY2WiIsCiAgICAgICJmaW5pc2hlZEF0IjogIjIwMjMtMDYtMjNUMTU6MDA6NDMuNzA5OTcwNjcrMDI6MDAiLAogICAgICAid29ya2Zsb3dSdW5JRCI6ICJhMTdjYTIxNi0yOWI2LTQxMDctYjNmYy05MjU2ZjdhZjJmZTYiLAogICAgICAid29ya2Zsb3dJRCI6ICI1ZTM0YjM0Zi04ODJjLTQ4YjgtODRjMC00ZjIzOGUxNWE1ZmQiCiAgICB9LAogICAgImVudiI6IHsgIm93bmVyIjogImpvaG4tY0BjaGFpbmxvb3AuZGV2IiwgInByb2plY3QiOiAiY2hhdGdwdCIgfSwKICAgICJidWlsZGVyIjogewogICAgICAiaWQiOiAiY2hhaW5sb29wLmRldi9jbGkvZGV2QHNoYTI1NjphOGRkZDczODEzMDAyZjYyZDJkZTExYTQyMDU0YzA2ZmFlMGQwYTg4YjZjODkwNGU3ZGU0NDc1MTk4YzBiMGQzIgogICAgfSwKICAgICJidWlsZFR5cGUiOiAiY2hhaW5sb29wLmRldi93b3JrZmxvd3J1bi92MC4xIiwKICAgICJydW5uZXJUeXBlIjogIlJVTk5FUl9UWVBFX1VOU1BFQ0lGSUVEIiwKICAgICJtYXRlcmlhbHMiOiBbCiAgICAgIHsKICAgICAgICAiZGlnZXN0IjogewogICAgICAgICAgInNoYTI1NiI6ICIyNjRmNTVhNmZmOWNlYzJmNDc0MmE5ZmFhY2MwMzNiMjlmNjVjMDRkZDQ0ODBlNzFlMjM1NzlkNDg0Mjg4ZDYxIgogICAgICAgIH0sCiAgICAgICAgIm5hbWUiOiAiaW5kZXguZG9ja2VyLmlvL2JpdG5hbWkvbmdpbngiLAogICAgICAgICJhbm5vdGF0aW9ucyI6IHsKICAgICAgICAgICJjaGFpbmxvb3AubWF0ZXJpYWwubmFtZSI6ICJpbWFnZSIsCiAgICAgICAgICAiY2hhaW5sb29wLm1hdGVyaWFsLnR5cGUiOiAiQ09OVEFJTkVSX0lNQUdFIgogICAgICAgIH0KICAgICAgfSwKICAgICAgewogICAgICAgICJkaWdlc3QiOiB7CiAgICAgICAgICAic2hhMjU2IjogIjE2MTU5YmI4ODFlYjRhYjdlYjVkOGFmYzUzNTBiMGZlZWVkMWUzMWMwYTI2OGUzNTVlNzRmOWNjYmU4ODVlMGMiCiAgICAgICAgfSwKICAgICAgICAibmFtZSI6ICJzYm9tLmN5Y2xvbmVkeC5qc29uIiwKICAgICAgICAiYW5ub3RhdGlvbnMiOiB7CiAgICAgICAgICAiY2hhaW5sb29wLm1hdGVyaWFsLmNhcyI6IHRydWUsCiAgICAgICAgICAiY2hhaW5sb29wLm1hdGVyaWFsLm5hbWUiOiAic2t5bmV0LXNib20iLAogICAgICAgICAgImNoYWlubG9vcC5tYXRlcmlhbC50eXBlIjogIlNCT01fQ1lDTE9ORURYX0pTT04iCiAgICAgICAgfQogICAgICB9LAogICAgICB7CiAgICAgICAgImRpZ2VzdCI6IHsKICAgICAgICAgICJzaGEyNTYiOiAiMTYxNTliYjg4MWViNGFiN2ViNWQ4YWZjNTM1MGIwZmVlZWQxZTMxYzBhMjY4ZTM1NWU3NGY5Y2NiZTg4NWUwYyIKICAgICAgICB9LAogICAgICAgICJuYW1lIjogInNib20uY3ljbG9uZWR4Lmpzb24iLAogICAgICAgICJhbm5vdGF0aW9ucyI6IHsKICAgICAgICAgICJjaGFpbmxvb3AubWF0ZXJpYWwuY2FzIjogdHJ1ZSwKICAgICAgICAgICJjaGFpbmxvb3AubWF0ZXJpYWwubmFtZSI6ICJza3luZXQyLXNib20iLAogICAgICAgICAgImNoYWlubG9vcC5tYXRlcmlhbC50eXBlIjogIlNCT01fQ1lDTE9ORURYX0pTT04iCiAgICAgICAgfQogICAgICB9CiAgICBdCiAgfQp9Cg==", + "signatures": [ + { + "keyid": "", + "sig": "MEQCIAFytdWto+Bi5Tht+7haXnjiHBfwLwgf6ks0mxeeSadEAiBoLKhy+UKsdDH3ukHJPuiHvWOGhEP19kZ9aipmaT4TlQ==" + } + ] +} diff --git a/app/controlplane/plugins/sdk/v1/testdata/attestations/full.md b/app/controlplane/plugins/sdk/v1/testdata/attestations/full.md new file mode 100644 index 000000000..28d46c867 --- /dev/null +++ b/app/controlplane/plugins/sdk/v1/testdata/attestations/full.md @@ -0,0 +1,26 @@ +# Workflow +| ID | deadbeef | +| Name | test-workflow | +| Team | test-team | +| Project | test-project | +| Workflow Run | | +| ID | beefdead | +| Started At | 22 Nov 21 00:00 UTC | +| Finished At | 22 Nov 21 00:10 UTC | +| State | success | +| Runner Link | chainloop.dev/runner | +# Materials +| Name | Type | Value | +| --- | --- | --- | +| image | CONTAINER_IMAGE | index.docker.io/bitnami/nginx@sha256:264f55a6ff9cec2f4742a9faacc033b29f65c04dd4480e71e23579d484288d61 | +| skynet-sbom | SBOM_CYCLONEDX_JSON | sbom.cyclonedx.json@sha256:16159bb881eb4ab7eb5d8afc5350b0feeed1e31c0a268e355e74f9ccbe885e0c | +| skynet2-sbom | SBOM_CYCLONEDX_JSON | sbom.cyclonedx.json@sha256:16159bb881eb4ab7eb5d8afc5350b0feeed1e31c0a268e355e74f9ccbe885e0c | +# Environment Variables +| Name | Value | +| --- | --- | +| owner | john-c@chainloop.dev | +| project | chatgpt | + +Get Full Attestation + +$ chainloop workflow run describe --id beefdead -o statement \ No newline at end of file diff --git a/app/controlplane/plugins/sdk/v1/testdata/attestations/full.txt b/app/controlplane/plugins/sdk/v1/testdata/attestations/full.txt new file mode 100644 index 000000000..5e1e1acd9 --- /dev/null +++ b/app/controlplane/plugins/sdk/v1/testdata/attestations/full.txt @@ -0,0 +1,37 @@ +┌─────────────────────────────────────┐ +│ Workflow │ +├──────────────┬──────────────────────┤ +│ ID │ deadbeef │ +│ Name │ test-workflow │ +│ Team │ test-team │ +│ Project │ test-project │ +├──────────────┼──────────────────────┤ +│ Workflow Run │ │ +├──────────────┼──────────────────────┤ +│ ID │ beefdead │ +│ Started At │ 22 Nov 21 00:00 UTC │ +│ Finished At │ 22 Nov 21 00:10 UTC │ +│ State │ success │ +│ Runner Link │ chainloop.dev/runner │ +└──────────────┴──────────────────────┘ +┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Materials │ +├──────────────┬─────────────────────┬───────────────────────────────────────────────────────────────────────────────────────────────────────┤ +│ NAME │ TYPE │ VALUE │ +├──────────────┼─────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────┤ +│ image │ CONTAINER_IMAGE │ index.docker.io/bitnami/nginx@sha256:264f55a6ff9cec2f4742a9faacc033b29f65c04dd4480e71e23579d484288d61 │ +│ skynet-sbom │ SBOM_CYCLONEDX_JSON │ sbom.cyclonedx.json@sha256:16159bb881eb4ab7eb5d8afc5350b0feeed1e31c0a268e355e74f9ccbe885e0c │ +│ skynet2-sbom │ SBOM_CYCLONEDX_JSON │ sbom.cyclonedx.json@sha256:16159bb881eb4ab7eb5d8afc5350b0feeed1e31c0a268e355e74f9ccbe885e0c │ +└──────────────┴─────────────────────┴───────────────────────────────────────────────────────────────────────────────────────────────────────┘ +┌────────────────────────────────┐ +│ Environment Variables │ +├─────────┬──────────────────────┤ +│ NAME │ VALUE │ +├─────────┼──────────────────────┤ +│ owner │ john-c@chainloop.dev │ +│ project │ chatgpt │ +└─────────┴──────────────────────┘ + +Get Full Attestation + +$ chainloop workflow run describe --id beefdead -o statement \ No newline at end of file