diff --git a/src/cmd/cli/command/commands.go b/src/cmd/cli/command/commands.go index ba304fde2..366edff64 100644 --- a/src/cmd/cli/command/commands.go +++ b/src/cmd/cli/command/commands.go @@ -25,13 +25,10 @@ import ( defangv1 "github.com/DefangLabs/defang/src/protos/io/defang/v1" "github.com/aws/smithy-go" "github.com/bufbuild/connect-go" - proj "github.com/compose-spec/compose-go/v2/types" + composeTypes "github.com/compose-spec/compose-go/v2/types" "github.com/spf13/cobra" ) -const DEFANG_PORTAL_HOST = "portal.defang.dev" -const SERVICE_PORTAL_URL = "https://" + DEFANG_PORTAL_HOST + "/service" - const authNeeded = "auth-needed" // annotation to indicate that a command needs authorization var authNeededAnnotation = map[string]string{authNeeded: ""} @@ -613,7 +610,7 @@ var newCmd = &cobra.Command{ RunE: generateCmd.RunE, } -func collectUnsetEnvVars(project *proj.Project) []string { +func collectUnsetEnvVars(project *composeTypes.Project) []string { var envVars []string if project != nil { for _, service := range project.Services { diff --git a/src/cmd/cli/command/compose.go b/src/cmd/cli/command/compose.go index 8eb09aa44..fa9d2a3d9 100644 --- a/src/cmd/cli/command/compose.go +++ b/src/cmd/cli/command/compose.go @@ -19,19 +19,19 @@ import ( "github.com/spf13/cobra" ) -func isManagedService(service *defangv1.Service) bool { - if service == nil { +func isManagedService(service compose.ServiceConfig) bool { + if service.Extensions == nil { return false } - return service.StaticFiles != nil || service.Redis != nil || service.Postgres != nil + return service.Extensions["x-defang-static-files"] != nil || service.Extensions["x-defang-redis"] != nil || service.Extensions["x-defang-postgres"] != nil } -func GetUnreferencedManagedResources(serviceInfos []*defangv1.ServiceInfo) []string { +func getUnreferencedManagedResources(serviceInfos compose.Services) []string { managedResources := make([]string, 0) for _, service := range serviceInfos { - if isManagedService(service.Service) { - managedResources = append(managedResources, service.Service.Name) + if isManagedService(service) { + managedResources = append(managedResources, service.Name) } } @@ -83,7 +83,7 @@ func makeComposeUpCmd() *cobra.Command { printPlaygroundPortalServiceURLs(deploy.Services) - var managedResources = GetUnreferencedManagedResources(deploy.Services) + var managedResources = getUnreferencedManagedResources(project.Services) if len(managedResources) > 0 { term.Warnf("Defang cannot monitor status of the following managed service(s): %v.\n To check if the managed service is up, check the status of the service which depends on it.", managedResources) } diff --git a/src/cmd/cli/command/compose_test.go b/src/cmd/cli/command/compose_test.go index 71049279c..f3bdce970 100644 --- a/src/cmd/cli/command/compose_test.go +++ b/src/cmd/cli/command/compose_test.go @@ -3,75 +3,83 @@ package command import ( "testing" - defangv1 "github.com/DefangLabs/defang/src/protos/io/defang/v1" + "github.com/compose-spec/compose-go/v2/types" ) func TestGetUnreferencedManagedResources(t *testing.T) { t.Run("no services", func(t *testing.T) { - project := []*defangv1.ServiceInfo{} - managed := GetUnreferencedManagedResources(project) + project := types.Services{} + + managed := getUnreferencedManagedResources(project) if len(managed) != 0 { t.Errorf("Expected 0 managed resources, got %d (%v)", len(managed), managed) } }) t.Run("one service all managed", func(t *testing.T) { - project := []*defangv1.ServiceInfo{} - project = append(project, &defangv1.ServiceInfo{ - Service: &defangv1.Service{Name: "service1", Postgres: &defangv1.Postgres{}}}) + project := types.Services{ + "service1": types.ServiceConfig{ + Extensions: map[string]any{"x-defang-postgres": true}, + }, + } - managed := GetUnreferencedManagedResources(project) + managed := getUnreferencedManagedResources(project) if len(managed) != 1 { t.Errorf("Expected 1 managed resource, got %d (%v)", len(managed), managed) } }) t.Run("one service unmanaged", func(t *testing.T) { - project := []*defangv1.ServiceInfo{} - project = append(project, &defangv1.ServiceInfo{ - Service: &defangv1.Service{Name: "service1"}}) + project := types.Services{ + "service1": types.ServiceConfig{}, + } - managed := GetUnreferencedManagedResources(project) + managed := getUnreferencedManagedResources(project) if len(managed) != 0 { t.Errorf("Expected 0 managed resource, got %d (%v)", len(managed), managed) } }) t.Run("one service unmanaged, one service managed", func(t *testing.T) { - project := []*defangv1.ServiceInfo{} - project = append(project, &defangv1.ServiceInfo{ - Service: &defangv1.Service{Name: "service1"}}) - project = append(project, &defangv1.ServiceInfo{ - Service: &defangv1.Service{Name: "service2", Postgres: &defangv1.Postgres{}}}) + project := types.Services{ + "service1": types.ServiceConfig{}, + "service2": types.ServiceConfig{ + Extensions: map[string]any{"x-defang-postgres": true}, + }, + } - managed := GetUnreferencedManagedResources(project) + managed := getUnreferencedManagedResources(project) if len(managed) != 1 { t.Errorf("Expected 1 managed resource, got %d (%v)", len(managed), managed) } }) t.Run("two service two unmanaged", func(t *testing.T) { - project := []*defangv1.ServiceInfo{} - project = append(project, &defangv1.ServiceInfo{ - Service: &defangv1.Service{Name: "service1"}}) - project = append(project, &defangv1.ServiceInfo{ - Service: &defangv1.Service{Name: "service2"}}) + project := types.Services{ + "service1": types.ServiceConfig{}, + "service2": types.ServiceConfig{}, + } - managed := GetUnreferencedManagedResources(project) + managed := getUnreferencedManagedResources(project) if len(managed) != 0 { t.Errorf("Expected 0 managed resource, got %d (%v)", len(managed), managed) } }) t.Run("one service two managed", func(t *testing.T) { - project := []*defangv1.ServiceInfo{} - project = append(project, &defangv1.ServiceInfo{ - Service: &defangv1.Service{Name: "service1", Postgres: &defangv1.Postgres{}, Redis: &defangv1.Redis{}}}) + project := types.Services{ + "service1": types.ServiceConfig{ + Extensions: map[string]any{"x-defang-postgres": true}, + }, + "service2": types.ServiceConfig{ + Extensions: map[string]any{"x-defang-redis": true}, + }, + } - managed := GetUnreferencedManagedResources(project) - if len(managed) != 1 { - t.Errorf("Expected 1 managed resource, got %d (%s)", len(managed), managed) + managed := getUnreferencedManagedResources(project) + if len(managed) != 2 { + t.Errorf("Expected 2 managed resource, got %d (%s)", len(managed), managed) } }) } diff --git a/src/cmd/cli/command/deploymentinfo.go b/src/cmd/cli/command/deploymentinfo.go index b0694cb69..f3e17abbf 100644 --- a/src/cmd/cli/command/deploymentinfo.go +++ b/src/cmd/cli/command/deploymentinfo.go @@ -1,20 +1,21 @@ package command import ( - "fmt" - "github.com/DefangLabs/defang/src/pkg/cli" cliClient "github.com/DefangLabs/defang/src/pkg/cli/client" "github.com/DefangLabs/defang/src/pkg/term" defangv1 "github.com/DefangLabs/defang/src/protos/io/defang/v1" ) +const DEFANG_PORTAL_HOST = "portal.defang.dev" +const SERVICE_PORTAL_URL = "https://" + DEFANG_PORTAL_HOST + "/service" + func printPlaygroundPortalServiceURLs(serviceInfos []*defangv1.ServiceInfo) { // We can only show services deployed to the prod1 defang SaaS environment. if provider == cliClient.ProviderDefang && cluster == cli.DefaultCluster { term.Info("Monitor your services' status in the defang portal") for _, serviceInfo := range serviceInfos { - fmt.Println(" -", SERVICE_PORTAL_URL+"/"+serviceInfo.Service.Name) + term.Println(" -", SERVICE_PORTAL_URL+"/"+serviceInfo.Service.Name) } } } @@ -36,13 +37,13 @@ func printEndpoints(serviceInfos []*defangv1.ServiceInfo) { if serviceInfo.Service.Ports[i].Mode == defangv1.Mode_INGRESS { endpoint = "https://" + endpoint } - fmt.Println(" -", endpoint) + term.Println(" -", endpoint) } - if serviceInfo.Service.Domainname != "" { + if serviceInfo.Domainname != "" { if serviceInfo.ZoneId != "" { - fmt.Println(" -", "https://"+serviceInfo.Service.Domainname) + term.Println(" -", "https://"+serviceInfo.Domainname) } else { - fmt.Println(" -", "https://"+serviceInfo.Service.Domainname+" (after `defang cert generate` to get a TLS certificate)") + term.Println(" -", "https://"+serviceInfo.Domainname+" (after `defang cert generate` to get a TLS certificate)") } } } diff --git a/src/cmd/cli/command/deploymentinfo_test.go b/src/cmd/cli/command/deploymentinfo_test.go new file mode 100644 index 000000000..18839b9cf --- /dev/null +++ b/src/cmd/cli/command/deploymentinfo_test.go @@ -0,0 +1,67 @@ +package command + +import ( + "bytes" + "testing" + + "github.com/DefangLabs/defang/src/pkg/cli" + cliClient "github.com/DefangLabs/defang/src/pkg/cli/client" + "github.com/DefangLabs/defang/src/pkg/term" + defangv1 "github.com/DefangLabs/defang/src/protos/io/defang/v1" +) + +func TestPrintPlaygroundPortalServiceURLs(t *testing.T) { + defaultTerm := term.DefaultTerm + t.Cleanup(func() { + term.DefaultTerm = defaultTerm + }) + + var stdout, stderr bytes.Buffer + term.DefaultTerm = term.NewTerm(&stdout, &stderr) + + provider = cliClient.ProviderDefang + cluster = cli.DefaultCluster + printPlaygroundPortalServiceURLs([]*defangv1.ServiceInfo{ + { + Service: &defangv1.Service{Name: "service1"}, + }}) + const want = ` * Monitor your services' status in the defang portal + - https://portal.defang.dev/service/service1 +` + if got := stdout.String(); got != want { + t.Errorf("got %q, want %q", got, want) + } +} + +func TestPrintEndpoints(t *testing.T) { + defaultTerm := term.DefaultTerm + t.Cleanup(func() { + term.DefaultTerm = defaultTerm + }) + + var stdout, stderr bytes.Buffer + term.DefaultTerm = term.NewTerm(&stdout, &stderr) + + printEndpoints([]*defangv1.ServiceInfo{ + { + Service: &defangv1.Service{ + Name: "service1", + Ports: []*defangv1.Port{ + {Mode: defangv1.Mode_INGRESS}, + {Mode: defangv1.Mode_HOST}, + }, + }, + Status: "UNKNOWN", + Endpoints: []string{ + "example.com", + "service1.internal", + }, + }}) + const want = ` * Service service1 has status UNKNOWN and will be available at: + - https://example.com + - service1.internal +` + if got := stdout.String(); got != want { + t.Errorf("got %q, want %q", got, want) + } +} diff --git a/src/pkg/cli/cert.go b/src/pkg/cli/cert.go index f7faffed0..96cb1fdb6 100644 --- a/src/pkg/cli/cert.go +++ b/src/pkg/cli/cert.go @@ -14,6 +14,7 @@ import ( "github.com/DefangLabs/defang/src/pkg" "github.com/DefangLabs/defang/src/pkg/cert" cliClient "github.com/DefangLabs/defang/src/pkg/cli/client" + "github.com/DefangLabs/defang/src/pkg/cli/compose" "github.com/DefangLabs/defang/src/pkg/dns" "github.com/DefangLabs/defang/src/pkg/spinner" "github.com/DefangLabs/defang/src/pkg/term" @@ -76,11 +77,11 @@ var ( ) func GenerateLetsEncryptCert(ctx context.Context, client cliClient.Client) error { - projectName, err := client.LoadProjectName(ctx) + project, err := client.LoadProject(ctx) if err != nil { return err } - term.Debugf("Generating TLS cert for project %q", projectName) + term.Debugf("Generating TLS cert for project %q", project.Name) services, err := client.GetServices(ctx) if err != nil { @@ -88,17 +89,17 @@ func GenerateLetsEncryptCert(ctx context.Context, client cliClient.Client) error } cnt := 0 - for _, service := range services.Services { - if service.Service != nil && service.Service.Domainname != "" && service.ZoneId == "" { + for _, serviceInfo := range services.Services { + if service, ok := project.Services[serviceInfo.Service.Name]; ok && service.DomainName != "" && serviceInfo.ZoneId == "" { cnt++ - targets := []string{service.PublicFqdn} - for i, endpoint := range service.Endpoints { - if service.Service.Ports[i].Mode == defangv1.Mode_INGRESS { + targets := []string{serviceInfo.PublicFqdn} + for i, endpoint := range serviceInfo.Endpoints { + if service.Ports[i].Mode == compose.Mode_INGRESS { targets = append(targets, endpoint) } } - term.Debugf("Found service %v with domain %v and targets %v", service.Service.Name, service.Service.Domainname, targets) - generateCert(ctx, service.Service.Domainname, targets, client) + term.Debugf("Found service %v with domain %v and targets %v", service.Name, service.DomainName, targets) + generateCert(ctx, service.DomainName, targets, client) } } if cnt == 0 { diff --git a/src/pkg/cli/cert_test.go b/src/pkg/cli/cert_test.go index 602a0bb80..50f8863d0 100644 --- a/src/pkg/cli/cert_test.go +++ b/src/pkg/cli/cert_test.go @@ -14,10 +14,6 @@ import ( "time" ) -type dnsRequest struct { - Type string - Domain string -} type tryResult struct { result *http.Response err error diff --git a/src/pkg/cli/client/byoc/aws/byoc.go b/src/pkg/cli/client/byoc/aws/byoc.go index 2d4946e37..18592b837 100644 --- a/src/pkg/cli/client/byoc/aws/byoc.go +++ b/src/pkg/cli/client/byoc/aws/byoc.go @@ -19,6 +19,7 @@ import ( "github.com/DefangLabs/defang/src/pkg" "github.com/DefangLabs/defang/src/pkg/cli/client" "github.com/DefangLabs/defang/src/pkg/cli/client/byoc" + "github.com/DefangLabs/defang/src/pkg/cli/compose" "github.com/DefangLabs/defang/src/pkg/clouds/aws" "github.com/DefangLabs/defang/src/pkg/clouds/aws/ecs" "github.com/DefangLabs/defang/src/pkg/clouds/aws/ecs/cfn" @@ -34,6 +35,8 @@ import ( "github.com/aws/aws-sdk-go-v2/service/sts" "github.com/aws/smithy-go/ptr" "github.com/bufbuild/connect-go" + "github.com/compose-spec/compose-go/v2/loader" + composeTypes "github.com/compose-spec/compose-go/v2/types" "google.golang.org/protobuf/proto" ) @@ -176,12 +179,19 @@ func (b *ByocAws) deploy(ctx context.Context, req *defangv1.DeployRequest, cmd s return nil, err } + // If multiple Compose files were provided, req.Compose is the merged representation of all the files + project, err := loader.LoadWithContext(ctx, composeTypes.ConfigDetails{ConfigFiles: []composeTypes.ConfigFile{{Content: req.Compose}}}) + if err != nil { + return nil, err + } + etag := pkg.RandomID() - if len(req.Services) > b.Quota.Services { + if len(project.Services) > b.Quota.Services { return nil, errors.New("maximum number of services reached") } + serviceInfos := []*defangv1.ServiceInfo{} - for _, service := range req.Services { + for _, service := range project.Services { serviceInfo, err := b.update(ctx, service) if err != nil { return nil, fmt.Errorf("service %q: %w", service.Name, err) @@ -245,7 +255,7 @@ func (b *ByocAws) deploy(ctx context.Context, req *defangv1.DeployRequest, cmd s for _, si := range serviceInfos { if si.UseAcmeCert { - term.Infof("To activate TLS certificate for %v, run 'defang cert gen'", si.Service.Domainname) + term.Infof("To activate TLS certificate for %v, run 'defang cert gen'", si.Domainname) } } @@ -624,35 +634,38 @@ func (b *ByocAws) Follow(ctx context.Context, req *defangv1.TailRequest) (client } // This function was copied from Fabric controller and slightly modified to work with BYOC -func (b *ByocAws) update(ctx context.Context, service *defangv1.Service) (*defangv1.ServiceInfo, error) { - if err := b.Quota.Validate(service); err != nil { +func (b *ByocAws) update(ctx context.Context, service composeTypes.ServiceConfig) (*defangv1.ServiceInfo, error) { + if err := b.Quota.Validate(&service); err != nil { return nil, err } - // Check to make sure all required secrets are present in the secrets store - missing, err := b.checkForMissingSecrets(ctx, service.Secrets) + // Check to make sure all required configs are present in the configs store + var configs []string + for config, value := range service.Environment { + if value == nil { + configs = append(configs, config) + } + } + err := b.checkForMissingSecrets(ctx, configs) if err != nil { return nil, err } - if missing != nil { - return nil, fmt.Errorf("missing config %q", missing) // retryable CodeFailedPrecondition - } ensure(b.ProjectName != "", "ProjectName not set") si := &defangv1.ServiceInfo{ - Service: service, - Project: b.ProjectName, // was: tenant Etag: pkg.RandomID(), // TODO: could be hash for dedup/idempotency + Project: b.ProjectName, // was: tenant + Service: &defangv1.Service{Name: service.Name}, } hasHost := false hasIngress := false fqn := service.Name - if service.StaticFiles == nil { + if sf := service.Extensions["x-defang-static-files"]; sf == nil { for _, port := range service.Ports { - hasIngress = hasIngress || port.Mode == defangv1.Mode_INGRESS - hasHost = hasHost || port.Mode == defangv1.Mode_HOST - si.Endpoints = append(si.Endpoints, b.getEndpoint(fqn, port)) + hasIngress = hasIngress || port.Mode == compose.Mode_INGRESS + hasHost = hasHost || port.Mode == compose.Mode_HOST + si.Endpoints = append(si.Endpoints, b.getEndpoint(fqn, &port)) } } else { si.PublicFqdn = b.getPublicFqdn(fqn) @@ -666,14 +679,15 @@ func (b *ByocAws) update(ctx context.Context, service *defangv1.Service) (*defan si.PrivateFqdn = b.getPrivateFqdn(fqn) } - if service.Domainname != "" { - if !hasIngress && service.StaticFiles == nil { + if service.DomainName != "" { + if !hasIngress && service.Extensions["x-defang-static-files"] == nil { return nil, errors.New("domainname requires at least one ingress port") // retryable CodeFailedPrecondition } - // Do a DNS lookup for Domainname and confirm it's indeed a CNAME to the service's public FQDN - cname, _ := net.LookupCNAME(service.Domainname) + // Do a DNS lookup for DomainName and confirm it's indeed a CNAME to the service's public FQDN + cname, _ := net.LookupCNAME(service.DomainName) if strings.TrimSuffix(cname, ".") != si.PublicFqdn { - zoneId, err := b.findZone(ctx, service.Domainname, service.DnsRole) + dnsRole, _ := service.Extensions["x-defang-dns-role"].(string) + zoneId, err := b.findZone(ctx, service.DomainName, dnsRole) if err != nil { return nil, err } @@ -689,7 +703,7 @@ func (b *ByocAws) update(ctx context.Context, service *defangv1.Service) (*defan si.Status = "UPDATE_QUEUED" si.State = defangv1.ServiceState_UPDATE_QUEUED - if si.Service.Build != nil { + if service.Build != nil { si.Status = "BUILD_QUEUED" // in SaaS, this gets overwritten by the ECS events for "kaniko" si.State = defangv1.ServiceState_BUILD_QUEUED } @@ -697,22 +711,22 @@ func (b *ByocAws) update(ctx context.Context, service *defangv1.Service) (*defan } // This function was copied from Fabric controller and slightly modified to work with BYOC -func (b *ByocAws) checkForMissingSecrets(ctx context.Context, secrets []*defangv1.Secret) (*defangv1.Secret, error) { +func (b *ByocAws) checkForMissingSecrets(ctx context.Context, secrets []string) error { if len(secrets) == 0 { - return nil, nil // no secrets to check + return nil // no secrets to check } prefix := b.getSecretID("") sorted, err := b.driver.ListSecretsByPrefix(ctx, prefix) if err != nil { - return nil, err + return err } for _, secret := range secrets { - fqn := b.getSecretID(secret.Source) + fqn := b.getSecretID(secret) if !searchSecret(sorted, fqn) { - return secret, nil // secret not found + return fmt.Errorf("missing config %q", secret) } } - return nil, nil // all secrets found + return nil // all secrets found } // This function was copied from Fabric controller @@ -726,8 +740,8 @@ func searchSecret(sorted []qualifiedName, fqn qualifiedName) bool { type qualifiedName = string // legacy // This function was copied from Fabric controller and slightly modified to work with BYOC -func (b *ByocAws) getEndpoint(fqn qualifiedName, port *defangv1.Port) string { - if port.Mode == defangv1.Mode_HOST { +func (b *ByocAws) getEndpoint(fqn qualifiedName, port *composeTypes.ServicePortConfig) string { + if port.Mode == compose.Mode_HOST { privateFqdn := b.getPrivateFqdn(fqn) return fmt.Sprintf("%s:%d", privateFqdn, port.Target) } diff --git a/src/pkg/cli/client/byoc/aws/byoc_test.go b/src/pkg/cli/client/byoc/aws/byoc_test.go index f9fcfeae8..b3dd0b749 100644 --- a/src/pkg/cli/client/byoc/aws/byoc_test.go +++ b/src/pkg/cli/client/byoc/aws/byoc_test.go @@ -13,21 +13,22 @@ import ( "testing" "github.com/DefangLabs/defang/src/pkg/cli/client" + "github.com/DefangLabs/defang/src/pkg/cli/compose" "github.com/DefangLabs/defang/src/pkg/clouds/aws/ecs" "github.com/DefangLabs/defang/src/pkg/types" defangv1 "github.com/DefangLabs/defang/src/protos/io/defang/v1" - compose "github.com/compose-spec/compose-go/v2/types" + composeTypes "github.com/compose-spec/compose-go/v2/types" ) func TestDomainMultipleProjectSupport(t *testing.T) { - port80 := &defangv1.Port{Mode: defangv1.Mode_INGRESS, Target: 80} - port8080 := &defangv1.Port{Mode: defangv1.Mode_INGRESS, Target: 8080} - hostModePort := &defangv1.Port{Mode: defangv1.Mode_HOST, Target: 80} + port80 := &composeTypes.ServicePortConfig{Mode: compose.Mode_INGRESS, Target: 80} + port8080 := &composeTypes.ServicePortConfig{Mode: compose.Mode_INGRESS, Target: 8080} + hostModePort := &composeTypes.ServicePortConfig{Mode: compose.Mode_HOST, Target: 80} tests := []struct { ProjectName string TenantID types.TenantID Fqn string - Port *defangv1.Port + Port *composeTypes.ServicePortConfig EndPoint string PublicFqdn string PrivateFqdn string @@ -76,8 +77,8 @@ type FakeLoader struct { ProjectName string } -func (f FakeLoader) LoadProject(ctx context.Context) (*compose.Project, error) { - return &compose.Project{Name: f.ProjectName}, nil +func (f FakeLoader) LoadProject(ctx context.Context) (*composeTypes.Project, error) { + return &composeTypes.Project{Name: f.ProjectName}, nil } func (f FakeLoader) LoadProjectName(ctx context.Context) (string, error) { diff --git a/src/pkg/cli/client/byoc/baseclient.go b/src/pkg/cli/client/byoc/baseclient.go index 489544c89..663e7f66f 100644 --- a/src/pkg/cli/client/byoc/baseclient.go +++ b/src/pkg/cli/client/byoc/baseclient.go @@ -13,7 +13,7 @@ import ( "github.com/DefangLabs/defang/src/pkg/types" defangv1 "github.com/DefangLabs/defang/src/protos/io/defang/v1" "github.com/bufbuild/connect-go" - compose "github.com/compose-spec/compose-go/v2/types" + composeTypes "github.com/compose-spec/compose-go/v2/types" ) const ( @@ -70,7 +70,7 @@ type ByocBaseClient struct { ShouldDelegateSubdomain bool TenantID string - project *compose.Project + project *composeTypes.Project bootstrapLister BootstrapLister } @@ -117,7 +117,7 @@ func (b *ByocBaseClient) GetVersions(context.Context) (*defangv1.Version, error) return &defangv1.Version{Fabric: CdLatestImageTag}, nil } -func (b *ByocBaseClient) LoadProject(ctx context.Context) (*compose.Project, error) { +func (b *ByocBaseClient) LoadProject(ctx context.Context) (*composeTypes.Project, error) { if b.project != nil { return b.project, nil } diff --git a/src/pkg/cli/client/byoc/do/byoc.go b/src/pkg/cli/client/byoc/do/byoc.go index 2e7bd5825..09b697984 100644 --- a/src/pkg/cli/client/byoc/do/byoc.go +++ b/src/pkg/cli/client/byoc/do/byoc.go @@ -281,8 +281,7 @@ func (b *ByocDo) CreateUploadURL(ctx context.Context, req *defangv1.UploadURLReq } func (b *ByocDo) Delete(ctx context.Context, req *defangv1.DeleteRequest) (*defangv1.DeleteResponse, error) { - // Unsupported in DO - return &defangv1.DeleteResponse{}, errors.ErrUnsupported + return nil, client.ErrNotImplemented("not implemented for DigitalOcean") } func (b *ByocDo) Destroy(ctx context.Context) (string, error) { @@ -628,9 +627,9 @@ func (b *ByocDo) environment() []*godo.AppVariableDefinition { func (b *ByocDo) update(ctx context.Context, service *defangv1.Service) (*defangv1.ServiceInfo, error) { si := &defangv1.ServiceInfo{ - Service: service, - Project: b.ProjectName, Etag: pkg.RandomID(), + Project: b.ProjectName, + Service: &defangv1.Service{Name: service.Name}, } //hasIngress := false @@ -705,9 +704,9 @@ func (b *ByocDo) processServiceInfo(service *godo.AppServiceSpec) *defangv1.Serv Project: b.ProjectName, Etag: pkg.RandomID(), Service: &defangv1.Service{ - Name: service.Name, - Image: service.Image.Digest, - Environment: getServiceEnv(service.Envs), + Name: service.Name, + // Image: service.Image.Digest, + // Environment: getServiceEnv(service.Envs), }, } @@ -858,5 +857,4 @@ func printlogs(resp *defangv1.TailResponse, msg *defangv1.LogEntry) { io.WriteString(buf, line) } term.Println(buf.String()) - } diff --git a/src/pkg/cli/client/client.go b/src/pkg/cli/client/client.go index 295408b20..a2e6a411d 100644 --- a/src/pkg/cli/client/client.go +++ b/src/pkg/cli/client/client.go @@ -5,7 +5,7 @@ import ( "github.com/DefangLabs/defang/src/pkg/types" defangv1 "github.com/DefangLabs/defang/src/protos/io/defang/v1" - compose "github.com/compose-spec/compose-go/v2/types" + composeTypes "github.com/compose-spec/compose-go/v2/types" ) type ServerStream[Res any] interface { @@ -17,7 +17,7 @@ type ServerStream[Res any] interface { type ProjectLoader interface { LoadProjectName(context.Context) (string, error) - LoadProject(context.Context) (*compose.Project, error) + LoadProject(context.Context) (*composeTypes.Project, error) } type FabricClient interface { @@ -58,7 +58,7 @@ type Client interface { TearDown(context.Context) error WhoAmI(context.Context) (*defangv1.WhoAmIResponse, error) - LoadProject(context.Context) (*compose.Project, error) + LoadProject(context.Context) (*composeTypes.Project, error) LoadProjectName(context.Context) (string, error) SetProjectName(string) } diff --git a/src/pkg/cli/client/mock.go b/src/pkg/cli/client/mock.go index 02f433138..dd3f9b7b4 100644 --- a/src/pkg/cli/client/mock.go +++ b/src/pkg/cli/client/mock.go @@ -6,13 +6,13 @@ import ( "io" defangv1 "github.com/DefangLabs/defang/src/protos/io/defang/v1" - compose "github.com/compose-spec/compose-go/v2/types" + composeTypes "github.com/compose-spec/compose-go/v2/types" ) type MockClient struct { Client UploadUrl string - Project *compose.Project + Project *composeTypes.Project ServerStream ServerStream[defangv1.TailResponse] } @@ -28,7 +28,7 @@ func (m MockClient) ServiceDNS(service string) string { return service } -func (m MockClient) LoadProject(ctx context.Context) (*compose.Project, error) { +func (m MockClient) LoadProject(ctx context.Context) (*composeTypes.Project, error) { return m.Project, nil } diff --git a/src/pkg/cli/client/playground.go b/src/pkg/cli/client/playground.go index f588c39ef..949ba950f 100644 --- a/src/pkg/cli/client/playground.go +++ b/src/pkg/cli/client/playground.go @@ -8,17 +8,17 @@ import ( "github.com/DefangLabs/defang/src/pkg/types" defangv1 "github.com/DefangLabs/defang/src/protos/io/defang/v1" "github.com/bufbuild/connect-go" - compose "github.com/compose-spec/compose-go/v2/types" + composeTypes "github.com/compose-spec/compose-go/v2/types" "google.golang.org/protobuf/types/known/emptypb" ) type PlaygroundClient struct { GrpcClient - project *compose.Project + project *composeTypes.Project projectName string } -func (g PlaygroundClient) LoadProject(ctx context.Context) (*compose.Project, error) { +func (g PlaygroundClient) LoadProject(ctx context.Context) (*composeTypes.Project, error) { if g.project != nil { return g.project, nil } diff --git a/src/pkg/cli/compose/constants.go b/src/pkg/cli/compose/constants.go new file mode 100644 index 000000000..67fa4f3ad --- /dev/null +++ b/src/pkg/cli/compose/constants.go @@ -0,0 +1,8 @@ +package compose + +const Mode_INGRESS = "ingress" +const Mode_HOST = "host" + +const Protocol_TCP = "tcp" +const Protocol_UDP = "udp" +const Protocol_HTTP = "http" diff --git a/src/pkg/cli/compose/convert.go b/src/pkg/cli/compose/convert.go index 3a2a3c062..e18635524 100644 --- a/src/pkg/cli/compose/convert.go +++ b/src/pkg/cli/compose/convert.go @@ -1,233 +1,13 @@ package compose import ( - "context" "fmt" - "slices" - "strings" - "github.com/DefangLabs/defang/src/pkg/cli/client" "github.com/DefangLabs/defang/src/pkg/term" - defangv1 "github.com/DefangLabs/defang/src/protos/io/defang/v1" - compose "github.com/compose-spec/compose-go/v2/types" + composeTypes "github.com/compose-spec/compose-go/v2/types" ) -func ConvertServices(ctx context.Context, c client.Client, serviceConfigs compose.Services, upload UploadMode) ([]*defangv1.Service, error) { - - svcNameReplacer := NewServiceNameReplacer(c, serviceConfigs) - - // Preload the current config so we can detect which environment variables should be passed as "secrets" - config, err := c.ListConfig(ctx) - if err != nil { - term.Debugf("failed to load config: %v", err) - config = &defangv1.Secrets{} - } - slices.Sort(config.Names) // sort for binary search - - // - // Publish updates - // - var services []*defangv1.Service - for _, svccfg := range serviceConfigs { - - var healthcheck *defangv1.HealthCheck - if svccfg.HealthCheck != nil && len(svccfg.HealthCheck.Test) > 0 && !svccfg.HealthCheck.Disable { - healthcheck = &defangv1.HealthCheck{ - Test: svccfg.HealthCheck.Test, - } - if nil != svccfg.HealthCheck.Interval { - healthcheck.Interval = uint32(*svccfg.HealthCheck.Interval / 1e9) - } - if nil != svccfg.HealthCheck.Timeout { - healthcheck.Timeout = uint32(*svccfg.HealthCheck.Timeout / 1e9) - } - if nil != svccfg.HealthCheck.Retries { - healthcheck.Retries = uint32(*svccfg.HealthCheck.Retries) - } - } - - var deploy *defangv1.Deploy - if svccfg.Deploy != nil { - deploy = &defangv1.Deploy{} - deploy.Replicas = 1 // default to one replica per service; allow the user to override this to 0 - if svccfg.Deploy.Replicas != nil { - deploy.Replicas = uint32(*svccfg.Deploy.Replicas) - } - - reservations := getResourceReservations(svccfg.Deploy.Resources) - if reservations != nil { - var devices []*defangv1.Device - for _, d := range reservations.Devices { - devices = append(devices, &defangv1.Device{ - Capabilities: d.Capabilities, - Count: uint32(d.Count), - Driver: d.Driver, - }) - } - deploy.Resources = &defangv1.Resources{ - Reservations: &defangv1.Resource{ - Cpus: float32(reservations.NanoCPUs), - Memory: float32(reservations.MemoryBytes) / MiB, - Devices: devices, - }, - } - } - } - - // Upload the build context, if any; TODO: parallelize - var build *defangv1.Build - if svccfg.Build != nil { - // Pack the build context into a tarball and upload - url, err := getRemoteBuildContext(ctx, c, svccfg.Name, svccfg.Build, upload) - if err != nil { - return nil, err - } - - build = &defangv1.Build{ - Context: url, - Dockerfile: svccfg.Build.Dockerfile, - ShmSize: float32(svccfg.Build.ShmSize) / MiB, - Target: svccfg.Build.Target, - } - - if len(svccfg.Build.Args) > 0 { - build.Args = make(map[string]string, len(svccfg.Build.Args)) - for key, value := range svccfg.Build.Args { - if key == "" || value == nil { - term.Warnf("service %q: skipping unset build argument %q", svccfg.Name, key) - continue - } - - build.Args[key] = svcNameReplacer.ReplaceServiceNameWithDNS(svccfg.Name, key, *value, BuildArgs) - } - } - } - - // Extract environment variables - var envFromConfig []string - envs := make(map[string]string, len(svccfg.Environment)) - for key, value := range svccfg.Environment { - // A bug in Compose-go env file parsing can cause empty keys - if key == "" { - term.Warnf("service %q: skipping unset environment variable key", svccfg.Name) - continue - } - // keep track of what environment variables were declared but not set in the compose environment section - if value == nil { - envFromConfig = append(envFromConfig, key) - continue - } - - // Check if the environment variable is an existing config; if so, mark it as such - if _, ok := slices.BinarySearch(config.Names, key); ok { - if svcNameReplacer.HasServiceName(*value) { - term.Warnf("service %q: environment variable %q will use the 'defang config' value instead of adjusted service name", svccfg.Name, key) - } else { - term.Warnf("service %q: environment variable %q will use the 'defang config' value instead", svccfg.Name, key) - } - envFromConfig = append(envFromConfig, key) - continue - } - - envs[key] = svcNameReplacer.ReplaceServiceNameWithDNS(svccfg.Name, key, *value, EnvironmentVars) - } - - // Add unset environment variables as "secrets" - var configs []*defangv1.Secret - for _, name := range envFromConfig { - configs = append(configs, &defangv1.Secret{ - Source: name, - }) - } - // Extract secret references; secrets are supposed to be files, not env, but it's kept for backward compatibility - for i, secret := range svccfg.Secrets { - if i == 0 { // only warn once - term.Warnf("service %q: secrets will be exposed as environment variables, not files (use 'environment' instead)", svccfg.Name) - } - configs = append(configs, &defangv1.Secret{ - Source: secret.Source, - }) - } - - init := false - if svccfg.Init != nil { - init = *svccfg.Init - } - - var dnsRole string - if dnsRoleVal := svccfg.Extensions["x-defang-dns-role"]; dnsRoleVal != nil { - dnsRole = dnsRoleVal.(string) // already validated - } - - var staticFiles *defangv1.StaticFiles - if staticFilesVal := svccfg.Extensions["x-defang-static-files"]; staticFilesVal != nil { - if str, ok := staticFilesVal.(string); ok { - staticFiles = &defangv1.StaticFiles{Folder: str} - } else { - obj := staticFilesVal.(map[string]interface{}) // already validated - var redirects []string - if r, ok := obj["redirects"].([]interface{}); ok { - redirects = make([]string, len(r)) - for i, v := range r { - redirects[i] = v.(string) - } - } - staticFiles = &defangv1.StaticFiles{ - Folder: obj["folder"].(string), - Redirects: redirects, - } - } - } - - var redis *defangv1.Redis - if _, ok := svccfg.Extensions["x-defang-redis"]; ok { - if _, ok := c.(*client.PlaygroundClient); ok { - term.Warnf("service %q: Managed redis is not supported in the Playground; consider using BYOC (https://s.defang.io/byoc)", svccfg.Name) - } else { - redis = &defangv1.Redis{} - } - } - - var postgres *defangv1.Postgres - if _, ok := svccfg.Extensions["x-defang-postgres"]; ok { - if _, ok := c.(*client.PlaygroundClient); ok { - term.Warnf("service %q: managed postgres is not supported in the Playground; consider using BYOC (https://s.defang.io/byoc)", svccfg.Name) - } else { - postgres = &defangv1.Postgres{} - } - } - - if redis == nil && postgres == nil && isStatefulImage(svccfg.Image) { - term.Warnf("service %q: stateful service will lose data on restart; use a managed service instead", svccfg.Name) - } - - network := network(&svccfg) - ports := convertPorts(svccfg.Ports) - services = append(services, &defangv1.Service{ - Name: NormalizeServiceName(svccfg.Name), - Image: svccfg.Image, - Build: build, - Internal: network == defangv1.Network_PRIVATE, - Networks: network, - Init: init, - Ports: ports, - Healthcheck: healthcheck, - Deploy: deploy, - Environment: envs, - Secrets: configs, - Command: svccfg.Command, - Domainname: svccfg.DomainName, - Platform: convertPlatform(svccfg.Platform), - DnsRole: dnsRole, - StaticFiles: staticFiles, - Redis: redis, - Postgres: postgres, - }) - } - return services, nil -} - -func getResourceReservations(r compose.Resources) *compose.Resource { +func getResourceReservations(r composeTypes.Resources) *composeTypes.Resource { if r.Reservations == nil { // TODO: we might not want to default to all the limits, maybe only memory? return r.Limits @@ -235,89 +15,28 @@ func getResourceReservations(r compose.Resources) *compose.Resource { return r.Reservations } -func convertPlatform(platform string) defangv1.Platform { - switch strings.ToLower(platform) { - default: - term.Warnf("unsupported platform: %q; assuming linux", platform) - fallthrough - case "", "linux": - return defangv1.Platform_LINUX_ANY - case "linux/amd64", "linux/x86_64": // Docker accepts both - return defangv1.Platform_LINUX_AMD64 - case "linux/arm64", "linux/arm64/v8", "linux/arm64/v7", "linux/arm64/v6": - return defangv1.Platform_LINUX_ARM64 - } -} - -func network(svccfg *compose.ServiceConfig) defangv1.Network { - // HACK: Use magic network name "public" to determine if the service is public - if _, ok := svccfg.Networks["public"]; ok { - return defangv1.Network_PUBLIC - } - // TODO: support external services (w/o LB), - return defangv1.Network_PRIVATE -} - -func convertPort(port compose.ServicePortConfig) *defangv1.Port { - pbPort := &defangv1.Port{ - // Mode string `yaml:",omitempty" json:"mode,omitempty"` - // HostIP string `mapstructure:"host_ip" yaml:"host_ip,omitempty" json:"host_ip,omitempty"` - // Published string `yaml:",omitempty" json:"published,omitempty"` - // Protocol string `yaml:",omitempty" json:"protocol,omitempty"` - Target: port.Target, - } - - // TODO: Use AppProtocol as hint for application protocol - // https://github.com/compose-spec/compose-spec/blob/main/05-services.md#long-syntax-3 - switch port.Protocol { - case "": - pbPort.Protocol = defangv1.Protocol_ANY // defaults to HTTP in CD - case "tcp": - pbPort.Protocol = defangv1.Protocol_TCP - case "udp": - pbPort.Protocol = defangv1.Protocol_UDP - case "http": // TODO: not per spec - pbPort.Protocol = defangv1.Protocol_HTTP - case "http2": // TODO: not per spec - pbPort.Protocol = defangv1.Protocol_HTTP2 - case "grpc": // TODO: not per spec - pbPort.Protocol = defangv1.Protocol_GRPC - default: - panic(fmt.Sprintf("port 'protocol' should have been validated to be one of [tcp udp http http2 grpc] but got: %v", port.Protocol)) - } - +func fixupPort(port *composeTypes.ServicePortConfig) { switch port.Mode { case "": - // TODO: This never happens now as compose-go set default to "ingress" term.Warnf("port %d: no 'mode' was specified; defaulting to 'ingress' (add 'mode: ingress' to silence)", port.Target) fallthrough case "ingress": - // This code is unnecessarily complex because compose-go silently converts short port: syntax to ingress+tcp + // This code is unnecessarily complex because compose-go silently converts short `ports:` syntax to ingress+tcp if port.Protocol != "udp" { if port.Published != "" { term.Debugf("port %d: ignoring 'published: %s' in 'ingress' mode", port.Target, port.Published) } - pbPort.Mode = defangv1.Mode_INGRESS - if pbPort.Protocol == defangv1.Protocol_TCP { - pbPort.Protocol = defangv1.Protocol_HTTP + if (port.Protocol == "tcp" || port.Protocol == "udp") && port.AppProtocol != "http" { + // term.Warnf("TCP ingress is not supported; assuming HTTP (remove 'protocol' to silence)") + port.AppProtocol = "http" } break } term.Warnf("port %d: UDP ports default to 'host' mode (add 'mode: host' to silence)", port.Target) - fallthrough + port.Mode = "host" case "host": - pbPort.Mode = defangv1.Mode_HOST + // no-op default: - panic(fmt.Sprintf("port mode should have been validated to be one of [host ingress] but got: %v", port.Mode)) - } - return pbPort -} - -func convertPorts(ports []compose.ServicePortConfig) []*defangv1.Port { - var pbports []*defangv1.Port - for _, port := range ports { - pbPort := convertPort(port) - pbports = append(pbports, pbPort) + panic(fmt.Sprintf("port %d: 'mode' should have been validated to be one of [host ingress] but got: %v", port.Target, port.Mode)) } - return pbports } diff --git a/src/pkg/cli/compose/convert_test.go b/src/pkg/cli/compose/convert_test.go index ab8ad6dae..009fa7511 100644 --- a/src/pkg/cli/compose/convert_test.go +++ b/src/pkg/cli/compose/convert_test.go @@ -1,15 +1,14 @@ package compose import ( - "context" - "encoding/json" - "slices" + "fmt" "strings" "testing" - "github.com/DefangLabs/defang/src/pkg/cli/client" + "github.com/DefangLabs/defang/src/pkg/term" defangv1 "github.com/DefangLabs/defang/src/protos/io/defang/v1" "github.com/compose-spec/compose-go/v2/types" + composeTypes "github.com/compose-spec/compose-go/v2/types" ) func TestConvertPort(t *testing.T) { @@ -139,6 +138,7 @@ func TestConvertPort(t *testing.T) { if tt.wantErr != "" { t.Errorf("convertPort() expected error: %v", tt.wantErr) } + fixupPort(&tt.input) got := convertPort(tt.input) if got.String() != tt.expected.String() { t.Errorf("convertPort() got %v, want %v", got, tt.expected.String()) @@ -147,29 +147,67 @@ func TestConvertPort(t *testing.T) { } } -func TestConvert(t *testing.T) { - testRunCompose(t, func(t *testing.T, path string) { - loader := NewLoaderWithPath(path) - proj, err := loader.LoadProject(context.Background()) - if err != nil { - t.Fatal(err) - } - services, err := ConvertServices(context.Background(), client.MockClient{}, proj.Services, UploadModeIgnore) - if err != nil { - t.Fatal(err) - } +// TODO: remove this (and change the test cases to avoid using the protobuf) +func convertPort(port composeTypes.ServicePortConfig) *defangv1.Port { + pbPort := &defangv1.Port{ + // Mode string `yaml:",omitempty" json:"mode,omitempty"` + // HostIP string `mapstructure:"host_ip" yaml:"host_ip,omitempty" json:"host_ip,omitempty"` + // Published string `yaml:",omitempty" json:"published,omitempty"` + // Protocol string `yaml:",omitempty" json:"protocol,omitempty"` + Target: port.Target, + } - // The order of the services is not guaranteed, so we sort the services before comparing - slices.SortFunc(services, func(i, j *defangv1.Service) int { return strings.Compare(i.Name, j.Name) }) + // TODO: Use AppProtocol as hint for application protocol + // https://github.com/compose-spec/compose-spec/blob/main/05-services.md#long-syntax-3 + switch port.Protocol { + case "": + pbPort.Protocol = defangv1.Protocol_ANY // defaults to HTTP in CD + case "tcp": + pbPort.Protocol = defangv1.Protocol_TCP + case "udp": + pbPort.Protocol = defangv1.Protocol_UDP + case "http": // TODO: not per spec; should use AppProtocol + pbPort.Protocol = defangv1.Protocol_HTTP + case "http2": // TODO: not per spec; should use AppProtocol + pbPort.Protocol = defangv1.Protocol_HTTP2 + case "grpc": // TODO: not per spec; should use AppProtocol + pbPort.Protocol = defangv1.Protocol_GRPC + default: + panic(fmt.Sprintf("port 'protocol' should have been validated to be one of [tcp udp http http2 grpc] but got: %q", port.Protocol)) + } - // Convert the protobuf services to pretty JSON for comparison (YAML would include all the zero values) - actual, err := json.MarshalIndent(services, "", " ") - if err != nil { - t.Fatal(err) - } + switch port.AppProtocol { + case "http": + pbPort.Protocol = defangv1.Protocol_HTTP + case "http2": + pbPort.Protocol = defangv1.Protocol_HTTP2 + case "grpc": + pbPort.Protocol = defangv1.Protocol_GRPC + } - if err := compare(actual, path+".convert"); err != nil { - t.Error(err) + switch port.Mode { + case "": + // TODO: This never happens now as compose-go set default to "ingress" + term.Warnf("No port 'mode' was specified; defaulting to 'ingress' (add 'mode: ingress' to silence)") + fallthrough + case "ingress": + // This code is unnecessarily complex because compose-go silently converts short port: syntax to ingress+tcp + if port.Protocol != "udp" { + if port.Published != "" { + term.Warnf("Published ports are ignored in ingress mode") + } + pbPort.Mode = defangv1.Mode_INGRESS + if pbPort.Protocol == defangv1.Protocol_TCP { + pbPort.Protocol = defangv1.Protocol_HTTP + } + break } - }) + term.Warnf("UDP ports default to 'host' mode (add 'mode: host' to silence)") + fallthrough + case "host": + pbPort.Mode = defangv1.Mode_HOST + default: + panic(fmt.Sprintf("port mode should have been validated to be one of [host ingress] but got: %q", port.Mode)) + } + return pbPort } diff --git a/src/pkg/cli/compose/fixup.go b/src/pkg/cli/compose/fixup.go new file mode 100644 index 000000000..00e354c19 --- /dev/null +++ b/src/pkg/cli/compose/fixup.go @@ -0,0 +1,110 @@ +package compose + +import ( + "context" + "slices" + + "github.com/DefangLabs/defang/src/pkg/cli/client" + "github.com/DefangLabs/defang/src/pkg/term" + defangv1 "github.com/DefangLabs/defang/src/protos/io/defang/v1" + composeTypes "github.com/compose-spec/compose-go/v2/types" +) + +// HACK: Use magic network name "public" to determine if the service is public +const NetworkPublic = "public" + +func FixupServices(ctx context.Context, c client.Client, serviceConfigs composeTypes.Services, upload UploadMode) error { + + svcNameReplacer := NewServiceNameReplacer(c, serviceConfigs) + + // Preload the current config so we can detect which environment variables should be passed as "secrets" + config, err := c.ListConfig(ctx) + if err != nil { + term.Debugf("failed to load config: %v", err) + config = &defangv1.Secrets{} + } + slices.Sort(config.Names) // sort for binary search + + for _, svccfg := range serviceConfigs { + if svccfg.Deploy != nil { + if svccfg.Deploy.Replicas == nil { + one := 1 // default to one replica per service; allow the user to override this to 0 + svccfg.Deploy.Replicas = &one + } + } + + // Upload the build context, if any; TODO: parallelize + if svccfg.Build != nil { + // Pack the build context into a tarball and upload + url, err := getRemoteBuildContext(ctx, c, svccfg.Name, svccfg.Build, upload) + if err != nil { + return err + } + svccfg.Build.Context = url + + for key, value := range svccfg.Build.Args { + if key == "" || value == nil { + term.Warnf("service %q: skipping unset build argument %q", svccfg.Name, key) + delete(svccfg.Build.Args, key) // remove the empty key; this is safe + continue + } + + val := svcNameReplacer.ReplaceServiceNameWithDNS(svccfg.Name, key, *value, BuildArgs) + svccfg.Build.Args[key] = &val + } + } + + // Fixup secret references; secrets are supposed to be files, not env, but it's kept for backward compatibility + for i, secret := range svccfg.Secrets { + if i == 0 { // only warn once + term.Warnf("service %q: secrets will be exposed as environment variables, not files (use 'environment' instead)", svccfg.Name) + } + svccfg.Environment[secret.Source] = nil + } + svccfg.Secrets = nil + + // Fixup environment variables + for key, value := range svccfg.Environment { + // A bug in Compose-go env file parsing can cause empty keys + if key == "" { + term.Warnf("service %q: skipping unset environment variable key", svccfg.Name) + delete(svccfg.Environment, key) // remove the empty key; this is safe + continue + } + if value == nil { + continue // will be from config + } + + // Check if the environment variable is an existing config; if so, mark it as such + if _, ok := slices.BinarySearch(config.Names, key); ok { + if svcNameReplacer.HasServiceName(*value) { + term.Warnf("service %q: environment variable %q will use the `defang config` value instead of adjusted service name", svccfg.Name, key) + } else { + term.Warnf("service %q: environment variable %q overridden by config", svccfg.Name, key) + } + svccfg.Environment[key] = nil + continue + } + + val := svcNameReplacer.ReplaceServiceNameWithDNS(svccfg.Name, key, *value, EnvironmentVars) + svccfg.Environment[key] = &val + } + + _, redis := svccfg.Extensions["x-defang-redis"] + _, postgres := svccfg.Extensions["x-defang-postgres"] + if !redis && !postgres && isStatefulImage(svccfg.Image) { + term.Warnf("service %q: stateful service will lose data on restart; use a managed service instead", svccfg.Name) + } + + // Fixup ports + for i, port := range svccfg.Ports { + fixupPort(&port) + svccfg.Ports[i] = port + } + + oldName := svccfg.Name + svccfg.Name = NormalizeServiceName(svccfg.Name) + serviceConfigs[oldName] = svccfg + } + return nil +} diff --git a/src/pkg/cli/compose/fixup_test.go b/src/pkg/cli/compose/fixup_test.go new file mode 100644 index 000000000..bdb5b126e --- /dev/null +++ b/src/pkg/cli/compose/fixup_test.go @@ -0,0 +1,44 @@ +package compose + +import ( + "context" + "encoding/json" + "slices" + "strings" + "testing" + + "github.com/DefangLabs/defang/src/pkg/cli/client" + composeTypes "github.com/compose-spec/compose-go/v2/types" +) + +func TestFixup(t *testing.T) { + testRunCompose(t, func(t *testing.T, path string) { + loader := NewLoaderWithPath(path) + proj, err := loader.LoadProject(context.Background()) + if err != nil { + t.Fatal(err) + } + err = FixupServices(context.Background(), client.MockClient{}, proj.Services, UploadModeIgnore) + if err != nil { + t.Fatal(err) + } + + var services []composeTypes.ServiceConfig + for _, svc := range proj.Services { + services = append(services, svc) + } + + // The order of the services is not guaranteed, so we sort the services before comparing + slices.SortFunc(services, func(i, j composeTypes.ServiceConfig) int { return strings.Compare(i.Name, j.Name) }) + + // Convert the protobuf services to pretty JSON for comparison (YAML would include all the zero values) + actual, err := json.MarshalIndent(services, "", " ") + if err != nil { + t.Fatal(err) + } + + if err := compare(actual, path+".fixup"); err != nil { + t.Error(err) + } + }) +} diff --git a/src/pkg/cli/compose/loader.go b/src/pkg/cli/compose/loader.go index 6a8b0421d..271613741 100644 --- a/src/pkg/cli/compose/loader.go +++ b/src/pkg/cli/compose/loader.go @@ -14,11 +14,17 @@ import ( "github.com/compose-spec/compose-go/v2/errdefs" "github.com/compose-spec/compose-go/v2/loader" "github.com/compose-spec/compose-go/v2/template" - compose "github.com/compose-spec/compose-go/v2/types" + composeTypes "github.com/compose-spec/compose-go/v2/types" "github.com/sirupsen/logrus" "gopkg.in/yaml.v3" ) +type Project = composeTypes.Project + +type ServiceConfig = composeTypes.ServiceConfig + +type Services = composeTypes.Services + type LoaderOptions struct { ConfigPaths []string ProjectName string @@ -61,7 +67,7 @@ func (c Loader) LoadProjectName(ctx context.Context) (string, error) { return project.Name, nil } -func (c Loader) LoadProject(ctx context.Context) (*compose.Project, error) { +func (c Loader) LoadProject(ctx context.Context) (*Project, error) { // Set logrus send logs via the term package termLogger := logs.TermLogFormatter{Term: term.DefaultTerm} logrus.SetFormatter(termLogger) diff --git a/src/pkg/cli/compose/loader_test.go b/src/pkg/cli/compose/loader_test.go index 2d6a66aec..676aa9076 100644 --- a/src/pkg/cli/compose/loader_test.go +++ b/src/pkg/cli/compose/loader_test.go @@ -66,13 +66,15 @@ func diff(actualRaw, goldenRaw string) error { func testRunCompose(t *testing.T, f func(t *testing.T, path string)) { t.Helper() - composeRegex := regexp.MustCompile(`^(docker-)?compose.ya?ml$`) + composeRegex := regexp.MustCompile(`^(?i)(docker-)?compose.ya?ml$`) err := filepath.WalkDir("../../../tests", func(path string, d os.DirEntry, err error) error { if err != nil || d.IsDir() || !composeRegex.MatchString(d.Name()) { return err } t.Run(path, func(t *testing.T) { + t.Helper() + t.Log(path) f(t, path) }) return nil diff --git a/src/pkg/cli/compose/serviceNameReplacer.go b/src/pkg/cli/compose/serviceNameReplacer.go index 638a46774..e39e7103d 100644 --- a/src/pkg/cli/compose/serviceNameReplacer.go +++ b/src/pkg/cli/compose/serviceNameReplacer.go @@ -7,8 +7,7 @@ import ( "github.com/DefangLabs/defang/src/pkg/cli/client" "github.com/DefangLabs/defang/src/pkg/term" - defangv1 "github.com/DefangLabs/defang/src/protos/io/defang/v1" - compose "github.com/compose-spec/compose-go/v2/types" + composeTypes "github.com/compose-spec/compose-go/v2/types" ) type FixupTarget string @@ -24,12 +23,12 @@ type ServiceNameReplacer struct { serviceNameRegex *regexp.Regexp } -func NewServiceNameReplacer(client client.Client, services compose.Services) ServiceNameReplacer { +func NewServiceNameReplacer(client client.Client, services composeTypes.Services) ServiceNameReplacer { // Create a regexp to detect private service names in environment variable and build arg values var serviceNames []string var nonReplaceServiceNames []string for _, svccfg := range services { - if network(&svccfg) == defangv1.Network_PRIVATE && slices.ContainsFunc(svccfg.Ports, func(p compose.ServicePortConfig) bool { + if _, public := svccfg.Networks["public"]; !public && slices.ContainsFunc(svccfg.Ports, func(p composeTypes.ServicePortConfig) bool { return p.Mode == "host" // only private services with host ports get DNS names }) { serviceNames = append(serviceNames, regexp.QuoteMeta(svccfg.Name)) diff --git a/src/pkg/cli/compose/serviceNameReplacer_test.go b/src/pkg/cli/compose/serviceNameReplacer_test.go index 4f09c2091..0c3d4285d 100644 --- a/src/pkg/cli/compose/serviceNameReplacer_test.go +++ b/src/pkg/cli/compose/serviceNameReplacer_test.go @@ -4,7 +4,7 @@ import ( "testing" "github.com/DefangLabs/defang/src/pkg/cli/client" - compose "github.com/compose-spec/compose-go/v2/types" + composeTypes "github.com/compose-spec/compose-go/v2/types" ) type serviceNameReplacerMockClient struct { @@ -16,31 +16,31 @@ func (m serviceNameReplacerMockClient) ServiceDNS(name string) string { } func setup() ServiceNameReplacer { - services := compose.Services{} - services["host-serviceA"] = compose.ServiceConfig{ + services := composeTypes.Services{} + services["host-serviceA"] = composeTypes.ServiceConfig{ Name: "host-serviceA", - Ports: []compose.ServicePortConfig{ + Ports: []composeTypes.ServicePortConfig{ {Mode: "host"}, }, } - services["host-serviceB"] = compose.ServiceConfig{ + services["host-serviceB"] = composeTypes.ServiceConfig{ Name: "host-serviceB", - Ports: []compose.ServicePortConfig{ + Ports: []composeTypes.ServicePortConfig{ {Mode: "host"}, }, } - services["ingress-serviceC"] = compose.ServiceConfig{ + services["ingress-serviceC"] = composeTypes.ServiceConfig{ Name: "ingress-serviceC", - Ports: []compose.ServicePortConfig{ + Ports: []composeTypes.ServicePortConfig{ {Mode: "ingress"}, }, } - services["ingress-serviceD"] = compose.ServiceConfig{ + services["ingress-serviceD"] = composeTypes.ServiceConfig{ Name: "ingress-serviceD", - Ports: []compose.ServicePortConfig{ + Ports: []composeTypes.ServicePortConfig{ {Mode: "ingress"}, }, } diff --git a/src/pkg/cli/compose/validation.go b/src/pkg/cli/compose/validation.go index 6911f7c88..d571d1508 100644 --- a/src/pkg/cli/compose/validation.go +++ b/src/pkg/cli/compose/validation.go @@ -12,12 +12,12 @@ import ( "github.com/DefangLabs/defang/src/pkg" "github.com/DefangLabs/defang/src/pkg/term" "github.com/compose-spec/compose-go/v2/types" - compose "github.com/compose-spec/compose-go/v2/types" + composeTypes "github.com/compose-spec/compose-go/v2/types" ) var ErrDockerfileNotFound = errors.New("dockerfile not found") -func ValidateProject(project *compose.Project) error { +func ValidateProject(project *composeTypes.Project) error { if project == nil { return errors.New("no project found") } @@ -220,7 +220,7 @@ func ValidateProject(project *compose.Project) error { term.Debugf("service %q: unsupported compose directive: healthcheck start_interval", svccfg.Name) } } - var reservations *compose.Resource + var reservations *composeTypes.Resource if svccfg.Deploy != nil { if svccfg.Deploy.Mode != "" && svccfg.Deploy.Mode != "replicated" { return fmt.Errorf("service %q: unsupported compose directive: deploy mode: %q", svccfg.Name, svccfg.Deploy.Mode) @@ -301,7 +301,7 @@ func ValidateProject(project *compose.Project) error { return nil } -func validatePorts(ports []compose.ServicePortConfig) error { +func validatePorts(ports []composeTypes.ServicePortConfig) error { for _, port := range ports { err := validatePort(port) if err != nil { @@ -315,7 +315,7 @@ func validatePorts(ports []compose.ServicePortConfig) error { var validProtocols = map[string]bool{"": true, "tcp": true, "udp": true, "http": true, "http2": true, "grpc": true} var validModes = map[string]bool{"": true, "host": true, "ingress": true} -func validatePort(port compose.ServicePortConfig) error { +func validatePort(port composeTypes.ServicePortConfig) error { if port.Target < 1 || port.Target > 32767 { return fmt.Errorf("port %d: 'target' must be an integer between 1 and 32767", port.Target) } diff --git a/src/pkg/cli/compose/validation_test.go b/src/pkg/cli/compose/validation_test.go index 7c9be0e07..ce1e95459 100644 --- a/src/pkg/cli/compose/validation_test.go +++ b/src/pkg/cli/compose/validation_test.go @@ -38,7 +38,7 @@ func TestValidationAndConvert(t *testing.T) { mockClient := MockClient{ configs: []string{"CONFIG1", "CONFIG2"}, } - if _, err = ConvertServices(context.Background(), mockClient, proj.Services, UploadModeIgnore); err != nil { + if err := FixupServices(context.Background(), mockClient, proj.Services, UploadModeIgnore); err != nil { t.Logf("Service conversion failed: %v", err) logs.WriteString(err.Error() + "\n") } diff --git a/src/pkg/cli/composeUp.go b/src/pkg/cli/composeUp.go index 951dac6e2..60d173519 100644 --- a/src/pkg/cli/composeUp.go +++ b/src/pkg/cli/composeUp.go @@ -30,32 +30,26 @@ func ComposeUp(ctx context.Context, c client.Client, upload compose.UploadMode, return nil, project, &ComposeError{err} } - services, err := compose.ConvertServices(ctx, c, project.Services, upload) - if err != nil { + if err := compose.FixupServices(ctx, c, project.Services, upload); err != nil { return nil, project, err } - if len(services) == 0 { - return nil, project, &ComposeError{fmt.Errorf("no services found")} + bytes, err := project.MarshalYAML() + if err != nil { + return nil, project, err } if upload == compose.UploadModeIgnore { - fmt.Println("Project:", project.Name) - for _, service := range services { - PrintObject(service.Name, service) - } + fmt.Println(string(bytes)) return nil, project, ErrDryRun } - for _, service := range services { - term.Info("Deploying service", service.Name) - } - + deployRequest := &defangv1.DeployRequest{Mode: mode, Project: project.Name, Compose: bytes} var resp *defangv1.DeployResponse if upload == compose.UploadModePreview { - resp, err = c.Preview(ctx, &defangv1.DeployRequest{Mode: mode, Project: project.Name, Services: services}) + resp, err = c.Preview(ctx, deployRequest) } else { - resp, err = c.Deploy(ctx, &defangv1.DeployRequest{Mode: mode, Project: project.Name, Services: services}) + resp, err = c.Deploy(ctx, deployRequest) } if err != nil { return nil, project, err diff --git a/src/pkg/cli/composeUp_test.go b/src/pkg/cli/composeUp_test.go index 487b87c4d..c58b86b67 100644 --- a/src/pkg/cli/composeUp_test.go +++ b/src/pkg/cli/composeUp_test.go @@ -5,13 +5,40 @@ import ( "errors" "net/http" "net/http/httptest" + "sync/atomic" "testing" "github.com/DefangLabs/defang/src/pkg/cli/client" "github.com/DefangLabs/defang/src/pkg/cli/compose" defangv1 "github.com/DefangLabs/defang/src/protos/io/defang/v1" + "github.com/compose-spec/compose-go/v2/loader" + "github.com/compose-spec/compose-go/v2/types" ) +type deployMock struct { + client.MockClient +} + +func (d deployMock) Deploy(ctx context.Context, req *defangv1.DeployRequest) (*defangv1.DeployResponse, error) { + if req.Compose == nil && req.Services == nil { + return nil, errors.New("DeployRequest needs Compose or Services") + } + + project, err := loader.LoadWithContext(ctx, types.ConfigDetails{ConfigFiles: []types.ConfigFile{{Content: req.Compose}}}) + if err != nil { + return nil, err + } + + var services []*defangv1.ServiceInfo + for _, service := range project.Services { + services = append(services, &defangv1.ServiceInfo{ + Service: &defangv1.Service{Name: service.Name}, + }) + } + + return &defangv1.DeployResponse{Services: services}, nil +} + func TestComposeUp(t *testing.T) { loader := compose.NewLoaderWithPath("../../tests/testproj/compose.yaml") proj, err := loader.LoadProject(context.Background()) @@ -19,16 +46,32 @@ func TestComposeUp(t *testing.T) { t.Fatalf("LoadProject() failed: %v", err) } + gotContext := atomic.Bool{} server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) // same as S3 + if r.Method != http.MethodPut { + t.Errorf("ComposeStart() failed: expected PUT request, got %s", r.Method) + } + gotContext.Store(true) + w.WriteHeader(http.StatusOK) // return 200 OK same as S3 })) - defer server.Close() + t.Cleanup(server.Close) - _, project, err := ComposeUp(context.Background(), client.MockClient{UploadUrl: server.URL + "/", Project: proj}, compose.UploadModeIgnore, defangv1.DeploymentMode_DEVELOPMENT) - if !errors.Is(err, ErrDryRun) { + d, project, err := ComposeUp(context.Background(), deployMock{MockClient: client.MockClient{UploadUrl: server.URL + "/", Project: proj}}, compose.UploadModeDigest, defangv1.DeploymentMode_DEVELOPMENT) + if err != nil { t.Fatalf("ComposeUp() failed: %v", err) } if project == nil { t.Fatalf("ComposeUp() failed: project is nil") } + if !gotContext.Load() { + t.Errorf("ComposeStart() failed: did not get context") + } + if len(d.Services) != len(proj.Services) { + t.Errorf("ComposeUp() failed: expected %d services, got %d", len(proj.Services), len(d.Services)) + } + for _, service := range d.Services { + if _, ok := proj.Services[service.Service.Name]; !ok { + t.Errorf("ComposeUp() failed: service %s not found", service.Service.Name) + } + } } diff --git a/src/pkg/cli/destroy_test.go b/src/pkg/cli/destroy_test.go index 5eebe0d74..fe0c87930 100644 --- a/src/pkg/cli/destroy_test.go +++ b/src/pkg/cli/destroy_test.go @@ -10,7 +10,7 @@ import ( defangv1 "github.com/DefangLabs/defang/src/protos/io/defang/v1" "github.com/DefangLabs/defang/src/protos/io/defang/v1/defangv1connect" "github.com/bufbuild/connect-go" - compose "github.com/compose-spec/compose-go/v2/types" + composeTypes "github.com/compose-spec/compose-go/v2/types" "google.golang.org/protobuf/types/known/emptypb" ) @@ -29,9 +29,7 @@ func (g *grpcDestroyMockHandler) GetServices(context.Context, *connect.Request[e Project: "tenantx", Services: []*defangv1.ServiceInfo{ { - Service: &defangv1.Service{ - Name: "test-service", - }, + Service: &defangv1.Service{Name: "test-service"}, }, }, }), nil @@ -64,8 +62,8 @@ type FakeLoader struct { ProjectName string } -func (f FakeLoader) LoadProject(ctx context.Context) (*compose.Project, error) { - return &compose.Project{Name: f.ProjectName}, nil +func (f FakeLoader) LoadProject(ctx context.Context) (*composeTypes.Project, error) { + return &composeTypes.Project{Name: f.ProjectName}, nil } func (f FakeLoader) LoadProjectName(ctx context.Context) (string, error) { diff --git a/src/pkg/quota/quota.go b/src/pkg/quota/quota.go index 92aa229eb..8dae14cd9 100644 --- a/src/pkg/quota/quota.go +++ b/src/pkg/quota/quota.go @@ -7,3 +7,11 @@ type Quotas struct { Ingress int Services int } + +func getOrZero[T any](v *T) T { + if v == nil { + var zero T + return zero + } + return *v +} diff --git a/src/pkg/quota/service.go b/src/pkg/quota/service.go index 1a8e1fb46..71d1b874c 100644 --- a/src/pkg/quota/service.go +++ b/src/pkg/quota/service.go @@ -5,18 +5,19 @@ import ( "fmt" "strings" - defangv1 "github.com/DefangLabs/defang/src/protos/io/defang/v1" + "github.com/DefangLabs/defang/src/pkg/cli/compose" + "github.com/compose-spec/compose-go/v2/types" ) type ServiceQuotas struct { Cpus float32 Gpus uint32 MemoryMiB float32 - Replicas uint32 + Replicas int ShmSizeMiB float32 } -func (q ServiceQuotas) Validate(service *defangv1.Service) error { +func (q ServiceQuotas) Validate(service *types.ServiceConfig) error { if service.Name == "" { return errors.New("service name is required") // CodeInvalidArgument } @@ -25,8 +26,8 @@ func (q ServiceQuotas) Validate(service *defangv1.Service) error { if service.Build.Context == "" { return errors.New("build.context is required") // CodeInvalidArgument } - if service.Build.ShmSize > q.ShmSizeMiB || service.Build.ShmSize < 0 { - return fmt.Errorf("build.shm_size exceeds quota (max %v MiB)", q.ShmSizeMiB) // CodeInvalidArgument + if shmSizeMiB := float32(service.Build.ShmSize) / compose.MiB; shmSizeMiB > q.ShmSizeMiB || service.Build.ShmSize < 0 { + return fmt.Errorf("build.shm_size %v MiB exceeds quota %v MiB", shmSizeMiB, q.ShmSizeMiB) // CodeInvalidArgument } } else { if service.Image == "" { @@ -41,9 +42,9 @@ func (q ServiceQuotas) Validate(service *defangv1.Service) error { if port.Target < 1 || port.Target > 32767 { return fmt.Errorf("port %d is out of range", port.Target) // CodeInvalidArgument } - if port.Mode == defangv1.Mode_INGRESS { + if port.Mode == compose.Mode_INGRESS { hasIngress = true - if port.Protocol == defangv1.Protocol_TCP || port.Protocol == defangv1.Protocol_UDP { + if port.Protocol == compose.Protocol_TCP || port.Protocol == compose.Protocol_UDP { return fmt.Errorf("mode:INGRESS is not supported by protocol:%s", port.Protocol) // CodeInvalidArgument } } @@ -53,17 +54,19 @@ func (q ServiceQuotas) Validate(service *defangv1.Service) error { // hasHost = hasHost || port.Mode == v1.Mode_HOST uniquePorts[port.Target] = true } - if service.Healthcheck != nil && len(service.Healthcheck.Test) > 0 { + if service.HealthCheck != nil && len(service.HealthCheck.Test) > 0 { // Technically this should test for <= but both interval and timeout have 30s as the default value in compose spec - if service.Healthcheck.Interval > 0 && service.Healthcheck.Interval < service.Healthcheck.Timeout { + interval := getOrZero(service.HealthCheck.Interval) + timeout := getOrZero(service.HealthCheck.Timeout) + if interval > 0 && interval < timeout { return errors.New("invalid healthcheck: timeout must be less than the interval") } - switch service.Healthcheck.Test[0] { + switch service.HealthCheck.Test[0] { case "CMD", "CMD-SHELL": if hasIngress { // For ingress ports, we derive the target group healthcheck path/port from the service healthcheck hasLocalhostUrl := false - for _, arg := range service.Healthcheck.Test[1:] { + for _, arg := range service.HealthCheck.Test[1:] { // Leave the actual parsing to the CD code; here we just check for localhost if strings.Contains(arg, "localhost") || strings.Contains(arg, "127.0.0.1") { hasLocalhostUrl = true @@ -79,20 +82,20 @@ func (q ServiceQuotas) Validate(service *defangv1.Service) error { return fmt.Errorf("invalid healthcheck: ingress ports require a CMD or CMD-SHELL healthcheck, see https://s.defang.io/healthchecks") } default: - return fmt.Errorf("unsupported healthcheck: %v", service.Healthcheck.Test) // this will have been caught by compose-go + return fmt.Errorf("unsupported healthcheck: %v", service.HealthCheck.Test) // this will have been caught by compose-go } } if service.Deploy != nil { - if service.Deploy.Replicas > q.Replicas { + if service.Deploy.Replicas != nil && *service.Deploy.Replicas > q.Replicas { return fmt.Errorf("replicas exceeds quota (max %d)", q.Replicas) // CodeInvalidArgument } - if service.Deploy.Resources != nil && service.Deploy.Resources.Reservations != nil { - if service.Deploy.Resources.Reservations.Cpus > q.Cpus || service.Deploy.Resources.Reservations.Cpus < 0 { + if service.Deploy.Resources.Reservations != nil { + if float32(service.Deploy.Resources.Reservations.NanoCPUs) > q.Cpus || service.Deploy.Resources.Reservations.NanoCPUs < 0 { return fmt.Errorf("cpus exceeds quota (max %v vCPU)", q.Cpus) // CodeInvalidArgument } - if service.Deploy.Resources.Reservations.Memory > q.MemoryMiB || service.Deploy.Resources.Reservations.Memory < 0 { - return fmt.Errorf("memory exceeds quota (max %v MiB)", q.MemoryMiB) // CodeInvalidArgument + if memoryMiB := float32(service.Deploy.Resources.Reservations.MemoryBytes) / compose.MiB; memoryMiB > q.MemoryMiB || service.Deploy.Resources.Reservations.MemoryBytes < 0 { + return fmt.Errorf("memory %v MiB exceeds quota %v MiB", memoryMiB, q.MemoryMiB) // CodeInvalidArgument } for _, device := range service.Deploy.Resources.Reservations.Devices { if len(device.Capabilities) != 1 || device.Capabilities[0] != "gpu" { @@ -101,8 +104,8 @@ func (q ServiceQuotas) Validate(service *defangv1.Service) error { if device.Driver != "" && device.Driver != "nvidia" { return errors.New("only nvidia GPU devices are supported") // CodeInvalidArgument } - if q.Gpus == 0 || device.Count > q.Gpus { - return fmt.Errorf("gpu count exceeds quota (max %d)", q.Gpus) // CodeInvalidArgument + if q.Gpus == 0 || uint32(device.Count) > q.Gpus { + return fmt.Errorf("gpu count %v exceeds quota %d", device.Count, q.Gpus) // CodeInvalidArgument } } } diff --git a/src/pkg/quota/service_test.go b/src/pkg/quota/service_test.go index c338dc328..f919038c1 100644 --- a/src/pkg/quota/service_test.go +++ b/src/pkg/quota/service_test.go @@ -3,75 +3,77 @@ package quota import ( "testing" - defangv1 "github.com/DefangLabs/defang/src/protos/io/defang/v1" + "github.com/DefangLabs/defang/src/pkg/cli/compose" + "github.com/aws/smithy-go/ptr" + "github.com/compose-spec/compose-go/v2/types" ) func TestValidate(t *testing.T) { tests := []struct { name string - service *defangv1.Service + service *types.ServiceConfig wantErr string }{ { name: "empty service", - service: &defangv1.Service{}, + service: &types.ServiceConfig{}, wantErr: "service name is required", }, { name: "no image, no build", - service: &defangv1.Service{Name: "test"}, + service: &types.ServiceConfig{Name: "test"}, wantErr: "missing image or build", }, { name: "empty build", - service: &defangv1.Service{Name: "test", Build: &defangv1.Build{}}, + service: &types.ServiceConfig{Name: "test", Build: &types.BuildConfig{}}, wantErr: "build.context is required", }, { name: "shm size exceeds quota", - service: &defangv1.Service{Name: "test", Build: &defangv1.Build{Context: ".", ShmSize: 30721}}, - wantErr: "build.shm_size exceeds quota (max 30720 MiB)", + service: &types.ServiceConfig{Name: "test", Build: &types.BuildConfig{Context: ".", ShmSize: 30721 * compose.MiB}}, + wantErr: "build.shm_size 30721 MiB exceeds quota 30720 MiB", }, { name: "port 0 out of range", - service: &defangv1.Service{Name: "test", Image: "asdf", Ports: []*defangv1.Port{{Target: 0}}}, + service: &types.ServiceConfig{Name: "test", Image: "asdf", Ports: []types.ServicePortConfig{{Target: 0}}}, wantErr: "port 0 is out of range", }, { name: "port out of range", - service: &defangv1.Service{Name: "test", Image: "asdf", Ports: []*defangv1.Port{{Target: 33333}}}, + service: &types.ServiceConfig{Name: "test", Image: "asdf", Ports: []types.ServicePortConfig{{Target: 33333}}}, wantErr: "port 33333 is out of range", }, { name: "ingress with UDP", - service: &defangv1.Service{Name: "test", Image: "asdf", Ports: []*defangv1.Port{{Target: 53, Mode: defangv1.Mode_INGRESS, Protocol: defangv1.Protocol_UDP}}}, - wantErr: "mode:INGRESS is not supported by protocol:UDP", + service: &types.ServiceConfig{Name: "test", Image: "asdf", Ports: []types.ServicePortConfig{{Target: 53, Mode: compose.Mode_INGRESS, Protocol: compose.Protocol_UDP}}}, + wantErr: "mode:INGRESS is not supported by protocol:udp", }, { name: "ingress with UDP", - service: &defangv1.Service{Name: "test", Image: "asdf", Ports: []*defangv1.Port{{Target: 80, Mode: defangv1.Mode_INGRESS, Protocol: defangv1.Protocol_TCP}}}, - wantErr: "mode:INGRESS is not supported by protocol:TCP", + service: &types.ServiceConfig{Name: "test", Image: "asdf", Ports: []types.ServicePortConfig{{Target: 80, Mode: compose.Mode_INGRESS, Protocol: compose.Protocol_TCP}}}, + wantErr: "mode:INGRESS is not supported by protocol:tcp", }, { name: "invalid healthcheck interval", - service: &defangv1.Service{ + service: &types.ServiceConfig{ Name: "test", Image: "asdf", - Healthcheck: &defangv1.HealthCheck{ + HealthCheck: &types.HealthCheckConfig{ Test: []string{"CMD-SHELL", "echo 1"}, - Interval: 1, - Timeout: 2, + Interval: duration(1), + Timeout: duration(2), }, }, wantErr: "invalid healthcheck: timeout must be less than the interval", }, { name: "invalid CMD healthcheck", - service: &defangv1.Service{ + service: &types.ServiceConfig{ Name: "test", Image: "asdf", - Ports: []*defangv1.Port{{Target: 80, Mode: defangv1.Mode_INGRESS, Protocol: defangv1.Protocol_HTTP}}, - Healthcheck: &defangv1.HealthCheck{ + Ports: []types.ServicePortConfig{{Target: 80, Mode: compose.Mode_INGRESS, Protocol: compose.Protocol_HTTP}}, + HealthCheck: &types.HealthCheckConfig{ Test: []string{"CMD", "echo 1"}, }, }, @@ -79,11 +81,11 @@ func TestValidate(t *testing.T) { }, { name: "CMD without curl or wget", - service: &defangv1.Service{ + service: &types.ServiceConfig{ Name: "test", Image: "asdf", - Ports: []*defangv1.Port{{Target: 80, Mode: defangv1.Mode_INGRESS, Protocol: defangv1.Protocol_HTTP}}, - Healthcheck: &defangv1.HealthCheck{ + Ports: []types.ServicePortConfig{{Target: 80, Mode: compose.Mode_INGRESS, Protocol: compose.Protocol_HTTP}}, + HealthCheck: &types.HealthCheckConfig{ Test: []string{"CMD", "echo", "1"}, }, }, @@ -91,11 +93,11 @@ func TestValidate(t *testing.T) { }, { name: "CMD without HTTP URL", - service: &defangv1.Service{ + service: &types.ServiceConfig{ Name: "test", Image: "asdf", - Ports: []*defangv1.Port{{Target: 80, Mode: defangv1.Mode_INGRESS, Protocol: defangv1.Protocol_HTTP}}, - Healthcheck: &defangv1.HealthCheck{ + Ports: []types.ServicePortConfig{{Target: 80, Mode: compose.Mode_INGRESS, Protocol: compose.Protocol_HTTP}}, + HealthCheck: &types.HealthCheckConfig{ Test: []string{"CMD", "curl", "1"}, }, }, @@ -103,10 +105,10 @@ func TestValidate(t *testing.T) { }, { name: "NONE with arguments", - service: &defangv1.Service{ + service: &types.ServiceConfig{ Name: "test", Image: "asdf", - Healthcheck: &defangv1.HealthCheck{ + HealthCheck: &types.HealthCheckConfig{ Test: []string{"NONE", "echo", "1"}, }, }, @@ -114,11 +116,11 @@ func TestValidate(t *testing.T) { }, { name: "CMD-SHELL with ingress", - service: &defangv1.Service{ + service: &types.ServiceConfig{ Name: "test", Image: "asdf", - Ports: []*defangv1.Port{{Target: 80, Mode: defangv1.Mode_INGRESS, Protocol: defangv1.Protocol_HTTP}}, - Healthcheck: &defangv1.HealthCheck{ + Ports: []types.ServicePortConfig{{Target: 80, Mode: compose.Mode_INGRESS, Protocol: compose.Protocol_HTTP}}, + HealthCheck: &types.HealthCheckConfig{ Test: []string{"CMD-SHELL", "echo 1"}, }, }, @@ -126,11 +128,11 @@ func TestValidate(t *testing.T) { }, { name: "NONE with ingress", - service: &defangv1.Service{ + service: &types.ServiceConfig{ Name: "test", Image: "asdf", - Ports: []*defangv1.Port{{Target: 80, Mode: defangv1.Mode_INGRESS, Protocol: defangv1.Protocol_HTTP}}, - Healthcheck: &defangv1.HealthCheck{ + Ports: []types.ServicePortConfig{{Target: 80, Mode: compose.Mode_INGRESS, Protocol: compose.Protocol_HTTP}}, + HealthCheck: &types.HealthCheckConfig{ Test: []string{"NONE"}, }, }, @@ -138,10 +140,10 @@ func TestValidate(t *testing.T) { }, { name: "unsupported healthcheck test", - service: &defangv1.Service{ + service: &types.ServiceConfig{ Name: "test", Image: "asdf", - Healthcheck: &defangv1.HealthCheck{ + HealthCheck: &types.HealthCheckConfig{ Test: []string{"BLAH"}, }, }, @@ -149,24 +151,24 @@ func TestValidate(t *testing.T) { }, { name: "too many replicas", - service: &defangv1.Service{ + service: &types.ServiceConfig{ Name: "test", Image: "asdf", - Deploy: &defangv1.Deploy{ - Replicas: 100, + Deploy: &types.DeployConfig{ + Replicas: ptr.Int(100), }, }, wantErr: "replicas exceeds quota (max 16)", }, { name: "too many CPUs", - service: &defangv1.Service{ + service: &types.ServiceConfig{ Name: "test", Image: "asdf", - Deploy: &defangv1.Deploy{ - Resources: &defangv1.Resources{ - Reservations: &defangv1.Resource{ - Cpus: 100, + Deploy: &types.DeployConfig{ + Resources: types.Resources{ + Reservations: &types.Resource{ + NanoCPUs: 100, }, }, }, @@ -175,13 +177,13 @@ func TestValidate(t *testing.T) { }, { name: "negative cpus", - service: &defangv1.Service{ + service: &types.ServiceConfig{ Name: "test", Image: "asdf", - Deploy: &defangv1.Deploy{ - Resources: &defangv1.Resources{ - Reservations: &defangv1.Resource{ - Cpus: -1, + Deploy: &types.DeployConfig{ + Resources: types.Resources{ + Reservations: &types.Resource{ + NanoCPUs: -1, }, }, }, @@ -190,43 +192,43 @@ func TestValidate(t *testing.T) { }, { name: "too much memory", - service: &defangv1.Service{ + service: &types.ServiceConfig{ Name: "test", Image: "asdf", - Deploy: &defangv1.Deploy{ - Resources: &defangv1.Resources{ - Reservations: &defangv1.Resource{ - Memory: 200000, + Deploy: &types.DeployConfig{ + Resources: types.Resources{ + Reservations: &types.Resource{ + MemoryBytes: compose.MiB * 200000, }, }, }, }, - wantErr: "memory exceeds quota (max 65536 MiB)", + wantErr: "memory 200000 MiB exceeds quota 65536 MiB", }, { name: "negative memory", - service: &defangv1.Service{ + service: &types.ServiceConfig{ Name: "test", Image: "asdf", - Deploy: &defangv1.Deploy{ - Resources: &defangv1.Resources{ - Reservations: &defangv1.Resource{ - Memory: -1, + Deploy: &types.DeployConfig{ + Resources: types.Resources{ + Reservations: &types.Resource{ + MemoryBytes: compose.MiB * -1, }, }, }, }, - wantErr: "memory exceeds quota (max 65536 MiB)", + wantErr: "memory -1 MiB exceeds quota 65536 MiB", }, { name: "only GPU", - service: &defangv1.Service{ + service: &types.ServiceConfig{ Name: "test", Image: "asdf", - Deploy: &defangv1.Deploy{ - Resources: &defangv1.Resources{ - Reservations: &defangv1.Resource{ - Devices: []*defangv1.Device{ + Deploy: &types.DeployConfig{ + Resources: types.Resources{ + Reservations: &types.Resource{ + Devices: []types.DeviceRequest{ {Capabilities: []string{"tpu"}}, }, }, @@ -237,13 +239,13 @@ func TestValidate(t *testing.T) { }, { name: "only nvidia GPU", - service: &defangv1.Service{ + service: &types.ServiceConfig{ Name: "test", Image: "asdf", - Deploy: &defangv1.Deploy{ - Resources: &defangv1.Resources{ - Reservations: &defangv1.Resource{ - Devices: []*defangv1.Device{ + Deploy: &types.DeployConfig{ + Resources: types.Resources{ + Reservations: &types.Resource{ + Devices: []types.DeviceRequest{ {Capabilities: []string{"gpu"}, Driver: "amd"}, }, }, @@ -254,36 +256,36 @@ func TestValidate(t *testing.T) { }, { name: "too many GPUs", - service: &defangv1.Service{ + service: &types.ServiceConfig{ Name: "test", Image: "asdf", - Deploy: &defangv1.Deploy{ - Resources: &defangv1.Resources{ - Reservations: &defangv1.Resource{ - Devices: []*defangv1.Device{ + Deploy: &types.DeployConfig{ + Resources: types.Resources{ + Reservations: &types.Resource{ + Devices: []types.DeviceRequest{ {Capabilities: []string{"gpu"}, Driver: "nvidia", Count: 100}, }, }, }, }, }, - wantErr: "gpu count exceeds quota (max 8)", + wantErr: "gpu count 100 exceeds quota 8", }, { name: "valid service", - service: &defangv1.Service{ + service: &types.ServiceConfig{ Name: "test", Image: "asdf", - Ports: []*defangv1.Port{{Target: 80, Mode: defangv1.Mode_INGRESS, Protocol: defangv1.Protocol_HTTP}}, - Healthcheck: &defangv1.HealthCheck{ + Ports: []types.ServicePortConfig{{Target: 80, Mode: compose.Mode_INGRESS, Protocol: compose.Protocol_HTTP}}, + HealthCheck: &types.HealthCheckConfig{ Test: []string{"CMD", "curl", "http://localhost"}, }, - Deploy: &defangv1.Deploy{ - Resources: &defangv1.Resources{ - Reservations: &defangv1.Resource{ - Cpus: 1, - Memory: 1024, - Devices: []*defangv1.Device{ + Deploy: &types.DeployConfig{ + Resources: types.Resources{ + Reservations: &types.Resource{ + NanoCPUs: 1, + MemoryBytes: compose.MiB * 1024, + Devices: []types.DeviceRequest{ { Capabilities: []string{"gpu"}, Driver: "nvidia", @@ -319,3 +321,7 @@ func TestValidate(t *testing.T) { }) } } + +func duration(d types.Duration) *types.Duration { + return &d +} diff --git a/src/protos/io/defang/v1/fabric.pb.go b/src/protos/io/defang/v1/fabric.pb.go index ee84ad1c8..a4f882161 100644 --- a/src/protos/io/defang/v1/fabric.pb.go +++ b/src/protos/io/defang/v1/fabric.pb.go @@ -896,7 +896,7 @@ type DeployRequest struct { // Deprecated: Marked as deprecated in io/defang/v1/fabric.proto. Project string `protobuf:"bytes,2,opt,name=project,proto3" json:"project,omitempty"` // deprecated; use compose.name Mode DeploymentMode `protobuf:"varint,3,opt,name=mode,proto3,enum=io.defang.v1.DeploymentMode" json:"mode,omitempty"` - Compose string `protobuf:"bytes,4,opt,name=compose,proto3" json:"compose,omitempty"` // yaml (or json) + Compose []byte `protobuf:"bytes,4,opt,name=compose,proto3" json:"compose,omitempty"` // yaml (or json) } func (x *DeployRequest) Reset() { @@ -952,11 +952,11 @@ func (x *DeployRequest) GetMode() DeploymentMode { return DeploymentMode_UNSPECIFIED_MODE } -func (x *DeployRequest) GetCompose() string { +func (x *DeployRequest) GetCompose() []byte { if x != nil { return x.Compose } - return "" + return nil } type DeployResponse struct { @@ -2647,11 +2647,12 @@ type ProjectUpdate struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Services []*ServiceInfo `protobuf:"bytes,1,rep,name=services,proto3" json:"services,omitempty"` - AlbArn string `protobuf:"bytes,2,opt,name=alb_arn,json=albArn,proto3" json:"alb_arn,omitempty"` - Project string `protobuf:"bytes,3,opt,name=project,proto3" json:"project,omitempty"` // should we use compose.name? - Compose string `protobuf:"bytes,4,opt,name=compose,proto3" json:"compose,omitempty"` - CdVersion string `protobuf:"bytes,5,opt,name=cd_version,json=cdVersion,proto3" json:"cd_version,omitempty"` + Services []*ServiceInfo `protobuf:"bytes,1,rep,name=services,proto3" json:"services,omitempty"` + AlbArn string `protobuf:"bytes,2,opt,name=alb_arn,json=albArn,proto3" json:"alb_arn,omitempty"` + // Deprecated: Marked as deprecated in io/defang/v1/fabric.proto. + Project string `protobuf:"bytes,3,opt,name=project,proto3" json:"project,omitempty"` // deprecated; use compose.name + Compose []byte `protobuf:"bytes,4,opt,name=compose,proto3" json:"compose,omitempty"` + CdVersion string `protobuf:"bytes,5,opt,name=cd_version,json=cdVersion,proto3" json:"cd_version,omitempty"` } func (x *ProjectUpdate) Reset() { @@ -2698,6 +2699,7 @@ func (x *ProjectUpdate) GetAlbArn() string { return "" } +// Deprecated: Marked as deprecated in io/defang/v1/fabric.proto. func (x *ProjectUpdate) GetProject() string { if x != nil { return x.Project @@ -2705,11 +2707,11 @@ func (x *ProjectUpdate) GetProject() string { return "" } -func (x *ProjectUpdate) GetCompose() string { +func (x *ProjectUpdate) GetCompose() []byte { if x != nil { return x.Compose } - return "" + return nil } func (x *ProjectUpdate) GetCdVersion() string { @@ -4187,7 +4189,7 @@ var file_io_defang_v1_fabric_proto_rawDesc = []byte{ 0x0e, 0x32, 0x1c, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x65, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x65, 0x22, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x65, 0x22, 0x5b, 0x0a, 0x0e, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, @@ -4366,421 +4368,421 @@ var file_io_defang_v1_fabric_proto_rawDesc = []byte{ 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x22, 0xb2, 0x01, 0x0a, 0x0d, 0x50, 0x72, 0x6f, + 0x07, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x22, 0xb6, 0x01, 0x0a, 0x0d, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x35, 0x0a, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x17, 0x0a, 0x07, 0x61, 0x6c, 0x62, 0x5f, 0x61, 0x72, 0x6e, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x61, 0x6c, 0x62, 0x41, 0x72, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, - 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x72, 0x6f, - 0x6a, 0x65, 0x63, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x65, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x65, 0x12, 0x1d, - 0x0a, 0x0a, 0x63, 0x64, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x63, 0x64, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x1f, 0x0a, - 0x09, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x49, 0x44, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, - 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x5e, - 0x0a, 0x06, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x63, 0x61, 0x70, 0x61, - 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, - 0x63, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, - 0x64, 0x72, 0x69, 0x76, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x72, - 0x69, 0x76, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0d, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x3a, 0x02, 0x18, 0x01, 0x22, 0x6a, - 0x0a, 0x08, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, - 0x6d, 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x02, 0x52, 0x06, 0x6d, 0x65, 0x6d, 0x6f, - 0x72, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x70, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x02, - 0x52, 0x04, 0x63, 0x70, 0x75, 0x73, 0x12, 0x2e, 0x0a, 0x07, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, - 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, - 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x07, 0x64, - 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x3a, 0x02, 0x18, 0x01, 0x22, 0x4b, 0x0a, 0x09, 0x52, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x3a, 0x0a, 0x0c, 0x72, 0x65, 0x73, 0x65, 0x72, - 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, - 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x3a, 0x02, 0x18, 0x01, 0x22, 0x5f, 0x0a, 0x06, 0x44, 0x65, 0x70, 0x6c, 0x6f, - 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x73, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0d, 0x52, 0x08, 0x72, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x73, 0x12, 0x35, 0x0a, - 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x17, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, - 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x73, 0x3a, 0x02, 0x18, 0x01, 0x22, 0x7e, 0x0a, 0x04, 0x50, 0x6f, 0x72, 0x74, - 0x12, 0x16, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, - 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x32, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x69, 0x6f, 0x2e, - 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, - 0x6f, 0x6c, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x26, 0x0a, 0x04, - 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x12, 0x2e, 0x69, 0x6f, 0x2e, - 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x04, - 0x6d, 0x6f, 0x64, 0x65, 0x3a, 0x02, 0x18, 0x01, 0x22, 0x24, 0x0a, 0x06, 0x53, 0x65, 0x63, 0x72, - 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x3a, 0x02, 0x18, 0x01, 0x22, 0xe4, - 0x01, 0x0a, 0x05, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, - 0x65, 0x78, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, - 0x78, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x66, 0x69, 0x6c, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x66, 0x69, - 0x6c, 0x65, 0x12, 0x31, 0x0a, 0x04, 0x61, 0x72, 0x67, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x1d, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, - 0x42, 0x75, 0x69, 0x6c, 0x64, 0x2e, 0x41, 0x72, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, - 0x04, 0x61, 0x72, 0x67, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x68, 0x6d, 0x5f, 0x73, 0x69, 0x7a, - 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x02, 0x52, 0x07, 0x73, 0x68, 0x6d, 0x53, 0x69, 0x7a, 0x65, - 0x12, 0x16, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x1a, 0x37, 0x0a, 0x09, 0x41, 0x72, 0x67, 0x73, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, - 0x01, 0x3a, 0x02, 0x18, 0x01, 0x22, 0x75, 0x0a, 0x0b, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, - 0x68, 0x65, 0x63, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x09, 0x52, 0x04, 0x74, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x74, 0x65, - 0x72, 0x76, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x65, - 0x72, 0x76, 0x61, 0x6c, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x18, - 0x0a, 0x07, 0x72, 0x65, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, - 0x07, 0x72, 0x65, 0x74, 0x72, 0x69, 0x65, 0x73, 0x3a, 0x02, 0x18, 0x01, 0x22, 0xd8, 0x06, 0x0a, - 0x07, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, - 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x69, 0x6d, 0x61, - 0x67, 0x65, 0x12, 0x32, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, - 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x52, 0x08, 0x70, 0x6c, - 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x1e, 0x0a, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, - 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x42, 0x02, 0x18, 0x01, 0x52, 0x08, 0x69, 0x6e, - 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x12, 0x2c, 0x0a, 0x06, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, - 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x52, 0x06, 0x64, 0x65, - 0x70, 0x6c, 0x6f, 0x79, 0x12, 0x28, 0x0a, 0x05, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x18, 0x06, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, - 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x52, 0x05, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x12, 0x48, - 0x0a, 0x0b, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x07, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, - 0x76, 0x31, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x45, 0x6e, 0x76, 0x69, 0x72, - 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0b, 0x65, 0x6e, 0x76, - 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x29, 0x0a, 0x05, 0x62, 0x75, 0x69, 0x6c, - 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, - 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x52, 0x05, 0x62, 0x75, - 0x69, 0x6c, 0x64, 0x12, 0x2e, 0x0a, 0x07, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x18, 0x09, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, - 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x07, 0x73, 0x65, 0x63, 0x72, - 0x65, 0x74, 0x73, 0x12, 0x3b, 0x0a, 0x0b, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, - 0x63, 0x6b, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, - 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, - 0x65, 0x63, 0x6b, 0x52, 0x0b, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, - 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x0b, 0x20, 0x03, 0x28, - 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x6f, - 0x6d, 0x61, 0x69, 0x6e, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, - 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x6e, - 0x69, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x69, 0x6e, 0x69, 0x74, 0x12, 0x19, - 0x0a, 0x08, 0x64, 0x6e, 0x73, 0x5f, 0x72, 0x6f, 0x6c, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x07, 0x64, 0x6e, 0x73, 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x3c, 0x0a, 0x0c, 0x73, 0x74, 0x61, - 0x74, 0x69, 0x63, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x19, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x53, - 0x74, 0x61, 0x74, 0x69, 0x63, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x0b, 0x73, 0x74, 0x61, 0x74, - 0x69, 0x63, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x31, 0x0a, 0x08, 0x6e, 0x65, 0x74, 0x77, 0x6f, - 0x72, 0x6b, 0x73, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x69, 0x6f, 0x2e, 0x64, - 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, - 0x52, 0x08, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x12, 0x29, 0x0a, 0x05, 0x72, 0x65, - 0x64, 0x69, 0x73, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x69, 0x6f, 0x2e, 0x64, - 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x64, 0x69, 0x73, 0x52, 0x05, - 0x72, 0x65, 0x64, 0x69, 0x73, 0x12, 0x32, 0x0a, 0x08, 0x70, 0x6f, 0x73, 0x74, 0x67, 0x72, 0x65, - 0x73, 0x18, 0x13, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, - 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x73, 0x74, 0x67, 0x72, 0x65, 0x73, 0x52, - 0x08, 0x70, 0x6f, 0x73, 0x74, 0x67, 0x72, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x6f, - 0x6a, 0x65, 0x63, 0x74, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x6a, - 0x65, 0x63, 0x74, 0x1a, 0x3e, 0x0a, 0x10, 0x45, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, - 0x6e, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, - 0x02, 0x38, 0x01, 0x3a, 0x02, 0x18, 0x01, 0x22, 0x47, 0x0a, 0x0b, 0x53, 0x74, 0x61, 0x74, 0x69, - 0x63, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x12, 0x1c, - 0x0a, 0x09, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, - 0x09, 0x52, 0x09, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x73, 0x3a, 0x02, 0x18, 0x01, - 0x22, 0x0b, 0x0a, 0x05, 0x52, 0x65, 0x64, 0x69, 0x73, 0x3a, 0x02, 0x18, 0x01, 0x22, 0x0e, 0x0a, - 0x08, 0x50, 0x6f, 0x73, 0x74, 0x67, 0x72, 0x65, 0x73, 0x3a, 0x02, 0x18, 0x01, 0x22, 0xa3, 0x02, - 0x0a, 0x0b, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x30, 0x0a, - 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x69, 0x6f, - 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x70, 0x6c, 0x6f, - 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x12, - 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, - 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, - 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x28, 0x0a, 0x0f, 0x64, - 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, - 0x74, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x61, 0x74, 0x61, 0x73, 0x63, 0x68, - 0x65, 0x6d, 0x61, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x61, 0x74, 0x61, 0x73, - 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, - 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, - 0x2e, 0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x08, 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, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x12, - 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, - 0x61, 0x74, 0x61, 0x22, 0x91, 0x02, 0x0a, 0x05, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x20, 0x0a, - 0x0b, 0x73, 0x70, 0x65, 0x63, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0b, 0x73, 0x70, 0x65, 0x63, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, - 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, - 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, - 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x28, 0x0a, 0x0f, 0x64, - 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, - 0x74, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x61, 0x74, 0x61, 0x73, 0x63, 0x68, - 0x65, 0x6d, 0x61, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x61, 0x74, 0x61, 0x73, - 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, - 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, - 0x2e, 0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x08, 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, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x12, - 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, - 0x61, 0x74, 0x61, 0x3a, 0x02, 0x18, 0x01, 0x22, 0x3b, 0x0a, 0x0e, 0x50, 0x75, 0x62, 0x6c, 0x69, - 0x73, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x05, 0x65, 0x76, 0x65, - 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, - 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x05, 0x65, - 0x76, 0x65, 0x6e, 0x74, 0x22, 0x42, 0x0a, 0x10, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x65, 0x74, 0x61, 0x67, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x65, 0x74, 0x61, 0x67, 0x22, 0xaa, 0x01, 0x0a, 0x11, 0x53, 0x75, 0x62, - 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, - 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x28, 0x09, 0x52, 0x06, 0x61, 0x6c, 0x62, 0x41, 0x72, 0x6e, 0x12, 0x1c, 0x0a, 0x07, 0x70, 0x72, + 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, + 0x07, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x70, + 0x6f, 0x73, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x70, 0x6f, + 0x73, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x64, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x64, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x22, 0x1f, 0x0a, 0x09, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x49, 0x44, 0x12, 0x12, + 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x22, 0x5e, 0x0a, 0x06, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x22, 0x0a, 0x0c, + 0x63, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x0c, 0x63, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, + 0x12, 0x16, 0x0a, 0x06, 0x64, 0x72, 0x69, 0x76, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x64, 0x72, 0x69, 0x76, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x3a, 0x02, + 0x18, 0x01, 0x22, 0x6a, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x16, + 0x0a, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x02, 0x52, 0x06, + 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x70, 0x75, 0x73, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x02, 0x52, 0x04, 0x63, 0x70, 0x75, 0x73, 0x12, 0x2e, 0x0a, 0x07, 0x64, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x69, 0x6f, + 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, + 0x65, 0x52, 0x07, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x3a, 0x02, 0x18, 0x01, 0x22, 0x4b, + 0x0a, 0x09, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x3a, 0x0a, 0x0c, 0x72, + 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x16, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, + 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x65, 0x72, + 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x3a, 0x02, 0x18, 0x01, 0x22, 0x5f, 0x0a, 0x06, 0x44, + 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, + 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x72, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, + 0x73, 0x12, 0x35, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, + 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x09, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x3a, 0x02, 0x18, 0x01, 0x22, 0x7e, 0x0a, 0x04, + 0x50, 0x6f, 0x72, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x32, 0x0a, 0x08, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, + 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, + 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, + 0x12, 0x26, 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x12, + 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x6f, + 0x64, 0x65, 0x52, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x3a, 0x02, 0x18, 0x01, 0x22, 0x24, 0x0a, 0x06, + 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x3a, 0x02, + 0x18, 0x01, 0x22, 0xe4, 0x01, 0x0a, 0x05, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x18, 0x0a, 0x07, + 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, + 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, + 0x66, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x6f, 0x63, 0x6b, + 0x65, 0x72, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x31, 0x0a, 0x04, 0x61, 0x72, 0x67, 0x73, 0x18, 0x03, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, + 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x2e, 0x41, 0x72, 0x67, 0x73, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x52, 0x04, 0x61, 0x72, 0x67, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x68, 0x6d, + 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x02, 0x52, 0x07, 0x73, 0x68, 0x6d, + 0x53, 0x69, 0x7a, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x1a, 0x37, 0x0a, 0x09, + 0x41, 0x72, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x3a, 0x02, 0x38, 0x01, 0x3a, 0x02, 0x18, 0x01, 0x22, 0x75, 0x0a, 0x0b, 0x48, 0x65, 0x61, + 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x65, 0x73, 0x74, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x74, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, + 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, + 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, + 0x6f, 0x75, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, + 0x75, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x07, 0x72, 0x65, 0x74, 0x72, 0x69, 0x65, 0x73, 0x3a, 0x02, 0x18, 0x01, + 0x22, 0xd8, 0x06, 0x0a, 0x07, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x32, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, + 0x72, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, + 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, + 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x1e, 0x0a, 0x08, 0x69, 0x6e, + 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x42, 0x02, 0x18, 0x01, + 0x52, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x12, 0x2c, 0x0a, 0x06, 0x64, 0x65, + 0x70, 0x6c, 0x6f, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x69, 0x6f, 0x2e, + 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, + 0x52, 0x06, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x12, 0x28, 0x0a, 0x05, 0x70, 0x6f, 0x72, 0x74, + 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, + 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x52, 0x05, 0x70, 0x6f, 0x72, + 0x74, 0x73, 0x12, 0x48, 0x0a, 0x0b, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, + 0x74, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, + 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x45, + 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, + 0x0b, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x29, 0x0a, 0x05, + 0x62, 0x75, 0x69, 0x6c, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x69, 0x6f, + 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, + 0x52, 0x05, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x2e, 0x0a, 0x07, 0x73, 0x65, 0x63, 0x72, 0x65, + 0x74, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, + 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x07, + 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x12, 0x3b, 0x0a, 0x0b, 0x68, 0x65, 0x61, 0x6c, 0x74, + 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x69, + 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x65, 0x61, 0x6c, + 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x0b, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, + 0x68, 0x65, 0x63, 0x6b, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, + 0x0b, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x1e, + 0x0a, 0x0a, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0c, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0a, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, + 0x0a, 0x04, 0x69, 0x6e, 0x69, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x69, 0x6e, + 0x69, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x64, 0x6e, 0x73, 0x5f, 0x72, 0x6f, 0x6c, 0x65, 0x18, 0x0e, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x64, 0x6e, 0x73, 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x3c, 0x0a, + 0x0c, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x0f, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, + 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x0b, + 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x31, 0x0a, 0x08, 0x6e, + 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, + 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x65, 0x74, + 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x08, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x12, 0x29, + 0x0a, 0x05, 0x72, 0x65, 0x64, 0x69, 0x73, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, + 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x64, + 0x69, 0x73, 0x52, 0x05, 0x72, 0x65, 0x64, 0x69, 0x73, 0x12, 0x32, 0x0a, 0x08, 0x70, 0x6f, 0x73, + 0x74, 0x67, 0x72, 0x65, 0x73, 0x18, 0x13, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x69, 0x6f, + 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x73, 0x74, 0x67, + 0x72, 0x65, 0x73, 0x52, 0x08, 0x70, 0x6f, 0x73, 0x74, 0x67, 0x72, 0x65, 0x73, 0x12, 0x18, 0x0a, + 0x07, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x1a, 0x3e, 0x0a, 0x10, 0x45, 0x6e, 0x76, 0x69, 0x72, + 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, + 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x3a, 0x02, 0x18, 0x01, 0x22, 0x47, 0x0a, 0x0b, 0x53, + 0x74, 0x61, 0x74, 0x69, 0x63, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x6f, + 0x6c, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x6f, 0x6c, 0x64, + 0x65, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x73, 0x18, + 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x73, + 0x3a, 0x02, 0x18, 0x01, 0x22, 0x0b, 0x0a, 0x05, 0x52, 0x65, 0x64, 0x69, 0x73, 0x3a, 0x02, 0x18, + 0x01, 0x22, 0x0e, 0x0a, 0x08, 0x50, 0x6f, 0x73, 0x74, 0x67, 0x72, 0x65, 0x73, 0x3a, 0x02, 0x18, + 0x01, 0x22, 0xa3, 0x02, 0x0a, 0x0b, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x12, 0x30, 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x1c, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x44, + 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x6d, + 0x6f, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, + 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, + 0x28, 0x0a, 0x0f, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x74, 0x79, + 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, + 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x61, 0x74, + 0x61, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, + 0x61, 0x74, 0x61, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x08, 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, 0x04, 0x74, + 0x69, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x09, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x91, 0x02, 0x0a, 0x05, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x12, 0x20, 0x0a, 0x0b, 0x73, 0x70, 0x65, 0x63, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x70, 0x65, 0x63, 0x76, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, + 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, + 0x28, 0x0a, 0x0f, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x74, 0x79, + 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, + 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x61, 0x74, + 0x61, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, + 0x61, 0x74, 0x61, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x08, 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, 0x04, 0x74, + 0x69, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x09, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x3a, 0x02, 0x18, 0x01, 0x22, 0x3b, 0x0a, 0x0e, 0x50, + 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x29, 0x0a, + 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x69, + 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x52, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x42, 0x0a, 0x10, 0x53, 0x75, 0x62, 0x73, + 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, + 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, + 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x65, 0x74, 0x61, 0x67, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x65, 0x74, 0x61, 0x67, 0x22, 0xaa, 0x01, 0x0a, + 0x11, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x37, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, + 0x76, 0x31, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x42, 0x02, + 0x18, 0x01, 0x52, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, + 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x30, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, + 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, + 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x22, 0x4e, 0x0a, 0x1c, 0x44, 0x65, 0x6c, + 0x65, 0x67, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5a, 0x6f, + 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x6e, 0x61, 0x6d, + 0x65, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x11, 0x6e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x22, 0x33, 0x0a, 0x1d, 0x44, 0x65, 0x6c, + 0x65, 0x67, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5a, 0x6f, + 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x7a, 0x6f, + 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x7a, 0x6f, 0x6e, 0x65, 0x22, 0x73, + 0x0a, 0x0e, 0x57, 0x68, 0x6f, 0x41, 0x6d, 0x49, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x16, 0x0a, 0x06, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, + 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x2a, 0x54, 0x0a, 0x0e, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, + 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x10, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, + 0x46, 0x49, 0x45, 0x44, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x10, 0x00, 0x12, 0x0f, 0x0a, 0x0b, 0x44, + 0x45, 0x56, 0x45, 0x4c, 0x4f, 0x50, 0x4d, 0x45, 0x4e, 0x54, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, + 0x53, 0x54, 0x41, 0x47, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x50, 0x52, 0x4f, + 0x44, 0x55, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x03, 0x2a, 0x89, 0x02, 0x0a, 0x0c, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x11, 0x0a, 0x0d, 0x4e, 0x4f, + 0x54, 0x5f, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x10, 0x0a, + 0x0c, 0x42, 0x55, 0x49, 0x4c, 0x44, 0x5f, 0x51, 0x55, 0x45, 0x55, 0x45, 0x44, 0x10, 0x01, 0x12, + 0x16, 0x0a, 0x12, 0x42, 0x55, 0x49, 0x4c, 0x44, 0x5f, 0x50, 0x52, 0x4f, 0x56, 0x49, 0x53, 0x49, + 0x4f, 0x4e, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x11, 0x0a, 0x0d, 0x42, 0x55, 0x49, 0x4c, 0x44, + 0x5f, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x03, 0x12, 0x14, 0x0a, 0x10, 0x42, 0x55, + 0x49, 0x4c, 0x44, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x56, 0x41, 0x54, 0x49, 0x4e, 0x47, 0x10, 0x04, + 0x12, 0x11, 0x0a, 0x0d, 0x42, 0x55, 0x49, 0x4c, 0x44, 0x5f, 0x52, 0x55, 0x4e, 0x4e, 0x49, 0x4e, + 0x47, 0x10, 0x05, 0x12, 0x12, 0x0a, 0x0e, 0x42, 0x55, 0x49, 0x4c, 0x44, 0x5f, 0x53, 0x54, 0x4f, + 0x50, 0x50, 0x49, 0x4e, 0x47, 0x10, 0x06, 0x12, 0x11, 0x0a, 0x0d, 0x55, 0x50, 0x44, 0x41, 0x54, + 0x45, 0x5f, 0x51, 0x55, 0x45, 0x55, 0x45, 0x44, 0x10, 0x07, 0x12, 0x16, 0x0a, 0x12, 0x44, 0x45, + 0x50, 0x4c, 0x4f, 0x59, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, + 0x10, 0x08, 0x12, 0x18, 0x0a, 0x14, 0x44, 0x45, 0x50, 0x4c, 0x4f, 0x59, 0x4d, 0x45, 0x4e, 0x54, + 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x4c, 0x45, 0x54, 0x45, 0x44, 0x10, 0x09, 0x12, 0x15, 0x0a, 0x11, + 0x44, 0x45, 0x50, 0x4c, 0x4f, 0x59, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, + 0x44, 0x10, 0x0a, 0x12, 0x10, 0x0a, 0x0c, 0x42, 0x55, 0x49, 0x4c, 0x44, 0x5f, 0x46, 0x41, 0x49, + 0x4c, 0x45, 0x44, 0x10, 0x0b, 0x2a, 0x42, 0x0a, 0x0a, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x54, + 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x16, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x54, 0x59, 0x50, + 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, + 0x18, 0x0a, 0x14, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x45, + 0x4e, 0x53, 0x49, 0x54, 0x49, 0x56, 0x45, 0x10, 0x01, 0x2a, 0x3f, 0x0a, 0x08, 0x50, 0x6c, 0x61, + 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x0f, 0x0a, 0x0b, 0x4c, 0x49, 0x4e, 0x55, 0x58, 0x5f, 0x41, + 0x4d, 0x44, 0x36, 0x34, 0x10, 0x00, 0x12, 0x0f, 0x0a, 0x0b, 0x4c, 0x49, 0x4e, 0x55, 0x58, 0x5f, + 0x41, 0x52, 0x4d, 0x36, 0x34, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x4c, 0x49, 0x4e, 0x55, 0x58, + 0x5f, 0x41, 0x4e, 0x59, 0x10, 0x02, 0x1a, 0x02, 0x18, 0x01, 0x2a, 0x48, 0x0a, 0x08, 0x50, 0x72, + 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x4e, 0x59, 0x10, 0x00, 0x12, + 0x07, 0x0a, 0x03, 0x55, 0x44, 0x50, 0x10, 0x01, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x43, 0x50, 0x10, + 0x02, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x54, 0x54, 0x50, 0x10, 0x03, 0x12, 0x09, 0x0a, 0x05, 0x48, + 0x54, 0x54, 0x50, 0x32, 0x10, 0x04, 0x12, 0x08, 0x0a, 0x04, 0x47, 0x52, 0x50, 0x43, 0x10, 0x05, + 0x1a, 0x02, 0x18, 0x01, 0x2a, 0x21, 0x0a, 0x04, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x08, 0x0a, 0x04, + 0x48, 0x4f, 0x53, 0x54, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x49, 0x4e, 0x47, 0x52, 0x45, 0x53, + 0x53, 0x10, 0x01, 0x1a, 0x02, 0x18, 0x01, 0x2a, 0x37, 0x0a, 0x07, 0x4e, 0x65, 0x74, 0x77, 0x6f, + 0x72, 0x6b, 0x12, 0x0f, 0x0a, 0x0b, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, + 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x52, 0x49, 0x56, 0x41, 0x54, 0x45, 0x10, 0x01, + 0x12, 0x0a, 0x0a, 0x06, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x10, 0x02, 0x1a, 0x02, 0x18, 0x01, + 0x32, 0x97, 0x14, 0x0a, 0x10, 0x46, 0x61, 0x62, 0x72, 0x69, 0x63, 0x43, 0x6f, 0x6e, 0x74, 0x72, + 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x12, 0x3e, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x14, 0x2e, 0x69, 0x6f, 0x2e, + 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x22, 0x03, 0x90, 0x02, 0x01, 0x12, 0x40, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x56, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x15, 0x2e, 0x69, 0x6f, + 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x22, 0x03, 0x90, 0x02, 0x01, 0x12, 0x40, 0x0a, 0x05, 0x54, 0x6f, 0x6b, 0x65, 0x6e, + 0x12, 0x1a, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x69, + 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x6f, 0x6b, 0x65, + 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x0b, 0x52, 0x65, 0x76, + 0x6f, 0x6b, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3f, 0x0a, 0x04, 0x54, 0x61, 0x69, 0x6c, + 0x12, 0x19, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, + 0x54, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x69, 0x6f, + 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x69, 0x6c, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x3f, 0x0a, 0x06, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x12, 0x15, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, + 0x76, 0x31, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x1a, 0x19, 0x2e, 0x69, 0x6f, 0x2e, + 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x22, 0x03, 0x88, 0x02, 0x01, 0x12, 0x43, 0x0a, 0x06, 0x44, 0x65, + 0x70, 0x6c, 0x6f, 0x79, 0x12, 0x1b, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, + 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1c, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, + 0x2e, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x3e, 0x0a, 0x03, 0x47, 0x65, 0x74, 0x12, 0x17, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, + 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x49, 0x44, 0x1a, 0x19, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x42, 0x02, 0x18, 0x01, 0x52, 0x07, - 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x12, 0x30, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, - 0x31, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, - 0x73, 0x74, 0x61, 0x74, 0x65, 0x22, 0x4e, 0x0a, 0x1c, 0x44, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, - 0x65, 0x53, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5a, 0x6f, 0x6e, 0x65, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x73, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x09, 0x52, 0x11, 0x6e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, - 0x63, 0x6f, 0x72, 0x64, 0x73, 0x22, 0x33, 0x0a, 0x1d, 0x44, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, - 0x65, 0x53, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5a, 0x6f, 0x6e, 0x65, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x7a, 0x6f, 0x6e, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x7a, 0x6f, 0x6e, 0x65, 0x22, 0x73, 0x0a, 0x0e, 0x57, 0x68, - 0x6f, 0x41, 0x6d, 0x49, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, - 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x65, - 0x6e, 0x61, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x16, - 0x0a, 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x2a, - 0x54, 0x0a, 0x0e, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x6f, 0x64, - 0x65, 0x12, 0x14, 0x0a, 0x10, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, - 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x10, 0x00, 0x12, 0x0f, 0x0a, 0x0b, 0x44, 0x45, 0x56, 0x45, 0x4c, - 0x4f, 0x50, 0x4d, 0x45, 0x4e, 0x54, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x54, 0x41, 0x47, - 0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x50, 0x52, 0x4f, 0x44, 0x55, 0x43, 0x54, - 0x49, 0x4f, 0x4e, 0x10, 0x03, 0x2a, 0x89, 0x02, 0x0a, 0x0c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x11, 0x0a, 0x0d, 0x4e, 0x4f, 0x54, 0x5f, 0x53, 0x50, - 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x42, 0x55, 0x49, - 0x4c, 0x44, 0x5f, 0x51, 0x55, 0x45, 0x55, 0x45, 0x44, 0x10, 0x01, 0x12, 0x16, 0x0a, 0x12, 0x42, - 0x55, 0x49, 0x4c, 0x44, 0x5f, 0x50, 0x52, 0x4f, 0x56, 0x49, 0x53, 0x49, 0x4f, 0x4e, 0x49, 0x4e, - 0x47, 0x10, 0x02, 0x12, 0x11, 0x0a, 0x0d, 0x42, 0x55, 0x49, 0x4c, 0x44, 0x5f, 0x50, 0x45, 0x4e, - 0x44, 0x49, 0x4e, 0x47, 0x10, 0x03, 0x12, 0x14, 0x0a, 0x10, 0x42, 0x55, 0x49, 0x4c, 0x44, 0x5f, - 0x41, 0x43, 0x54, 0x49, 0x56, 0x41, 0x54, 0x49, 0x4e, 0x47, 0x10, 0x04, 0x12, 0x11, 0x0a, 0x0d, - 0x42, 0x55, 0x49, 0x4c, 0x44, 0x5f, 0x52, 0x55, 0x4e, 0x4e, 0x49, 0x4e, 0x47, 0x10, 0x05, 0x12, - 0x12, 0x0a, 0x0e, 0x42, 0x55, 0x49, 0x4c, 0x44, 0x5f, 0x53, 0x54, 0x4f, 0x50, 0x50, 0x49, 0x4e, - 0x47, 0x10, 0x06, 0x12, 0x11, 0x0a, 0x0d, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x51, 0x55, - 0x45, 0x55, 0x45, 0x44, 0x10, 0x07, 0x12, 0x16, 0x0a, 0x12, 0x44, 0x45, 0x50, 0x4c, 0x4f, 0x59, - 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x08, 0x12, 0x18, - 0x0a, 0x14, 0x44, 0x45, 0x50, 0x4c, 0x4f, 0x59, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x43, 0x4f, 0x4d, - 0x50, 0x4c, 0x45, 0x54, 0x45, 0x44, 0x10, 0x09, 0x12, 0x15, 0x0a, 0x11, 0x44, 0x45, 0x50, 0x4c, - 0x4f, 0x59, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x0a, 0x12, - 0x10, 0x0a, 0x0c, 0x42, 0x55, 0x49, 0x4c, 0x44, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, - 0x0b, 0x2a, 0x42, 0x0a, 0x0a, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x54, 0x79, 0x70, 0x65, 0x12, - 0x1a, 0x0a, 0x16, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, - 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x18, 0x0a, 0x14, 0x43, - 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x45, 0x4e, 0x53, 0x49, 0x54, - 0x49, 0x56, 0x45, 0x10, 0x01, 0x2a, 0x3f, 0x0a, 0x08, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, - 0x6d, 0x12, 0x0f, 0x0a, 0x0b, 0x4c, 0x49, 0x4e, 0x55, 0x58, 0x5f, 0x41, 0x4d, 0x44, 0x36, 0x34, - 0x10, 0x00, 0x12, 0x0f, 0x0a, 0x0b, 0x4c, 0x49, 0x4e, 0x55, 0x58, 0x5f, 0x41, 0x52, 0x4d, 0x36, - 0x34, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x4c, 0x49, 0x4e, 0x55, 0x58, 0x5f, 0x41, 0x4e, 0x59, - 0x10, 0x02, 0x1a, 0x02, 0x18, 0x01, 0x2a, 0x48, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, - 0x6f, 0x6c, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x4e, 0x59, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x55, - 0x44, 0x50, 0x10, 0x01, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x43, 0x50, 0x10, 0x02, 0x12, 0x08, 0x0a, - 0x04, 0x48, 0x54, 0x54, 0x50, 0x10, 0x03, 0x12, 0x09, 0x0a, 0x05, 0x48, 0x54, 0x54, 0x50, 0x32, - 0x10, 0x04, 0x12, 0x08, 0x0a, 0x04, 0x47, 0x52, 0x50, 0x43, 0x10, 0x05, 0x1a, 0x02, 0x18, 0x01, - 0x2a, 0x21, 0x0a, 0x04, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x4f, 0x53, 0x54, - 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x49, 0x4e, 0x47, 0x52, 0x45, 0x53, 0x53, 0x10, 0x01, 0x1a, - 0x02, 0x18, 0x01, 0x2a, 0x37, 0x0a, 0x07, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x0f, - 0x0a, 0x0b, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, - 0x0b, 0x0a, 0x07, 0x50, 0x52, 0x49, 0x56, 0x41, 0x54, 0x45, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, - 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x10, 0x02, 0x1a, 0x02, 0x18, 0x01, 0x32, 0x97, 0x14, 0x0a, - 0x10, 0x46, 0x61, 0x62, 0x72, 0x69, 0x63, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, - 0x72, 0x12, 0x3e, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x14, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, - 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x03, 0x90, 0x02, - 0x01, 0x12, 0x40, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x22, 0x03, 0x90, 0x02, 0x01, 0x12, + 0x48, 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x1b, 0x2e, 0x69, 0x6f, 0x2e, 0x64, + 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, + 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x88, 0x02, 0x01, 0x12, 0x4b, 0x0a, 0x07, 0x44, 0x65, 0x73, + 0x74, 0x72, 0x6f, 0x79, 0x12, 0x1c, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, + 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x73, 0x74, 0x72, 0x6f, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, + 0x31, 0x2e, 0x44, 0x65, 0x73, 0x74, 0x72, 0x6f, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x03, 0x90, 0x02, 0x02, 0x12, 0x44, 0x0a, 0x07, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, + 0x68, 0x12, 0x1c, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, + 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x15, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, - 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x03, - 0x90, 0x02, 0x01, 0x12, 0x40, 0x0a, 0x05, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1a, 0x2e, 0x69, - 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x6f, 0x6b, 0x65, - 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, - 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x0b, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x54, - 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, - 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, - 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3f, 0x0a, 0x04, 0x54, 0x61, 0x69, 0x6c, 0x12, 0x19, 0x2e, 0x69, - 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x69, 0x6c, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, - 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x3f, 0x0a, 0x06, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, - 0x15, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x1a, 0x19, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, - 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x49, 0x6e, 0x66, - 0x6f, 0x22, 0x03, 0x88, 0x02, 0x01, 0x12, 0x43, 0x0a, 0x06, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, - 0x12, 0x1b, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, - 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, - 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x70, - 0x6c, 0x6f, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x03, 0x47, - 0x65, 0x74, 0x12, 0x17, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, - 0x31, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x49, 0x44, 0x1a, 0x19, 0x2e, 0x69, 0x6f, - 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x22, 0x03, 0x90, 0x02, 0x01, 0x12, 0x48, 0x0a, 0x06, 0x44, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x1b, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, - 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, - 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x03, 0x88, 0x02, 0x01, 0x12, 0x4b, 0x0a, 0x07, 0x44, 0x65, 0x73, 0x74, 0x72, 0x6f, 0x79, - 0x12, 0x1c, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, - 0x44, 0x65, 0x73, 0x74, 0x72, 0x6f, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, - 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, - 0x73, 0x74, 0x72, 0x6f, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x90, - 0x02, 0x02, 0x12, 0x44, 0x0a, 0x07, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x12, 0x1c, 0x2e, - 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x62, - 0x6c, 0x69, 0x73, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, + 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x03, 0x88, 0x02, 0x01, 0x12, 0x4e, 0x0a, 0x09, + 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x12, 0x1e, 0x2e, 0x69, 0x6f, 0x2e, 0x64, + 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, + 0x62, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x69, 0x6f, 0x2e, 0x64, + 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, + 0x62, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x4e, 0x0a, 0x0b, + 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, - 0x70, 0x74, 0x79, 0x22, 0x03, 0x88, 0x02, 0x01, 0x12, 0x4e, 0x0a, 0x09, 0x53, 0x75, 0x62, 0x73, - 0x63, 0x72, 0x69, 0x62, 0x65, 0x12, 0x1e, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, - 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, - 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x4e, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, - 0x22, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x03, 0x90, 0x02, 0x01, 0x12, 0x58, 0x0a, 0x0d, 0x47, 0x65, 0x6e, 0x65, - 0x72, 0x61, 0x74, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x22, 0x2e, 0x69, 0x6f, 0x2e, 0x64, - 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, - 0x65, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, + 0x70, 0x74, 0x79, 0x1a, 0x22, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, + 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x90, 0x02, 0x01, 0x12, 0x58, 0x0a, 0x0d, + 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x22, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x6e, - 0x65, 0x72, 0x61, 0x74, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x58, 0x0a, 0x0d, 0x53, 0x74, 0x61, 0x72, 0x74, 0x47, 0x65, 0x6e, 0x65, 0x72, - 0x61, 0x74, 0x65, 0x12, 0x22, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, - 0x76, 0x31, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x73, + 0x65, 0x72, 0x61, 0x74, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x23, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, + 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x58, 0x0a, 0x0d, 0x53, 0x74, 0x61, 0x72, 0x74, 0x47, + 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x12, 0x22, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, + 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x46, + 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x69, 0x6f, + 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, + 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x5f, 0x0a, 0x0e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x12, 0x23, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, + 0x31, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, - 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x47, 0x65, 0x6e, 0x65, - 0x72, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5f, 0x0a, 0x0e, - 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x23, - 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, - 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, - 0x76, 0x31, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x90, 0x02, 0x01, 0x12, 0x40, 0x0a, - 0x05, 0x44, 0x65, 0x62, 0x75, 0x67, 0x12, 0x1a, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, - 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, - 0x31, 0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x3a, 0x0a, 0x08, 0x53, 0x69, 0x67, 0x6e, 0x45, 0x55, 0x4c, 0x41, 0x12, 0x16, 0x2e, 0x67, 0x6f, + 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x46, + 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x90, 0x02, + 0x01, 0x12, 0x40, 0x0a, 0x05, 0x44, 0x65, 0x62, 0x75, 0x67, 0x12, 0x1a, 0x2e, 0x69, 0x6f, 0x2e, + 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, + 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x08, 0x53, 0x69, 0x67, 0x6e, 0x45, 0x55, 0x4c, 0x41, 0x12, + 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, + 0x3f, 0x0a, 0x08, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x54, 0x6f, 0x53, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3f, 0x0a, 0x08, 0x43, - 0x68, 0x65, 0x63, 0x6b, 0x54, 0x6f, 0x53, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, - 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x03, 0x90, 0x02, 0x01, 0x12, 0x48, 0x0a, 0x09, - 0x50, 0x75, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x1e, 0x2e, 0x69, 0x6f, 0x2e, 0x64, - 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x74, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x22, 0x03, 0x88, 0x02, 0x01, 0x12, 0x43, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x12, 0x15, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, - 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x1a, 0x16, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x03, 0x90, 0x02, 0x01, + 0x12, 0x48, 0x0a, 0x09, 0x50, 0x75, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x1e, 0x2e, + 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x74, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x03, 0x88, 0x02, 0x01, 0x12, 0x43, 0x0a, 0x0d, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x12, 0x15, 0x2e, 0x69, 0x6f, + 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, + 0x74, 0x73, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x03, 0x88, 0x02, 0x01, 0x12, + 0x44, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x03, 0x88, 0x02, 0x01, 0x12, 0x44, 0x0a, 0x0b, 0x4c, - 0x69, 0x73, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, + 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x15, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, + 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x22, 0x06, 0x88, + 0x02, 0x01, 0x90, 0x02, 0x01, 0x12, 0x54, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x73, 0x12, 0x1f, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, + 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, + 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x90, 0x02, 0x01, 0x12, 0x48, 0x0a, 0x09, 0x50, + 0x75, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1e, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, + 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x22, 0x03, 0x90, 0x02, 0x02, 0x12, 0x50, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x12, 0x22, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, + 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, - 0x74, 0x79, 0x1a, 0x15, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, - 0x31, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x22, 0x06, 0x88, 0x02, 0x01, 0x90, 0x02, - 0x01, 0x12, 0x54, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x12, - 0x1f, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x47, - 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x20, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, - 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x03, 0x90, 0x02, 0x01, 0x12, 0x48, 0x0a, 0x09, 0x50, 0x75, 0x74, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1e, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, - 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x03, 0x90, 0x02, - 0x02, 0x12, 0x50, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x73, 0x12, 0x22, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, - 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x03, - 0x90, 0x02, 0x02, 0x12, 0x57, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x73, 0x12, 0x20, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, - 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, - 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x90, 0x02, 0x01, 0x12, 0x52, 0x0a, 0x0f, - 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x55, 0x52, 0x4c, 0x12, - 0x1e, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x55, - 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x55, 0x52, 0x4c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1f, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x55, - 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x55, 0x52, 0x4c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x70, 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x64, - 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5a, 0x6f, 0x6e, 0x65, 0x12, 0x2a, 0x2e, 0x69, 0x6f, 0x2e, 0x64, + 0x74, 0x79, 0x22, 0x03, 0x90, 0x02, 0x02, 0x12, 0x57, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x12, 0x20, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, + 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, + 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x90, 0x02, 0x01, + 0x12, 0x52, 0x0a, 0x0f, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, + 0x55, 0x52, 0x4c, 0x12, 0x1e, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, + 0x76, 0x31, 0x2e, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x55, 0x52, 0x4c, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, + 0x76, 0x31, 0x2e, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x55, 0x52, 0x4c, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x70, 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x65, + 0x53, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5a, 0x6f, 0x6e, 0x65, 0x12, 0x2a, 0x2e, + 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, + 0x65, 0x67, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5a, 0x6f, + 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5a, 0x6f, 0x6e, 0x65, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, - 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, - 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5a, 0x6f, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x45, 0x0a, 0x13, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x75, 0x62, 0x64, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x45, 0x0a, 0x13, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x53, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5a, 0x6f, 0x6e, 0x65, 0x12, 0x16, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x64, 0x0a, + 0x18, 0x47, 0x65, 0x74, 0x44, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5a, 0x6f, 0x6e, 0x65, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x64, 0x0a, 0x18, 0x47, 0x65, 0x74, - 0x44, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, - 0x6e, 0x5a, 0x6f, 0x6e, 0x65, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x2b, 0x2e, - 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, - 0x65, 0x67, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5a, 0x6f, - 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x90, 0x02, 0x01, 0x12, - 0x43, 0x0a, 0x06, 0x57, 0x68, 0x6f, 0x41, 0x6d, 0x49, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x1a, 0x1c, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, - 0x2e, 0x57, 0x68, 0x6f, 0x41, 0x6d, 0x49, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x03, 0x90, 0x02, 0x01, 0x12, 0x3b, 0x0a, 0x05, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x12, 0x1a, 0x2e, - 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x72, 0x61, - 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x12, 0x3f, 0x0a, 0x08, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x65, 0x12, 0x16, 0x2e, + 0x79, 0x1a, 0x2b, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, + 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, + 0x69, 0x6e, 0x5a, 0x6f, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, + 0x90, 0x02, 0x01, 0x12, 0x43, 0x0a, 0x06, 0x57, 0x68, 0x6f, 0x41, 0x6d, 0x49, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x03, 0x90, - 0x02, 0x02, 0x12, 0x52, 0x0a, 0x0e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x44, 0x4e, 0x53, 0x53, - 0x65, 0x74, 0x75, 0x70, 0x12, 0x23, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, - 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x44, 0x4e, 0x53, 0x53, 0x65, 0x74, - 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, + 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x68, 0x6f, 0x41, 0x6d, 0x49, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x03, 0x90, 0x02, 0x01, 0x12, 0x3b, 0x0a, 0x05, 0x54, 0x72, 0x61, 0x63, + 0x6b, 0x12, 0x1a, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, + 0x2e, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3f, 0x0a, 0x08, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, + 0x65, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x22, 0x03, 0x90, 0x02, 0x01, 0x42, 0xb0, 0x01, 0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x2e, 0x69, - 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x42, 0x0b, 0x46, 0x61, 0x62, - 0x72, 0x69, 0x63, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x3d, 0x67, 0x69, 0x74, 0x68, - 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x44, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x4c, 0x61, 0x62, - 0x73, 0x2f, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2f, 0x73, 0x72, 0x63, 0x2f, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x73, 0x2f, 0x69, 0x6f, 0x2f, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2f, 0x76, 0x31, - 0x3b, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x49, 0x44, 0x58, 0xaa, - 0x02, 0x0c, 0x49, 0x6f, 0x2e, 0x44, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x56, 0x31, 0xca, 0x02, - 0x0c, 0x49, 0x6f, 0x5c, 0x44, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x18, - 0x49, 0x6f, 0x5c, 0x44, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, - 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x0e, 0x49, 0x6f, 0x3a, 0x3a, 0x44, - 0x65, 0x66, 0x61, 0x6e, 0x67, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x33, + 0x79, 0x22, 0x03, 0x90, 0x02, 0x02, 0x12, 0x52, 0x0a, 0x0e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, + 0x44, 0x4e, 0x53, 0x53, 0x65, 0x74, 0x75, 0x70, 0x12, 0x23, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, + 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x44, 0x4e, + 0x53, 0x53, 0x65, 0x74, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x03, 0x90, 0x02, 0x01, 0x42, 0xb0, 0x01, 0x0a, 0x10, 0x63, + 0x6f, 0x6d, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x42, + 0x0b, 0x46, 0x61, 0x62, 0x72, 0x69, 0x63, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x3d, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x44, 0x65, 0x66, 0x61, 0x6e, + 0x67, 0x4c, 0x61, 0x62, 0x73, 0x2f, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2f, 0x73, 0x72, 0x63, + 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x69, 0x6f, 0x2f, 0x64, 0x65, 0x66, 0x61, 0x6e, + 0x67, 0x2f, 0x76, 0x31, 0x3b, 0x64, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x76, 0x31, 0xa2, 0x02, 0x03, + 0x49, 0x44, 0x58, 0xaa, 0x02, 0x0c, 0x49, 0x6f, 0x2e, 0x44, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x2e, + 0x56, 0x31, 0xca, 0x02, 0x0c, 0x49, 0x6f, 0x5c, 0x44, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x5c, 0x56, + 0x31, 0xe2, 0x02, 0x18, 0x49, 0x6f, 0x5c, 0x44, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x5c, 0x56, 0x31, + 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x0e, 0x49, + 0x6f, 0x3a, 0x3a, 0x44, 0x65, 0x66, 0x61, 0x6e, 0x67, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/src/protos/io/defang/v1/fabric.proto b/src/protos/io/defang/v1/fabric.proto index 8bdbcff61..df75b8b7a 100644 --- a/src/protos/io/defang/v1/fabric.proto +++ b/src/protos/io/defang/v1/fabric.proto @@ -163,7 +163,7 @@ message DeployRequest { [ deprecated = true ]; // deprecated; use compose string project = 2 [ deprecated = true ]; // deprecated; use compose.name DeploymentMode mode = 3; - string compose = 4; // yaml (or json) + bytes compose = 4; // yaml (or json) } message DeployResponse { @@ -352,8 +352,8 @@ message ListServicesResponse { message ProjectUpdate { repeated ServiceInfo services = 1; string alb_arn = 2; - string project = 3; // should we use compose.name? - string compose = 4; + string project = 3 [deprecated = true]; // deprecated; use compose.name + bytes compose = 4; string cd_version = 5; } diff --git a/src/tests/Fancy-Proj_Dir/compose.yaml.convert b/src/tests/Fancy-Proj_Dir/compose.yaml.fixup similarity index 100% rename from src/tests/Fancy-Proj_Dir/compose.yaml.convert rename to src/tests/Fancy-Proj_Dir/compose.yaml.fixup diff --git a/src/tests/alttestproj/compose.yaml.convert b/src/tests/alttestproj/compose.yaml.fixup similarity index 50% rename from src/tests/alttestproj/compose.yaml.convert rename to src/tests/alttestproj/compose.yaml.fixup index f753ad037..9f1fdc7bc 100644 --- a/src/tests/alttestproj/compose.yaml.convert +++ b/src/tests/alttestproj/compose.yaml.fixup @@ -1,20 +1,23 @@ [ { - "name": "dfnx", - "platform": 2, - "internal": true, + "build": { + "context": ".", + "dockerfile": "Dockerfile" + }, + "command": null, "deploy": { "replicas": 1, "resources": { "reservations": { - "memory": 256 + "memory": "268435456" } - } + }, + "placement": {} }, - "build": { - "context": ".", - "dockerfile": "Dockerfile" + "entrypoint": null, + "networks": { + "default": null }, - "networks": 1 + "restart": "unless-stopped" } ] \ No newline at end of file diff --git a/src/tests/build/compose.yaml.fixup b/src/tests/build/compose.yaml.fixup new file mode 100644 index 000000000..035b697d8 --- /dev/null +++ b/src/tests/build/compose.yaml.fixup @@ -0,0 +1,34 @@ +[ + { + "build": { + "context": ".", + "dockerfile": "Dockerfile", + "args": { + "BASE_IMAGE": "nginx" + }, + "target": "test", + "shm_size": "2147483648" + }, + "command": null, + "entrypoint": null, + "networks": { + "default": null + } + }, + { + "build": { + "context": ".", + "dockerfile": "Dockerfile", + "args": { + "BASE_IMAGE": "alpine" + }, + "target": "test", + "shm_size": "2147483648" + }, + "command": null, + "entrypoint": null, + "networks": { + "default": null + } + } +] \ No newline at end of file diff --git a/src/tests/compose-go-warn/compose.yaml.convert b/src/tests/compose-go-warn/compose.yaml.convert deleted file mode 100644 index 309e0865d..000000000 --- a/src/tests/compose-go-warn/compose.yaml.convert +++ /dev/null @@ -1,16 +0,0 @@ -[ - { - "name": "echo", - "image": "ealen/echo-server", - "platform": 2, - "internal": true, - "ports": [ - { - "target": 80, - "protocol": 3, - "mode": 1 - } - ], - "networks": 1 - } -] \ No newline at end of file diff --git a/src/tests/compose-go-warn/compose.yaml.fixup b/src/tests/compose-go-warn/compose.yaml.fixup new file mode 100644 index 000000000..42ef69e4d --- /dev/null +++ b/src/tests/compose-go-warn/compose.yaml.fixup @@ -0,0 +1,18 @@ +[ + { + "command": null, + "entrypoint": null, + "image": "ealen/echo-server", + "networks": { + "default": null + }, + "ports": [ + { + "mode": "ingress", + "target": 80, + "protocol": "tcp", + "app_protocol": "http" + } + ] + } +] \ No newline at end of file diff --git a/src/tests/configoverride/compose.yaml.convert b/src/tests/configoverride/compose.yaml.convert deleted file mode 100644 index f8d0db311..000000000 --- a/src/tests/configoverride/compose.yaml.convert +++ /dev/null @@ -1,14 +0,0 @@ -[ - { - "name": "service1", - "image": "alpine", - "platform": 2, - "internal": true, - "secrets": [ - { - "source": "VAR1" - } - ], - "networks": 1 - } -] \ No newline at end of file diff --git a/src/tests/configoverride/compose.yaml.fixup b/src/tests/configoverride/compose.yaml.fixup new file mode 100644 index 000000000..10e0b25c9 --- /dev/null +++ b/src/tests/configoverride/compose.yaml.fixup @@ -0,0 +1,13 @@ +[ + { + "command": null, + "entrypoint": null, + "environment": { + "VAR1": null + }, + "image": "alpine", + "networks": { + "default": null + } + } +] \ No newline at end of file diff --git a/src/tests/debugproj/compose.yaml.convert b/src/tests/debugproj/compose.yaml.convert deleted file mode 100644 index 4297ad631..000000000 --- a/src/tests/debugproj/compose.yaml.convert +++ /dev/null @@ -1,22 +0,0 @@ -[ - { - "name": "failing", - "platform": 2, - "internal": true, - "build": { - "context": "./app", - "dockerfile": "Dockerfile" - }, - "networks": 1 - }, - { - "name": "ok", - "platform": 2, - "internal": true, - "build": { - "context": ".", - "dockerfile": "Dockerfile" - }, - "networks": 1 - } -] \ No newline at end of file diff --git a/src/tests/debugproj/compose.yaml.fixup b/src/tests/debugproj/compose.yaml.fixup new file mode 100644 index 000000000..4826d0b0e --- /dev/null +++ b/src/tests/debugproj/compose.yaml.fixup @@ -0,0 +1,24 @@ +[ + { + "build": { + "context": "./app", + "dockerfile": "Dockerfile" + }, + "command": null, + "entrypoint": null, + "networks": { + "default": null + } + }, + { + "build": { + "context": ".", + "dockerfile": "Dockerfile" + }, + "command": null, + "entrypoint": null, + "networks": { + "default": null + } + } +] \ No newline at end of file diff --git a/src/tests/empty/compose.yaml.convert b/src/tests/empty/compose.yaml.fixup similarity index 100% rename from src/tests/empty/compose.yaml.convert rename to src/tests/empty/compose.yaml.fixup diff --git a/src/tests/emptyenv/compose.yaml.convert b/src/tests/emptyenv/compose.yaml.fixup similarity index 54% rename from src/tests/emptyenv/compose.yaml.convert rename to src/tests/emptyenv/compose.yaml.fixup index 8722c56a0..edca9413d 100644 --- a/src/tests/emptyenv/compose.yaml.convert +++ b/src/tests/emptyenv/compose.yaml.fixup @@ -1,11 +1,5 @@ [ { - "name": "emptyenv", - "platform": 2, - "internal": true, - "environment": { - "ENV2": "" - }, "build": { "context": ".", "dockerfile": "Dockerfile", @@ -13,11 +7,14 @@ "ARG2": "" } }, - "secrets": [ - { - "source": "ENV1" - } - ], - "networks": 1 + "command": null, + "entrypoint": null, + "environment": { + "ENV1": null, + "ENV2": "" + }, + "networks": { + "default": null + } } ] \ No newline at end of file diff --git a/src/tests/fixupenv/compose.yaml.convert b/src/tests/fixupenv/compose.yaml.convert deleted file mode 100644 index fde7bb94b..000000000 --- a/src/tests/fixupenv/compose.yaml.convert +++ /dev/null @@ -1,77 +0,0 @@ -[ - { - "name": "bad-service", - "image": "somedb:latest", - "platform": 2, - "internal": true, - "ports": [ - { - "target": 5432, - "protocol": 3, - "mode": 1 - } - ], - "networks": 1 - }, - { - "name": "env-in-config", - "image": "service:latest", - "platform": 2, - "internal": true, - "environment": { - "CONFIG1": "http://mistral:8000" - }, - "networks": 1 - }, - { - "name": "fixup-args", - "platform": 2, - "internal": true, - "build": { - "context": ".", - "dockerfile": "Dockerfile", - "args": { - "API_URL": "http://mistral:8000" - } - }, - "networks": 1 - }, - { - "name": "mistral", - "image": "mistral:latest", - "platform": 2, - "internal": true, - "ports": [ - { - "target": 8000, - "protocol": 2 - } - ], - "networks": 1 - }, - { - "name": "ui", - "image": "ui:latest", - "platform": 2, - "internal": true, - "environment": { - "API_URL": "http://mistral:8000" - }, - "secrets": [ - { - "source": "SENSITIVE_DATA" - } - ], - "networks": 1 - }, - { - "name": "use-bad-service", - "image": "service:latest", - "platform": 2, - "internal": true, - "environment": { - "DB_URL": "bad-service:5432" - }, - "networks": 1 - } -] \ No newline at end of file diff --git a/src/tests/fixupenv/compose.yaml.fixup b/src/tests/fixupenv/compose.yaml.fixup new file mode 100644 index 000000000..0c05d7910 --- /dev/null +++ b/src/tests/fixupenv/compose.yaml.fixup @@ -0,0 +1,81 @@ +[ + { + "command": null, + "entrypoint": null, + "image": "somedb:latest", + "networks": { + "default": null + }, + "ports": [ + { + "mode": "ingress", + "target": 5432, + "protocol": "tcp", + "app_protocol": "http" + } + ] + }, + { + "command": null, + "entrypoint": null, + "environment": { + "CONFIG1": "http://mistral:8000" + }, + "image": "service:latest", + "networks": { + "default": null + } + }, + { + "build": { + "context": ".", + "dockerfile": "Dockerfile", + "args": { + "API_URL": "http://mistral:8000" + } + }, + "command": null, + "entrypoint": null, + "networks": { + "default": null + } + }, + { + "command": null, + "entrypoint": null, + "image": "mistral:latest", + "networks": { + "default": null + }, + "ports": [ + { + "mode": "host", + "target": 8000, + "protocol": "tcp" + } + ] + }, + { + "command": null, + "entrypoint": null, + "environment": { + "API_URL": "http://mistral:8000", + "SENSITIVE_DATA": null + }, + "image": "ui:latest", + "networks": { + "default": null + } + }, + { + "command": null, + "entrypoint": null, + "environment": { + "DB_URL": "bad-service:5432" + }, + "image": "service:latest", + "networks": { + "default": null + } + } +] \ No newline at end of file diff --git a/src/tests/fixupenv/compose.yaml.warnings b/src/tests/fixupenv/compose.yaml.warnings index 2b1f8ced3..c928e43f0 100644 --- a/src/tests/fixupenv/compose.yaml.warnings +++ b/src/tests/fixupenv/compose.yaml.warnings @@ -1,7 +1,7 @@ ! service "Mistral": missing memory reservation; using provider-specific defaults. Specify deploy.resources.reservations.memory to avoid out-of-memory errors ! service "bad-service": ingress port without healthcheck defaults to GET / HTTP/1.1 ! service "bad-service": missing memory reservation; using provider-specific defaults. Specify deploy.resources.reservations.memory to avoid out-of-memory errors - ! service "env-in-config": environment variable "CONFIG1" will use the 'defang config' value instead of adjusted service name + ! service "env-in-config": environment variable "CONFIG1" will use the `defang config` value instead of adjusted service name ! service "env-in-config": missing memory reservation; using provider-specific defaults. Specify deploy.resources.reservations.memory to avoid out-of-memory errors ! service "fixup-args": missing memory reservation; using provider-specific defaults. Specify deploy.resources.reservations.memory to avoid out-of-memory errors ! service "fixup-args": service name was adjusted: build argument "API_URL" assigned value "http://mock-mistral:8000" diff --git a/src/tests/healthcheck/compose.yaml b/src/tests/healthcheck/compose.yaml index a81542946..7cd78478b 100644 --- a/src/tests/healthcheck/compose.yaml +++ b/src/tests/healthcheck/compose.yaml @@ -35,3 +35,6 @@ services: - 5000 healthcheck: test: ["NONE", "ignored"] + interval: 1m + retries: 3 + timeout: 1s diff --git a/src/tests/healthcheck/compose.yaml.convert b/src/tests/healthcheck/compose.yaml.fixup similarity index 53% rename from src/tests/healthcheck/compose.yaml.convert rename to src/tests/healthcheck/compose.yaml.fixup index 95fe416f1..19269e7f6 100644 --- a/src/tests/healthcheck/compose.yaml.convert +++ b/src/tests/healthcheck/compose.yaml.fixup @@ -1,16 +1,7 @@ [ { - "name": "cmd-shell", - "image": "alpine", - "platform": 2, - "internal": true, - "ports": [ - { - "target": 5000, - "protocol": 3, - "mode": 1 - } - ], + "command": null, + "entrypoint": null, "healthcheck": { "test": [ "CMD-SHELL", @@ -18,20 +9,22 @@ "second line" ] }, - "networks": 1 - }, - { - "name": "curl", - "image": "curl", - "platform": 2, - "internal": true, + "image": "alpine", + "networks": { + "default": null + }, "ports": [ { - "target": 80, - "protocol": 3, - "mode": 1 + "mode": "ingress", + "target": 5000, + "protocol": "tcp", + "app_protocol": "http" } - ], + ] + }, + { + "command": null, + "entrypoint": null, "healthcheck": { "test": [ "CMD", @@ -40,20 +33,22 @@ "localhost" ] }, - "networks": 1 - }, - { - "name": "flask1", - "image": "flask", - "platform": 2, - "internal": true, + "image": "curl", + "networks": { + "default": null + }, "ports": [ { - "target": 5000, - "protocol": 3, - "mode": 1 + "mode": "ingress", + "target": 80, + "protocol": "tcp", + "app_protocol": "http" } - ], + ] + }, + { + "command": null, + "entrypoint": null, "healthcheck": { "test": [ "CMD", @@ -63,20 +58,22 @@ "http://localhost/" ] }, - "networks": 1 - }, - { - "name": "flask2", "image": "flask", - "platform": 2, - "internal": true, + "networks": { + "default": null + }, "ports": [ { + "mode": "ingress", "target": 5000, - "protocol": 3, - "mode": 1 + "protocol": "tcp", + "app_protocol": "http" } - ], + ] + }, + { + "command": null, + "entrypoint": null, "healthcheck": { "test": [ "CMD", @@ -85,40 +82,47 @@ "import urllib.request;urllib.request.urlopen('http://127.0.0.1/path').read()" ] }, - "networks": 1 - }, - { - "name": "none", - "image": "alpine", - "platform": 2, - "internal": true, + "image": "flask", + "networks": { + "default": null + }, "ports": [ { + "mode": "ingress", "target": 5000, - "protocol": 3, - "mode": 1 + "protocol": "tcp", + "app_protocol": "http" } - ], + ] + }, + { + "command": null, + "entrypoint": null, "healthcheck": { "test": [ "NONE", "ignored" - ] + ], + "timeout": "1s", + "interval": "1m0s", + "retries": 3 }, - "networks": 1 - }, - { - "name": "wget", "image": "alpine", - "platform": 2, - "internal": true, + "networks": { + "default": null + }, "ports": [ { - "target": 80, - "protocol": 3, - "mode": 1 + "mode": "ingress", + "target": 5000, + "protocol": "tcp", + "app_protocol": "http" } - ], + ] + }, + { + "command": null, + "entrypoint": null, "healthcheck": { "test": [ "CMD", @@ -128,6 +132,17 @@ "localhost:80" ] }, - "networks": 1 + "image": "alpine", + "networks": { + "default": null + }, + "ports": [ + { + "mode": "ingress", + "target": 80, + "protocol": "tcp", + "app_protocol": "http" + } + ] } ] \ No newline at end of file diff --git a/src/tests/healthcheck/compose.yaml.golden b/src/tests/healthcheck/compose.yaml.golden index 11b9a9bd0..cd33af965 100644 --- a/src/tests/healthcheck/compose.yaml.golden +++ b/src/tests/healthcheck/compose.yaml.golden @@ -61,6 +61,9 @@ services: test: - NONE - ignored + timeout: 1s + interval: 1m0s + retries: 3 image: alpine networks: default: null diff --git a/src/tests/interpolate/compose.yaml.fixup b/src/tests/interpolate/compose.yaml.fixup new file mode 100644 index 000000000..0dffb4739 --- /dev/null +++ b/src/tests/interpolate/compose.yaml.fixup @@ -0,0 +1,20 @@ +[ + { + "command": null, + "entrypoint": null, + "environment": { + "BRACED": "interpolate", + "DB": "postgres://user:${POSTGRES_PASSWORD}@db:5432/db", + "NAME": "interpolate", + "NODE_ENV": "${NODE_ENV}", + "NOP": "abc$def", + "NOP_BRACED": "abc${def}", + "PORT": "8080", + "interpolate": "value" + }, + "image": "alpine", + "networks": { + "default": null + } + } +] \ No newline at end of file diff --git a/src/tests/longname/compose.yaml.convert b/src/tests/longname/compose.yaml.convert deleted file mode 100644 index d5f8d7759..000000000 --- a/src/tests/longname/compose.yaml.convert +++ /dev/null @@ -1,9 +0,0 @@ -[ - { - "name": "averylongservicenamethatisdefinitelytoolongthatwillcauseanerror", - "image": "alpine", - "platform": 2, - "internal": true, - "networks": 1 - } -] \ No newline at end of file diff --git a/src/tests/longname/compose.yaml.fixup b/src/tests/longname/compose.yaml.fixup new file mode 100644 index 000000000..0492b12c1 --- /dev/null +++ b/src/tests/longname/compose.yaml.fixup @@ -0,0 +1,10 @@ +[ + { + "command": null, + "entrypoint": null, + "image": "alpine", + "networks": { + "default": null + } + } +] \ No newline at end of file diff --git a/src/tests/networks/compose.yaml.convert b/src/tests/networks/compose.yaml.convert deleted file mode 100644 index cc40c8377..000000000 --- a/src/tests/networks/compose.yaml.convert +++ /dev/null @@ -1,28 +0,0 @@ -[ - { - "name": "service1", - "image": "example", - "platform": 2, - "internal": true, - "networks": 1 - }, - { - "name": "service2", - "image": "example", - "platform": 2, - "networks": 2 - }, - { - "name": "service3", - "image": "example", - "platform": 2, - "networks": 2 - }, - { - "name": "service4", - "image": "example", - "platform": 2, - "internal": true, - "networks": 1 - } -] \ No newline at end of file diff --git a/src/tests/networks/compose.yaml.fixup b/src/tests/networks/compose.yaml.fixup new file mode 100644 index 000000000..3d5e6ba6e --- /dev/null +++ b/src/tests/networks/compose.yaml.fixup @@ -0,0 +1,34 @@ +[ + { + "command": null, + "entrypoint": null, + "image": "example", + "networks": { + "invalid-network-name": {} + } + }, + { + "command": null, + "entrypoint": null, + "image": "example", + "networks": { + "public": {} + } + }, + { + "command": null, + "entrypoint": null, + "image": "example", + "networks": { + "public": null + } + }, + { + "command": null, + "entrypoint": null, + "image": "example", + "networks": { + "private": null + } + } +] \ No newline at end of file diff --git a/src/tests/noprojname/compose.yaml.convert b/src/tests/noprojname/compose.yaml.fixup similarity index 100% rename from src/tests/noprojname/compose.yaml.convert rename to src/tests/noprojname/compose.yaml.fixup diff --git a/src/tests/platforms/compose.yaml.convert b/src/tests/platforms/compose.yaml.convert deleted file mode 100644 index dbd848e95..000000000 --- a/src/tests/platforms/compose.yaml.convert +++ /dev/null @@ -1,8 +0,0 @@ -[ - { - "name": "intel2", - "image": "busybox", - "internal": true, - "networks": 1 - } -] \ No newline at end of file diff --git a/src/tests/platforms/compose.yaml.fixup b/src/tests/platforms/compose.yaml.fixup new file mode 100644 index 000000000..3bf2df17a --- /dev/null +++ b/src/tests/platforms/compose.yaml.fixup @@ -0,0 +1,11 @@ +[ + { + "command": null, + "entrypoint": null, + "image": "busybox", + "networks": { + "default": null + }, + "platform": "LINUX/X86_64" + } +] \ No newline at end of file diff --git a/src/tests/ports/compose.yaml.fixup b/src/tests/ports/compose.yaml.fixup new file mode 100644 index 000000000..848efaec4 --- /dev/null +++ b/src/tests/ports/compose.yaml.fixup @@ -0,0 +1,99 @@ +[ + { + "command": null, + "entrypoint": null, + "image": "bogus", + "networks": { + "default": null + }, + "ports": [ + { + "mode": "ingress", + "target": 82, + "protocol": "tcp", + "app_protocol": "http" + } + ] + }, + { + "command": null, + "entrypoint": null, + "image": "bogus", + "networks": { + "default": null + }, + "ports": [ + { + "mode": "ingress", + "target": 83, + "published": "8083", + "protocol": "tcp", + "app_protocol": "http" + } + ] + }, + { + "command": null, + "entrypoint": null, + "image": "bogus", + "networks": { + "default": null + }, + "ports": [ + { + "mode": "ingress", + "target": 80, + "protocol": "tcp", + "app_protocol": "http" + } + ] + }, + { + "command": null, + "entrypoint": null, + "image": "bogus", + "networks": { + "default": null + }, + "ports": [ + { + "mode": "ingress", + "target": 81, + "published": "8081", + "protocol": "tcp", + "app_protocol": "http" + } + ] + }, + { + "command": null, + "entrypoint": null, + "image": "bogus", + "networks": { + "default": null + }, + "ports": [ + { + "mode": "host", + "target": 84, + "protocol": "udp" + } + ] + }, + { + "command": null, + "entrypoint": null, + "image": "bogus", + "networks": { + "default": null + }, + "ports": [ + { + "mode": "host", + "target": 85, + "published": "8085", + "protocol": "udp" + } + ] + } +] \ No newline at end of file diff --git a/src/tests/postgres/compose.yaml.convert b/src/tests/postgres/compose.yaml.convert deleted file mode 100644 index a6cb845d4..000000000 --- a/src/tests/postgres/compose.yaml.convert +++ /dev/null @@ -1,25 +0,0 @@ -[ - { - "name": "x", - "image": "postgres", - "platform": 2, - "internal": true, - "networks": 1, - "postgres": {} - }, - { - "name": "y", - "image": "example", - "platform": 2, - "internal": true, - "networks": 1, - "postgres": {} - }, - { - "name": "z", - "image": "postgres", - "platform": 2, - "internal": true, - "networks": 1 - } -] \ No newline at end of file diff --git a/src/tests/postgres/compose.yaml.fixup b/src/tests/postgres/compose.yaml.fixup new file mode 100644 index 000000000..4667b57be --- /dev/null +++ b/src/tests/postgres/compose.yaml.fixup @@ -0,0 +1,26 @@ +[ + { + "command": null, + "entrypoint": null, + "image": "postgres", + "networks": { + "default": null + } + }, + { + "command": null, + "entrypoint": null, + "image": "example", + "networks": { + "default": null + } + }, + { + "command": null, + "entrypoint": null, + "image": "postgres", + "networks": { + "default": null + } + } +] \ No newline at end of file diff --git a/src/tests/profiles/compose.yaml.convert b/src/tests/profiles/compose.yaml.convert deleted file mode 100644 index 6fc3031b1..000000000 --- a/src/tests/profiles/compose.yaml.convert +++ /dev/null @@ -1,16 +0,0 @@ -[ - { - "name": "always", - "image": "alpine", - "platform": 2, - "internal": true, - "networks": 1 - }, - { - "name": "defangonly", - "image": "alpine", - "platform": 2, - "internal": true, - "networks": 1 - } -] \ No newline at end of file diff --git a/src/tests/profiles/compose.yaml.fixup b/src/tests/profiles/compose.yaml.fixup new file mode 100644 index 000000000..3eb57e9cc --- /dev/null +++ b/src/tests/profiles/compose.yaml.fixup @@ -0,0 +1,21 @@ +[ + { + "command": null, + "entrypoint": null, + "image": "alpine", + "networks": { + "default": null + } + }, + { + "profiles": [ + "defang" + ], + "command": null, + "entrypoint": null, + "image": "alpine", + "networks": { + "default": null + } + } +] \ No newline at end of file diff --git a/src/tests/redis/compose.yaml.convert b/src/tests/redis/compose.yaml.convert deleted file mode 100644 index c2f551bbf..000000000 --- a/src/tests/redis/compose.yaml.convert +++ /dev/null @@ -1,25 +0,0 @@ -[ - { - "name": "x", - "image": "redis", - "platform": 2, - "internal": true, - "networks": 1, - "redis": {} - }, - { - "name": "y", - "image": "example", - "platform": 2, - "internal": true, - "networks": 1, - "redis": {} - }, - { - "name": "z", - "image": "redis", - "platform": 2, - "internal": true, - "networks": 1 - } -] \ No newline at end of file diff --git a/src/tests/redis/compose.yaml.fixup b/src/tests/redis/compose.yaml.fixup new file mode 100644 index 000000000..0dd32f415 --- /dev/null +++ b/src/tests/redis/compose.yaml.fixup @@ -0,0 +1,26 @@ +[ + { + "command": null, + "entrypoint": null, + "image": "redis", + "networks": { + "default": null + } + }, + { + "command": null, + "entrypoint": null, + "image": "example", + "networks": { + "default": null + } + }, + { + "command": null, + "entrypoint": null, + "image": "redis", + "networks": { + "default": null + } + } +] \ No newline at end of file diff --git a/src/tests/sanity/compose.yaml.convert b/src/tests/sanity/compose.yaml.convert deleted file mode 100644 index f10f2f1e4..000000000 --- a/src/tests/sanity/compose.yaml.convert +++ /dev/null @@ -1,29 +0,0 @@ -[ - { - "name": "nginx", - "image": "nginx", - "platform": 2, - "internal": true, - "deploy": { - "replicas": 1, - "resources": { - "reservations": { - "memory": 256 - } - } - }, - "ports": [ - { - "target": 80, - "protocol": 3, - "mode": 1 - } - ], - "secrets": [ - { - "source": "dummy" - } - ], - "networks": 1 - } -] \ No newline at end of file diff --git a/src/tests/sanity/compose.yaml.fixup b/src/tests/sanity/compose.yaml.fixup new file mode 100644 index 000000000..aada04531 --- /dev/null +++ b/src/tests/sanity/compose.yaml.fixup @@ -0,0 +1,31 @@ +[ + { + "command": null, + "deploy": { + "replicas": 1, + "resources": { + "reservations": { + "memory": "268435456" + } + }, + "placement": {} + }, + "entrypoint": null, + "environment": { + "dummy": null + }, + "image": "nginx", + "networks": { + "default": null + }, + "ports": [ + { + "mode": "ingress", + "target": 80, + "protocol": "tcp", + "app_protocol": "http" + } + ], + "restart": "unless-stopped" + } +] \ No newline at end of file diff --git a/src/tests/secretname/compose.yaml.convert b/src/tests/secretname/compose.yaml.convert deleted file mode 100644 index 2797bfd64..000000000 --- a/src/tests/secretname/compose.yaml.convert +++ /dev/null @@ -1,17 +0,0 @@ -[ - { - "name": "app", - "platform": 2, - "internal": true, - "build": { - "context": ".", - "dockerfile": "Dockerfile" - }, - "secrets": [ - { - "source": "dummy" - } - ], - "networks": 1 - } -] \ No newline at end of file diff --git a/src/tests/secretname/compose.yaml.fixup b/src/tests/secretname/compose.yaml.fixup new file mode 100644 index 000000000..3abe543bd --- /dev/null +++ b/src/tests/secretname/compose.yaml.fixup @@ -0,0 +1,17 @@ +[ + { + "build": { + "context": ".", + "dockerfile": "Dockerfile" + }, + "command": null, + "entrypoint": null, + "environment": { + "dummy": null + }, + "networks": { + "default": null + }, + "restart": "unless-stopped" + } +] \ No newline at end of file diff --git a/src/tests/static-files/compose.yaml.convert b/src/tests/static-files/compose.yaml.convert deleted file mode 100644 index fe38deee1..000000000 --- a/src/tests/static-files/compose.yaml.convert +++ /dev/null @@ -1,22 +0,0 @@ -[ - { - "name": "x", - "image": "blah", - "platform": 2, - "internal": true, - "static_files": { - "folder": "./folder" - }, - "networks": 1 - }, - { - "name": "y", - "image": "blah", - "platform": 2, - "internal": true, - "static_files": { - "folder": "./folder" - }, - "networks": 1 - } -] \ No newline at end of file diff --git a/src/tests/static-files/compose.yaml.fixup b/src/tests/static-files/compose.yaml.fixup new file mode 100644 index 000000000..d154cf7bb --- /dev/null +++ b/src/tests/static-files/compose.yaml.fixup @@ -0,0 +1,18 @@ +[ + { + "command": null, + "entrypoint": null, + "image": "blah", + "networks": { + "default": null + } + }, + { + "command": null, + "entrypoint": null, + "image": "blah", + "networks": { + "default": null + } + } +] \ No newline at end of file diff --git a/src/tests/testproj/.dockerignore b/src/tests/testproj/.dockerignore index 6f36fac4f..8b62d039e 100644 --- a/src/tests/testproj/.dockerignore +++ b/src/tests/testproj/.dockerignore @@ -23,3 +23,4 @@ Dockerfile **/compose.yaml.golden **/compose.yaml.warnings **/compose.yaml.convert +**/compose.yaml.fixup diff --git a/src/tests/testproj/compose.yaml.convert b/src/tests/testproj/compose.yaml.fixup similarity index 52% rename from src/tests/testproj/compose.yaml.convert rename to src/tests/testproj/compose.yaml.fixup index e416d9590..d86c6747f 100644 --- a/src/tests/testproj/compose.yaml.convert +++ b/src/tests/testproj/compose.yaml.fixup @@ -1,51 +1,35 @@ [ { - "name": "dfnx", - "platform": 2, - "internal": true, + "build": { + "context": ".", + "dockerfile": "Dockerfile", + "args": { + "DNS": "dfnx" + }, + "target": "testproj" + }, + "command": null, "deploy": { "replicas": 1, "resources": { + "limits": { + "cpus": 0.5, + "memory": "536870912" + }, "reservations": { - "memory": 256, - "cpus": 0.25 + "cpus": 0.25, + "memory": "268435456" } - } - }, - "ports": [ - { - "target": 80, - "protocol": 3, - "mode": 1 - }, - { - "target": 1234, - "protocol": 3, - "mode": 1 }, - { - "target": 4567, - "protocol": 1 - } - ], + "placement": {} + }, + "entrypoint": null, "environment": { "DOTENV": "enabled", "DOT_ENV_INTERPOLATION": "enabled", - "FOO": "bar" - }, - "build": { - "context": ".", - "dockerfile": "Dockerfile", - "args": { - "DNS": "dfnx" - }, - "target": "testproj" + "FOO": "bar", + "dummy": null }, - "secrets": [ - { - "source": "dummy" - } - ], "healthcheck": { "test": [ "CMD", @@ -54,6 +38,28 @@ "http://localhost/" ] }, - "networks": 1 + "networks": { + "default": null + }, + "ports": [ + { + "mode": "ingress", + "target": 80, + "protocol": "tcp", + "app_protocol": "http" + }, + { + "mode": "ingress", + "target": 1234, + "protocol": "tcp", + "app_protocol": "http" + }, + { + "mode": "host", + "target": 4567, + "protocol": "udp" + } + ], + "restart": "unless-stopped" } ] \ No newline at end of file diff --git a/src/tests/toomany/compose.yaml.convert b/src/tests/toomany/compose.yaml.convert deleted file mode 100644 index 6ec32af99..000000000 --- a/src/tests/toomany/compose.yaml.convert +++ /dev/null @@ -1,9 +0,0 @@ -[ - { - "name": "service1", - "image": "example", - "platform": 2, - "internal": true, - "networks": 1 - } -] \ No newline at end of file diff --git a/src/tests/toomany/compose.yaml.fixup b/src/tests/toomany/compose.yaml.fixup new file mode 100644 index 000000000..64ec97d57 --- /dev/null +++ b/src/tests/toomany/compose.yaml.fixup @@ -0,0 +1,10 @@ +[ + { + "command": null, + "entrypoint": null, + "image": "example", + "networks": { + "default": null + } + } +] \ No newline at end of file diff --git a/src/tests/toomany/docker-compose.yml.convert b/src/tests/toomany/docker-compose.yml.convert deleted file mode 100644 index 6ec32af99..000000000 --- a/src/tests/toomany/docker-compose.yml.convert +++ /dev/null @@ -1,9 +0,0 @@ -[ - { - "name": "service1", - "image": "example", - "platform": 2, - "internal": true, - "networks": 1 - } -] \ No newline at end of file diff --git a/src/tests/toomany/docker-compose.yml.fixup b/src/tests/toomany/docker-compose.yml.fixup new file mode 100644 index 000000000..64ec97d57 --- /dev/null +++ b/src/tests/toomany/docker-compose.yml.fixup @@ -0,0 +1,10 @@ +[ + { + "command": null, + "entrypoint": null, + "image": "example", + "networks": { + "default": null + } + } +] \ No newline at end of file