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 }