mirror of
https://github.com/luxfi/cli.git
synced 2026-10-30 17:01:20 +00:00
402 lines
11 KiB
Go
402 lines
11 KiB
Go
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
package upgradecmd
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/luxfi/cli/v2/pkg/binutils"
|
|
"github.com/luxfi/cli/v2/pkg/cobrautils"
|
|
"github.com/luxfi/cli/v2/pkg/constants"
|
|
"github.com/luxfi/cli/v2/pkg/models"
|
|
"github.com/luxfi/cli/v2/pkg/plugins"
|
|
"github.com/luxfi/cli/v2/pkg/subnet"
|
|
"github.com/luxfi/cli/v2/pkg/utils"
|
|
"github.com/luxfi/cli/v2/pkg/ux"
|
|
"github.com/luxfi/cli/v2/pkg/vm"
|
|
"github.com/luxfi/netrunner/server"
|
|
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
const (
|
|
futureDeployment = "Update config for future deployments"
|
|
localDeployment = "Existing local deployment"
|
|
testnetDeployment = "Testnet"
|
|
mainnetDeployment = "Mainnet"
|
|
)
|
|
|
|
var (
|
|
pluginDir string
|
|
|
|
useTestnet bool
|
|
useMainnet bool
|
|
useLocal bool
|
|
useConfig bool
|
|
useManual bool
|
|
useLatest bool
|
|
targetVersion string
|
|
binaryPathArg string
|
|
)
|
|
|
|
// lux blockchain upgrade vm
|
|
func newUpgradeVMCmd() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "vm [blockchainName]",
|
|
Short: "Upgrade a blockchain's binary",
|
|
Long: `The blockchain upgrade vm command enables the user to upgrade their Blockchain's VM binary. The command
|
|
can upgrade both local Blockchains and publicly deployed Blockchains on Testnet and Mainnet.
|
|
|
|
The command walks the user through an interactive wizard. The user can skip the wizard by providing
|
|
command line flags.`,
|
|
RunE: upgradeVM,
|
|
Args: cobrautils.ExactArgs(1),
|
|
}
|
|
|
|
cmd.Flags().BoolVar(&useConfig, "config", false, "upgrade config for future subnet deployments")
|
|
cmd.Flags().BoolVar(&useLocal, "local", false, "upgrade existing `local` deployment")
|
|
cmd.Flags().BoolVar(&useTestnet, "testnet", false, "upgrade existing `testnet` deployment (alias for `testnet`)")
|
|
cmd.Flags().BoolVar(&useTestnet, "testnet", false, "upgrade existing `testnet` deployment (alias for `testnet`)")
|
|
cmd.Flags().BoolVar(&useMainnet, "mainnet", false, "upgrade existing `mainnet` deployment")
|
|
|
|
cmd.Flags().BoolVar(&useManual, "print", false, "print instructions for upgrading")
|
|
cmd.Flags().StringVar(&pluginDir, "plugin-dir", "", "plugin directory to automatically upgrade VM")
|
|
|
|
cmd.Flags().BoolVar(&useLatest, "latest", false, "upgrade to latest version")
|
|
cmd.Flags().StringVar(&targetVersion, "version", "", "Upgrade to custom version")
|
|
cmd.Flags().StringVar(&binaryPathArg, "binary", "", "Upgrade to custom binary")
|
|
|
|
return cmd
|
|
}
|
|
|
|
func atMostOneNetworkSelected() bool {
|
|
return !(useConfig && useLocal || useConfig && useTestnet || useConfig && useMainnet || useLocal && useTestnet ||
|
|
useLocal && useMainnet || useTestnet && useMainnet)
|
|
}
|
|
|
|
func atMostOneVersionSelected() bool {
|
|
return !(useLatest && targetVersion != "" || useLatest && binaryPathArg != "" || targetVersion != "" && binaryPathArg != "")
|
|
}
|
|
|
|
func atMostOneAutomationSelected() bool {
|
|
return !(useManual && pluginDir != "")
|
|
}
|
|
|
|
func upgradeVM(_ *cobra.Command, args []string) error {
|
|
// Check flag preconditions
|
|
if !atMostOneNetworkSelected() {
|
|
return errors.New("too many networks selected")
|
|
}
|
|
|
|
if !atMostOneVersionSelected() {
|
|
return errors.New("too many versions selected")
|
|
}
|
|
|
|
if !atMostOneAutomationSelected() {
|
|
return errors.New("--print and --plugin-dir are mutually exclusive")
|
|
}
|
|
|
|
blockchainName := args[0]
|
|
|
|
if !app.BlockchainConfigExists(blockchainName) {
|
|
return errors.New("blockchain does not exist")
|
|
}
|
|
|
|
sc, err := app.LoadSidecar(blockchainName)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to load sidecar: %w", err)
|
|
}
|
|
|
|
upgradeOptions := []string{futureDeployment}
|
|
networkToUpgrade, err := selectNetworkToUpgrade(sc, upgradeOptions)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// if upgrading local, check that the network is off otherwise fail here
|
|
serverRunning, err := isServerRunning()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if serverRunning {
|
|
ux.Logger.PrintToUser("Please stop network before upgrading local VMs")
|
|
return errors.New("network is still running")
|
|
}
|
|
|
|
vmType := sc.VM
|
|
if vmType == models.SubnetEvm {
|
|
return selectUpdateOption(vmType, sc, networkToUpgrade)
|
|
}
|
|
|
|
// Must be a custom update
|
|
return updateToCustomBin(sc, networkToUpgrade, binaryPathArg, true)
|
|
}
|
|
|
|
// select which network to upgrade
|
|
// optionally provide a list of options to preload
|
|
func selectNetworkToUpgrade(sc models.Sidecar, upgradeOptions []string) (string, error) {
|
|
switch {
|
|
case useConfig:
|
|
return futureDeployment, nil
|
|
case useLocal:
|
|
return localDeployment, nil
|
|
case useTestnet:
|
|
return testnetDeployment, nil
|
|
case useMainnet:
|
|
return mainnetDeployment, nil
|
|
}
|
|
|
|
updatePrompt := "What deployment would you like to upgrade"
|
|
if upgradeOptions == nil {
|
|
upgradeOptions = []string{}
|
|
}
|
|
|
|
// get locally deployed subnets from file since network is shut down
|
|
locallyDeployedSubnets, err := subnet.GetLocallyDeployedSubnetsFromFile(app)
|
|
if err != nil {
|
|
return "", fmt.Errorf("unable to read deployed subnets: %w", err)
|
|
}
|
|
|
|
for _, subnet := range locallyDeployedSubnets {
|
|
if subnet == sc.Name {
|
|
upgradeOptions = append(upgradeOptions, localDeployment)
|
|
}
|
|
}
|
|
|
|
// check if subnet deployed on testnet
|
|
if _, ok := sc.Networks[models.Testnet.String()]; ok {
|
|
upgradeOptions = append(upgradeOptions, testnetDeployment)
|
|
}
|
|
|
|
// check if subnet deployed on mainnet
|
|
if _, ok := sc.Networks[models.Mainnet.String()]; ok {
|
|
upgradeOptions = append(upgradeOptions, mainnetDeployment)
|
|
}
|
|
|
|
if len(upgradeOptions) == 0 {
|
|
return "", errors.New("no deployment target available")
|
|
}
|
|
|
|
selectedDeployment, err := app.Prompt.CaptureList(updatePrompt, upgradeOptions)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return selectedDeployment, nil
|
|
}
|
|
|
|
func selectUpdateOption(vmType models.VMType, sc models.Sidecar, networkToUpgrade string) error {
|
|
switch {
|
|
case useLatest:
|
|
return updateToLatestVersion(vmType, sc, networkToUpgrade)
|
|
case targetVersion != "":
|
|
return updateToSpecificVersion(sc, networkToUpgrade)
|
|
case binaryPathArg != "":
|
|
return updateToCustomBin(sc, networkToUpgrade, binaryPathArg, true)
|
|
}
|
|
|
|
latestVersionUpdate := "Update to latest version"
|
|
specificVersionUpdate := "Update to a specific version"
|
|
customBinaryUpdate := "Update to a custom binary"
|
|
|
|
updateOptions := []string{latestVersionUpdate, specificVersionUpdate, customBinaryUpdate}
|
|
|
|
updatePrompt := "How would you like to update your subnet's virtual machine"
|
|
updateDecision, err := app.Prompt.CaptureList(updatePrompt, updateOptions)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
switch updateDecision {
|
|
case latestVersionUpdate:
|
|
return updateToLatestVersion(vmType, sc, networkToUpgrade)
|
|
case specificVersionUpdate:
|
|
return updateToSpecificVersion(sc, networkToUpgrade)
|
|
case customBinaryUpdate:
|
|
return updateToCustomBin(sc, networkToUpgrade, binaryPathArg, true)
|
|
default:
|
|
return errors.New("invalid option")
|
|
}
|
|
}
|
|
|
|
func updateToLatestVersion(vmType models.VMType, sc models.Sidecar, networkToUpgrade string) error {
|
|
// pull in current version
|
|
currentVersion := sc.VMVersion
|
|
|
|
// check latest version
|
|
latestVersion, err := app.Downloader.GetLatestReleaseVersion(
|
|
constants.LuxOrg,
|
|
vmType.RepoName(),
|
|
"",
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// check if current version equals latest
|
|
if currentVersion == "latest" || currentVersion == latestVersion {
|
|
ux.Logger.PrintToUser("VM already up-to-date")
|
|
return nil
|
|
}
|
|
|
|
return updateVMByNetwork(sc, latestVersion, networkToUpgrade)
|
|
}
|
|
|
|
func updateToSpecificVersion(sc models.Sidecar, networkToUpgrade string) error {
|
|
// pull in current version
|
|
currentVersion := sc.VMVersion
|
|
|
|
// Get version to update to
|
|
var err error
|
|
if targetVersion == "" {
|
|
targetVersion, err = app.Prompt.CaptureVersion("Enter version")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// check if current version equals chosen version
|
|
if currentVersion == targetVersion {
|
|
ux.Logger.PrintToUser("VM already up-to-date")
|
|
return nil
|
|
}
|
|
|
|
return updateVMByNetwork(sc, targetVersion, networkToUpgrade)
|
|
}
|
|
|
|
func updateVMByNetwork(sc models.Sidecar, targetVersion string, networkToUpgrade string) error {
|
|
switch networkToUpgrade {
|
|
case futureDeployment:
|
|
return updateFutureVM(sc, targetVersion)
|
|
case localDeployment:
|
|
return updateExistingLocalVM(sc, targetVersion)
|
|
case testnetDeployment:
|
|
return chooseManualOrAutomatic(sc, targetVersion)
|
|
case mainnetDeployment:
|
|
return chooseManualOrAutomatic(sc, targetVersion)
|
|
default:
|
|
return errors.New("unknown deployment")
|
|
}
|
|
}
|
|
|
|
func updateToCustomBin(sc models.Sidecar, networkToUpgrade, binaryPath string, updateVMBinaryProtocolVersion bool) error {
|
|
var err error
|
|
if binaryPath == "" {
|
|
binaryPath, err = app.Prompt.CaptureExistingFilepath("Enter path to custom binary")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if err := app.CopyVMBinary(binaryPath, sc.Name); err != nil {
|
|
return err
|
|
}
|
|
|
|
sc.VM = models.CustomVM
|
|
if updateVMBinaryProtocolVersion {
|
|
sc.RPCVersion, err = vm.GetVMBinaryProtocolVersion(binaryPath)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to get RPC version: %w", err)
|
|
}
|
|
}
|
|
targetVersion := ""
|
|
|
|
return updateVMByNetwork(sc, targetVersion, networkToUpgrade)
|
|
}
|
|
|
|
func updateFutureVM(sc models.Sidecar, targetVersion string) error {
|
|
// to switch to new version, just need to update sidecar
|
|
sc.VMVersion = targetVersion
|
|
if err := app.UpdateSidecar(&sc); err != nil {
|
|
return err
|
|
}
|
|
ux.Logger.PrintToUser("VM updated for future deployments. Update will apply next time subnet is deployed.")
|
|
return nil
|
|
}
|
|
|
|
func updateExistingLocalVM(sc models.Sidecar, targetVersion string) error {
|
|
vmid, err := utils.VMID(sc.Name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var vmBin string
|
|
switch sc.VM {
|
|
// download the binary and prepare to copy it
|
|
case models.SubnetEvm:
|
|
_, vmBin, err = binutils.SetupSubnetEVM(app, targetVersion)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to install subnet-evm: %w", err)
|
|
}
|
|
case models.CustomVM:
|
|
// get the path to the already copied binary
|
|
vmBin = binutils.SetupCustomBin(app, sc.Name)
|
|
|
|
default:
|
|
return errors.New("unknown VM type " + string(sc.VM))
|
|
}
|
|
|
|
rpcVersion, err := vm.GetVMBinaryProtocolVersion(vmBin)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to get RPC version: %w", err)
|
|
}
|
|
|
|
// Update the binary in the plugin directory
|
|
if err := binutils.UpgradeVM(app, vmid.String(), vmBin); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Update the sidecar with new RPC version
|
|
if err = binutils.UpdateLocalSidecarRPC(app, sc, rpcVersion); err != nil {
|
|
return fmt.Errorf("unable to set RPC version: %w", err)
|
|
}
|
|
|
|
ux.Logger.PrintToUser("Upgrade complete. Ready to restart the network.")
|
|
|
|
return nil
|
|
}
|
|
|
|
func chooseManualOrAutomatic(sc models.Sidecar, targetVersion string) error {
|
|
switch {
|
|
case useManual:
|
|
return plugins.ManualUpgrade(app, sc, targetVersion)
|
|
case pluginDir != "":
|
|
return plugins.AutomatedUpgrade(app, sc, targetVersion, pluginDir)
|
|
}
|
|
|
|
const (
|
|
choiceManual = "Manual"
|
|
choiceAutomatic = "Automatic (Make sure your node isn't running)"
|
|
)
|
|
choice, err := app.Prompt.CaptureList(
|
|
"How would you like to update the luxd config?",
|
|
[]string{choiceAutomatic, choiceManual},
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if choice == choiceManual {
|
|
return plugins.ManualUpgrade(app, sc, targetVersion)
|
|
}
|
|
return plugins.AutomatedUpgrade(app, sc, targetVersion, pluginDir)
|
|
}
|
|
|
|
func isServerRunning() (bool, error) {
|
|
cli, err := binutils.NewGRPCClient()
|
|
if err == binutils.ErrGRPCTimeout {
|
|
return false, nil
|
|
} else if err != nil {
|
|
return false, err
|
|
}
|
|
ctx, cancel := utils.GetAPIContext()
|
|
defer cancel()
|
|
|
|
_, err = cli.Status(ctx)
|
|
|
|
if err == nil || !server.IsServerError(err, server.ErrNotBootstrapped) {
|
|
return true, nil
|
|
}
|
|
return false, nil
|
|
}
|