github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/pkg/storage/utils.go (about)

     1  // Copyright 2022 PingCAP, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package storage
    15  
    16  import (
    17  	"context"
    18  	"os"
    19  	"path"
    20  	"path/filepath"
    21  	"strings"
    22  
    23  	gstorage "cloud.google.com/go/storage"
    24  	"github.com/aws/aws-sdk-go/aws/awserr"
    25  	"github.com/aws/aws-sdk-go/service/s3"
    26  	"github.com/pingcap/errors"
    27  	bstorage "github.com/pingcap/tidb/br/pkg/storage"
    28  )
    29  
    30  // AdjustPath adjust rawURL, add uniqueId as path suffix, returns a new path.
    31  // This function supports both local dir or s3 path. It can be used like the following:
    32  // 1. adjust subtask's `LoaderConfig.Dir`, uniqueID like `.test-mysql01`.
    33  // 2. add Lightning checkpoint's fileName to rawURL, uniqueID like `/tidb_lightning_checkpoint.pb`.
    34  func AdjustPath(rawURL string, uniqueID string) (string, error) {
    35  	if rawURL == "" || uniqueID == "" {
    36  		return rawURL, nil
    37  	}
    38  	u, err := bstorage.ParseRawURL(rawURL)
    39  	if err != nil {
    40  		return "", errors.Trace(err)
    41  	}
    42  	// not url format, we don't use url library to avoid being escaped or unescaped
    43  	if u.Scheme == "" {
    44  		// avoid duplicate add uniqueID, and trim suffix '/' like './dump_data/'
    45  		trimPath := strings.TrimRight(rawURL, string(filepath.Separator))
    46  		if !strings.HasSuffix(trimPath, uniqueID) {
    47  			return trimPath + uniqueID, nil
    48  		}
    49  		return rawURL, nil
    50  	}
    51  	// u.Path is an unescaped string and can be used as normal
    52  	trimPath := strings.TrimRight(u.Path, string(filepath.Separator))
    53  	if !strings.HasSuffix(trimPath, uniqueID) {
    54  		u.Path = trimPath + uniqueID
    55  		// u.String will return escaped url and can be used safely in other steps
    56  		return u.String(), err
    57  	}
    58  	return rawURL, nil
    59  }
    60  
    61  // TrimPath trims rawURL suffix which is uniqueID, supports local and s3.
    62  func TrimPath(rawURL string, uniqueID string) (string, error) {
    63  	if rawURL == "" || uniqueID == "" {
    64  		return rawURL, nil
    65  	}
    66  	u, err := bstorage.ParseRawURL(rawURL)
    67  	if err != nil {
    68  		return "", errors.Trace(err)
    69  	}
    70  	// not url format, we don't use url library to avoid being escaped or unescaped
    71  	if u.Scheme == "" {
    72  		return strings.TrimSuffix(rawURL, uniqueID), nil
    73  	}
    74  	// u.Path is an unescaped string and can be used as normal
    75  	u.Path = strings.TrimSuffix(u.Path, uniqueID)
    76  	// u.String will return escaped url and can be used safely in other steps
    77  	return u.String(), err
    78  }
    79  
    80  // IsS3Path judges if rawURL is s3 path.
    81  func IsS3Path(rawURL string) bool {
    82  	if rawURL == "" {
    83  		return false
    84  	}
    85  	u, err := bstorage.ParseRawURL(rawURL)
    86  	if err != nil {
    87  		return false
    88  	}
    89  	return u.Scheme == "s3"
    90  }
    91  
    92  // IsLocalDiskPath judges if path is local disk path.
    93  func IsLocalDiskPath(rawURL string) bool {
    94  	if rawURL == "" {
    95  		return false
    96  	}
    97  	u, err := bstorage.ParseRawURL(rawURL)
    98  	if err != nil {
    99  		return false
   100  	}
   101  	return u.Scheme == "" || u.Scheme == "file"
   102  }
   103  
   104  // CreateStorage creates ExternalStore.
   105  func CreateStorage(ctx context.Context, path string) (bstorage.ExternalStorage, error) {
   106  	backend, err := bstorage.ParseBackend(path, nil)
   107  	if err != nil {
   108  		return nil, err
   109  	}
   110  	return bstorage.New(ctx, backend, &bstorage.ExternalStorageOptions{})
   111  }
   112  
   113  // CollectDirFiles gets files in dir.
   114  func CollectDirFiles(ctx context.Context, dir string, storage bstorage.ExternalStorage) (map[string]struct{}, error) {
   115  	var err error
   116  	if storage == nil {
   117  		storage, err = CreateStorage(ctx, dir)
   118  		if err != nil {
   119  			return nil, err
   120  		}
   121  	}
   122  	files := make(map[string]struct{})
   123  
   124  	err = storage.WalkDir(ctx, &bstorage.WalkOption{}, func(filePath string, size int64) error {
   125  		name := path.Base(filePath)
   126  		files[name] = struct{}{}
   127  		return nil
   128  	})
   129  
   130  	return files, err
   131  }
   132  
   133  // RemoveAll remove files in dir.
   134  func RemoveAll(ctx context.Context, dir string, storage bstorage.ExternalStorage) error {
   135  	var err error
   136  	if storage == nil {
   137  		storage, err = CreateStorage(ctx, dir)
   138  		if err != nil {
   139  			return err
   140  		}
   141  	}
   142  
   143  	err = storage.WalkDir(ctx, &bstorage.WalkOption{}, func(filePath string, size int64) error {
   144  		err2 := storage.DeleteFile(ctx, filePath)
   145  		if errors.Cause(err2) == gstorage.ErrObjectNotExist {
   146  			// ignore not exist error when we delete files
   147  			return nil
   148  		}
   149  		return err2
   150  	})
   151  	if err == nil {
   152  		err = storage.DeleteFile(ctx, "")
   153  		if errors.Cause(err) == gstorage.ErrObjectNotExist {
   154  			// ignore not exist error when we delete files
   155  			return nil
   156  		}
   157  	}
   158  	return err
   159  }
   160  
   161  func ReadFile(ctx context.Context, dir, fileName string, storage bstorage.ExternalStorage) ([]byte, error) {
   162  	var err error
   163  	if storage == nil {
   164  		storage, err = CreateStorage(ctx, dir)
   165  		if err != nil {
   166  			return nil, err
   167  		}
   168  	}
   169  	return storage.ReadFile(ctx, fileName)
   170  }
   171  
   172  func OpenFile(ctx context.Context, dir, fileName string, storage bstorage.ExternalStorage) (bstorage.ExternalFileReader, error) {
   173  	var err error
   174  	if storage == nil {
   175  		storage, err = CreateStorage(ctx, dir)
   176  		if err != nil {
   177  			return nil, err
   178  		}
   179  	}
   180  	return storage.Open(ctx, fileName, nil)
   181  }
   182  
   183  func IsNotExistError(err error) bool {
   184  	if err == nil {
   185  		return false
   186  	}
   187  	err = errors.Cause(err)
   188  	if os.IsNotExist(err) {
   189  		return true
   190  	}
   191  	if aerr, ok := err.(awserr.Error); ok {
   192  		switch aerr.Code() {
   193  		case s3.ErrCodeNoSuchBucket, s3.ErrCodeNoSuchKey, "NotFound":
   194  			return true
   195  		}
   196  	}
   197  	return false
   198  }