fix health report upload error (#3275)
This commit is contained in:
@@ -17,22 +17,22 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
b64 "encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/minio/console/pkg/logger"
|
||||
"github.com/minio/console/pkg/utils"
|
||||
|
||||
"github.com/klauspost/compress/gzip"
|
||||
xhttp "github.com/minio/console/pkg/http"
|
||||
subnet "github.com/minio/console/pkg/subnet"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
mc "github.com/minio/mc/cmd"
|
||||
"github.com/minio/websocket"
|
||||
)
|
||||
|
||||
@@ -63,7 +63,7 @@ func startHealthInfo(ctx context.Context, conn WSConn, client MinioAdmin, deadli
|
||||
return err
|
||||
}
|
||||
|
||||
compressedDiag, err := tarGZ(healthInfo, version)
|
||||
compressedDiag, err := mc.TarGZHealthInfo(healthInfo, version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -75,11 +75,11 @@ func startHealthInfo(ctx context.Context, conn WSConn, client MinioAdmin, deadli
|
||||
}
|
||||
|
||||
ctx = context.WithValue(ctx, utils.ContextClientIP, conn.remoteAddress())
|
||||
subnetResp, err := sendHealthInfoToSubnet(ctx, healthInfo, client)
|
||||
err = sendHealthInfoToSubnet(ctx, healthInfo, client)
|
||||
report := messageReport{
|
||||
Encoded: encodedDiag,
|
||||
ServerHealthInfo: healthInfo,
|
||||
SubnetResponse: subnetResp,
|
||||
SubnetResponse: mc.SubnetBaseURL() + "/health",
|
||||
}
|
||||
if err != nil {
|
||||
report.SubnetResponse = fmt.Sprintf("Error: %s", err.Error())
|
||||
@@ -94,31 +94,6 @@ func startHealthInfo(ctx context.Context, conn WSConn, client MinioAdmin, deadli
|
||||
return conn.writeMessage(websocket.TextMessage, message)
|
||||
}
|
||||
|
||||
// compress and tar MinIO diagnostics output
|
||||
func tarGZ(healthInfo interface{}, version string) ([]byte, error) {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
gzWriter := gzip.NewWriter(buffer)
|
||||
|
||||
enc := json.NewEncoder(gzWriter)
|
||||
|
||||
header := struct {
|
||||
Version string `json:"version"`
|
||||
}{Version: version}
|
||||
|
||||
if err := enc.Encode(header); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := enc.Encode(healthInfo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err := gzWriter.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buffer.Bytes(), nil
|
||||
}
|
||||
|
||||
// getHealthInfoOptionsFromReq gets duration for startHealthInfo request
|
||||
// path come as : `/health-info?deadline=2h`
|
||||
func getHealthInfoOptionsFromReq(req *http.Request) (*time.Duration, error) {
|
||||
@@ -129,16 +104,27 @@ func getHealthInfoOptionsFromReq(req *http.Request) (*time.Duration, error) {
|
||||
return &deadlineDuration, nil
|
||||
}
|
||||
|
||||
func sendHealthInfoToSubnet(ctx context.Context, healthInfo interface{}, client MinioAdmin) (string, error) {
|
||||
filename := fmt.Sprintf("health_%d.json", time.Now().Unix())
|
||||
func updateMcGlobals(subnetTokenConfig subnet.LicenseTokenConfig) error {
|
||||
mc.GlobalDevMode = getConsoleDevMode()
|
||||
if len(subnetTokenConfig.Proxy) > 0 {
|
||||
proxyURL, e := url.Parse(subnetTokenConfig.Proxy)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
mc.GlobalSubnetProxyURL = proxyURL
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
clientIP := utils.ClientIPFromContext(ctx)
|
||||
|
||||
subnetUploadURL := subnet.UploadURL("health", filename)
|
||||
subnetHTTPClient := &xhttp.Client{Client: GetConsoleHTTPClient("", clientIP)}
|
||||
func sendHealthInfoToSubnet(ctx context.Context, healthInfo interface{}, client MinioAdmin) error {
|
||||
filename := fmt.Sprintf("health_%d.json.gz", time.Now().Unix())
|
||||
subnetTokenConfig, e := GetSubnetKeyFromMinIOConfig(ctx, client)
|
||||
if e != nil {
|
||||
return "", e
|
||||
return e
|
||||
}
|
||||
e = updateMcGlobals(*subnetTokenConfig)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
var apiKey string
|
||||
if len(subnetTokenConfig.APIKey) != 0 {
|
||||
@@ -146,32 +132,41 @@ func sendHealthInfoToSubnet(ctx context.Context, healthInfo interface{}, client
|
||||
} else {
|
||||
apiKey, e = subnet.GetSubnetAPIKeyUsingLicense(subnetTokenConfig.License)
|
||||
if e != nil {
|
||||
return "", e
|
||||
return e
|
||||
}
|
||||
}
|
||||
headers := subnet.UploadAuthHeaders(apiKey)
|
||||
uploadInfo, formDataType, e := subnet.ProcessUploadInfo(healthInfo, "health", filename)
|
||||
compressedHealthInfo, e := mc.TarGZHealthInfo(healthInfo, madmin.HealthInfoVersion)
|
||||
if e != nil {
|
||||
return "", e
|
||||
return e
|
||||
}
|
||||
e = os.WriteFile(filename, compressedHealthInfo, 0o666)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
headers := mc.SubnetAPIKeyAuthHeaders(apiKey)
|
||||
resp, e := (&mc.SubnetFileUploader{
|
||||
FilePath: filename,
|
||||
ReqURL: mc.SubnetUploadURL("health"),
|
||||
Headers: headers,
|
||||
DeleteAfterUpload: true,
|
||||
}).UploadFileToSubnet()
|
||||
if e != nil {
|
||||
// file gets deleted only if upload is successful
|
||||
// so we delete explicitly here as we already have the bytes
|
||||
logger.LogIf(ctx, os.Remove(filename))
|
||||
return e
|
||||
}
|
||||
resp, e := subnet.UploadFileToSubnet(uploadInfo, subnetHTTPClient, subnetUploadURL, headers, formDataType)
|
||||
|
||||
if e != nil {
|
||||
return "", e
|
||||
}
|
||||
type SubnetResponse struct {
|
||||
ClusterURL string `json:"cluster_url,omitempty"`
|
||||
LicenseV2 string `json:"license_v2,omitempty"`
|
||||
APIKey string `json:"api_key,omitempty"`
|
||||
}
|
||||
|
||||
var subnetResp SubnetResponse
|
||||
e = json.Unmarshal([]byte(resp), &subnetResp)
|
||||
if e != nil {
|
||||
return "", e
|
||||
}
|
||||
if len(subnetResp.ClusterURL) != 0 {
|
||||
subnetClusterURL := strings.ReplaceAll(subnetResp.ClusterURL, "%2f", "/")
|
||||
return subnetClusterURL, nil
|
||||
return e
|
||||
}
|
||||
|
||||
return "", ErrSubnetUploadFail
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -506,7 +506,7 @@ func replaceBaseInIndex(indexPageBytes []byte, basePath string) []byte {
|
||||
func replaceLicense(indexPageBytes []byte) []byte {
|
||||
indexPageStr := string(indexPageBytes)
|
||||
newPlan := fmt.Sprintf("<meta name=\"minio-license\" content=\"%s\" />", InstanceLicensePlan.String())
|
||||
indexPageStr = strings.Replace(indexPageStr, "<meta name=\"minio-license\" content=\"apgl\"/>", newPlan, 1)
|
||||
indexPageStr = strings.Replace(indexPageStr, "<meta name=\"minio-license\" content=\"agpl\"/>", newPlan, 1)
|
||||
indexPageBytes = []byte(indexPageStr)
|
||||
return indexPageBytes
|
||||
}
|
||||
|
||||
@@ -72,7 +72,6 @@ var (
|
||||
ErrEncryptionConfigNotFound = errors.New("encryption configuration not found")
|
||||
ErrPolicyNotFound = errors.New("policy does not exist")
|
||||
ErrLoginNotAllowed = errors.New("login not allowed")
|
||||
ErrSubnetUploadFail = errors.New("SUBNET upload failed")
|
||||
ErrHealthReportFail = errors.New("failure to generate Health report")
|
||||
)
|
||||
|
||||
|
||||
4
go.mod
4
go.mod
@@ -21,8 +21,8 @@ require (
|
||||
github.com/minio/cli v1.24.2
|
||||
github.com/minio/highwayhash v1.0.2
|
||||
github.com/minio/kes v0.23.0
|
||||
github.com/minio/madmin-go/v3 v3.0.50-0.20240307075442-63b4fc3ac1fd
|
||||
github.com/minio/mc v0.0.0-20240320210729-9043bbf545d2
|
||||
github.com/minio/madmin-go/v3 v3.0.50
|
||||
github.com/minio/mc v0.0.0-20240330152952-9f8147bf0e03
|
||||
github.com/minio/minio-go/v7 v7.0.69
|
||||
github.com/minio/selfupdate v0.6.0
|
||||
github.com/minio/websocket v1.6.0
|
||||
|
||||
8
go.sum
8
go.sum
@@ -184,10 +184,10 @@ github.com/minio/kes v0.23.0 h1:T0zHtyDoI3JdKrVvzdM4xwVryYYyh5pKwNUVBoqxsNs=
|
||||
github.com/minio/kes v0.23.0/go.mod h1:vvXVGcgu9mYLkbVWlEvFFl6bYR196RQlOU2Q+rHApl8=
|
||||
github.com/minio/kes-go v0.2.1 h1:KnqS+p6xoSFJZbQhmJaz/PbxeA6nQyRqT/ywrn5lU2o=
|
||||
github.com/minio/kes-go v0.2.1/go.mod h1:76xf7l41Wrh+IifisABXK2S8uZWYgWV1IGBKC3GdOJk=
|
||||
github.com/minio/madmin-go/v3 v3.0.50-0.20240307075442-63b4fc3ac1fd h1:oZycRrLgzEg4+lGZ5Tkua3BKvK7rOtDlmoOR3gq58l4=
|
||||
github.com/minio/madmin-go/v3 v3.0.50-0.20240307075442-63b4fc3ac1fd/go.mod h1:ZDF7kf5fhmxLhbGTqyq5efs4ao0v4eWf7nOuef/ljJs=
|
||||
github.com/minio/mc v0.0.0-20240320210729-9043bbf545d2 h1:zF4kiN6HJp4B5IFp+riDA8roY0r+UJV0RvaaVitchH4=
|
||||
github.com/minio/mc v0.0.0-20240320210729-9043bbf545d2/go.mod h1:YgeY0RTYvbm/H6Gzwb+rpRsQ/XREi3NwITZUcTNATOQ=
|
||||
github.com/minio/madmin-go/v3 v3.0.50 h1:+RQMetVFvPQmAOEDN/xmLhwk9+xOzu3rqwnlZEskgvg=
|
||||
github.com/minio/madmin-go/v3 v3.0.50/go.mod h1:ZDF7kf5fhmxLhbGTqyq5efs4ao0v4eWf7nOuef/ljJs=
|
||||
github.com/minio/mc v0.0.0-20240330152952-9f8147bf0e03 h1:xF1hntqvs/CVEHGBETSrIMTW3iSU3k2j/YCFXGDWoBs=
|
||||
github.com/minio/mc v0.0.0-20240330152952-9f8147bf0e03/go.mod h1:RMCe706GTL9EOO6pxzFXd9Vp+3w2L1uctPiycmLDr9U=
|
||||
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||
github.com/minio/minio-go/v7 v7.0.69 h1:l8AnsQFyY1xiwa/DaQskY4NXSLA2yrGsW5iD9nRPVS0=
|
||||
|
||||
@@ -18,14 +18,11 @@ package subnet
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
@@ -79,65 +76,6 @@ func UploadAuthHeaders(apiKey string) map[string]string {
|
||||
return map[string]string{"x-subnet-api-key": apiKey}
|
||||
}
|
||||
|
||||
func ProcessUploadInfo(info interface{}, uploadType string, filename string) ([]byte, string, error) {
|
||||
if uploadType == "health" {
|
||||
return processHealthReport(info, filename)
|
||||
}
|
||||
return nil, "", errors.New("invalid SUBNET upload type")
|
||||
}
|
||||
|
||||
func UploadFileToSubnet(info []byte, client *xhttp.Client, reqURL string, headers map[string]string, formDataType string) (string, error) {
|
||||
req, e := subnetUploadReq(info, reqURL, formDataType)
|
||||
if e != nil {
|
||||
return "", e
|
||||
}
|
||||
resp, e := subnetReqDo(client, req, headers)
|
||||
return resp, e
|
||||
}
|
||||
|
||||
func processHealthReport(info interface{}, filename string) ([]byte, string, error) {
|
||||
var body bytes.Buffer
|
||||
writer := multipart.NewWriter(&body)
|
||||
zipWriter := gzip.NewWriter(&body)
|
||||
version := "3"
|
||||
enc := json.NewEncoder(zipWriter)
|
||||
|
||||
header := struct {
|
||||
Version string `json:"version"`
|
||||
}{Version: version}
|
||||
|
||||
if e := enc.Encode(header); e != nil {
|
||||
return nil, "", e
|
||||
}
|
||||
|
||||
if e := enc.Encode(info); e != nil {
|
||||
return nil, "", e
|
||||
}
|
||||
zipWriter.Close()
|
||||
temp := body
|
||||
part, e := writer.CreateFormFile("file", filename)
|
||||
if e != nil {
|
||||
return nil, "", e
|
||||
}
|
||||
if _, e = io.Copy(part, &temp); e != nil {
|
||||
return nil, "", e
|
||||
}
|
||||
|
||||
writer.Close()
|
||||
return body.Bytes(), writer.FormDataContentType(), nil
|
||||
}
|
||||
|
||||
func subnetUploadReq(body []byte, url string, formDataType string) (*http.Request, error) {
|
||||
uploadDataBody := bytes.NewReader(body)
|
||||
r, e := http.NewRequest(http.MethodPost, url, uploadDataBody)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
r.Header.Add("Content-Type", formDataType)
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func GenerateRegToken(clusterRegInfo mc.ClusterRegistrationInfo) (string, error) {
|
||||
token, e := json.Marshal(clusterRegInfo)
|
||||
if e != nil {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
name="theme-color"
|
||||
/>
|
||||
<meta content="MinIO Console" name="description" />
|
||||
<meta name="minio-license" content="apgl" />
|
||||
<meta name="minio-license" content="agpl" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
|
||||
Reference in New Issue
Block a user