cmd/tc-collector/main.go (122 lines of code) (raw):

package main import ( "encoding/json" "errors" "flag" "fmt" "log/slog" "net/http" "os" "strings" "time" "github.com/JetBrains/ij-perf-report-aggregator/pkg/util" "github.com/araddon/dateparse" "go.deanishe.net/env" ) // 1. You need to provide CONFIG env variable that may look like: // {"teamcityUrl": "http://buildserver.labs.intellij.net", "buildConfigurations":[{"db": "perfint", "configurations": ["ijplatform_IjPlatform221_IntegrationPerformanceTestsLinux"]}]} // 2. You also need to provide TC_TOKEN env variable which can be generated at: https://buildserver.labs.intellij.net/profile.html?item=accessTokens# // 3. Clickhouse DB should be up and running (see readme.md "Adding a New Database" section) func main() { err := configureCollectFromTeamCity() if err != nil { slog.Error("cannot collect", "err", err) os.Exit(78) } } func hasOSSuffix(osList []string, configuration string) bool { for _, osName := range osList { if strings.HasSuffix(configuration, osName) { return true } } return false } // TC REST API: By default only builds from the default branch are returned (https://www.jetbrains.com/help/teamcity/rest-api.html#Build-Locator), // so, no need to explicitly specify filter func configureCollectFromTeamCity() error { clickHouseUrl := env.Get("CLICKHOUSE", "127.0.0.1:9000") sinceDate := flag.String("since", "", "The date to force collecting since") flag.Parse() var since time.Time if *sinceDate != "" { var err error since, err = dateparse.ParseStrict(*sinceDate) if err != nil { return fmt.Errorf("cannot parse since date: %w", err) } } var config CollectorConfiguration rawJson, err := util.GetEnvOrFile("CONFIG", "/etc/config/config.json") if err != nil { return err } rawJson = strings.TrimSpace(rawJson) if rawJson == "" { return errors.New("file /etc/config/config.json is empty or env CONFIG is not set") } err = json.Unmarshal([]byte(rawJson), &config) if err != nil { return errors.New("cannot parse json: " + rawJson) } httpClient := &http.Client{ Timeout: 60 * time.Second, Transport: &http.Transport{ MaxIdleConns: 10, MaxIdleConnsPerHost: 10, }, } httpClient.CheckRedirect = checkRedirectFunc taskContext, cancel := util.CreateCommandContext() defer cancel() for _, chunk := range config.BuildConfigurations { if taskContext.Err() != nil { break } var buildConfigurationIds []string switch { case chunk.Database == "ij": osList := []string{"Linux", "Windows", "MacM2"} for _, configuration := range chunk.Configurations { if hasOSSuffix(osList, configuration) { buildConfigurationIds = append(buildConfigurationIds, configuration) } else { for _, osName := range osList { buildConfigurationIds = append(buildConfigurationIds, configuration+osName) } } } default: for _, configuration := range chunk.Configurations { collector := &Collector{ serverUrl: config.TeamcityUrl + "/app/rest", httpClient: httpClient, } configurations, err := collector.getSnapshots(taskContext, configuration) slog.Info("get snapshots", "configurations", configurations) if err != nil { slog.Warn("cannot get snapshots", "err", err) } buildConfigurationIds = append(buildConfigurationIds, configurations...) } } err = collectFromTeamCity(taskContext, clickHouseUrl, config.TeamcityUrl, chunk.Database, buildConfigurationIds, since, httpClient) if err != nil { return err } } natsUrl := os.Getenv("NATS") if natsUrl != "" { err = doNotifyServer(natsUrl) if err != nil { return err } } return nil } func checkRedirectFunc(req *http.Request, via []*http.Request) error { req.Header.Add("Authorization", via[0].Header.Get("Authorization")) return nil } type CollectorConfiguration struct { TeamcityUrl string `json:"teamcityUrl"` BuildConfigurations []CollectorChunk `json:"buildConfigurations"` } type CollectorChunk struct { Database string `json:"db"` Configurations []string `json:"configurations"` }