tools/teamcity-diff-check/main.go (101 lines of code) (raw):
package main
import (
"bufio"
"flag"
"fmt"
"io"
"os"
"regexp"
)
var version = flag.String("version", "", "the provider version under test. Must be `ga` or `beta`")
var teamcityServiceFile = flag.String("teamcity_services", "", "path to a kotlin service file to be parsed")
var providerServiceFile = flag.String("provider_services", "", "path to a .txt file listing all service packages in the provider")
// listDifference checks that all the items in list B are present in list A
func listDifference(listA, listB []string) error {
a := make(map[string]struct{}, len(listA))
for _, s := range listA {
a[s] = struct{}{}
}
var diff []string
for _, s := range listB {
if _, found := a[s]; !found {
diff = append(diff, s)
}
}
if len(diff) > 0 {
return fmt.Errorf("%v", diff)
}
return nil
}
func main() {
flag.Parse()
ga := *version == "ga"
beta := *version == "beta"
if !ga && !beta {
fmt.Fprint(os.Stderr, "the flag `version` must be set to either `ga` or `beta`, and is case sensitive\n")
os.Exit(1)
}
err := compareServices(*teamcityServiceFile, *providerServiceFile)
if err != nil {
fmt.Fprintf(os.Stderr, "Errors when inspecting the %s version of the Google provider\n", *version)
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
os.Exit(1)
}
fmt.Fprintf(os.Stdout, "All services present in the %s provider codebase are present in TeamCity config, and vice versa\n", *version)
}
// compareServices contains most of the logic of the main function, but is separated to make the code more testable
func compareServices(teamcityServiceFile, providerServiceFile string) error {
// Get array of services from the provider service list file
file, err := os.Open(providerServiceFile)
if err != nil {
return fmt.Errorf("error opening provider service list file: %w", err)
}
defer file.Close()
googleServices := []string{}
scanner := bufio.NewScanner(file)
for scanner.Scan() {
googleServices = append(googleServices, scanner.Text())
}
if len(googleServices) == 0 {
return fmt.Errorf("could not find any services in the provider service list file %s", providerServiceFile)
}
// Get array of services from the TeamCity service list file
f, err := os.Open(teamcityServiceFile)
if err != nil {
return fmt.Errorf("error opening TeamCity service list file: %w", err)
}
stat, err := f.Stat()
if err != nil {
return fmt.Errorf("error stating TeamCity service list file: %w", err)
}
bs := make([]byte, stat.Size())
_, err = bufio.NewReader(f).Read(bs)
if err != nil && err != io.EOF {
return fmt.Errorf("error processing TeamCity service list file: %w", err)
}
// Regex pattern captures "services" from the Kotlin service list file.
pattern := regexp.MustCompile(`(?m)"(?P<service>\w+)"\sto\s+mapOf`)
template := []byte("$service")
dst := []byte{}
teamcityServices := []string{}
for _, submatches := range pattern.FindAllSubmatchIndex(bs, -1) {
service := pattern.Expand(dst, template, bs, submatches)
teamcityServices = append(teamcityServices, string(service))
}
if len(teamcityServices) == 0 {
return fmt.Errorf("could not find any services in the TeamCity service list file %s", teamcityServiceFile)
}
// Determine diffs
errTeamCity := listDifference(teamcityServices, googleServices)
errProvider := listDifference(googleServices, teamcityServices)
switch {
case errTeamCity != nil && errProvider != nil:
return fmt.Errorf(`mismatches detected:
TeamCity service file is missing services present in the provider: %s
Provider codebase is missing services present in the TeamCity service file: %s`,
errTeamCity, errProvider)
case errTeamCity != nil:
return fmt.Errorf(`mismatches detected:
TeamCity service file is missing services present in the provider: %s`,
errTeamCity)
case errProvider != nil:
return fmt.Errorf(`mismatches detected:
Provider codebase is missing services present in the TeamCity service file: %s`,
errProvider)
}
return nil
}