-
Notifications
You must be signed in to change notification settings - Fork 0
fix: implement misc stubs (InvokeService, BuildBinary, permissions, app.container) #178
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4350b4c
469ae58
4a87f7e
a6d26d0
5e7d279
c4109e5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2,6 +2,8 @@ package module | |||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| import ( | ||||||||||||||||||||||||||||||||||||||
| "fmt" | ||||||||||||||||||||||||||||||||||||||
| "os" | ||||||||||||||||||||||||||||||||||||||
| "path/filepath" | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| "github.com/CrisisTextLine/modular" | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -27,6 +29,7 @@ type AppContainerModule struct { | |||||||||||||||||||||||||||||||||||||
| current *AppDeployResult // current deployment state | ||||||||||||||||||||||||||||||||||||||
| previous *AppDeployResult // last known-good deployment for rollback | ||||||||||||||||||||||||||||||||||||||
| backend appContainerBackend | ||||||||||||||||||||||||||||||||||||||
| logger modular.Logger | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // AppContainerSpec describes the desired state of an application container. | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -111,6 +114,8 @@ func (m *AppContainerModule) Name() string { return m.name } | |||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // Init resolves the environment module and initialises the platform backend. | ||||||||||||||||||||||||||||||||||||||
| func (m *AppContainerModule) Init(app modular.Application) error { | ||||||||||||||||||||||||||||||||||||||
| m.logger = app.Logger() | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| envName, _ := m.config["environment"].(string) | ||||||||||||||||||||||||||||||||||||||
| if envName != "" { | ||||||||||||||||||||||||||||||||||||||
| svc, ok := app.SvcRegistry()[envName] | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -129,9 +134,20 @@ func (m *AppContainerModule) Init(app modular.Application) error { | |||||||||||||||||||||||||||||||||||||
| return fmt.Errorf("app.container %q: environment %q is not a platform.kubernetes or platform.ecs module (got %T)", m.name, envName, svc) | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||
| // Default to kubernetes mock when no environment is specified. | ||||||||||||||||||||||||||||||||||||||
| m.backend = &k8sAppBackend{} | ||||||||||||||||||||||||||||||||||||||
| m.platformType = "kubernetes" | ||||||||||||||||||||||||||||||||||||||
| // No environment configured: choose backend based on whether a kubeconfig is available. | ||||||||||||||||||||||||||||||||||||||
| kubeconfigPath := kubeconfigPath() | ||||||||||||||||||||||||||||||||||||||
| if kubeconfigPath != "" { | ||||||||||||||||||||||||||||||||||||||
| m.logger.Info("app.container: no environment configured, using kubernetes backend from kubeconfig", | ||||||||||||||||||||||||||||||||||||||
| "module", m.name, "kubeconfig", kubeconfigPath) | ||||||||||||||||||||||||||||||||||||||
| m.backend = &k8sAppBackend{} | ||||||||||||||||||||||||||||||||||||||
| m.platformType = "kubernetes" | ||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||
| m.logger.Warn("app.container: no environment configured and no kubeconfig found; defaulting to mock kubernetes backend", | ||||||||||||||||||||||||||||||||||||||
| "module", m.name, | ||||||||||||||||||||||||||||||||||||||
| "hint", "set 'environment' to a platform.kubernetes or platform.ecs module, or ensure KUBECONFIG / ~/.kube/config is present") | ||||||||||||||||||||||||||||||||||||||
| m.backend = &k8sAppBackend{} | ||||||||||||||||||||||||||||||||||||||
| m.platformType = "kubernetes" | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+137
to
+149
|
||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| m.spec = m.parseSpec() | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -413,6 +429,24 @@ type K8sServicePortRef struct { | |||||||||||||||||||||||||||||||||||||
| Number int `json:"number"` | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // kubeconfigPath returns the path to the kubeconfig file if it exists, or an | ||||||||||||||||||||||||||||||||||||||
| // empty string. It checks KUBECONFIG env var first, then ~/.kube/config. | ||||||||||||||||||||||||||||||||||||||
| func kubeconfigPath() string { | ||||||||||||||||||||||||||||||||||||||
| if kc := os.Getenv("KUBECONFIG"); kc != "" { | ||||||||||||||||||||||||||||||||||||||
| kc = filepath.Clean(kc) | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+432
to
+436
|
||||||||||||||||||||||||||||||||||||||
| if _, err := os.Stat(kc); err == nil { //nolint:gosec // KUBECONFIG is a trusted env var | ||||||||||||||||||||||||||||||||||||||
| return kc | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+433
to
+438
|
||||||||||||||||||||||||||||||||||||||
| // empty string. It checks KUBECONFIG env var first, then ~/.kube/config. | |
| func kubeconfigPath() string { | |
| if kc := os.Getenv("KUBECONFIG"); kc != "" { | |
| kc = filepath.Clean(kc) | |
| if _, err := os.Stat(kc); err == nil { //nolint:gosec // KUBECONFIG is a trusted env var | |
| return kc | |
| // empty string. It checks KUBECONFIG env var first (which may be a list of paths), | |
| // then ~/.kube/config. | |
| func kubeconfigPath() string { | |
| if kc := os.Getenv("KUBECONFIG"); kc != "" { | |
| for _, entry := range filepath.SplitList(kc) { | |
| entry = filepath.Clean(entry) | |
| if entry == "" { | |
| continue | |
| } | |
| if info, err := os.Stat(entry); err == nil && !info.IsDir() { //nolint:gosec // KUBECONFIG is a trusted env var | |
| return entry | |
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -165,13 +165,26 @@ func (s *BuildBinaryStep) generateGoMod() string { | |||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // generateMainGo returns the contents of the generated main.go file. | ||||||||||||||||||||||||||
| // The generated binary loads a workflow config (either embedded at compile-time | ||||||||||||||||||||||||||
| // or read from disk at runtime), builds the workflow engine, and runs it until | ||||||||||||||||||||||||||
| // SIGINT/SIGTERM is received. | ||||||||||||||||||||||||||
| func (s *BuildBinaryStep) generateMainGo() string { | ||||||||||||||||||||||||||
| var sb strings.Builder | ||||||||||||||||||||||||||
| sb.WriteString("package main\n\n") | ||||||||||||||||||||||||||
| sb.WriteString("import (\n") | ||||||||||||||||||||||||||
| sb.WriteString("\t_ \"embed\"\n") | ||||||||||||||||||||||||||
| if s.embedConfig { | ||||||||||||||||||||||||||
| sb.WriteString("\t_ \"embed\"\n") | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| sb.WriteString("\t\"context\"\n") | ||||||||||||||||||||||||||
| sb.WriteString("\t\"fmt\"\n") | ||||||||||||||||||||||||||
| sb.WriteString("\t\"log/slog\"\n") | ||||||||||||||||||||||||||
| sb.WriteString("\t\"os\"\n") | ||||||||||||||||||||||||||
| sb.WriteString("\t\"os/signal\"\n") | ||||||||||||||||||||||||||
| sb.WriteString("\t\"syscall\"\n") | ||||||||||||||||||||||||||
| sb.WriteString("\n") | ||||||||||||||||||||||||||
| sb.WriteString("\t\"github.com/CrisisTextLine/modular\"\n") | ||||||||||||||||||||||||||
| sb.WriteString("\tworkflow \"github.com/GoCodeAlone/workflow\"\n") | ||||||||||||||||||||||||||
| sb.WriteString("\t\"github.com/GoCodeAlone/workflow/config\"\n") | ||||||||||||||||||||||||||
| sb.WriteString(")\n\n") | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| if s.embedConfig { | ||||||||||||||||||||||||||
|
|
@@ -180,17 +193,51 @@ func (s *BuildBinaryStep) generateMainGo() string { | |||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| sb.WriteString("func main() {\n") | ||||||||||||||||||||||||||
| sb.WriteString("\tlogger := slog.New(slog.NewTextHandler(os.Stdout, nil))\n\n") | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| if s.embedConfig { | ||||||||||||||||||||||||||
| sb.WriteString("\tif len(configYAML) == 0 {\n") | ||||||||||||||||||||||||||
| sb.WriteString("\t\tfmt.Fprintln(os.Stderr, \"embedded config is empty\")\n") | ||||||||||||||||||||||||||
| sb.WriteString("\t\tos.Exit(1)\n") | ||||||||||||||||||||||||||
| sb.WriteString("\t}\n") | ||||||||||||||||||||||||||
| sb.WriteString("\tfmt.Printf(\"Starting workflow app (config: %d bytes)\\n\", len(configYAML))\n") | ||||||||||||||||||||||||||
| sb.WriteString("\t}\n\n") | ||||||||||||||||||||||||||
| sb.WriteString("\tcfg, err := config.LoadFromString(string(configYAML))\n") | ||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||
| sb.WriteString("\tfmt.Println(\"Starting workflow app\")\n") | ||||||||||||||||||||||||||
| sb.WriteString("\tcfgFile := \"app.yaml\"\n") | ||||||||||||||||||||||||||
| sb.WriteString("\tif len(os.Args) > 1 {\n") | ||||||||||||||||||||||||||
| sb.WriteString("\t\tcfgFile = os.Args[1]\n") | ||||||||||||||||||||||||||
| sb.WriteString("\t}\n\n") | ||||||||||||||||||||||||||
| sb.WriteString("\tcfg, err := config.LoadFromFile(cfgFile)\n") | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| sb.WriteString("\t// TODO: wire up modular.NewStdApplication with embedded config\n") | ||||||||||||||||||||||||||
| sb.WriteString("\t_ = os.Args\n") | ||||||||||||||||||||||||||
| sb.WriteString("\tif err != nil {\n") | ||||||||||||||||||||||||||
| sb.WriteString("\t\tfmt.Fprintf(os.Stderr, \"parse config: %v\\n\", err)\n") | ||||||||||||||||||||||||||
| sb.WriteString("\t\tos.Exit(1)\n") | ||||||||||||||||||||||||||
| sb.WriteString("\t}\n\n") | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
| if s.embedConfig { | |
| sb.WriteString("\tif cfg != nil && cfg.ConfigDir == \"\" {\n") | |
| sb.WriteString("\t\twd, err := os.Getwd()\n") | |
| sb.WriteString("\t\tif err != nil {\n") | |
| sb.WriteString("\t\t\tfmt.Fprintf(os.Stderr, \"determine working directory: %v\\n\", err)\n") | |
| sb.WriteString("\t\t\tos.Exit(1)\n") | |
| sb.WriteString("\t\t}\n") | |
| sb.WriteString("\t\tcfg.ConfigDir = wd\n") | |
| sb.WriteString("\t}\n\n") | |
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,8 +9,6 @@ import ( | |
| goplugin "github.com/GoCodeAlone/go-plugin" | ||
| pb "github.com/GoCodeAlone/workflow/plugin/external/proto" | ||
| "github.com/google/uuid" | ||
| "google.golang.org/grpc/codes" | ||
| "google.golang.org/grpc/status" | ||
| "google.golang.org/protobuf/types/known/emptypb" | ||
| "google.golang.org/protobuf/types/known/structpb" | ||
| ) | ||
|
|
@@ -364,8 +362,29 @@ func (s *grpcServer) GetConfigFragment(_ context.Context, _ *emptypb.Empty) (*pb | |
|
|
||
| // --- Service RPCs --- | ||
|
|
||
| func (s *grpcServer) InvokeService(_ context.Context, _ *pb.InvokeServiceRequest) (*pb.InvokeServiceResponse, error) { | ||
| return nil, status.Error(codes.Unimplemented, "InvokeService not implemented") | ||
| // InvokeService routes a service method call to the registered module identified | ||
| // by handle_id. The module must implement ServiceInvoker; if it does not, an | ||
| // error is returned to the host. | ||
| func (s *grpcServer) InvokeService(_ context.Context, req *pb.InvokeServiceRequest) (*pb.InvokeServiceResponse, error) { | ||
| s.mu.RLock() | ||
|
Comment on lines
+368
to
+369
|
||
| inst, ok := s.modules[req.HandleId] | ||
| s.mu.RUnlock() | ||
| if !ok { | ||
| return &pb.InvokeServiceResponse{Error: fmt.Sprintf("unknown module handle: %s", req.HandleId)}, nil | ||
| } | ||
|
|
||
| invoker, ok := inst.(ServiceInvoker) | ||
| if !ok { | ||
| return &pb.InvokeServiceResponse{ | ||
| Error: fmt.Sprintf("module handle %s does not implement ServiceInvoker", req.HandleId), | ||
| }, nil | ||
| } | ||
|
|
||
| result, err := invoker.InvokeMethod(req.Method, structToMap(req.Args)) | ||
|
Comment on lines
+365
to
+383
|
||
| if err != nil { | ||
| return &pb.InvokeServiceResponse{Error: err.Error()}, nil //nolint:nilerr // app error in response field | ||
| } | ||
| return &pb.InvokeServiceResponse{Result: mapToStruct(result)}, nil | ||
|
Comment on lines
+383
to
+387
|
||
| } | ||
|
|
||
| // --- Message delivery RPC --- | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The log message says the module is "using kubernetes backend from kubeconfig", but the selected backend is still k8sAppBackend which (per the comment below) doesn’t use kubeconfig or talk to the real k8s API. This message is likely misleading; consider rewording it to something like "kubeconfig detected; defaulting to kubernetes backend" (or explicitly call out that this is the mock backend).