2024-12-03 11:27:04 +00:00
|
|
|
package diagnostic
|
|
|
|
|
|
|
|
import (
|
|
|
|
"archive/zip"
|
2024-12-03 12:07:55 +00:00
|
|
|
"context"
|
2024-12-10 21:01:24 +00:00
|
|
|
"encoding/json"
|
2024-12-03 11:27:04 +00:00
|
|
|
"fmt"
|
|
|
|
"io"
|
2024-12-03 12:07:55 +00:00
|
|
|
"net/url"
|
2024-12-03 11:27:04 +00:00
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
"time"
|
2024-12-03 12:07:55 +00:00
|
|
|
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"github.com/rs/zerolog"
|
2024-12-03 11:27:04 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// CreateDiagnosticZipFile create a zip file with the contents from the all
|
|
|
|
// files paths. The files will be written in the root of the zip file.
|
|
|
|
// In case of an error occurs after whilst writing to the zip file
|
|
|
|
// this will be removed.
|
|
|
|
func CreateDiagnosticZipFile(base string, paths []string) (zipFileName string, err error) {
|
|
|
|
// Create a zip file with all files from paths added to the root
|
|
|
|
suffix := time.Now().Format(time.RFC3339)
|
|
|
|
zipFileName = base + "-" + suffix + ".zip"
|
|
|
|
zipFileName = strings.ReplaceAll(zipFileName, ":", "-")
|
|
|
|
|
|
|
|
archive, cerr := os.Create(zipFileName)
|
|
|
|
if cerr != nil {
|
|
|
|
return "", fmt.Errorf("error creating file %s: %w", zipFileName, cerr)
|
|
|
|
}
|
|
|
|
|
|
|
|
archiveWriter := zip.NewWriter(archive)
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
archiveWriter.Close()
|
|
|
|
archive.Close()
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
os.Remove(zipFileName)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
for _, file := range paths {
|
|
|
|
if file == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
var handle *os.File
|
|
|
|
|
|
|
|
handle, err = os.Open(file)
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("error opening file %s: %w", zipFileName, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
defer handle.Close()
|
|
|
|
|
|
|
|
// Keep the base only to not create sub directories in the
|
|
|
|
// zip file.
|
|
|
|
var writer io.Writer
|
|
|
|
|
|
|
|
writer, err = archiveWriter.Create(filepath.Base(file))
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("error creating archive writer from %s: %w", file, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, err = io.Copy(writer, handle); err != nil {
|
|
|
|
return "", fmt.Errorf("error copying file %s: %w", file, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
zipFileName = archive.Name()
|
|
|
|
return zipFileName, nil
|
|
|
|
}
|
2024-12-03 12:07:55 +00:00
|
|
|
|
|
|
|
type AddressableTunnelState struct {
|
|
|
|
*TunnelState
|
|
|
|
URL *url.URL
|
|
|
|
}
|
|
|
|
|
|
|
|
func findMetricsServerPredicate(tunnelID, connectorID uuid.UUID) func(state *TunnelState) bool {
|
|
|
|
if tunnelID != uuid.Nil && connectorID != uuid.Nil {
|
|
|
|
return func(state *TunnelState) bool {
|
|
|
|
return state.ConnectorID == connectorID && state.TunnelID == tunnelID
|
|
|
|
}
|
|
|
|
} else if tunnelID == uuid.Nil && connectorID != uuid.Nil {
|
|
|
|
return func(state *TunnelState) bool {
|
|
|
|
return state.ConnectorID == connectorID
|
|
|
|
}
|
|
|
|
} else if tunnelID != uuid.Nil && connectorID == uuid.Nil {
|
|
|
|
return func(state *TunnelState) bool {
|
|
|
|
return state.TunnelID == tunnelID
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return func(*TunnelState) bool {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// The FindMetricsServer will try to find the metrics server url.
|
|
|
|
// There are two possible error scenarios:
|
|
|
|
// 1. No instance is found which will only return ErrMetricsServerNotFound
|
|
|
|
// 2. Multiple instances are found which will return an array of state and ErrMultipleMetricsServerFound
|
|
|
|
// In case of success, only the state for the instance is returned.
|
|
|
|
func FindMetricsServer(
|
|
|
|
log *zerolog.Logger,
|
|
|
|
client *httpClient,
|
|
|
|
addresses []string,
|
|
|
|
) (*AddressableTunnelState, []*AddressableTunnelState, error) {
|
|
|
|
instances := make([]*AddressableTunnelState, 0)
|
|
|
|
|
|
|
|
for _, address := range addresses {
|
|
|
|
url, err := url.Parse("http://" + address)
|
|
|
|
if err != nil {
|
|
|
|
log.Debug().Err(err).Msgf("error parsing address %s", address)
|
|
|
|
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
client.SetBaseURL(url)
|
|
|
|
|
|
|
|
state, err := client.GetTunnelState(context.Background())
|
|
|
|
if err == nil {
|
|
|
|
instances = append(instances, &AddressableTunnelState{state, url})
|
|
|
|
} else {
|
|
|
|
log.Debug().Err(err).Msgf("error getting tunnel state from address %s", address)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(instances) == 0 {
|
|
|
|
return nil, nil, ErrMetricsServerNotFound
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(instances) == 1 {
|
|
|
|
return instances[0], nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, instances, ErrMultipleMetricsServerFound
|
|
|
|
}
|
2024-12-10 21:01:24 +00:00
|
|
|
|
|
|
|
// newFormattedEncoder return a JSON encoder with identation
|
|
|
|
func newFormattedEncoder(w io.Writer) *json.Encoder {
|
|
|
|
encoder := json.NewEncoder(w)
|
|
|
|
encoder.SetIndent("", " ")
|
|
|
|
return encoder
|
|
|
|
}
|