github.com/oam-dev/kubevela@v1.9.11/pkg/addon/reader_oss.go (about)

     1  /*
     2  Copyright 2021 The KubeVela Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8  	http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package addon
    18  
    19  import (
    20  	"encoding/xml"
    21  	"fmt"
    22  	"path"
    23  	"sort"
    24  	"strings"
    25  	"time"
    26  
    27  	"github.com/go-resty/resty/v2"
    28  	"github.com/pkg/errors"
    29  )
    30  
    31  var _ AsyncReader = &ossReader{}
    32  
    33  // ListBucketResult describe a file list from OSS
    34  type ListBucketResult struct {
    35  	Files []File `xml:"Contents"`
    36  	Count int    `xml:"KeyCount"`
    37  }
    38  
    39  // File is for oss xml parse
    40  type File struct {
    41  	Name         string    `xml:"Key"`
    42  	Size         int       `xml:"Size"`
    43  	LastModified time.Time `xml:"LastModified"`
    44  	Type         string    `xml:"Type"`
    45  	StorageClass string    `xml:"StorageClass"`
    46  }
    47  
    48  type ossReader struct {
    49  	bucketEndPoint string
    50  	path           string
    51  	client         *resty.Client
    52  }
    53  
    54  // OSSItem is Item implement for OSS
    55  type OSSItem struct {
    56  	tp   string
    57  	path string
    58  	name string
    59  }
    60  
    61  // GetType from OSSItem
    62  func (i OSSItem) GetType() string {
    63  	return i.tp
    64  }
    65  
    66  // GetPath from OSSItem
    67  func (i OSSItem) GetPath() string {
    68  	return i.path
    69  }
    70  
    71  // GetName from OSSItem
    72  func (i OSSItem) GetName() string {
    73  	return i.name
    74  }
    75  
    76  // ReadFile read file content from OSS bucket, path is relative to oss bucket and sub-path in reader
    77  func (o *ossReader) ReadFile(relativePath string) (content string, err error) {
    78  	resp, err := o.client.R().Get(fmt.Sprintf(singleOSSFileTmpl, o.bucketEndPoint, path.Join(o.path, relativePath)))
    79  	if err != nil {
    80  		return "", err
    81  	}
    82  	return string(resp.Body()), nil
    83  }
    84  
    85  // ListAddonMeta list object from OSS and convert it to metadata
    86  func (o *ossReader) ListAddonMeta() (map[string]SourceMeta, error) {
    87  	resp, err := o.client.R().Get(fmt.Sprintf(listOSSFileTmpl, o.bucketEndPoint, o.path))
    88  	if err != nil {
    89  		return nil, errors.Wrapf(err, "fail to read path %s", o.path)
    90  	}
    91  
    92  	list := ListBucketResult{}
    93  	err = xml.Unmarshal(resp.Body(), &list)
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  	list = filterEmptyObj(list)
    98  	addons := o.convertOSSFiles2Addons(list.Files)
    99  	return addons, nil
   100  }
   101  
   102  // convertOSSFiles2Addons convert OSS list result to map of addon meta information
   103  func (o *ossReader) convertOSSFiles2Addons(files []File) map[string]SourceMeta {
   104  	addonMetas := make(map[string]SourceMeta)
   105  	pathBuckets := make(map[string][]Item)
   106  	fPaths := make(map[string][]string)
   107  	actualFiles := make([]File, 0)
   108  	// first traversal to confirm addon and initialize addonMetas
   109  	for _, f := range files {
   110  		fPath := trimAndSplitPath(f.Name, o.path)
   111  		if len(fPath) < 2 || f.Size == 0 {
   112  			// this is a file or directory in root, remove it
   113  			continue
   114  		}
   115  		fPaths[f.Name] = fPath
   116  		actualFiles = append(actualFiles, f)
   117  		var addonName = fPath[0]
   118  		if len(fPath) == 2 && fPath[1] == MetadataFileName {
   119  			addonMetas[addonName] = SourceMeta{Name: addonName}
   120  			pathBuckets[addonName] = make([]Item, 0)
   121  		}
   122  	}
   123  	// second sort all addon file item by name
   124  	for _, f := range actualFiles {
   125  		fPath := fPaths[f.Name]
   126  		addonName := fPath[0]
   127  		pathList, ok := pathBuckets[addonName]
   128  		// this path doesn't belong to an addon
   129  		if !ok {
   130  			continue
   131  		}
   132  		pathList = append(pathList, &OSSItem{
   133  			path: path.Join(fPath...),
   134  			tp:   FileType,
   135  			name: fPath[len(fPath)-1],
   136  		})
   137  		pathBuckets[addonName] = pathList
   138  	}
   139  	var addonList = make(map[string]SourceMeta)
   140  	for k, v := range addonMetas {
   141  		items := pathBuckets[k]
   142  		sort.Slice(items, func(i, j int) bool {
   143  			return items[i].GetPath() < items[j].GetPath()
   144  		})
   145  		v.Items = pathBuckets[k]
   146  		addonList[k] = v
   147  	}
   148  	return addonList
   149  }
   150  
   151  func trimAndSplitPath(absPath string, path2Bucket string) []string {
   152  	const slash = "/"
   153  	var p = absPath
   154  	if path2Bucket != "" {
   155  		p = strings.TrimPrefix(p, path2Bucket)
   156  		p = strings.TrimPrefix(p, "/")
   157  	}
   158  	return strings.Split(p, slash)
   159  }
   160  
   161  func (o *ossReader) RelativePath(item Item) string {
   162  	return item.GetPath()
   163  }
   164  
   165  // OSSAddonSource is UIData source from alibaba cloud OSS style source
   166  type OSSAddonSource struct {
   167  	Endpoint string `json:"end_point" validate:"required"`
   168  	Bucket   string `json:"bucket"`
   169  	Path     string `json:"path"`
   170  }
   171  
   172  func filterEmptyObj(list ListBucketResult) ListBucketResult {
   173  	var actualFiles []File
   174  	for _, f := range list.Files {
   175  		if f.Size > 0 {
   176  			actualFiles = append(actualFiles, f)
   177  		}
   178  	}
   179  	return ListBucketResult{
   180  		Files: actualFiles,
   181  		Count: len(actualFiles),
   182  	}
   183  }