dragonfly/client/config/dfcache.go

234 lines
6.1 KiB
Go

/*
* Copyright 2022 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
import (
"encoding/json"
"errors"
"fmt"
"os"
"path"
"path/filepath"
"syscall"
"time"
"golang.org/x/time/rate"
"d7y.io/dragonfly/v2/cmd/dependency/base"
"d7y.io/dragonfly/v2/internal/dferrors"
"d7y.io/dragonfly/v2/pkg/os/user"
"d7y.io/dragonfly/v2/pkg/strings"
)
type DfcacheConfig = CacheOption
// CacheOption holds all the runtime config information.
type CacheOption struct {
base.Options `yaml:",inline" mapstructure:",squash"`
// Cid content/cache ID
Cid string `yaml:"cid,omitempty" mapstructure:"cid,omitempty"`
// Tag identify task
Tag string `yaml:"tag,omitempty" mapstructure:"tag,omitempty"`
// Timeout operation timeout(second).
Timeout time.Duration `yaml:"timeout,omitempty" mapstructure:"timeout,omitempty"`
// LogDir is log directory of dfcache.
LogDir string `yaml:"logDir,omitempty" mapstructure:"logDir,omitempty"`
// WorkHome is working directory of dfcache.
WorkHome string `yaml:"workHome,omitempty" mapstructure:"workHome,omitempty"`
// Output full output path for export task
Output string `yaml:"output,omitempty" mapstructure:"output,omitempty"`
// Path full input path for import task
// TODO: change to Input
Path string `yaml:"path,omitempty" mapstructure:"path,omitempty"`
// RateLimit limits export task
RateLimit rate.Limit `yaml:"rateLimit,omitempty" mapstructure:"rateLimit,omitempty"`
// LocalOnly indicates check local cache only
LocalOnly bool `yaml:"localOnly,omitempty" mapstructure:"localOnly,omitempty"`
}
func NewDfcacheConfig() *CacheOption {
return &CacheOption{}
}
func validateCacheStat(cfg *CacheOption) error {
return nil
}
func validateCacheImport(cfg *CacheOption) error {
if err := cfg.checkInput(); err != nil {
return fmt.Errorf("input path %s: %w", err.Error(), dferrors.ErrInvalidArgument)
}
return nil
}
func ValidateCacheExport(cfg *CacheOption) error {
if err := cfg.checkOutput(); err != nil {
return fmt.Errorf("output %s: %w", err.Error(), dferrors.ErrInvalidArgument)
}
return nil
}
func ValidateCacheDelete(cfg *CacheOption) error {
return nil
}
func (cfg *CacheOption) Validate(cmd string) error {
// Some common validations
if cfg == nil {
return fmt.Errorf("runtime config: %w", dferrors.ErrInvalidArgument)
}
if cfg.Cid == "" {
return fmt.Errorf("missing Cid: %w", dferrors.ErrInvalidArgument)
}
if strings.IsBlank(cfg.Cid) {
return fmt.Errorf("Cid are all blanks: %w", dferrors.ErrInvalidArgument)
}
// cmd specific validations
switch cmd {
case CmdStat:
return validateCacheStat(cfg)
case CmdImport:
return validateCacheImport(cfg)
case CmdExport:
return ValidateCacheExport(cfg)
case CmdDelete:
return ValidateCacheDelete(cfg)
default:
return fmt.Errorf("unknown cache subcommand %s: %w", cmd, dferrors.ErrInvalidArgument)
}
}
func ConvertCacheStat(cfg *CacheOption, args []string) error {
return nil
}
func convertCacheImport(cfg *CacheOption, args []string) error {
var err error
if cfg.Path == "" && len(args) > 0 {
cfg.Path = args[0]
}
if cfg.Path == "" {
return fmt.Errorf("missing input file: %w", dferrors.ErrInvalidArgument)
}
if cfg.Path, err = filepath.Abs(cfg.Path); err != nil {
return fmt.Errorf("get absulate path for %s: %w", cfg.Path, err)
}
return nil
}
func ConvertCacheExport(cfg *CacheOption, args []string) error {
var err error
if cfg.Output == "" && len(args) > 0 {
cfg.Output = args[0]
}
if cfg.Output == "" {
return fmt.Errorf("missing output file: %w", dferrors.ErrInvalidArgument)
}
if cfg.Output, err = filepath.Abs(cfg.Output); err != nil {
return fmt.Errorf("get absulate path for %s: %w", cfg.Output, err)
}
return nil
}
func ConvertCacheDelete(cfg *CacheOption, args []string) error {
return nil
}
func (cfg *CacheOption) Convert(cmd string, args []string) error {
if cfg == nil {
return fmt.Errorf("runtime config: %w", dferrors.ErrInvalidArgument)
}
switch cmd {
case CmdStat:
return ConvertCacheStat(cfg, args)
case CmdImport:
return convertCacheImport(cfg, args)
case CmdExport:
return ConvertCacheExport(cfg, args)
case CmdDelete:
return ConvertCacheDelete(cfg, args)
default:
return fmt.Errorf("unknown cache subcommand %s: %w", cmd, dferrors.ErrInvalidArgument)
}
}
func (cfg *CacheOption) String() string {
data, _ := json.Marshal(cfg)
return string(data)
}
func (cfg *CacheOption) checkInput() error {
stat, err := os.Stat(cfg.Path)
if err != nil {
return fmt.Errorf("stat input path %q: %w", cfg.Path, err)
}
if stat.IsDir() {
return fmt.Errorf("path[%q] is directory but requires file path", cfg.Path)
}
if err := syscall.Access(cfg.Path, syscall.O_RDONLY); err != nil {
return fmt.Errorf("access %q: %w", cfg.Path, err)
}
return nil
}
func (cfg *CacheOption) checkOutput() error {
if cfg.Output == "" {
return errors.New("no output file path specified")
}
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
}
outputDir, _ := path.Split(cfg.Output)
if err := MkdirAll(outputDir, 0777, os.Getuid(), os.Getgid()); err != nil {
return err
}
f, err := os.Stat(cfg.Output)
if err == nil && f.IsDir() {
return fmt.Errorf("path[%s] is directory but requires file path", cfg.Output)
}
// check permission
for dir := cfg.Output; !strings.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", user.Username(), cfg.Output, err)
}
}
return nil
}