diff --git a/Makefile b/Makefile index bdd9bc5ca1..3fc0568c0d 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ NAME = "github.com/odpf/optimus" LAST_COMMIT := $(shell git rev-parse --short HEAD) LAST_TAG := "$(shell git rev-list --tags --max-count=1)" OPMS_VERSION := "$(shell git describe --tags ${LAST_TAG})-next" -PROTON_COMMIT := "90b5d53e3e58e017032d12275597b93f53263add" +PROTON_COMMIT := "e75e288ec2a42ecd3358b2a12ddcd8bea50a4a07" .PHONY: build test test-ci generate-proto unit-test-ci integration-test vet coverage clean install lint diff --git a/api/handler/v1beta1/job_spec.go b/api/handler/v1beta1/job_spec.go index 4fd734cfc2..e09b336b8a 100644 --- a/api/handler/v1beta1/job_spec.go +++ b/api/handler/v1beta1/job_spec.go @@ -398,6 +398,7 @@ func (sv *JobSpecServiceServer) GetJobSpecifications(ctx context.Context, req *p ProjectName: req.GetProjectName(), JobName: req.GetJobName(), ResourceDestination: req.GetResourceDestination(), + NamespaceName: req.GetNamespaceName(), } jobSpecs, err := sv.jobSvc.GetByFilter(ctx, jobSpecFilter) if err != nil { diff --git a/api/handler/v1beta1/job_spec_test.go b/api/handler/v1beta1/job_spec_test.go index 241509a57b..57cd76ddb1 100644 --- a/api/handler/v1beta1/job_spec_test.go +++ b/api/handler/v1beta1/job_spec_test.go @@ -375,8 +375,8 @@ func (s *JobSpecServiceServerTestSuite) TestGetJobSpecification_Fail_JobServiceG } func (s *JobSpecServiceServerTestSuite) TestGetJobSpecifications_Success() { - req := &pb.GetJobSpecificationsRequest{JobName: "job-1"} - jobSpecFilter := models.JobSpecFilter{JobName: req.GetJobName()} + req := &pb.GetJobSpecificationsRequest{JobName: "job-1", NamespaceName: "namespace-1"} + jobSpecFilter := models.JobSpecFilter{JobName: req.GetJobName(), NamespaceName: req.GetNamespaceName()} execUnit1 := new(mock.YamlMod) execUnit1.On("PluginInfo").Return(&models.PluginInfoResponse{Name: "task"}, nil) diff --git a/client/cmd/job/export.go b/client/cmd/job/export.go new file mode 100644 index 0000000000..2ea23aaf7b --- /dev/null +++ b/client/cmd/job/export.go @@ -0,0 +1,316 @@ +package job + +import ( + "errors" + "fmt" + "path" + "strings" + "time" + + "github.com/odpf/salt/log" + "github.com/spf13/afero" + "github.com/spf13/cobra" + + "github.com/odpf/optimus/client/cmd/internal/connectivity" + "github.com/odpf/optimus/client/cmd/internal/logger" + "github.com/odpf/optimus/client/local" + "github.com/odpf/optimus/client/local/model" + "github.com/odpf/optimus/client/local/specio" + "github.com/odpf/optimus/config" + pb "github.com/odpf/optimus/protos/odpf/optimus/core/v1beta1" +) + +const ( + fetchTenantTimeout = time.Minute + fetchJobTimeout = time.Minute * 15 +) + +type exportCommand struct { + logger log.Logger + writer local.SpecWriter[*model.JobSpec] + + configFilePath string + outputDirPath string + host string + + projectName string + namespaceName string + jobName string +} + +// NewExportCommand initializes command for exporting job specification to yaml file +func NewExportCommand() *cobra.Command { + export := &exportCommand{ + logger: logger.NewClientLogger(), + } + + cmd := &cobra.Command{ + Use: "export", + Short: "Export job specifications to YAML files", + Example: "optimus job export", + RunE: export.RunE, + PreRunE: export.PreRunE, + } + + cmd.Flags().StringVarP(&export.configFilePath, "config", "c", export.configFilePath, "File path for client configuration") + cmd.Flags().StringVar(&export.outputDirPath, "dir", "", "Output directory path") + cmd.Flags().StringVar(&export.host, "host", "", "Host of the server source (will override value from config)") + + cmd.Flags().StringVarP(&export.projectName, "project-name", "p", "", "Project name target") + cmd.Flags().StringVarP(&export.namespaceName, "namespace-name", "n", "", "Namespace name target within the selected project name") + cmd.Flags().StringVarP(&export.jobName, "job-name", "r", "", "Job name target") + + cmd.MarkFlagRequired("dir") + return cmd +} + +func (e *exportCommand) PreRunE(_ *cobra.Command, _ []string) error { + readWriter, err := specio.NewJobSpecReadWriter(afero.NewOsFs()) + if err != nil { + e.logger.Error(err.Error()) + } + e.writer = readWriter + + if e.host != "" { + return nil + } + + if e.configFilePath != "" { + e.logger.Info("Loading client config from %s", e.configFilePath) + } + cfg, err := config.LoadClientConfig(e.configFilePath) + if err != nil { + e.logger.Warn("error is encountered when reading config file: %s", err) + } else { + e.host = cfg.Host + } + return err +} + +func (e *exportCommand) RunE(_ *cobra.Command, _ []string) error { + e.logger.Info("Validating input") + if err := e.validate(); err != nil { + return err + } + + var success bool + if e.projectName != "" && e.namespaceName != "" && e.jobName != "" { + e.logger.Info("Downloading job [%s] from project [%s] namespace [%s]", e.jobName, e.projectName, e.namespaceName) + success = e.downloadSpecificJob(e.projectName, e.namespaceName, e.jobName) + } else if e.projectName != "" && e.namespaceName != "" { + e.logger.Info("Downloading all jobs within project [%s] namespace [%s]", e.projectName, e.namespaceName) + success = e.downloadByProjectNameAndNamespaceName(e.projectName, e.namespaceName) + } else if e.projectName != "" { + e.logger.Info("Downloading all jobs within project [%s]", e.projectName) + success = e.downloadByProjectName(e.projectName) + } else { + e.logger.Info("Downloading all jobs") + success = e.downloadAll() + } + + if !success { + e.logger.Error("Download process failed") + return errors.New("encountered one or more errors during download jobs") + } + e.logger.Info("Download process success") + return nil +} + +func (e *exportCommand) downloadAll() bool { + e.logger.Info("Fetching all project names") + projectNames, err := e.fetchProjectNames() + if err != nil { + e.logger.Error("error is encountered when fetching project names: %s", err) + return false + } + if len(projectNames) == 0 { + e.logger.Warn("no project is found from the specified host") + return true + } + + success := true + for _, pName := range projectNames { + if !e.downloadByProjectName(pName) { + success = false + } + } + return success +} + +func (e *exportCommand) downloadByProjectName(projectName string) bool { + e.logger.Info("Fetching all jobs for project [%s]", projectName) + namespaceJobs, err := e.fetchNamespaceJobsByProjectName(projectName) + if err != nil { + e.logger.Error("error is encountered when fetching job specs for project [%s]: %s", projectName, err) + return false + } + + success := true + for namespaceName, jobSpecs := range namespaceJobs { + if len(jobSpecs) == 0 { + e.logger.Warn("No jobs found for project [%s] namespace [%s]", projectName, namespaceName) + continue + } + if err := e.writeJobs(projectName, namespaceName, jobSpecs); err != nil { + e.logger.Error(err.Error()) + success = false + } + } + return success +} + +func (e *exportCommand) downloadByProjectNameAndNamespaceName(projectName, namespaceName string) bool { + e.logger.Info("Fetching all jobs for project [%s] namespace [%s]", projectName, namespaceName) + jobs, err := e.fetchJobsByProjectAndNamespaceName(projectName, namespaceName) + if err != nil { + e.logger.Error("error is encountered when fetching job specs for project [%s]: %s", projectName, err) + return false + } + if len(jobs) == 0 { + e.logger.Warn("No jobs found for project [%s] namespace [%s]", projectName, namespaceName) + return true + } + if err := e.writeJobs(projectName, namespaceName, jobs); err != nil { + e.logger.Error(err.Error()) + return false + } + return true +} + +func (e *exportCommand) downloadSpecificJob(projectName, namespaceName, jobName string) bool { + e.logger.Info("Fetching job [%s] from project [%s] namespace [%s]", jobName, projectName, namespaceName) + job, err := e.fetchSpecificJob(projectName, namespaceName, jobName) + if err != nil { + e.logger.Error("error is encountered when fetching job specs for project [%s]: %s", projectName, err) + return false + } + + if err := e.writeJobs(projectName, namespaceName, []*model.JobSpec{job}); err != nil { + e.logger.Error(err.Error()) + return false + } + return true +} + +func (e *exportCommand) writeJobs(projectName, namespaceName string, jobs []*model.JobSpec) error { + e.logger.Info("Writing %d jobs for project [%s] namespace [%s]", len(jobs), projectName, namespaceName) + + var errMsgs []string + for _, spec := range jobs { + dirPath := path.Join(e.outputDirPath, projectName, namespaceName, "jobs", spec.Name) + + e.logger.Info("Writing job to [%s]", dirPath) + if err := e.writer.Write(dirPath, spec); err != nil { + errMsgs = append(errMsgs, err.Error()) + } + } + if len(errMsgs) > 0 { + return fmt.Errorf("encountered one or more errors when writing jobs:\n%s", strings.Join(errMsgs, "\n")) + } + return nil +} + +func (e *exportCommand) fetchNamespaceJobsByProjectName(projectName string) (map[string][]*model.JobSpec, error) { + conn, err := connectivity.NewConnectivity(e.host, fetchJobTimeout) + if err != nil { + return nil, err + } + defer conn.Close() + + jobSpecificationServiceClient := pb.NewJobSpecificationServiceClient(conn.GetConnection()) + + response, err := jobSpecificationServiceClient.GetJobSpecifications(conn.GetContext(), &pb.GetJobSpecificationsRequest{ + ProjectName: projectName, + }) + if err != nil { + return nil, err + } + + namespaceJobsMap := make(map[string][]*model.JobSpec) + for _, jobProto := range response.JobSpecificationResponses { + namespaceJobsMap[jobProto.GetNamespaceName()] = append(namespaceJobsMap[jobProto.GetNamespaceName()], model.ToJobSpec(jobProto.Job)) + } + return namespaceJobsMap, nil +} + +func (e *exportCommand) fetchJobsByProjectAndNamespaceName(projectName, namespaceName string) ([]*model.JobSpec, error) { + conn, err := connectivity.NewConnectivity(e.host, fetchJobTimeout) + if err != nil { + return nil, err + } + defer conn.Close() + + jobSpecificationServiceClient := pb.NewJobSpecificationServiceClient(conn.GetConnection()) + + response, err := jobSpecificationServiceClient.GetJobSpecifications(conn.GetContext(), &pb.GetJobSpecificationsRequest{ + ProjectName: projectName, + NamespaceName: namespaceName, + }) + if err != nil { + return nil, err + } + + jobs := make([]*model.JobSpec, len(response.JobSpecificationResponses)) + for i, jobProto := range response.JobSpecificationResponses { + jobs[i] = model.ToJobSpec(jobProto.Job) + } + return jobs, nil +} + +func (e *exportCommand) fetchSpecificJob(projectName, namespaceName, jobName string) (*model.JobSpec, error) { + conn, err := connectivity.NewConnectivity(e.host, fetchJobTimeout) + if err != nil { + return nil, err + } + defer conn.Close() + + jobSpecificationServiceClient := pb.NewJobSpecificationServiceClient(conn.GetConnection()) + + response, err := jobSpecificationServiceClient.GetJobSpecifications(conn.GetContext(), &pb.GetJobSpecificationsRequest{ + ProjectName: projectName, + NamespaceName: namespaceName, + JobName: jobName, + }) + if err != nil { + return nil, err + } + + if len(response.JobSpecificationResponses) == 0 { + return nil, errors.New("job is not found") + } + return model.ToJobSpec(response.JobSpecificationResponses[0].Job), nil +} + +func (e *exportCommand) fetchProjectNames() ([]string, error) { + conn, err := connectivity.NewConnectivity(e.host, fetchTenantTimeout) + if err != nil { + return nil, err + } + defer conn.Close() + + projectServiceClient := pb.NewProjectServiceClient(conn.GetConnection()) + + response, err := projectServiceClient.ListProjects(conn.GetContext(), &pb.ListProjectsRequest{}) + if err != nil { + return nil, err + } + + output := make([]string, len(response.Projects)) + for i, p := range response.Projects { + output[i] = p.GetName() + } + return output, nil +} + +func (e *exportCommand) validate() error { + if e.host == "" { + return errors.New("host is not specified in both config file and flag argument") + } + if e.namespaceName != "" && e.projectName == "" { + return errors.New("project name has to be specified since namespace name is specified") + } + if e.jobName != "" && (e.projectName == "" || e.namespaceName == "") { + return errors.New("project name and namespace name have to be specified since job name is specified") + } + return nil +} diff --git a/client/cmd/job/job.go b/client/cmd/job/job.go index 3c556b3cc2..1a1836acf1 100644 --- a/client/cmd/job/job.go +++ b/client/cmd/job/job.go @@ -33,6 +33,7 @@ func NewJobCommand() *cobra.Command { NewValidateCommand(), NewJobRunInputCommand(), NewInspectCommand(), + NewExportCommand(), ) return cmd } diff --git a/client/cmd/resource/create.go b/client/cmd/resource/create.go index e9d5df30fa..5c3b604278 100644 --- a/client/cmd/resource/create.go +++ b/client/cmd/resource/create.go @@ -16,17 +16,16 @@ import ( ) type createCommand struct { - logger log.Logger - clientConfig *config.ClientConfig + logger log.Logger + configFilePath string namespaceSurvey *survey.NamespaceSurvey } // NewCreateCommand initializes resource create command -func NewCreateCommand(clientConfig *config.ClientConfig) *cobra.Command { +func NewCreateCommand() *cobra.Command { l := logger.NewClientLogger() create := &createCommand{ - clientConfig: clientConfig, logger: l, namespaceSurvey: survey.NewNamespaceSurvey(l), } @@ -37,11 +36,18 @@ func NewCreateCommand(clientConfig *config.ClientConfig) *cobra.Command { Example: "optimus resource create", RunE: create.RunE, } + cmd.Flags().StringVarP(&create.configFilePath, "config", "c", create.configFilePath, "File path for client configuration") + cmd.MarkFlagRequired("config") return cmd } func (c createCommand) RunE(_ *cobra.Command, _ []string) error { - selectedNamespace, err := c.namespaceSurvey.AskToSelectNamespace(c.clientConfig) + cfg, err := config.LoadClientConfig(c.configFilePath) + if err != nil { + return err + } + + selectedNamespace, err := c.namespaceSurvey.AskToSelectNamespace(cfg) if err != nil { return err } diff --git a/client/cmd/resource/export.go b/client/cmd/resource/export.go new file mode 100644 index 0000000000..3d9c6cef59 --- /dev/null +++ b/client/cmd/resource/export.go @@ -0,0 +1,323 @@ +package resource + +import ( + "errors" + "fmt" + "path" + "strings" + "time" + + "github.com/odpf/salt/log" + "github.com/spf13/afero" + "github.com/spf13/cobra" + + "github.com/odpf/optimus/client/cmd/internal/connectivity" + "github.com/odpf/optimus/client/cmd/internal/logger" + "github.com/odpf/optimus/client/local" + "github.com/odpf/optimus/client/local/model" + "github.com/odpf/optimus/client/local/specio" + "github.com/odpf/optimus/config" + pb "github.com/odpf/optimus/protos/odpf/optimus/core/v1beta1" +) + +const ( + fetchTenantTimeout = time.Minute + fetchResourceTimeout = time.Minute * 15 +) + +type exportCommand struct { + logger log.Logger + writer local.SpecWriter[*model.ResourceSpec] + + configFilePath string + outputDirPath string + host string + + projectName string + namespaceName string + resourceName string + + storeName string +} + +func NewExportCommand() *cobra.Command { + l := logger.NewClientLogger() + export := &exportCommand{ + logger: l, + storeName: "bigquery", + } + + cmd := &cobra.Command{ + Use: "export", + Short: "Export resources to YAML files", + Example: "optimus resource export", + RunE: export.RunE, + PreRunE: export.PreRunE, + } + + cmd.Flags().StringVarP(&export.configFilePath, "config", "c", export.configFilePath, "File path for client configuration") + cmd.Flags().StringVar(&export.outputDirPath, "dir", "", "Output directory path") + cmd.Flags().StringVar(&export.host, "host", "", "Host of the server source (will override value from config)") + + cmd.Flags().StringVarP(&export.projectName, "project-name", "p", "", "Project name target") + cmd.Flags().StringVarP(&export.namespaceName, "namespace-name", "n", "", "Namespace name target within the selected project name") + cmd.Flags().StringVarP(&export.resourceName, "resource-name", "r", "", "Resource name target") + + cmd.MarkFlagRequired("dir") + return cmd +} + +func (e *exportCommand) PreRunE(_ *cobra.Command, _ []string) error { + readWriter, err := specio.NewResourceSpecReadWriter(afero.NewOsFs()) + if err != nil { + return err + } + e.writer = readWriter + + if e.host != "" { + return nil + } + + if e.configFilePath != "" { + e.logger.Info("Loading client config from %s", e.configFilePath) + } + cfg, err := config.LoadClientConfig(e.configFilePath) + if err != nil { + e.logger.Warn("error is encountered when loading config file: %s", err) + } else { + e.host = cfg.Host + } + return nil +} + +func (e *exportCommand) RunE(_ *cobra.Command, _ []string) error { + e.logger.Info("Validating input") + if err := e.validate(); err != nil { + return err + } + + var success bool + if e.projectName != "" && e.namespaceName != "" && e.resourceName != "" { + e.logger.Info("Downloading resource with project [%s] namespace [%s] resource [%s]", e.projectName, e.namespaceName, e.resourceName) + success = e.downloadSpecificResource(e.projectName, e.namespaceName, e.resourceName) + } else if e.projectName != "" && e.namespaceName != "" { + e.logger.Info("Downloading all resources within project [%s] namespace [%s]", e.projectName, e.namespaceName) + success = e.downloadByProjectNameAndNamespaceName(e.projectName, e.namespaceName) + } else if e.projectName != "" { + e.logger.Info("Downloading all resources within project [%s]", e.projectName) + success = e.downloadByProjectName(e.projectName) + } else { + e.logger.Info("Downloading all resources") + success = e.downloadAll() + } + + if !success { + e.logger.Error("Download process failed") + return errors.New("encountered one or more errors during download resources") + } + e.logger.Info("Download process success") + return nil +} + +func (e *exportCommand) downloadAll() bool { + e.logger.Info("Fetching all project names") + projectNames, err := e.fetchProjectNames() + if err != nil { + e.logger.Error("error is encountered when fetching project names: %s", err) + return false + } + if len(projectNames) == 0 { + e.logger.Warn("no project is found from the specified host") + return true + } + + success := true + for _, pName := range projectNames { + if !e.downloadByProjectName(pName) { + success = false + } + } + return success +} + +func (e *exportCommand) downloadByProjectName(projectName string) bool { + e.logger.Info("Fetching all namespace names for project [%s]", projectName) + namespaceNames, err := e.fetchNamespaceNames(projectName) + if err != nil { + e.logger.Error("error is encountered when fetching namespace names for project [%s]: %s", projectName, err) + return false + } + + success := true + for _, nName := range namespaceNames { + if !e.downloadByProjectNameAndNamespaceName(projectName, nName) { + success = false + } + } + return success +} + +func (e *exportCommand) downloadByProjectNameAndNamespaceName(projectName, namespaceName string) bool { + e.logger.Info("Fetching all resources for project [%s] namespace [%s]", projectName, namespaceName) + resources, err := e.fetchAllResources(projectName, namespaceName) + if err != nil { + e.logger.Error("error is encountered when fetching resource for project [%s] namespace [%s]: %s", projectName, namespaceName, err) + return false + } + if len(resources) == 0 { + e.logger.Warn("No resources found for project [%s] namespace [%s]", projectName, namespaceName) + return true + } + if err := e.writeResources(projectName, namespaceName, resources); err != nil { + e.logger.Error(err.Error()) + return false + } + return true +} + +func (e *exportCommand) downloadSpecificResource(projectName, namespaceName, resourceName string) bool { + e.logger.Info("Fetching resource [%s] for project [%s] namespace [%s]", resourceName, projectName, namespaceName) + resource, err := e.fetchSpecificResource(projectName, namespaceName, resourceName) + if err != nil { + e.logger.Error("error is encountered when fetching resource for project [%s] namespace [%s]: %s", projectName, namespaceName, err) + return false + } + if err := e.writeResources(projectName, namespaceName, []*model.ResourceSpec{resource}); err != nil { + e.logger.Error(err.Error()) + return false + } + return true +} + +func (e *exportCommand) writeResources(projectName, namespaceName string, resources []*model.ResourceSpec) error { + e.logger.Info("Writing %d resources for project [%s] namespace [%s]", len(resources), projectName, namespaceName) + + var errMsgs []string + for _, res := range resources { + dirPath := path.Join(e.outputDirPath, projectName, namespaceName, "resources", e.storeName, res.Name) + + e.logger.Info("Writing resource to [%s]", dirPath) + if err := e.writer.Write(dirPath, res); err != nil { + errMsgs = append(errMsgs, err.Error()) + } + } + if len(errMsgs) > 0 { + return fmt.Errorf("encountered one or more errors when writing resources:\n%s", strings.Join(errMsgs, "\n")) + } + return nil +} + +func (e *exportCommand) fetchAllResources(projectName, namespaceName string) ([]*model.ResourceSpec, error) { + conn, err := connectivity.NewConnectivity(e.host, fetchResourceTimeout) + if err != nil { + return nil, err + } + defer conn.Close() + + resourceServiceClient := pb.NewResourceServiceClient(conn.GetConnection()) + + response, err := resourceServiceClient.ListResourceSpecification(conn.GetContext(), &pb.ListResourceSpecificationRequest{ + ProjectName: projectName, + NamespaceName: namespaceName, + DatastoreName: e.storeName, + }) + if err != nil { + return nil, err + } + + output := make([]*model.ResourceSpec, len(response.Resources)) + for i, resource := range response.Resources { + output[i] = &model.ResourceSpec{ + Version: int(resource.GetVersion()), + Name: resource.GetName(), + Type: resource.GetType(), + Labels: resource.GetLabels(), + Spec: resource.GetSpec().AsMap(), + } + } + return output, nil +} + +func (e *exportCommand) fetchSpecificResource(projectName, namespaceName, resourceName string) (*model.ResourceSpec, error) { + conn, err := connectivity.NewConnectivity(e.host, fetchResourceTimeout) + if err != nil { + return nil, err + } + defer conn.Close() + + resourceServiceClient := pb.NewResourceServiceClient(conn.GetConnection()) + + response, err := resourceServiceClient.ReadResource(conn.GetContext(), &pb.ReadResourceRequest{ + ProjectName: projectName, + NamespaceName: namespaceName, + ResourceName: resourceName, + DatastoreName: e.storeName, + }) + if err != nil { + return nil, err + } + return &model.ResourceSpec{ + Version: int(response.GetResource().Version), + Name: response.Resource.GetName(), + Type: response.GetResource().Type, + Labels: response.Resource.GetLabels(), + Spec: response.GetResource().Spec.AsMap(), + }, nil +} + +func (e *exportCommand) fetchNamespaceNames(projectName string) ([]string, error) { + conn, err := connectivity.NewConnectivity(e.host, fetchTenantTimeout) + if err != nil { + return nil, err + } + defer conn.Close() + + namespaceServiceClient := pb.NewNamespaceServiceClient(conn.GetConnection()) + + response, err := namespaceServiceClient.ListProjectNamespaces(conn.GetContext(), &pb.ListProjectNamespacesRequest{ + ProjectName: projectName, + }) + if err != nil { + return nil, err + } + + output := make([]string, len(response.Namespaces)) + for i, n := range response.Namespaces { + output[i] = n.GetName() + } + return output, nil +} + +func (e *exportCommand) fetchProjectNames() ([]string, error) { + conn, err := connectivity.NewConnectivity(e.host, fetchTenantTimeout) + if err != nil { + return nil, err + } + defer conn.Close() + + projectServiceClient := pb.NewProjectServiceClient(conn.GetConnection()) + + response, err := projectServiceClient.ListProjects(conn.GetContext(), &pb.ListProjectsRequest{}) + if err != nil { + return nil, err + } + + output := make([]string, len(response.Projects)) + for i, p := range response.Projects { + output[i] = p.GetName() + } + return output, nil +} + +func (e *exportCommand) validate() error { + if e.host == "" { + return errors.New("host is not specified in both config file and flag argument") + } + if e.namespaceName != "" && e.projectName == "" { + return errors.New("project name has to be specified since namespace name is specified") + } + if e.resourceName != "" && (e.projectName == "" || e.namespaceName == "") { + return errors.New("project name and namespace name have to be specified since resource name is specified") + } + return nil +} diff --git a/client/cmd/resource/resource.go b/client/cmd/resource/resource.go index 62cd142cf2..458665db75 100644 --- a/client/cmd/resource/resource.go +++ b/client/cmd/resource/resource.go @@ -2,41 +2,19 @@ package resource import ( "github.com/spf13/cobra" - - "github.com/odpf/optimus/config" ) -type resourceCommand struct { - configFilePath string - clientConfig *config.ClientConfig -} - // NewResourceCommand initializes command for resource func NewResourceCommand() *cobra.Command { - resource := &resourceCommand{ - clientConfig: &config.ClientConfig{}, - } - cmd := &cobra.Command{ Use: "resource", Short: "Interact with data resource", Annotations: map[string]string{ "group:core": "true", }, - PersistentPreRunE: resource.PersistentPreRunE, } - cmd.PersistentFlags().StringVarP(&resource.configFilePath, "config", "c", resource.configFilePath, "File path for client configuration") - cmd.AddCommand(NewCreateCommand(resource.clientConfig)) + cmd.AddCommand(NewCreateCommand()) + cmd.AddCommand(NewExportCommand()) return cmd } - -func (r *resourceCommand) PersistentPreRunE(_ *cobra.Command, _ []string) error { - // TODO: find a way to load the config in one place - c, err := config.LoadClientConfig(r.configFilePath) - if err != nil { - return err - } - *r.clientConfig = *c - return nil -} diff --git a/client/local/model/job_spec.go b/client/local/model/job_spec.go index be9999629a..eeb0a3140f 100644 --- a/client/local/model/job_spec.go +++ b/client/local/model/job_spec.go @@ -418,3 +418,144 @@ func getValue[V int | string | bool | time.Duration](reference, other V) V { } return reference } + +func ToJobSpec(protoSpec *pb.JobSpecification) *JobSpec { + return &JobSpec{ + Version: int(protoSpec.Version), + Name: protoSpec.Name, + Owner: protoSpec.Owner, + Description: protoSpec.Description, + Schedule: JobSpecSchedule{ + StartDate: protoSpec.StartDate, + EndDate: protoSpec.EndDate, + Interval: protoSpec.Interval, + }, + Behavior: toJobSpecBehavior(protoSpec.Behavior, protoSpec.DependsOnPast, protoSpec.CatchUp), + Task: JobSpecTask{ + Name: protoSpec.TaskName, + Config: configProtoToMap(protoSpec.Config), + Window: JobSpecTaskWindow{ + Size: protoSpec.WindowSize, + Offset: protoSpec.WindowOffset, + TruncateTo: protoSpec.WindowTruncateTo, + }, + }, + Asset: protoSpec.Assets, + Labels: protoSpec.Labels, + Hooks: toJobSpecHooks(protoSpec.Hooks), + Dependencies: toJobSpecDependencies(protoSpec.Dependencies), + Metadata: toJobSpecMetadata(protoSpec.Metadata), + } +} + +func toJobSpecMetadata(protoMetadata *pb.JobMetadata) *JobSpecMetadata { + var metadataSpec *JobSpecMetadata + if protoMetadata != nil { + var metadataResourceSpec *JobSpecMetadataResource + if protoMetadata.Resource != nil { + var metadataResourceRequest *JobSpecMetadataResourceConfig + if protoMetadata.Resource.Request != nil { + metadataResourceRequest = &JobSpecMetadataResourceConfig{ + Memory: protoMetadata.Resource.Request.Memory, + CPU: protoMetadata.Resource.Request.Cpu, + } + } + var metadataResourceLimit *JobSpecMetadataResourceConfig + if protoMetadata.Resource.Limit != nil { + metadataResourceLimit = &JobSpecMetadataResourceConfig{ + Memory: protoMetadata.Resource.Limit.Memory, + CPU: protoMetadata.Resource.Limit.Cpu, + } + } + metadataResourceSpec = &JobSpecMetadataResource{ + Request: metadataResourceRequest, + Limit: metadataResourceLimit, + } + } + + var metadataAirflowSpec *JobSpecMetadataAirflow + if protoMetadata.Airflow != nil { + metadataAirflowSpec = &JobSpecMetadataAirflow{ + Pool: protoMetadata.Airflow.Pool, + Queue: protoMetadata.Airflow.Queue, + } + } + metadataSpec = &JobSpecMetadata{ + Resource: metadataResourceSpec, + Airflow: metadataAirflowSpec, + } + } + return metadataSpec +} + +func toJobSpecDependencies(protoDependencies []*pb.JobDependency) []JobSpecDependency { + var dependencySpecs []JobSpecDependency + for _, dependency := range protoDependencies { + var httpDependency *JobSpecDependencyHTTP + if dependency.HttpDependency != nil { + httpDependency = &JobSpecDependencyHTTP{ + Name: dependency.HttpDependency.Name, + RequestParams: dependency.HttpDependency.Params, + URL: dependency.HttpDependency.Url, + Headers: dependency.HttpDependency.Headers, + } + } + dependencySpec := JobSpecDependency{ + JobName: dependency.Name, + Type: dependency.Type, + HTTP: httpDependency, + } + dependencySpecs = append(dependencySpecs, dependencySpec) + } + return dependencySpecs +} + +func toJobSpecHooks(protoHooks []*pb.JobSpecHook) []JobSpecHook { + var hookSpecs []JobSpecHook + for _, protoHook := range protoHooks { + hookSpec := JobSpecHook{ + Name: protoHook.Name, + Config: configProtoToMap(protoHook.Config), + } + hookSpecs = append(hookSpecs, hookSpec) + } + return hookSpecs +} + +func toJobSpecBehavior(protoBehavior *pb.JobSpecification_Behavior, dependsOnPast bool, catchUp bool) JobSpecBehavior { + var retry *JobSpecBehaviorRetry + var notifiers []JobSpecBehaviorNotifier + if protoBehavior != nil { + if protoBehavior.Retry != nil { + retry = &JobSpecBehaviorRetry{ + Count: int(protoBehavior.Retry.Count), + Delay: protoBehavior.Retry.Delay.AsDuration(), + ExponentialBackoff: protoBehavior.Retry.ExponentialBackoff, + } + } + if protoBehavior.Notify != nil { + for _, protoNotifier := range protoBehavior.Notify { + notifier := JobSpecBehaviorNotifier{ + On: utils.FromEnumProto(protoNotifier.On.String(), "type"), + Config: protoNotifier.Config, + Channels: protoNotifier.Channels, + } + notifiers = append(notifiers, notifier) + } + } + } + return JobSpecBehavior{ + DependsOnPast: dependsOnPast, + Catchup: catchUp, + Retry: retry, + Notify: notifiers, + } +} + +func configProtoToMap(configProtoItems []*pb.JobConfigItem) map[string]string { + configMap := map[string]string{} + for _, configProto := range configProtoItems { + configMap[configProto.GetName()] = configProto.GetValue() + } + return configMap +} diff --git a/client/local/model/job_spec_test.go b/client/local/model/job_spec_test.go index 72d973478c..fd18a12d4d 100644 --- a/client/local/model/job_spec_test.go +++ b/client/local/model/job_spec_test.go @@ -82,6 +82,55 @@ func (s *JobSpecTestSuite) TestMergeFrom() { }) } +func (s *JobSpecTestSuite) TestToJobSpec() { + s.Run("should return job spec with behavior.retry nil and behavior.notify nil when behavior proto is nil", func() { + jobProto := s.getCompleteJobSpecProto() + jobProto.Behavior = nil + + expectedJobSpec := s.getCompleteJobSpec() + expectedJobSpec.Behavior.Retry = nil + expectedJobSpec.Behavior.Notify = nil + + actualJobSpec := model.ToJobSpec(jobProto) + + s.Assert().EqualValues(&expectedJobSpec, actualJobSpec) + }) + + s.Run("should return job spec with metadata nil when job proto metadata is nil", func() { + jobProto := s.getCompleteJobSpecProto() + jobProto.Metadata = nil + + expectedJobSpec := s.getCompleteJobSpec() + expectedJobSpec.Metadata = nil + + actualJobSpec := model.ToJobSpec(jobProto) + + s.Assert().EqualValues(&expectedJobSpec, actualJobSpec) + }) + + s.Run("should return job spec with metadata resource config nil when metadata.resource config proto is nil", func() { + jobProto := s.getCompleteJobSpecProto() + jobProto.Metadata.Resource.Request = nil + + expectedJobSpec := s.getCompleteJobSpec() + expectedJobSpec.Metadata.Resource.Request = nil + + actualJobSpec := model.ToJobSpec(jobProto) + + s.Assert().EqualValues(&expectedJobSpec, actualJobSpec) + }) + + s.Run("should return complete job spec when job spec proto is complete", func() { + jobProto := s.getCompleteJobSpecProto() + + expectedJobSpec := s.getCompleteJobSpec() + + actualJobSpec := model.ToJobSpec(jobProto) + + s.Assert().EqualValues(&expectedJobSpec, actualJobSpec) + }) +} + func (*JobSpecTestSuite) getCompleteJobSpec() model.JobSpec { return model.JobSpec{ Version: 1, diff --git a/job/service.go b/job/service.go index 194de35087..cd0fcaaad5 100644 --- a/job/service.go +++ b/job/service.go @@ -227,10 +227,7 @@ func (srv *Service) GetByFilter(ctx context.Context, filter models.JobSpecFilter } return jobSpecs, nil } - if filter.ProjectName != "" { - if filter.JobName == "" { - return srv.jobSpecRepository.GetAllByProjectName(ctx, filter.ProjectName) - } + if filter.JobName != "" && filter.ProjectName != "" { jobSpec, err := srv.jobSpecRepository.GetByNameAndProjectName(ctx, filter.JobName, filter.ProjectName) if err != nil { if errors.Is(err, store.ErrResourceNotFound) { @@ -240,6 +237,19 @@ func (srv *Service) GetByFilter(ctx context.Context, filter models.JobSpecFilter } return []models.JobSpec{jobSpec}, nil } + if filter.NamespaceName != "" && filter.ProjectName != "" { + jobSpecs, err := srv.jobSpecRepository.GetAllByProjectNameAndNamespaceName(ctx, filter.ProjectName, filter.NamespaceName) + if err != nil { + if errors.Is(err, store.ErrResourceNotFound) { + return []models.JobSpec{}, nil + } + return nil, err + } + return jobSpecs, nil + } + if filter.ProjectName != "" { + return srv.jobSpecRepository.GetAllByProjectName(ctx, filter.ProjectName) + } return nil, fmt.Errorf("filters not specified") } diff --git a/job/service_test.go b/job/service_test.go index e2b869e420..5b62768cf1 100644 --- a/job/service_test.go +++ b/job/service_test.go @@ -804,6 +804,60 @@ func TestService(t *testing.T) { assert.NoError(t, actualError) }) + t.Run("should return spec and nil if both project name and namespace name are set, and job is found", func(t *testing.T) { + jobSpecRepository := mock.NewJobSpecRepository(t) + service := job.NewService(nil, nil, nil, nil, nil, nil, nil, nil, nil, jobSpecRepository, nil) + + ctx := context.Background() + filter := models.JobSpecFilter{ + ProjectName: "project_test", + NamespaceName: "namespace_test", + } + + jobSpecRepository.On("GetAllByProjectNameAndNamespaceName", ctx, filter.ProjectName, filter.NamespaceName).Return([]models.JobSpec{{}}, nil) + + actualJobSpecs, actualError := service.GetByFilter(ctx, filter) + + assert.NotEmpty(t, actualJobSpecs) + assert.NoError(t, actualError) + }) + + t.Run("should return empty and nil if both project name and namespace name are set, but jobs are not found", func(t *testing.T) { + jobSpecRepository := mock.NewJobSpecRepository(t) + service := job.NewService(nil, nil, nil, nil, nil, nil, nil, nil, nil, jobSpecRepository, nil) + + ctx := context.Background() + filter := models.JobSpecFilter{ + ProjectName: "project_test", + NamespaceName: "namespace_test", + } + + jobSpecRepository.On("GetAllByProjectNameAndNamespaceName", ctx, filter.ProjectName, filter.NamespaceName).Return(nil, store.ErrResourceNotFound) + + actualJobSpecs, actualError := service.GetByFilter(ctx, filter) + + assert.Empty(t, actualJobSpecs) + assert.NoError(t, actualError) + }) + + t.Run("should return nil and error if both project name and namespace name are set, and encountered error when fetching jobs from repository", func(t *testing.T) { + jobSpecRepository := mock.NewJobSpecRepository(t) + service := job.NewService(nil, nil, nil, nil, nil, nil, nil, nil, nil, jobSpecRepository, nil) + + ctx := context.Background() + filter := models.JobSpecFilter{ + ProjectName: "project_test", + NamespaceName: "namespace_test", + } + + jobSpecRepository.On("GetAllByProjectNameAndNamespaceName", ctx, filter.ProjectName, filter.NamespaceName).Return(nil, errors.New("internal error")) + + actualJobSpecs, actualError := service.GetByFilter(ctx, filter) + + assert.Nil(t, actualJobSpecs) + assert.ErrorContains(t, actualError, "internal error") + }) + t.Run("should return nil and error if the filters are not set", func(t *testing.T) { service := job.NewService(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil) diff --git a/models/job.go b/models/job.go index 292bcea27a..ef6255b334 100644 --- a/models/job.go +++ b/models/job.go @@ -401,6 +401,7 @@ type JobSpecFilter struct { ProjectName string JobName string ResourceDestination string + NamespaceName string } type JobEventType string diff --git a/protos/odpf/optimus/core/v1beta1/job_spec.pb.go b/protos/odpf/optimus/core/v1beta1/job_spec.pb.go index cc6ea5e935..b4438ae51c 100644 --- a/protos/odpf/optimus/core/v1beta1/job_spec.pb.go +++ b/protos/odpf/optimus/core/v1beta1/job_spec.pb.go @@ -2253,6 +2253,7 @@ type GetJobSpecificationsRequest struct { ProjectName string `protobuf:"bytes,1,opt,name=project_name,json=projectName,proto3" json:"project_name,omitempty"` ResourceDestination string `protobuf:"bytes,2,opt,name=resource_destination,json=resourceDestination,proto3" json:"resource_destination,omitempty"` JobName string `protobuf:"bytes,3,opt,name=job_name,json=jobName,proto3" json:"job_name,omitempty"` + NamespaceName string `protobuf:"bytes,4,opt,name=namespace_name,json=namespaceName,proto3" json:"namespace_name,omitempty"` } func (x *GetJobSpecificationsRequest) Reset() { @@ -2308,6 +2309,13 @@ func (x *GetJobSpecificationsRequest) GetJobName() string { return "" } +func (x *GetJobSpecificationsRequest) GetNamespaceName() string { + if x != nil { + return x.NamespaceName + } + return "" +} + type GetJobSpecificationsResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -3476,7 +3484,7 @@ var file_odpf_optimus_core_v1beta1_job_spec_proto_rawDesc = []byte{ 0x6f, 0x62, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6a, 0x6f, 0x62, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, - 0x22, 0x8e, 0x01, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x4a, 0x6f, 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, + 0x22, 0xb5, 0x01, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x4a, 0x6f, 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x4e, @@ -3485,179 +3493,182 @@ var file_odpf_optimus_core_v1beta1_job_spec_proto_rawDesc = []byte{ 0x09, 0x52, 0x13, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x6a, 0x6f, 0x62, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6a, 0x6f, 0x62, 0x4e, 0x61, 0x6d, - 0x65, 0x22, 0xd8, 0x01, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x4a, 0x6f, 0x62, 0x53, 0x70, 0x65, 0x63, - 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x43, 0x0a, 0x04, 0x6a, 0x6f, 0x62, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x2b, 0x2e, 0x6f, 0x64, 0x70, 0x66, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, - 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4a, 0x6f, 0x62, - 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x02, 0x18, - 0x01, 0x52, 0x04, 0x6a, 0x6f, 0x62, 0x73, 0x12, 0x73, 0x0a, 0x1b, 0x6a, 0x6f, 0x62, 0x5f, 0x73, - 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x6f, - 0x64, 0x70, 0x66, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, - 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4a, 0x6f, 0x62, 0x53, 0x70, 0x65, 0x63, - 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x52, 0x19, 0x6a, 0x6f, 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x73, 0x22, 0xa3, 0x01, 0x0a, - 0x18, 0x4a, 0x6f, 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x6f, - 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0b, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x25, 0x0a, 0x0e, - 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4e, - 0x61, 0x6d, 0x65, 0x12, 0x3d, 0x0a, 0x03, 0x6a, 0x6f, 0x62, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x2b, 0x2e, 0x6f, 0x64, 0x70, 0x66, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, - 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4a, 0x6f, 0x62, - 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x03, 0x6a, - 0x6f, 0x62, 0x32, 0x9e, 0x11, 0x0a, 0x17, 0x4a, 0x6f, 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, - 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x93, - 0x01, 0x0a, 0x16, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x4a, 0x6f, 0x62, 0x53, 0x70, 0x65, 0x63, - 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x38, 0x2e, 0x6f, 0x64, 0x70, 0x66, - 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, - 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x4a, 0x6f, 0x62, 0x53, - 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x39, 0x2e, 0x6f, 0x64, 0x70, 0x66, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, - 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, - 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x4a, 0x6f, 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, - 0x28, 0x01, 0x30, 0x01, 0x12, 0xbc, 0x01, 0x0a, 0x0a, 0x4a, 0x6f, 0x62, 0x49, 0x6e, 0x73, 0x70, - 0x65, 0x63, 0x74, 0x12, 0x2c, 0x2e, 0x6f, 0x64, 0x70, 0x66, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, - 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, - 0x4a, 0x6f, 0x62, 0x49, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x2d, 0x2e, 0x6f, 0x64, 0x70, 0x66, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, - 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4a, 0x6f, - 0x62, 0x49, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x51, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x4b, 0x22, 0x46, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, - 0x61, 0x31, 0x2f, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x2f, 0x7b, 0x70, 0x72, 0x6f, 0x6a, - 0x65, 0x63, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, - 0x61, 0x63, 0x65, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6e, - 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x6a, 0x6f, 0x62, 0x2f, 0x69, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, - 0x3a, 0x01, 0x2a, 0x12, 0xd8, 0x01, 0x0a, 0x16, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4a, 0x6f, - 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x38, - 0x2e, 0x6f, 0x64, 0x70, 0x66, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, - 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x4a, 0x6f, 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x39, 0x2e, 0x6f, 0x64, 0x70, 0x66, 0x2e, - 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, - 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x53, 0x70, - 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x49, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x43, 0x22, 0x3e, 0x2f, 0x76, 0x31, - 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x2f, 0x7b, 0x70, - 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x6e, 0x61, 0x6d, - 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, - 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x6a, 0x6f, 0x62, 0x3a, 0x01, 0x2a, 0x12, 0xd3, - 0x01, 0x0a, 0x14, 0x41, 0x64, 0x64, 0x4a, 0x6f, 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x36, 0x2e, 0x6f, 0x64, 0x70, 0x66, 0x2e, 0x6f, + 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x73, + 0x70, 0x61, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0xd8, 0x01, 0x0a, 0x1c, 0x47, 0x65, 0x74, + 0x4a, 0x6f, 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x04, 0x6a, 0x6f, 0x62, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x6f, 0x64, 0x70, 0x66, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, - 0x74, 0x61, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x4a, 0x6f, 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, - 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x37, 0x2e, 0x6f, 0x64, 0x70, 0x66, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, - 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x4a, - 0x6f, 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x4a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x44, - 0x22, 0x3f, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x70, 0x72, 0x6f, 0x6a, 0x65, - 0x63, 0x74, 0x2f, 0x7b, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, - 0x7d, 0x2f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, - 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x6a, 0x6f, 0x62, - 0x73, 0x3a, 0x01, 0x2a, 0x12, 0xd7, 0x01, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x4a, 0x6f, 0x62, 0x53, - 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x35, 0x2e, 0x6f, - 0x64, 0x70, 0x66, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, - 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4a, 0x6f, 0x62, 0x53, - 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, 0x6f, 0x64, 0x70, 0x66, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, + 0x74, 0x61, 0x31, 0x2e, 0x4a, 0x6f, 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x02, 0x18, 0x01, 0x52, 0x04, 0x6a, 0x6f, 0x62, 0x73, 0x12, 0x73, + 0x0a, 0x1b, 0x6a, 0x6f, 0x62, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x73, 0x18, 0x02, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x6f, 0x64, 0x70, 0x66, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, - 0x47, 0x65, 0x74, 0x4a, 0x6f, 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x51, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x4b, 0x12, 0x49, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x70, 0x72, - 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x2f, 0x7b, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6e, - 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2f, 0x7b, - 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, - 0x6a, 0x6f, 0x62, 0x2f, 0x7b, 0x6a, 0x6f, 0x62, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12, 0x9e, - 0x01, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x4a, 0x6f, 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x36, 0x2e, 0x6f, 0x64, 0x70, 0x66, 0x2e, 0x6f, + 0x4a, 0x6f, 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x19, 0x6a, 0x6f, 0x62, 0x53, 0x70, 0x65, + 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x73, 0x22, 0xa3, 0x01, 0x0a, 0x18, 0x4a, 0x6f, 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, + 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x4e, + 0x61, 0x6d, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, + 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x61, 0x6d, + 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x3d, 0x0a, 0x03, 0x6a, 0x6f, + 0x62, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x6f, 0x64, 0x70, 0x66, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, - 0x74, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4a, 0x6f, 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, - 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x37, 0x2e, 0x6f, 0x64, 0x70, 0x66, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, - 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4a, - 0x6f, 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x15, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0f, - 0x12, 0x0d, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x6a, 0x6f, 0x62, 0x73, 0x12, - 0xe0, 0x01, 0x0a, 0x16, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x53, 0x70, 0x65, - 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x38, 0x2e, 0x6f, 0x64, 0x70, + 0x74, 0x61, 0x31, 0x2e, 0x4a, 0x6f, 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x03, 0x6a, 0x6f, 0x62, 0x32, 0x9e, 0x11, 0x0a, 0x17, 0x4a, 0x6f, + 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x93, 0x01, 0x0a, 0x16, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, + 0x4a, 0x6f, 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x38, 0x2e, 0x6f, 0x64, 0x70, 0x66, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, + 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x70, + 0x6c, 0x6f, 0x79, 0x4a, 0x6f, 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x39, 0x2e, 0x6f, 0x64, 0x70, 0x66, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, - 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4a, 0x6f, 0x62, - 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x39, 0x2e, 0x6f, 0x64, 0x70, 0x66, 0x2e, 0x6f, 0x70, 0x74, 0x69, - 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, - 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, - 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x51, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x4b, 0x2a, 0x49, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, + 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x4a, 0x6f, 0x62, + 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x12, 0xbc, 0x01, 0x0a, 0x0a, + 0x4a, 0x6f, 0x62, 0x49, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x12, 0x2c, 0x2e, 0x6f, 0x64, 0x70, + 0x66, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, + 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4a, 0x6f, 0x62, 0x49, 0x6e, 0x73, 0x70, 0x65, 0x63, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x6f, 0x64, 0x70, 0x66, 0x2e, + 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, + 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4a, 0x6f, 0x62, 0x49, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x51, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x4b, 0x22, + 0x46, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, + 0x74, 0x2f, 0x7b, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, + 0x2f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x6a, 0x6f, 0x62, 0x2f, + 0x69, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x3a, 0x01, 0x2a, 0x12, 0xd8, 0x01, 0x0a, 0x16, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x38, 0x2e, 0x6f, 0x64, 0x70, 0x66, 0x2e, 0x6f, 0x70, 0x74, + 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, + 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, + 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x39, 0x2e, 0x6f, 0x64, 0x70, 0x66, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, + 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x49, 0x82, 0xd3, 0xe4, 0x93, + 0x02, 0x43, 0x22, 0x3e, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x70, 0x72, 0x6f, + 0x6a, 0x65, 0x63, 0x74, 0x2f, 0x7b, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6e, 0x61, + 0x6d, 0x65, 0x7d, 0x2f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2f, 0x7b, 0x6e, + 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x6a, + 0x6f, 0x62, 0x3a, 0x01, 0x2a, 0x12, 0xd3, 0x01, 0x0a, 0x14, 0x41, 0x64, 0x64, 0x4a, 0x6f, 0x62, + 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x36, + 0x2e, 0x6f, 0x64, 0x70, 0x66, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, + 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x4a, 0x6f, + 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x37, 0x2e, 0x6f, 0x64, 0x70, 0x66, 0x2e, 0x6f, 0x70, + 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, + 0x61, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x4a, 0x6f, 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x4a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x44, 0x22, 0x3f, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x2f, 0x7b, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6e, 0x61, - 0x6d, 0x65, 0x7d, 0x2f, 0x6a, 0x6f, 0x62, 0x2f, 0x7b, 0x6a, 0x6f, 0x62, 0x5f, 0x6e, 0x61, 0x6d, - 0x65, 0x7d, 0x12, 0xcf, 0x01, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x4a, 0x6f, 0x62, 0x53, 0x70, - 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x36, 0x2e, 0x6f, 0x64, - 0x70, 0x66, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, - 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4a, 0x6f, 0x62, 0x53, - 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x37, 0x2e, 0x6f, 0x64, 0x70, 0x66, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, + 0x6d, 0x65, 0x7d, 0x2f, 0x6a, 0x6f, 0x62, 0x73, 0x3a, 0x01, 0x2a, 0x12, 0xd7, 0x01, 0x0a, 0x13, + 0x47, 0x65, 0x74, 0x4a, 0x6f, 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x35, 0x2e, 0x6f, 0x64, 0x70, 0x66, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, - 0x4c, 0x69, 0x73, 0x74, 0x4a, 0x6f, 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x46, 0x82, 0xd3, - 0xe4, 0x93, 0x02, 0x40, 0x12, 0x3e, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x70, - 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x2f, 0x7b, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, - 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2f, - 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, - 0x2f, 0x6a, 0x6f, 0x62, 0x12, 0xbd, 0x01, 0x0a, 0x15, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4a, 0x6f, - 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x37, + 0x47, 0x65, 0x74, 0x4a, 0x6f, 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, 0x6f, 0x64, 0x70, + 0x66, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, + 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4a, 0x6f, 0x62, 0x53, 0x70, 0x65, + 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x51, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x4b, 0x12, 0x49, 0x2f, 0x76, 0x31, 0x62, + 0x65, 0x74, 0x61, 0x31, 0x2f, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x2f, 0x7b, 0x70, 0x72, + 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x6e, 0x61, 0x6d, 0x65, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, + 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x6a, 0x6f, 0x62, 0x2f, 0x7b, 0x6a, 0x6f, 0x62, 0x5f, + 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12, 0x9e, 0x01, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x4a, 0x6f, 0x62, + 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x36, + 0x2e, 0x6f, 0x64, 0x70, 0x66, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, + 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4a, 0x6f, + 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x37, 0x2e, 0x6f, 0x64, 0x70, 0x66, 0x2e, 0x6f, 0x70, + 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, + 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4a, 0x6f, 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x15, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0f, 0x12, 0x0d, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, + 0x31, 0x2f, 0x6a, 0x6f, 0x62, 0x73, 0x12, 0xe0, 0x01, 0x0a, 0x16, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x4a, 0x6f, 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x38, 0x2e, 0x6f, 0x64, 0x70, 0x66, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, + 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x39, 0x2e, 0x6f, 0x64, + 0x70, 0x66, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, + 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4a, 0x6f, + 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x51, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x4b, 0x2a, 0x49, + 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, + 0x2f, 0x7b, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, + 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x73, + 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x6a, 0x6f, 0x62, 0x2f, 0x7b, + 0x6a, 0x6f, 0x62, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12, 0xcf, 0x01, 0x0a, 0x14, 0x4c, 0x69, + 0x73, 0x74, 0x4a, 0x6f, 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x36, 0x2e, 0x6f, 0x64, 0x70, 0x66, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, + 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, + 0x69, 0x73, 0x74, 0x4a, 0x6f, 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x37, 0x2e, 0x6f, 0x64, 0x70, + 0x66, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, + 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4a, 0x6f, 0x62, 0x53, 0x70, + 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x46, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x40, 0x12, 0x3e, 0x2f, 0x76, 0x31, + 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x2f, 0x7b, 0x70, + 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x6e, 0x61, 0x6d, + 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x6a, 0x6f, 0x62, 0x12, 0xbd, 0x01, 0x0a, 0x15, + 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4a, 0x6f, 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x37, 0x2e, 0x6f, 0x64, 0x70, 0x66, 0x2e, 0x6f, 0x70, 0x74, + 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, + 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4a, 0x6f, 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x38, 0x2e, 0x6f, 0x64, 0x70, 0x66, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4a, 0x6f, 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x38, 0x2e, 0x6f, 0x64, 0x70, 0x66, 0x2e, 0x6f, - 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, - 0x74, 0x61, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4a, 0x6f, 0x62, 0x53, 0x70, 0x65, 0x63, - 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x31, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2b, 0x22, 0x29, 0x2f, 0x76, 0x31, 0x62, 0x65, - 0x74, 0x61, 0x31, 0x2f, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x2f, 0x7b, 0x70, 0x72, 0x6f, - 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x6a, 0x6f, 0x62, 0x2f, 0x63, - 0x68, 0x65, 0x63, 0x6b, 0x12, 0x91, 0x01, 0x0a, 0x16, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4a, 0x6f, - 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, - 0x38, 0x2e, 0x6f, 0x64, 0x70, 0x66, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, - 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, - 0x6b, 0x4a, 0x6f, 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x39, 0x2e, 0x6f, 0x64, 0x70, 0x66, - 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, - 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4a, 0x6f, 0x62, 0x53, 0x70, - 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x70, 0x0a, 0x0b, 0x52, 0x65, 0x66, 0x72, - 0x65, 0x73, 0x68, 0x4a, 0x6f, 0x62, 0x73, 0x12, 0x2d, 0x2e, 0x6f, 0x64, 0x70, 0x66, 0x2e, 0x6f, - 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, - 0x74, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x4a, 0x6f, 0x62, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x6f, 0x64, 0x70, 0x66, 0x2e, 0x6f, 0x70, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x31, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2b, + 0x22, 0x29, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x70, 0x72, 0x6f, 0x6a, 0x65, + 0x63, 0x74, 0x2f, 0x7b, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, + 0x7d, 0x2f, 0x6a, 0x6f, 0x62, 0x2f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x91, 0x01, 0x0a, 0x16, + 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4a, 0x6f, 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x38, 0x2e, 0x6f, 0x64, 0x70, 0x66, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, - 0x61, 0x31, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x4a, 0x6f, 0x62, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x86, 0x01, 0x0a, 0x13, 0x47, - 0x65, 0x74, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x4a, 0x6f, 0x62, 0x73, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x12, 0x35, 0x2e, 0x6f, 0x64, 0x70, 0x66, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, - 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x47, - 0x65, 0x74, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x4a, 0x6f, 0x62, 0x73, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, 0x6f, 0x64, 0x70, 0x66, + 0x61, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4a, 0x6f, 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, + 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x39, 0x2e, 0x6f, 0x64, 0x70, 0x66, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, + 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x68, 0x65, + 0x63, 0x6b, 0x4a, 0x6f, 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, + 0x70, 0x0a, 0x0b, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x4a, 0x6f, 0x62, 0x73, 0x12, 0x2d, + 0x2e, 0x6f, 0x64, 0x70, 0x66, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, + 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, + 0x73, 0x68, 0x4a, 0x6f, 0x62, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, + 0x6f, 0x64, 0x70, 0x66, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, + 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, + 0x68, 0x4a, 0x6f, 0x62, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, + 0x01, 0x12, 0x86, 0x01, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x4a, + 0x6f, 0x62, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x35, 0x2e, 0x6f, 0x64, 0x70, 0x66, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x4a, - 0x6f, 0x62, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x00, 0x42, 0xa2, 0x01, 0x0a, 0x16, 0x69, 0x6f, 0x2e, 0x6f, 0x64, 0x70, 0x66, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x6e, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x42, 0x1e, - 0x4a, 0x6f, 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x50, 0x01, - 0x5a, 0x1e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x64, 0x70, - 0x66, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x6e, 0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, - 0x92, 0x41, 0x45, 0x12, 0x05, 0x32, 0x03, 0x30, 0x2e, 0x31, 0x1a, 0x0e, 0x31, 0x32, 0x37, 0x2e, - 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x3a, 0x39, 0x31, 0x30, 0x30, 0x22, 0x04, 0x2f, 0x61, 0x70, 0x69, - 0x2a, 0x01, 0x01, 0x72, 0x23, 0x0a, 0x21, 0x4f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x20, 0x4a, - 0x6f, 0x62, 0x20, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6f, 0x62, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x36, 0x2e, 0x6f, 0x64, 0x70, 0x66, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, + 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, + 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x4a, 0x6f, 0x62, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0xa2, 0x01, 0x0a, 0x16, 0x69, + 0x6f, 0x2e, 0x6f, 0x64, 0x70, 0x66, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x6e, 0x2e, 0x6f, 0x70, + 0x74, 0x69, 0x6d, 0x75, 0x73, 0x42, 0x1e, 0x4a, 0x6f, 0x62, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4d, 0x61, + 0x6e, 0x61, 0x67, 0x65, 0x72, 0x50, 0x01, 0x5a, 0x1e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x64, 0x70, 0x66, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x6e, 0x2f, + 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x92, 0x41, 0x45, 0x12, 0x05, 0x32, 0x03, 0x30, 0x2e, + 0x31, 0x1a, 0x0e, 0x31, 0x32, 0x37, 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x3a, 0x39, 0x31, 0x30, + 0x30, 0x22, 0x04, 0x2f, 0x61, 0x70, 0x69, 0x2a, 0x01, 0x01, 0x72, 0x23, 0x0a, 0x21, 0x4f, 0x70, + 0x74, 0x69, 0x6d, 0x75, 0x73, 0x20, 0x4a, 0x6f, 0x62, 0x20, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/protos/odpf/optimus/core/v1beta1/job_spec.swagger.json b/protos/odpf/optimus/core/v1beta1/job_spec.swagger.json index 24f0bae755..77ef43d8d4 100644 --- a/protos/odpf/optimus/core/v1beta1/job_spec.swagger.json +++ b/protos/odpf/optimus/core/v1beta1/job_spec.swagger.json @@ -57,6 +57,12 @@ "in": "query", "required": false, "type": "string" + }, + { + "name": "namespaceName", + "in": "query", + "required": false, + "type": "string" } ], "tags": [