Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Task and Targets #38

Closed
wants to merge 15 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Microsoft.Build.Utilities.Core" Version="17.2.0"/>
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageVersion Include="MSTest.TestAdapter" Version="2.2.10" />
<PackageVersion Include="MSTest.TestFramework" Version="2.2.10" />
Expand Down
115 changes: 115 additions & 0 deletions System.Containers.Tasks/CreateNewImage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Resources;
using System.Text;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

#nullable disable

namespace System.Containers.Tasks
{
public class CreateNewImage : Microsoft.Build.Utilities.Task
{
/// <summary>
/// Base image name.
/// </summary>
[Required]
public string BaseImageName { get; set; }

[Required]
public string BaseImageTag { get; set; }

[Required]
public string InputRegistryURL { get; set; }

[Required]
public string OutputRegistryURL { get; set; }

[Required]
public ITaskItem[] Files { get; set; }

/// <summary>
/// $(ContainerWorkingDirectory)
/// </summary>
[Required]
public string WorkingDirectory { get; set; }

[Required]
public string NewImageName { get; set; }

[Required]
public string Entrypoint { get; set; }

/// <summary>
/// Arguments to pass alongside Entrypoint.
/// </summary>
public string EntrypointArgs { get; set; }

public string PublishDirectory { get; set; }

/// <summary>
/// CreateNewImage needs to:
/// 1. Pull a base image (needs parameters: URL, BaseImage, BaseImageTag)
/// 2. Add output of build as a new layer
/// 3. Push image back to some registry (needs parameters: OutputURL, NewName, EntryPoint)
/// </summary>
/// <returns></returns>
public override bool Execute()
{
if (Files.Length == 0)
{
Console.WriteLine("Files is empty, aborting.");
return false;
}

Registry reg = new Registry(new Uri(InputRegistryURL));

Image image;
try
{
image = reg.GetImageManifest(BaseImageName, BaseImageTag).Result;
}
catch (Exception ex)
{
Log.LogError("GetImageManifest Failed: {0}.\n{1}", ex.Message, ex.InnerException);
return false;
}

// Turn the build output from items into an array of filepaths
string[] filePaths = Files.Select((f) => f.ItemSpec).ToArray();

// preserve the folder structure of items published
string[] relativeFilePaths = filePaths.Select((x) => Path.GetRelativePath(PublishDirectory, x)).ToArray<string>();

List<(string file, string relativePath)> filesWithPaths = new List<(string file, string relativePath)>();

for(int i = 0; i < filePaths.Length; i++)
{
Log.LogMessage("File {0} has relative path of {1}", filePaths[i], relativeFilePaths[i]);
filesWithPaths.Add((filePaths[i], relativeFilePaths[i]));
}

Layer newLayer = Layer.FromFiles(filesWithPaths.AsEnumerable());

image.AddLayer(newLayer);
image.SetEntrypoint(Entrypoint, EntrypointArgs?.Split(' ').ToArray());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it'll be important to document that the Entrypoint needs to be a fully-rooted path. I could see users doing something like

<ContainerEntrypoint>$(AppName)</ContainerEntrypoint>

and attempting to package up an executable at what is effectively /$(AppName), which won't exist.

Maybe there's some validation we can do here to save them: either checking that a fully-rooted path was provided, or attempting to 'root' a relative or un-rooted path against the ContainerWorkingDirectory.


Registry outputReg = new Registry(new Uri(OutputRegistryURL));

try
{
outputReg.Push(image, NewImageName, BaseImageName).Wait();
}
catch
{
Console.WriteLine("Registry.Push failed");
return false;
}

return true;
}
}
}
59 changes: 59 additions & 0 deletions System.Containers.Tasks/System.Containers.Tasks.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>

<IsPackable>true</IsPackable>

<TargetsForTfmSpecificBuildOutput>
$(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage
</TargetsForTfmSpecificBuildOutput>

<BuildOutputTargetFolder>tasks</BuildOutputTargetFolder>

<!-- Tell the SDK to generate a deps.json file -->
<GenerateDependencyFile>true</GenerateDependencyFile>

<!-- MSBuild tasks shouldn't be referenced. This is by design. -->
<NoWarn>NU5100;NU5128</NoWarn>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Build.Utilities.Core" PrivateAssets="all" ExcludeAssets="runtime" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\System.Containers\System.Containers.csproj" PrivateAssets="all"/>
</ItemGroup>

<ItemGroup>
<Content Include="build\System.Containers.Tasks.props" Pack="true" PackagePath="build\" />
<Content Include="build\System.Containers.Tasks.targets" Pack="true" PackagePath="build\" />
</ItemGroup>

<!-- This target adds all of our PackageReference and ProjectReference's runtime assets to our package output. -->
<Target Name="CopyProjectReferencesToPackage" DependsOnTargets="ResolveReferences">
<ItemGroup>
<BuildOutputInPackage Include="@(ReferenceCopyLocalPaths)"
TargetPath="%(ReferenceCopyLocalPaths.DestinationSubPath)" />
</ItemGroup>
</Target>

<!-- This target adds the generated deps.json file to our package output -->
<Target Name="AddBuildDependencyFileToBuiltProjectOutputGroupOutput" BeforeTargets="BuiltProjectOutputGroup"
Condition=" '$(GenerateDependencyFile)' == 'true'">
<ItemGroup>
<BuiltProjectOutputGroupOutput Include="$(ProjectDepsFilePath)" TargetPath="$(ProjectDepsFileName)"
FinalOutputPath="$(ProjectDepsFilePath)" />
</ItemGroup>
</Target>

<!-- Hacky workaround for the fact that we don't publish the package yet. -->
<Target Name="CopyNupkgToCustomFolder" AfterTargets="Pack">
<Copy SourceFiles="$(OutDir)..\System.Containers.Tasks.1.0.0.nupkg" DestinationFiles="..\Test.System.Containers.Filesystem\package\System.Containers.Tasks.1.0.0.nupkg" />
</Target>

</Project>
14 changes: 14 additions & 0 deletions System.Containers.Tasks/build/System.Containers.Tasks.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project>
<PropertyGroup>
<taskForldername>tasks</taskForldername>
<taskFramework>net7.0</taskFramework>
<!--The folder where the custom task will be present. It points to inside the nuget package. -->
<CustomTasksFolder>$(MSBuildThisFileDirectory)..\$(taskForldername)\$(taskFramework)</CustomTasksFolder>
<!--Reference to the assembly which contains the MSBuild Task-->
<CustomTasksAssembly>$(CustomTasksFolder)\$(MSBuildThisFileName).dll</CustomTasksAssembly>
</PropertyGroup>

<!--Register our custom task-->
<UsingTask TaskName="$(MSBuildThisFileName).CreateNewImage" AssemblyFile="$(CustomTasksAssembly)"/>

</Project>
50 changes: 50 additions & 0 deletions System.Containers.Tasks/build/System.Containers.Tasks.targets
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<Project>
<PropertyGroup>
<TargetFrameworkVersion>6.0</TargetFrameworkVersion>
</PropertyGroup>

<!-- Required targets dependencies -->
<PropertyGroup>
<PublishContainerDependsOn>
Build;
ComputeContainerConfig
</PublishContainerDependsOn>
</PropertyGroup>

<Target Name="ComputeContainerConfig" AfterTargets="Build;_CopyResolvedFilesToPublishPreserveNewest">
<!-- Find everything in the publish directory -->
<ItemGroup>
<_BuildOutputsNonRelative Include="$(PublishDir)**\*.*"/>
<BuildOutputs Include="%(_BuildOutputsNonRelative.FullPath)"/>
</ItemGroup>

<!-- Container Defaults -->
<PropertyGroup>
<ContainerBaseImageName Condition="'$(ContainerBaseImageName)' == ''"></ContainerBaseImageName>
<ContainerBaseImageTag Condition="'$(ContainerBaseImageTag)' == '' and $([MSBuild]::VersionGreaterThanOrEquals('$(TargetFrameworkVersion)', 6.0))">6.0</ContainerBaseImageTag>
<ContainerBaseImage Condition="'$(ContainerBaseImage)' == ''">$(ContainerBaseImageName):$(ContainerBaseImageTag)</ContainerBaseImage>
<ContainerInputRegistryURL Condition="'$(ContainerInputRegistryURL)' == ''">http://localhost:5010</ContainerInputRegistryURL>
<ContainerOutputRegistryURL Condition="'$(ContainerOutputRegistryURL)' == ''">http://localhost:5010</ContainerOutputRegistryURL>
<ContainerImageName Condition="'$(ContainerImageName)' == ''">$(AssemblyName.ToLower())</ContainerImageName>
<ContainerImageTag Condition="'$(ContainerImageTag)' == ''">$(Version)</ContainerImageTag>
<ContainerWorkingDirectory Condition="'$(ContainerWorkingDirectory)' == ''">/app</ContainerWorkingDirectory>
<ContainerEntrypoint Condition="'$(ContainerEntrypoint)' == '' and '$(SelfContained)' != 'true'">dotnet $(TargetFileName)</ContainerEntrypoint>
<!-- https://gist.github.com/BenVillalobos/e5336491e683b87e7ec2a5322f58dfbe -->
<ContainerEntrypoint Condition="'$(ContainerEntrypoint)' == '' and '$(SelfContained)' == 'true'">$(ContainerWorkingDirectory)/$(AssemblyName)$(_NativeExecutableExtension)</ContainerEntrypoint>
<ContainerEntrypointArgs Condition="'$(ContainerEntrypointArgs)' == ''"></ContainerEntrypointArgs>
</PropertyGroup>
</Target>

<Target Name="PublishContainer" DependsOnTargets="$(PublishContainerDependsOn)" BeforeTargets="Publish">
<CreateNewImage InputRegistryURL="$(ContainerInputRegistryURL)"
OutputRegistryURL="$(ContainerOutputRegistryURL)"
BaseImageName="$(ContainerBaseImageName)"
BaseImageTag="$(ContainerBaseImageTag)"
Files="@(BuildOutputs)"
WorkingDirectory="$(ContainerWorkingDirectory)"
NewImageName="$(ContainerImageName)"
Entrypoint="$(ContainerEntrypoint)"
EntrypointArgs="$(ContainerEntrypointArgs)"
PublishDirectory="$(MSBuildProjectDirectory)\$(PublishDir)"/>
</Target>
</Project>
17 changes: 9 additions & 8 deletions System.Containers.sln
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
Microsoft Visual Studio Solution File, Format Version 12.00

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.4.32710.52
VisualStudioVersion = 17.4.32707.430
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Containers", "System.Containers\System.Containers.csproj", "{A85F5917-A1F5-43D2-BDDC-CAE179B15DDE}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Test.System.Containers", "Test.System.Containers\Test.System.Containers.csproj", "{E6EE341A-E10A-423C-B3D8-FEEA0942CFA8}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Test.System.Containers.Filesystem", "Test.System.Containers.Filesystem\Test.System.Containers.Filesystem.csproj", "{CC249FAD-C24D-4B37-AF8F-FA05D0FD5620}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "containerize", "containerize\containerize.csproj", "{151F7937-D2AF-4242-9B6D-81FD5228D132}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Containers.Tasks", "System.Containers.Tasks\System.Containers.Tasks.csproj", "{BD3EA8D3-8CEF-4E84-A153-AE4178837739}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand All @@ -28,15 +29,15 @@ Global
{CC249FAD-C24D-4B37-AF8F-FA05D0FD5620}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CC249FAD-C24D-4B37-AF8F-FA05D0FD5620}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CC249FAD-C24D-4B37-AF8F-FA05D0FD5620}.Release|Any CPU.Build.0 = Release|Any CPU
{151F7937-D2AF-4242-9B6D-81FD5228D132}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{151F7937-D2AF-4242-9B6D-81FD5228D132}.Debug|Any CPU.Build.0 = Debug|Any CPU
{151F7937-D2AF-4242-9B6D-81FD5228D132}.Release|Any CPU.ActiveCfg = Release|Any CPU
{151F7937-D2AF-4242-9B6D-81FD5228D132}.Release|Any CPU.Build.0 = Release|Any CPU
{BD3EA8D3-8CEF-4E84-A153-AE4178837739}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BD3EA8D3-8CEF-4E84-A153-AE4178837739}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BD3EA8D3-8CEF-4E84-A153-AE4178837739}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BD3EA8D3-8CEF-4E84-A153-AE4178837739}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D68F4804-C298-48F6-9993-8CB75A141BD3}
SolutionGuid = {BCB99E6F-5414-479C-A178-4E4751304026}
EndGlobalSection
EndGlobal
9 changes: 8 additions & 1 deletion System.Containers/System.Containers.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>

</Project>
<!-- Hacky workaround for the fact that we don't publish the package yet. -->
<!-- <Target Name="CopyNupkgToCustomFolder" AfterTargets="Pack">
<Copy SourceFiles="$(OutDir)..\System.Containers.1.0.0.nupkg"
DestinationFiles="..\Test.System.Containers\package\System.Containers.1.0.0.nupkg" />
</Target> -->

</Project>
4 changes: 4 additions & 0 deletions Test.System.Containers.Filesystem/DockerRegistryManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,15 @@ public static void StartAndPopulateDockerRegistry(TestContext context)
ProcessStartInfo startRegistry = new("docker", "run --rm --publish 5010:5000 --detach registry:2")
{
RedirectStandardOutput = true,
RedirectStandardError = true,
};

using Process? registryProcess = Process.Start(startRegistry);
Assert.IsNotNull(registryProcess);
string? registryContainerId = registryProcess.StandardOutput.ReadLine();
// debugging purposes
string? everythingElse = registryProcess.StandardOutput.ReadToEnd();
string? errStream = registryProcess.StandardError.ReadToEnd();
Assert.IsNotNull(registryContainerId);
registryProcess.WaitForExit();
Assert.AreEqual(0, registryProcess.ExitCode);
Expand Down
Loading