blob: decb6d834cfd1c0227379a67df13f06a69591a3d [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Package main reads a recipe spec by creating a checkout of the specified repository,
// according to the current build input. Clients should run this in the directory where
// they want the repository to be checked out. The spec is printed to stdout on success.
// On failure, a non-zero exit code is returned.
//
// This MUST be called after the input Buildbucket build has been resolved to contain a
// Gitiles commit. The build.proto object written to stdin must be base64 encoded to avoid
// logging invalid UTF-8 characters which breaks logdog.
//
// The exit codes of this program are as follows:
// 0: Ok
// 1: Unexpected fatal error
// 2: The spec file was not found
package main
import (
"context"
"encoding/base64"
"errors"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"net/url"
"os"
"github.com/golang/protobuf/proto"
"fuchsia.googlesource.com/infra/infra/cmd/build_init/checkout"
buildbucketpb "go.chromium.org/luci/buildbucket/proto"
)
const (
// The directory containing specs in the spec repository.
defaultSpecDir = "infra/config/generated"
defaultGitPath = "/usr/bin/git"
)
// Exit codes
const (
ExitOk = 0
ExitFatal = 1
ExitFileNotFound = 2
)
// Command line flags.
var (
// The URL of the spec repository.
specRemote string
// The directory containing specs in the spec repository.
specDir string
)
// Variables derived from user input.
var (
// The URL to the spec repository.
specRepoURL *url.URL
// The build.proto object read from stdin.
build *buildbucketpb.Build
)
func init() {
// Required
flag.StringVar(&specRemote, "spec_remote", "", "The URL of the spec repository")
// Optional
flag.StringVar(&specDir, "spec_dir", defaultSpecDir, "The directory containing specs")
}
func main() {
flag.Parse()
if err := validateInputs(); err != nil {
log.Fatal(err)
}
os.Exit(execute(context.Background()))
}
func validateInputs() (err error) {
if specRemote == "" {
return errors.New("missing -spec_remote")
}
specRepoURL, err = url.Parse(specRemote)
if err != nil {
return fmt.Errorf("invalid URL for -spec_remote: %v", err)
}
build, err = loadBuildProto(os.Stdin)
if err != nil {
return fmt.Errorf("failed to read build.proto: %v", err)
}
if err := validateBuild(*build); err != nil {
return fmt.Errorf("invalid build message: %v", err)
}
return err
}
func validateBuild(build buildbucketpb.Build) error {
if build.Input == nil {
return errors.New("build has no input")
}
if build.Input.GitilesCommit == nil {
return errors.New("build input has no Gitiles commit")
}
if build.Builder == nil {
return errors.New("input build has no builder information")
}
if build.Builder.Bucket == "" {
return errors.New("input build's builder has no bucket")
}
if build.Builder.Builder == "" {
return errors.New("input build's builder name is empty")
}
return nil
}
// Execute runs the program. Public for testing.
func execute(ctx context.Context) (code int) {
if err := checkout.Checkout(*build.Input, *specRepoURL); err != nil {
log.Printf("failed to checkout %s: %v", specRepoURL.String(), err)
return ExitFatal
}
specPath := fmt.Sprintf("%s/%s/%s", specDir, build.Builder.Bucket, build.Builder.Builder)
if _, err := os.Stat(specPath); os.IsNotExist(err) {
log.Printf("file not found %v", specPath)
return ExitFileNotFound
}
fd, err := os.Open(specPath)
if err != nil {
log.Printf("failed to open file %v: %v", specPath, err)
return ExitFatal
}
defer fd.Close()
if _, err := io.Copy(os.Stdout, fd); err != nil {
log.Printf("failed to write file to output: %v", err)
return ExitFatal
}
return ExitOk
}
// Loads a buildbucketpb.Build message from the given reader. The input must be base64.
func loadBuildProto(r io.Reader) (*buildbucketpb.Build, error) {
build := new(buildbucketpb.Build)
decoder := base64.NewDecoder(base64.StdEncoding, r)
bytes, err := ioutil.ReadAll(decoder)
if err != nil {
return nil, fmt.Errorf("failed to read build.proto: %v", err)
}
if err := proto.Unmarshal(bytes, build); err != nil {
return nil, fmt.Errorf("failed to unmarshal build.proto: %v", err)
}
return build, nil
}