Skip to content

Commit

Permalink
feat(tools/cosmovisor): create current symlink as relative
Browse files Browse the repository at this point in the history
if cosmovisor used within docker
daemon workdir can be mounted as volume which leads
to current symlink no working on the host machine

Signed-off-by: Artur Troian <troian.ap@gmail.com>
  • Loading branch information
troian committed Sep 27, 2024
1 parent 0d9b416 commit f932209
Show file tree
Hide file tree
Showing 11 changed files with 366 additions and 150 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ Every module contains its own CHANGELOG.md. Please refer to the module you are i

### Improvements

* (tools/cosmovisor) [#21891](https://github.com/cosmos/cosmos-sdk/pull/21891) create `current` symlink as relative
* (all) [#16537](https://github.com/cosmos/cosmos-sdk/pull/16537) Properly propagated `fmt.Errorf` errors and using `errors.New` where appropriate.
* (client) [#17503](https://github.com/cosmos/cosmos-sdk/pull/17503) Add `client.Context{}.WithAddressCodec`, `WithValidatorAddressCodec`, `WithConsensusAddressCodec` to provide address codecs to the client context. See the [UPGRADING.md](./UPGRADING.md) for more details.
* (crypto/keyring) [#17503](https://github.com/cosmos/cosmos-sdk/pull/17503) Simplify keyring interfaces to use `[]byte` instead of `sdk.Address` for addresses.
Expand Down
40 changes: 21 additions & 19 deletions tools/cosmovisor/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,14 +111,19 @@ func (cfg *Config) UpgradeInfoFilePath() string {

// SymLinkToGenesis creates a symbolic link from "./current" to the genesis directory.
func (cfg *Config) SymLinkToGenesis() (string, error) {
genesis := filepath.Join(cfg.Root(), genesisDir)
link := filepath.Join(cfg.Root(), currentLink)
// workdir is set to cosmovisor directory so relative
// symlinks are getting resolved correctly
if err := os.Symlink(genesisDir, currentLink); err != nil {
return "", err
}

if err := os.Symlink(genesis, link); err != nil {
res, err := filepath.EvalSymlinks(cfg.GenesisBin())
if err != nil {
return "", err
}

// and return the genesis binary
return cfg.GenesisBin(), nil
return res, nil
}

// WaitRestartDelay will block and wait until the RestartDelay has elapsed.
Expand All @@ -132,27 +137,24 @@ func (cfg *Config) WaitRestartDelay() {
// This will resolve the symlink to the underlying directory to make it easier to debug
func (cfg *Config) CurrentBin() (string, error) {
cur := filepath.Join(cfg.Root(), currentLink)

// if nothing here, fallback to genesis
info, err := os.Lstat(cur)
if err != nil {
// Create symlink to the genesis
return cfg.SymLinkToGenesis()
}
// if it is there, ensure it is a symlink
if info.Mode()&os.ModeSymlink == 0 {
info, err := os.Lstat(cur)
if err != nil || (info.Mode()&os.ModeSymlink == 0) {
// Create symlink to the genesis
return cfg.SymLinkToGenesis()
}

// resolve it
dest, err := os.Readlink(cur)
res, err := filepath.EvalSymlinks(cur)
if err != nil {
// Create symlink to the genesis
return cfg.SymLinkToGenesis()
}

// and return the binary
binpath := filepath.Join(dest, "bin", cfg.Name)
binpath := filepath.Join(res, "bin", cfg.Name)

return binpath, nil
}

Expand Down Expand Up @@ -378,24 +380,24 @@ func (cfg *Config) SetCurrentUpgrade(u upgradetypes.Plan) (rerr error) {
}

// set a symbolic link
link := filepath.Join(cfg.Root(), currentLink)
// link := filepath.Join(cfg.Root(), currentLink)
safeName := url.PathEscape(u.Name)
upgrade := filepath.Join(cfg.Root(), upgradesDir, safeName)
upgrade := filepath.Join(upgradesDir, safeName)

// remove link if it exists
if _, err := os.Stat(link); err == nil {
if err := os.Remove(link); err != nil {
if _, err := os.Stat(currentLink); err == nil {
if err := os.Remove(currentLink); err != nil {
return fmt.Errorf("failed to remove existing link: %w", err)
}
}

// point to the new directory
if err := os.Symlink(upgrade, link); err != nil {
if err := os.Symlink(upgrade, currentLink); err != nil {
return fmt.Errorf("creating current symlink: %w", err)
}

cfg.currentUpgrade = u
f, err := os.Create(filepath.Join(upgrade, upgradetypes.UpgradeInfoFilename))
f, err := os.Create(filepath.Join(cfg.Root(), upgrade, upgradetypes.UpgradeInfoFilename))
if err != nil {
return err
}
Expand Down
8 changes: 7 additions & 1 deletion tools/cosmovisor/cmd/cosmovisor/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
"cosmossdk.io/x/upgrade/plan"
)

func NewIntCmd() *cobra.Command {
func NewInitCmd() *cobra.Command {
initCmd := &cobra.Command{
Use: "init <path to executable>",
Short: "Initialize a cosmovisor daemon home directory.",
Expand Down Expand Up @@ -93,6 +93,12 @@ func InitializeCosmovisor(logger log.Logger, args []string) error {
return err
}

// set current working directory to $DAEMON_NAME/cosmosvisor
// to allow current symlink to be relative
if err = os.Chdir(cfg.Root()); err != nil {
return fmt.Errorf("failed to change directory to %s: %w", cfg.Root(), err)
}

logger.Info("checking on the current symlink and creating it if needed")
cur, curErr := cfg.CurrentBin()
if curErr != nil {
Expand Down
109 changes: 71 additions & 38 deletions tools/cosmovisor/cmd/cosmovisor/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,8 @@ func (s *InitTestSuite) readStdInpFromFile(data []byte) {
}

var (
_ io.Reader = BufferedPipe{}
_ io.Writer = BufferedPipe{}
_ io.Reader = &BufferedPipe{}
_ io.Writer = &BufferedPipe{}
)

// BufferedPipe contains a connected read/write pair of files (a pipe),
Expand Down Expand Up @@ -167,8 +167,8 @@ type BufferedPipe struct {
// NewBufferedPipe creates a new BufferedPipe with the given name.
// Files must be closed once you are done with them (e.g. with .Close()).
// Once ready, buffering must be started using .Start(). See also StartNewBufferedPipe.
func NewBufferedPipe(name string, replicateTo ...io.Writer) (BufferedPipe, error) {
p := BufferedPipe{Name: name}
func NewBufferedPipe(name string, replicateTo ...io.Writer) (*BufferedPipe, error) {
p := &BufferedPipe{Name: name}
p.Reader, p.Writer, p.Error = os.Pipe()
if p.Error != nil {
return p, p.Error
Expand All @@ -184,7 +184,7 @@ func NewBufferedPipe(name string, replicateTo ...io.Writer) (BufferedPipe, error
//
// p, _ := NewBufferedPipe(name, replicateTo...)
// p.Start()
func StartNewBufferedPipe(name string, replicateTo ...io.Writer) (BufferedPipe, error) {
func StartNewBufferedPipe(name string, replicateTo ...io.Writer) (*BufferedPipe, error) {
p, err := NewBufferedPipe(name, replicateTo...)
if err != nil {
return p, err
Expand Down Expand Up @@ -214,6 +214,7 @@ func (p *BufferedPipe) Start() {
if _, p.Error = io.Copy(&b, p.BufferReader); p.Error != nil {
b.WriteString("buffer error: " + p.Error.Error())
}

p.buffer <- b.Bytes()
}()
p.started = true
Expand All @@ -238,6 +239,7 @@ func (p *BufferedPipe) Collect() []byte {
panic("buffered pipe " + p.Name + " has not been started: cannot collect")
}
_ = p.Writer.Close()

if p.buffer == nil {
return []byte{}
}
Expand All @@ -247,12 +249,12 @@ func (p *BufferedPipe) Collect() []byte {
}

// Read implements the io.Reader interface on this BufferedPipe.
func (p BufferedPipe) Read(bz []byte) (n int, err error) {
func (p *BufferedPipe) Read(bz []byte) (n int, err error) {
return p.Reader.Read(bz)
}

// Write implements the io.Writer interface on this BufferedPipe.
func (p BufferedPipe) Write(bz []byte) (n int, err error) {
func (p *BufferedPipe) Write(bz []byte) (n int, err error) {
return p.Writer.Write(bz)
}

Expand All @@ -274,7 +276,7 @@ func (s *InitTestSuite) NewCapturingLogger() (*BufferedPipe, log.Logger) {
bufferedStdOut, err := StartNewBufferedPipe("stdout", os.Stdout)
s.Require().NoError(err, "creating stdout buffered pipe")
logger := log.NewLogger(bufferedStdOut, log.ColorOption(false), log.TimeFormatOption(time.RFC3339Nano)).With(log.ModuleKey, cosmovisorDirName)
return &bufferedStdOut, logger
return bufferedStdOut, logger
}

// CreateHelloWorld creates a shell script that outputs HELLO WORLD.
Expand Down Expand Up @@ -443,15 +445,13 @@ func (s *InitTestSuite) TestInitializeCosmovisorInvalidExisting() {
rootDir := filepath.Join(env.Home, cosmovisorDirName)
require.NoError(t, os.MkdirAll(rootDir, 0o755))
curLn := filepath.Join(rootDir, "current")
genDir := filepath.Join(rootDir, "genesis")
require.NoError(t, copyFile(hwExe, curLn))
expErr := fmt.Sprintf("symlink %s %s: file exists", genDir, curLn)

s.setEnv(t, env)
buffer, logger := s.NewCapturingLogger()
logger.Info(fmt.Sprintf("Calling InitializeCosmovisor: %s", t.Name()))
err := InitializeCosmovisor(logger, []string{hwExe})
require.EqualError(t, err, expErr, "calling InitializeCosmovisor")
require.EqualError(t, err, "symlink genesis current: file exists", "calling InitializeCosmovisor")
bufferBz := buffer.Collect()
bufferStr := string(bufferBz)
assert.Contains(t, bufferStr, "checking on the current symlink and creating it if needed")
Expand Down Expand Up @@ -484,37 +484,43 @@ func (s *InitTestSuite) TestInitializeCosmovisorValid() {
hwExe := s.CreateHelloWorld(0o755)

s.T().Run("starting with blank slate", func(t *testing.T) {
testDir := s.T().TempDir()
env := &cosmovisorInitEnv{
Home: filepath.Join(testDir, "home"),
env := s.prepareConfig(s.T(), cosmovisorInitEnv{
Name: "blank",
}
})

curLn := filepath.Join(env.Home, cosmovisorDirName, "current")
genBinDir := filepath.Join(env.Home, cosmovisorDirName, "genesis", "bin")
genBinExe := filepath.Join(genBinDir, env.Name)

s.setEnv(s.T(), env)
buffer, logger := s.NewCapturingLogger()
logger.Info(fmt.Sprintf("Calling InitializeCosmovisor: %s", t.Name()))
err := InitializeCosmovisor(logger, []string{hwNonExe})
require.NoError(t, err, "calling InitializeCosmovisor")

genDir := filepath.Join(env.Home, cosmovisorDirName, "genesis", "bin")
genBinExe := filepath.Join(genDir, env.Name)

genBinDirEval, err := filepath.EvalSymlinks(genDir)
require.NoError(t, err)

genBinEvalExe := filepath.Join(genBinDirEval, env.Name)

expInLog := []string{
"checking on the genesis/bin directory",
fmt.Sprintf("creating directory (and any parents): %q", genBinDir),
fmt.Sprintf("creating directory (and any parents): %q", genDir),
"checking on the genesis/bin executable",
fmt.Sprintf("copying executable into place: %q", genBinExe),
fmt.Sprintf("making sure %q is executable", genBinExe),
"checking on the current symlink and creating it if needed",
fmt.Sprintf("the current symlink points to: %q", genBinExe),
fmt.Sprintf("the current symlink points to: %q", genBinEvalExe),
fmt.Sprintf("cosmovisor config.toml created at: %s", filepath.Join(env.Home, cosmovisorDirName, cfgFileWithExt)),
}

s.setEnv(s.T(), env)
buffer, logger := s.NewCapturingLogger()
logger.Info(fmt.Sprintf("Calling InitializeCosmovisor: %s", t.Name()))
err := InitializeCosmovisor(logger, []string{hwNonExe})
require.NoError(t, err, "calling InitializeCosmovisor")

_, err = os.Stat(genBinDir)
assert.NoErrorf(t, err, "statting the genesis bin dir: %q", genBinDir)
_, err = os.Stat(genBinDirEval)
assert.NoErrorf(t, err, "statting the genesis bin dir: %q", genBinDirEval)
_, err = os.Stat(curLn)
assert.NoError(t, err, "statting the current link: %q", curLn)
exeInfo, exeErr := os.Stat(genBinExe)
if assert.NoError(t, exeErr, "statting the executable: %q", genBinExe) {
exeInfo, exeErr := os.Stat(genBinEvalExe)
if assert.NoError(t, exeErr, "statting the executable: %q", genBinEvalExe) {
assert.True(t, exeInfo.Mode().IsRegular(), "executable is regular file")
// Check if the world-executable bit is set.
exePermMask := exeInfo.Mode().Perm() & 0o001
Expand All @@ -534,10 +540,18 @@ func (s *InitTestSuite) TestInitializeCosmovisorValid() {
Name: "nocur",
}
rootDir := filepath.Join(env.Home, cosmovisorDirName)

genBinDir := filepath.Join(rootDir, "genesis", "bin")
genBinDirExe := filepath.Join(genBinDir, env.Name)

require.NoError(t, os.MkdirAll(genBinDir, 0o755), "making genesis bin dir")
require.NoError(t, copyFile(hwExe, genBinDirExe), "copying executable to genesis")

genBinDirEval, err := filepath.EvalSymlinks(genBinDir)
require.NoError(t, err)

genBinEvalExe := filepath.Join(genBinDirEval, env.Name)

upgradesDir := filepath.Join(rootDir, "upgrades")
for i := 1; i <= 5; i++ {
upgradeBinDir := filepath.Join(upgradesDir, fmt.Sprintf("upgrade-%02d", i), "bin")
Expand All @@ -552,14 +566,14 @@ func (s *InitTestSuite) TestInitializeCosmovisorValid() {
"checking on the genesis/bin executable",
fmt.Sprintf("the %q file already exists", genBinDirExe),
fmt.Sprintf("making sure %q is executable", genBinDirExe),
fmt.Sprintf("the current symlink points to: %q", genBinDirExe),
fmt.Sprintf("the current symlink points to: %q", genBinEvalExe),
fmt.Sprintf("cosmovisor config.toml created at: %s", filepath.Join(env.Home, cosmovisorDirName, cfgFileWithExt)),
}

s.setEnv(t, env)
buffer, logger := s.NewCapturingLogger()
logger.Info(fmt.Sprintf("Calling InitializeCosmovisor: %s", t.Name()))
err := InitializeCosmovisor(logger, []string{hwExe})
err = InitializeCosmovisor(logger, []string{hwExe})
require.NoError(t, err, "calling InitializeCosmovisor")
bufferBz := buffer.Collect()
bufferStr := string(bufferBz)
Expand All @@ -579,21 +593,27 @@ func (s *InitTestSuite) TestInitializeCosmovisorValid() {
genBinExe := filepath.Join(genBinDir, env.Name)
require.NoError(t, os.MkdirAll(genBinDir, 0o755), "making genesis bin dir")

s.setEnv(t, env)
buffer, logger := s.NewCapturingLogger()
logger.Info(fmt.Sprintf("Calling InitializeCosmovisor: %s", t.Name()))
err := InitializeCosmovisor(logger, []string{hwExe})
require.NoError(t, err, "calling InitializeCosmovisor")

genBinDirEval, err := filepath.EvalSymlinks(genBinDir)
require.NoError(t, err)

genBinEvalExe := filepath.Join(genBinDirEval, env.Name)

expInLog := []string{
"checking on the genesis/bin directory",
fmt.Sprintf("the %q directory already exists", genBinDir),
"checking on the genesis/bin executable",
fmt.Sprintf("copying executable into place: %q", genBinExe),
fmt.Sprintf("making sure %q is executable", genBinExe),
fmt.Sprintf("the current symlink points to: %q", genBinExe),
fmt.Sprintf("the current symlink points to: %q", genBinEvalExe),
fmt.Sprintf("cosmovisor config.toml created at: %s", filepath.Join(env.Home, cosmovisorDirName, cfgFileWithExt)),
}

s.setEnv(t, env)
buffer, logger := s.NewCapturingLogger()
logger.Info(fmt.Sprintf("Calling InitializeCosmovisor: %s", t.Name()))
err := InitializeCosmovisor(logger, []string{hwExe})
require.NoError(t, err, "calling InitializeCosmovisor")
bufferBz := buffer.Collect()
bufferStr := string(bufferBz)
for _, exp := range expInLog {
Expand Down Expand Up @@ -693,7 +713,9 @@ func (s *InitTestSuite) TestInitializeCosmovisorWithOverrideCfg() {
// read the config file
cfgFile, err := os.Open(tc.cfg.DefaultCfgPath())
require.NoError(t, err)
defer cfgFile.Close()
defer func() {
_ = cfgFile.Close()
}()

err = toml.NewDecoder(cfgFile).Decode(cfg)
require.NoError(t, err)
Expand All @@ -708,3 +730,14 @@ func (s *InitTestSuite) TestInitializeCosmovisorWithOverrideCfg() {
})
}
}

func (s *InitTestSuite) prepareConfig(t *testing.T, config cosmovisorInitEnv) *cosmovisorInitEnv {
t.Helper()

config.Home = s.T().TempDir()

err := os.Chdir(config.Home)
require.NoError(t, err)

return &config
}
2 changes: 1 addition & 1 deletion tools/cosmovisor/cmd/cosmovisor/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ func NewRootCmd() *cobra.Command {
}

rootCmd.AddCommand(
NewIntCmd(),
NewInitCmd(),
runCmd,
configCmd,
NewVersionCmd(),
Expand Down
7 changes: 7 additions & 0 deletions tools/cosmovisor/cmd/cosmovisor/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"fmt"
"os"
"strings"

"github.com/spf13/cobra"
Expand Down Expand Up @@ -39,6 +40,12 @@ func run(cfgPath string, args []string, options ...RunOption) error {
opt(&runCfg)
}

// set current working directory to $DAEMON_NAME/cosmosvisor
// to allow current symlink to be relative
if err = os.Chdir(cfg.Root()); err != nil {
return err
}

logger := cfg.Logger(runCfg.StdOut)
launcher, err := cosmovisor.NewLauncher(logger, cfg)
if err != nil {
Expand Down
Loading

0 comments on commit f932209

Please sign in to comment.