14
14
15
15
package com .google .devtools .build .lib .authandtls .credentialhelper ;
16
16
17
+ import static java .nio .charset .StandardCharsets .UTF_8 ;
18
+
17
19
import com .google .common .annotations .VisibleForTesting ;
18
20
import 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 ;
19
25
import com .google .devtools .build .lib .vfs .Path ;
20
26
import 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 ;
21
37
22
38
/** Wraps an external tool used to obtain credentials. */
23
39
@ Immutable
24
40
public final class CredentialHelper {
41
+ private static final Gson GSON = new Gson ();
42
+
25
43
// `Path` is immutable, but not annotated.
26
44
@ SuppressWarnings ("Immutable" )
27
45
private final Path path ;
@@ -35,5 +53,101 @@ Path getPath() {
35
53
return path ;
36
54
}
37
55
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
+ }
39
153
}
0 commit comments