[build_init] Allow passing any spec path

This opens the door for adding a .textproto suffix to specs.

Test: go test -count=4 ./...
Bug: IN-1227 #comment
Change-Id: I6f336a5ca89003f4fdb0b1a69b26a926f6f27df6
diff --git a/cmd/build_init/main.go b/cmd/build_init/main.go
index 0593a77..e4c1141 100644
--- a/cmd/build_init/main.go
+++ b/cmd/build_init/main.go
@@ -51,11 +51,14 @@
 
 // Command line flags.
 var (
+	// The directory containing specs in the spec repository.
+	deprecatedSpecDir string
+
+	// The path to the spec file within the repository.
+	specPath string
+
 	// The URL of the spec repository.
 	specRemote string
-
-	// The directory containing specs in the spec repository.
-	specDir string
 )
 
 // Variables derived from user input.
@@ -72,7 +75,8 @@
 	flag.StringVar(&specRemote, "spec_remote", "", "The URL of the spec repository")
 
 	// Optional
-	flag.StringVar(&specDir, "spec_dir", defaultSpecDir, "The directory containing specs")
+	flag.StringVar(&deprecatedSpecDir, "spec_dir", defaultSpecDir, "Deprecated. Use spec_path instead")
+	flag.StringVar(&specPath, "spec_path", "", "The path to the spec file relative to the repo root. Errors if spec_dir is set")
 }
 
 func main() {
@@ -80,7 +84,7 @@
 	if err := validateInputs(); err != nil {
 		log.Fatal(err)
 	}
-	os.Exit(Execute(context.Background(), build, specRepoURL))
+	os.Exit(Execute(context.Background(), build, specRepoURL, specPath))
 }
 
 func validateInputs() (err error) {
@@ -100,7 +104,16 @@
 	if err := validateBuild(*build); err != nil {
 		return fmt.Errorf("invalid build message: %v", err)
 	}
-	return err
+
+	if (specPath != "" && deprecatedSpecDir != "") || (specPath == "" && deprecatedSpecDir == "") {
+		return errors.New("must specify exactly one of -spec_dir or -spec_path")
+	}
+
+	if specPath == "" {
+		specPath = filepath.Join(deprecatedSpecDir, build.Builder.Bucket, build.Builder.Builder)
+	}
+
+	return nil
 }
 
 func validateBuild(build buildbucketpb.Build) error {
@@ -123,21 +136,15 @@
 }
 
 // Execute runs the program. Public for testing.
-func Execute(ctx context.Context, build *buildbucketpb.Build, specRepoURL *url.URL) (code int) {
+func Execute(ctx context.Context, build *buildbucketpb.Build, specRepoURL *url.URL, specPath string) (code int) {
 	if err := checkout.Checkout(*build.Input, *specRepoURL); err != nil {
 		log.Printf("failed to checkout %s: %v", specRepoURL.String(), err)
 		return ExitFatal
 	}
-	specPath := filepath.Join(specDir, build.Builder.Bucket, build.Builder.Builder)
+
 	if _, err := os.Stat(specPath); os.IsNotExist(err) {
-		// TODO(IN-1227): Make this the only supported suffix.
-		// TODO(IN-1227): Add a .textproto file to the test project and roll the commits in integration tests.
-		withSuffix := specPath + ".textproto"
-		log.Printf("file not found %v. Trying %v", specPath, withSuffix)
-		if _, err := os.Stat(withSuffix); os.IsNotExist(err) {
-			log.Printf("file not found %v", withSuffix)
-			return ExitFileNotFound
-		}
+		log.Printf("file not found %v", specPath)
+		return ExitFileNotFound
 	}
 	fd, err := os.Open(specPath)
 	if err != nil {
diff --git a/cmd/build_init/main_integration_test.go b/cmd/build_init/main_integration_test.go
index ab07de7..8cded2d 100644
--- a/cmd/build_init/main_integration_test.go
+++ b/cmd/build_init/main_integration_test.go
@@ -7,6 +7,7 @@
 import (
 	"context"
 	"net/url"
+	"path/filepath"
 	"testing"
 
 	"fuchsia.googlesource.com/infra/infra/cmd/build_init/testbed"
@@ -28,6 +29,7 @@
 		name         string
 		build        *buildbucketpb.Build
 		repoURL      *url.URL
+		specPath     string
 		expectedCode int
 	}{
 		{
@@ -47,6 +49,7 @@
 				Host:   "fuchsia.googlesource.com",
 				Path:   "/infra/testproject",
 			},
+			specPath:     filepath.Join("infra", "config", "generated", "bucket", "example"),
 			expectedCode: ExitOk,
 		},
 		{
@@ -65,6 +68,7 @@
 				Host:   "fuchsia.googlesource.com",
 				Path:   "/infra/testproject",
 			},
+			specPath:     filepath.Join("infra", "config", "generated", "bucket", "noexist"),
 			expectedCode: ExitFileNotFound,
 		},
 	}
@@ -75,7 +79,7 @@
 			tb.Setup()
 			defer tb.Teardown()
 
-			code := Execute(context.Background(), tt.build, tt.repoURL)
+			code := Execute(context.Background(), tt.build, tt.repoURL, tt.specPath)
 			if code != tt.expectedCode {
 				t.Errorf("wanted exit code %d but got %d", tt.expectedCode, code)
 			}
diff --git a/cmd/build_init/testbed/testbed.go b/cmd/build_init/testbed/testbed.go
index 2d1139a..5120373 100644
--- a/cmd/build_init/testbed/testbed.go
+++ b/cmd/build_init/testbed/testbed.go
@@ -49,6 +49,6 @@
 
 func (tb *TestBed) Teardown() {
 	if err := os.RemoveAll(tb.dir); err != nil {
-		tb.t.Fatal(err)
+		tb.t.Fatalf("failed to teardown: %v", err)
 	}
 }