golang.org/x/build@v0.0.0-20240506185731-218518f32b70/internal/swarmclient/config.go (about) 1 // Copyright 2023 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package swarmclient 6 7 import ( 8 "context" 9 "errors" 10 "fmt" 11 "net/http" 12 13 bbpb "go.chromium.org/luci/buildbucket/proto" 14 luciconfig "go.chromium.org/luci/config" 15 "go.chromium.org/luci/config/cfgclient" 16 "go.chromium.org/luci/config/impl/memory" 17 "google.golang.org/grpc/credentials" 18 "google.golang.org/protobuf/encoding/prototext" 19 ) 20 21 // ConfigClient is a client for the LUCI configuration service. 22 type ConfigClient struct { 23 config luciconfig.Interface 24 } 25 26 // NewConfigClient creates a client to access the LUCI configuration service. 27 func NewConfigClient(ctx context.Context) (*ConfigClient, error) { 28 cc, err := cfgclient.New(ctx, cfgclient.Options{ 29 ServiceHost: "luci-config.appspot.com", 30 ClientFactory: func(context.Context) (*http.Client, error) { 31 return http.DefaultClient, nil 32 }, 33 GetPerRPCCredsFn: func(context.Context) (credentials.PerRPCCredentials, error) { 34 return nil, errors.New("GetPerRPCCredsFn unimplemented") 35 }, 36 }) 37 if err != nil { 38 return nil, fmt.Errorf("cfgclient.New() = nil, %w", err) 39 } 40 return &ConfigClient{config: cc}, nil 41 } 42 43 // ConfigEntry represents a configuration file in the configuration directory. It 44 // should only be used for testing. 45 type ConfigEntry struct { 46 Filename string // name of the file. 47 Contents []byte // contents of the configuration file. 48 } 49 50 // NewMemoryConfigClient creates a config client where the configuration files are stored in memory. 51 // See https://go.chromium.org/luci/config/impl/filesystem for the expected directory layout. 52 // This should only be used while testing. 53 func NewMemoryConfigClient(ctx context.Context, files []*ConfigEntry) *ConfigClient { 54 f := make(map[string]string) 55 for _, entry := range files { 56 f[entry.Filename] = string(entry.Contents) 57 } 58 cc := memory.New(map[luciconfig.Set]memory.Files{ 59 luciconfig.Set("projects/golang"): f, 60 }) 61 return &ConfigClient{config: cc} 62 } 63 64 // SwarmingBot contains the metadata for a LUCI swarming bot. 65 type SwarmingBot struct { 66 // BucketName is the name of the bucket the builder is defined in. 67 BucketName string 68 // Dimensions contains attributes about the builder. Form is in 69 // <key>:<value> or <time>:<key>:<value> 70 Dimensions []string 71 // Host is the hostname of the swarming instance. 72 Host string 73 // Name of the builder. 74 Name string 75 } 76 77 // ListSwarmingBots lists all of the swarming bots in the golang project defined in the 78 // cr-buildbucket.cfg configuration file. 79 func (cc *ConfigClient) ListSwarmingBots(ctx context.Context) ([]*SwarmingBot, error) { 80 bb, err := cc.config.GetConfig(ctx, luciconfig.Set("projects/golang"), "cr-buildbucket.cfg", false) 81 if err != nil { 82 return nil, fmt.Errorf("client.GetConfig() = nil, %s", err) 83 } 84 bbc := &bbpb.BuildbucketCfg{} 85 if err := prototext.Unmarshal([]byte(bb.Content), bbc); err != nil { 86 return nil, fmt.Errorf("prototext.Unmarshal() = %w", err) 87 } 88 var bots []*SwarmingBot 89 for _, bucket := range bbc.GetBuckets() { 90 for _, builder := range bucket.GetSwarming().GetBuilders() { 91 bots = append(bots, &SwarmingBot{ 92 BucketName: bucket.GetName(), 93 Dimensions: builder.GetDimensions(), 94 Host: builder.GetSwarmingHost(), 95 Name: builder.GetName(), 96 }) 97 } 98 } 99 return bots, nil 100 }