249 lines
7.3 KiB
Go
249 lines
7.3 KiB
Go
/*
|
|
* Copyright 2020 The Dragonfly Authors
|
|
*
|
|
* 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,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
// Package config holds all DaemonConfig of dfget.
|
|
package config
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
"golang.org/x/time/rate"
|
|
|
|
"d7y.io/dragonfly/v2/cmd/dependency/base"
|
|
"d7y.io/dragonfly/v2/internal/dferrors"
|
|
logger "d7y.io/dragonfly/v2/internal/dflog"
|
|
"d7y.io/dragonfly/v2/pkg/basic"
|
|
"d7y.io/dragonfly/v2/pkg/unit"
|
|
"d7y.io/dragonfly/v2/pkg/util/net/urlutils"
|
|
"d7y.io/dragonfly/v2/pkg/util/stringutils"
|
|
)
|
|
|
|
type DfgetConfig = ClientOption
|
|
|
|
// ClientOption holds all the runtime config information.
|
|
type ClientOption struct {
|
|
base.Options `yaml:",inline" mapstructure:",squash"`
|
|
// URL download URL.
|
|
URL string `yaml:"url,omitempty" mapstructure:"url,omitempty"`
|
|
|
|
// Output full output path.
|
|
Output string `yaml:"output,omitempty" mapstructure:"output,omitempty"`
|
|
|
|
// Timeout download timeout(second).
|
|
Timeout time.Duration `yaml:"timeout,omitempty" mapstructure:"timeout,omitempty"`
|
|
|
|
BenchmarkRate unit.Bytes `yaml:"benchmarkRate,omitempty" mapstructure:"benchmarkRate,omitempty"`
|
|
|
|
// Md5 expected file md5.
|
|
// Deprecated: Md5 is deprecated, use DigestMethod with DigestValue instead
|
|
Md5 string `yaml:"md5,omitempty" mapstructure:"md5,omitempty"`
|
|
Digest string `yaml:"digest,omitempty" mapstructure:"digest,omitempty"`
|
|
// DigestMethod indicates digest method, like md5, sha256
|
|
DigestMethod string `yaml:"digestMethod,omitempty" mapstructure:"digestMethod,omitempty"`
|
|
|
|
// DigestValue indicates digest value
|
|
DigestValue string `yaml:"digestValue,omitempty" mapstructure:"digestValue,omitempty"`
|
|
|
|
// Tag identify download task, it is available merely when md5 param not exist.
|
|
Tag string `yaml:"tag,omitempty" mapstructure:"tag,omitempty"`
|
|
|
|
// CallSystem system name that executes dfget.
|
|
CallSystem string `yaml:"callSystem,omitempty" mapstructure:"callSystem,omitempty"`
|
|
|
|
// Pattern download pattern, must be 'p2p' or 'cdn' or 'source',
|
|
// default:`p2p`.
|
|
Pattern string `yaml:"pattern,omitempty" mapstructure:"pattern,omitempty"`
|
|
|
|
// CA certificate to verify when supernode interact with the source.
|
|
Cacerts []string `yaml:"cacert,omitempty" mapstructure:"cacert,omitempty"`
|
|
|
|
// Filter filter some query params of url, use char '&' to separate different params.
|
|
// eg: -f 'key&sign' will filter 'key' and 'sign' query param.
|
|
// in this way, different urls correspond one same download task that can use p2p mode.
|
|
Filter string `yaml:"filter,omitempty" mapstructure:"filter,omitempty"`
|
|
|
|
// Header of http request.
|
|
// eg: --header='Accept: *' --header='Host: abc'.
|
|
Header []string `yaml:"header,omitempty" mapstructure:"header,omitempty"`
|
|
|
|
// DisableBackSource indicates whether to not back source to download when p2p fails.
|
|
DisableBackSource bool `yaml:"disableBackSource,omitempty" mapstructure:"disableBackSource,omitempty"`
|
|
|
|
// Insecure indicates whether skip secure verify when supernode interact with the source.
|
|
Insecure bool `yaml:"insecure,omitempty" mapstructure:"insecure,omitempty"`
|
|
|
|
// ShowProgress shows progress bar, it's conflict with `--console`.
|
|
ShowProgress bool `yaml:"show-progress,omitempty" mapstructure:"show-progress,omitempty"`
|
|
|
|
RateLimit rate.Limit `yaml:"rateLimit,omitempty" mapstructure:"rateLimit,omitempty"`
|
|
|
|
// Config file paths,
|
|
// default:["/etc/dragonfly/dfget.yaml","/etc/dragonfly.conf"].
|
|
//
|
|
// NOTE: It is recommended to use `/etc/dragonfly/dfget.yaml` as default,
|
|
// and the `/etc/dragonfly.conf` is just to ensure compatibility with previous versions.
|
|
//ConfigFiles []string `json:"-"`
|
|
|
|
// MoreDaemonOptions indicates more options passed to daemon by command line.
|
|
MoreDaemonOptions string `yaml:"moreDaemonOptions,omitempty" mapstructure:"moreDaemonOptions,omitempty"`
|
|
}
|
|
|
|
func NewDfgetConfig() *ClientOption {
|
|
return &dfgetConfig
|
|
}
|
|
|
|
func (cfg *ClientOption) Validate() error {
|
|
if cfg == nil {
|
|
return errors.Wrap(dferrors.ErrInvalidArgument, "runtime config")
|
|
}
|
|
|
|
if !urlutils.IsValidURL(cfg.URL) {
|
|
return errors.Wrapf(dferrors.ErrInvalidArgument, "url: %v", cfg.URL)
|
|
}
|
|
|
|
if err := cfg.checkOutput(); err != nil {
|
|
return errors.Wrapf(dferrors.ErrInvalidArgument, "output: %v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (cfg *ClientOption) Convert(args []string) error {
|
|
var err error
|
|
|
|
if cfg.Output, err = filepath.Abs(cfg.Output); err != nil {
|
|
return err
|
|
}
|
|
|
|
if cfg.URL == "" && len(args) > 0 {
|
|
cfg.URL = args[0]
|
|
}
|
|
|
|
if cfg.Digest != "" {
|
|
cfg.Tag = ""
|
|
}
|
|
|
|
if cfg.Console {
|
|
cfg.ShowProgress = false
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (cfg *ClientOption) String() string {
|
|
js, _ := json.Marshal(cfg)
|
|
return string(js)
|
|
}
|
|
|
|
// This function must be called after checkURL
|
|
func (cfg *ClientOption) checkOutput() error {
|
|
if stringutils.IsBlank(cfg.Output) {
|
|
url := strings.TrimRight(cfg.URL, "/")
|
|
idx := strings.LastIndexByte(url, '/')
|
|
if idx < 0 {
|
|
return fmt.Errorf("get output from url[%s] error", cfg.URL)
|
|
}
|
|
cfg.Output = url[idx+1:]
|
|
}
|
|
|
|
if !filepath.IsAbs(cfg.Output) {
|
|
absPath, err := filepath.Abs(cfg.Output)
|
|
if err != nil {
|
|
return fmt.Errorf("get absolute path[%s] error: %v", cfg.Output, err)
|
|
}
|
|
cfg.Output = absPath
|
|
}
|
|
|
|
dir, _ := path.Split(cfg.Output)
|
|
if err := MkdirAll(dir, 0777, basic.UserID, basic.UserGroup); err != nil {
|
|
return err
|
|
}
|
|
|
|
if f, err := os.Stat(cfg.Output); err == nil && f.IsDir() {
|
|
return fmt.Errorf("path[%s] is directory but requires file path", cfg.Output)
|
|
}
|
|
|
|
// check permission
|
|
for dir := cfg.Output; !stringutils.IsBlank(dir); dir = filepath.Dir(dir) {
|
|
if err := syscall.Access(dir, syscall.O_RDWR); err == nil {
|
|
break
|
|
} else if os.IsPermission(err) || dir == "/" {
|
|
return fmt.Errorf("user[%s] path[%s] %v", basic.Username, cfg.Output, err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// MkdirAll make directories recursive, and changes uid, gid to latest directory.
|
|
// For example: the path /data/x exists, uid=1, gid=1
|
|
// when call MkdirAll("/data/x/y/z", 0755, 2, 2)
|
|
// MkdirAll creates /data/x/y and change owner to 2:2, creates /data/x/y/z and change owner to 2:2
|
|
func MkdirAll(dir string, perm os.FileMode, uid, gid int) error {
|
|
var (
|
|
// directories to create
|
|
dirs []string
|
|
err error
|
|
)
|
|
subDir := dir
|
|
// find not exist directories from bottom to top
|
|
for {
|
|
if subDir == "" {
|
|
break
|
|
}
|
|
_, err = os.Stat(subDir)
|
|
// directory exists
|
|
if err == nil {
|
|
break
|
|
}
|
|
if os.IsNotExist(err) {
|
|
dirs = append(dirs, subDir)
|
|
} else {
|
|
logger.Errorf("stat error: %s", err)
|
|
return err
|
|
}
|
|
subDir = path.Dir(subDir)
|
|
}
|
|
|
|
// no directory to create
|
|
if len(dirs) == 0 {
|
|
return nil
|
|
}
|
|
|
|
err = os.MkdirAll(dir, perm)
|
|
if err != nil {
|
|
logger.Errorf("mkdir error: %s", err)
|
|
return err
|
|
}
|
|
|
|
// update owner from top to bottom
|
|
for _, d := range dirs {
|
|
err = os.Chown(d, uid, gid)
|
|
if err != nil {
|
|
logger.Errorf("chown error: %s", err)
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|