| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160 |
- package services
- import (
- "crypto/md5"
- "encoding/json"
- "fmt"
- "io/ioutil"
- "net/http"
- "net/url"
- "os"
- "path"
- "runtime"
- "github.com/grafana/grafana/pkg/cmd/grafana-cli/logger"
- "github.com/grafana/grafana/pkg/cmd/grafana-cli/models"
- "github.com/grafana/grafana/pkg/util/errutil"
- "golang.org/x/xerrors"
- )
- type GrafanaComClient struct {
- retryCount int
- }
- func (client *GrafanaComClient) GetPlugin(pluginId, repoUrl string) (models.Plugin, error) {
- logger.Debugf("getting plugin metadata from: %v pluginId: %v \n", repoUrl, pluginId)
- body, err := sendRequest(HttpClient, repoUrl, "repo", pluginId)
- if err != nil {
- if err == ErrNotFoundError {
- return models.Plugin{}, errutil.Wrap("Failed to find requested plugin, check if the plugin_id is correct", err)
- }
- return models.Plugin{}, errutil.Wrap("Failed to send request", err)
- }
- var data models.Plugin
- err = json.Unmarshal(body, &data)
- if err != nil {
- logger.Info("Failed to unmarshal plugin repo response error:", err)
- return models.Plugin{}, err
- }
- return data, nil
- }
- func (client *GrafanaComClient) DownloadFile(pluginName, filePath, url string, checksum string) (content []byte, err error) {
- // Try handling url like local file path first
- if _, err := os.Stat(url); err == nil {
- bytes, err := ioutil.ReadFile(url)
- if err != nil {
- return nil, errutil.Wrap("Failed to read file", err)
- }
- return bytes, nil
- }
- client.retryCount = 0
- defer func() {
- if r := recover(); r != nil {
- client.retryCount++
- if client.retryCount < 3 {
- logger.Info("Failed downloading. Will retry once.")
- content, err = client.DownloadFile(pluginName, filePath, url, checksum)
- } else {
- client.retryCount = 0
- failure := fmt.Sprintf("%v", r)
- if failure == "runtime error: makeslice: len out of range" {
- err = xerrors.New("Corrupt http response from source. Please try again")
- } else {
- panic(r)
- }
- }
- }
- }()
- // TODO: this would be better if it was streamed file by file instead of buffered.
- // Using no timeout here as some plugins can be bigger and smaller timeout would prevent to download a plugin on
- // slow network. As this is CLI operation hanging is not a big of an issue as user can just abort.
- body, err := sendRequest(HttpClientNoTimeout, url)
- if err != nil {
- return nil, errutil.Wrap("Failed to send request", err)
- }
- if len(checksum) > 0 && checksum != fmt.Sprintf("%x", md5.Sum(body)) {
- return nil, xerrors.New("Expected MD5 checksum does not match the downloaded archive. Please contact security@grafana.com.")
- }
- return body, nil
- }
- func (client *GrafanaComClient) ListAllPlugins(repoUrl string) (models.PluginRepo, error) {
- body, err := sendRequest(HttpClient, repoUrl, "repo")
- if err != nil {
- logger.Info("Failed to send request", "error", err)
- return models.PluginRepo{}, errutil.Wrap("Failed to send request", err)
- }
- var data models.PluginRepo
- err = json.Unmarshal(body, &data)
- if err != nil {
- logger.Info("Failed to unmarshal plugin repo response error:", err)
- return models.PluginRepo{}, err
- }
- return data, nil
- }
- func sendRequest(client http.Client, repoUrl string, subPaths ...string) ([]byte, error) {
- u, _ := url.Parse(repoUrl)
- for _, v := range subPaths {
- u.Path = path.Join(u.Path, v)
- }
- req, err := http.NewRequest(http.MethodGet, u.String(), nil)
- req.Header.Set("grafana-version", grafanaVersion)
- req.Header.Set("grafana-os", runtime.GOOS)
- req.Header.Set("grafana-arch", runtime.GOARCH)
- req.Header.Set("User-Agent", "grafana "+grafanaVersion)
- if err != nil {
- return []byte{}, err
- }
- res, err := client.Do(req)
- if err != nil {
- return []byte{}, err
- }
- return handleResponse(res)
- }
- func handleResponse(res *http.Response) ([]byte, error) {
- if res.StatusCode == 404 {
- return []byte{}, ErrNotFoundError
- }
- if res.StatusCode/100 != 2 && res.StatusCode/100 != 4 {
- return []byte{}, fmt.Errorf("Api returned invalid status: %s", res.Status)
- }
- body, err := ioutil.ReadAll(res.Body)
- defer res.Body.Close()
- if res.StatusCode/100 == 4 {
- if len(body) == 0 {
- return []byte{}, &BadRequestError{Status: res.Status}
- }
- var message string
- var jsonBody map[string]string
- err = json.Unmarshal(body, &jsonBody)
- if err != nil || len(jsonBody["message"]) == 0 {
- message = string(body)
- } else {
- message = jsonBody["message"]
- }
- return []byte{}, &BadRequestError{Status: res.Status, Message: message}
- }
- return body, err
- }
|