go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/luciexe/host/options.go (about)

     1  // Copyright 2019 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package host
    16  
    17  import (
    18  	"io/ioutil"
    19  	"os"
    20  	"path/filepath"
    21  	"runtime"
    22  
    23  	"go.chromium.org/luci/auth"
    24  	"go.chromium.org/luci/auth/authctx"
    25  	bbpb "go.chromium.org/luci/buildbucket/proto"
    26  	"go.chromium.org/luci/common/errors"
    27  	"go.chromium.org/luci/common/logging"
    28  	"go.chromium.org/luci/hardcoded/chromeinfra"
    29  	ldOutput "go.chromium.org/luci/logdog/client/butler/output"
    30  	"go.chromium.org/luci/logdog/client/butler/output/null"
    31  	"go.chromium.org/luci/logdog/client/butlerlib/streamproto"
    32  	"go.chromium.org/luci/logdog/common/viewer"
    33  )
    34  
    35  // Options is an optional struct which allows you to control how Run operates.
    36  type Options struct {
    37  	// Where the butler will sink its data to.
    38  	//
    39  	// This is typically one of the implementations in
    40  	// go.chromium.org/luci/logdog/client/butler/output.
    41  	//
    42  	// If nil, will use the 'null' logdog Output.
    43  	LogdogOutput ldOutput.Output
    44  
    45  	// Butler is VERY noisy at debug level, potentially amplifying client writes
    46  	// by up to two orders of magnitude.
    47  	//
    48  	// If this is higher than the log level in the context, this will be applied
    49  	// to the butler agent.
    50  	ButlerLogLevel logging.Level
    51  
    52  	// If set, enables logging at context level for the butler streamserver.
    53  	// If unset (the default), logging in the butler streamserver is set to
    54  	//   Warning.
    55  	//
    56  	// Streamsever logging is generally redundant with the butler logs at level
    57  	// Info or Debug.
    58  	StreamServerDisableLogAdjustment bool
    59  
    60  	// ExeAuth describes the LUCI Auth environment to run the user code within.
    61  	//
    62  	// `Run` will manage the lifecycle of ExeAuth entirely.
    63  	//
    64  	// If nil, defaults to `DefaultExeAuth("luciexe", nil)`.
    65  	//
    66  	// It's recommended to use DefaultExeAuth() explicitly with reasonable values
    67  	// for `id` and `knownGerritHosts`.
    68  	ExeAuth *authctx.Context
    69  
    70  	// The BaseDir becomes the root of this hosted luciexe session; All
    71  	// directories (workdirs, tempdirs, etc.) are derived relative to this.
    72  	//
    73  	// If not provided, Run will pick a random directory under os.TempDir as the
    74  	// value for BaseDir.
    75  	//
    76  	// The BaseDir (provided or picked) will be managed by Run; Prior to
    77  	// execution, Run will ensure that the directory is empty by removing its
    78  	// contents.
    79  	BaseDir string
    80  
    81  	// The base Build message to use as the template for all merged Build
    82  	// messages.
    83  	//
    84  	// This will add logdog tags based on Build.Builder to all log streams.
    85  	// e.g., "buildbucket.bucket"
    86  	BaseBuild *bbpb.Build
    87  
    88  	// If LeakBaseDir is true, Run will not try to remove BaseDir at the end if
    89  	// it's execution.
    90  	//
    91  	// If BaseDir is not provided, this must be false.
    92  	LeakBaseDir bool
    93  
    94  	// The viewer URL for this hosted execution (if any). This will be used to
    95  	// apply viewer.LogdogViewerURLTag to all logdog streams (the tag which is
    96  	// used to implement the "Back to build" link in Milo).
    97  	ViewerURL string
    98  
    99  	authDir          string
   100  	lucictxDir       string
   101  	streamServerPath string
   102  
   103  	logdogTags streamproto.TagMap
   104  }
   105  
   106  // The function below is in `var name = func` form so that it shows up in godoc.
   107  
   108  // DefaultExeAuth returns a copy of the default value for Options.ExeAuth.
   109  var DefaultExeAuth = func(id string, knownGerritHosts []string) *authctx.Context {
   110  	return &authctx.Context{
   111  		ID: id,
   112  		Options: chromeinfra.SetDefaultAuthOptions(auth.Options{
   113  			Scopes: []string{
   114  				"https://www.googleapis.com/auth/cloud-platform",
   115  				"https://www.googleapis.com/auth/userinfo.email",
   116  				"https://www.googleapis.com/auth/gerritcodereview",
   117  				"https://www.googleapis.com/auth/firebase",
   118  			},
   119  		}),
   120  		EnableGitAuth:      true,
   121  		EnableGCEEmulation: true,
   122  		EnableDockerAuth:   true,
   123  		EnableFirebaseAuth: true,
   124  		KnownGerritHosts:   knownGerritHosts,
   125  	}
   126  }
   127  
   128  type pathToMake struct {
   129  	path string  // unique directory name under BaseDir
   130  	name string  // human readable name of this dir (i.e. it's purpose)
   131  	dest *string // output destination in Options struct
   132  }
   133  
   134  func (p pathToMake) create(base string) error {
   135  	*p.dest = filepath.Join(base, p.path)
   136  	return errors.Annotate(os.Mkdir(*p.dest, 0777), "making %q dir", p.name).Err()
   137  }
   138  
   139  func (o *Options) initialize() (err error) {
   140  	if o.BaseDir == "" {
   141  		if o.LeakBaseDir {
   142  			return errors.New("BaseDir was provided but LeakBaseDir == true")
   143  		}
   144  		o.BaseDir, err = ioutil.TempDir("", "luciexe-host-")
   145  		if err != nil {
   146  			return errors.Annotate(err, "Cannot create BaseDir").Err()
   147  		}
   148  	} else {
   149  		if o.BaseDir, err = filepath.Abs(o.BaseDir); err != nil {
   150  			return errors.Annotate(err, "resolving BaseDir").Err()
   151  		}
   152  		if err := os.RemoveAll(o.BaseDir); err != nil && !os.IsNotExist(err) {
   153  			return errors.Annotate(err, "clearing options.BaseDir").Err()
   154  		}
   155  		if err := os.Mkdir(o.BaseDir, 0777); err != nil {
   156  			return errors.Annotate(err, "creating options.BaseDir").Err()
   157  		}
   158  	}
   159  
   160  	if o.BaseBuild == nil {
   161  		o.BaseBuild = &bbpb.Build{}
   162  	}
   163  
   164  	pathsToMake := []pathToMake{
   165  		{"a", "auth", &o.authDir},
   166  		{"l", "luci context", &o.lucictxDir},
   167  	}
   168  
   169  	if runtime.GOOS != "windows" {
   170  		pathsToMake = append(
   171  			pathsToMake, pathToMake{"ld", "logdog socket", &o.streamServerPath})
   172  	} else {
   173  		o.streamServerPath = "luciexe/host"
   174  	}
   175  
   176  	merr := errors.NewLazyMultiError(len(pathsToMake))
   177  	for i, paths := range pathsToMake {
   178  		merr.Assign(i, paths.create(o.BaseDir))
   179  	}
   180  	if err := merr.Get(); err != nil {
   181  		return err
   182  	}
   183  
   184  	if runtime.GOOS != "windows" {
   185  		tFile, err := ioutil.TempFile(o.streamServerPath, "sock.")
   186  		if err != nil {
   187  			return errors.Annotate(err, "creating tempfile").Err()
   188  		}
   189  		o.streamServerPath = tFile.Name()
   190  		if err := tFile.Close(); err != nil {
   191  			return errors.Annotate(err, "closing tempfile %q", o.streamServerPath).Err()
   192  		}
   193  	}
   194  
   195  	if o.LogdogOutput == nil {
   196  		o.LogdogOutput = &null.Output{}
   197  	}
   198  
   199  	if o.ExeAuth == nil {
   200  		o.ExeAuth = DefaultExeAuth("luciexe", nil)
   201  	}
   202  
   203  	if o.ViewerURL != "" {
   204  		o.logdogTags = streamproto.TagMap{viewer.LogDogViewerURLTag: o.ViewerURL}
   205  	}
   206  
   207  	// BaseBuild is nil in testing scenarios only.
   208  	if builder := o.BaseBuild.GetBuilder(); builder != nil {
   209  		if o.logdogTags == nil {
   210  			o.logdogTags = streamproto.TagMap{}
   211  		}
   212  		o.logdogTags["buildbucket.bucket"] = builder.Bucket
   213  		o.logdogTags["buildbucket.builder"] = builder.Builder
   214  		o.logdogTags["buildbucket.project"] = builder.Project
   215  	}
   216  
   217  	return nil
   218  }