package diagnostic import ( "archive/zip" "context" "encoding/json" "fmt" "io" "net/url" "os" "path/filepath" "strings" "time" "github.com/google/uuid" "github.com/rs/zerolog" ) // 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 } 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 } // newFormattedEncoder return a JSON encoder with identation func newFormattedEncoder(w io.Writer) *json.Encoder { encoder := json.NewEncoder(w) encoder.SetIndent("", " ") return encoder }