blob: d2bd414da7cc425507dc59052a8baf5518b279e1 [file] [log] [blame]
// 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())
}