go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/gce/cmd/agent/main.go (about)

     1  // Copyright 2018 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 main contains an agent for connecting to a Swarming server.
    16  package main
    17  
    18  import (
    19  	"bytes"
    20  	"context"
    21  	"fmt"
    22  	"os"
    23  	"text/template"
    24  
    25  	"cloud.google.com/go/compute/metadata"
    26  	"github.com/maruel/subcommands"
    27  
    28  	"go.chromium.org/luci/auth"
    29  	"go.chromium.org/luci/auth/client/authcli"
    30  	"go.chromium.org/luci/cipd/version"
    31  	"go.chromium.org/luci/common/cli"
    32  	"go.chromium.org/luci/common/logging"
    33  	"go.chromium.org/luci/common/logging/gologger"
    34  	"go.chromium.org/luci/grpc/prpc"
    35  	"go.chromium.org/luci/hardcoded/chromeinfra"
    36  
    37  	"go.chromium.org/luci/gce/api/instances/v1"
    38  	"go.chromium.org/luci/gce/vmtoken/client"
    39  )
    40  
    41  // substitute performs substitutions in a template string.
    42  func substitute(c context.Context, s string, subs any) (string, error) {
    43  	t, err := template.New("tmpl").Parse(s)
    44  	if err != nil {
    45  		return "", err
    46  	}
    47  	buf := bytes.Buffer{}
    48  	if err = t.Execute(&buf, subs); err != nil {
    49  		return "", nil
    50  	}
    51  	return buf.String(), nil
    52  }
    53  
    54  // metaKey is the key to a *metadata.Client in the context.
    55  var metaKey = "meta"
    56  
    57  // withMetadata returns a new context with the given *metadata.Client installed.
    58  func withMetadata(c context.Context, cli *metadata.Client) context.Context {
    59  	return context.WithValue(c, &metaKey, cli)
    60  }
    61  
    62  // getMetadata returns the *metadata.Client installed in the current context.
    63  func getMetadata(c context.Context) *metadata.Client {
    64  	return c.Value(&metaKey).(*metadata.Client)
    65  }
    66  
    67  // newInstances returns a new instances.InstancesClient.
    68  func newInstances(c context.Context, acc, host string) (instances.InstancesClient, error) {
    69  	info, err := version.GetCurrentVersion()
    70  	if err != nil {
    71  		return nil, err
    72  	}
    73  	options := prpc.DefaultOptions()
    74  	options.UserAgent = fmt.Sprintf("gce-agent, instanceID=%q", info.InstanceID)
    75  	return instances.NewInstancesPRPCClient(&prpc.Client{
    76  		C:       client.NewClient(getMetadata(c), acc),
    77  		Host:    host,
    78  		Options: options,
    79  	}), nil
    80  }
    81  
    82  // cmdRunBase is the base struct all subcommands should embed.
    83  // Implements cli.ContextModificator.
    84  type cmdRunBase struct {
    85  	subcommands.CommandRunBase
    86  	authFlags      authcli.Flags
    87  	serviceAccount string
    88  }
    89  
    90  // Initialize registers common flags.
    91  func (b *cmdRunBase) Initialize() {
    92  	opts := chromeinfra.DefaultAuthOptions()
    93  	b.authFlags.Register(b.GetFlags(), opts)
    94  }
    95  
    96  // ModifyContext returns a new context to be used by all commands. Implements
    97  // cli.ContextModificator.
    98  func (b *cmdRunBase) ModifyContext(c context.Context) context.Context {
    99  	c = logging.SetLevel(gologger.StdConfig.Use(c), logging.Debug)
   100  	opts, err := b.authFlags.Options()
   101  	if err != nil {
   102  		logging.Errorf(c, "%s", err.Error())
   103  		panic("failed to get auth options")
   104  	}
   105  	b.serviceAccount = opts.GCEAccountName
   106  	http, err := auth.NewAuthenticator(c, auth.OptionalLogin, opts).Client()
   107  	if err != nil {
   108  		logging.Errorf(c, "%s", err.Error())
   109  		panic("failed to get authenticator")
   110  	}
   111  	meta := metadata.NewClient(http)
   112  	swr := &SwarmingClient{
   113  		Client:           http,
   114  		PlatformStrategy: newStrategy(),
   115  	}
   116  	return withSwarming(withMetadata(c, meta), swr)
   117  }
   118  
   119  // New returns a new agent application.
   120  func New() *cli.Application {
   121  	return &cli.Application{
   122  		Name:  "agent",
   123  		Title: "GCE agent",
   124  		Commands: []*subcommands.Command{
   125  			subcommands.CmdHelp,
   126  			newConnectCmd(),
   127  		},
   128  	}
   129  }
   130  
   131  func main() {
   132  	os.Exit(subcommands.Run(New(), os.Args[1:]))
   133  }