diff --git a/addons/addon.go b/addons/addon.go index 91edf6c4..e89bf6d0 100644 --- a/addons/addon.go +++ b/addons/addon.go @@ -20,7 +20,7 @@ type Addon[T any] struct { func (a *Addon[T]) RegisterIfNeeded() { if a.registered.CompareAndSwap(false, true) { - Register(a) + a.register() } } diff --git a/addons/bootstrap/apt/dpkg/compare.go b/addons/bootstrap/apt/dpkg/compare.go index f3cb8ba1..f05b9f1f 100644 --- a/addons/bootstrap/apt/dpkg/compare.go +++ b/addons/bootstrap/apt/dpkg/compare.go @@ -87,28 +87,23 @@ func StringToVersion(str string) (Version, error) { } // Find Epoch - sepEpoch := strings.Index(str, ":") - if sepEpoch > -1 { - intEpoch, err := strconv.Atoi(str[:sepEpoch]) - if err == nil { - version.Epoch = intEpoch - } else { + epochStr, str, ok := strings.Cut(str, ":") + if ok { + if intEpoch, err := strconv.Atoi(epochStr); err != nil { return Version{}, errors.New("epoch in version is not a number") - } - if intEpoch < 0 { + } else if intEpoch < 0 { return Version{}, errors.New("epoch in version is negative") + } else { + version.Epoch = intEpoch } } else { version.Epoch = 0 + str = epochStr } // Find UpstreamVersion / DebianRevision - sepDebianRevision := strings.LastIndex(str, "-") - if sepDebianRevision > -1 { - version.UpstreamVersion = str[sepEpoch+1 : sepDebianRevision] - version.DebianRevision = str[sepDebianRevision+1:] - } else { - version.UpstreamVersion = str[sepEpoch+1:] + version.UpstreamVersion, version.DebianRevision, ok = strings.CutLast(str, "-") + if !ok { version.DebianRevision = "0" } // Verify format diff --git a/addons/bootstrap/apt/state.go b/addons/bootstrap/apt/state.go index c313c18d..3aa53631 100644 --- a/addons/bootstrap/apt/state.go +++ b/addons/bootstrap/apt/state.go @@ -34,13 +34,13 @@ const ( // DpkgInstalled returns a map of installed packages to their versions. func DpkgInstalled(ctx *bootstrap.Context) (map[string]string, error) { - data, ok := bootstrap.Get(ctx, installedKey) + data, ok := ctx.Get(installedKey) if ok { if st, err := os.Stat(dpkgStatusFile); err != nil || st.ModTime().After(data.timestamp) { // on-disk file is newer than in memory cache, invalidate it ok = false - bootstrap.Clear(ctx, installedKey) + ctx.Clear(installedKey) } } if ok { @@ -87,18 +87,18 @@ func DpkgInstalled(ctx *bootstrap.Context) (map[string]string, error) { } data.data[fields[0]] = fields[1] // package name -> version } - bootstrap.Save(ctx, installedKey, data) + ctx.Save(installedKey, data) return data.data, nil } func AptAvailable(ctx *bootstrap.Context) (map[string]string, error) { - data, ok := bootstrap.Get(ctx, availableKey) + data, ok := ctx.Get(availableKey) if ok { if st, err := os.Stat(aptPkgCacheFile); err != nil || st.ModTime().After(data.timestamp) { // on-disk file is newer than in memory cache, invalidate it ok = false - bootstrap.Clear(ctx, availableKey) + ctx.Clear(availableKey) } } if ok { @@ -145,6 +145,6 @@ func AptAvailable(ctx *bootstrap.Context) (map[string]string, error) { if err := s.Err(); err != nil { return nil, fmt.Errorf("error reading apt-cache dumpavail: %w", err) } - bootstrap.Save(ctx, availableKey, data) + ctx.Save(availableKey, data) return data.data, nil } diff --git a/addons/bootstrap/apt/steps-packages.go b/addons/bootstrap/apt/steps-packages.go index 66b7e45c..edca4e75 100644 --- a/addons/bootstrap/apt/steps-packages.go +++ b/addons/bootstrap/apt/steps-packages.go @@ -38,7 +38,7 @@ func updateStep() *bootstrap.Step { var sourcesDirty = bootstrap.NewKey[bool]("apt sources dirty") func DoUpdate(ctx *bootstrap.Context) error { - dirty, ok := bootstrap.Get(ctx, sourcesDirty) + dirty, ok := ctx.Get(sourcesDirty) // TODO: heuristic if we can skip the update entirely, e.g. if no sources were // changed and it ran within the last hour or something? if ok && !dirty { @@ -55,14 +55,14 @@ func DoUpdate(ctx *bootstrap.Context) error { ); err != nil { return err } - bootstrap.Set(ctx, sourcesDirty, false) + ctx.Set(sourcesDirty, false) return nil } // ChangedSources will mark the apt sources list as dirty, so a secondary // `apt update` step registered with [WithExtraUpdate] will actually run. func ChangedSources(ctx *bootstrap.Context) { - bootstrap.Set(ctx, sourcesDirty, true) + ctx.Set(sourcesDirty, true) } // Name of the step registered by [AddAptInstall]. This step will install @@ -132,10 +132,10 @@ func DoInstall( extraPackages []string, sudoPrompt string, ) error { - pkgSet, _ := bootstrap.Get(ctx, pendingPackages) + pkgSet, _ := ctx.Get(pendingPackages) if pkgSet == nil { pkgSet = map[string]struct{}{} - bootstrap.Save(ctx, pendingPackages, pkgSet) + ctx.Save(pendingPackages, pkgSet) } if len(extraPackages) > 0 { // don't mutate the stored list @@ -180,7 +180,7 @@ func DoInstall( // clear the pending package list so that a little trickery can install more // package groups later, e.g. in case setting up some apt source requires // installing some packages. - bootstrap.Clear(ctx, pendingPackages) + ctx.Clear(pendingPackages) // assume that installing or upgrading packages requires a reboot. Note that // we intentionally don't just look at the packages we were asked to install, @@ -200,10 +200,10 @@ func InstallNeeded( ctx *bootstrap.Context, extras ...string, ) (bool, error) { - pkgSet, _ := bootstrap.Get(ctx, pendingPackages) + pkgSet, _ := ctx.Get(pendingPackages) if pkgSet == nil { pkgSet = map[string]struct{}{} - bootstrap.Save(ctx, pendingPackages, pkgSet) + ctx.Save(pendingPackages, pkgSet) } if len(extras) > 0 { // don't mutate the stored list @@ -229,7 +229,7 @@ func needsInstall(ctx *bootstrap.Context, set map[string]struct{}) (bool, error) } func simInstall(ctx *bootstrap.Context) error { - pkgSet, _ := bootstrap.Get(ctx, pendingPackages) + pkgSet, _ := ctx.Get(pendingPackages) if len(pkgSet) == 0 { return nil } @@ -248,10 +248,10 @@ func simInstall(ctx *bootstrap.Context) error { // // The caller is responsible for ensuring that such a step runs after this. func AddPackages(ctx *bootstrap.Context, names ...string) { - pkgSet, _ := bootstrap.Get(ctx, pendingPackages) + pkgSet, _ := ctx.Get(pendingPackages) if pkgSet == nil { pkgSet = map[string]struct{}{} - bootstrap.Save(ctx, pendingPackages, pkgSet) + ctx.Save(pendingPackages, pkgSet) } added := []string{} for _, name := range names { diff --git a/addons/bootstrap/context-keys.go b/addons/bootstrap/context-keys.go index ea4a0cab..9f758daa 100644 --- a/addons/bootstrap/context-keys.go +++ b/addons/bootstrap/context-keys.go @@ -5,7 +5,7 @@ var skipLoginsKey = NewKey[bool]("bootstrap.skipLogins") // SkipLogins returns whether login steps should be skipped. This is set by a // command line argument. Custom bootstrap steps/plans should obey this. func SkipLogins(ctx *Context) bool { - v, ok := Get(ctx, skipLoginsKey) + v, ok := ctx.Get(skipLoginsKey) return ok && v } @@ -20,6 +20,6 @@ var headlessKey = NewKey[bool]("bootstrap.headless") // Headless returns whether steps should run in headless mode. This is set by a // command line argument. Custom bootstrap steps/plans should obey this. func Headless(ctx *Context) bool { - v, ok := Get(ctx, headlessKey) + v, ok := ctx.Get(headlessKey) return ok && v } diff --git a/addons/bootstrap/context.go b/addons/bootstrap/context.go index 7e2b421e..99be4cbc 100644 --- a/addons/bootstrap/context.go +++ b/addons/bootstrap/context.go @@ -21,18 +21,30 @@ func NewKey[T any](name string) InfoKey[T] { return internal.NewKey[T](name) } +// Deprecated: modernize +// +//go:fix inline func Save[T any](ctx *Context, k InfoKey[T], v T) { - internal.Save(ctx, k, v) + ctx.Save(k, v) } +// Deprecated: modernize +// +//go:fix inline func Set[T any](ctx *Context, k InfoKey[T], v T) { - internal.Set(ctx, k, v) + ctx.Set(k, v) } +// Deprecated: modernize +// +//go:fix inline func Get[T any](ctx *Context, k InfoKey[T]) (T, bool) { - return internal.Get(ctx, k) + return ctx.Get(k) } +// Deprecated: modernize +// +//go:fix inline func Clear[T any](ctx *Context, k InfoKey[T]) { - internal.Clear(ctx, k) + ctx.Clear(k) } diff --git a/addons/bootstrap/env-detect.go b/addons/bootstrap/env-detect.go index d67a44eb..4cd72fb9 100644 --- a/addons/bootstrap/env-detect.go +++ b/addons/bootstrap/env-detect.go @@ -7,13 +7,13 @@ import ( var isInContainerKey = NewKey[bool]("is-in-container") func IsInContainer(ctx *Context) bool { - if v, ok := Get(ctx, isInContainerKey); ok { + if v, ok := ctx.Get(isInContainerKey); ok { return v } // TODO: better detection than this _, err := os.Stat("/.dockerenv") v := err == nil - Save(ctx, isInContainerKey, v) + ctx.Save(isInContainerKey, v) return v } @@ -26,11 +26,11 @@ func SkipInContainer() StepOpt { var hasGUIKey = NewKey[bool]("has-gui") func HasGUI(ctx *Context) bool { - if v, ok := Get(ctx, hasGUIKey); ok { + if v, ok := ctx.Get(hasGUIKey); ok { return v } v := os.Getenv("DISPLAY") != "" || os.Getenv("WAYLAND_DISPLAY") != "" - Save(ctx, hasGUIKey, v) + ctx.Save(hasGUIKey, v) return v } diff --git a/addons/bootstrap/input/go.mod b/addons/bootstrap/input/go.mod index b527bd1a..0a8c494e 100644 --- a/addons/bootstrap/input/go.mod +++ b/addons/bootstrap/input/go.mod @@ -1,6 +1,6 @@ module fastcat.org/go/gdev/addons/bootstrap/input -go 1.26.6 +go 1.27.1 require ( fastcat.org/go/gdev v0.16.0 diff --git a/addons/bootstrap/input/prompt.go b/addons/bootstrap/input/prompt.go index 9ca7a0fc..7d9a797a 100644 --- a/addons/bootstrap/input/prompt.go +++ b/addons/bootstrap/input/prompt.go @@ -176,7 +176,7 @@ func (p *Prompter[T]) key() internal.AnyInfoKey { } func (p *Prompter[T]) init(ctx *internal.Context) (value T, ok, guessed bool, err error) { - value, ok = internal.Get(ctx, p.infoKey) + value, ok = ctx.Get(p.infoKey) if ok && p.validator != nil { if p.validator(value) == nil { return value, true, false, nil @@ -244,7 +244,7 @@ func (p *Prompter[T]) field(ctx *internal.Context) (huh.Field, error) { if err != nil { return nil, err } else if ok && !guessed { - internal.Save(ctx, p.infoKey, value) + ctx.Save(p.infoKey, value) return nil, nil } @@ -296,7 +296,7 @@ func (p *Prompter[T]) finishForm( return fmt.Errorf("invalid value: %w", err) } } - internal.Set(ctx, p.infoKey, value) + ctx.Set(p.infoKey, value) var errs []error for _, writer := range p.writers { if err := writer(ctx, value); err != nil { @@ -341,7 +341,7 @@ func (p *Prompter[T]) Sim(ctx *internal.Context) error { } // in a sim (dry run), assume the user would confirm the guess as far as the // in-memory storage - internal.Save(ctx, p.infoKey, value) + ctx.Save(p.infoKey, value) if guessed { fmt.Printf("Would confirm guessed value for %s: %s\n", p.infoKey, p.stringer(value)) } else { diff --git a/addons/bootstrap/internal/context.go b/addons/bootstrap/internal/context.go index 59811d7e..e780f1f0 100644 --- a/addons/bootstrap/internal/context.go +++ b/addons/bootstrap/internal/context.go @@ -30,18 +30,32 @@ func NewEmptyContext(ctx context.Context) *Context { } } +// Deprecated: modernize +// +//go:fix inline +func Save[T any](ctx *Context, k InfoKey[T], v T) { + ctx.Save(k, v) +} + // Save stores a value, but only if it is not already set. // // If the value is already set, it panics. -func Save[T any](ctx *Context, k InfoKey[T], v T) { +func (ctx *Context) Save[T any](k InfoKey[T], v T) { if _, ok := ctx.info[k]; ok { panic(fmt.Errorf("already saved %s for %v", k.k, k.typ())) } ctx.info[k] = v } -// Set is like save, but it will overwrite any existing value as well. +// Deprecated: modernize +// +//go:fix inline func Set[T any](ctx *Context, k InfoKey[T], v T) { + ctx.Set(k, v) +} + +// Set is like save, but it will overwrite any existing value as well. +func (ctx *Context) Set[T any](k InfoKey[T], v T) { ctx.info[k] = v } @@ -52,7 +66,14 @@ func SetDefault[T any](k InfoKey[T], v T) { defaults[k] = v } +// Deprecated: modernize +// +//go:fix inline func Get[T any](ctx *Context, k InfoKey[T]) (T, bool) { + return ctx.Get(k) +} + +func (ctx *Context) Get[T any](k InfoKey[T]) (T, bool) { v, ok := ctx.info[k] if !ok { var t T @@ -61,7 +82,14 @@ func Get[T any](ctx *Context, k InfoKey[T]) (T, bool) { return v.(T), ok } +// Deprecated: modernize +// +//go:fix inline func Clear[T any](ctx *Context, k InfoKey[T]) { + ctx.Clear(k) +} + +func (ctx *Context) Clear[T any](k InfoKey[T]) { if _, ok := ctx.info[k]; !ok { panic(fmt.Errorf("not saved %s for %v", k.k, k.typ())) } diff --git a/addons/bootstrap/reboot.go b/addons/bootstrap/reboot.go index ad6271be..8fbc9ee1 100644 --- a/addons/bootstrap/reboot.go +++ b/addons/bootstrap/reboot.go @@ -3,10 +3,10 @@ package bootstrap var needsRebootKey = NewKey[bool]("need-reboot-after-bootstrap") func SetNeedsReboot(ctx *Context) { - Set(ctx, needsRebootKey, true) + ctx.Set(needsRebootKey, true) } func needsReboot(ctx *Context) bool { - v, ok := Get(ctx, needsRebootKey) + v, ok := ctx.Get(needsRebootKey) return ok && v } diff --git a/addons/containerd/go.mod b/addons/containerd/go.mod index a35c4ed1..39314a84 100644 --- a/addons/containerd/go.mod +++ b/addons/containerd/go.mod @@ -1,6 +1,6 @@ module fastcat.org/go/gdev/addons/containerd -go 1.26.6 +go 1.27.1 require ( fastcat.org/go/gdev v0.16.0 diff --git a/addons/description.go b/addons/description.go index b8713b46..c7fa71e8 100644 --- a/addons/description.go +++ b/addons/description.go @@ -23,7 +23,7 @@ type Description struct { var enabled = map[string]*registration{} -func Register[T any](a *Addon[T]) { +func (a *Addon[T]) register() { if a.Definition.Name == "" { panic(fmt.Errorf("addon name required")) } diff --git a/addons/docker/go.mod b/addons/docker/go.mod index fbbfa33e..7f290f74 100644 --- a/addons/docker/go.mod +++ b/addons/docker/go.mod @@ -1,6 +1,6 @@ module fastcat.org/go/gdev/addons/docker -go 1.26.6 +go 1.27.1 require ( fastcat.org/go/gdev v0.16.0 diff --git a/addons/gcs/go.mod b/addons/gcs/go.mod index 600f720c..58e78937 100644 --- a/addons/gcs/go.mod +++ b/addons/gcs/go.mod @@ -1,6 +1,6 @@ module fastcat.org/go/gdev/addons/gcs -go 1.26.4 +go 1.27.1 require ( cloud.google.com/go/storage v1.68.0 diff --git a/addons/gcs/go.sum b/addons/gcs/go.sum index 66639ed1..77298057 100644 --- a/addons/gcs/go.sum +++ b/addons/gcs/go.sum @@ -78,6 +78,7 @@ github.com/envoyproxy/protoc-gen-validate v1.3.3/go.mod h1:TsndJ/ngyIdQRhMcVVGDD github.com/felixge/httpsnoop v1.1.0 h1:3YtUj32ZZkqZtt3sZZsClsymw/QDuVfpNhoA31zeORc= github.com/felixge/httpsnoop v1.1.0/go.mod h1:Zqxgdd+1Rkcz8euOqdr7lqgCRJztwr5hp9vDSi5UZCE= github.com/fxamacker/cbor/v2 v2.9.4 h1:xwjVlxEMR3S605oUlgBjKLTTeGFciYPGYCtF/35LKGo= +github.com/fxamacker/cbor/v2 v2.9.4/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/go-jose/go-jose/v4 v4.1.5 h1:RjgjO2LOtWOJKUC5wpwY9LR3B3vwVAz6JS2YHfYU6eA= github.com/go-jose/go-jose/v4 v4.1.5/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= diff --git a/addons/gocache/addon.go b/addons/gocache/addon.go index 474389f7..1b13a4ed 100644 --- a/addons/gocache/addon.go +++ b/addons/gocache/addon.go @@ -120,8 +120,8 @@ func makeCmd() *cobra.Command { fullArgs = append(fullArgs, args...) args = fullArgs ARGS: - for i := len(args) - 1; i >= 0; i-- { - url := args[i] + for _, url := range slices.Backward(args) { + for _, f := range addon.Config.factories { if f.Want(url) { nextRemote, err := f.New(url) diff --git a/addons/gocache/gcs/go.mod b/addons/gocache/gcs/go.mod index 1cb733ba..99fff77a 100644 --- a/addons/gocache/gcs/go.mod +++ b/addons/gocache/gcs/go.mod @@ -1,6 +1,6 @@ module fastcat.org/go/gdev/addons/gocache/gcs -go 1.26.4 +go 1.27.1 require ( cloud.google.com/go/storage v1.68.0 diff --git a/addons/gocache/go.mod b/addons/gocache/go.mod index c4097097..79bb7974 100644 --- a/addons/gocache/go.mod +++ b/addons/gocache/go.mod @@ -1,6 +1,6 @@ module fastcat.org/go/gdev/addons/gocache -go 1.26.6 +go 1.27.1 require ( fastcat.org/go/gdev v0.16.0 diff --git a/addons/gocache/s3/go.mod b/addons/gocache/s3/go.mod index 36453bc3..66334bf4 100644 --- a/addons/gocache/s3/go.mod +++ b/addons/gocache/s3/go.mod @@ -1,6 +1,6 @@ module fastcat.org/go/gdev/addons/gocache/s3 -go 1.26.4 +go 1.27.1 require ( fastcat.org/go/gdev v0.16.0 diff --git a/addons/k3s/go.mod b/addons/k3s/go.mod index 89fd71fc..9c4bc023 100644 --- a/addons/k3s/go.mod +++ b/addons/k3s/go.mod @@ -1,6 +1,6 @@ module fastcat.org/go/gdev/addons/k3s -go 1.26.6 +go 1.27.1 require ( fastcat.org/go/gdev v0.16.0 diff --git a/addons/k3s/go.sum b/addons/k3s/go.sum index 31281b14..90604cf5 100644 --- a/addons/k3s/go.sum +++ b/addons/k3s/go.sum @@ -20,6 +20,7 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8Yc github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/fxamacker/cbor/v2 v2.9.4 h1:xwjVlxEMR3S605oUlgBjKLTTeGFciYPGYCtF/35LKGo= +github.com/fxamacker/cbor/v2 v2.9.4/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/gabriel-vasile/mimetype v1.4.15 h1:05iP/CYtZ/w455R/KZM6rZ5ieAdh99UPtd+d3YzLmaI= github.com/gabriel-vasile/mimetype v1.4.15/go.mod h1:azpTcoLcDZRNgFou5j+APrqQx9HqVPWa6ijYQIIVswQ= github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= diff --git a/addons/k8s/diags.go b/addons/k8s/diags.go index 32853802..cd99447b 100644 --- a/addons/k8s/diags.go +++ b/addons/k8s/diags.go @@ -15,7 +15,8 @@ func DiagsSources() diags.SourceProvider { } var sources []diags.Source for _, ns := range []Namespace{addon.Config.namespace, "kube-system"} { - sources = append(sources, + sources = append( + sources, accessorSource(accStatefulSet, client, ns), accessorSource(accDeployment, client, ns), accessorSource(accService, client, ns), @@ -30,13 +31,17 @@ func DiagsSources() diags.SourceProvider { ) } // non-namespaced objects, but pretend they are in kube-system - sources = append(sources, + sources = append( + sources, accessorSource(accNode, client, "kube-system"), ) return sources, nil } } +// NOTE: these could be methods on accessor[...], but they aren't conceptually +// connected that way. + func accessorSource[ Client client[Resource, Apply], Resource any, @@ -73,7 +78,12 @@ func collectAccessor[ for i := range list { _, om := acc.resourceMeta(&list[i]) // TODO: check tm and om.Namespace match what we got from the accessor - if err := diags.CollectJSON(ctx, coll, path.Join(base, om.Name+".json"), &list[i]); err != nil { + if err := diags.CollectJSON( + ctx, + coll, + path.Join(base, om.Name+".json"), + &list[i], + ); err != nil { // if we get here it's fatal return err } diff --git a/addons/k8s/go.mod b/addons/k8s/go.mod index d03972e1..62a3607b 100644 --- a/addons/k8s/go.mod +++ b/addons/k8s/go.mod @@ -1,6 +1,6 @@ module fastcat.org/go/gdev/addons/k8s -go 1.26.6 +go 1.27.1 require ( fastcat.org/go/gdev v0.16.0 diff --git a/addons/k8s/go.sum b/addons/k8s/go.sum index 720e9ab1..f297a900 100644 --- a/addons/k8s/go.sum +++ b/addons/k8s/go.sum @@ -8,6 +8,7 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8Yc github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/fxamacker/cbor/v2 v2.9.4 h1:xwjVlxEMR3S605oUlgBjKLTTeGFciYPGYCtF/35LKGo= +github.com/fxamacker/cbor/v2 v2.9.4/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-openapi/jsonpointer v1.0.1 h1:2KxywRmNwJkT/FMBa3iRNHEaAxSJvjqoufQZy3au1Mg= diff --git a/addons/k8s/resource-config.go b/addons/k8s/resource-config.go index 9e4accc3..4ff54f56 100644 --- a/addons/k8s/resource-config.go +++ b/addons/k8s/resource-config.go @@ -21,7 +21,7 @@ func ConfigMap(apply *applyCoreV1.ConfigMapApplyConfiguration) Resource { apply. WithLabels(l). WithAnnotations(l) - return &configMap{newAppliable(accConfigMap, apply)} + return &configMap{accConfigMap.appliable(apply)} } type secret struct { @@ -37,5 +37,5 @@ func Secret(apply *applyCoreV1.SecretApplyConfiguration) Resource { apply. WithLabels(l). WithAnnotations(l) - return &secret{newAppliable(accSecret, apply)} + return &secret{accSecret.appliable(apply)} } diff --git a/addons/k8s/resource-generic.go b/addons/k8s/resource-generic.go index 6ca9558c..e82f824d 100644 --- a/addons/k8s/resource-generic.go +++ b/addons/k8s/resource-generic.go @@ -22,12 +22,7 @@ type appliable[ apply Apply } -func newAppliable[ - Client client[Resource, Apply], - Resource any, - Apply apply[Apply], -]( - acc accessor[Client, Resource, Apply], +func (acc accessor[Client, Resource, Apply]) appliable( apply Apply, ) appliable[Client, Resource, Apply] { m, o := acc.applyMeta(apply) diff --git a/addons/k8s/resource-podder.go b/addons/k8s/resource-podder.go index 6886d1d1..c9cd8e17 100644 --- a/addons/k8s/resource-podder.go +++ b/addons/k8s/resource-podder.go @@ -16,16 +16,11 @@ type podder[ appliable[Client, Resource, Apply] } -func newPodder[ - Client client[Resource, Apply], - Resource any, - Apply apply[Apply], -]( - acc accessor[Client, Resource, Apply], +func (acc accessor[Client, Resource, Apply]) podder( apply Apply, ) *podder[Client, Resource, Apply] { // TODO: add standard annotations and labels - return &podder[Client, Resource, Apply]{newAppliable(acc, apply)} + return &podder[Client, Resource, Apply]{acc.appliable(apply)} } // ContainerImages implements resource.ContainerResource. @@ -55,7 +50,7 @@ func StatefulSet(apply *applyAppsV1.StatefulSetApplyConfiguration) ContainerReso apply.Spec.Template. WithLabels(l). WithAnnotations(l) - return newPodder(accStatefulSet, apply) + return accStatefulSet.podder(apply) } func Deployment(apply *applyAppsV1.DeploymentApplyConfiguration) ContainerResource { @@ -67,7 +62,7 @@ func Deployment(apply *applyAppsV1.DeploymentApplyConfiguration) ContainerResour apply.Spec.Template. WithLabels(l). WithAnnotations(l) - return newPodder(accDeployment, apply) + return accDeployment.podder(apply) } func CronJob(apply *applyBatchV1.CronJobApplyConfiguration) ContainerResource { @@ -79,7 +74,7 @@ func CronJob(apply *applyBatchV1.CronJobApplyConfiguration) ContainerResource { apply.Spec.JobTemplate.Spec.Template. WithLabels(l). WithAnnotations(l) - return newPodder(accCronJob, apply) + return accCronJob.podder(apply) } func BatchJob(apply *applyBatchV1.JobApplyConfiguration) ContainerResource { @@ -91,7 +86,7 @@ func BatchJob(apply *applyBatchV1.JobApplyConfiguration) ContainerResource { apply.Spec.Template. WithLabels(l). WithAnnotations(l) - return newPodder(accBatchJob, apply) + return accBatchJob.podder(apply) } // NOTE: Direct Pod manipulation is intentionally left out because it's a Bad Idea diff --git a/addons/k8s/resource-service.go b/addons/k8s/resource-service.go index 4fe13143..70ed553e 100644 --- a/addons/k8s/resource-service.go +++ b/addons/k8s/resource-service.go @@ -21,5 +21,5 @@ func Service(apply *applyCoreV1.ServiceApplyConfiguration) Resource { apply. WithLabels(l). WithAnnotations(l) - return &service{newAppliable(accService, apply)} + return &service{accService.appliable(apply)} } diff --git a/addons/k8s/resource-storage.go b/addons/k8s/resource-storage.go index 3c873159..e5826158 100644 --- a/addons/k8s/resource-storage.go +++ b/addons/k8s/resource-storage.go @@ -29,5 +29,5 @@ func PersistentVolumeClaim(apply *applyCoreV1.PersistentVolumeClaimApplyConfigur apply. WithLabels(l). WithAnnotations(l) - return &pvc{newAppliable(accPVC, apply)} + return &pvc{accPVC.appliable(apply)} } diff --git a/addons/mariadb/go.mod b/addons/mariadb/go.mod index 2a6c0498..ae250dbf 100644 --- a/addons/mariadb/go.mod +++ b/addons/mariadb/go.mod @@ -1,6 +1,6 @@ module fastcat.org/go/gdev/addons/mariadb -go 1.26.6 +go 1.27.1 require ( fastcat.org/go/gdev v0.16.0 diff --git a/addons/mariadb/go.sum b/addons/mariadb/go.sum index 1094a7cd..8c5ab275 100644 --- a/addons/mariadb/go.sum +++ b/addons/mariadb/go.sum @@ -22,6 +22,7 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8Yc github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/fxamacker/cbor/v2 v2.9.4 h1:xwjVlxEMR3S605oUlgBjKLTTeGFciYPGYCtF/35LKGo= +github.com/fxamacker/cbor/v2 v2.9.4/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-openapi/jsonpointer v1.0.1 h1:2KxywRmNwJkT/FMBa3iRNHEaAxSJvjqoufQZy3au1Mg= diff --git a/addons/pm/client/http.go b/addons/pm/client/http.go index 3ac275aa..66f84a81 100644 --- a/addons/pm/client/http.go +++ b/addons/pm/client/http.go @@ -45,20 +45,24 @@ func (h *HTTP) Ping(ctx context.Context) error { // Child implements api.API. func (h *HTTP) Child(ctx context.Context, name string) (*api.ChildWithStatus, error) { - r, err := h.do(ctx, http.MethodGet, withPathValue(api.PathOneChild, api.PathChildParamName, name), nil) - if err != nil { - return nil, err - } - return internal.JSONBody[*api.ChildWithStatus](ctx, r.Body, "", true) + return h.doJSON[*api.ChildWithStatus]( + ctx, + http.MethodGet, + withPathValue(api.PathOneChild, api.PathChildParamName, name), + nil, + "", + ) } // DeleteChild implements api.API. func (h *HTTP) DeleteChild(ctx context.Context, name string) (*api.ChildWithStatus, error) { - r, err := h.do(ctx, http.MethodDelete, withPathValue(api.PathOneChild, api.PathChildParamName, name), nil) - if err != nil { - return nil, err - } - return internal.JSONBody[*api.ChildWithStatus](ctx, r.Body, "", true) + return h.doJSON[*api.ChildWithStatus]( + ctx, + http.MethodDelete, + withPathValue(api.PathOneChild, api.PathChildParamName, name), + nil, + "", + ) } // PutChild implements api.API. @@ -67,38 +71,40 @@ func (h *HTTP) PutChild(ctx context.Context, child api.Child) (*api.ChildWithSta if err != nil { return nil, err } - r, err := h.do(ctx, http.MethodPut, api.PathChild, bytes.NewReader(body)) - if err != nil { - return nil, err - } - return internal.JSONBody[*api.ChildWithStatus](ctx, r.Body, "", true) + return h.doJSON[*api.ChildWithStatus]( + ctx, + http.MethodPut, + api.PathChild, + bytes.NewReader(body), + "", + ) } // StartChild implements api.API. func (h *HTTP) StartChild(ctx context.Context, name string) (*api.ChildWithStatus, error) { - r, err := h.do(ctx, http.MethodPost, withPathValue(api.PathStartChild, api.PathChildParamName, name), nil) - if err != nil { - return nil, err - } - return internal.JSONBody[*api.ChildWithStatus](ctx, r.Body, "", true) + return h.doJSON[*api.ChildWithStatus]( + ctx, + http.MethodPost, + withPathValue(api.PathStartChild, api.PathChildParamName, name), + nil, + "", + ) } // StopChild implements api.API. func (h *HTTP) StopChild(ctx context.Context, name string) (*api.ChildWithStatus, error) { - r, err := h.do(ctx, http.MethodPost, withPathValue(api.PathStopChild, api.PathChildParamName, name), nil) - if err != nil { - return nil, err - } - return internal.JSONBody[*api.ChildWithStatus](ctx, r.Body, "", true) + return h.doJSON[*api.ChildWithStatus]( + ctx, + http.MethodPost, + withPathValue(api.PathStopChild, api.PathChildParamName, name), + nil, + "", + ) } // Summary implements api.API. func (h *HTTP) Summary(ctx context.Context) ([]api.ChildSummary, error) { - r, err := h.do(ctx, http.MethodGet, api.PathSummary, nil) - if err != nil { - return nil, err - } - return internal.JSONBody[[]api.ChildSummary](ctx, r.Body, "dive", true) + return h.doJSON[[]api.ChildSummary](ctx, http.MethodGet, api.PathSummary, nil, "dive") } // Terminate implements api.API. @@ -127,6 +133,21 @@ func (h *HTTP) do( return res, nil } +func (h *HTTP) doJSON[T any]( + ctx context.Context, + method string, + path string, + reqBody io.Reader, + validation string, +) (T, error) { + resp, err := h.do(ctx, method, path, reqBody) + if err != nil { + var zero T + return zero, err + } + return internal.JSONBody[T](ctx, resp.Body, validation, true) +} + func (h *HTTP) c() *http.Client { if h.Client != nil { return h.Client diff --git a/addons/pm/internal/json.go b/addons/pm/internal/json.go index 4e238dc4..64666ba5 100644 --- a/addons/pm/internal/json.go +++ b/addons/pm/internal/json.go @@ -41,6 +41,7 @@ func JSONBody[T any]( return value, badReqOrResp(errors.New("body required"), response) } defer r.Close() // nolint:errcheck + // FUTURE: convert to json/v2.UnmarshalRead d := json.NewDecoder(r) d.DisallowUnknownFields() if err := d.Decode(&value); err != nil { diff --git a/addons/pm/server/child.go b/addons/pm/server/child.go index 00e64f00..b149369d 100644 --- a/addons/pm/server/child.go +++ b/addons/pm/server/child.go @@ -12,6 +12,7 @@ import ( "net/url" "os" "os/exec" + "runtime/pprof" "slices" "strconv" "sync" @@ -469,9 +470,11 @@ func (c *child) start( return nil, api.ExecStatus{State: api.ExecNotStarted, StartErr: err.Error()}, errorState } log.Printf("started %s as pid %d", name, cmd.Process.Pid) - c.wg.Go(func() { - err := cmd.Wait() - exited <- err + pprof.Do(ctx, pprof.Labels("child", name), func(context.Context) { + c.wg.Go(func() { + err := cmd.Wait() + exited <- err + }) }) eStat := api.ExecStatus{ State: api.ExecRunning, diff --git a/addons/pm/server/daemon.go b/addons/pm/server/daemon.go index 32ad2697..54131666 100644 --- a/addons/pm/server/daemon.go +++ b/addons/pm/server/daemon.go @@ -6,6 +6,7 @@ import ( "log" "maps" "net/http" + "runtime/pprof" "slices" "sync" "time" @@ -94,12 +95,14 @@ func (d *daemon) PutChild(ctx context.Context, child api.Child) (*api.ChildWithS } c := newChild(child, d.isolator) d.children[child.Name] = c - go func() { - c.run() - d.mu.Lock() - delete(d.children, child.Name) - d.mu.Unlock() - }() + pprof.Do(ctx, pprof.Labels("child", child.Name), func(context.Context) { + go func() { + c.run() + d.mu.Lock() + delete(d.children, child.Name) + d.mu.Unlock() + }() + }) // ensure the manager goroutine has started c.cmds <- childPing return &api.ChildWithStatus{ @@ -211,7 +214,7 @@ func (d *daemon) Summary(ctx context.Context) ([]api.ChildSummary, error) { return ret, nil } -func (d *daemon) Terminate(context.Context) error { +func (d *daemon) Terminate(ctx context.Context) error { if d.onTerminate != nil { d.onTerminate() } @@ -229,22 +232,24 @@ func (d *daemon) Terminate(context.Context) error { d.mu.Unlock() var wg sync.WaitGroup for _, child := range children { - wg.Go(func() { - child.cmds <- childStop - // wait for it to stop - // TODO: avoid polling - // TODO: add a final timeout? - check := time.NewTicker(10 * time.Millisecond) - defer check.Stop() - for range check.C { - if s := child.Status().State; s == api.ChildError || - s == api.ChildStopped || - s == api.ChildDone { - break + pprof.Do(ctx, pprof.Labels("child", child.def.Name), func(context.Context) { + wg.Go(func() { + child.cmds <- childStop + // wait for it to stop + // TODO: avoid polling + // TODO: add a final timeout? + check := time.NewTicker(10 * time.Millisecond) + defer check.Stop() + for range check.C { + if s := child.Status().State; s == api.ChildError || + s == api.ChildStopped || + s == api.ChildDone { + break + } } - } - child.cmds <- childDelete - child.Wait() + child.cmds <- childDelete + child.Wait() + }) }) } wg.Wait() diff --git a/addons/postgres/go.mod b/addons/postgres/go.mod index 2d702c52..09364f36 100644 --- a/addons/postgres/go.mod +++ b/addons/postgres/go.mod @@ -1,6 +1,6 @@ module fastcat.org/go/gdev/addons/postgres -go 1.26.6 +go 1.27.1 require ( fastcat.org/go/gdev v0.16.0 diff --git a/addons/postgres/go.sum b/addons/postgres/go.sum index 26967c88..829e8cf0 100644 --- a/addons/postgres/go.sum +++ b/addons/postgres/go.sum @@ -20,6 +20,7 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8Yc github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/fxamacker/cbor/v2 v2.9.4 h1:xwjVlxEMR3S605oUlgBjKLTTeGFciYPGYCtF/35LKGo= +github.com/fxamacker/cbor/v2 v2.9.4/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-openapi/jsonpointer v1.0.1 h1:2KxywRmNwJkT/FMBa3iRNHEaAxSJvjqoufQZy3au1Mg= diff --git a/addons/valkey/go.mod b/addons/valkey/go.mod index 1955aa16..0644e0ae 100644 --- a/addons/valkey/go.mod +++ b/addons/valkey/go.mod @@ -1,6 +1,6 @@ module fastcat.org/go/gdev/addons/valkey -go 1.26.6 +go 1.27.1 require ( fastcat.org/go/gdev v0.16.0 diff --git a/addons/valkey/go.sum b/addons/valkey/go.sum index f1e0f855..9f006b38 100644 --- a/addons/valkey/go.sum +++ b/addons/valkey/go.sum @@ -20,6 +20,7 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8Yc github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/fxamacker/cbor/v2 v2.9.4 h1:xwjVlxEMR3S605oUlgBjKLTTeGFciYPGYCtF/35LKGo= +github.com/fxamacker/cbor/v2 v2.9.4/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-openapi/jsonpointer v1.0.1 h1:2KxywRmNwJkT/FMBa3iRNHEaAxSJvjqoufQZy3au1Mg= diff --git a/examples/full-stack/go.mod b/examples/full-stack/go.mod index d5a851e9..86b7488b 100644 --- a/examples/full-stack/go.mod +++ b/examples/full-stack/go.mod @@ -1,6 +1,6 @@ module fastcat.org/go/gdev/examples/full-stack -go 1.26.6 +go 1.27.1 require ( entgo.io/ent v0.14.6 diff --git a/examples/full-stack/go.sum b/examples/full-stack/go.sum index 8a1a1aab..8d2e2c14 100644 --- a/examples/full-stack/go.sum +++ b/examples/full-stack/go.sum @@ -60,6 +60,7 @@ github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuM github.com/felixge/httpsnoop v1.1.0 h1:3YtUj32ZZkqZtt3sZZsClsymw/QDuVfpNhoA31zeORc= github.com/felixge/httpsnoop v1.1.0/go.mod h1:Zqxgdd+1Rkcz8euOqdr7lqgCRJztwr5hp9vDSi5UZCE= github.com/fxamacker/cbor/v2 v2.9.4 h1:xwjVlxEMR3S605oUlgBjKLTTeGFciYPGYCtF/35LKGo= +github.com/fxamacker/cbor/v2 v2.9.4/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/gabriel-vasile/mimetype v1.4.15 h1:05iP/CYtZ/w455R/KZM6rZ5ieAdh99UPtd+d3YzLmaI= github.com/gabriel-vasile/mimetype v1.4.15/go.mod h1:azpTcoLcDZRNgFou5j+APrqQx9HqVPWa6ijYQIIVswQ= github.com/go-chi/chi/v5 v5.3.2 h1:5YQkICvTCSZ25hoRsyJazN0scjzKGiu4VAUc7H1o1nY= diff --git a/examples/gdev/go.mod b/examples/gdev/go.mod index db6a329f..f04097a0 100644 --- a/examples/gdev/go.mod +++ b/examples/gdev/go.mod @@ -1,6 +1,6 @@ module fastcat.org/go/gdev/examples/gdev -go 1.26.6 +go 1.27.1 require ( fastcat.org/go/gdev v0.16.0 diff --git a/examples/gdev/go.sum b/examples/gdev/go.sum index 7b24a4cf..c2f2844a 100644 --- a/examples/gdev/go.sum +++ b/examples/gdev/go.sum @@ -149,6 +149,7 @@ github.com/containerd/cgroups/v3 v3.1.3/go.mod h1:PKZ2AcWmSBsY/tJUVhtS/rluX0b1uq github.com/containerd/containerd/api v1.12.0 h1:kuQm82SbDrCuO4n7hf2L8zsBtZLuympyq5X/VotfX2A= github.com/containerd/containerd/api v1.12.0/go.mod h1:EBcSzoi9Vl18cdODaXUCskf3D2NT8lsSXeZJnU5jIUc= github.com/containerd/containerd/v2 v2.4.1 h1:DUx/ZJN7cEu0WuzHClDB+68H/bqMEH5pWoEjf0ae4hc= +github.com/containerd/containerd/v2 v2.4.1/go.mod h1:vgLdtvl3prFk1d3ZVqQcsDD5Q9IKM+vAIdU54U85w+I= github.com/containerd/continuity v0.5.0 h1:7a85HZpCSs+1Zps0Ee3DPSuAWY+0SJM1JNM51nlEVDg= github.com/containerd/continuity v0.5.0/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= @@ -205,6 +206,7 @@ github.com/erofs/go-erofs v0.3.1/go.mod h1:XkSeN9MHszGd4+3gcEjadJLYHCQpWzJ7/8yzn github.com/felixge/httpsnoop v1.1.0 h1:3YtUj32ZZkqZtt3sZZsClsymw/QDuVfpNhoA31zeORc= github.com/felixge/httpsnoop v1.1.0/go.mod h1:Zqxgdd+1Rkcz8euOqdr7lqgCRJztwr5hp9vDSi5UZCE= github.com/fxamacker/cbor/v2 v2.9.4 h1:xwjVlxEMR3S605oUlgBjKLTTeGFciYPGYCtF/35LKGo= +github.com/fxamacker/cbor/v2 v2.9.4/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/gabriel-vasile/mimetype v1.4.15 h1:05iP/CYtZ/w455R/KZM6rZ5ieAdh99UPtd+d3YzLmaI= github.com/gabriel-vasile/mimetype v1.4.15/go.mod h1:azpTcoLcDZRNgFou5j+APrqQx9HqVPWa6ijYQIIVswQ= github.com/go-jose/go-jose/v4 v4.1.5 h1:RjgjO2LOtWOJKUC5wpwY9LR3B3vwVAz6JS2YHfYU6eA= diff --git a/examples/stack/go.mod b/examples/stack/go.mod index 1fe086f4..27a97a3f 100644 --- a/examples/stack/go.mod +++ b/examples/stack/go.mod @@ -1,6 +1,6 @@ module fastcat.org/go/gdev/examples/stack -go 1.26.6 +go 1.27.1 require ( fastcat.org/go/gdev v0.16.0 diff --git a/go.mod b/go.mod index edbd6676..b89952e7 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module fastcat.org/go/gdev -go 1.26.6 +go 1.27.1 require ( github.com/ProtonMail/go-crypto v1.4.1 diff --git a/go.work b/go.work index 3bb59f8e..6f537f32 100644 --- a/go.work +++ b/go.work @@ -1,4 +1,4 @@ -go 1.26.7 +go 1.27.1 use ( . diff --git a/internal/json.go b/internal/json.go index 202c44e4..d31ecca4 100644 --- a/internal/json.go +++ b/internal/json.go @@ -16,6 +16,7 @@ func ReadJSONFile[T any](name string) (T, error) { return result, err } defer f.Close() //nolint:errcheck // don't care, it's a read-only handle + // FUTURE: convert to json/v2.UnmarshalRead d := json.NewDecoder(f) err = d.Decode(&result) if err != nil { diff --git a/magefiles/go.mod b/magefiles/go.mod index 8f972879..fd4867e7 100644 --- a/magefiles/go.mod +++ b/magefiles/go.mod @@ -1,6 +1,6 @@ module fastcat.org/go/gdev/magefiles -go 1.26.6 +go 1.27.1 require ( github.com/goccy/go-yaml v1.19.2