fix health report upload error (#3275)
This commit is contained in:
@@ -17,22 +17,22 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
b64 "encoding/base64"
|
b64 "encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"net/url"
|
||||||
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/minio/console/pkg/logger"
|
||||||
"github.com/minio/console/pkg/utils"
|
"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"
|
subnet "github.com/minio/console/pkg/subnet"
|
||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
|
mc "github.com/minio/mc/cmd"
|
||||||
"github.com/minio/websocket"
|
"github.com/minio/websocket"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ func startHealthInfo(ctx context.Context, conn WSConn, client MinioAdmin, deadli
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
compressedDiag, err := tarGZ(healthInfo, version)
|
compressedDiag, err := mc.TarGZHealthInfo(healthInfo, version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -75,11 +75,11 @@ func startHealthInfo(ctx context.Context, conn WSConn, client MinioAdmin, deadli
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx = context.WithValue(ctx, utils.ContextClientIP, conn.remoteAddress())
|
ctx = context.WithValue(ctx, utils.ContextClientIP, conn.remoteAddress())
|
||||||
subnetResp, err := sendHealthInfoToSubnet(ctx, healthInfo, client)
|
err = sendHealthInfoToSubnet(ctx, healthInfo, client)
|
||||||
report := messageReport{
|
report := messageReport{
|
||||||
Encoded: encodedDiag,
|
Encoded: encodedDiag,
|
||||||
ServerHealthInfo: healthInfo,
|
ServerHealthInfo: healthInfo,
|
||||||
SubnetResponse: subnetResp,
|
SubnetResponse: mc.SubnetBaseURL() + "/health",
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
report.SubnetResponse = fmt.Sprintf("Error: %s", err.Error())
|
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)
|
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
|
// getHealthInfoOptionsFromReq gets duration for startHealthInfo request
|
||||||
// path come as : `/health-info?deadline=2h`
|
// path come as : `/health-info?deadline=2h`
|
||||||
func getHealthInfoOptionsFromReq(req *http.Request) (*time.Duration, error) {
|
func getHealthInfoOptionsFromReq(req *http.Request) (*time.Duration, error) {
|
||||||
@@ -129,16 +104,27 @@ func getHealthInfoOptionsFromReq(req *http.Request) (*time.Duration, error) {
|
|||||||
return &deadlineDuration, nil
|
return &deadlineDuration, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendHealthInfoToSubnet(ctx context.Context, healthInfo interface{}, client MinioAdmin) (string, error) {
|
func updateMcGlobals(subnetTokenConfig subnet.LicenseTokenConfig) error {
|
||||||
filename := fmt.Sprintf("health_%d.json", time.Now().Unix())
|
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)
|
func sendHealthInfoToSubnet(ctx context.Context, healthInfo interface{}, client MinioAdmin) error {
|
||||||
|
filename := fmt.Sprintf("health_%d.json.gz", time.Now().Unix())
|
||||||
subnetUploadURL := subnet.UploadURL("health", filename)
|
|
||||||
subnetHTTPClient := &xhttp.Client{Client: GetConsoleHTTPClient("", clientIP)}
|
|
||||||
subnetTokenConfig, e := GetSubnetKeyFromMinIOConfig(ctx, client)
|
subnetTokenConfig, e := GetSubnetKeyFromMinIOConfig(ctx, client)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
return "", e
|
return e
|
||||||
|
}
|
||||||
|
e = updateMcGlobals(*subnetTokenConfig)
|
||||||
|
if e != nil {
|
||||||
|
return e
|
||||||
}
|
}
|
||||||
var apiKey string
|
var apiKey string
|
||||||
if len(subnetTokenConfig.APIKey) != 0 {
|
if len(subnetTokenConfig.APIKey) != 0 {
|
||||||
@@ -146,32 +132,41 @@ func sendHealthInfoToSubnet(ctx context.Context, healthInfo interface{}, client
|
|||||||
} else {
|
} else {
|
||||||
apiKey, e = subnet.GetSubnetAPIKeyUsingLicense(subnetTokenConfig.License)
|
apiKey, e = subnet.GetSubnetAPIKeyUsingLicense(subnetTokenConfig.License)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
return "", e
|
return e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
headers := subnet.UploadAuthHeaders(apiKey)
|
compressedHealthInfo, e := mc.TarGZHealthInfo(healthInfo, madmin.HealthInfoVersion)
|
||||||
uploadInfo, formDataType, e := subnet.ProcessUploadInfo(healthInfo, "health", filename)
|
|
||||||
if e != nil {
|
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 {
|
type SubnetResponse struct {
|
||||||
ClusterURL string `json:"cluster_url,omitempty"`
|
LicenseV2 string `json:"license_v2,omitempty"`
|
||||||
|
APIKey string `json:"api_key,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var subnetResp SubnetResponse
|
var subnetResp SubnetResponse
|
||||||
e = json.Unmarshal([]byte(resp), &subnetResp)
|
e = json.Unmarshal([]byte(resp), &subnetResp)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
return "", e
|
return e
|
||||||
}
|
|
||||||
if len(subnetResp.ClusterURL) != 0 {
|
|
||||||
subnetClusterURL := strings.ReplaceAll(subnetResp.ClusterURL, "%2f", "/")
|
|
||||||
return subnetClusterURL, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", ErrSubnetUploadFail
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -506,7 +506,7 @@ func replaceBaseInIndex(indexPageBytes []byte, basePath string) []byte {
|
|||||||
func replaceLicense(indexPageBytes []byte) []byte {
|
func replaceLicense(indexPageBytes []byte) []byte {
|
||||||
indexPageStr := string(indexPageBytes)
|
indexPageStr := string(indexPageBytes)
|
||||||
newPlan := fmt.Sprintf("<meta name=\"minio-license\" content=\"%s\" />", InstanceLicensePlan.String())
|
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)
|
indexPageBytes = []byte(indexPageStr)
|
||||||
return indexPageBytes
|
return indexPageBytes
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,7 +72,6 @@ var (
|
|||||||
ErrEncryptionConfigNotFound = errors.New("encryption configuration not found")
|
ErrEncryptionConfigNotFound = errors.New("encryption configuration not found")
|
||||||
ErrPolicyNotFound = errors.New("policy does not exist")
|
ErrPolicyNotFound = errors.New("policy does not exist")
|
||||||
ErrLoginNotAllowed = errors.New("login not allowed")
|
ErrLoginNotAllowed = errors.New("login not allowed")
|
||||||
ErrSubnetUploadFail = errors.New("SUBNET upload failed")
|
|
||||||
ErrHealthReportFail = errors.New("failure to generate Health report")
|
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/cli v1.24.2
|
||||||
github.com/minio/highwayhash v1.0.2
|
github.com/minio/highwayhash v1.0.2
|
||||||
github.com/minio/kes v0.23.0
|
github.com/minio/kes v0.23.0
|
||||||
github.com/minio/madmin-go/v3 v3.0.50-0.20240307075442-63b4fc3ac1fd
|
github.com/minio/madmin-go/v3 v3.0.50
|
||||||
github.com/minio/mc v0.0.0-20240320210729-9043bbf545d2
|
github.com/minio/mc v0.0.0-20240330152952-9f8147bf0e03
|
||||||
github.com/minio/minio-go/v7 v7.0.69
|
github.com/minio/minio-go/v7 v7.0.69
|
||||||
github.com/minio/selfupdate v0.6.0
|
github.com/minio/selfupdate v0.6.0
|
||||||
github.com/minio/websocket v1.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 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 h1:KnqS+p6xoSFJZbQhmJaz/PbxeA6nQyRqT/ywrn5lU2o=
|
||||||
github.com/minio/kes-go v0.2.1/go.mod h1:76xf7l41Wrh+IifisABXK2S8uZWYgWV1IGBKC3GdOJk=
|
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 h1:+RQMetVFvPQmAOEDN/xmLhwk9+xOzu3rqwnlZEskgvg=
|
||||||
github.com/minio/madmin-go/v3 v3.0.50-0.20240307075442-63b4fc3ac1fd/go.mod h1:ZDF7kf5fhmxLhbGTqyq5efs4ao0v4eWf7nOuef/ljJs=
|
github.com/minio/madmin-go/v3 v3.0.50/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-20240330152952-9f8147bf0e03 h1:xF1hntqvs/CVEHGBETSrIMTW3iSU3k2j/YCFXGDWoBs=
|
||||||
github.com/minio/mc v0.0.0-20240320210729-9043bbf545d2/go.mod h1:YgeY0RTYvbm/H6Gzwb+rpRsQ/XREi3NwITZUcTNATOQ=
|
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 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||||
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
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=
|
github.com/minio/minio-go/v7 v7.0.69 h1:l8AnsQFyY1xiwa/DaQskY4NXSLA2yrGsW5iD9nRPVS0=
|
||||||
|
|||||||
@@ -18,14 +18,11 @@ package subnet
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"compress/gzip"
|
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"mime/multipart"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
@@ -79,65 +76,6 @@ func UploadAuthHeaders(apiKey string) map[string]string {
|
|||||||
return map[string]string{"x-subnet-api-key": apiKey}
|
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) {
|
func GenerateRegToken(clusterRegInfo mc.ClusterRegistrationInfo) (string, error) {
|
||||||
token, e := json.Marshal(clusterRegInfo)
|
token, e := json.Marshal(clusterRegInfo)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
name="theme-color"
|
name="theme-color"
|
||||||
/>
|
/>
|
||||||
<meta content="MinIO Console" name="description" />
|
<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
|
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/
|
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||||
|
|||||||
Reference in New Issue
Block a user