diff --git a/cmd/frostfs-adm/internal/modules/morph/download.go b/cmd/frostfs-adm/internal/modules/morph/download.go new file mode 100644 index 000000000..5bd2d98bd --- /dev/null +++ b/cmd/frostfs-adm/internal/modules/morph/download.go @@ -0,0 +1,81 @@ +package morph + +import ( + "context" + "errors" + "fmt" + "io" + "net" + "net/http" + "strings" + "time" + + "code.gitea.io/sdk/gitea" + "github.com/spf13/cobra" +) + +func downloadContracts(cmd *cobra.Command, url string) (io.ReadCloser, error) { + cmd.Printf("Downloading contracts archive from '%s'\n", url) + + // HTTP client with connect timeout + client := http.Client{ + Transport: &http.Transport{ + DialContext: (&net.Dialer{ + Timeout: 10 * time.Second, + }).DialContext, + }, + } + + ctx, cancel := context.WithTimeout(cmd.Context(), 60*time.Second) + defer cancel() + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + return nil, fmt.Errorf("can't create request: %w", err) + } + + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("can't fetch contracts archive: %w", err) + } + return resp.Body, nil +} + +func downloadContractsFromRepository(cmd *cobra.Command) (io.ReadCloser, error) { + client, err := gitea.NewClient("https://git.frostfs.info") + if err != nil { + return nil, fmt.Errorf("can't initialize repository client: %w", err) + } + + releases, _, err := client.ListReleases("TrueCloudLab", "frostfs-contract", gitea.ListReleasesOptions{}) + if err != nil { + return nil, fmt.Errorf("can't fetch release information: %w", err) + } + + var latestRelease *gitea.Release + for _, r := range releases { + if !r.IsDraft && !r.IsPrerelease { + latestRelease = r + break + } + } + + if latestRelease == nil { + return nil, fmt.Errorf("attempt to fetch contracts archive from the offitial repository failed: no releases found") + } + + cmd.Printf("Found release %s (%s)\n", latestRelease.TagName, latestRelease.Title) + + var url string + for _, a := range latestRelease.Attachments { + if strings.HasPrefix(a.Name, "frostfs-contract") { + url = a.DownloadURL + break + } + } + if url == "" { + return nil, errors.New("can't find contracts archive in the latest release") + } + + return downloadContracts(cmd, url) +} diff --git a/cmd/frostfs-adm/internal/modules/morph/initialize.go b/cmd/frostfs-adm/internal/modules/morph/initialize.go index 78356e6b4..04e8620cc 100644 --- a/cmd/frostfs-adm/internal/modules/morph/initialize.go +++ b/cmd/frostfs-adm/internal/modules/morph/initialize.go @@ -50,6 +50,7 @@ type initializeContext struct { Contracts map[string]*contractState Command *cobra.Command ContractPath string + ContractURL string } var ErrTooManyAlphabetNodes = fmt.Errorf("too many alphabet nodes (maximum allowed is %d)", maxAlphabetNodes) @@ -152,6 +153,11 @@ func newInitializeContext(cmd *cobra.Command, v *viper.Viper) (*initializeContex return nil, err } + var ctrURL string + if needContracts { + ctrURL, _ = cmd.Flags().GetString(contractsURLFlag) + } + if err := checkNotaryEnabled(c); err != nil { return nil, err } @@ -176,6 +182,7 @@ func newInitializeContext(cmd *cobra.Command, v *viper.Viper) (*initializeContex Command: cmd, Contracts: make(map[string]*contractState), ContractPath: ctrPath, + ContractURL: ctrURL, } if needContracts { diff --git a/cmd/frostfs-adm/internal/modules/morph/initialize_deploy.go b/cmd/frostfs-adm/internal/modules/morph/initialize_deploy.go index 6cf75c5f9..f715f0e03 100644 --- a/cmd/frostfs-adm/internal/modules/morph/initialize_deploy.go +++ b/cmd/frostfs-adm/internal/modules/morph/initialize_deploy.go @@ -399,10 +399,14 @@ func (c *initializeContext) readContracts(names []string) error { } } else { var r io.ReadCloser - if c.ContractPath == "" { - return errors.New("contracts flag is missing") + if c.ContractPath != "" { + r, err = os.Open(c.ContractPath) + } else if c.ContractURL != "" { + r, err = downloadContracts(c.Command, c.ContractURL) + } else { + r, err = downloadContractsFromRepository(c.Command) } - r, err = os.Open(c.ContractPath) + if err != nil { return fmt.Errorf("can't open contracts archive: %w", err) } diff --git a/cmd/frostfs-adm/internal/modules/morph/root.go b/cmd/frostfs-adm/internal/modules/morph/root.go index 600fe21c8..6cc2d5a96 100644 --- a/cmd/frostfs-adm/internal/modules/morph/root.go +++ b/cmd/frostfs-adm/internal/modules/morph/root.go @@ -17,6 +17,9 @@ const ( storageGasCLIFlag = "initial-gas" storageGasConfigFlag = "storage.initial_gas" contractsInitFlag = "contracts" + contractsInitFlagDesc = "Path to archive with compiled FrostFS contracts (the default is to fetch the latest release from the official repository)" + contractsURLFlag = "contracts-url" + contractsURLFlagDesc = "URL to archive with compiled FrostFS contracts" maxObjectSizeInitFlag = "network.max_object_size" maxObjectSizeCLIFlag = "max-object-size" epochDurationInitFlag = "network.epoch_duration" @@ -370,8 +373,9 @@ func initUpdateContractsCmd() { RootCmd.AddCommand(updateContractsCmd) updateContractsCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc) updateContractsCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc) - updateContractsCmd.Flags().String(contractsInitFlag, "", "Path to archive with compiled FrostFS contracts") - _ = updateContractsCmd.MarkFlagRequired(contractsInitFlag) + updateContractsCmd.Flags().String(contractsInitFlag, "", contractsInitFlagDesc) + updateContractsCmd.Flags().String(contractsURLFlag, "", contractsURLFlagDesc) + updateContractsCmd.MarkFlagsMutuallyExclusive(contractsInitFlag, contractsURLFlag) } func initDumpBalancesCmd() { @@ -441,8 +445,8 @@ func initInitCmd() { RootCmd.AddCommand(initCmd) initCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc) initCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc) - initCmd.Flags().String(contractsInitFlag, "", "Path to archive with compiled FrostFS contracts") - _ = initCmd.MarkFlagRequired(contractsInitFlag) + initCmd.Flags().String(contractsInitFlag, "", contractsInitFlagDesc) + initCmd.Flags().String(contractsURLFlag, "", contractsURLFlagDesc) initCmd.Flags().Uint(epochDurationCLIFlag, 240, "Amount of side chain blocks in one FrostFS epoch") initCmd.Flags().Uint(maxObjectSizeCLIFlag, 67108864, "Max single object size in bytes") initCmd.Flags().Bool(homomorphicHashDisabledCLIFlag, false, "Disable object homomorphic hashing") @@ -451,6 +455,7 @@ func initInitCmd() { initCmd.Flags().Uint64(containerAliasFeeCLIFlag, 500, "Container alias fee") initCmd.Flags().String(protoConfigPath, "", "Path to the consensus node configuration") initCmd.Flags().String(localDumpFlag, "", "Path to the blocks dump file") + initCmd.MarkFlagsMutuallyExclusive(contractsInitFlag, contractsURLFlag) } func initGenerateAlphabetCmd() { diff --git a/go.mod b/go.mod index 07097c547..5d6edc222 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module git.frostfs.info/TrueCloudLab/frostfs-node go 1.20 require ( + code.gitea.io/sdk/gitea v0.17.1 git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.16.1-0.20240112150928-72885aae835c git.frostfs.info/TrueCloudLab/frostfs-contract v0.18.1-0.20240115082915-f2a82aa635aa git.frostfs.info/TrueCloudLab/frostfs-observability v0.0.0-20231101111734-b3ad3335ff65 @@ -61,8 +62,10 @@ require ( github.com/consensys/gnark-crypto v0.12.2-0.20231222162921-eb75782795d2 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/davidmz/go-pageant v1.0.2 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/go-fed/httpsig v1.1.0 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/protobuf v1.5.3 // indirect @@ -71,6 +74,7 @@ require ( github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.1 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect + github.com/hashicorp/go-version v1.6.0 // indirect github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/holiman/uint256 v1.2.4 // indirect diff --git a/go.sum b/go.sum index 5243ac435..b9052efde 100644 Binary files a/go.sum and b/go.sum differ