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