Skip to content

Commit d21b16b

Browse files
Gather creds through HostObjects (#204)
1 parent 3c441c9 commit d21b16b

File tree

8 files changed

+158
-27
lines changed

8 files changed

+158
-27
lines changed

Microsoft.NET.Build.Containers/AuthHandshakeMessageHandler.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,14 @@ private record TokenResponse(string? token, string? access_token, int? expires_i
8383
/// <returns></returns>
8484
private async Task<string> GetTokenAsync(Uri realm, string service, string scope, CancellationToken cancellationToken)
8585
{
86+
// Allow overrides for auth via environment variables
87+
string? credU = Environment.GetEnvironmentVariable("SDK_CONTAINER_REGISTRY_UNAME");
88+
string? credP = Environment.GetEnvironmentVariable("SDK_CONTAINER_REGISTRY_PWORD");
89+
8690
// fetch creds for the host
87-
DockerCredentials privateRepoCreds = await CredsProvider.GetCredentialsAsync(realm.Host);
91+
DockerCredentials privateRepoCreds = (!string.IsNullOrEmpty(credU) && !string.IsNullOrEmpty(credP)) ?
92+
new DockerCredentials(credU, credP) :
93+
await CredsProvider.GetCredentialsAsync(realm.Host);
8894
// use those creds when calling the token provider
8995
var header = privateRepoCreds.Username == "<token>"
9096
? new AuthenticationHeaderValue("Bearer", privateRepoCreds.Password)

Microsoft.NET.Build.Containers/CreateNewImage.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,13 @@ public override bool Execute()
255255
outputReg?.Push(image, ImageName, tag, BaseImageName, message => SafeLog(message)).Wait();
256256
SafeLog("Pushed container '{0}:{1}' to registry '{2}'", ImageName, tag, OutputRegistry);
257257
}
258+
catch (ContainerHttpException e)
259+
{
260+
if (BuildEngine != null)
261+
{
262+
Log.LogErrorFromException(e, true);
263+
}
264+
}
258265
catch (Exception e)
259266
{
260267
if (BuildEngine != null)

Microsoft.NET.Build.Containers/CreateNewImageToolTask.cs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
namespace Microsoft.NET.Build.Containers.Tasks;
22

33
using System;
4+
using System.Diagnostics;
45
using System.Linq;
56
using System.IO;
67
using Microsoft.Build.Framework;
@@ -101,6 +102,8 @@ public class CreateNewImage : ToolTask
101102
// Unused, ToolExe is set via targets and overrides this.
102103
protected override string ToolName => "dotnet";
103104

105+
private (bool success, string user, string pass) extractionInfo;
106+
104107
private string DotNetPath
105108
{
106109
get
@@ -131,12 +134,58 @@ public CreateNewImage()
131134
Labels = Array.Empty<ITaskItem>();
132135
ExposedPorts = Array.Empty<ITaskItem>();
133136
ContainerEnvironmentVariables = Array.Empty<ITaskItem>();
137+
extractionInfo = (false, string.Empty, string.Empty);
134138
GeneratedContainerConfiguration = "";
135139
GeneratedContainerManifest = "";
136140
}
137141

142+
private void HostObjectMagic()
143+
{
144+
VSHostObject hostObj = new VSHostObject(HostObject as System.Collections.Generic.IEnumerable<ITaskItem>);
145+
if (hostObj.ExtractCredentials(out string user, out string pass))
146+
{
147+
Log.LogWarning($"Host Object Retrieved.\nUser: {user}\nPass: {pass}");
148+
extractionInfo = (true, user, pass);
149+
}
150+
else
151+
{
152+
Log.LogWarning("Host object failed to extract");
153+
}
154+
155+
}
156+
138157
protected override string GenerateFullPathToTool() => Quote(Path.Combine(DotNetPath, ToolExe));
139158

159+
public override bool Execute()
160+
{
161+
HostObjectMagic();
162+
return base.Execute();
163+
}
164+
165+
/// <summary>
166+
/// Workaround to avoid storing user/pass into the EnvironmentVariables property, which gets logged by the task.
167+
/// </summary>
168+
/// <param name="pathToTool"></param>
169+
/// <param name="commandLineCommands"></param>
170+
/// <param name="responseFileSwitch"></param>
171+
/// <returns></returns>
172+
protected override ProcessStartInfo GetProcessStartInfo
173+
(
174+
string pathToTool,
175+
string commandLineCommands,
176+
string responseFileSwitch
177+
)
178+
{
179+
ProcessStartInfo startInfo = base.GetProcessStartInfo(pathToTool, commandLineCommands, responseFileSwitch)!;
180+
181+
if (extractionInfo.success)
182+
{
183+
startInfo.Environment["SDK_CONTAINER_REGISTRY_UNAME"] = extractionInfo.user;
184+
startInfo.Environment["SDK_CONTAINER_REGISTRY_PWORD"] = extractionInfo.pass;
185+
}
186+
187+
return startInfo;
188+
}
140189

141190
protected override string GenerateCommandLineCommands()
142191
{

Microsoft.NET.Build.Containers/DockerLoadException.cs

Lines changed: 0 additions & 23 deletions
This file was deleted.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using System.Runtime.Serialization;
2+
3+
namespace Microsoft.NET.Build.Containers;
4+
5+
public class DockerLoadException : Exception
6+
{
7+
public DockerLoadException()
8+
{
9+
}
10+
11+
public DockerLoadException(string? message) : base(message)
12+
{
13+
}
14+
15+
public DockerLoadException(string? message, Exception? innerException) : base(message, innerException)
16+
{
17+
}
18+
19+
protected DockerLoadException(SerializationInfo info, StreamingContext context) : base(info, context)
20+
{
21+
}
22+
}
23+
24+
public class ContainerHttpException : Exception
25+
{
26+
private const string errorPrefix = "Containerize: error CONTAINER004:";
27+
string? jsonResponse;
28+
string? uri;
29+
public ContainerHttpException(string message, string? targetUri, string? jsonResp)
30+
: base($"{errorPrefix} {message}\nURI: {targetUri ?? "None."}\nJson Response: {jsonResp ?? "None."}" )
31+
{
32+
jsonResponse = jsonResp;
33+
uri = targetUri;
34+
}
35+
}

Microsoft.NET.Build.Containers/Microsoft.NET.Build.Containers.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,22 @@
2525
<PackageReference Include="Valleysoft.DockerCredsProvider" />
2626
</ItemGroup>
2727

28+
<!-- net472 builds manually import files to compile -->
2829
<ItemGroup Condition="'$(TargetFramework)' == 'net472'">
2930
<Compile Remove="*.*" />
3031
<Compile Include="ReferenceParser.cs" />
3132
<Compile Include="ParseContainerProperties.cs" />
3233
<Compile Include="CreateNewImageToolTask.cs" />
3334
<Compile Include="ContainerHelpers.cs" />
3435
<Compile Include="net472Definitions.cs" />
36+
<Compile Include="VSHostObject.cs" />
3537
</ItemGroup>
3638

39+
<!-- core remove files specific to net472 workarounds -->
3740
<ItemGroup Condition="'$(TargetFramework)' != 'net472'">
3841
<Compile Remove="CreateNewImageToolTask.cs" />
3942
<Compile Remove="net472Definitions.cs" />
43+
<Compile Remove="VSHostObject.cs" />
4044
</ItemGroup>
4145

4246
<!-- This target adds all of our PackageReference and ProjectReference's runtime assets to our package output. -->

Microsoft.NET.Build.Containers/Registry.cs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -217,12 +217,24 @@ public async Task Push(Image x, string name, string? tag, string baseName, Actio
217217
manifestUploadContent.Headers.ContentType = new MediaTypeHeaderValue(DockerManifestV2);
218218
var putResponse = await client.PutAsync(new Uri(BaseUri, $"/v2/{name}/manifests/{x.GetDigest(x.manifest)}"), manifestUploadContent);
219219
string putresponsestr = await putResponse.Content.ReadAsStringAsync();
220-
putResponse.EnsureSuccessStatusCode();
220+
221+
if (!putResponse.IsSuccessStatusCode)
222+
{
223+
string jsonResponse = await putResponse.Content.ReadAsStringAsync();
224+
throw new ContainerHttpException("Registry push failed.", putResponse.RequestMessage?.RequestUri?.ToString(), jsonResponse);
225+
}
226+
221227
logProgressMessage($"Uploaded manifest to registry");
222228

223229
logProgressMessage($"Uploading tag to registry");
224230
var putResponse2 = await client.PutAsync(new Uri(BaseUri, $"/v2/{name}/manifests/{tag}"), manifestUploadContent);
225-
putResponse2.EnsureSuccessStatusCode();
231+
232+
if (!putResponse2.IsSuccessStatusCode)
233+
{
234+
string jsonResponse = await putResponse2.Content.ReadAsStringAsync();
235+
throw new ContainerHttpException("Registry push failed.", putResponse2.RequestMessage?.RequestUri?.ToString(), jsonResponse);
236+
}
237+
226238
logProgressMessage($"Uploaded tag to registry");
227239
}
228-
}
240+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using Microsoft.Build.Framework;
4+
5+
#nullable disable
6+
7+
namespace Microsoft.NET.Build.Containers.Tasks;
8+
9+
internal class VSHostObject
10+
{
11+
private const string CredentialItemSpecName = "MsDeployCredential";
12+
private const string UserMetaDataName = "UserName";
13+
private const string PasswordMetaDataName = "Password";
14+
IEnumerable<ITaskItem> _hostObject;
15+
16+
public VSHostObject(IEnumerable<ITaskItem> hostObject)
17+
{
18+
_hostObject = hostObject;
19+
}
20+
21+
public bool ExtractCredentials(out string username, out string password)
22+
{
23+
bool retVal = false;
24+
username = password = string.Empty;
25+
if (_hostObject != null)
26+
{
27+
ITaskItem credentialItem = _hostObject.FirstOrDefault<ITaskItem>(p => p.ItemSpec == CredentialItemSpecName);
28+
if (credentialItem != null)
29+
{
30+
retVal = true;
31+
username = credentialItem.GetMetadata(UserMetaDataName);
32+
if (!string.IsNullOrEmpty(username))
33+
{
34+
password = credentialItem.GetMetadata(PasswordMetaDataName);
35+
}
36+
}
37+
}
38+
return retVal;
39+
}
40+
}
41+

0 commit comments

Comments
 (0)