package main import ( "go/ast" "go/parser" "go/token" "os" "path/filepath" "sort" "strconv" "strings" "testing" ) // Region HA spec 2 §25: the provider boundary. Spec 2's core names no // cloud provider; its AWS parts are adapters in files named for AWS. Each // plan task that adds a core file lists it here in the same commit. // regionHACoreFiles are spec 2's provider-neutral files. None may import // an AWS package. var regionHACoreFiles = []string{ "internal/lbhealth/lbhealth.go", "internal/hostactors/roles.go", "internal/boxchoice/policy.go", "internal/host/boxaddress.go", "internal/edgesfile/private.go", "internal/edge/lbhealth.go", "internal/console/apiboxroles.go", "system_roles.go", "internal/console/nocapacity.go", "internal/hostops/chosen.go", "internal/console/regioncapacity.go", "internal/hostenv/hostenv.go", "internal/hostenv/cli.go", "internal/hostenv/write_linux.go", "internal/hostenv/write_other.go", "internal/host/installchannel.go", "internal/hostactors/membershipread.go", "internal/hostactors/groupref.go", "internal/hostactors/autoenroll.go", "internal/console/enrollpass.go", "internal/host/joinmode.go", "internal/host/boxreference.go", "internal/status/fleet.go", "internal/host/joinroot.go", "internal/host/joining.go", } // regionHAAdapterFiles are spec 2's AWS adapters. Each is named for AWS so // a reader can see where the provider lives. var regionHAAdapterFiles = []string{ "internal/host/awsaddr.go", "internal/console/awsregionmetric.go", "internal/hostenv/awsssm.go", "internal/hostactors/awsmembership/awsmembership.go", "internal/console/awsmembership.go", } // regionHAPackages are the packages spec 2 touches. Every S3 client built // in them goes through s3compat.Options(), so CARLOS_S3_ENDPOINT applies. var regionHAPackages = []string{ "internal/activator", "internal/boxchoice", "internal/console", "internal/edge", "internal/edgesfile", "internal/host", "internal/hostactors", "internal/hostactors/awsmembership", "internal/hostenv", "internal/hostops", "internal/lbhealth", } // regionHAS3Exceptions are S3 clients on main that skip s3compat, each // with the discussion that fixes it outside spec 2. A listed file that has // since been fixed passes. var regionHAS3Exceptions = map[string]string{ "internal/host/claimproduction_marker.go": "#298", } func TestRegionHACoreImportsNoAWS(t *testing.T) { for _, f := range regionHACoreFiles { file, err := parser.ParseFile(token.NewFileSet(), f, nil, parser.ImportsOnly) if err != nil { t.Fatalf("%s: %v", f, err) } for _, imp := range file.Imports { path, _ := strconv.Unquote(imp.Path.Value) if strings.HasPrefix(path, "github.com/aws/") { t.Errorf("%s imports %s: spec 2's core names no provider (§25); move it to an AWS adapter", f, path) } } } } func TestRegionHAAdaptersAreNamedForAWS(t *testing.T) { for _, f := range regionHAAdapterFiles { if _, err := os.Stat(f); err != nil { t.Errorf("%s: %v", f, err) } if !strings.Contains(filepath.Base(f), "aws") && !strings.Contains(filepath.Dir(f), "aws") { t.Errorf("%s is an AWS adapter but its name does not say so", f) } } } // s3NewFromConfigCalls returns the line of each s3.NewFromConfig call in // src that does not pass s3compat.Options() as an argument. func s3NewFromConfigCalls(t *testing.T, path string) []int { t.Helper() fset := token.NewFileSet() f, err := parser.ParseFile(fset, path, nil, 0) if err != nil { t.Fatalf("%s: %v", path, err) } s3Names, compatNames := map[string]bool{}, map[string]bool{} for _, imp := range f.Imports { p, _ := strconv.Unquote(imp.Path.Value) name := filepath.Base(p) if imp.Name != nil { name = imp.Name.Name } switch p { case "github.com/aws/aws-sdk-go-v2/service/s3": s3Names[name] = true case "github.com/carlosframework/platform/internal/s3compat": compatNames[name] = true } } var bad []int ast.Inspect(f, func(n ast.Node) bool { call, ok := n.(*ast.CallExpr) if !ok { return true } sel, ok := call.Fun.(*ast.SelectorExpr) if !ok || sel.Sel.Name != "NewFromConfig" { return true } pkg, ok := sel.X.(*ast.Ident) if !ok || !s3Names[pkg.Name] { return true } for _, arg := range call.Args { inner, ok := arg.(*ast.CallExpr) if !ok { continue } isel, ok := inner.Fun.(*ast.SelectorExpr) if !ok || isel.Sel.Name != "Options" { continue } if id, ok := isel.X.(*ast.Ident); ok && compatNames[id.Name] { return true } } bad = append(bad, fset.Position(call.Pos()).Line) return true }) return bad } func TestRegionHAS3ClientsUseS3Compat(t *testing.T) { for _, dir := range regionHAPackages { paths, err := filepath.Glob(filepath.Join(dir, "*.go")) if err != nil { t.Fatal(err) } sort.Strings(paths) for _, p := range paths { if strings.HasSuffix(p, "_test.go") { continue } if _, ok := regionHAS3Exceptions[filepath.ToSlash(p)]; ok { continue } for _, line := range s3NewFromConfigCalls(t, p) { t.Errorf("%s:%d builds an S3 client without s3compat.Options() (§25): CARLOS_S3_ENDPOINT would not apply", p, line) } } } } // The checker itself: a bare client is caught, one with Options() is not, // whatever the import names. func TestRegionHAS3CheckerCatchesABareClient(t *testing.T) { dir := t.TempDir() write := func(name, body string) string { p := filepath.Join(dir, name) if err := os.WriteFile(p, []byte(body), 0o644); err != nil { t.Fatal(err) } return p } bare := write("bare.go", `package x import awss3 "github.com/aws/aws-sdk-go-v2/service/s3" func f(cfg any) { _ = awss3.NewFromConfig(cfg) } `) good := write("good.go", `package x import ( "github.com/aws/aws-sdk-go-v2/service/s3" "github.com/carlosframework/platform/internal/s3compat" ) func f(cfg any) { _ = s3.NewFromConfig(cfg, s3compat.Options()) } `) if got := s3NewFromConfigCalls(t, bare); len(got) != 1 { t.Fatalf("bare client: %v", got) } if got := s3NewFromConfigCalls(t, good); len(got) != 0 { t.Fatalf("compat client flagged: %v", got) } }