1414
1515package com .google .devtools .build .lib .authandtls .credentialhelper ;
1616
17+ import static java .nio .charset .StandardCharsets .UTF_8 ;
18+
1719import com .google .common .annotations .VisibleForTesting ;
1820import com .google .common .base .Preconditions ;
21+ import com .google .common .collect .ImmutableList ;
22+ import com .google .common .io .CharStreams ;
23+ import com .google .devtools .build .lib .shell .Subprocess ;
24+ import com .google .devtools .build .lib .shell .SubprocessBuilder ;
1925import com .google .devtools .build .lib .vfs .Path ;
2026import com .google .errorprone .annotations .Immutable ;
27+ import com .google .gson .Gson ;
28+ import com .google .gson .JsonSyntaxException ;
29+ import java .io .IOException ;
30+ import java .io .InputStreamReader ;
31+ import java .io .OutputStreamWriter ;
32+ import java .io .Reader ;
33+ import java .io .Writer ;
34+ import java .net .URI ;
35+ import java .util .Locale ;
36+ import java .util .Objects ;
2137
2238/** Wraps an external tool used to obtain credentials. */
2339@ Immutable
2440public final class CredentialHelper {
41+ private static final Gson GSON = new Gson ();
42+
2543 // `Path` is immutable, but not annotated.
2644 @ SuppressWarnings ("Immutable" )
2745 private final Path path ;
@@ -35,5 +53,101 @@ Path getPath() {
3553 return path ;
3654 }
3755
38- // TODO(yannic): Implement running the helper subprocess.
56+ /**
57+ * Fetches credentials for the specified {@link URI} by invoking the credential helper as
58+ * subprocess according to the <a
59+ * href="https://github.com/bazelbuild/proposals/blob/main/designs/2022-06-07-bazel-credential-helpers.md">credential
60+ * helper protocol</a>.
61+ *
62+ * @param environment The environment to run the subprocess in.
63+ * @param uri The {@link URI} to fetch credentials for.
64+ * @return The response from the subprocess.
65+ */
66+ public GetCredentialsResponse getCredentials (CredentialHelperEnvironment environment , URI uri )
67+ throws InterruptedException , IOException {
68+ Preconditions .checkNotNull (environment );
69+ Preconditions .checkNotNull (uri );
70+
71+ Subprocess process = spawnSubprocess (environment , "get" );
72+ try (Reader stdout = new InputStreamReader (process .getInputStream (), UTF_8 );
73+ Reader stderr = new InputStreamReader (process .getErrorStream (), UTF_8 )) {
74+ try (Writer stdin = new OutputStreamWriter (process .getOutputStream (), UTF_8 )) {
75+ GSON .toJson (GetCredentialsRequest .newBuilder ().setUri (uri ).build (), stdin );
76+ }
77+
78+ process .waitFor ();
79+ if (process .timedout ()) {
80+ throw new IOException (
81+ String .format (
82+ Locale .US ,
83+ "Failed to get credentials for '%s' from helper '%s': process timed out" ,
84+ uri ,
85+ path ));
86+ }
87+ if (process .exitValue () != 0 ) {
88+ throw new IOException (
89+ String .format (
90+ Locale .US ,
91+ "Failed to get credentials for '%s' from helper '%s': process exited with code %d."
92+ + " stderr: %s" ,
93+ uri ,
94+ path ,
95+ process .exitValue (),
96+ CharStreams .toString (stderr )));
97+ }
98+
99+ try {
100+ GetCredentialsResponse response = GSON .fromJson (stdout , GetCredentialsResponse .class );
101+ if (response == null ) {
102+ throw new IOException (
103+ String .format (
104+ Locale .US ,
105+ "Failed to get credentials for '%s' from helper '%s': process exited without"
106+ + " output. stderr: %s" ,
107+ uri ,
108+ path ,
109+ CharStreams .toString (stderr )));
110+ }
111+ return response ;
112+ } catch (JsonSyntaxException e ) {
113+ throw new IOException (
114+ String .format (
115+ Locale .US ,
116+ "Failed to get credentials for '%s' from helper '%s': error parsing output. stderr:"
117+ + " %s" ,
118+ uri ,
119+ path ,
120+ CharStreams .toString (stderr )),
121+ e );
122+ }
123+ }
124+ }
125+
126+ private Subprocess spawnSubprocess (CredentialHelperEnvironment environment , String ... args )
127+ throws IOException {
128+ Preconditions .checkNotNull (environment );
129+ Preconditions .checkNotNull (args );
130+
131+ return new SubprocessBuilder ()
132+ .setArgv (ImmutableList .<String >builder ().add (path .getPathString ()).add (args ).build ())
133+ .setWorkingDirectory (environment .getWorkspacePath ().getPathFile ())
134+ .setEnv (environment .getClientEnvironment ())
135+ .setTimeoutMillis (environment .getHelperExecutionTimeout ().toMillis ())
136+ .start ();
137+ }
138+
139+ @ Override
140+ public boolean equals (Object o ) {
141+ if (o instanceof CredentialHelper ) {
142+ CredentialHelper that = (CredentialHelper ) o ;
143+ return Objects .equals (this .getPath (), that .getPath ());
144+ }
145+
146+ return false ;
147+ }
148+
149+ @ Override
150+ public int hashCode () {
151+ return Objects .hashCode (getPath ());
152+ }
39153}
0 commit comments