Skip to content
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

feat(tools/cosmovisor): create current symlink as relative #21891

Merged
merged 2 commits into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions tools/cosmovisor/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ Ref: https://keepachangelog.com/en/1.0.0/

### Improvements

* [#21891](https://github.com/cosmos/cosmos-sdk/pull/21891) create `current` symlink as relative
* [#21462](https://github.com/cosmos/cosmos-sdk/pull/21462) Pass `stdin` to binary.

### Features
Expand Down
39 changes: 20 additions & 19 deletions tools/cosmovisor/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,14 +113,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 @@ -134,27 +139,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 @@ -385,24 +387,23 @@ func (cfg *Config) SetCurrentUpgrade(u upgradetypes.Plan) (rerr error) {
}

// set a symbolic link
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
58 changes: 30 additions & 28 deletions tools/cosmovisor/args_test.go

Large diffs are not rendered by default.

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(),
troian marked this conversation as resolved.
Show resolved Hide resolved
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
Loading