1package azuremonitor 2 3import ( 4 "fmt" 5 "io/ioutil" 6 "net/http" 7 "net/url" 8 "strings" 9 10 "github.com/grafana/grafana-plugin-sdk-go/backend/resource/httpadapter" 11) 12 13func getTarget(original string) (target string, err error) { 14 splittedPath := strings.Split(original, "/") 15 if len(splittedPath) < 3 { 16 err = fmt.Errorf("the request should contain the service on its path") 17 return 18 } 19 target = fmt.Sprintf("/%s", strings.Join(splittedPath[2:], "/")) 20 return 21} 22 23type httpServiceProxy struct{} 24 25func (s *httpServiceProxy) Do(rw http.ResponseWriter, req *http.Request, cli *http.Client) http.ResponseWriter { 26 res, err := cli.Do(req) 27 if err != nil { 28 rw.WriteHeader(http.StatusInternalServerError) 29 _, err = rw.Write([]byte(fmt.Sprintf("unexpected error %v", err))) 30 if err != nil { 31 azlog.Error("Unable to write HTTP response", "error", err) 32 } 33 return nil 34 } 35 defer func() { 36 if err := res.Body.Close(); err != nil { 37 azlog.Warn("Failed to close response body", "err", err) 38 } 39 }() 40 41 body, err := ioutil.ReadAll(res.Body) 42 if err != nil { 43 rw.WriteHeader(http.StatusInternalServerError) 44 _, err = rw.Write([]byte(fmt.Sprintf("unexpected error %v", err))) 45 if err != nil { 46 azlog.Error("Unable to write HTTP response", "error", err) 47 } 48 return nil 49 } 50 rw.WriteHeader(res.StatusCode) 51 _, err = rw.Write(body) 52 if err != nil { 53 azlog.Error("Unable to write HTTP response", "error", err) 54 } 55 56 for k, v := range res.Header { 57 rw.Header().Set(k, v[0]) 58 for _, v := range v[1:] { 59 rw.Header().Add(k, v) 60 } 61 } 62 // Returning the response write for testing purposes 63 return rw 64} 65 66func (s *Service) getDataSourceFromHTTPReq(req *http.Request) (datasourceInfo, error) { 67 ctx := req.Context() 68 pluginContext := httpadapter.PluginConfigFromContext(ctx) 69 i, err := s.im.Get(pluginContext) 70 if err != nil { 71 return datasourceInfo{}, nil 72 } 73 ds, ok := i.(datasourceInfo) 74 if !ok { 75 return datasourceInfo{}, fmt.Errorf("unable to convert datasource from service instance") 76 } 77 return ds, nil 78} 79 80func writeResponse(rw http.ResponseWriter, code int, msg string) { 81 rw.WriteHeader(http.StatusBadRequest) 82 _, err := rw.Write([]byte(msg)) 83 if err != nil { 84 azlog.Error("Unable to write HTTP response", "error", err) 85 } 86} 87 88func (s *Service) resourceHandler(subDataSource string) func(rw http.ResponseWriter, req *http.Request) { 89 return func(rw http.ResponseWriter, req *http.Request) { 90 azlog.Debug("Received resource call", "url", req.URL.String(), "method", req.Method) 91 92 newPath, err := getTarget(req.URL.Path) 93 if err != nil { 94 writeResponse(rw, http.StatusBadRequest, err.Error()) 95 return 96 } 97 98 dsInfo, err := s.getDataSourceFromHTTPReq(req) 99 if err != nil { 100 writeResponse(rw, http.StatusInternalServerError, fmt.Sprintf("unexpected error %v", err)) 101 return 102 } 103 104 service := dsInfo.Services[subDataSource] 105 serviceURL, err := url.Parse(service.URL) 106 if err != nil { 107 writeResponse(rw, http.StatusInternalServerError, fmt.Sprintf("unexpected error %v", err)) 108 return 109 } 110 req.URL.Path = newPath 111 req.URL.Host = serviceURL.Host 112 req.URL.Scheme = serviceURL.Scheme 113 114 s.executors[subDataSource].resourceRequest(rw, req, service.HTTPClient) 115 } 116} 117 118// registerRoutes provides route definitions shared with the frontend. 119// Check: /public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/common.ts <routeNames> 120func (s *Service) registerRoutes(mux *http.ServeMux) { 121 mux.HandleFunc("/azuremonitor/", s.resourceHandler(azureMonitor)) 122 mux.HandleFunc("/appinsights/", s.resourceHandler(appInsights)) 123 mux.HandleFunc("/loganalytics/", s.resourceHandler(azureLogAnalytics)) 124 mux.HandleFunc("/resourcegraph/", s.resourceHandler(azureResourceGraph)) 125} 126