feat: add generic phased background shutdown to managed services
This commit is contained in:
@@ -8,7 +8,7 @@ v0.14 新增 `api format [--check]`、`api upgrade [--dry-run|--preview <空目
|
||||
|
||||
```sh
|
||||
# 仓库读取权限与 PATH 配置见根目录 README
|
||||
go install git.yuntuo.hk/k2safe/go-omni/cmd/go-omni@v0.14.1-preview
|
||||
go install git.yuntuo.hk/k2safe/go-omni/cmd/go-omni@v0.15.0-preview
|
||||
go-omni new --module example.com/hello ./hello
|
||||
|
||||
# 从自己的契约生成;service.api 及其相对 import 文件需要在本地存在
|
||||
@@ -43,14 +43,14 @@ main、Gen.go、OpenAPI 和 manifest 由生成器维护;业务文件按字节
|
||||
|
||||
## 版本与迁移
|
||||
|
||||
CLI 与框架依赖固定使用 v0.14.1-preview。远程安装的 CLI 默认把自身版本写入 go.mod,不添加本地 replace;`--framework-version` 可显式指定已发布的兼容版本。仅在开发框架源码时使用本地替换:
|
||||
CLI 与框架依赖固定使用 v0.15.0-preview。远程安装的 CLI 默认把自身版本写入 go.mod,不添加本地 replace;`--framework-version` 可显式指定已发布的兼容版本。仅在开发框架源码时使用本地替换:
|
||||
|
||||
```sh
|
||||
# 在 go-omni 源码仓库中执行
|
||||
go run ./cmd/go-omni new --module example.com/hello --framework-dir . ../hello
|
||||
```
|
||||
|
||||
模板最低需要 v0.14.0-preview 的通用 Runtime、配置和绑定能力,继续使用 manifest v2;GORM 可选适配器从 v0.14.1-preview 提供。已有项目使用 [定点升级与预览](../../DEVELOPMENT.md#v013-项目升级):原始模板可自动替换,含业务修改的基础文件按预览合并;通用清理由 Runtime 负责。独立工具命令不会被删除。受管理项目通过 `.api` 模块导入增加业务,不使用旧 `add` 命令的手动路由装配。
|
||||
模板最低需要 v0.15.0-preview 的分阶段 Runtime 能力,继续使用 manifest v2;GORM 可选适配器从 v0.14.1-preview 提供。已有项目使用 [定点升级与预览](../../DEVELOPMENT.md#v013-项目升级):原始模板可自动替换,含业务修改的基础文件按预览合并;通用清理由 Runtime 负责。独立工具命令不会被删除。受管理项目通过 `.api` 模块导入增加业务,不使用旧 `add` 命令的手动路由装配。
|
||||
|
||||
## 数据库迁移命令
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
)
|
||||
|
||||
const frameworkModule = "git.yuntuo.hk/k2safe/go-omni"
|
||||
const minimumVersion = "v0.14.0-preview"
|
||||
const minimumVersion = "v0.15.0-preview"
|
||||
const generatedHeader = "// Code generated by go-omni api. DO NOT EDIT.\n"
|
||||
|
||||
type Options struct {
|
||||
@@ -151,7 +151,7 @@ func generate(o Options, c *Compiled, starter bool) (Output, error) {
|
||||
}
|
||||
}
|
||||
if !localCurrent {
|
||||
return Output{}, fmt.Errorf("existing framework dependency predates the generated Runtime, request policy and structured MySQL support; use %s or a current local replacement", minimumVersion)
|
||||
return Output{}, fmt.Errorf("existing framework dependency predates the generated phased lifecycle support; use %s or a current local replacement", minimumVersion)
|
||||
}
|
||||
}
|
||||
if o.FrameworkDir != "" {
|
||||
|
||||
@@ -71,6 +71,7 @@ func TestManagedGeneratedProject(t *testing.T) {
|
||||
files := map[string]string{
|
||||
"consumer_test.go": "consumer_test.go", "internal/svc/options.go": "options.go", "internal/svc/client.go": "client.go", "internal/logic/demo/check.go": "operations.go",
|
||||
"shutdown_test.go": "shutdown_test.go",
|
||||
"phases_test.go": "phases_test.go", "internal/svc/background.go": "background.go",
|
||||
}
|
||||
for path, fixture := range files {
|
||||
writeFile(t, filepath.Join(dir, path), readFile(t, filepath.Join("testdata/managed", fixture)))
|
||||
@@ -79,7 +80,7 @@ func TestManagedGeneratedProject(t *testing.T) {
|
||||
writeFile(t, filepath.Join(dir, "internal/logic/demo", name+".go"), "package demo\n")
|
||||
}
|
||||
configPath := filepath.Join(dir, "internal/config/config.go")
|
||||
writeFile(t, configPath, strings.Replace(readFile(t, configPath), "type Config struct {", "type Config struct {\n FailInit bool `json:\"failInit\"`\n ShutdownInit bool `json:\"shutdownInit\"`", 1))
|
||||
writeFile(t, configPath, strings.Replace(readFile(t, configPath), "type Config struct {", "type Config struct {\n BackgroundMode string `json:\"backgroundMode\"`\n FailInit bool `json:\"failInit\"`\n ShutdownInit bool `json:\"shutdownInit\"`", 1))
|
||||
svcPath := filepath.Join(dir, "internal/svc/serviceContext.go")
|
||||
source := strings.Replace(readFile(t, svcPath), "type ServiceContext struct {", "type ServiceContext struct {\n Client *Client", 1)
|
||||
source = strings.Replace(source, "return nil", "return initClient(ctx,c)", 1)
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"git.yuntuo.hk/k2safe/go-omni/logx"
|
||||
"git.yuntuo.hk/k2safe/go-omni/lifecycle"
|
||||
"MODULE/internal/config"
|
||||
"MODULE/internal/handler"
|
||||
"MODULE/internal/svc"
|
||||
@@ -27,7 +28,9 @@ func main(){
|
||||
func run(ctx context.Context,path string)(runErr error){
|
||||
cfg,err:=config.Load(path);if err!=nil{return err}
|
||||
logger,err:=logx.New(cfg.Server.Log);if err!=nil{return err}
|
||||
defer func(){runErr=errors.Join(runErr,logger.Close())}()
|
||||
// Unjoined background work may still log. The owning process exits on this
|
||||
// error; do not close its borrowed logger before that work has finished.
|
||||
defer func(){if !errors.Is(runErr,lifecycle.ErrResourcesRetained){runErr=errors.Join(runErr,logger.Close())}}()
|
||||
deps,err:=svc.New(ctx,logger,cfg);if err!=nil{return err}
|
||||
// RunContext drains HTTP requests before returning. This scope owns dependencies
|
||||
// and closes them exactly once, before closing the borrowed logger.
|
||||
@@ -182,7 +185,16 @@ then immediately register each cleanup with OnClose. A later initialization fail
|
||||
runs registered cleanups in reverse order. Close is idempotent. GlobalMiddleware
|
||||
provides global hooks; Middlewares supplies named API hooks. AppOptions is evaluated
|
||||
once before route registration; logging is configured earlier through Config.
|
||||
The generated main runs HTTP draining before closing svc resources, then the logger.
|
||||
The generated main runs HTTP draining, all OnStop hooks, all OnDrain hooks, resource
|
||||
OnClose callbacks, then logger close. Stop/drain hooks within a phase run concurrently;
|
||||
combine components with ordering dependencies into one pair. Pair OnStop/OnDrain
|
||||
with the same name. OnDrain returns nil only after final work and acknowledgements
|
||||
have finished. Use WorkContext for accepted background work; Context is an early
|
||||
shutdown notification. server.lifecycle configures stopTimeout (5s), drainTimeout
|
||||
(10s), and cancelTimeout (5s grace per timed-out phase). Zero uses defaults.
|
||||
Timeout/failure cancels WorkContext. If hooks cannot prove completion, resources
|
||||
and the borrowed logger are retained and the generated process exits unsuccessfully.
|
||||
Errors retain their causes. No automatic retry of close or force killing of goroutines.
|
||||
Do not close borrowed resources or attach duplicate cleanup hooks to App. The startup
|
||||
context cancels on signals; long-lived clients/workers that requests depend on need
|
||||
their own lifetime, canceled by OnClose after HTTP draining.
|
||||
|
||||
49
cmd/go-omni/apigen/testdata/managed/background.go
vendored
Normal file
49
cmd/go-omni/apigen/testdata/managed/background.go
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
package svc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"git.yuntuo.hk/k2safe/go-omni/task"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
type BackgroundState struct {
|
||||
Stop, Drain, Release, Started chan struct{}
|
||||
Wrote atomic.Bool
|
||||
Future *task.Future
|
||||
}
|
||||
|
||||
var Background atomic.Pointer[BackgroundState]
|
||||
|
||||
func initBackground(c *ServiceContext) error {
|
||||
state := &BackgroundState{Stop: make(chan struct{}), Drain: make(chan struct{}), Release: make(chan struct{}), Started: make(chan struct{})}
|
||||
baseline := Closed.Load()
|
||||
runner, err := task.New(task.Config{Workers: 1, QueueSize: 1, TaskTimeout: time.Minute, OnError: func(string, error) {}})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.OnStop("jobs", func(ctx context.Context) error { close(state.Stop); return runner.StopAccepting(ctx) })
|
||||
c.OnDrain("jobs", func(ctx context.Context) error { close(state.Drain); return runner.Wait(ctx) })
|
||||
state.Future, err = runner.Submit(c.WorkContext(), "finish", func(ctx context.Context) error {
|
||||
close(state.Started)
|
||||
if c.Config.BackgroundMode == "auto" {
|
||||
<-state.Stop
|
||||
} else {
|
||||
<-state.Release
|
||||
}
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
if Closed.Load() != baseline {
|
||||
return errors.New("shared client closed before background write")
|
||||
}
|
||||
state.Wrote.Store(true)
|
||||
return nil
|
||||
})
|
||||
if err == nil {
|
||||
<-state.Started
|
||||
}
|
||||
Background.Store(state)
|
||||
return err
|
||||
}
|
||||
@@ -20,6 +20,11 @@ func initClient(ctx context.Context, c *ServiceContext) error {
|
||||
Opened.Add(1)
|
||||
c.Client = &Client{}
|
||||
c.OnClose(func() error { Closed.Add(1); c.Logger.Info("test.client.closed"); return nil })
|
||||
if c.Config.BackgroundMode != "" {
|
||||
if err := initBackground(c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if c.Config.FailInit {
|
||||
return errors.New("client initialization failed")
|
||||
}
|
||||
|
||||
155
cmd/go-omni/apigen/testdata/managed/phases_test.go
vendored
Normal file
155
cmd/go-omni/apigen/testdata/managed/phases_test.go
vendored
Normal file
@@ -0,0 +1,155 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"example.com/managed/internal/svc"
|
||||
"git.yuntuo.hk/k2safe/go-omni/lifecycle"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func phasedConfig(t *testing.T, address, mode string, fail bool) string {
|
||||
t.Helper()
|
||||
path := filepath.Join(t.TempDir(), "config.json")
|
||||
drainTimeout := "1s"
|
||||
if mode == "stuck" {
|
||||
drainTimeout = "50ms"
|
||||
}
|
||||
data, _ := json.Marshal(map[string]any{"backgroundMode": mode, "failInit": fail, "server": map[string]any{"address": address, "lifecycle": map[string]string{"stopTimeout": "1s", "drainTimeout": drainTimeout, "cancelTimeout": "50ms"}}})
|
||||
if err := os.WriteFile(path, data, 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return path
|
||||
}
|
||||
func await(t *testing.T, ch <-chan struct{}) {
|
||||
t.Helper()
|
||||
select {
|
||||
case <-ch:
|
||||
case <-time.After(3 * time.Second):
|
||||
t.Fatal("phase did not arrive")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeneratedBackgroundPhases(t *testing.T) {
|
||||
for _, transport := range []string{"nethttp", "native"} {
|
||||
t.Run(transport, func(t *testing.T) {
|
||||
t.Setenv("GO_OMNI_TRANSPORT", transport)
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
addr := listener.Addr().String()
|
||||
_ = listener.Close()
|
||||
svc.Started = make(chan struct{}, 1)
|
||||
svc.Release = make(chan struct{})
|
||||
before := svc.Closed.Load()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
done := make(chan error, 1)
|
||||
path := phasedConfig(t, addr, "hold", false)
|
||||
go func() { done <- run(ctx, path) }()
|
||||
waitReady(t, "http://"+addr, done)
|
||||
state := svc.Background.Load()
|
||||
defer func() {
|
||||
select {
|
||||
case <-state.Release:
|
||||
default:
|
||||
close(state.Release)
|
||||
}
|
||||
}()
|
||||
requestDone := make(chan error, 1)
|
||||
go func() {
|
||||
client := http.Client{Timeout: 3 * time.Second}
|
||||
resp, e := client.Get("http://" + addr + "/slow")
|
||||
if e == nil {
|
||||
_, _ = io.Copy(io.Discard, resp.Body)
|
||||
_ = resp.Body.Close()
|
||||
if resp.StatusCode != 200 {
|
||||
e = errors.New("HTTP drain failed")
|
||||
}
|
||||
}
|
||||
requestDone <- e
|
||||
}()
|
||||
await(t, svc.Started)
|
||||
deps := svc.Current.Load()
|
||||
deps.RequestShutdown(svc.FatalDependency)
|
||||
if deps.WorkContext().Err() != nil {
|
||||
t.Fatal("accepted work canceled at signal")
|
||||
}
|
||||
select {
|
||||
case <-state.Stop:
|
||||
t.Fatal("stopped background before HTTP finished")
|
||||
default:
|
||||
}
|
||||
close(svc.Release)
|
||||
if err := <-requestDone; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
await(t, state.Drain)
|
||||
if svc.Closed.Load() != before {
|
||||
t.Fatal("client closed during background drain")
|
||||
}
|
||||
close(state.Release)
|
||||
if err := <-done; !errors.Is(err, svc.FatalDependency) || errors.Is(err, lifecycle.ErrResourcesRetained) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !state.Wrote.Load() || svc.Closed.Load() != before+1 {
|
||||
t.Fatal("missing final write or cleanup")
|
||||
}
|
||||
if err := state.Future.Wait(context.Background()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeneratedBackgroundFailureCleanup(t *testing.T) {
|
||||
for _, failInit := range []bool{false, true} {
|
||||
t.Run(map[bool]string{true: "initialization", false: "listen"}[failInit], func(t *testing.T) {
|
||||
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer l.Close()
|
||||
before := svc.Closed.Load()
|
||||
err = run(context.Background(), phasedConfig(t, l.Addr().String(), "auto", failInit))
|
||||
if err == nil || errors.Is(err, lifecycle.ErrResourcesRetained) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
state := svc.Background.Load()
|
||||
if !state.Wrote.Load() || svc.Closed.Load() != before+1 {
|
||||
t.Fatal("failure cleanup did not join background first")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeneratedBackgroundTimeoutProcess(t *testing.T) {
|
||||
if path := os.Getenv("OMNI_PHASE_TIMEOUT_CONFIG"); path != "" {
|
||||
os.Args = []string{os.Args[0], "-config", path}
|
||||
main()
|
||||
return
|
||||
}
|
||||
path := phasedConfig(t, "127.0.0.1:0", "stuck", true)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
cmd := exec.CommandContext(ctx, os.Args[0], "-test.run=^TestGeneratedBackgroundTimeoutProcess$")
|
||||
cmd.Env = append(os.Environ(), "OMNI_PHASE_TIMEOUT_CONFIG="+path)
|
||||
output, err := cmd.CombinedOutput()
|
||||
var exit *exec.ExitError
|
||||
if !errors.As(err, &exit) || exit.ExitCode() != 1 || ctx.Err() != nil {
|
||||
t.Fatal(err, string(output))
|
||||
}
|
||||
if strings.Contains(string(output), "test.client.closed") || !strings.Contains(string(output), "shared resources retained") || !strings.Contains(string(output), "client initialization failed") {
|
||||
t.Fatal(string(output))
|
||||
}
|
||||
}
|
||||
@@ -78,6 +78,12 @@ func PlanUpgrade(dir string) (UpgradePlan, error) {
|
||||
names[name] = true
|
||||
}
|
||||
}
|
||||
// v0.14 already owns config/svc extensions. Only bump the module here;
|
||||
// Generate updates generator-owned main/runtime files without replaying the
|
||||
// v0.13 seed migration over developer configuration and dependency code.
|
||||
if semver.IsValid(m.TemplateVersion) && semver.Compare(m.TemplateVersion, "v0.14.0-preview") >= 0 {
|
||||
names = map[string]bool{}
|
||||
}
|
||||
sorted := make([]string, 0, len(names))
|
||||
for name := range names {
|
||||
sorted = append(sorted, name)
|
||||
|
||||
@@ -60,6 +60,49 @@ func TestUpgradeRejectsEscapingEntry(t *testing.T) {
|
||||
t.Fatal("invalid upgrade wrote project")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpgradeV014PreservesExtensions(t *testing.T) {
|
||||
dir := filepath.Join(t.TempDir(), "service")
|
||||
if _, err := New(Options{Dir: dir, Module: "example.com/upgrade", FrameworkDir: frameworkRoot(t)}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var m manifest
|
||||
if err := json.Unmarshal([]byte(readFile(t, filepath.Join(dir, manifestPath))), &m); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
m.TemplateVersion = "v0.14.0-preview"
|
||||
raw, _ := json.Marshal(m)
|
||||
writeFile(t, filepath.Join(dir, manifestPath), string(raw))
|
||||
mod := filepath.Join(dir, "go.mod")
|
||||
writeFile(t, mod, strings.ReplaceAll(readFile(t, mod), minimumVersion, "v0.14.1-preview"))
|
||||
owned := map[string]string{}
|
||||
for _, name := range []string{"internal/svc/serviceContext.go", "internal/config/config.go", "etc/config.yaml"} {
|
||||
path := filepath.Join(dir, name)
|
||||
value := readFile(t, path)
|
||||
if strings.HasSuffix(name, ".go") {
|
||||
value += "\n// project extension kept\n"
|
||||
} else {
|
||||
value += "\n# project configuration kept\n"
|
||||
}
|
||||
writeFile(t, path, value)
|
||||
owned[path] = value
|
||||
}
|
||||
plan, err := PlanUpgrade(dir)
|
||||
if err != nil || len(plan.Changes) != 1 || plan.Changes[0].Path != "go.mod" {
|
||||
t.Fatal(plan, err)
|
||||
}
|
||||
if _, err := ApplyUpgrade(dir); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for path, want := range owned {
|
||||
if readFile(t, path) != want {
|
||||
t.Fatal("developer file modified", path)
|
||||
}
|
||||
}
|
||||
if _, err := Generate(Options{Dir: dir, API: filepath.Join(dir, "api/service.api"), Check: true}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
func TestSafeInfrastructureUpgrade(t *testing.T) {
|
||||
dir := oldInfrastructureProject(t)
|
||||
before := snapshot(t, dir)
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
func TestNewWithRemoteFrameworkVersion(t *testing.T) {
|
||||
for _, flag := range [][]string{{"--framework-version", "v0.14.0-preview"}, {"--framework-version=v0.14.0-preview"}} {
|
||||
for _, flag := range [][]string{{"--framework-version", "v0.15.0-preview"}, {"--framework-version=v0.15.0-preview"}} {
|
||||
project := filepath.Join(t.TempDir(), "billing")
|
||||
args := append([]string{"--module", "example.com/billing"}, flag...)
|
||||
if err := runNew(append(args, project)); err != nil {
|
||||
@@ -18,7 +18,7 @@ func TestNewWithRemoteFrameworkVersion(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(string(content), "go-omni v0.14.0-preview") || strings.Contains(string(content), "replace ") {
|
||||
if !strings.Contains(string(content), "go-omni v0.15.0-preview") || strings.Contains(string(content), "replace ") {
|
||||
t.Fatalf("CLI did not pin remote dependency: %s", content)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@ type Context struct {
|
||||
{{end}}
|
||||
}
|
||||
// Init is project-owned. Add dependencies here and immediately register each
|
||||
// cleanup with c.OnClose. Returning an error automatically rolls everything back.
|
||||
// cleanup with c.OnClose. Background components use paired OnStop/OnDrain hooks
|
||||
// and WorkContext. Failed initialization stops/drains before releasing resources;
|
||||
// unjoined background work returns ErrResourcesRetained instead of closing its dependencies.
|
||||
// Generated infrastructure is ready here. Never create another App or logger.
|
||||
func(c *Context)Init(ctx context.Context)error {return nil}
|
||||
|
||||
@@ -18,7 +18,8 @@ func New(ctx context.Context,logger *logx.Logger,configs ...settings.Config)(res
|
||||
cfg:=settings.Default();if len(configs)==1{cfg=configs[0]}
|
||||
if err=cfg.Validate();err!=nil{return nil,err}
|
||||
checker,err:=health.New();if err!=nil{return nil,err}
|
||||
c:=&Context{Config:cfg,Logger:logger,Health:checker,Middlewares:middleware.Defaults(),Runtime:lifecycle.NewRuntime(ctx)}
|
||||
runtime,err:=lifecycle.NewRuntimeWithConfig(logx.WithLogger(ctx,logger),cfg.Server.Lifecycle);if err!=nil{return nil,err}
|
||||
c:=&Context{Config:cfg,Logger:logger,Health:checker,Middlewares:middleware.Defaults(),Runtime:runtime}
|
||||
ctx=logx.WithLogger(c.Context(),logger)
|
||||
defer func(){if p:=recover();p!=nil{_ = c.Close();panic(p)};if err!=nil{err=c.Finish(err)}}()
|
||||
{{if eq .Database "mysql"}}
|
||||
|
||||
@@ -4,7 +4,11 @@ server:
|
||||
enableLogger: true
|
||||
slowRequestThreshold: 500ms
|
||||
shutdownTimeout: 10s
|
||||
{{if .API}} endpoints:
|
||||
{{if .API}} lifecycle:
|
||||
stopTimeout: 5s
|
||||
drainTimeout: 10s
|
||||
cancelTimeout: 5s
|
||||
endpoints:
|
||||
liveness:
|
||||
enabled: true
|
||||
path: /health/live
|
||||
|
||||
@@ -10,7 +10,7 @@ const frameworkModule = "git.yuntuo.hk/k2safe/go-omni"
|
||||
// Source builds can carry an unpublished VCS version (including +dirty).
|
||||
// Keep this fallback in sync with the next release; installed CLIs use their
|
||||
// downloaded module version, which also has a module checksum.
|
||||
const defaultFrameworkVersion = "v0.14.1-preview"
|
||||
const defaultFrameworkVersion = "v0.15.0-preview"
|
||||
|
||||
// This target is not a claim that the release already exists. Source users
|
||||
// select a local framework checkout until the module templates are published.
|
||||
|
||||
Reference in New Issue
Block a user