[recipe_bootstrap] Delete the recipe_version property.
We don't need to set the recipe_version property and pass it to child builders, instead we can look up the recipe version in the infra/recipes manifest at the specified integration_base_revision and use that.
Rename the "dir" arguments to "integrationDir" and "recipesDir" for clarity.
Bug: 64892
Change-Id: I3643bb1e520731d40a67a0012829854bcf5e744b
Reviewed-on: https://fuchsia-review.googlesource.com/c/infra/infra/+/507359
Fuchsia-Auto-Submit: Mahesh Saripalli <maheshsr@google.com>
Commit-Queue: Auto-Submit <auto-submit@fuchsia-infra.iam.gserviceaccount.com>
Reviewed-by: Oliver Newman <olivernewman@google.com>
diff --git a/cmd/recipe_bootstrap/main.go b/cmd/recipe_bootstrap/main.go
index 9820c76..e3d78b3 100644
--- a/cmd/recipe_bootstrap/main.go
+++ b/cmd/recipe_bootstrap/main.go
@@ -8,7 +8,6 @@
"bytes"
"context"
"encoding/json"
- "errors"
"flag"
"fmt"
"io/ioutil"
@@ -161,50 +160,39 @@
return nil
}
-// resolveRecipeVersionAndProperties resolves the recipe version and build properties to use.
-func resolveRecipeVersionAndProperties(ctx context.Context, build *buildbucketpb.Build) error {
+// getBuilderFilePath returns the builder file path to read the build input properties.
+func getBuilderFilePath(integrationDir string, build *buildbucketpb.Build) string {
+ // TODO(fxbug.dev//71232): switch to "builders" directory once all maintained release branches are newer.
+ return filepath.Join(
+ integrationDir, "infra", "config", "generated", build.Builder.Project,
+ "for_review_only", "buildbucket", build.Builder.Bucket,
+ fmt.Sprintf("%s.textproto", build.Builder.Builder),
+ )
+}
+
+// checkoutIntegration checks out the integration.git repo and returns the path
+// to the directory containing the checkout.
+func checkoutIntegration(ctx context.Context, build *buildbucketpb.Build) (string, error) {
// Do a checkout in a temporary directory to avoid checkout conflicts with
// the recipe's working directory.
cwd, err := os.Getwd()
if err != nil {
- return err
+ return "", err
}
- dir, err := ioutil.TempDir(cwd, "checkout")
+ integrationDir, err := ioutil.TempDir(cwd, "integration-checkout")
if err != nil {
- return err
+ return integrationDir, err
}
- defer os.RemoveAll(dir)
- if err := checkoutIntegration(ctx, dir, build); err != nil {
- return fmt.Errorf("failed to checkout integration bundle: %w", err)
- }
- if err := resolveRecipeVersion(dir, build); err != nil {
- return fmt.Errorf("failed to resolve recipe version: %w", err)
- }
- // Open builder-specific file.
- // TODO(fxbug.dev//71232): switch to "builders" directory once all maintained release branches are newer.
- builderFilePath := filepath.Join(
- dir, "infra", "config", "generated", build.Builder.Project,
- "for_review_only", "buildbucket", build.Builder.Bucket,
- fmt.Sprintf("%s.textproto", build.Builder.Builder),
- )
- if err := resolveBuildProperties(builderFilePath, build); err != nil {
- return fmt.Errorf("failed to resolve build properties: %w", err)
- }
- return nil
-}
-
-// checkoutIntegration checks out the integration.git repo in dir.
-func checkoutIntegration(ctx context.Context, dir string, build *buildbucketpb.Build) error {
integrationURL, integrationBaseRevision, err := resolveIntegration(ctx, build)
if err != nil {
- return err
+ return integrationDir, err
}
if err := props.SetInputProperty(build, "integration_base_revision", integrationBaseRevision); err != nil {
- return err
+ return integrationDir, err
}
tctx, cancel := context.WithTimeout(ctx, defaultCheckoutTimeout)
defer cancel()
- return checkout.Checkout(tctx, build.Input, *integrationURL, integrationBaseRevision, dir)
+ return integrationDir, checkout.Checkout(tctx, build.Input, *integrationURL, integrationBaseRevision, integrationDir)
}
// resolveIntegration resolves the integration URL and revision to checkout.
@@ -250,48 +238,10 @@
return integrationURL, revision, nil
}
-// resolveRecipeVersion resolves the recipe version to use.
-//
-// If Build.Input.GerritChanges indicates that the recipes repo is under test,
-// we short-circuit immediately.
-//
-// In all other cases, we ensure that the recipe version property is set in
-// Build.Input.Properties.
-func resolveRecipeVersion(dir string, build *buildbucketpb.Build) error {
- recipeVersion, err := props.GetStringInputProperty(build, "recipe_version")
- if err != nil {
- return err
- }
- // If the recipe version property is already set, we're done.
- if recipeVersion != "" {
- return nil
- }
- // If the recipes repo is under test, we're done. checkoutRecipes will use
- // the build input directly.
- hasRecipeChange, err := rbc.HasRepoChange(build.Input, recipesRemote)
- if err != nil {
- return fmt.Errorf("could not determine whether build input has recipe change: %w", err)
- }
- if hasRecipeChange {
- return nil
- }
- // Otherwise, resolve the recipe version from the checkout dir.
- manifestXML, err := os.Open(filepath.Join(dir, recipesManifest))
- if err != nil {
- return fmt.Errorf("failed to read recipes manifest file: %s: %v", filepath.Join(dir, recipesManifest), err)
- }
- defer manifestXML.Close()
- proj, err := manifest.ResolveRecipesProject(manifestXML, recipesRemote)
- if err != nil {
- return err
- }
- return props.SetInputProperty(build, "recipe_version", proj.Revision)
-}
-
// resolveBuildProperties resolves the build input properties to use.
//
-// If enable_property_versioning is set, we merge the build input properties
-// with intergration properties.
+// If enable_property_versioning is set, merge the build input properties with
+// integration properties.
func resolveBuildProperties(builderFilePath string, build *buildbucketpb.Build) error {
enablePropertyVersion, err := props.GetBoolInputProperty(build, "enable_property_versioning")
if err != nil {
@@ -339,46 +289,62 @@
return data, nil
}
-// checkoutRecipes checks out the recipes.git repo in dir.
+// checkoutRecipes resolves the recipe version to check out, checks out the recipes
+// repo, and returns the path to the checked-out repository.
//
// If the build input has a recipe change, then checkout directly using the
// build input.
//
// Otherwise, checkout at the revision specified by Build.Input.Properties.
-func checkoutRecipes(ctx context.Context, dir string, build *buildbucketpb.Build) error {
+func checkoutRecipes(ctx context.Context, integrationDir string, build *buildbucketpb.Build) (string, error) {
+ // Do recipes checkout in a temporary directory to avoid checkout conflicts
+ // with the recipe's working directory.
+ cwd, err := os.Getwd()
+ if err != nil {
+ return "", fmt.Errorf("failed to get working directory: %w", err)
+
+ }
+ recipesDir, err := ioutil.TempDir(cwd, "recipes-checkout")
+ if err != nil {
+ return recipesDir, fmt.Errorf("failed to get create tempdir: %w", err)
+
+ }
recipesURL, err := url.Parse(recipesRemote)
if err != nil {
- return fmt.Errorf("could not parse URL %s", recipesRemote)
+ return recipesDir, fmt.Errorf("could not parse URL %s", recipesRemote)
}
var input *buildbucketpb.Build_Input
hasRecipeChange, err := rbc.HasRepoChange(build.Input, recipesRemote)
if err != nil {
- return fmt.Errorf("could not determine whether build input has recipe change: %w", err)
+ return recipesDir, fmt.Errorf("could not determine whether build input has recipe change: %w", err)
}
if hasRecipeChange {
input = build.Input
} else {
- recipeVersion, err := props.GetStringInputProperty(build, "recipe_version")
+ // Use the recipe version from the checkout integrationDir.
+ manifestXML, err := os.Open(filepath.Join(integrationDir, recipesManifest))
if err != nil {
- return err
+ return recipesDir, fmt.Errorf("failed to read recipes manifest file: %s: %v", filepath.Join(integrationDir, recipesManifest), err)
}
- if recipeVersion == "" {
- return errors.New("recipe_version property is unexpectedly empty")
+ defer manifestXML.Close()
+ proj, err := manifest.ResolveRecipesProject(manifestXML, recipesRemote)
+ if err != nil {
+ return recipesDir, err
}
input = &buildbucketpb.Build_Input{
GitilesCommit: &buildbucketpb.GitilesCommit{
Host: recipesURL.Host,
Project: strings.TrimLeft(recipesURL.Path, "/"),
- Id: recipeVersion,
+ Id: proj.Revision,
},
}
}
tctx, cancel := context.WithTimeout(ctx, defaultCheckoutTimeout)
defer cancel()
- if err := checkout.Checkout(tctx, input, *recipesURL, "", dir); err != nil {
- return fmt.Errorf("failed to checkout: %w", err)
+ if err := checkout.Checkout(tctx, input, *recipesURL, "", recipesDir); err != nil {
+ return recipesDir, fmt.Errorf("failed to checkout: %w", err)
}
- return nil
+ return recipesDir, nil
}
// fetchRecipeDeps runs `recipes.py fetch`, which checks out all recipe git
@@ -387,8 +353,8 @@
// beforehand, but running `recipes.py fetch` separately makes it easier to
// distinguish between fetch errors (which are generally caused by transient Git
// server issues) and true build errors within the executed recipe.
-func fetchRecipeDeps(ctx context.Context, dir string) error {
- cmd, err := recipeCommand(ctx, dir, "fetch")
+func fetchRecipeDeps(ctx context.Context, recipesDir string) error {
+ cmd, err := recipeCommand(ctx, recipesDir, "fetch")
if err != nil {
return err
}
@@ -399,12 +365,12 @@
}
// executeRecipe exec's `recipes.py luciexe`, passing build.proto through stdin.
-func executeRecipe(ctx context.Context, dir string, build *buildbucketpb.Build, outputPath string) error {
+func executeRecipe(ctx context.Context, recipesDir string, build *buildbucketpb.Build, outputPath string) error {
args := []string{"luciexe"}
if outputPath != "" {
args = append(args, "--output", outputPath)
}
- cmd, err := recipeCommand(ctx, dir, args...)
+ cmd, err := recipeCommand(ctx, recipesDir, args...)
if err != nil {
return err
}
@@ -417,9 +383,9 @@
}
// recipeCommand constructs an exec.Cmd that will run the `recipes.py` script
-// found in `dir` with the specified arguments.
-func recipeCommand(ctx context.Context, dir string, recipesPyArgs ...string) (*exec.Cmd, error) {
- args := []string{filepath.Join(dir, "recipes.py")}
+// found in `recipesDir` with the specified arguments.
+func recipeCommand(ctx context.Context, recipesDir string, recipesPyArgs ...string) (*exec.Cmd, error) {
+ args := []string{filepath.Join(recipesDir, "recipes.py")}
args = append(args, recipesPyArgs...)
python, err := exec.LookPath("python")
if err != nil {
@@ -460,7 +426,7 @@
// to checkout, then checks out the recipes repo at that revision. It returns
// the deserialized input build.proto along with the path to the recipes
// checkout.
-func setUpRecipes(ctx context.Context) (build *buildbucketpb.Build, dir string, err error) {
+func setUpRecipes(ctx context.Context) (build *buildbucketpb.Build, recipesDir string, err error) {
data, err := ioutil.ReadAll(os.Stdin)
if err != nil {
err = fmt.Errorf("failed to read build.proto from stdin: %w", err)
@@ -475,26 +441,22 @@
err = fmt.Errorf("failed to ensure that build has a gitiles commit: %w", err)
return
}
- if err = resolveRecipeVersionAndProperties(ctx, build); err != nil {
- return
- }
- // Do recipes checkout in a temporary directory to avoid checkout conflicts
- // with the recipe's working directory.
- cwd, err := os.Getwd()
+ integrationDir, err := checkoutIntegration(ctx, build)
+ defer os.RemoveAll(integrationDir)
if err != nil {
- err = fmt.Errorf("failed to get working directory: %w", err)
+ err = fmt.Errorf("failed to checkout integration bundle: %w", err)
return
}
- dir, err = ioutil.TempDir(cwd, "recipes-checkout")
+ if err = resolveBuildProperties(getBuilderFilePath(integrationDir, build), build); err != nil {
+ err = fmt.Errorf("failed to resolve the build properties: %w", err)
+ return
+ }
+ recipesDir, err = checkoutRecipes(ctx, integrationDir, build)
if err != nil {
- err = fmt.Errorf("failed to get create tempdir: %w", err)
- return
- }
- if err = checkoutRecipes(ctx, dir, build); err != nil {
err = fmt.Errorf("failed to download recipe bundle: %w", err)
return
}
- if err = fetchRecipeDeps(ctx, dir); err != nil {
+ if err = fetchRecipeDeps(ctx, recipesDir); err != nil {
return
}
b, err := json.MarshalIndent(build.Input.Properties, "", " ")
@@ -506,24 +468,24 @@
// it's possible for recipe_bootstrap to emit build.proto to the same Logdog stream
// as the recipe engine.
logging.Infof(ctx, "resolved input properties: %s", string(b))
- return build, dir, nil
+ return build, recipesDir, nil
}
func execute(ctx context.Context) error {
outputPath := flag.String("output", "", "Path to write the final build.proto state to.")
flag.Parse()
- build, dir, err := setUpRecipes(ctx)
- // `dir` might not exist depending on the setup failure mode, so ignore any
+ build, recipesDir, err := setUpRecipes(ctx)
+ // `recipesDir` might not exist depending on the setup failure mode, so ignore any
// error when removing it.
- defer os.RemoveAll(dir)
+ defer os.RemoveAll(recipesDir)
if err != nil {
if outputErr := outputBuildSummary(ctx, err.Error()); outputErr != nil {
logging.Errorf(ctx, "Failed to output build summary after recipe setup failed: %v", outputErr)
}
return err
}
- if err := executeRecipe(ctx, dir, build, *outputPath); err != nil {
+ if err := executeRecipe(ctx, recipesDir, build, *outputPath); err != nil {
return fmt.Errorf("failed to execute recipe: %w", err)
}
return nil
diff --git a/cmd/recipe_bootstrap/main_test.go b/cmd/recipe_bootstrap/main_test.go
index 69d5f76..90e7d32 100644
--- a/cmd/recipe_bootstrap/main_test.go
+++ b/cmd/recipe_bootstrap/main_test.go
@@ -110,7 +110,7 @@
t.Fatalf("expected error, got nil")
}
if diff := cmp.Diff(test.expectProperties, build.Input.Properties.AsMap()); diff != "" {
- t.Errorf("resolveBuildProperties(): output differs (-want, +got):\n%s", diff)
+ t.Errorf("mergeProperties(): output differs (-want, +got):\n%s", diff)
}
})
}