| // Copyright 2021 The Chromium OS 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 implements the cros-publish used to upload artifacts to GCS |
| // bucket. |
| package main |
| |
| import ( |
| "context" |
| "flag" |
| "fmt" |
| "io" |
| "log" |
| "os" |
| "path/filepath" |
| "strings" |
| "time" |
| |
| "chromiumos/test/publish/cmd/publishserver" |
| "go.chromium.org/luci/common/errors" |
| ) |
| |
| const ( |
| // version is the version info of this command. It is filled in during emerge. |
| version = "<unknown>" |
| helpDescription = `cros-publish tool |
| |
| The tool allows to upload test result artifacts to GCS buckets for testing |
| needs. Please read go/cros-upload-to-gs-tko-design-proposal for mode details. |
| |
| Commands: |
| cli Running publish server in CLI mode result will be printed to output |
| file. |
| usage: cros-publish cli -service_account_creds service_account_file |
| |
| server Starting server and allow work with server by RPC calls. Mostly |
| used for tests. |
| usage: cros-publish server -service_account_creds service_account_file [-port 80] |
| |
| -version Print version of lib. |
| -help Print this help.` |
| defaultLogDirectory = "/tmp/publish/" |
| defaultPort = 80 |
| ) |
| |
| // createLogFile creates a file and its parent directory for logging purpose. |
| func createLogFile(logPath string) (*os.File, error) { |
| t := time.Now() |
| fullPath := filepath.Join(logPath, t.Format("20060102-150405")) |
| if err := os.MkdirAll(fullPath, 0755); err != nil { |
| return nil, fmt.Errorf("failed to create directory %v: %v", fullPath, err) |
| } |
| |
| logFullPathName := filepath.Join(fullPath, "log.txt") |
| |
| // Log the full output of the command to disk. |
| logFile, err := os.Create(logFullPathName) |
| if err != nil { |
| return nil, fmt.Errorf("failed to create file %v: %v", fullPath, err) |
| } |
| return logFile, nil |
| } |
| |
| // newLogger creates a logger. Using go default logger for now. |
| func newLogger(logFile *os.File) *log.Logger { |
| mw := io.MultiWriter(logFile, os.Stderr) |
| return log.New(mw, "", log.LstdFlags|log.LUTC) |
| } |
| |
| type args struct { |
| // Common input params. |
| // Local log file path. |
| logPath string |
| // Local directory that will be uploaded. |
| localDir string |
| // GCS bucket path where the local directory will be uploaded to. |
| gsDir string |
| // Service account file containing gcp credentials. |
| serviceAccountCreds string |
| // Output log file. |
| outputPath string |
| |
| // Server mode params |
| port int |
| } |
| |
| func validate(a args) error { |
| if a.serviceAccountCreds == "" { |
| return errors.Reason("Service account file not specified").Err() |
| } |
| |
| _, err := os.Open(a.serviceAccountCreds) |
| if err != nil { |
| return errors.Reason("Failed to read service account file").Err() |
| } |
| return nil |
| } |
| |
| func runCLI(ctx context.Context, d []string) int { |
| a := args{} |
| fs := flag.NewFlagSet("Start publish publishService", flag.ExitOnError) |
| fs.StringVar(&a.logPath, "log_path", defaultLogDirectory, fmt.Sprintf("Path to record execution logs. Default value is %s", defaultLogDirectory)) |
| fs.StringVar(&a.localDir, "local_dir", "", "path to the local directory that will be uploaded") |
| fs.StringVar(&a.gsDir, "gs_dir", "", "path to the GCS bucket where the local directory will be uploaded to") |
| fs.StringVar(&a.serviceAccountCreds, "service_account_creds", "", "path to service account file containing gcp credentials") |
| fs.StringVar(&a.outputPath, "output", "", "path to the response jsonproto output file.") |
| fs.Parse(d) |
| |
| logFile, err := createLogFile(a.logPath) |
| if err != nil { |
| log.Fatalln("Failed to create log file", err) |
| return 2 |
| } |
| defer logFile.Close() |
| |
| logger := newLogger(logFile) |
| if err := validate(a); err != nil { |
| log.Fatalf("Validate arguments fail: %s", err) |
| return 2 |
| } |
| |
| publishService, destructor, err := publishserver.NewPublishService(ctx, a.serviceAccountCreds, logger) |
| defer destructor() |
| if err != nil { |
| logger.Fatalln("Failed to create publish: ", err) |
| return 2 |
| } |
| |
| if err := publishService.RunCli(ctx, a.localDir, a.gsDir, a.outputPath); err != nil { |
| logger.Fatalln("Failed to perform publish: ", err) |
| return 1 |
| } |
| return 0 |
| } |
| |
| func startServer(ctx context.Context, d []string) int { |
| a := args{} |
| fs := flag.NewFlagSet("Start publish publishService", flag.ExitOnError) |
| fs.StringVar(&a.logPath, "log_path", defaultLogDirectory, fmt.Sprintf("Path to record execution logs. Default value is %s", defaultLogDirectory)) |
| fs.StringVar(&a.serviceAccountCreds, "service_account_creds", "", "path to service account file containing gcp credentials") |
| fs.IntVar(&a.port, "port", defaultPort, fmt.Sprintf("Specify the port for the publishService. Default value %d.", defaultPort)) |
| fs.Parse(d) |
| |
| logFile, err := createLogFile(a.logPath) |
| if err != nil { |
| log.Fatalln("Failed to create log file", err) |
| return 2 |
| } |
| defer logFile.Close() |
| |
| logger := newLogger(logFile) |
| if err := validate(a); err != nil { |
| log.Fatalf("Validate arguments fail: %s", err) |
| return 2 |
| } |
| |
| publishService, destructor, err := publishserver.NewPublishService(ctx, a.serviceAccountCreds, logger) |
| defer destructor() |
| if err != nil { |
| logger.Fatalln("Failed to create publish: ", err) |
| return 2 |
| } |
| |
| if err := publishService.StartServer(a.port); err != nil { |
| logger.Fatalln("Failed to perform publish: ", err) |
| return 1 |
| } |
| return 0 |
| } |
| |
| // Specify run mode for cros-publish. |
| type runMode string |
| |
| const ( |
| runCli runMode = "cli" |
| runServer runMode = "server" |
| runVersion runMode = "version" |
| runHelp runMode = "help" |
| ) |
| |
| func getRunMode() (runMode, error) { |
| if len(os.Args) > 1 { |
| for _, a := range os.Args { |
| if a == "-version" { |
| return runVersion, nil |
| } |
| } |
| switch strings.ToLower(os.Args[1]) { |
| case "cli": |
| return runCli, nil |
| case "server": |
| return runServer, nil |
| } |
| } |
| // If we did not find special run mode then just print help for user. |
| return runHelp, nil |
| } |
| |
| func mainInternal() int { |
| rm, err := getRunMode() |
| if err != nil { |
| log.Fatalln(err) |
| return 2 |
| } |
| |
| ctx := context.Background() |
| switch rm { |
| case runCli: |
| log.Printf("Running CLI mode!") |
| return runCLI(ctx, os.Args[2:]) |
| case runServer: |
| log.Printf("Running server mode!") |
| return startServer(ctx, os.Args[2:]) |
| case runVersion: |
| log.Printf("cros-publish version: %s", version) |
| return 0 |
| } |
| |
| log.Printf(helpDescription) |
| return 0 |
| } |
| |
| func main() { |
| os.Exit(mainInternal()) |
| } |