From a8c876ab53ac348de4395e02f9445576663a87f2 Mon Sep 17 00:00:00 2001 From: Tom Gehrke Date: Fri, 29 Sep 2023 11:25:54 +0200 Subject: [PATCH] implement kwasm lockfile support to avoid unnecessary restarts --- cmd/install.go | 22 ++++++++++++---- pkg/shim/install.go | 32 ++++++++++++++++++----- pkg/state/shim.go | 38 +++++++++++++++++++++++++++ pkg/state/state.go | 62 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 143 insertions(+), 11 deletions(-) create mode 100644 pkg/state/shim.go create mode 100644 pkg/state/state.go diff --git a/cmd/install.go b/cmd/install.go index 6d91c99..22e4dcd 100644 --- a/cmd/install.go +++ b/cmd/install.go @@ -35,21 +35,33 @@ var installCmd = &cobra.Command{ slog.Error(err.Error()) return } + anythingChanged := false for _, file := range files { - binPath, err := shim.Install(&config, file.Name()) + fileName := file.Name() + runtimeName := shim.RuntimeName(fileName) + + binPath, changed, err := shim.Install(&config, fileName) if err != nil { - slog.Error(err.Error()) + slog.Error("failed to install shim", "shim", runtimeName, "error", err) return } - slog.Info("shim installed", "shim", shim.RuntimeName(file.Name()), "path", binPath) + anythingChanged = anythingChanged || changed + slog.Info("shim installed", "shim", runtimeName, "path", binPath, "new-version", changed) + configPath, err := containerd.WriteConfig(&config, binPath) if err != nil { - slog.Error(err.Error()) + slog.Error("failed to write containerd config", "shim", runtimeName, "path", configPath, "error", err) return } - slog.Info("shim configured", "shim", shim.RuntimeName(file.Name()), "path", configPath) + slog.Info("shim configured", "shim", runtimeName, "path", configPath) + } + + if !anythingChanged { + slog.Info("nothing changed, nothing more to do") + return } + slog.Info("restarting containerd") err = containerd.RestartRuntime() if err != nil { slog.Error("failed to restart containerd", "error", err) diff --git a/pkg/shim/install.go b/pkg/shim/install.go index 8bdc6d8..0c519fb 100644 --- a/pkg/shim/install.go +++ b/pkg/shim/install.go @@ -17,32 +17,52 @@ package shim import ( + "crypto/sha256" "io" "os" "path" "github.com/kwasm/kwasm-node-installer/pkg/config" + "github.com/kwasm/kwasm-node-installer/pkg/state" ) -func Install(config *config.Config, shimName string) (string, error) { +func Install(config *config.Config, shimName string) (string, bool, error) { shimPath := config.AssetPath(shimName) srcFile, err := os.OpenFile(shimPath, os.O_RDONLY, 0000) if err != nil { - return "", err + return "", false, err } dstFilePath := path.Join(config.Kwasm.Path, "bin", shimName) dstFilePathHost := config.PathWithHost(dstFilePath) err = os.MkdirAll(path.Dir(dstFilePathHost), 0755) if err != nil { - return dstFilePath, err + return dstFilePath, false, err } dstFile, err := os.OpenFile(dstFilePathHost, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) if err != nil { - return "", err + return "", false, err } - _, err = io.Copy(dstFile, srcFile) - return dstFilePath, err + st, err := state.Get(config) + if err != nil { + return "", false, err + } + shimSha256 := sha256.New() + + _, err = io.Copy(io.MultiWriter(dstFile, shimSha256), srcFile) + runtimeName := RuntimeName(shimName) + changed := st.ShimChanged(runtimeName, shimSha256.Sum(nil), dstFilePath) + if changed { + st.UpdateShim(runtimeName, state.Shim{ + Path: dstFilePath, + Sha256: shimSha256.Sum(nil), + }) + if err := st.Write(); err != nil { + return "", false, err + } + } + + return dstFilePath, changed, err } diff --git a/pkg/state/shim.go b/pkg/state/shim.go new file mode 100644 index 0000000..eb00401 --- /dev/null +++ b/pkg/state/shim.go @@ -0,0 +1,38 @@ +package state + +import ( + "encoding/hex" + "encoding/json" +) + +type Shim struct { + Sha256 []byte + Path string +} + +func (s *Shim) MarshalJSON() ([]byte, error) { + return json.Marshal(&struct { + Sha256 string `json:"sha256"` + Path string `json:"path"` + }{ + Sha256: hex.EncodeToString(s.Sha256), + Path: s.Path, + }) +} + +func (s *Shim) UnmarshalJSON(data []byte) error { + aux := &struct { + Sha256 string `json:"sha256"` + Path string `json:"path"` + }{} + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + s.Path = aux.Path + sha256, err := hex.DecodeString(aux.Sha256) + if err != nil { + return err + } + s.Sha256 = sha256 + return nil +} diff --git a/pkg/state/state.go b/pkg/state/state.go new file mode 100644 index 0000000..f22bcc8 --- /dev/null +++ b/pkg/state/state.go @@ -0,0 +1,62 @@ +package state + +import ( + "bytes" + "encoding/json" + "errors" + "log/slog" + "os" + "path" + + "github.com/kwasm/kwasm-node-installer/pkg/config" +) + +type state struct { + Shims map[string]*Shim `json:"shims"` + config *config.Config +} + +func Get(config *config.Config) (*state, error) { + out := state{ + Shims: make(map[string]*Shim), + config: config, + } + content, err := os.ReadFile(filePath(config)) + if err == nil { + err := json.Unmarshal(content, &out) + return &out, err + } + if !errors.Is(err, os.ErrNotExist) { + return nil, err + } + + return &out, nil +} + +func (l *state) ShimChanged(shimName string, sha256 []byte, path string) bool { + shim, ok := l.Shims[shimName] + if !ok { + return true + } + + return !bytes.Equal(shim.Sha256, sha256) || shim.Path != path +} + +func (l *state) UpdateShim(shimName string, shim Shim) { + l.Shims[shimName] = &shim +} + +func (l *state) Write() error { + out, err := json.MarshalIndent(l, "", " ") + if err != nil { + return err + } + + slog.Info("writing lock file", "content", string(out)) + + return os.WriteFile(filePath(l.config), out, 0644) +} + +func filePath(config *config.Config) string { + return config.PathWithHost(path.Join(config.Kwasm.Path, "kwasm-lock.json")) +}