github.com/kyma-project/kyma/components/asset-store-controller-manager@v0.0.0-20191203152857-3792b5df17c5/internal/assethook/metadata_engine.go (about) 1 package assethook 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "fmt" 8 "io" 9 "io/ioutil" 10 "mime/multipart" 11 "net/http" 12 "os" 13 "path/filepath" 14 "time" 15 16 "github.com/kyma-project/kyma/components/asset-store-controller-manager/internal/assethook/api/v1alpha1" 17 pkgPath "github.com/kyma-project/kyma/components/asset-store-controller-manager/internal/path" 18 "github.com/kyma-project/kyma/components/asset-store-controller-manager/pkg/apis/assetstore/v1alpha2" 19 "github.com/pkg/errors" 20 ) 21 22 //go:generate mockery -name=MetadataExtractor -output=automock -outpkg=automock -case=underscore 23 type MetadataExtractor interface { 24 Extract(ctx context.Context, basePath string, files []string, services []v1alpha2.WebhookService) ([]File, error) 25 } 26 27 type File struct { 28 Name string 29 Metadata *json.RawMessage 30 } 31 32 type metadataEngine struct { 33 timeout time.Duration 34 fileReader func(filename string) ([]byte, error) 35 httpClient HttpClient 36 } 37 38 func NewMetadataExtractor(httpClient HttpClient, timeout time.Duration) MetadataExtractor { 39 return &metadataEngine{ 40 httpClient: httpClient, 41 timeout: timeout, 42 fileReader: ioutil.ReadFile, 43 } 44 } 45 46 func (e *metadataEngine) Extract(ctx context.Context, basePath string, files []string, services []v1alpha2.WebhookService) ([]File, error) { 47 results := make(map[string]*json.RawMessage) 48 for _, service := range services { 49 filtered, err := pkgPath.Filter(files, service.Filter) 50 if err != nil { 51 return nil, errors.Wrapf(err, "while filtering files with regex %s", service.Filter) 52 } 53 54 body, contentType, err := e.buildQuery(basePath, filtered) 55 if err != nil { 56 return nil, errors.Wrap(err, "while building multipart query") 57 } 58 59 response := &v1alpha1.MetadataResponse{} 60 err = e.do(ctx, contentType, service, body, response) 61 if err != nil { 62 return nil, errors.Wrap(err, "while sending request to metadata webhook") 63 } 64 65 results = e.replaceMetadata(results, response.Data) 66 } 67 68 return e.toFiles(results), nil 69 } 70 71 func (*metadataEngine) replaceMetadata(current map[string]*json.RawMessage, results []v1alpha1.MetadataResultSuccess) map[string]*json.RawMessage { 72 for _, result := range results { 73 current[result.FilePath] = result.Metadata 74 } 75 76 return current 77 } 78 79 func (*metadataEngine) toFiles(results map[string]*json.RawMessage) []File { 80 files := make([]File, 0, len(results)) 81 for k, v := range results { 82 files = append(files, File{Name: k, Metadata: v}) 83 } 84 85 return files 86 } 87 88 func (e *metadataEngine) buildQuery(basePath string, files []string) (io.Reader, string, error) { 89 b := &bytes.Buffer{} 90 formWriter := multipart.NewWriter(b) 91 defer formWriter.Close() 92 93 for _, file := range files { 94 path := filepath.Join(basePath, file) 95 if err := e.buildQueryField(formWriter, file, path); err != nil { 96 return nil, "", errors.Wrapf(err, "while building query part") 97 } 98 } 99 100 return b, formWriter.FormDataContentType(), nil 101 } 102 103 func (e *metadataEngine) buildQueryField(writer *multipart.Writer, filename, path string) error { 104 file, err := os.Open(path) 105 if err != nil { 106 return errors.Wrapf(err, "while opening file %s", filename) 107 } 108 defer file.Close() 109 110 part, err := writer.CreateFormFile(filename, filepath.Base(file.Name())) 111 if err != nil { 112 return errors.Wrapf(err, "while creating part for file %s", filename) 113 } 114 115 _, err = io.Copy(part, file) 116 if err != nil { 117 return errors.Wrapf(err, "while copying file %s to part", filename) 118 } 119 120 return nil 121 } 122 123 func (e *metadataEngine) do(ctx context.Context, contentType string, webhook v1alpha2.WebhookService, body io.Reader, response interface{}) error { 124 ctx, cancel := context.WithTimeout(ctx, e.timeout) 125 defer cancel() 126 127 req, err := http.NewRequest("POST", e.getWebhookUrl(webhook), body) 128 if err != nil { 129 return err 130 } 131 132 req.Header.Set("Content-Type", contentType) 133 req.WithContext(ctx) 134 135 rsp, err := e.httpClient.Do(req) 136 if err != nil { 137 return errors.Wrapf(err, "while sending request to webhook") 138 } 139 defer rsp.Body.Close() 140 141 if rsp.StatusCode != http.StatusOK { 142 return fmt.Errorf("invalid response from %s, code: %d", req.URL, rsp.StatusCode) 143 } 144 145 responseBytes, err := ioutil.ReadAll(rsp.Body) 146 if err != nil { 147 return errors.Wrapf(err, "while reading response body") 148 } 149 150 err = json.Unmarshal(responseBytes, response) 151 if err != nil { 152 return errors.Wrapf(err, "while parsing response body") 153 } 154 155 return nil 156 } 157 158 func (*metadataEngine) getWebhookUrl(service v1alpha2.WebhookService) string { 159 return fmt.Sprintf("http://%s.%s.svc.cluster.local%s", service.Name, service.Namespace, service.Endpoint) 160 }