diff --git a/pkg/cluster/operation/download.go b/pkg/cluster/operation/download.go index f697d86..bb8da23 100644 --- a/pkg/cluster/operation/download.go +++ b/pkg/cluster/operation/download.go @@ -48,22 +48,22 @@ func Download(prefix, component, nodeOS, arch, version string) error { fileName = fmt.Sprintf("%s-%s.%s-%s.tar.gz", component, ver.GrafanaVersion, nodeOS, arch) componentUrl = strings.Join([]string{"https://dl.grafana.com/oss/release", fileName}, "/") } - - srcPath := spec.ProfilePath(spec.OpenGeminiPackageCacheDir, fileName) + dstPath := spec.ProfilePath(spec.OpenGeminiPackageCacheDir, fileName) if err := os.MkdirAll(spec.ProfilePath(spec.OpenGeminiPackageCacheDir), 0750); err != nil { return errors.WithStack(err) } - //progress.StartDownload([]string{fileName}) - - //lint:ignore SA9003 TODO: verify component sha256 - if utils2.IsExist(srcPath) { - //os.Remove(srcPath) + if utils2.IsExist(dstPath) { + if component == spec.ComponentOpenGemini { + if err := repository.VerifyComponent(version, dstPath); err != nil { + _ = os.Remove(dstPath) // nolint + } + } } // Download from repository if not exists - if utils2.IsNotExist(srcPath) { - err := progress.NewDownloadProgram(prefix, componentUrl, srcPath) + if utils2.IsNotExist(dstPath) { + err := progress.NewDownloadProgram(prefix, componentUrl, dstPath) if err != nil { return errors.WithStack(err) } diff --git a/pkg/cluster/spec/parse_topology.go b/pkg/cluster/spec/parse_topology.go index f5424c4..39af0fa 100644 --- a/pkg/cluster/spec/parse_topology.go +++ b/pkg/cluster/spec/parse_topology.go @@ -138,8 +138,19 @@ func expandRelativePath(user string, topology Topology) { topo.GlobalOptions.DeployDir = Abs(user, topo.GlobalOptions.DeployDir) topo.GlobalOptions.LogDir = Abs(user, topo.GlobalOptions.LogDir) - topo.MonitoredOptions.DeployDir = Abs(user, topo.MonitoredOptions.DeployDir) - topo.MonitoredOptions.LogDir = Abs(user, topo.MonitoredOptions.LogDir) + // set ts-monitor default deploy directory + if strings.TrimSpace(topo.MonitoredOptions.DeployDir) == "" { + topo.MonitoredOptions.DeployDir = topo.GlobalOptions.DeployDir + } else { + topo.MonitoredOptions.DeployDir = Abs(user, topo.MonitoredOptions.DeployDir) + } + + // set ts-monitor default log directory + if strings.TrimSpace(topo.MonitoredOptions.LogDir) == "" { + topo.MonitoredOptions.LogDir = topo.GlobalOptions.LogDir + } else { + topo.MonitoredOptions.LogDir = Abs(user, topo.MonitoredOptions.LogDir) + } for i := range topo.TSMetaServers { server := topo.TSMetaServers[i] diff --git a/pkg/cluster/spec/ts_meta.go b/pkg/cluster/spec/ts_meta.go index d8d53d8..e65dbe8 100644 --- a/pkg/cluster/spec/ts_meta.go +++ b/pkg/cluster/spec/ts_meta.go @@ -41,8 +41,8 @@ type TSMetaSpec struct { ListenHost string `yaml:"listen_host,omitempty"` SSHPort int `yaml:"ssh_port,omitempty" validate:"ssh_port:editable"` - LogDir string `yaml:"log_dir"` - DeployDir string `yaml:"deploy_dir"` + LogDir string `yaml:"log_dir,omitempty"` + DeployDir string `yaml:"deploy_dir,omitempty"` DataDir string `yaml:"data_dir,omitempty"` // port specification diff --git a/pkg/cluster/spec/ts_monitor.go b/pkg/cluster/spec/ts_monitor.go index 6793d71..acde0c4 100644 --- a/pkg/cluster/spec/ts_monitor.go +++ b/pkg/cluster/spec/ts_monitor.go @@ -43,8 +43,8 @@ type TSMonitorSpec struct { ListenHost string `yaml:"listen_host,omitempty"` SSHPort int `yaml:"ssh_port,omitempty" validate:"ssh_port:editable"` - LogDir string `yaml:"log_dir"` - DeployDir string `yaml:"deploy_dir"` + LogDir string `yaml:"log_dir,omitempty"` + DeployDir string `yaml:"deploy_dir,omitempty"` MonitorProcess map[string]struct{} Config map[string]any `yaml:"config,omitempty" validate:"config:ignore"` diff --git a/pkg/cluster/spec/ts_sql.go b/pkg/cluster/spec/ts_sql.go index e86700a..379c9a8 100644 --- a/pkg/cluster/spec/ts_sql.go +++ b/pkg/cluster/spec/ts_sql.go @@ -41,8 +41,8 @@ type TSSqlSpec struct { ListenHost string `yaml:"listen_host,omitempty"` SSHPort int `yaml:"ssh_port,omitempty" validate:"ssh_port:editable"` - LogDir string `yaml:"log_dir"` - DeployDir string `yaml:"deploy_dir"` + LogDir string `yaml:"log_dir,omitempty"` + DeployDir string `yaml:"deploy_dir,omitempty"` // port specification Port int `yaml:"port" default:"8086"` diff --git a/pkg/cluster/spec/ts_store.go b/pkg/cluster/spec/ts_store.go index 6a0ac67..0aabd10 100644 --- a/pkg/cluster/spec/ts_store.go +++ b/pkg/cluster/spec/ts_store.go @@ -41,10 +41,9 @@ type TSStoreSpec struct { ListenHost string `yaml:"listen_host,omitempty"` SSHPort int `yaml:"ssh_port,omitempty" validate:"ssh_port:editable"` - LogDir string `yaml:"log_dir"` - DeployDir string `yaml:"deploy_dir"` - DataDir string `yaml:"data_dir"` - MetaDir string `yaml:"meta_dir"` + LogDir string `yaml:"log_dir,omitempty"` + DeployDir string `yaml:"deploy_dir,omitempty"` + DataDir string `yaml:"data_dir,omitempty"` // port specification IngestPort int `yaml:"ingest_port" default:"8400"` diff --git a/pkg/repository/repo.go b/pkg/repository/repo.go index e8c6784..6bc8b7f 100644 --- a/pkg/repository/repo.go +++ b/pkg/repository/repo.go @@ -15,13 +15,20 @@ package repository import ( + "bufio" "os" "strings" + + "github.com/openGemini/gemix/pkg/cluster/spec" + "github.com/openGemini/gemix/pkg/gui/progress" + "github.com/openGemini/gemix/pkg/utils" + "github.com/pkg/errors" ) const ( GITEE_REPO = "https://gitee.com/opengemini/Releases/releases/download" // https://gitee.com/opengemini/Releases/releases/download/v1.1.1/openGemini-1.1.1-linux-amd64.tar.gz GITHUB_REPO = "https://github.com/openGemini/openGemini/releases/download" // https://github.com/openGemini/openGemini/releases/download/v1.1.1/openGemini-1.1.1-linux-amd64.tar.gz + CHECKSUMS = "checksums.txt" ) func GetRepo() string { @@ -31,3 +38,58 @@ func GetRepo() string { } return GITHUB_REPO } + +func tryToDownloadCheckSumsFile(version string) error { + dstPath := spec.ProfilePath(spec.OpenGeminiPackageCacheDir, CHECKSUMS) + checksumsFile := strings.Join([]string{GetRepo(), "v" + version, CHECKSUMS}, "/") + err := progress.NewDownloadProgram("", checksumsFile, dstPath) + return errors.WithStack(err) +} + +func verifySum256(target string, sha string) error { + file, err := os.Open(target) + if err != nil { + return errors.WithStack(err) + } + defer file.Close() + + err = utils.CheckSHA256(file, sha) + return errors.WithStack(err) +} + +func VerifyComponent(version, target string) error { + checksums := spec.ProfilePath(spec.OpenGeminiPackageCacheDir, CHECKSUMS) + if utils.IsNotExist(checksums) { + if err := tryToDownloadCheckSumsFile(version); err != nil { + return errors.WithStack(err) + } + } + + file, err := os.Open(checksums) + if err != nil { + return err + } + defer file.Close() + + var verify bool + scanner := bufio.NewScanner(file) + for scanner.Scan() { + data := strings.Split(scanner.Text(), " ") // [8b3c2213c044f07108557f899d83ba471ea63455d17113d96c3216e5e2b5fffa, "", openGemini-1.1.1-darwin-amd64.tar.gz] + if len(data) != 3 { + _ = os.Remove(checksums) //nolint + break + } + if strings.Contains(target, data[2]) { + if err = verifySum256(target, data[0]); err != nil { + return errors.WithStack(err) + } + verify = true + break + } + } + + if verify { + return nil + } + return errors.Errorf("failed to verify component") +} diff --git a/pkg/utils/error.go b/pkg/utils/error.go index 2fae82a..9ceec1f 100644 --- a/pkg/utils/error.go +++ b/pkg/utils/error.go @@ -15,6 +15,8 @@ package utils import ( + "fmt" + "github.com/joomcode/errorx" ) @@ -25,3 +27,38 @@ var ( // ErrTraitPreCheck means that the Error is a pre-check error so that no error logs will be outputted directly. ErrTraitPreCheck = errorx.RegisterTrait("pre_check") ) + +var ( + // ErrValidateChecksum is an empty HashValidationErr object, useful for type checking + ErrValidateChecksum = &HashValidationErr{} +) + +// HashValidationErr is the error indicates a failed hash validation +type HashValidationErr struct { + cipher string + expect string // expected hash + actual string // input hash +} + +// Error implements the error interface +func (e *HashValidationErr) Error() string { + return fmt.Sprintf( + "%s checksum mismatch, expect: %v, got: %v", + e.cipher, e.expect, e.actual, + ) +} + +// Unwrap implements the error interface +func (e *HashValidationErr) Unwrap() error { return nil } + +// Is implements the error interface +func (e *HashValidationErr) Is(target error) bool { + t, ok := target.(*HashValidationErr) + if !ok { + return false + } + + return (e.cipher == t.cipher || t.cipher == "") && + (e.expect == t.expect || t.expect == "") && + (e.actual == t.actual || t.actual == "") +} diff --git a/pkg/utils/sha.go b/pkg/utils/sha.go new file mode 100644 index 0000000..9d4b497 --- /dev/null +++ b/pkg/utils/sha.go @@ -0,0 +1,64 @@ +// Copyright 2020 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package utils + +import ( + "crypto/sha256" + "crypto/sha512" + "encoding/hex" + "io" + "strings" + + "github.com/pkg/errors" +) + +// CheckSHA256 returns an error if the hash of reader mismatches `sha` +func CheckSHA256(reader io.Reader, sha string) error { + shaWriter := sha256.New() + if _, err := io.Copy(shaWriter, reader); err != nil { + return errors.WithStack(err) + } + + checksum := hex.EncodeToString(shaWriter.Sum(nil)) + if checksum != strings.TrimSpace(sha) { + return &HashValidationErr{ + cipher: "sha256", + expect: sha, + actual: checksum, + } + } + return nil +} + +// SHA256 returns the hash of reader +func SHA256(reader io.Reader) (string, error) { + shaWriter := sha256.New() + if _, err := io.Copy(shaWriter, reader); err != nil { + return "", errors.WithStack(err) + } + + checksum := hex.EncodeToString(shaWriter.Sum(nil)) + return checksum, nil +} + +// SHA512 returns the hash of reader +func SHA512(reader io.Reader) (string, error) { + shaWriter := sha512.New() + if _, err := io.Copy(shaWriter, reader); err != nil { + return "", errors.WithStack(err) + } + + checksum := hex.EncodeToString(shaWriter.Sum(nil)) + return checksum, nil +}