From 617f4cbd0f8541d11c3091e2fdaf493e1b7f109d Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Fri, 15 Nov 2024 11:00:52 +1100 Subject: [PATCH] feat: hot reload JVM languages can now used how reload for instant feedback --- backend/controller/controller.go | 1 + .../scaling/localscaling/local_scaling.go | 61 ++- backend/controller/scaling/scaling.go | 5 + .../xyz/block/ftl/v1/language/language.pb.go | 415 ++++++++++-------- .../xyz/block/ftl/v1/language/language.proto | 29 +- .../xyz/block/ftl/v1/language/mixins.go | 6 +- backend/runner/runner.go | 143 +++--- common/plugin/serve.go | 3 +- common/plugin/spawn.go | 2 +- .../time/src/main/java/ftl/time/Time.java | 11 - frontend/cli/cmd_box_run.go | 3 +- frontend/cli/cmd_dev.go | 6 +- frontend/cli/cmd_new.go | 2 +- frontend/cli/cmd_serve.go | 15 +- .../xyz/block/ftl/v1/language/language_pb.ts | 69 ++- go-runtime/goplugin/service_test.go | 5 +- internal/buildengine/build.go | 16 +- internal/buildengine/engine.go | 18 +- internal/buildengine/languageplugin/plugin.go | 17 +- .../languageplugin/plugin_client.go | 11 +- .../buildengine/languageplugin/plugin_test.go | 2 + internal/moduleconfig/moduleconfig.go | 6 + internal/moduleconfig/moduleconfig_test.go | 5 + .../ftl-runtime/common/deployment/pom.xml | 4 + .../ftl-runtime/common/runtime/pom.xml | 4 + jvm-runtime/plugin/common/java_plugin_test.go | 4 +- jvm-runtime/plugin/common/jvmcommon.go | 174 +++++++- .../xyz/block/ftl/v1/language/language_pb2.py | 116 ++--- .../block/ftl/v1/language/language_pb2.pyi | 20 +- 29 files changed, 766 insertions(+), 407 deletions(-) diff --git a/backend/controller/controller.go b/backend/controller/controller.go index f0ee82c767..e84e0cc28d 100644 --- a/backend/controller/controller.go +++ b/backend/controller/controller.go @@ -1092,6 +1092,7 @@ func (s *Service) CreateDeployment(ctx context.Context, req *connect.Request[ftl ingressRoutes := extractIngressRoutingEntries(req.Msg) dkey, err := s.dal.CreateDeployment(ctx, ms.Runtime.Language, module, artefacts, ingressRoutes) + logger.Infof("Created deployment %s", dkey) if err != nil { logger.Errorf(err, "Could not create deployment") return nil, fmt.Errorf("could not create deployment: %w", err) diff --git a/backend/controller/scaling/localscaling/local_scaling.go b/backend/controller/scaling/localscaling/local_scaling.go index 6cee81b958..d03b3b8c38 100644 --- a/backend/controller/scaling/localscaling/local_scaling.go +++ b/backend/controller/scaling/localscaling/local_scaling.go @@ -38,11 +38,32 @@ type localScaling struct { portAllocator *bind.BindAllocator controllerAddresses []*url.URL - prevRunnerSuffix int - ideSupport optional.Option[localdebug.IDEIntegration] + prevRunnerSuffix int + ideSupport optional.Option[localdebug.IDEIntegration] + devModeEndpointsUpdates <-chan scaling.DevModeEndpoints + devModeEndpoints map[string]*devModeRunner +} + +type devModeRunner struct { + uri url.URL + deploymentKey string } func (l *localScaling) Start(ctx context.Context, endpoint url.URL, leaser leases.Leaser) error { + go func() { + for { + select { + case <-ctx.Done(): + return + case devEndpoints := <-l.devModeEndpointsUpdates: + l.lock.Lock() + l.devModeEndpoints[devEndpoints.Module] = &devModeRunner{ + uri: devEndpoints.Endpoint, + } + l.lock.Unlock() + } + } + }() scaling.BeginGrpcScaling(ctx, endpoint, leaser, l.handleSchemaChange) return nil } @@ -80,20 +101,22 @@ type runnerInfo struct { port string } -func NewLocalScaling(portAllocator *bind.BindAllocator, controllerAddresses []*url.URL, configPath string, enableIDEIntegration bool) (scaling.RunnerScaling, error) { +func NewLocalScaling(portAllocator *bind.BindAllocator, controllerAddresses []*url.URL, configPath string, enableIDEIntegration bool, devModeEndpoints <-chan scaling.DevModeEndpoints) (scaling.RunnerScaling, error) { cacheDir, err := os.UserCacheDir() if err != nil { return nil, err } local := localScaling{ - lock: sync.Mutex{}, - cacheDir: cacheDir, - runners: map[string]map[string]*deploymentInfo{}, - portAllocator: portAllocator, - controllerAddresses: controllerAddresses, - prevRunnerSuffix: -1, - debugPorts: map[string]*localdebug.DebugInfo{}, + lock: sync.Mutex{}, + cacheDir: cacheDir, + runners: map[string]map[string]*deploymentInfo{}, + portAllocator: portAllocator, + controllerAddresses: controllerAddresses, + prevRunnerSuffix: -1, + debugPorts: map[string]*localdebug.DebugInfo{}, + devModeEndpointsUpdates: devModeEndpoints, + devModeEndpoints: map[string]*devModeRunner{}, } if enableIDEIntegration && configPath != "" { local.ideSupport = optional.Ptr(localdebug.NewIDEIntegration(configPath)) @@ -165,6 +188,17 @@ func (l *localScaling) startRunner(ctx context.Context, deploymentKey string, in return nil default: } + + devEndpoint := l.devModeEndpoints[info.module] + devUri := optional.None[url.URL]() + if devEndpoint != nil { + devUri = optional.Some(devEndpoint.uri) + if devEndpoint.deploymentKey == deploymentKey { + // Already running, don't start another + return nil + } + devEndpoint.deploymentKey = deploymentKey + } controllerEndpoint := l.controllerAddresses[len(l.runners)%len(l.controllerAddresses)] logger := log.FromContext(ctx) @@ -197,6 +231,7 @@ func (l *localScaling) startRunner(ctx context.Context, deploymentKey string, in Key: model.NewLocalRunnerKey(keySuffix), Deployment: deploymentKey, DebugPort: debugPort, + DevEndpoint: devUri, } simpleName := fmt.Sprintf("runner%d", keySuffix) @@ -219,10 +254,12 @@ func (l *localScaling) startRunner(ctx context.Context, deploymentKey string, in err := runner.Start(runnerCtx, config) l.lock.Lock() defer l.lock.Unlock() + if devEndpoint != nil { + devEndpoint.deploymentKey = "" + } + // Don't count context.Canceled as an a restart error if err != nil && !errors.Is(err, context.Canceled) { logger.Errorf(err, "Runner failed: %s", err) - } else { - // Don't count context.Canceled as an a restart error info.exits++ } if info.exits >= maxExits { diff --git a/backend/controller/scaling/scaling.go b/backend/controller/scaling/scaling.go index 49d69653a7..94fa463c10 100644 --- a/backend/controller/scaling/scaling.go +++ b/backend/controller/scaling/scaling.go @@ -65,3 +65,8 @@ func runGrpcScaling(ctx context.Context, url url.URL, handler func(ctx context.C rpc.RetryStreamingServerStream(ctx, backoff.Backoff{Max: time.Second}, &ftlv1.PullSchemaRequest{}, client.PullSchema, handler, rpc.AlwaysRetry()) logger.Debugf("Stopped Runner Scaling") } + +type DevModeEndpoints struct { + Module string + Endpoint url.URL +} diff --git a/backend/protos/xyz/block/ftl/v1/language/language.pb.go b/backend/protos/xyz/block/ftl/v1/language/language.pb.go index 01e1ae5101..a1ed42bbda 100644 --- a/backend/protos/xyz/block/ftl/v1/language/language.pb.go +++ b/backend/protos/xyz/block/ftl/v1/language/language.pb.go @@ -137,15 +137,17 @@ type ModuleConfig struct { DeployDir string `protobuf:"bytes,4,opt,name=deploy_dir,json=deployDir,proto3" json:"deploy_dir,omitempty"` // Build is the command to build the module. Build *string `protobuf:"bytes,5,opt,name=build,proto3,oneof" json:"build,omitempty"` + // DevModeBuild is the command to build the module in dev mode. + DevModeBuild *string `protobuf:"bytes,6,opt,name=devModeBuild,proto3,oneof" json:"devModeBuild,omitempty"` // Build lock path to prevent concurrent builds - BuildLock string `protobuf:"bytes,6,opt,name=build_lock,json=buildLock,proto3" json:"build_lock,omitempty"` + BuildLock string `protobuf:"bytes,7,opt,name=build_lock,json=buildLock,proto3" json:"build_lock,omitempty"` // The directory to generate protobuf schema files into. These can be picked up by language specific build tools - GeneratedSchemaDir *string `protobuf:"bytes,7,opt,name=generated_schema_dir,json=generatedSchemaDir,proto3,oneof" json:"generated_schema_dir,omitempty"` + GeneratedSchemaDir *string `protobuf:"bytes,8,opt,name=generated_schema_dir,json=generatedSchemaDir,proto3,oneof" json:"generated_schema_dir,omitempty"` // Patterns to watch for file changes - Watch []string `protobuf:"bytes,8,rep,name=watch,proto3" json:"watch,omitempty"` + Watch []string `protobuf:"bytes,9,rep,name=watch,proto3" json:"watch,omitempty"` // LanguageConfig contains any metadata specific to a specific language. // These are stored in the ftl.toml file under the same key as the language (eg: "go", "java") - LanguageConfig *structpb.Struct `protobuf:"bytes,9,opt,name=language_config,json=languageConfig,proto3" json:"language_config,omitempty"` + LanguageConfig *structpb.Struct `protobuf:"bytes,10,opt,name=language_config,json=languageConfig,proto3" json:"language_config,omitempty"` } func (x *ModuleConfig) Reset() { @@ -213,6 +215,13 @@ func (x *ModuleConfig) GetBuild() string { return "" } +func (x *ModuleConfig) GetDevModeBuild() string { + if x != nil && x.DevModeBuild != nil { + return *x.DevModeBuild + } + return "" +} + func (x *ModuleConfig) GetBuildLock() string { if x != nil { return x.BuildLock @@ -573,15 +582,17 @@ type ModuleConfigDefaultsResponse struct { DeployDir string `protobuf:"bytes,1,opt,name=deploy_dir,json=deployDir,proto3" json:"deploy_dir,omitempty"` // Default build command Build *string `protobuf:"bytes,2,opt,name=build,proto3,oneof" json:"build,omitempty"` + // Dev mode build command, if different from the regular build command + DevModeBuild *string `protobuf:"bytes,3,opt,name=devModeBuild,proto3,oneof" json:"devModeBuild,omitempty"` // Build lock path to prevent concurrent builds - BuildLock *string `protobuf:"bytes,3,opt,name=build_lock,json=buildLock,proto3,oneof" json:"build_lock,omitempty"` + BuildLock *string `protobuf:"bytes,4,opt,name=build_lock,json=buildLock,proto3,oneof" json:"build_lock,omitempty"` // Default relative path to the directory containing generated schema files - GeneratedSchemaDir *string `protobuf:"bytes,4,opt,name=generated_schema_dir,json=generatedSchemaDir,proto3,oneof" json:"generated_schema_dir,omitempty"` + GeneratedSchemaDir *string `protobuf:"bytes,5,opt,name=generated_schema_dir,json=generatedSchemaDir,proto3,oneof" json:"generated_schema_dir,omitempty"` // Default patterns to watch for file changes, relative to the module directory - Watch []string `protobuf:"bytes,5,rep,name=watch,proto3" json:"watch,omitempty"` + Watch []string `protobuf:"bytes,6,rep,name=watch,proto3" json:"watch,omitempty"` // Default language specific configuration. // These defaults are filled in by looking at each root key only. If the key is not present, the default is used. - LanguageConfig *structpb.Struct `protobuf:"bytes,6,opt,name=language_config,json=languageConfig,proto3" json:"language_config,omitempty"` + LanguageConfig *structpb.Struct `protobuf:"bytes,7,opt,name=language_config,json=languageConfig,proto3" json:"language_config,omitempty"` } func (x *ModuleConfigDefaultsResponse) Reset() { @@ -628,6 +639,13 @@ func (x *ModuleConfigDefaultsResponse) GetBuild() string { return "" } +func (x *ModuleConfigDefaultsResponse) GetDevModeBuild() string { + if x != nil && x.DevModeBuild != nil { + return *x.DevModeBuild + } + return "" +} + func (x *ModuleConfigDefaultsResponse) GetBuildLock() string { if x != nil && x.BuildLock != nil { return *x.BuildLock @@ -1237,10 +1255,15 @@ type BuildSuccess struct { Module *schema.Module `protobuf:"bytes,3,opt,name=module,proto3" json:"module,omitempty"` // Paths for files/directories to be deployed Deploy []string `protobuf:"bytes,4,rep,name=deploy,proto3" json:"deploy,omitempty"` + // Name of the docker image to use for the runner + DockerImage string `protobuf:"bytes,5,opt,name=docker_image,json=dockerImage,proto3" json:"docker_image,omitempty"` // Errors contains any errors that occurred during the build // No errors can have a level of ERROR, instead a BuildFailure should be sent // Instead this is useful for INFO and WARN level errors. - Errors *ErrorList `protobuf:"bytes,5,opt,name=errors,proto3" json:"errors,omitempty"` + Errors *ErrorList `protobuf:"bytes,6,opt,name=errors,proto3" json:"errors,omitempty"` + // Dev mode endpoint URI. If this is set then rather than trying to deploy the module, FTL will start a runner that + // connects to this endpoint. + DevEndpoint *string `protobuf:"bytes,7,opt,name=dev_endpoint,json=devEndpoint,proto3,oneof" json:"dev_endpoint,omitempty"` } func (x *BuildSuccess) Reset() { @@ -1301,6 +1324,13 @@ func (x *BuildSuccess) GetDeploy() []string { return nil } +func (x *BuildSuccess) GetDockerImage() string { + if x != nil { + return x.DockerImage + } + return "" +} + func (x *BuildSuccess) GetErrors() *ErrorList { if x != nil { return x.Errors @@ -1308,6 +1338,13 @@ func (x *BuildSuccess) GetErrors() *ErrorList { return nil } +func (x *BuildSuccess) GetDevEndpoint() string { + if x != nil && x.DevEndpoint != nil { + return *x.DevEndpoint + } + return "" +} + // BuildFailure should be sent when a build fails. // // FTL may ignore this event if it does not match FTL's current build context and state. @@ -1789,7 +1826,7 @@ var file_xyz_block_ftl_v1_language_language_proto_rawDesc = []byte{ 0x74, 0x6c, 0x2f, 0x76, 0x31, 0x2f, 0x66, 0x74, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x24, 0x78, 0x79, 0x7a, 0x2f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2f, 0x66, 0x74, 0x6c, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xdb, 0x02, 0x0a, 0x0c, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x95, 0x03, 0x0a, 0x0c, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x64, 0x69, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x64, 0x69, 0x72, 0x12, 0x1a, 0x0a, 0x08, @@ -1798,183 +1835,196 @@ var file_xyz_block_ftl_v1_language_language_proto_rawDesc = []byte{ 0x6f, 0x79, 0x5f, 0x64, 0x69, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x44, 0x69, 0x72, 0x12, 0x19, 0x0a, 0x05, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x05, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x88, - 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x4c, 0x6f, 0x63, - 0x6b, 0x12, 0x35, 0x0a, 0x14, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x73, - 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x64, 0x69, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x48, - 0x01, 0x52, 0x12, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x53, 0x63, 0x68, 0x65, - 0x6d, 0x61, 0x44, 0x69, 0x72, 0x88, 0x01, 0x01, 0x12, 0x14, 0x0a, 0x05, 0x77, 0x61, 0x74, 0x63, - 0x68, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x77, 0x61, 0x74, 0x63, 0x68, 0x12, 0x40, - 0x0a, 0x0f, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, - 0x52, 0x0e, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x42, 0x17, 0x0a, 0x15, 0x5f, 0x67, - 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, - 0x64, 0x69, 0x72, 0x22, 0x64, 0x0a, 0x0d, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x64, 0x69, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x03, 0x64, 0x69, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x6e, 0x6f, - 0x5f, 0x67, 0x69, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x6e, 0x6f, 0x47, 0x69, - 0x74, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, 0x72, 0x6d, 0x69, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x06, 0x68, 0x65, 0x72, 0x6d, 0x69, 0x74, 0x22, 0x39, 0x0a, 0x1b, 0x47, 0x65, 0x74, - 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x46, 0x6c, 0x61, 0x67, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x61, 0x6e, 0x67, - 0x75, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x61, 0x6e, 0x67, - 0x75, 0x61, 0x67, 0x65, 0x22, 0xcf, 0x02, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x18, 0x01, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3c, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, + 0x01, 0x01, 0x12, 0x27, 0x0a, 0x0c, 0x64, 0x65, 0x76, 0x4d, 0x6f, 0x64, 0x65, 0x42, 0x75, 0x69, + 0x6c, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x0c, 0x64, 0x65, 0x76, 0x4d, + 0x6f, 0x64, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x0a, 0x62, + 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x09, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x4c, 0x6f, 0x63, 0x6b, 0x12, 0x35, 0x0a, 0x14, 0x67, 0x65, + 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x64, + 0x69, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x12, 0x67, 0x65, 0x6e, 0x65, + 0x72, 0x61, 0x74, 0x65, 0x64, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x44, 0x69, 0x72, 0x88, 0x01, + 0x01, 0x12, 0x14, 0x0a, 0x05, 0x77, 0x61, 0x74, 0x63, 0x68, 0x18, 0x09, 0x20, 0x03, 0x28, 0x09, + 0x52, 0x05, 0x77, 0x61, 0x74, 0x63, 0x68, 0x12, 0x40, 0x0a, 0x0f, 0x6c, 0x61, 0x6e, 0x67, 0x75, + 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x0e, 0x6c, 0x61, 0x6e, 0x67, 0x75, + 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x62, 0x75, + 0x69, 0x6c, 0x64, 0x42, 0x0f, 0x0a, 0x0d, 0x5f, 0x64, 0x65, 0x76, 0x4d, 0x6f, 0x64, 0x65, 0x42, + 0x75, 0x69, 0x6c, 0x64, 0x42, 0x17, 0x0a, 0x15, 0x5f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, + 0x65, 0x64, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x64, 0x69, 0x72, 0x22, 0x64, 0x0a, + 0x0d, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x10, + 0x0a, 0x03, 0x64, 0x69, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x64, 0x69, 0x72, + 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x6e, 0x6f, 0x5f, 0x67, 0x69, 0x74, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x6e, 0x6f, 0x47, 0x69, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x68, + 0x65, 0x72, 0x6d, 0x69, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x68, 0x65, 0x72, + 0x6d, 0x69, 0x74, 0x22, 0x39, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x22, 0xcf, + 0x02, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4d, 0x6f, 0x64, 0x75, + 0x6c, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x52, 0x0a, 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3c, + 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, + 0x31, 0x2e, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x46, 0x6c, 0x61, 0x67, 0x52, 0x05, 0x66, 0x6c, + 0x61, 0x67, 0x73, 0x1a, 0xda, 0x01, 0x0a, 0x04, 0x46, 0x6c, 0x61, 0x67, 0x12, 0x12, 0x0a, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x12, 0x12, 0x0a, 0x04, 0x68, 0x65, 0x6c, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x68, 0x65, 0x6c, 0x70, 0x12, 0x19, 0x0a, 0x05, 0x65, 0x6e, 0x76, 0x61, 0x72, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x05, 0x65, 0x6e, 0x76, 0x61, 0x72, 0x88, 0x01, 0x01, 0x12, + 0x19, 0x0a, 0x05, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, + 0x52, 0x05, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x88, 0x01, 0x01, 0x12, 0x25, 0x0a, 0x0b, 0x70, 0x6c, + 0x61, 0x63, 0x65, 0x68, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, + 0x02, 0x52, 0x0b, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x68, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x88, 0x01, + 0x01, 0x12, 0x1d, 0x0a, 0x07, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x09, 0x48, 0x03, 0x52, 0x07, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x88, 0x01, 0x01, + 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x65, 0x6e, 0x76, 0x61, 0x72, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x73, + 0x68, 0x6f, 0x72, 0x74, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x68, 0x6f, + 0x6c, 0x64, 0x65, 0x72, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, + 0x22, 0xbb, 0x01, 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4d, 0x6f, 0x64, 0x75, 0x6c, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, + 0x64, 0x69, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x64, 0x69, 0x72, 0x12, 0x4f, + 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, + 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, + 0x67, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, + 0x2d, 0x0a, 0x05, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x05, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x22, 0x16, + 0x0a, 0x14, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2f, 0x0a, 0x1b, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x64, 0x69, 0x72, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x64, 0x69, 0x72, 0x22, 0xf7, 0x02, 0x0a, 0x1c, 0x4d, 0x6f, 0x64, 0x75, + 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x65, 0x70, 0x6c, + 0x6f, 0x79, 0x5f, 0x64, 0x69, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x65, + 0x70, 0x6c, 0x6f, 0x79, 0x44, 0x69, 0x72, 0x12, 0x19, 0x0a, 0x05, 0x62, 0x75, 0x69, 0x6c, 0x64, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x05, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x88, + 0x01, 0x01, 0x12, 0x27, 0x0a, 0x0c, 0x64, 0x65, 0x76, 0x4d, 0x6f, 0x64, 0x65, 0x42, 0x75, 0x69, + 0x6c, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x0c, 0x64, 0x65, 0x76, 0x4d, + 0x6f, 0x64, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x88, 0x01, 0x01, 0x12, 0x22, 0x0a, 0x0a, 0x62, + 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, + 0x02, 0x52, 0x09, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x4c, 0x6f, 0x63, 0x6b, 0x88, 0x01, 0x01, 0x12, + 0x35, 0x0a, 0x14, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x73, 0x63, 0x68, + 0x65, 0x6d, 0x61, 0x5f, 0x64, 0x69, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, + 0x12, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, + 0x44, 0x69, 0x72, 0x88, 0x01, 0x01, 0x12, 0x14, 0x0a, 0x05, 0x77, 0x61, 0x74, 0x63, 0x68, 0x18, + 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x77, 0x61, 0x74, 0x63, 0x68, 0x12, 0x40, 0x0a, 0x0f, + 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x0e, + 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x08, + 0x0a, 0x06, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x42, 0x0f, 0x0a, 0x0d, 0x5f, 0x64, 0x65, 0x76, + 0x4d, 0x6f, 0x64, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x62, 0x75, + 0x69, 0x6c, 0x64, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x42, 0x17, 0x0a, 0x15, 0x5f, 0x67, 0x65, 0x6e, + 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x64, 0x69, + 0x72, 0x22, 0x63, 0x0a, 0x13, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x69, 0x65, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4c, 0x0a, 0x0d, 0x6d, 0x6f, 0x64, 0x75, + 0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x27, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, + 0x76, 0x31, 0x2e, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x2e, 0x4d, 0x6f, 0x64, 0x75, + 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0c, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x30, 0x0a, 0x14, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, + 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, + 0x0a, 0x07, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x07, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x22, 0xe6, 0x01, 0x0a, 0x0c, 0x42, 0x75, 0x69, + 0x6c, 0x64, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x4c, 0x0a, 0x0d, 0x6d, 0x6f, 0x64, + 0x75, 0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x27, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, + 0x2e, 0x76, 0x31, 0x2e, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x2e, 0x4d, 0x6f, 0x64, + 0x75, 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0c, 0x6d, 0x6f, 0x64, 0x75, 0x6c, + 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x37, 0x0a, 0x06, 0x73, 0x63, 0x68, 0x65, 0x6d, + 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, + 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, + 0x61, 0x2e, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x06, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, + 0x12, 0x22, 0x0a, 0x0c, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, + 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, + 0x63, 0x69, 0x65, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x65, 0x6e, + 0x76, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x45, 0x6e, + 0x76, 0x22, 0x69, 0x0a, 0x1a, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, + 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x4b, 0x0a, 0x0c, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, + 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, + 0x65, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x0c, + 0x62, 0x75, 0x69, 0x6c, 0x64, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x22, 0x1d, 0x0a, 0x1b, + 0x42, 0x75, 0x69, 0x6c, 0x64, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xb1, 0x02, 0x0a, 0x05, + 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x12, 0x41, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2b, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, + 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, + 0x67, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x4c, 0x65, + 0x76, 0x65, 0x6c, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x3a, 0x0a, 0x03, 0x70, 0x6f, + 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, + 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x6c, 0x61, 0x6e, 0x67, 0x75, + 0x61, 0x67, 0x65, 0x2e, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x03, + 0x70, 0x6f, 0x73, 0x88, 0x01, 0x01, 0x12, 0x3e, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2a, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, - 0x2e, 0x47, 0x65, 0x74, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, - 0x46, 0x6c, 0x61, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x46, 0x6c, - 0x61, 0x67, 0x52, 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x1a, 0xda, 0x01, 0x0a, 0x04, 0x46, 0x6c, - 0x61, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x65, 0x6c, 0x70, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x65, 0x6c, 0x70, 0x12, 0x19, 0x0a, 0x05, 0x65, 0x6e, - 0x76, 0x61, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x05, 0x65, 0x6e, 0x76, - 0x61, 0x72, 0x88, 0x01, 0x01, 0x12, 0x19, 0x0a, 0x05, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x05, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x88, 0x01, 0x01, - 0x12, 0x25, 0x0a, 0x0b, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x68, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x0b, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x68, 0x6f, - 0x6c, 0x64, 0x65, 0x72, 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x07, 0x64, 0x65, 0x66, 0x61, 0x75, - 0x6c, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, 0x07, 0x64, 0x65, 0x66, 0x61, - 0x75, 0x6c, 0x74, 0x88, 0x01, 0x01, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x65, 0x6e, 0x76, 0x61, 0x72, - 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x70, - 0x6c, 0x61, 0x63, 0x65, 0x68, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x64, - 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x22, 0xbb, 0x01, 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, - 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, - 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x64, 0x69, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x03, 0x64, 0x69, 0x72, 0x12, 0x4f, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, - 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x78, - 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, - 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2d, 0x0a, 0x05, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x05, 0x46, - 0x6c, 0x61, 0x67, 0x73, 0x22, 0x16, 0x0a, 0x14, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4d, 0x6f, - 0x64, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2f, 0x0a, 0x1b, - 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x44, 0x65, 0x66, 0x61, - 0x75, 0x6c, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x64, - 0x69, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x64, 0x69, 0x72, 0x22, 0xbd, 0x02, - 0x0a, 0x1c, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x44, 0x65, - 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1d, - 0x0a, 0x0a, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x5f, 0x64, 0x69, 0x72, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x44, 0x69, 0x72, 0x12, 0x19, 0x0a, - 0x05, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x05, - 0x62, 0x75, 0x69, 0x6c, 0x64, 0x88, 0x01, 0x01, 0x12, 0x22, 0x0a, 0x0a, 0x62, 0x75, 0x69, 0x6c, - 0x64, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x09, - 0x62, 0x75, 0x69, 0x6c, 0x64, 0x4c, 0x6f, 0x63, 0x6b, 0x88, 0x01, 0x01, 0x12, 0x35, 0x0a, 0x14, - 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, - 0x5f, 0x64, 0x69, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x12, 0x67, 0x65, - 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x44, 0x69, 0x72, - 0x88, 0x01, 0x01, 0x12, 0x14, 0x0a, 0x05, 0x77, 0x61, 0x74, 0x63, 0x68, 0x18, 0x05, 0x20, 0x03, - 0x28, 0x09, 0x52, 0x05, 0x77, 0x61, 0x74, 0x63, 0x68, 0x12, 0x40, 0x0a, 0x0f, 0x6c, 0x61, 0x6e, - 0x67, 0x75, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x0e, 0x6c, 0x61, 0x6e, - 0x67, 0x75, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x08, 0x0a, 0x06, 0x5f, - 0x62, 0x75, 0x69, 0x6c, 0x64, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, - 0x6c, 0x6f, 0x63, 0x6b, 0x42, 0x17, 0x0a, 0x15, 0x5f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, - 0x65, 0x64, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x64, 0x69, 0x72, 0x22, 0x63, 0x0a, - 0x13, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x4c, 0x0a, 0x0d, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x63, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x78, 0x79, - 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x6c, - 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x2e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0c, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x22, 0x30, 0x0a, 0x14, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x69, - 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x6f, - 0x64, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x6f, 0x64, - 0x75, 0x6c, 0x65, 0x73, 0x22, 0xe6, 0x01, 0x0a, 0x0c, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x43, 0x6f, - 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x4c, 0x0a, 0x0d, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f, - 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x78, - 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, - 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x2e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0c, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x12, 0x37, 0x0a, 0x06, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, - 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x53, 0x63, - 0x68, 0x65, 0x6d, 0x61, 0x52, 0x06, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, 0x22, 0x0a, 0x0c, - 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, - 0x28, 0x09, 0x52, 0x0c, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, - 0x12, 0x1b, 0x0a, 0x09, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x65, 0x6e, 0x76, 0x18, 0x05, 0x20, - 0x03, 0x28, 0x09, 0x52, 0x08, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x45, 0x6e, 0x76, 0x22, 0x69, 0x0a, - 0x1a, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4b, 0x0a, 0x0c, 0x62, - 0x75, 0x69, 0x6c, 0x64, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, + 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x2b, 0x0a, 0x0a, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x4c, + 0x65, 0x76, 0x65, 0x6c, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x46, 0x4f, 0x10, 0x00, 0x12, 0x08, + 0x0a, 0x04, 0x57, 0x41, 0x52, 0x4e, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, + 0x52, 0x10, 0x02, 0x22, 0x22, 0x0a, 0x09, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x07, 0x0a, 0x03, 0x46, 0x54, 0x4c, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x43, 0x4f, 0x4d, + 0x50, 0x49, 0x4c, 0x45, 0x52, 0x10, 0x01, 0x42, 0x06, 0x0a, 0x04, 0x5f, 0x70, 0x6f, 0x73, 0x22, + 0x7a, 0x0a, 0x08, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x66, + 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, + 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x73, + 0x74, 0x61, 0x72, 0x74, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0b, 0x73, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x12, 0x1c, 0x0a, + 0x09, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x09, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x22, 0x45, 0x0a, 0x09, 0x45, + 0x72, 0x72, 0x6f, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x38, 0x0a, 0x06, 0x65, 0x72, 0x72, 0x6f, + 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, + 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x6c, 0x61, 0x6e, 0x67, + 0x75, 0x61, 0x67, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x06, 0x65, 0x72, 0x72, 0x6f, + 0x72, 0x73, 0x22, 0xd3, 0x01, 0x0a, 0x0c, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, + 0x6f, 0x6f, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x6a, 0x65, + 0x63, 0x74, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x75, 0x62, 0x73, 0x5f, + 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x75, 0x62, + 0x73, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x33, 0x0a, 0x15, 0x72, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, + 0x5f, 0x61, 0x75, 0x74, 0x6f, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x6c, 0x79, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x72, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x41, 0x75, 0x74, + 0x6f, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x6c, 0x79, 0x12, 0x4c, 0x0a, 0x0d, 0x62, 0x75, + 0x69, 0x6c, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x0c, 0x62, 0x75, 0x69, 0x6c, - 0x64, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x22, 0x1d, 0x0a, 0x1b, 0x42, 0x75, 0x69, 0x6c, - 0x64, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xb1, 0x02, 0x0a, 0x05, 0x45, 0x72, 0x72, 0x6f, - 0x72, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, - 0x6d, 0x73, 0x67, 0x12, 0x41, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x0e, 0x32, 0x2b, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, - 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x2e, 0x45, - 0x72, 0x72, 0x6f, 0x72, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, - 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x3a, 0x0a, 0x03, 0x70, 0x6f, 0x73, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, + 0x64, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x22, 0x33, 0x0a, 0x12, 0x41, 0x75, 0x74, 0x6f, + 0x52, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x12, 0x1d, + 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x49, 0x64, 0x22, 0xca, 0x02, + 0x0a, 0x0c, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, 0x1d, + 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x49, 0x64, 0x12, 0x30, 0x0a, + 0x14, 0x69, 0x73, 0x5f, 0x61, 0x75, 0x74, 0x6f, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x5f, 0x72, 0x65, + 0x62, 0x75, 0x69, 0x6c, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x69, 0x73, 0x41, + 0x75, 0x74, 0x6f, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x52, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x12, + 0x37, 0x0a, 0x06, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1f, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, + 0x76, 0x31, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, + 0x52, 0x06, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x65, 0x70, 0x6c, + 0x6f, 0x79, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, + 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x49, 0x6d, + 0x61, 0x67, 0x65, 0x12, 0x3c, 0x0a, 0x06, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x2e, - 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x03, 0x70, 0x6f, 0x73, 0x88, - 0x01, 0x01, 0x12, 0x3e, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, - 0x32, 0x2a, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, - 0x2e, 0x76, 0x31, 0x2e, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x2e, 0x45, 0x72, 0x72, - 0x6f, 0x72, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, - 0x70, 0x65, 0x22, 0x2b, 0x0a, 0x0a, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x4c, 0x65, 0x76, 0x65, 0x6c, - 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x46, 0x4f, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x57, 0x41, - 0x52, 0x4e, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x22, - 0x22, 0x0a, 0x09, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x07, 0x0a, 0x03, - 0x46, 0x54, 0x4c, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x43, 0x4f, 0x4d, 0x50, 0x49, 0x4c, 0x45, - 0x52, 0x10, 0x01, 0x42, 0x06, 0x0a, 0x04, 0x5f, 0x70, 0x6f, 0x73, 0x22, 0x7a, 0x0a, 0x08, 0x50, - 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x6e, - 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x6e, - 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x72, 0x74, - 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x73, 0x74, - 0x61, 0x72, 0x74, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x65, 0x6e, 0x64, - 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x65, 0x6e, - 0x64, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x22, 0x45, 0x0a, 0x09, 0x45, 0x72, 0x72, 0x6f, 0x72, - 0x4c, 0x69, 0x73, 0x74, 0x12, 0x38, 0x0a, 0x06, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x18, 0x01, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, - 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, - 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x06, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x22, 0xd3, - 0x01, 0x0a, 0x0c, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x6f, - 0x6f, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x75, 0x62, 0x73, 0x5f, 0x72, 0x6f, 0x6f, 0x74, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x75, 0x62, 0x73, 0x52, 0x6f, 0x6f, - 0x74, 0x12, 0x33, 0x0a, 0x15, 0x72, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x61, 0x75, 0x74, - 0x6f, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x6c, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x14, 0x72, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x41, 0x75, 0x74, 0x6f, 0x6d, 0x61, 0x74, - 0x69, 0x63, 0x61, 0x6c, 0x6c, 0x79, 0x12, 0x4c, 0x0a, 0x0d, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, - 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, - 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, - 0x2e, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x43, - 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x0c, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x43, 0x6f, 0x6e, - 0x74, 0x65, 0x78, 0x74, 0x22, 0x33, 0x0a, 0x12, 0x41, 0x75, 0x74, 0x6f, 0x52, 0x65, 0x62, 0x75, - 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, - 0x6e, 0x74, 0x65, 0x78, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x49, 0x64, 0x22, 0xee, 0x01, 0x0a, 0x0c, 0x42, 0x75, - 0x69, 0x6c, 0x64, 0x53, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, - 0x6e, 0x74, 0x65, 0x78, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x14, 0x69, 0x73, 0x5f, - 0x61, 0x75, 0x74, 0x6f, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x5f, 0x72, 0x65, 0x62, 0x75, 0x69, 0x6c, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x69, 0x73, 0x41, 0x75, 0x74, 0x6f, 0x6d, - 0x61, 0x74, 0x69, 0x63, 0x52, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x37, 0x0a, 0x06, 0x6d, - 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x78, 0x79, - 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x73, - 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x52, 0x06, 0x6d, 0x6f, - 0x64, 0x75, 0x6c, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x18, 0x04, - 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x12, 0x3c, 0x0a, 0x06, - 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x78, - 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, - 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x4c, 0x69, - 0x73, 0x74, 0x52, 0x06, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x22, 0xd6, 0x01, 0x0a, 0x0c, 0x42, + 0x45, 0x72, 0x72, 0x6f, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x06, 0x65, 0x72, 0x72, 0x6f, 0x72, + 0x73, 0x12, 0x26, 0x0a, 0x0c, 0x64, 0x65, 0x76, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, + 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0b, 0x64, 0x65, 0x76, 0x45, 0x6e, + 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x88, 0x01, 0x01, 0x42, 0x0f, 0x0a, 0x0d, 0x5f, 0x64, 0x65, + 0x76, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x22, 0xd6, 0x01, 0x0a, 0x0c, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x14, 0x69, 0x73, @@ -2221,6 +2271,7 @@ func file_xyz_block_ftl_v1_language_language_proto_init() { file_xyz_block_ftl_v1_language_language_proto_msgTypes[0].OneofWrappers = []any{} file_xyz_block_ftl_v1_language_language_proto_msgTypes[7].OneofWrappers = []any{} file_xyz_block_ftl_v1_language_language_proto_msgTypes[13].OneofWrappers = []any{} + file_xyz_block_ftl_v1_language_language_proto_msgTypes[18].OneofWrappers = []any{} file_xyz_block_ftl_v1_language_language_proto_msgTypes[20].OneofWrappers = []any{ (*BuildEvent_AutoRebuildStarted)(nil), (*BuildEvent_BuildSuccess)(nil), diff --git a/backend/protos/xyz/block/ftl/v1/language/language.proto b/backend/protos/xyz/block/ftl/v1/language/language.proto index 198035fdc8..d44124401c 100644 --- a/backend/protos/xyz/block/ftl/v1/language/language.proto +++ b/backend/protos/xyz/block/ftl/v1/language/language.proto @@ -22,16 +22,18 @@ message ModuleConfig { string deploy_dir = 4; // Build is the command to build the module. optional string build = 5; + // DevModeBuild is the command to build the module in dev mode. + optional string devModeBuild = 6; // Build lock path to prevent concurrent builds - string build_lock = 6; + string build_lock = 7; // The directory to generate protobuf schema files into. These can be picked up by language specific build tools - optional string generated_schema_dir = 7; + optional string generated_schema_dir = 8; // Patterns to watch for file changes - repeated string watch = 8; + repeated string watch = 9; // LanguageConfig contains any metadata specific to a specific language. // These are stored in the ftl.toml file under the same key as the language (eg: "go", "java") - google.protobuf.Struct language_config = 9; + google.protobuf.Struct language_config = 10; } // ProjectConfig contains the configuration for a project, found in the ftl-project.toml file. @@ -94,18 +96,21 @@ message ModuleConfigDefaultsResponse { // Default build command optional string build = 2; + // Dev mode build command, if different from the regular build command + optional string devModeBuild = 3; + // Build lock path to prevent concurrent builds - optional string build_lock = 3; + optional string build_lock = 4; // Default relative path to the directory containing generated schema files - optional string generated_schema_dir = 4; + optional string generated_schema_dir = 5; // Default patterns to watch for file changes, relative to the module directory - repeated string watch = 5; + repeated string watch = 6; // Default language specific configuration. // These defaults are filled in by looking at each root key only. If the key is not present, the default is used. - google.protobuf.Struct language_config = 6; + google.protobuf.Struct language_config = 7; } message DependenciesRequest { @@ -208,11 +213,17 @@ message BuildSuccess { schema.Module module = 3; // Paths for files/directories to be deployed repeated string deploy = 4; + // Name of the docker image to use for the runner + string docker_image = 5; // Errors contains any errors that occurred during the build // No errors can have a level of ERROR, instead a BuildFailure should be sent // Instead this is useful for INFO and WARN level errors. - ErrorList errors = 5; + ErrorList errors = 6; + + // Dev mode endpoint URI. If this is set then rather than trying to deploy the module, FTL will start a runner that + // connects to this endpoint. + optional string dev_endpoint = 7; } // BuildFailure should be sent when a build fails. diff --git a/backend/protos/xyz/block/ftl/v1/language/mixins.go b/backend/protos/xyz/block/ftl/v1/language/mixins.go index e7462f488b..ed43129227 100644 --- a/backend/protos/xyz/block/ftl/v1/language/mixins.go +++ b/backend/protos/xyz/block/ftl/v1/language/mixins.go @@ -4,7 +4,7 @@ import ( "fmt" "github.com/alecthomas/types/optional" - structpb "google.golang.org/protobuf/types/known/structpb" + "google.golang.org/protobuf/types/known/structpb" "github.com/TBD54566975/ftl/internal/builderrors" "github.com/TBD54566975/ftl/internal/moduleconfig" @@ -100,6 +100,9 @@ func ModuleConfigToProto(config moduleconfig.AbsModuleConfig) (*ModuleConfig, er if config.Build != "" { proto.Build = &config.Build } + if config.DevModeBuild != "" { + proto.DevModeBuild = &config.DevModeBuild + } if config.GeneratedSchemaDir != "" { proto.GeneratedSchemaDir = &config.GeneratedSchemaDir } @@ -122,6 +125,7 @@ func ModuleConfigFromProto(proto *ModuleConfig) moduleconfig.AbsModuleConfig { Watch: proto.Watch, Language: proto.Language, Build: proto.GetBuild(), + DevModeBuild: proto.GetDevModeBuild(), BuildLock: proto.BuildLock, GeneratedSchemaDir: proto.GetGeneratedSchemaDir(), } diff --git a/backend/runner/runner.go b/backend/runner/runner.go index 7c4fcf446f..afaaa08872 100644 --- a/backend/runner/runner.go +++ b/backend/runner/runner.go @@ -32,6 +32,7 @@ import ( "github.com/TBD54566975/ftl/backend/runner/observability" "github.com/TBD54566975/ftl/common/plugin" "github.com/TBD54566975/ftl/internal/download" + "github.com/TBD54566975/ftl/internal/exec" "github.com/TBD54566975/ftl/internal/identity" "github.com/TBD54566975/ftl/internal/log" "github.com/TBD54566975/ftl/internal/model" @@ -43,18 +44,19 @@ import ( ) type Config struct { - Config []string `name:"config" short:"C" help:"Paths to FTL project configuration files." env:"FTL_CONFIG" placeholder:"FILE[,FILE,...]" type:"existingfile"` - Bind *url.URL `help:"Endpoint the Runner should bind to and advertise." default:"http://127.0.0.1:8893" env:"FTL_RUNNER_BIND"` - Key model.RunnerKey `help:"Runner key (auto)."` - ControllerEndpoint *url.URL `name:"ftl-endpoint" help:"Controller endpoint." env:"FTL_ENDPOINT" default:"http://127.0.0.1:8892"` - ControllerPublicKey *identity.PublicKey `name:"ftl-public-key" help:"Controller public key in Base64. Temporarily optional." env:"FTL_CONTROLLER_PUBLIC_KEY"` - TemplateDir string `help:"Template directory to copy into each deployment, if any." type:"existingdir"` - DeploymentDir string `help:"Directory to store deployments in." default:"${deploymentdir}"` - DeploymentKeepHistory int `help:"Number of deployments to keep history for." default:"3"` - HeartbeatPeriod time.Duration `help:"Minimum period between heartbeats." default:"3s"` - HeartbeatJitter time.Duration `help:"Jitter to add to heartbeat period." default:"2s"` - Deployment string `help:"The deployment this runner is for." env:"FTL_DEPLOYMENT"` - DebugPort int `help:"The port to use for debugging." env:"FTL_DEBUG_PORT"` + Config []string `name:"config" short:"C" help:"Paths to FTL project configuration files." env:"FTL_CONFIG" placeholder:"FILE[,FILE,...]" type:"existingfile"` + Bind *url.URL `help:"Endpoint the Runner should bind to and advertise." default:"http://127.0.0.1:8893" env:"FTL_RUNNER_BIND"` + Key model.RunnerKey `help:"Runner key (auto)."` + ControllerEndpoint *url.URL `name:"ftl-endpoint" help:"Controller endpoint." env:"FTL_ENDPOINT" default:"http://127.0.0.1:8892"` + ControllerPublicKey *identity.PublicKey `name:"ftl-public-key" help:"Controller public key in Base64. Temporarily optional." env:"FTL_CONTROLLER_PUBLIC_KEY"` + TemplateDir string `help:"Template directory to copy into each deployment, if any." type:"existingdir"` + DeploymentDir string `help:"Directory to store deployments in." default:"${deploymentdir}"` + DeploymentKeepHistory int `help:"Number of deployments to keep history for." default:"3"` + HeartbeatPeriod time.Duration `help:"Minimum period between heartbeats." default:"3s"` + HeartbeatJitter time.Duration `help:"Jitter to add to heartbeat period." default:"2s"` + Deployment string `help:"The deployment this runner is for." env:"FTL_DEPLOYMENT"` + DebugPort int `help:"The port to use for debugging." env:"FTL_DEBUG_PORT"` + DevEndpoint optional.Option[url.URL] `help:"An existing endpoint to connect to in development mode" env:"FTL_DEV_ENDPOINT"` } func Start(ctx context.Context, config Config) error { @@ -117,6 +119,7 @@ func Start(ctx context.Context, config Config) error { labels: labels, deploymentLogQueue: make(chan log.Entry, 10000), cancelFunc: doneFunc, + devEndpoint: config.DevEndpoint, } err = svc.deploy(ctx) if err != nil { @@ -234,10 +237,12 @@ func manageDeploymentDirectory(logger *log.Logger, config Config) error { var _ ftlv1connect.VerbServiceHandler = (*Service)(nil) type deployment struct { - key model.DeploymentKey - plugin *plugin.Plugin[ftlv1connect.VerbServiceClient] + key model.DeploymentKey // Cancelled when plugin terminates - ctx context.Context + ctx context.Context + cmd optional.Option[exec.Cmd] + endpoint *url.URL // The endpoint the plugin is listening on. + client ftlv1connect.VerbServiceClient } type Service struct { @@ -254,6 +259,7 @@ type Service struct { labels *structpb.Struct deploymentLogQueue chan log.Entry cancelFunc func() + devEndpoint optional.Option[url.URL] } func (s *Service) Call(ctx context.Context, req *connect.Request[ftlv1.CallRequest]) (*connect.Response[ftlv1.CallResponse], error) { @@ -261,7 +267,7 @@ func (s *Service) Call(ctx context.Context, req *connect.Request[ftlv1.CallReque if !ok { return nil, connect.NewError(connect.CodeUnavailable, errors.New("no deployment")) } - response, err := deployment.plugin.Client.Call(ctx, req) + response, err := deployment.client.Call(ctx, req) if err != nil { deploymentLogger := s.getDeploymentLogger(ctx, deployment.key) deploymentLogger.Errorf(err, "Call to deployments %s failed to perform gRPC call", deployment.key) @@ -331,37 +337,50 @@ func (s *Service) deploy(ctx context.Context) error { return fmt.Errorf("failed to create deployment directory: %w", err) } } - err = download.Artefacts(ctx, s.controllerClient, key, deploymentDir) - if err != nil { - observability.Deployment.Failure(ctx, optional.Some(key.String())) - return fmt.Errorf("failed to download artefacts: %w", err) - } - - envVars := []string{"FTL_ENDPOINT=" + s.config.ControllerEndpoint.String(), - "FTL_CONFIG=" + strings.Join(s.config.Config, ","), - "FTL_OBSERVABILITY_ENDPOINT=" + s.config.ControllerEndpoint.String()} - if s.config.DebugPort > 0 { - envVars = append(envVars, fmt.Sprintf("FTL_DEBUG_PORT=%d", s.config.DebugPort)) - } - - verbCtx := log.ContextWithLogger(ctx, deploymentLogger.Attrs(map[string]string{"module": module.Name})) - deployment, cmdCtx, err := plugin.Spawn( - unstoppable.Context(verbCtx), - log.FromContext(ctx).GetLevel(), - gdResp.Msg.Schema.Name, - deploymentDir, - "./launch", - ftlv1connect.NewVerbServiceClient, - plugin.WithEnvars( - envVars..., - ), - ) - if err != nil { - observability.Deployment.Failure(ctx, optional.Some(key.String())) - return fmt.Errorf("failed to spawn plugin: %w", err) + var dep *deployment + if ep, ok := s.devEndpoint.Get(); ok { + client := rpc.Dial(ftlv1connect.NewVerbServiceClient, ep.String(), log.Error) + dep = &deployment{ + ctx: ctx, + key: key, + cmd: optional.None[exec.Cmd](), + endpoint: &ep, + client: client, + } + } else { + + err = download.Artefacts(ctx, s.controllerClient, key, deploymentDir) + if err != nil { + observability.Deployment.Failure(ctx, optional.Some(key.String())) + return fmt.Errorf("failed to download artefacts: %w", err) + } + + envVars := []string{"FTL_ENDPOINT=" + s.config.ControllerEndpoint.String(), + "FTL_CONFIG=" + strings.Join(s.config.Config, ","), + "FTL_OBSERVABILITY_ENDPOINT=" + s.config.ControllerEndpoint.String()} + if s.config.DebugPort > 0 { + envVars = append(envVars, fmt.Sprintf("FTL_DEBUG_PORT=%d", s.config.DebugPort)) + } + + verbCtx := log.ContextWithLogger(ctx, deploymentLogger.Attrs(map[string]string{"module": module.Name})) + deployment, cmdCtx, err := plugin.Spawn( + unstoppable.Context(verbCtx), + log.FromContext(ctx).GetLevel(), + gdResp.Msg.Schema.Name, + deploymentDir, + "./launch", + ftlv1connect.NewVerbServiceClient, + plugin.WithEnvars( + envVars..., + ), + ) + if err != nil { + observability.Deployment.Failure(ctx, optional.Some(key.String())) + return fmt.Errorf("failed to spawn plugin: %w", err) + } + dep = s.makeDeployment(cmdCtx, key, deployment) } - dep := s.makeDeployment(cmdCtx, key, deployment) s.readyTime.Store(time.Now().Add(time.Second * 2)) // Istio is a bit flakey, add a small delay for readiness s.deployment.Store(optional.Some(dep)) logger.Debugf("Deployed %s", key) @@ -383,20 +402,22 @@ func (s *Service) Close() error { if !ok { return connect.NewError(connect.CodeNotFound, errors.New("no deployment")) } - // Soft kill. - err := depl.plugin.Cmd.Kill(syscall.SIGTERM) - if err != nil { - return fmt.Errorf("failed to kill plugin: %w", err) - } - // Hard kill after 10 seconds. - select { - case <-depl.ctx.Done(): - case <-time.After(10 * time.Second): - err := depl.plugin.Cmd.Kill(syscall.SIGKILL) + if cmd, ok := depl.cmd.Get(); ok { + // Soft kill. + err := cmd.Kill(syscall.SIGTERM) if err != nil { - // Should we os.Exit(1) here? return fmt.Errorf("failed to kill plugin: %w", err) } + // Hard kill after 10 seconds. + select { + case <-depl.ctx.Done(): + case <-time.After(10 * time.Second): + err := cmd.Kill(syscall.SIGKILL) + if err != nil { + // Should we os.Exit(1) here? + return fmt.Errorf("failed to kill plugin: %w", err) + } + } } s.deployment.Store(optional.None[*deployment]()) return nil @@ -409,16 +430,18 @@ func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) { http.Error(w, "no deployment", http.StatusNotFound) return } - proxy := httputil.NewSingleHostReverseProxy(deployment.plugin.Endpoint) + proxy := httputil.NewSingleHostReverseProxy(deployment.endpoint) proxy.ServeHTTP(w, r) } func (s *Service) makeDeployment(ctx context.Context, key model.DeploymentKey, plugin *plugin.Plugin[ftlv1connect.VerbServiceClient]) *deployment { return &deployment{ - ctx: ctx, - key: key, - plugin: plugin, + ctx: ctx, + key: key, + cmd: optional.Ptr(plugin.Cmd), + endpoint: plugin.Endpoint, + client: plugin.Client, } } diff --git a/common/plugin/serve.go b/common/plugin/serve.go index 70cefa6422..87b0ae9928 100644 --- a/common/plugin/serve.go +++ b/common/plugin/serve.go @@ -169,8 +169,9 @@ func Start[Impl any, Iface any, Config any]( kctx.Exit(0) } -func allocatePort() (*net.TCPAddr, error) { +func AllocatePort() (*net.TCPAddr, error) { l, err := net.ListenTCP("tcp", &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1)}) + if err != nil { return nil, fmt.Errorf("failed to allocate port: %w", err) } diff --git a/common/plugin/spawn.go b/common/plugin/spawn.go index 4b79ebb1f2..ab4f45b15b 100644 --- a/common/plugin/spawn.go +++ b/common/plugin/spawn.go @@ -114,7 +114,7 @@ func Spawn[Client PingableClient]( } // Find a free port. - addr, err := allocatePort() + addr, err := AllocatePort() if err != nil { return nil, nil, err } diff --git a/examples/java/time/src/main/java/ftl/time/Time.java b/examples/java/time/src/main/java/ftl/time/Time.java index d27668c8cb..754e4855bd 100644 --- a/examples/java/time/src/main/java/ftl/time/Time.java +++ b/examples/java/time/src/main/java/ftl/time/Time.java @@ -2,25 +2,14 @@ import java.time.OffsetDateTime; -import io.opentelemetry.api.metrics.LongCounter; -import io.opentelemetry.api.metrics.Meter; import xyz.block.ftl.Export; import xyz.block.ftl.Verb; public class Time { - final LongCounter counter; - - public Time(Meter meter) { - counter = meter.counterBuilder("time.invocations") - .setDescription("The number of time invocations") - .setUnit("invocations") - .build(); - } @Verb @Export public TimeResponse time() { - counter.add(1); return new TimeResponse(OffsetDateTime.now()); } } diff --git a/frontend/cli/cmd_box_run.go b/frontend/cli/cmd_box_run.go index b330d6a6c8..aa4ecbb514 100644 --- a/frontend/cli/cmd_box_run.go +++ b/frontend/cli/cmd_box_run.go @@ -11,6 +11,7 @@ import ( "golang.org/x/sync/errgroup" "github.com/TBD54566975/ftl/backend/controller" + "github.com/TBD54566975/ftl/backend/controller/scaling" "github.com/TBD54566975/ftl/backend/controller/scaling/localscaling" "github.com/TBD54566975/ftl/backend/controller/sql/databasetesting" "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1/ftlv1connect" @@ -58,7 +59,7 @@ func (b *boxRunCmd) Run( if err != nil { return fmt.Errorf("failed to create runner port allocator: %w", err) } - runnerScaling, err := localscaling.NewLocalScaling(bindAllocator, []*url.URL{b.Bind}, "", false) + runnerScaling, err := localscaling.NewLocalScaling(bindAllocator, []*url.URL{b.Bind}, "", false, make(<-chan scaling.DevModeEndpoints)) if err != nil { return fmt.Errorf("failed to create runner autoscaler: %w", err) } diff --git a/frontend/cli/cmd_dev.go b/frontend/cli/cmd_dev.go index ca21b8c2f9..ed0ccaf074 100644 --- a/frontend/cli/cmd_dev.go +++ b/frontend/cli/cmd_dev.go @@ -10,6 +10,7 @@ import ( "github.com/alecthomas/types/optional" "golang.org/x/sync/errgroup" + "github.com/TBD54566975/ftl/backend/controller/scaling" "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1/ftlv1connect" "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1beta1/provisioner/provisionerconnect" "github.com/TBD54566975/ftl/internal/bind" @@ -81,6 +82,7 @@ func (d *devCmd) Run( return fmt.Errorf("could not create bind allocator: %w", err) } + devModeEndpointUpdates := make(chan scaling.DevModeEndpoints, 1) // cmdServe will notify this channel when startup commands are complete and the controller is ready controllerReady := make(chan bool, 1) if !d.NoServe { @@ -93,7 +95,7 @@ func (d *devCmd) Run( } g.Go(func() error { - return d.ServeCmd.run(ctx, projConfig, cm, sm, optional.Some(controllerReady), true, bindAllocator) + return d.ServeCmd.run(ctx, projConfig, cm, sm, optional.Some(controllerReady), true, bindAllocator, devModeEndpointUpdates) }) } @@ -105,7 +107,7 @@ func (d *devCmd) Run( } starting.Close() - opts := []buildengine.Option{buildengine.Parallelism(d.Build.Parallelism), buildengine.BuildEnv(d.Build.BuildEnv), buildengine.WithDevMode(true), buildengine.WithStartTime(startTime)} + opts := []buildengine.Option{buildengine.Parallelism(d.Build.Parallelism), buildengine.BuildEnv(d.Build.BuildEnv), buildengine.WithDevMode(devModeEndpointUpdates), buildengine.WithStartTime(startTime)} if d.Lsp { d.languageServer = lsp.NewServer(ctx) ctx = log.ContextWithLogger(ctx, log.FromContext(ctx).AddSink(lsp.NewLogSink(d.languageServer))) diff --git a/frontend/cli/cmd_new.go b/frontend/cli/cmd_new.go index 1b5869b991..6938a2561c 100644 --- a/frontend/cli/cmd_new.go +++ b/frontend/cli/cmd_new.go @@ -55,7 +55,7 @@ func prepareNewCmd(ctx context.Context, k *kong.Kong, args []string) (optionalPl return optionalPlugin, fmt.Errorf("could not find new command") } - plugin, err := languageplugin.New(ctx, pluginDir(), language, "new") + plugin, err := languageplugin.New(ctx, pluginDir(), language, "new", false) if err != nil { return optionalPlugin, fmt.Errorf("could not create plugin for %v: %w", language, err) } diff --git a/frontend/cli/cmd_serve.go b/frontend/cli/cmd_serve.go index dd93916299..dc71b78e5b 100644 --- a/frontend/cli/cmd_serve.go +++ b/frontend/cli/cmd_serve.go @@ -20,6 +20,7 @@ import ( "github.com/TBD54566975/ftl" "github.com/TBD54566975/ftl/backend/controller" + "github.com/TBD54566975/ftl/backend/controller/scaling" "github.com/TBD54566975/ftl/backend/controller/scaling/localscaling" ftlv1 "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1" "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1/ftlv1connect" @@ -64,19 +65,11 @@ func (s *serveCmd) Run( if err != nil { return fmt.Errorf("could not create bind allocator: %w", err) } - return s.run(ctx, projConfig, cm, sm, optional.None[chan bool](), false, bindAllocator) + return s.run(ctx, projConfig, cm, sm, optional.None[chan bool](), false, bindAllocator, nil) } //nolint:maintidx -func (s *serveCmd) run( - ctx context.Context, - projConfig projectconfig.Config, - cm *manager.Manager[configuration.Configuration], - sm *manager.Manager[configuration.Secrets], - initialised optional.Option[chan bool], - devMode bool, - bindAllocator *bind.BindAllocator, -) error { +func (s *serveCmd) run(ctx context.Context, projConfig projectconfig.Config, cm *manager.Manager[configuration.Configuration], sm *manager.Manager[configuration.Secrets], initialised optional.Option[chan bool], devMode bool, bindAllocator *bind.BindAllocator, devModeEndpoints <-chan scaling.DevModeEndpoints) error { logger := log.FromContext(ctx) controllerClient := rpc.ClientFromContext[ftlv1connect.ControllerServiceClient](ctx) provisionerClient := rpc.ClientFromContext[provisionerconnect.ProvisionerServiceClient](ctx) @@ -163,7 +156,7 @@ func (s *serveCmd) run( provisionerAddresses = append(provisionerAddresses, bind) } - runnerScaling, err := localscaling.NewLocalScaling(bindAllocator, controllerAddresses, projConfig.Path, devMode && !projConfig.DisableIDEIntegration) + runnerScaling, err := localscaling.NewLocalScaling(bindAllocator, controllerAddresses, projConfig.Path, devMode && !projConfig.DisableIDEIntegration, devModeEndpoints) if err != nil { return err } diff --git a/frontend/console/src/protos/xyz/block/ftl/v1/language/language_pb.ts b/frontend/console/src/protos/xyz/block/ftl/v1/language/language_pb.ts index 3f04ee0a44..a91f399a1c 100644 --- a/frontend/console/src/protos/xyz/block/ftl/v1/language/language_pb.ts +++ b/frontend/console/src/protos/xyz/block/ftl/v1/language/language_pb.ts @@ -48,24 +48,31 @@ export class ModuleConfig extends Message { */ build?: string; + /** + * DevModeBuild is the command to build the module in dev mode. + * + * @generated from field: optional string devModeBuild = 6; + */ + devModeBuild?: string; + /** * Build lock path to prevent concurrent builds * - * @generated from field: string build_lock = 6; + * @generated from field: string build_lock = 7; */ buildLock = ""; /** * The directory to generate protobuf schema files into. These can be picked up by language specific build tools * - * @generated from field: optional string generated_schema_dir = 7; + * @generated from field: optional string generated_schema_dir = 8; */ generatedSchemaDir?: string; /** * Patterns to watch for file changes * - * @generated from field: repeated string watch = 8; + * @generated from field: repeated string watch = 9; */ watch: string[] = []; @@ -73,7 +80,7 @@ export class ModuleConfig extends Message { * LanguageConfig contains any metadata specific to a specific language. * These are stored in the ftl.toml file under the same key as the language (eg: "go", "java") * - * @generated from field: google.protobuf.Struct language_config = 9; + * @generated from field: google.protobuf.Struct language_config = 10; */ languageConfig?: Struct; @@ -90,10 +97,11 @@ export class ModuleConfig extends Message { { no: 3, name: "language", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 4, name: "deploy_dir", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 5, name: "build", kind: "scalar", T: 9 /* ScalarType.STRING */, opt: true }, - { no: 6, name: "build_lock", kind: "scalar", T: 9 /* ScalarType.STRING */ }, - { no: 7, name: "generated_schema_dir", kind: "scalar", T: 9 /* ScalarType.STRING */, opt: true }, - { no: 8, name: "watch", kind: "scalar", T: 9 /* ScalarType.STRING */, repeated: true }, - { no: 9, name: "language_config", kind: "message", T: Struct }, + { no: 6, name: "devModeBuild", kind: "scalar", T: 9 /* ScalarType.STRING */, opt: true }, + { no: 7, name: "build_lock", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 8, name: "generated_schema_dir", kind: "scalar", T: 9 /* ScalarType.STRING */, opt: true }, + { no: 9, name: "watch", kind: "scalar", T: 9 /* ScalarType.STRING */, repeated: true }, + { no: 10, name: "language_config", kind: "message", T: Struct }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): ModuleConfig { @@ -473,24 +481,31 @@ export class ModuleConfigDefaultsResponse extends Message [ { no: 1, name: "deploy_dir", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "build", kind: "scalar", T: 9 /* ScalarType.STRING */, opt: true }, - { no: 3, name: "build_lock", kind: "scalar", T: 9 /* ScalarType.STRING */, opt: true }, - { no: 4, name: "generated_schema_dir", kind: "scalar", T: 9 /* ScalarType.STRING */, opt: true }, - { no: 5, name: "watch", kind: "scalar", T: 9 /* ScalarType.STRING */, repeated: true }, - { no: 6, name: "language_config", kind: "message", T: Struct }, + { no: 3, name: "devModeBuild", kind: "scalar", T: 9 /* ScalarType.STRING */, opt: true }, + { no: 4, name: "build_lock", kind: "scalar", T: 9 /* ScalarType.STRING */, opt: true }, + { no: 5, name: "generated_schema_dir", kind: "scalar", T: 9 /* ScalarType.STRING */, opt: true }, + { no: 6, name: "watch", kind: "scalar", T: 9 /* ScalarType.STRING */, repeated: true }, + { no: 7, name: "language_config", kind: "message", T: Struct }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): ModuleConfigDefaultsResponse { @@ -1094,15 +1110,30 @@ export class BuildSuccess extends Message { */ deploy: string[] = []; + /** + * Name of the docker image to use for the runner + * + * @generated from field: string docker_image = 5; + */ + dockerImage = ""; + /** * Errors contains any errors that occurred during the build * No errors can have a level of ERROR, instead a BuildFailure should be sent * Instead this is useful for INFO and WARN level errors. * - * @generated from field: xyz.block.ftl.v1.language.ErrorList errors = 5; + * @generated from field: xyz.block.ftl.v1.language.ErrorList errors = 6; */ errors?: ErrorList; + /** + * Dev mode endpoint URI. If this is set then rather than trying to deploy the module, FTL will start a runner that + * connects to this endpoint. + * + * @generated from field: optional string dev_endpoint = 7; + */ + devEndpoint?: string; + constructor(data?: PartialMessage) { super(); proto3.util.initPartial(data, this); @@ -1115,7 +1146,9 @@ export class BuildSuccess extends Message { { no: 2, name: "is_automatic_rebuild", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, { no: 3, name: "module", kind: "message", T: Module }, { no: 4, name: "deploy", kind: "scalar", T: 9 /* ScalarType.STRING */, repeated: true }, - { no: 5, name: "errors", kind: "message", T: ErrorList }, + { no: 5, name: "docker_image", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 6, name: "errors", kind: "message", T: ErrorList }, + { no: 7, name: "dev_endpoint", kind: "scalar", T: 9 /* ScalarType.STRING */, opt: true }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): BuildSuccess { diff --git a/go-runtime/goplugin/service_test.go b/go-runtime/goplugin/service_test.go index 678da1e7cb..ece908589d 100644 --- a/go-runtime/goplugin/service_test.go +++ b/go-runtime/goplugin/service_test.go @@ -7,12 +7,14 @@ import ( "testing" "connectrpc.com/connect" + langpb "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1/language" "github.com/TBD54566975/ftl/internal/slices" - "github.com/TBD54566975/ftl/internal/moduleconfig" "github.com/alecthomas/assert/v2" "github.com/alecthomas/types/optional" + + "github.com/TBD54566975/ftl/internal/moduleconfig" ) func TestParseImportsFromTestData(t *testing.T) { @@ -113,6 +115,7 @@ func defaultsFromProto(proto *langpb.ModuleConfigDefaultsResponse) moduleconfig. DeployDir: proto.DeployDir, Watch: proto.Watch, Build: optional.Ptr(proto.Build), + DevModeBuild: optional.Ptr(proto.DevModeBuild), GeneratedSchemaDir: optional.Ptr(proto.GeneratedSchemaDir), LanguageConfig: proto.LanguageConfig.AsMap(), } diff --git a/internal/buildengine/build.go b/internal/buildengine/build.go index eb623d0c2e..b712d1edb5 100644 --- a/internal/buildengine/build.go +++ b/internal/buildengine/build.go @@ -3,6 +3,7 @@ package buildengine import ( "context" "fmt" + "net/url" "os" "path/filepath" "time" @@ -10,6 +11,7 @@ import ( "github.com/alecthomas/types/result" "google.golang.org/protobuf/proto" + "github.com/TBD54566975/ftl/backend/controller/scaling" "github.com/TBD54566975/ftl/internal/buildengine/languageplugin" "github.com/TBD54566975/ftl/internal/builderrors" "github.com/TBD54566975/ftl/internal/errors" @@ -26,16 +28,16 @@ var errInvalidateDependencies = errors.New("dependencies need to be updated") // Plugins must use a lock file to ensure that only one build is running at a time. // // Returns invalidateDependenciesError if the build failed due to a change in dependencies. -func build(ctx context.Context, plugin *languageplugin.LanguagePlugin, projectConfig projectconfig.Config, bctx languageplugin.BuildContext, devMode bool) (moduleSchema *schema.Module, deploy []string, err error) { +func build(ctx context.Context, plugin *languageplugin.LanguagePlugin, projectConfig projectconfig.Config, bctx languageplugin.BuildContext, devMode bool, devModeEndpoints chan scaling.DevModeEndpoints) (moduleSchema *schema.Module, deploy []string, err error) { logger := log.FromContext(ctx).Module(bctx.Config.Module).Scope("build") ctx = log.ContextWithLogger(ctx, logger) stubsRoot := stubsLanguageDir(projectConfig.Root(), bctx.Config.Language) - return handleBuildResult(ctx, projectConfig, bctx.Config, result.From(plugin.Build(ctx, projectConfig.Root(), stubsRoot, bctx, devMode))) + return handleBuildResult(ctx, projectConfig, bctx.Config, result.From(plugin.Build(ctx, projectConfig.Root(), stubsRoot, bctx, devMode)), devModeEndpoints) } // handleBuildResult processes the result of a build -func handleBuildResult(ctx context.Context, projectConfig projectconfig.Config, c moduleconfig.ModuleConfig, eitherResult result.Result[languageplugin.BuildResult]) (moduleSchema *schema.Module, deploy []string, err error) { +func handleBuildResult(ctx context.Context, projectConfig projectconfig.Config, c moduleconfig.ModuleConfig, eitherResult result.Result[languageplugin.BuildResult], devModeEndpoints chan scaling.DevModeEndpoints) (moduleSchema *schema.Module, deploy []string, err error) { logger := log.FromContext(ctx) config := c.Abs() @@ -76,5 +78,13 @@ func handleBuildResult(ctx context.Context, projectConfig projectconfig.Config, if err := os.WriteFile(schemaPath, schemaBytes, 0600); err != nil { return nil, nil, fmt.Errorf("failed to write schema: %w", err) } + if endpoint, ok := result.DevEndpoint.Get(); ok { + if devModeEndpoints != nil { + parsed, err := url.Parse(endpoint) + if err == nil { + devModeEndpoints <- scaling.DevModeEndpoints{Module: config.Module, Endpoint: *parsed} + } + } + } return result.Schema, result.Deploy, nil } diff --git a/internal/buildengine/engine.go b/internal/buildengine/engine.go index 6bae3133f0..5214d8bbd1 100644 --- a/internal/buildengine/engine.go +++ b/internal/buildengine/engine.go @@ -18,6 +18,7 @@ import ( "golang.org/x/exp/maps" "golang.org/x/sync/errgroup" + "github.com/TBD54566975/ftl/backend/controller/scaling" ftlv1 "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1" "github.com/TBD54566975/ftl/internal/buildengine/languageplugin" "github.com/TBD54566975/ftl/internal/log" @@ -185,7 +186,6 @@ type Engine struct { parallelism int modulesToBuild *xsync.MapOf[string, bool] buildEnv []string - devMode bool startTime optional.Option[time.Time] // events coming in from plugins @@ -199,6 +199,9 @@ type Engine struct { // topic to subscribe to engine events EngineUpdates *pubsub.Topic[EngineEvent] + + devModeEndpointUpdates chan scaling.DevModeEndpoints + devMode bool } type Option func(o *Engine) @@ -216,9 +219,10 @@ func BuildEnv(env []string) Option { } // WithDevMode sets the engine to dev mode. -func WithDevMode(devMode bool) Option { +func WithDevMode(updates chan scaling.DevModeEndpoints) Option { return func(o *Engine) { - o.devMode = devMode + o.devModeEndpointUpdates = updates + o.devMode = true } } @@ -991,7 +995,7 @@ func (e *Engine) build(ctx context.Context, moduleName string, builtModules map[ Schema: sch, Dependencies: meta.module.Dependencies(Raw), BuildEnv: e.buildEnv, - }, e.devMode) + }, e.devMode, e.devModeEndpointUpdates) if err != nil { if errors.Is(err, errInvalidateDependencies) { // Do not start a build directly as we are already building out a graph of modules. @@ -1036,7 +1040,7 @@ func (e *Engine) gatherSchemas( } func (e *Engine) newModuleMeta(ctx context.Context, config moduleconfig.UnvalidatedModuleConfig) (moduleMeta, error) { - plugin, err := languageplugin.New(ctx, config.Dir, config.Language, config.Module) + plugin, err := languageplugin.New(ctx, config.Dir, config.Language, config.Module, e.devMode) if err != nil { return moduleMeta{}, fmt.Errorf("could not create plugin for %s: %w", config.Module, err) } @@ -1098,7 +1102,7 @@ func (e *Engine) watchForPluginEvents(originalCtx context.Context) { e.rawEngineUpdates <- ModuleBuildStarted{Config: meta.module.Config, IsAutoRebuild: true} case languageplugin.AutoRebuildEndedEvent: - _, deploy, err := handleBuildResult(ctx, e.projectConfig, meta.module.Config, event.Result) + _, deploy, err := handleBuildResult(ctx, e.projectConfig, meta.module.Config, event.Result, nil) if err != nil { e.rawEngineUpdates <- ModuleBuildFailed{Config: meta.module.Config, IsAutoRebuild: true, Error: err} if errors.Is(err, errInvalidateDependencies) { @@ -1111,10 +1115,12 @@ func (e *Engine) watchForPluginEvents(originalCtx context.Context) { e.rawEngineUpdates <- ModuleBuildSuccess{Config: meta.module.Config, IsAutoRebuild: true} e.rawEngineUpdates <- ModuleDeployStarted{Module: event.Module} + if err := Deploy(ctx, e.projectConfig, meta.module, deploy, 1, true, e.client); err != nil { e.rawEngineUpdates <- ModuleDeployFailed{Module: event.Module, Error: err} continue } + e.rawEngineUpdates <- ModuleDeploySuccess{Module: event.Module} } case languageplugin.PluginDiedEvent: diff --git a/internal/buildengine/languageplugin/plugin.go b/internal/buildengine/languageplugin/plugin.go index 2b697fb844..77c6ef78cb 100644 --- a/internal/buildengine/languageplugin/plugin.go +++ b/internal/buildengine/languageplugin/plugin.go @@ -37,6 +37,9 @@ type BuildResult struct { // Whether the module needs to recalculate its dependencies InvalidateDependencies bool + + // Endpoint of an instance started by the plugin to use in dev mode + DevEndpoint optional.Option[string] } // PluginEvent is used to notify of updates from the plugin. @@ -90,8 +93,8 @@ type BuildContext struct { var ErrPluginNotRunning = errors.New("language plugin no longer running") // PluginFromConfig creates a new language plugin from the given config. -func New(ctx context.Context, dir, language, name string) (p *LanguagePlugin, err error) { - impl, err := newClientImpl(ctx, dir, language, name) +func New(ctx context.Context, dir, language, name string, devMode bool) (p *LanguagePlugin, err error) { + impl, err := newClientImpl(ctx, dir, language, name, devMode) if err != nil { return nil, err } @@ -237,6 +240,7 @@ func customDefaultsFromProto(proto *langpb.ModuleConfigDefaultsResponse) modulec DeployDir: proto.DeployDir, Watch: proto.Watch, Build: optional.Ptr(proto.Build), + DevModeBuild: optional.Ptr(proto.DevModeBuild), BuildLock: optional.Ptr(proto.BuildLock), GeneratedSchemaDir: optional.Ptr(proto.GeneratedSchemaDir), LanguageConfig: proto.LanguageConfig.AsMap(), @@ -562,10 +566,11 @@ func buildResultFromProto(result either.Either[*langpb.BuildEvent_BuildSuccess, errs := langpb.ErrorsFromProto(buildSuccess.Errors) builderrors.SortErrorsByPosition(errs) return BuildResult{ - Errors: errs, - Schema: moduleSch, - Deploy: buildSuccess.Deploy, - StartTime: startTime, + Errors: errs, + Schema: moduleSch, + Deploy: buildSuccess.Deploy, + StartTime: startTime, + DevEndpoint: optional.Ptr(buildSuccess.DevEndpoint), }, nil case either.Right[*langpb.BuildEvent_BuildSuccess, *langpb.BuildEvent_BuildFailure]: buildFailure := result.Get().BuildFailure diff --git a/internal/buildengine/languageplugin/plugin_client.go b/internal/buildengine/languageplugin/plugin_client.go index 74e590457a..56052203fa 100644 --- a/internal/buildengine/languageplugin/plugin_client.go +++ b/internal/buildengine/languageplugin/plugin_client.go @@ -42,9 +42,9 @@ type pluginClientImpl struct { cmdError chan error } -func newClientImpl(ctx context.Context, dir, language, name string) (*pluginClientImpl, error) { +func newClientImpl(ctx context.Context, dir, language, name string, devMode bool) (*pluginClientImpl, error) { impl := &pluginClientImpl{} - err := impl.start(ctx, dir, language, name) + err := impl.start(ctx, dir, language, name, devMode) if err != nil { return nil, err } @@ -52,20 +52,21 @@ func newClientImpl(ctx context.Context, dir, language, name string) (*pluginClie } // Start launches the plugin and blocks until the plugin is ready. -func (p *pluginClientImpl) start(ctx context.Context, dir, language, name string) error { +func (p *pluginClientImpl) start(ctx context.Context, dir, language, name string, devMode bool) error { cmdName := "ftl-language-" + language cmdPath, err := exec.LookPath(cmdName) + if err != nil { return fmt.Errorf("failed to find plugin for %s: %w", language, err) } - + envvars := []string{"FTL_NAME=" + name} plugin, cmdCtx, err := plugin.Spawn(ctx, log.FromContext(ctx).GetLevel(), name, dir, cmdPath, langconnect.NewLanguageServiceClient, - plugin.WithEnvars("FTL_NAME="+name), + plugin.WithEnvars(envvars...), ) if err != nil { return fmt.Errorf("failed to spawn plugin for %s: %w", name, err) diff --git a/internal/buildengine/languageplugin/plugin_test.go b/internal/buildengine/languageplugin/plugin_test.go index f1c94089d3..015cb66a97 100644 --- a/internal/buildengine/languageplugin/plugin_test.go +++ b/internal/buildengine/languageplugin/plugin_test.go @@ -14,6 +14,7 @@ import ( "github.com/alecthomas/types/result" "connectrpc.com/connect" + langpb "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1/language" "github.com/TBD54566975/ftl/internal/builderrors" "github.com/TBD54566975/ftl/internal/log" @@ -84,6 +85,7 @@ func buildContextFromProto(proto *langpb.BuildContext) (BuildContext, error) { Realm: "test", Module: proto.ModuleConfig.Name, Build: optional.Ptr(proto.ModuleConfig.Build).Default(""), + DevModeBuild: optional.Ptr(proto.ModuleConfig.DevModeBuild).Default(""), DeployDir: proto.ModuleConfig.DeployDir, GeneratedSchemaDir: optional.Ptr(proto.ModuleConfig.GeneratedSchemaDir).Default(""), Watch: proto.ModuleConfig.Watch, diff --git a/internal/moduleconfig/moduleconfig.go b/internal/moduleconfig/moduleconfig.go index 049dae54a6..09c1ccf4d8 100644 --- a/internal/moduleconfig/moduleconfig.go +++ b/internal/moduleconfig/moduleconfig.go @@ -24,6 +24,8 @@ type ModuleConfig struct { Module string `toml:"module"` // Build is the command to build the module. Build string `toml:"build"` + // Build is the command to build the module in dev mode. + DevModeBuild string `toml:"dev-mode-build"` // BuildLock is file lock path to prevent concurrent builds of a module. BuildLock string `toml:"build-lock"` // DeployDir is the directory to deploy from, relative to the module directory. @@ -58,6 +60,7 @@ type CustomDefaults struct { Watch []string BuildLock optional.Option[string] Build optional.Option[string] + DevModeBuild optional.Option[string] GeneratedSchemaDir optional.Option[string] // only the root keys in LanguageConfig are used to find missing values that can be defaulted @@ -150,6 +153,9 @@ func (c UnvalidatedModuleConfig) FillDefaultsAndValidate(customDefaults CustomDe if defaultValue, ok := customDefaults.Build.Get(); ok && c.Build == "" { c.Build = defaultValue } + if defaultValue, ok := customDefaults.DevModeBuild.Get(); ok && c.DevModeBuild == "" { + c.DevModeBuild = defaultValue + } if c.BuildLock == "" { if defaultValue, ok := customDefaults.BuildLock.Get(); ok { c.BuildLock = defaultValue diff --git a/internal/moduleconfig/moduleconfig_test.go b/internal/moduleconfig/moduleconfig_test.go index 7079047650..7bd837b3f8 100644 --- a/internal/moduleconfig/moduleconfig_test.go +++ b/internal/moduleconfig/moduleconfig_test.go @@ -23,6 +23,7 @@ func TestDefaulting(t *testing.T) { }, defaults: CustomDefaults{ Build: optional.Some("build"), + DevModeBuild: optional.Some("devmodebuild"), BuildLock: optional.Some("customdefaultlock"), DeployDir: "deploydir", GeneratedSchemaDir: optional.Some("generatedschemadir"), @@ -34,6 +35,7 @@ func TestDefaulting(t *testing.T) { Module: "nothingset", Language: "test", Build: "build", + DevModeBuild: "devmodebuild", BuildLock: "customdefaultlock", DeployDir: "deploydir", GeneratedSchemaDir: "generatedschemadir", @@ -46,6 +48,7 @@ func TestDefaulting(t *testing.T) { Module: "allset", Language: "test", Build: "custombuild", + DevModeBuild: "customdevmodebuild", BuildLock: "custombuildlock", DeployDir: "customdeploydir", GeneratedSchemaDir: "customgeneratedschemadir", @@ -57,6 +60,7 @@ func TestDefaulting(t *testing.T) { }, defaults: CustomDefaults{ Build: optional.Some("build"), + DevModeBuild: optional.Some("devmodebuild"), DeployDir: "deploydir", GeneratedSchemaDir: optional.Some("generatedschemadir"), Watch: []string{"a", "b", "c"}, @@ -67,6 +71,7 @@ func TestDefaulting(t *testing.T) { Module: "allset", Language: "test", Build: "custombuild", + DevModeBuild: "customdevmodebuild", BuildLock: "custombuildlock", DeployDir: "customdeploydir", GeneratedSchemaDir: "customgeneratedschemadir", diff --git a/jvm-runtime/ftl-runtime/common/deployment/pom.xml b/jvm-runtime/ftl-runtime/common/deployment/pom.xml index 72d4d26f41..0e80fcb3c1 100644 --- a/jvm-runtime/ftl-runtime/common/deployment/pom.xml +++ b/jvm-runtime/ftl-runtime/common/deployment/pom.xml @@ -23,6 +23,10 @@ io.quarkus quarkus-rest-jackson-deployment + + io.quarkus + quarkus-smallrye-health-deployment + io.quarkus quarkus-opentelemetry-deployment diff --git a/jvm-runtime/ftl-runtime/common/runtime/pom.xml b/jvm-runtime/ftl-runtime/common/runtime/pom.xml index 19feaf84ed..48a3bdeb5d 100644 --- a/jvm-runtime/ftl-runtime/common/runtime/pom.xml +++ b/jvm-runtime/ftl-runtime/common/runtime/pom.xml @@ -20,6 +20,10 @@ io.quarkus quarkus-grpc + + io.quarkus + quarkus-smallrye-health + io.quarkus quarkus-rest-jackson diff --git a/jvm-runtime/plugin/common/java_plugin_test.go b/jvm-runtime/plugin/common/java_plugin_test.go index ca21dc5c5f..2199fd6748 100644 --- a/jvm-runtime/plugin/common/java_plugin_test.go +++ b/jvm-runtime/plugin/common/java_plugin_test.go @@ -37,6 +37,7 @@ func TestJavaConfigDefaults(t *testing.T) { dir: "testdata/kotlin/echo", expected: moduleconfig.CustomDefaults{ Build: optional.Some("mvn -B package"), + DevModeBuild: optional.Some("mvn quarkus:dev"), DeployDir: "target", GeneratedSchemaDir: optional.Some("src/main/ftl-module-schema"), Watch: watch, @@ -50,6 +51,7 @@ func TestJavaConfigDefaults(t *testing.T) { dir: "testdata/kotlin/external", expected: moduleconfig.CustomDefaults{ Build: optional.Some("mvn -B package"), + DevModeBuild: optional.Some("mvn quarkus:dev"), DeployDir: "target", GeneratedSchemaDir: optional.Some("src/main/ftl-module-schema"), Watch: watch, @@ -67,7 +69,7 @@ func TestJavaConfigDefaults(t *testing.T) { dir, err := filepath.Abs(tt.dir) assert.NoError(t, err) - plugin, err := languageplugin.New(ctx, t.TempDir(), "java", "test") + plugin, err := languageplugin.New(ctx, t.TempDir(), "java", "test", false) assert.NoError(t, err) t.Cleanup(func() { _ = plugin.Kill() //nolint:errcheck diff --git a/jvm-runtime/plugin/common/jvmcommon.go b/jvm-runtime/plugin/common/jvmcommon.go index 890827598b..6579fc4e8e 100644 --- a/jvm-runtime/plugin/common/jvmcommon.go +++ b/jvm-runtime/plugin/common/jvmcommon.go @@ -6,6 +6,7 @@ import ( "context" "fmt" "io/fs" + "net/http" "os" "path/filepath" "regexp" @@ -29,6 +30,7 @@ import ( langpb "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1/language" langconnect "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1/language/languagepbconnect" schemapb "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1/schema" + "github.com/TBD54566975/ftl/common/plugin" "github.com/TBD54566975/ftl/internal" "github.com/TBD54566975/ftl/internal/builderrors" "github.com/TBD54566975/ftl/internal/errors" @@ -37,11 +39,14 @@ import ( "github.com/TBD54566975/ftl/internal/log" "github.com/TBD54566975/ftl/internal/moduleconfig" "github.com/TBD54566975/ftl/internal/schema" + "github.com/TBD54566975/ftl/internal/sha256" islices "github.com/TBD54566975/ftl/internal/slices" "github.com/TBD54566975/ftl/internal/watch" ) const BuildLockTimeout = time.Minute +const SchemaFile = "schema.pb" +const ErrorFile = "errors.pb" //sumtype:decl type updateEvent interface{ updateEvent() } @@ -167,6 +172,9 @@ func (s *Service) SyncStubReferences(ctx context.Context, req *connect.Request[l // rebuild must include the latest build context id provided by the request or subsequent BuildContextUpdated // calls. func (s *Service) Build(ctx context.Context, req *connect.Request[langpb.BuildRequest], stream *connect.ServerStream[langpb.BuildEvent]) error { + if req.Msg.RebuildAutomatically { + return s.handleDevModeRequest(ctx, req, stream) + } events := make(chan updateEvent, 32) s.updatesTopic.Subscribe(events) defer s.updatesTopic.Unsubscribe(events) @@ -231,6 +239,139 @@ func (s *Service) Build(ctx context.Context, req *connect.Request[langpb.BuildRe } } +func (s *Service) handleDevModeRequest(ctx context.Context, req *connect.Request[langpb.BuildRequest], stream *connect.ServerStream[langpb.BuildEvent]) error { + logger := log.FromContext(ctx) + + first := true + buildCtx, err := buildContextFromProto(req.Msg.BuildContext) + if err != nil { + return err + } + address, err := plugin.AllocatePort() + if err != nil { + return fmt.Errorf("could not allocate port: %w", err) + } + errorFile := filepath.Join(buildCtx.Config.DeployDir, ErrorFile) + schemaFile := filepath.Join(buildCtx.Config.DeployDir, SchemaFile) + os.Remove(errorFile) + os.Remove(schemaFile) + errorHash := sha256.SHA256{} + schemaHash := sha256.SHA256{} + + ctx, cancel := context.WithCancel(context.Background()) + ctx = log.ContextWithLogger(ctx, logger) + bind := fmt.Sprintf("http://localhost:%d", address.Port) + go func() { + logger.Infof("Using build command '%s'", buildCtx.Config.DevModeBuild) + command := exec.Command(ctx, log.Debug, buildCtx.Config.Dir, "bash", "-c", buildCtx.Config.DevModeBuild) + command.Env = append(command.Env, fmt.Sprintf("FTL_BIND=%s", bind)) + command.Stdout = os.Stdout + command.Stderr = os.Stderr + err = command.RunBuffered(ctx) + if err != nil { + logger.Errorf(err, "Dev mode process exited with error") + } else { + logger.Infof("Dev mode process exited") + } + cancel() + }() + + schemaChangeTicker := time.Tick(100 * time.Millisecond) + forceReloadTicker := time.Tick(time.Second) + for { + select { + case <-ctx.Done(): + return nil + case <-schemaChangeTicker: + select { + // We only force a reload every second, but we check for schema changes every 100ms + case <-forceReloadTicker: + if !first { + // Force a hot reload via HTTP request + resp, err := http.Head(bind) + if err == nil { + resp.Body.Close() + } + } + default: + } + + changed := false + file, err := os.ReadFile(errorFile) + if err == nil { + sum := sha256.Sum(file) + if sum != errorHash { + changed = true + errorHash = sum + } + } + file, err = os.ReadFile(schemaFile) + if err == nil { + sum := sha256.Sum(file) + if sum != schemaHash { + changed = true + schemaHash = sum + } + } + if changed { + if !first { + logger.Infof("New schema detected, doing fast redeploy") + } + buildErrs, err := loadProtoErrors(buildCtx.Config) + if err != nil { + _ = stream.Send(&langpb.BuildEvent{Event: &langpb.BuildEvent_BuildFailure{ + BuildFailure: &langpb.BuildFailure{ + IsAutomaticRebuild: !first, + ContextId: buildCtx.ID, + Errors: &langpb.ErrorList{Errors: []*langpb.Error{{Msg: err.Error(), Level: langpb.Error_ERROR, Type: langpb.Error_FTL}}}, + }}}) + first = false + continue + } + if builderrors.ContainsTerminalError(langpb.ErrorsFromProto(buildErrs)) { + // skip reading schema + _ = stream.Send(&langpb.BuildEvent{Event: &langpb.BuildEvent_BuildFailure{ + BuildFailure: &langpb.BuildFailure{ + IsAutomaticRebuild: !first, + ContextId: buildCtx.ID, + Errors: buildErrs, + }}}) + first = false + continue + } + + moduleProto, err := readSchema(buildCtx) + if err != nil { + _ = stream.Send(&langpb.BuildEvent{Event: &langpb.BuildEvent_BuildFailure{ + BuildFailure: &langpb.BuildFailure{ + IsAutomaticRebuild: !first, + ContextId: buildCtx.ID, + Errors: &langpb.ErrorList{Errors: []*langpb.Error{{Msg: err.Error(), Level: langpb.Error_ERROR, Type: langpb.Error_FTL}}}, + }}}) + first = false + continue + } + + err = stream.Send(&langpb.BuildEvent{ + Event: &langpb.BuildEvent_BuildSuccess{ + BuildSuccess: &langpb.BuildSuccess{ + ContextId: req.Msg.BuildContext.Id, + IsAutomaticRebuild: !first, + Module: moduleProto, + DevEndpoint: ptr(fmt.Sprintf("http://localhost:%d", address.Port)), + Deploy: []string{SchemaFile}, + }, + }, + }) + first = false + } + + } + } + + return nil +} + func build(ctx context.Context, bctx buildContext, autoRebuild bool, transaction watch.ModifyFilesTransaction) (*langpb.BuildEvent, error) { logger := log.FromContext(ctx) release, err := flock.Acquire(ctx, bctx.Config.BuildLock, BuildLockTimeout) @@ -301,19 +442,10 @@ func build(ctx context.Context, bctx buildContext, autoRebuild bool, transaction }}}, nil } - moduleSchema, err := schema.ModuleFromProtoFile(filepath.Join(config.DeployDir, "schema.pb")) + moduleProto, err := readSchema(bctx) if err != nil { - return nil, fmt.Errorf("failed to read schema for module: %w", err) - } - - moduleSchema.Runtime = &schema.ModuleRuntime{ - CreateTime: time.Now(), - Language: bctx.Config.Language, - MinReplicas: 1, - Image: "ftl0/ftl-runner-jvm", + return nil, err } - - moduleProto := moduleSchema.ToProto().(*schemapb.Module) //nolint:forcetypeassert return &langpb.BuildEvent{ Event: &langpb.BuildEvent_BuildSuccess{ BuildSuccess: &langpb.BuildSuccess{ @@ -327,6 +459,24 @@ func build(ctx context.Context, bctx buildContext, autoRebuild bool, transaction }, nil } +func readSchema(bctx buildContext) (*schemapb.Module, error) { + path := filepath.Join(bctx.Config.DeployDir, SchemaFile) + moduleSchema, err := schema.ModuleFromProtoFile(path) + if err != nil { + return nil, fmt.Errorf("failed to read schema for module: %s from %s %w", bctx.Config.Module, path, err) + } + + moduleSchema.Runtime = &schema.ModuleRuntime{ + CreateTime: time.Now(), + Language: bctx.Config.Language, + MinReplicas: 1, + Image: "ftl0/ftl-runner-jvm", + } + + moduleProto := moduleSchema.ToProto().(*schemapb.Module) //nolint:forcetypeassert + return moduleProto, nil +} + // BuildContextUpdated is called whenever the build context is update while a Build call with "rebuild_automatically" is active. // // Each time this call is made, the Build call must send back a corresponding BuildSuccess or BuildFailure @@ -493,10 +643,12 @@ func (s *Service) ModuleConfigDefaults(ctx context.Context, req *connect.Request buildGradleKts := filepath.Join(dir, "build.gradle.kts") if fileExists(pom) { defaults.LanguageConfig.Fields["build-tool"] = structpb.NewStringValue(JavaBuildToolMaven) + defaults.DevModeBuild = ptr("mvn quarkus:dev") defaults.Build = ptr("mvn -B package") defaults.DeployDir = "target" } else if fileExists(buildGradle) || fileExists(buildGradleKts) { defaults.LanguageConfig.Fields["build-tool"] = structpb.NewStringValue(JavaBuildToolGradle) + defaults.DevModeBuild = ptr("gradle quarkusDev") defaults.Build = ptr("gradle build") defaults.DeployDir = "build" } else { diff --git a/python-runtime/ftl/src/ftl/protos/xyz/block/ftl/v1/language/language_pb2.py b/python-runtime/ftl/src/ftl/protos/xyz/block/ftl/v1/language/language_pb2.py index cbe141552f..b54318260a 100644 --- a/python-runtime/ftl/src/ftl/protos/xyz/block/ftl/v1/language/language_pb2.py +++ b/python-runtime/ftl/src/ftl/protos/xyz/block/ftl/v1/language/language_pb2.py @@ -27,7 +27,7 @@ from xyz.block.ftl.v1.schema import schema_pb2 as xyz_dot_block_dot_ftl_dot_v1_dot_schema_dot_schema__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n(xyz/block/ftl/v1/language/language.proto\x12\x19xyz.block.ftl.v1.language\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1axyz/block/ftl/v1/ftl.proto\x1a$xyz/block/ftl/v1/schema/schema.proto\"\xdb\x02\n\x0cModuleConfig\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x10\n\x03\x64ir\x18\x02 \x01(\tR\x03\x64ir\x12\x1a\n\x08language\x18\x03 \x01(\tR\x08language\x12\x1d\n\ndeploy_dir\x18\x04 \x01(\tR\tdeployDir\x12\x19\n\x05\x62uild\x18\x05 \x01(\tH\x00R\x05\x62uild\x88\x01\x01\x12\x1d\n\nbuild_lock\x18\x06 \x01(\tR\tbuildLock\x12\x35\n\x14generated_schema_dir\x18\x07 \x01(\tH\x01R\x12generatedSchemaDir\x88\x01\x01\x12\x14\n\x05watch\x18\x08 \x03(\tR\x05watch\x12@\n\x0flanguage_config\x18\t \x01(\x0b\x32\x17.google.protobuf.StructR\x0elanguageConfigB\x08\n\x06_buildB\x17\n\x15_generated_schema_dir\"d\n\rProjectConfig\x12\x10\n\x03\x64ir\x18\x01 \x01(\tR\x03\x64ir\x12\x12\n\x04name\x18\x02 \x01(\tR\x04name\x12\x15\n\x06no_git\x18\x03 \x01(\x08R\x05noGit\x12\x16\n\x06hermit\x18\x04 \x01(\x08R\x06hermit\"9\n\x1bGetCreateModuleFlagsRequest\x12\x1a\n\x08language\x18\x01 \x01(\tR\x08language\"\xcf\x02\n\x1cGetCreateModuleFlagsResponse\x12R\n\x05\x66lags\x18\x01 \x03(\x0b\x32<.xyz.block.ftl.v1.language.GetCreateModuleFlagsResponse.FlagR\x05\x66lags\x1a\xda\x01\n\x04\x46lag\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x12\n\x04help\x18\x02 \x01(\tR\x04help\x12\x19\n\x05\x65nvar\x18\x03 \x01(\tH\x00R\x05\x65nvar\x88\x01\x01\x12\x19\n\x05short\x18\x04 \x01(\tH\x01R\x05short\x88\x01\x01\x12%\n\x0bplaceholder\x18\x05 \x01(\tH\x02R\x0bplaceholder\x88\x01\x01\x12\x1d\n\x07\x64\x65\x66\x61ult\x18\x06 \x01(\tH\x03R\x07\x64\x65\x66\x61ult\x88\x01\x01\x42\x08\n\x06_envarB\x08\n\x06_shortB\x0e\n\x0c_placeholderB\n\n\x08_default\"\xbb\x01\n\x13\x43reateModuleRequest\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x10\n\x03\x64ir\x18\x02 \x01(\tR\x03\x64ir\x12O\n\x0eproject_config\x18\x03 \x01(\x0b\x32(.xyz.block.ftl.v1.language.ProjectConfigR\rprojectConfig\x12-\n\x05\x46lags\x18\x04 \x01(\x0b\x32\x17.google.protobuf.StructR\x05\x46lags\"\x16\n\x14\x43reateModuleResponse\"/\n\x1bModuleConfigDefaultsRequest\x12\x10\n\x03\x64ir\x18\x01 \x01(\tR\x03\x64ir\"\xbd\x02\n\x1cModuleConfigDefaultsResponse\x12\x1d\n\ndeploy_dir\x18\x01 \x01(\tR\tdeployDir\x12\x19\n\x05\x62uild\x18\x02 \x01(\tH\x00R\x05\x62uild\x88\x01\x01\x12\"\n\nbuild_lock\x18\x03 \x01(\tH\x01R\tbuildLock\x88\x01\x01\x12\x35\n\x14generated_schema_dir\x18\x04 \x01(\tH\x02R\x12generatedSchemaDir\x88\x01\x01\x12\x14\n\x05watch\x18\x05 \x03(\tR\x05watch\x12@\n\x0flanguage_config\x18\x06 \x01(\x0b\x32\x17.google.protobuf.StructR\x0elanguageConfigB\x08\n\x06_buildB\r\n\x0b_build_lockB\x17\n\x15_generated_schema_dir\"c\n\x13\x44\x65pendenciesRequest\x12L\n\rmodule_config\x18\x01 \x01(\x0b\x32\'.xyz.block.ftl.v1.language.ModuleConfigR\x0cmoduleConfig\"0\n\x14\x44\x65pendenciesResponse\x12\x18\n\x07modules\x18\x01 \x03(\tR\x07modules\"\xe6\x01\n\x0c\x42uildContext\x12\x0e\n\x02id\x18\x01 \x01(\tR\x02id\x12L\n\rmodule_config\x18\x02 \x01(\x0b\x32\'.xyz.block.ftl.v1.language.ModuleConfigR\x0cmoduleConfig\x12\x37\n\x06schema\x18\x03 \x01(\x0b\x32\x1f.xyz.block.ftl.v1.schema.SchemaR\x06schema\x12\"\n\x0c\x64\x65pendencies\x18\x04 \x03(\tR\x0c\x64\x65pendencies\x12\x1b\n\tbuild_env\x18\x05 \x03(\tR\x08\x62uildEnv\"i\n\x1a\x42uildContextUpdatedRequest\x12K\n\x0c\x62uildContext\x18\x01 \x01(\x0b\x32\'.xyz.block.ftl.v1.language.BuildContextR\x0c\x62uildContext\"\x1d\n\x1b\x42uildContextUpdatedResponse\"\xb1\x02\n\x05\x45rror\x12\x10\n\x03msg\x18\x01 \x01(\tR\x03msg\x12\x41\n\x05level\x18\x04 \x01(\x0e\x32+.xyz.block.ftl.v1.language.Error.ErrorLevelR\x05level\x12:\n\x03pos\x18\x05 \x01(\x0b\x32#.xyz.block.ftl.v1.language.PositionH\x00R\x03pos\x88\x01\x01\x12>\n\x04type\x18\x06 \x01(\x0e\x32*.xyz.block.ftl.v1.language.Error.ErrorTypeR\x04type\"+\n\nErrorLevel\x12\x08\n\x04INFO\x10\x00\x12\x08\n\x04WARN\x10\x01\x12\t\n\x05\x45RROR\x10\x02\"\"\n\tErrorType\x12\x07\n\x03\x46TL\x10\x00\x12\x0c\n\x08\x43OMPILER\x10\x01\x42\x06\n\x04_pos\"z\n\x08Position\x12\x1a\n\x08\x66ilename\x18\x01 \x01(\tR\x08\x66ilename\x12\x12\n\x04line\x18\x02 \x01(\x03R\x04line\x12 \n\x0bstartColumn\x18\x03 \x01(\x03R\x0bstartColumn\x12\x1c\n\tendColumn\x18\x04 \x01(\x03R\tendColumn\"E\n\tErrorList\x12\x38\n\x06\x65rrors\x18\x01 \x03(\x0b\x32 .xyz.block.ftl.v1.language.ErrorR\x06\x65rrors\"\xd3\x01\n\x0c\x42uildRequest\x12!\n\x0cproject_root\x18\x01 \x01(\tR\x0bprojectRoot\x12\x1d\n\nstubs_root\x18\x02 \x01(\tR\tstubsRoot\x12\x33\n\x15rebuild_automatically\x18\x03 \x01(\x08R\x14rebuildAutomatically\x12L\n\rbuild_context\x18\x04 \x01(\x0b\x32\'.xyz.block.ftl.v1.language.BuildContextR\x0c\x62uildContext\"3\n\x12\x41utoRebuildStarted\x12\x1d\n\ncontext_id\x18\x01 \x01(\tR\tcontextId\"\xee\x01\n\x0c\x42uildSuccess\x12\x1d\n\ncontext_id\x18\x01 \x01(\tR\tcontextId\x12\x30\n\x14is_automatic_rebuild\x18\x02 \x01(\x08R\x12isAutomaticRebuild\x12\x37\n\x06module\x18\x03 \x01(\x0b\x32\x1f.xyz.block.ftl.v1.schema.ModuleR\x06module\x12\x16\n\x06\x64\x65ploy\x18\x04 \x03(\tR\x06\x64\x65ploy\x12<\n\x06\x65rrors\x18\x05 \x01(\x0b\x32$.xyz.block.ftl.v1.language.ErrorListR\x06\x65rrors\"\xd6\x01\n\x0c\x42uildFailure\x12\x1d\n\ncontext_id\x18\x01 \x01(\tR\tcontextId\x12\x30\n\x14is_automatic_rebuild\x18\x02 \x01(\x08R\x12isAutomaticRebuild\x12<\n\x06\x65rrors\x18\x03 \x01(\x0b\x32$.xyz.block.ftl.v1.language.ErrorListR\x06\x65rrors\x12\x37\n\x17invalidate_dependencies\x18\x04 \x01(\x08R\x16invalidateDependencies\"\x98\x02\n\nBuildEvent\x12\x61\n\x14\x61uto_rebuild_started\x18\x02 \x01(\x0b\x32-.xyz.block.ftl.v1.language.AutoRebuildStartedH\x00R\x12\x61utoRebuildStarted\x12N\n\rbuild_success\x18\x03 \x01(\x0b\x32\'.xyz.block.ftl.v1.language.BuildSuccessH\x00R\x0c\x62uildSuccess\x12N\n\rbuild_failure\x18\x04 \x01(\x0b\x32\'.xyz.block.ftl.v1.language.BuildFailureH\x00R\x0c\x62uildFailureB\x07\n\x05\x65vent\"\xa8\x02\n\x14GenerateStubsRequest\x12\x10\n\x03\x64ir\x18\x01 \x01(\tR\x03\x64ir\x12\x37\n\x06module\x18\x02 \x01(\x0b\x32\x1f.xyz.block.ftl.v1.schema.ModuleR\x06module\x12L\n\rmodule_config\x18\x03 \x01(\x0b\x32\'.xyz.block.ftl.v1.language.ModuleConfigR\x0cmoduleConfig\x12^\n\x14native_module_config\x18\x04 \x01(\x0b\x32\'.xyz.block.ftl.v1.language.ModuleConfigH\x00R\x12nativeModuleConfig\x88\x01\x01\x42\x17\n\x15_native_module_config\"\x17\n\x15GenerateStubsResponse\"\xa2\x01\n\x19SyncStubReferencesRequest\x12L\n\rmodule_config\x18\x01 \x01(\x0b\x32\'.xyz.block.ftl.v1.language.ModuleConfigR\x0cmoduleConfig\x12\x1d\n\nstubs_root\x18\x02 \x01(\tR\tstubsRoot\x12\x18\n\x07modules\x18\x03 \x03(\tR\x07modules\"\x1c\n\x1aSyncStubReferencesResponse2\xb0\x08\n\x0fLanguageService\x12J\n\x04Ping\x12\x1d.xyz.block.ftl.v1.PingRequest\x1a\x1e.xyz.block.ftl.v1.PingResponse\"\x03\x90\x02\x01\x12\x87\x01\n\x14GetCreateModuleFlags\x12\x36.xyz.block.ftl.v1.language.GetCreateModuleFlagsRequest\x1a\x37.xyz.block.ftl.v1.language.GetCreateModuleFlagsResponse\x12o\n\x0c\x43reateModule\x12..xyz.block.ftl.v1.language.CreateModuleRequest\x1a/.xyz.block.ftl.v1.language.CreateModuleResponse\x12\x87\x01\n\x14ModuleConfigDefaults\x12\x36.xyz.block.ftl.v1.language.ModuleConfigDefaultsRequest\x1a\x37.xyz.block.ftl.v1.language.ModuleConfigDefaultsResponse\x12r\n\x0fGetDependencies\x12..xyz.block.ftl.v1.language.DependenciesRequest\x1a/.xyz.block.ftl.v1.language.DependenciesResponse\x12Y\n\x05\x42uild\x12\'.xyz.block.ftl.v1.language.BuildRequest\x1a%.xyz.block.ftl.v1.language.BuildEvent0\x01\x12\x84\x01\n\x13\x42uildContextUpdated\x12\x35.xyz.block.ftl.v1.language.BuildContextUpdatedRequest\x1a\x36.xyz.block.ftl.v1.language.BuildContextUpdatedResponse\x12r\n\rGenerateStubs\x12/.xyz.block.ftl.v1.language.GenerateStubsRequest\x1a\x30.xyz.block.ftl.v1.language.GenerateStubsResponse\x12\x81\x01\n\x12SyncStubReferences\x12\x34.xyz.block.ftl.v1.language.SyncStubReferencesRequest\x1a\x35.xyz.block.ftl.v1.language.SyncStubReferencesResponseBRP\x01ZNgithub.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1/language;languagepbb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n(xyz/block/ftl/v1/language/language.proto\x12\x19xyz.block.ftl.v1.language\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1axyz/block/ftl/v1/ftl.proto\x1a$xyz/block/ftl/v1/schema/schema.proto\"\x95\x03\n\x0cModuleConfig\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x10\n\x03\x64ir\x18\x02 \x01(\tR\x03\x64ir\x12\x1a\n\x08language\x18\x03 \x01(\tR\x08language\x12\x1d\n\ndeploy_dir\x18\x04 \x01(\tR\tdeployDir\x12\x19\n\x05\x62uild\x18\x05 \x01(\tH\x00R\x05\x62uild\x88\x01\x01\x12\'\n\x0c\x64\x65vModeBuild\x18\x06 \x01(\tH\x01R\x0c\x64\x65vModeBuild\x88\x01\x01\x12\x1d\n\nbuild_lock\x18\x07 \x01(\tR\tbuildLock\x12\x35\n\x14generated_schema_dir\x18\x08 \x01(\tH\x02R\x12generatedSchemaDir\x88\x01\x01\x12\x14\n\x05watch\x18\t \x03(\tR\x05watch\x12@\n\x0flanguage_config\x18\n \x01(\x0b\x32\x17.google.protobuf.StructR\x0elanguageConfigB\x08\n\x06_buildB\x0f\n\r_devModeBuildB\x17\n\x15_generated_schema_dir\"d\n\rProjectConfig\x12\x10\n\x03\x64ir\x18\x01 \x01(\tR\x03\x64ir\x12\x12\n\x04name\x18\x02 \x01(\tR\x04name\x12\x15\n\x06no_git\x18\x03 \x01(\x08R\x05noGit\x12\x16\n\x06hermit\x18\x04 \x01(\x08R\x06hermit\"9\n\x1bGetCreateModuleFlagsRequest\x12\x1a\n\x08language\x18\x01 \x01(\tR\x08language\"\xcf\x02\n\x1cGetCreateModuleFlagsResponse\x12R\n\x05\x66lags\x18\x01 \x03(\x0b\x32<.xyz.block.ftl.v1.language.GetCreateModuleFlagsResponse.FlagR\x05\x66lags\x1a\xda\x01\n\x04\x46lag\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x12\n\x04help\x18\x02 \x01(\tR\x04help\x12\x19\n\x05\x65nvar\x18\x03 \x01(\tH\x00R\x05\x65nvar\x88\x01\x01\x12\x19\n\x05short\x18\x04 \x01(\tH\x01R\x05short\x88\x01\x01\x12%\n\x0bplaceholder\x18\x05 \x01(\tH\x02R\x0bplaceholder\x88\x01\x01\x12\x1d\n\x07\x64\x65\x66\x61ult\x18\x06 \x01(\tH\x03R\x07\x64\x65\x66\x61ult\x88\x01\x01\x42\x08\n\x06_envarB\x08\n\x06_shortB\x0e\n\x0c_placeholderB\n\n\x08_default\"\xbb\x01\n\x13\x43reateModuleRequest\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x10\n\x03\x64ir\x18\x02 \x01(\tR\x03\x64ir\x12O\n\x0eproject_config\x18\x03 \x01(\x0b\x32(.xyz.block.ftl.v1.language.ProjectConfigR\rprojectConfig\x12-\n\x05\x46lags\x18\x04 \x01(\x0b\x32\x17.google.protobuf.StructR\x05\x46lags\"\x16\n\x14\x43reateModuleResponse\"/\n\x1bModuleConfigDefaultsRequest\x12\x10\n\x03\x64ir\x18\x01 \x01(\tR\x03\x64ir\"\xf7\x02\n\x1cModuleConfigDefaultsResponse\x12\x1d\n\ndeploy_dir\x18\x01 \x01(\tR\tdeployDir\x12\x19\n\x05\x62uild\x18\x02 \x01(\tH\x00R\x05\x62uild\x88\x01\x01\x12\'\n\x0c\x64\x65vModeBuild\x18\x03 \x01(\tH\x01R\x0c\x64\x65vModeBuild\x88\x01\x01\x12\"\n\nbuild_lock\x18\x04 \x01(\tH\x02R\tbuildLock\x88\x01\x01\x12\x35\n\x14generated_schema_dir\x18\x05 \x01(\tH\x03R\x12generatedSchemaDir\x88\x01\x01\x12\x14\n\x05watch\x18\x06 \x03(\tR\x05watch\x12@\n\x0flanguage_config\x18\x07 \x01(\x0b\x32\x17.google.protobuf.StructR\x0elanguageConfigB\x08\n\x06_buildB\x0f\n\r_devModeBuildB\r\n\x0b_build_lockB\x17\n\x15_generated_schema_dir\"c\n\x13\x44\x65pendenciesRequest\x12L\n\rmodule_config\x18\x01 \x01(\x0b\x32\'.xyz.block.ftl.v1.language.ModuleConfigR\x0cmoduleConfig\"0\n\x14\x44\x65pendenciesResponse\x12\x18\n\x07modules\x18\x01 \x03(\tR\x07modules\"\xe6\x01\n\x0c\x42uildContext\x12\x0e\n\x02id\x18\x01 \x01(\tR\x02id\x12L\n\rmodule_config\x18\x02 \x01(\x0b\x32\'.xyz.block.ftl.v1.language.ModuleConfigR\x0cmoduleConfig\x12\x37\n\x06schema\x18\x03 \x01(\x0b\x32\x1f.xyz.block.ftl.v1.schema.SchemaR\x06schema\x12\"\n\x0c\x64\x65pendencies\x18\x04 \x03(\tR\x0c\x64\x65pendencies\x12\x1b\n\tbuild_env\x18\x05 \x03(\tR\x08\x62uildEnv\"i\n\x1a\x42uildContextUpdatedRequest\x12K\n\x0c\x62uildContext\x18\x01 \x01(\x0b\x32\'.xyz.block.ftl.v1.language.BuildContextR\x0c\x62uildContext\"\x1d\n\x1b\x42uildContextUpdatedResponse\"\xb1\x02\n\x05\x45rror\x12\x10\n\x03msg\x18\x01 \x01(\tR\x03msg\x12\x41\n\x05level\x18\x04 \x01(\x0e\x32+.xyz.block.ftl.v1.language.Error.ErrorLevelR\x05level\x12:\n\x03pos\x18\x05 \x01(\x0b\x32#.xyz.block.ftl.v1.language.PositionH\x00R\x03pos\x88\x01\x01\x12>\n\x04type\x18\x06 \x01(\x0e\x32*.xyz.block.ftl.v1.language.Error.ErrorTypeR\x04type\"+\n\nErrorLevel\x12\x08\n\x04INFO\x10\x00\x12\x08\n\x04WARN\x10\x01\x12\t\n\x05\x45RROR\x10\x02\"\"\n\tErrorType\x12\x07\n\x03\x46TL\x10\x00\x12\x0c\n\x08\x43OMPILER\x10\x01\x42\x06\n\x04_pos\"z\n\x08Position\x12\x1a\n\x08\x66ilename\x18\x01 \x01(\tR\x08\x66ilename\x12\x12\n\x04line\x18\x02 \x01(\x03R\x04line\x12 \n\x0bstartColumn\x18\x03 \x01(\x03R\x0bstartColumn\x12\x1c\n\tendColumn\x18\x04 \x01(\x03R\tendColumn\"E\n\tErrorList\x12\x38\n\x06\x65rrors\x18\x01 \x03(\x0b\x32 .xyz.block.ftl.v1.language.ErrorR\x06\x65rrors\"\xd3\x01\n\x0c\x42uildRequest\x12!\n\x0cproject_root\x18\x01 \x01(\tR\x0bprojectRoot\x12\x1d\n\nstubs_root\x18\x02 \x01(\tR\tstubsRoot\x12\x33\n\x15rebuild_automatically\x18\x03 \x01(\x08R\x14rebuildAutomatically\x12L\n\rbuild_context\x18\x04 \x01(\x0b\x32\'.xyz.block.ftl.v1.language.BuildContextR\x0c\x62uildContext\"3\n\x12\x41utoRebuildStarted\x12\x1d\n\ncontext_id\x18\x01 \x01(\tR\tcontextId\"\xca\x02\n\x0c\x42uildSuccess\x12\x1d\n\ncontext_id\x18\x01 \x01(\tR\tcontextId\x12\x30\n\x14is_automatic_rebuild\x18\x02 \x01(\x08R\x12isAutomaticRebuild\x12\x37\n\x06module\x18\x03 \x01(\x0b\x32\x1f.xyz.block.ftl.v1.schema.ModuleR\x06module\x12\x16\n\x06\x64\x65ploy\x18\x04 \x03(\tR\x06\x64\x65ploy\x12!\n\x0c\x64ocker_image\x18\x05 \x01(\tR\x0b\x64ockerImage\x12<\n\x06\x65rrors\x18\x06 \x01(\x0b\x32$.xyz.block.ftl.v1.language.ErrorListR\x06\x65rrors\x12&\n\x0c\x64\x65v_endpoint\x18\x07 \x01(\tH\x00R\x0b\x64\x65vEndpoint\x88\x01\x01\x42\x0f\n\r_dev_endpoint\"\xd6\x01\n\x0c\x42uildFailure\x12\x1d\n\ncontext_id\x18\x01 \x01(\tR\tcontextId\x12\x30\n\x14is_automatic_rebuild\x18\x02 \x01(\x08R\x12isAutomaticRebuild\x12<\n\x06\x65rrors\x18\x03 \x01(\x0b\x32$.xyz.block.ftl.v1.language.ErrorListR\x06\x65rrors\x12\x37\n\x17invalidate_dependencies\x18\x04 \x01(\x08R\x16invalidateDependencies\"\x98\x02\n\nBuildEvent\x12\x61\n\x14\x61uto_rebuild_started\x18\x02 \x01(\x0b\x32-.xyz.block.ftl.v1.language.AutoRebuildStartedH\x00R\x12\x61utoRebuildStarted\x12N\n\rbuild_success\x18\x03 \x01(\x0b\x32\'.xyz.block.ftl.v1.language.BuildSuccessH\x00R\x0c\x62uildSuccess\x12N\n\rbuild_failure\x18\x04 \x01(\x0b\x32\'.xyz.block.ftl.v1.language.BuildFailureH\x00R\x0c\x62uildFailureB\x07\n\x05\x65vent\"\xa8\x02\n\x14GenerateStubsRequest\x12\x10\n\x03\x64ir\x18\x01 \x01(\tR\x03\x64ir\x12\x37\n\x06module\x18\x02 \x01(\x0b\x32\x1f.xyz.block.ftl.v1.schema.ModuleR\x06module\x12L\n\rmodule_config\x18\x03 \x01(\x0b\x32\'.xyz.block.ftl.v1.language.ModuleConfigR\x0cmoduleConfig\x12^\n\x14native_module_config\x18\x04 \x01(\x0b\x32\'.xyz.block.ftl.v1.language.ModuleConfigH\x00R\x12nativeModuleConfig\x88\x01\x01\x42\x17\n\x15_native_module_config\"\x17\n\x15GenerateStubsResponse\"\xa2\x01\n\x19SyncStubReferencesRequest\x12L\n\rmodule_config\x18\x01 \x01(\x0b\x32\'.xyz.block.ftl.v1.language.ModuleConfigR\x0cmoduleConfig\x12\x1d\n\nstubs_root\x18\x02 \x01(\tR\tstubsRoot\x12\x18\n\x07modules\x18\x03 \x03(\tR\x07modules\"\x1c\n\x1aSyncStubReferencesResponse2\xb0\x08\n\x0fLanguageService\x12J\n\x04Ping\x12\x1d.xyz.block.ftl.v1.PingRequest\x1a\x1e.xyz.block.ftl.v1.PingResponse\"\x03\x90\x02\x01\x12\x87\x01\n\x14GetCreateModuleFlags\x12\x36.xyz.block.ftl.v1.language.GetCreateModuleFlagsRequest\x1a\x37.xyz.block.ftl.v1.language.GetCreateModuleFlagsResponse\x12o\n\x0c\x43reateModule\x12..xyz.block.ftl.v1.language.CreateModuleRequest\x1a/.xyz.block.ftl.v1.language.CreateModuleResponse\x12\x87\x01\n\x14ModuleConfigDefaults\x12\x36.xyz.block.ftl.v1.language.ModuleConfigDefaultsRequest\x1a\x37.xyz.block.ftl.v1.language.ModuleConfigDefaultsResponse\x12r\n\x0fGetDependencies\x12..xyz.block.ftl.v1.language.DependenciesRequest\x1a/.xyz.block.ftl.v1.language.DependenciesResponse\x12Y\n\x05\x42uild\x12\'.xyz.block.ftl.v1.language.BuildRequest\x1a%.xyz.block.ftl.v1.language.BuildEvent0\x01\x12\x84\x01\n\x13\x42uildContextUpdated\x12\x35.xyz.block.ftl.v1.language.BuildContextUpdatedRequest\x1a\x36.xyz.block.ftl.v1.language.BuildContextUpdatedResponse\x12r\n\rGenerateStubs\x12/.xyz.block.ftl.v1.language.GenerateStubsRequest\x1a\x30.xyz.block.ftl.v1.language.GenerateStubsResponse\x12\x81\x01\n\x12SyncStubReferences\x12\x34.xyz.block.ftl.v1.language.SyncStubReferencesRequest\x1a\x35.xyz.block.ftl.v1.language.SyncStubReferencesResponseBRP\x01ZNgithub.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1/language;languagepbb\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -38,61 +38,61 @@ _globals['_LANGUAGESERVICE'].methods_by_name['Ping']._loaded_options = None _globals['_LANGUAGESERVICE'].methods_by_name['Ping']._serialized_options = b'\220\002\001' _globals['_MODULECONFIG']._serialized_start=168 - _globals['_MODULECONFIG']._serialized_end=515 - _globals['_PROJECTCONFIG']._serialized_start=517 - _globals['_PROJECTCONFIG']._serialized_end=617 - _globals['_GETCREATEMODULEFLAGSREQUEST']._serialized_start=619 - _globals['_GETCREATEMODULEFLAGSREQUEST']._serialized_end=676 - _globals['_GETCREATEMODULEFLAGSRESPONSE']._serialized_start=679 - _globals['_GETCREATEMODULEFLAGSRESPONSE']._serialized_end=1014 - _globals['_GETCREATEMODULEFLAGSRESPONSE_FLAG']._serialized_start=796 - _globals['_GETCREATEMODULEFLAGSRESPONSE_FLAG']._serialized_end=1014 - _globals['_CREATEMODULEREQUEST']._serialized_start=1017 - _globals['_CREATEMODULEREQUEST']._serialized_end=1204 - _globals['_CREATEMODULERESPONSE']._serialized_start=1206 - _globals['_CREATEMODULERESPONSE']._serialized_end=1228 - _globals['_MODULECONFIGDEFAULTSREQUEST']._serialized_start=1230 - _globals['_MODULECONFIGDEFAULTSREQUEST']._serialized_end=1277 - _globals['_MODULECONFIGDEFAULTSRESPONSE']._serialized_start=1280 - _globals['_MODULECONFIGDEFAULTSRESPONSE']._serialized_end=1597 - _globals['_DEPENDENCIESREQUEST']._serialized_start=1599 - _globals['_DEPENDENCIESREQUEST']._serialized_end=1698 - _globals['_DEPENDENCIESRESPONSE']._serialized_start=1700 - _globals['_DEPENDENCIESRESPONSE']._serialized_end=1748 - _globals['_BUILDCONTEXT']._serialized_start=1751 - _globals['_BUILDCONTEXT']._serialized_end=1981 - _globals['_BUILDCONTEXTUPDATEDREQUEST']._serialized_start=1983 - _globals['_BUILDCONTEXTUPDATEDREQUEST']._serialized_end=2088 - _globals['_BUILDCONTEXTUPDATEDRESPONSE']._serialized_start=2090 - _globals['_BUILDCONTEXTUPDATEDRESPONSE']._serialized_end=2119 - _globals['_ERROR']._serialized_start=2122 - _globals['_ERROR']._serialized_end=2427 - _globals['_ERROR_ERRORLEVEL']._serialized_start=2340 - _globals['_ERROR_ERRORLEVEL']._serialized_end=2383 - _globals['_ERROR_ERRORTYPE']._serialized_start=2385 - _globals['_ERROR_ERRORTYPE']._serialized_end=2419 - _globals['_POSITION']._serialized_start=2429 - _globals['_POSITION']._serialized_end=2551 - _globals['_ERRORLIST']._serialized_start=2553 - _globals['_ERRORLIST']._serialized_end=2622 - _globals['_BUILDREQUEST']._serialized_start=2625 - _globals['_BUILDREQUEST']._serialized_end=2836 - _globals['_AUTOREBUILDSTARTED']._serialized_start=2838 - _globals['_AUTOREBUILDSTARTED']._serialized_end=2889 - _globals['_BUILDSUCCESS']._serialized_start=2892 - _globals['_BUILDSUCCESS']._serialized_end=3130 - _globals['_BUILDFAILURE']._serialized_start=3133 - _globals['_BUILDFAILURE']._serialized_end=3347 - _globals['_BUILDEVENT']._serialized_start=3350 - _globals['_BUILDEVENT']._serialized_end=3630 - _globals['_GENERATESTUBSREQUEST']._serialized_start=3633 - _globals['_GENERATESTUBSREQUEST']._serialized_end=3929 - _globals['_GENERATESTUBSRESPONSE']._serialized_start=3931 - _globals['_GENERATESTUBSRESPONSE']._serialized_end=3954 - _globals['_SYNCSTUBREFERENCESREQUEST']._serialized_start=3957 - _globals['_SYNCSTUBREFERENCESREQUEST']._serialized_end=4119 - _globals['_SYNCSTUBREFERENCESRESPONSE']._serialized_start=4121 - _globals['_SYNCSTUBREFERENCESRESPONSE']._serialized_end=4149 - _globals['_LANGUAGESERVICE']._serialized_start=4152 - _globals['_LANGUAGESERVICE']._serialized_end=5224 + _globals['_MODULECONFIG']._serialized_end=573 + _globals['_PROJECTCONFIG']._serialized_start=575 + _globals['_PROJECTCONFIG']._serialized_end=675 + _globals['_GETCREATEMODULEFLAGSREQUEST']._serialized_start=677 + _globals['_GETCREATEMODULEFLAGSREQUEST']._serialized_end=734 + _globals['_GETCREATEMODULEFLAGSRESPONSE']._serialized_start=737 + _globals['_GETCREATEMODULEFLAGSRESPONSE']._serialized_end=1072 + _globals['_GETCREATEMODULEFLAGSRESPONSE_FLAG']._serialized_start=854 + _globals['_GETCREATEMODULEFLAGSRESPONSE_FLAG']._serialized_end=1072 + _globals['_CREATEMODULEREQUEST']._serialized_start=1075 + _globals['_CREATEMODULEREQUEST']._serialized_end=1262 + _globals['_CREATEMODULERESPONSE']._serialized_start=1264 + _globals['_CREATEMODULERESPONSE']._serialized_end=1286 + _globals['_MODULECONFIGDEFAULTSREQUEST']._serialized_start=1288 + _globals['_MODULECONFIGDEFAULTSREQUEST']._serialized_end=1335 + _globals['_MODULECONFIGDEFAULTSRESPONSE']._serialized_start=1338 + _globals['_MODULECONFIGDEFAULTSRESPONSE']._serialized_end=1713 + _globals['_DEPENDENCIESREQUEST']._serialized_start=1715 + _globals['_DEPENDENCIESREQUEST']._serialized_end=1814 + _globals['_DEPENDENCIESRESPONSE']._serialized_start=1816 + _globals['_DEPENDENCIESRESPONSE']._serialized_end=1864 + _globals['_BUILDCONTEXT']._serialized_start=1867 + _globals['_BUILDCONTEXT']._serialized_end=2097 + _globals['_BUILDCONTEXTUPDATEDREQUEST']._serialized_start=2099 + _globals['_BUILDCONTEXTUPDATEDREQUEST']._serialized_end=2204 + _globals['_BUILDCONTEXTUPDATEDRESPONSE']._serialized_start=2206 + _globals['_BUILDCONTEXTUPDATEDRESPONSE']._serialized_end=2235 + _globals['_ERROR']._serialized_start=2238 + _globals['_ERROR']._serialized_end=2543 + _globals['_ERROR_ERRORLEVEL']._serialized_start=2456 + _globals['_ERROR_ERRORLEVEL']._serialized_end=2499 + _globals['_ERROR_ERRORTYPE']._serialized_start=2501 + _globals['_ERROR_ERRORTYPE']._serialized_end=2535 + _globals['_POSITION']._serialized_start=2545 + _globals['_POSITION']._serialized_end=2667 + _globals['_ERRORLIST']._serialized_start=2669 + _globals['_ERRORLIST']._serialized_end=2738 + _globals['_BUILDREQUEST']._serialized_start=2741 + _globals['_BUILDREQUEST']._serialized_end=2952 + _globals['_AUTOREBUILDSTARTED']._serialized_start=2954 + _globals['_AUTOREBUILDSTARTED']._serialized_end=3005 + _globals['_BUILDSUCCESS']._serialized_start=3008 + _globals['_BUILDSUCCESS']._serialized_end=3338 + _globals['_BUILDFAILURE']._serialized_start=3341 + _globals['_BUILDFAILURE']._serialized_end=3555 + _globals['_BUILDEVENT']._serialized_start=3558 + _globals['_BUILDEVENT']._serialized_end=3838 + _globals['_GENERATESTUBSREQUEST']._serialized_start=3841 + _globals['_GENERATESTUBSREQUEST']._serialized_end=4137 + _globals['_GENERATESTUBSRESPONSE']._serialized_start=4139 + _globals['_GENERATESTUBSRESPONSE']._serialized_end=4162 + _globals['_SYNCSTUBREFERENCESREQUEST']._serialized_start=4165 + _globals['_SYNCSTUBREFERENCESREQUEST']._serialized_end=4327 + _globals['_SYNCSTUBREFERENCESRESPONSE']._serialized_start=4329 + _globals['_SYNCSTUBREFERENCESRESPONSE']._serialized_end=4357 + _globals['_LANGUAGESERVICE']._serialized_start=4360 + _globals['_LANGUAGESERVICE']._serialized_end=5432 # @@protoc_insertion_point(module_scope) diff --git a/python-runtime/ftl/src/ftl/protos/xyz/block/ftl/v1/language/language_pb2.pyi b/python-runtime/ftl/src/ftl/protos/xyz/block/ftl/v1/language/language_pb2.pyi index c9faae24f2..f40b027293 100644 --- a/python-runtime/ftl/src/ftl/protos/xyz/block/ftl/v1/language/language_pb2.pyi +++ b/python-runtime/ftl/src/ftl/protos/xyz/block/ftl/v1/language/language_pb2.pyi @@ -10,12 +10,13 @@ from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Map DESCRIPTOR: _descriptor.FileDescriptor class ModuleConfig(_message.Message): - __slots__ = ("name", "dir", "language", "deploy_dir", "build", "build_lock", "generated_schema_dir", "watch", "language_config") + __slots__ = ("name", "dir", "language", "deploy_dir", "build", "devModeBuild", "build_lock", "generated_schema_dir", "watch", "language_config") NAME_FIELD_NUMBER: _ClassVar[int] DIR_FIELD_NUMBER: _ClassVar[int] LANGUAGE_FIELD_NUMBER: _ClassVar[int] DEPLOY_DIR_FIELD_NUMBER: _ClassVar[int] BUILD_FIELD_NUMBER: _ClassVar[int] + DEVMODEBUILD_FIELD_NUMBER: _ClassVar[int] BUILD_LOCK_FIELD_NUMBER: _ClassVar[int] GENERATED_SCHEMA_DIR_FIELD_NUMBER: _ClassVar[int] WATCH_FIELD_NUMBER: _ClassVar[int] @@ -25,11 +26,12 @@ class ModuleConfig(_message.Message): language: str deploy_dir: str build: str + devModeBuild: str build_lock: str generated_schema_dir: str watch: _containers.RepeatedScalarFieldContainer[str] language_config: _struct_pb2.Struct - def __init__(self, name: _Optional[str] = ..., dir: _Optional[str] = ..., language: _Optional[str] = ..., deploy_dir: _Optional[str] = ..., build: _Optional[str] = ..., build_lock: _Optional[str] = ..., generated_schema_dir: _Optional[str] = ..., watch: _Optional[_Iterable[str]] = ..., language_config: _Optional[_Union[_struct_pb2.Struct, _Mapping]] = ...) -> None: ... + def __init__(self, name: _Optional[str] = ..., dir: _Optional[str] = ..., language: _Optional[str] = ..., deploy_dir: _Optional[str] = ..., build: _Optional[str] = ..., devModeBuild: _Optional[str] = ..., build_lock: _Optional[str] = ..., generated_schema_dir: _Optional[str] = ..., watch: _Optional[_Iterable[str]] = ..., language_config: _Optional[_Union[_struct_pb2.Struct, _Mapping]] = ...) -> None: ... class ProjectConfig(_message.Message): __slots__ = ("dir", "name", "no_git", "hermit") @@ -93,20 +95,22 @@ class ModuleConfigDefaultsRequest(_message.Message): def __init__(self, dir: _Optional[str] = ...) -> None: ... class ModuleConfigDefaultsResponse(_message.Message): - __slots__ = ("deploy_dir", "build", "build_lock", "generated_schema_dir", "watch", "language_config") + __slots__ = ("deploy_dir", "build", "devModeBuild", "build_lock", "generated_schema_dir", "watch", "language_config") DEPLOY_DIR_FIELD_NUMBER: _ClassVar[int] BUILD_FIELD_NUMBER: _ClassVar[int] + DEVMODEBUILD_FIELD_NUMBER: _ClassVar[int] BUILD_LOCK_FIELD_NUMBER: _ClassVar[int] GENERATED_SCHEMA_DIR_FIELD_NUMBER: _ClassVar[int] WATCH_FIELD_NUMBER: _ClassVar[int] LANGUAGE_CONFIG_FIELD_NUMBER: _ClassVar[int] deploy_dir: str build: str + devModeBuild: str build_lock: str generated_schema_dir: str watch: _containers.RepeatedScalarFieldContainer[str] language_config: _struct_pb2.Struct - def __init__(self, deploy_dir: _Optional[str] = ..., build: _Optional[str] = ..., build_lock: _Optional[str] = ..., generated_schema_dir: _Optional[str] = ..., watch: _Optional[_Iterable[str]] = ..., language_config: _Optional[_Union[_struct_pb2.Struct, _Mapping]] = ...) -> None: ... + def __init__(self, deploy_dir: _Optional[str] = ..., build: _Optional[str] = ..., devModeBuild: _Optional[str] = ..., build_lock: _Optional[str] = ..., generated_schema_dir: _Optional[str] = ..., watch: _Optional[_Iterable[str]] = ..., language_config: _Optional[_Union[_struct_pb2.Struct, _Mapping]] = ...) -> None: ... class DependenciesRequest(_message.Message): __slots__ = ("module_config",) @@ -207,18 +211,22 @@ class AutoRebuildStarted(_message.Message): def __init__(self, context_id: _Optional[str] = ...) -> None: ... class BuildSuccess(_message.Message): - __slots__ = ("context_id", "is_automatic_rebuild", "module", "deploy", "errors") + __slots__ = ("context_id", "is_automatic_rebuild", "module", "deploy", "docker_image", "errors", "dev_endpoint") CONTEXT_ID_FIELD_NUMBER: _ClassVar[int] IS_AUTOMATIC_REBUILD_FIELD_NUMBER: _ClassVar[int] MODULE_FIELD_NUMBER: _ClassVar[int] DEPLOY_FIELD_NUMBER: _ClassVar[int] + DOCKER_IMAGE_FIELD_NUMBER: _ClassVar[int] ERRORS_FIELD_NUMBER: _ClassVar[int] + DEV_ENDPOINT_FIELD_NUMBER: _ClassVar[int] context_id: str is_automatic_rebuild: bool module: _schema_pb2.Module deploy: _containers.RepeatedScalarFieldContainer[str] + docker_image: str errors: ErrorList - def __init__(self, context_id: _Optional[str] = ..., is_automatic_rebuild: bool = ..., module: _Optional[_Union[_schema_pb2.Module, _Mapping]] = ..., deploy: _Optional[_Iterable[str]] = ..., errors: _Optional[_Union[ErrorList, _Mapping]] = ...) -> None: ... + dev_endpoint: str + def __init__(self, context_id: _Optional[str] = ..., is_automatic_rebuild: bool = ..., module: _Optional[_Union[_schema_pb2.Module, _Mapping]] = ..., deploy: _Optional[_Iterable[str]] = ..., docker_image: _Optional[str] = ..., errors: _Optional[_Union[ErrorList, _Mapping]] = ..., dev_endpoint: _Optional[str] = ...) -> None: ... class BuildFailure(_message.Message): __slots__ = ("context_id", "is_automatic_rebuild", "errors", "invalidate_dependencies")