aboutsummaryrefslogtreecommitdiff
path: root/ui/build/cipd.go
diff options
context:
space:
mode:
Diffstat (limited to 'ui/build/cipd.go')
-rw-r--r--ui/build/cipd.go169
1 files changed, 169 insertions, 0 deletions
diff --git a/ui/build/cipd.go b/ui/build/cipd.go
new file mode 100644
index 000000000..ba7ca3190
--- /dev/null
+++ b/ui/build/cipd.go
@@ -0,0 +1,169 @@
+// Copyright 2025 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package build
+
+import (
+ "bufio"
+ "errors"
+ "fmt"
+ "io"
+ "log"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+ "sync"
+ "sync/atomic"
+)
+
+const (
+ cipdProxyPolicyPath = "build/soong/ui/build/cipd_proxy_policy.txtpb"
+ cipdProxyUrlKey = "CIPD_PROXY_URL"
+)
+
+type cipdProxy struct {
+ cmd *Cmd
+ wg sync.WaitGroup
+ stopping atomic.Bool
+}
+
+func cipdPath(config Config) string {
+ return filepath.Join("prebuilts/cipd", config.HostPrebuiltTag(), "cipd")
+}
+
+func shouldRunCIPDProxy(config Config) bool {
+ cipdPath := cipdPath(config)
+ _, err := os.Stat(cipdPath)
+ return err == nil
+}
+
+func startCIPDProxyServer(ctx Context, config Config) *cipdProxy {
+ ctx.Status.Status("Starting CIPD proxy server...")
+
+ cipdArgs := []string{"proxy", "-proxy-policy", cipdProxyPolicyPath}
+ adcFlagAdded := false
+
+ if config.UseRBE() {
+ // Determine RBE authentication mechanism and propagate to CIPD flags.
+ authType, authValue := config.rbeAuth()
+ switch authType {
+ case "RBE_credential_file":
+ cipdArgs = append(cipdArgs, "-service-account-json", authValue)
+ case "RBE_credentials_helper", "RBE_use_google_prod_creds":
+ helperPath := filepath.Join(config.rbeDir(), "credshelper")
+
+ var credHelperArgsParts []string
+ // RBE_credentials_helper_args contains space-separated arguments for the helper
+ // and need to be formatted as repeated 'args:"..."' for the -credential-helper spec.
+ // e.g. "--f=foo --b=bar" -> 'args:"--f=foo" args:"--b=bar"'.
+ if rbeArgsStr, ok := config.environ.Get("RBE_credentials_helper_args"); ok && rbeArgsStr != "" {
+ argList := strings.Fields(rbeArgsStr)
+ for _, arg := range argList {
+ credHelperArgsParts = append(credHelperArgsParts, fmt.Sprintf("args:%q", arg))
+ }
+ } else {
+ credHelperArgsParts = append(credHelperArgsParts, fmt.Sprintf("args:%q", "--auth_source=automaticAuth"))
+ credHelperArgsParts = append(credHelperArgsParts, fmt.Sprintf("args:%q", "--gcert_refresh_timeout=20"))
+ }
+ helperSpec := fmt.Sprintf("protocol:RECLIENT exec:'%s' %s", helperPath, strings.Join(credHelperArgsParts, " "))
+ cipdArgs = append(cipdArgs, "-credential-helper", helperSpec)
+ case "RBE_use_application_default_credentials", "RBE_use_gce_credentials":
+ fallthrough
+ default:
+ cipdArgs = append(cipdArgs, "-application-default-credentials=always")
+ adcFlagAdded = true
+ }
+ } else {
+ ctx.Printf("No RBE configuration detected. You may need to use application default credentials.")
+ }
+
+ if !adcFlagAdded {
+ // RBE instructions for non-corp machines set both RBE_credentials_helper and
+ // RBE_use_application_default_credentials. Pass that along to CIPD as well.
+ // Even if USE_RBE=false, CIPD can still use ADC.
+ if useAdcStr, ok := config.environ.Get("RBE_use_application_default_credentials"); ok {
+ parsedVal, err := strconv.ParseBool(useAdcStr)
+ if err == nil && parsedVal {
+ cipdArgs = append(cipdArgs, "-application-default-credentials=always")
+ }
+ }
+ }
+
+ cipdCmd := fmt.Sprintf("cipd %s", strings.Join(cipdArgs, " "))
+ ctx.Printf("Starting CIPD proxy server with: %s", cipdCmd)
+
+ cmd := Command(ctx, config, "cipd", cipdPath(config), cipdArgs...)
+ stdout, err := cmd.StdoutPipe()
+ if err != nil {
+ log.Fatal(err)
+ }
+ stderr, err := cmd.StderrPipe()
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ if err := cmd.Start(); err != nil {
+ log.Fatal(err)
+ }
+ cp := cipdProxy{cmd: cmd}
+ cp.wg.Add(1)
+ go func() {
+ // Log any error output from cipd until it exits.
+ defer cp.wg.Done()
+
+ bufReader := bufio.NewReader(stderr)
+ for {
+ l, err := bufReader.ReadString('\n')
+ if err != nil {
+ if errors.Is(err, io.EOF) {
+ if !cp.stopping.Load() {
+ err := cmd.Wait()
+ ctx.Printf("cipd: unexpected EOF, process exited with %v", err)
+ }
+ break
+ }
+ ctx.Fatalf("cipd: %v %v", l, err)
+ }
+ ctx.Printf("cipd: %v", l)
+ }
+ }()
+
+ bufReader := bufio.NewReader(stdout)
+ for {
+ l, err := bufReader.ReadString('\n')
+ if errors.Is(err, io.EOF) {
+ ctx.Printf("cipd: unexpected EOF: %v\n", l)
+ // The stderr goroutine will handle the EOF
+ cp.wg.Wait()
+ }
+
+ if err != nil {
+ log.Fatalf("Got %v reading from cipd process", err)
+ }
+ if strings.HasPrefix(l, cipdProxyUrlKey) {
+ proxyUrl := strings.TrimSpace(l[len(cipdProxyUrlKey)+1:])
+ config.environ.Set(cipdProxyUrlKey, proxyUrl)
+ ctx.Println("Started CIPD proxy listening on", proxyUrl)
+ break
+ }
+ }
+ return &cp
+}
+
+func (c *cipdProxy) Stop() {
+ c.stopping.Store(true)
+ c.cmd.Process.Kill()
+ c.wg.Wait()
+}