go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/logdog/client/butlerlib/bootstrap/bootstrap.go (about)

     1  // Copyright 2015 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 bootstrap
    16  
    17  import (
    18  	"fmt"
    19  
    20  	"go.chromium.org/luci/common/errors"
    21  	"go.chromium.org/luci/common/system/environ"
    22  	"go.chromium.org/luci/config"
    23  	"go.chromium.org/luci/logdog/client/butlerlib/streamclient"
    24  	"go.chromium.org/luci/logdog/common/types"
    25  	"go.chromium.org/luci/logdog/common/viewer"
    26  )
    27  
    28  // ErrNotBootstrapped is returned by Get when the current process is not
    29  // bootstrapped.
    30  var ErrNotBootstrapped = errors.New("not bootstrapped")
    31  
    32  // Bootstrap contains information about the configured bootstrap environment.
    33  //
    34  // The bootstrap environment is loaded by probing the local application
    35  // environment for variables emitted by a bootstrapping Butler.
    36  type Bootstrap struct {
    37  	// CoordinatorHost is the name of the upstream Coordinator host.
    38  	//
    39  	// This is just the host name ("example.appspot.com"), not a full URL.
    40  	//
    41  	// If this instance is not configured using a production Coordinator Output,
    42  	// this will be empty.
    43  	CoordinatorHost string
    44  
    45  	// Project is the Butler instance project name.
    46  	Project string
    47  	// Prefix is the Butler instance prefix.
    48  	Prefix types.StreamName
    49  	// Namespace is prefix for stream names.
    50  	Namespace types.StreamName
    51  
    52  	// Client is the streamclient for this instance, or nil if the Butler has no
    53  	// streamserver.
    54  	Client *streamclient.Client
    55  }
    56  
    57  // GetFromEnv loads a Bootstrap instance from the given environment.
    58  //
    59  // It will return an error if the bootstrap data is invalid, and will return
    60  // ErrNotBootstrapped if the current process is not bootstrapped.
    61  func GetFromEnv(env environ.Env) (*Bootstrap, error) {
    62  	// Detect Butler by looking for EnvStreamServerPath in the envrironent. This
    63  	// is the only environment variable which matters for constructing a Butler
    64  	// Client; all the rest are just needed to assemble viewer URLs.
    65  	butlerSocket, ok := env.Lookup(EnvStreamServerPath)
    66  	if !ok {
    67  		return nil, ErrNotBootstrapped
    68  	}
    69  
    70  	bs := &Bootstrap{
    71  		CoordinatorHost: env.Get(EnvCoordinatorHost),
    72  		Prefix:          types.StreamName(env.Get(EnvStreamPrefix)),
    73  		Namespace:       types.StreamName(env.Get(EnvNamespace)),
    74  		Project:         env.Get(EnvStreamProject),
    75  	}
    76  	if err := bs.initializeClient(butlerSocket); err != nil {
    77  		return nil, fmt.Errorf("bootstrap: failed to create stream client [%s]: %s", butlerSocket, err)
    78  	}
    79  
    80  	if len(bs.Prefix) > 0 {
    81  		if err := bs.Prefix.Validate(); err != nil {
    82  			return nil, fmt.Errorf("bootstrap: failed to validate prefix %q: %s", bs.Prefix, err)
    83  		}
    84  	}
    85  	if len(bs.Project) > 0 {
    86  		if err := config.ValidateProjectName(bs.Project); err != nil {
    87  			return nil, fmt.Errorf("bootstrap: failed to validate project %q: %s", bs.Project, err)
    88  		}
    89  	}
    90  	if len(bs.Namespace) > 0 {
    91  		if err := bs.Namespace.Validate(); err != nil {
    92  			return nil, fmt.Errorf("bootstrap: failed to validate namespace %q: %s", bs.Namespace, err)
    93  		}
    94  	}
    95  
    96  	return bs, nil
    97  }
    98  
    99  func (bs *Bootstrap) initializeClient(v string) error {
   100  	c, err := streamclient.New(v, bs.Namespace)
   101  	if err != nil {
   102  		return errors.Annotate(err, "bootstrap: failed to create stream client [%s]", v).Err()
   103  	}
   104  	bs.Client = c
   105  	return nil
   106  }
   107  
   108  // Get is shorthand for `GetFromEnv(environ.System())`.
   109  func Get() (*Bootstrap, error) {
   110  	return GetFromEnv(environ.System())
   111  }
   112  
   113  // GetViewerURL returns a log stream viewer URL to the aggregate set of supplied
   114  // stream paths.
   115  //
   116  // If both the Project and CoordinatorHost values are not populated, an error
   117  // will be returned.
   118  func (bs *Bootstrap) GetViewerURL(paths ...types.StreamPath) (string, error) {
   119  	if bs.Project == "" {
   120  		return "", errors.New("no project is configured")
   121  	}
   122  	if bs.CoordinatorHost == "" {
   123  		return "", errors.New("no coordinator host is configured")
   124  	}
   125  	return viewer.GetURL(bs.CoordinatorHost, bs.Project, paths...), nil
   126  }