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

[Fix] Liner ordering needs to be strict in FileBuilder. #119

Merged
merged 5 commits into from
Nov 1, 2019
Merged
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
31 changes: 31 additions & 0 deletions Ductus.FluentDocker.Tests/FluentApiTests/ImageBuilderTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Ductus.FluentDocker.Tests.Extensions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;

namespace Ductus.FluentDocker.Tests.FluentApiTests
{
Expand All @@ -12,6 +13,36 @@ public static void Initialize(TestContext ctx)
Utilities.LinuxMode();
}

[TestMethod]
public void BuildImageShallPerserveLineOrdering()
{
var dockerfile = Fd.Dockerfile()
.UseParent("mcr.microsoft.com/windows/servercore:ltsc2019")
.Shell("powershell", "-Command", "$ErrorActionPreference = 'Stop';")
.Run("Set-ExecutionPolicy Bypass -Scope Process -Force; " +
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12")
.Run("Invoke-WebRequest -OutFile install.ps1 https://www.chocolatey.org/install.ps1; " +
"./install.ps1")
.Run("choco feature enable --name=allowGlobalConfirmation")
.Run("choco install python3")
.Copy("Resources/Issue/111/server.py", "C:/")
.ExposePorts(8000)
.Command("python", "server.py").ToDockerfileString();

var lines = dockerfile.Split(Environment.NewLine);
Assert.AreEqual(10, lines.Length);
Assert.AreEqual("FROM mcr.microsoft.com/windows/servercore:ltsc2019", lines[0]);
Assert.AreEqual("SHELL [powershell-Command,$ErrorActionPreference = 'Stop';]", lines[1]);
Assert.AreEqual("RUN Set-ExecutionPolicy Bypass -Scope Process -Force; [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12", lines[2]);
Assert.AreEqual("RUN Invoke-WebRequest -OutFile install.ps1 https://www.chocolatey.org/install.ps1; ./install.ps1", lines[3]);
Assert.AreEqual("RUN choco feature enable --name=allowGlobalConfirmation", lines[4]);
Assert.AreEqual("RUN choco install python3", lines[5]);
Assert.AreEqual("COPY Resources/Issue/111/server.py C:/", lines[6]);
Assert.AreEqual("EXPOSE 8000", lines[7]);
Assert.AreEqual("CMD [pythonserver.py]", lines[8]);
Assert.AreEqual(string.Empty, lines[9]);
}

[TestMethod]
[Ignore]
public void BuildImageFromFileWithCopyAndRunInstructionShallWork()
Expand Down
99 changes: 62 additions & 37 deletions Ductus.FluentDocker/Builders/FileBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Ductus.FluentDocker.Common;
using Ductus.FluentDocker.Extensions;
using Ductus.FluentDocker.Model.Builders;
using Ductus.FluentDocker.Model.Builders.FileBuilder;
using Ductus.FluentDocker.Model.Common;
using Ductus.FluentDocker.Services;

Expand All @@ -14,12 +15,28 @@ public sealed class FileBuilder
private readonly FileBuilderConfig _config = new FileBuilderConfig();
private readonly ImageBuilder _parent;
private TemplateString _workingFolder;
private string _lastContents = null;

internal FileBuilder(ImageBuilder parent)
{
_parent = parent;
}

/// <summary>
/// Used to create a Dockerfile as string
/// </summary>
/// <remarks>
/// This is mainly for unit testing but may be used by external
/// code to render Dockerfile for custom usage. Use the
/// <see cref="ToDockerfileString()"/> method to produce the
/// Dockerfile contents (and copy files / render Dockerfile
/// on working directory). If no working directory is set
/// it will create a temporary one.
/// </remarks>
public FileBuilder()
{
}

internal string PrepareBuild()
{
if (null == _workingFolder)
Expand All @@ -34,6 +51,11 @@ internal string PrepareBuild()

public IContainerImageService Build()
{
if (null == _parent)
{
throw new FluentDockerException("No ImageBuilder was set as parent");
}

return _parent.Build();
}

Expand All @@ -44,6 +66,11 @@ public Builder Builder()

public ImageBuilder ToImage()
{
if (null == _parent)
{
throw new FluentDockerException("No ImageBuilder was set as parent");
}

return _parent;
}

Expand All @@ -55,63 +82,58 @@ public FileBuilder WorkingFolder(TemplateString workingFolder)

public FileBuilder UseParent(string from)
{
_config.From = from;
_config.Commands.Add(new FromCommand(from));
return this;
}

public FileBuilder Maintainer(string maintainer)
{
_config.Maintainer = maintainer;
_config.Commands.Add(new MaintainerCommand(maintainer));
return this;
}

public FileBuilder Add(TemplateString source, TemplateString destination)
{
_config.AddRunCommands.Add(new AddCommand {Source = source, Destination = destination});
_config.Commands.Add(new AddCommand(source, destination));
return this;
}

public FileBuilder Shell(string command, params string[] args)
{
_config.Shell.Add(command);
if (null != args && 0 != args.Length)
{
((List<string>) _config.Shell).AddRange(args);
}
_config.Commands.Add(new ShellCommand(command, args));
return this;
}

public FileBuilder Run(params TemplateString[] commands)
{
_config.AddRunCommands.Add(new RunCommand {Lines = new List<TemplateString>(commands)});
foreach(var cmd in commands)
{
_config.Commands.Add(new RunCommand(cmd));
}
return this;
}

public FileBuilder Copy(TemplateString source, TemplateString dest)
{
_config.Copy.Add(new Tuple<string, string>(source,dest));
_config.Commands.Add(new CopyCommand(source, dest));
return this;
}

public FileBuilder UseWorkDir(string workdir)
{
_config.Workdir = workdir;
_config.Commands.Add(new WorkdirCommand(workdir));
return this;
}

public FileBuilder ExposePorts(params int[] ports)
{
_config.Expose = new List<int>(ports);
_config.Commands.Add(new ExposeCommand(ports));
return this;
}

public FileBuilder Command(string command, params string[] args)
{
_config.Command.Add(command);
if (null != args && 0 != args.Length)
{
((List<string>) _config.Command).AddRange(args);
}
_config.Commands.Add(new CmdCommand(command, args));
return this;
}

Expand All @@ -136,17 +158,17 @@ public FileBuilder FromString(string dockerFileAsString)
private void CopyToWorkDir(TemplateString workingFolder)
{
// Copy all files from copy arguments.
foreach (var cp in _config.Copy)
foreach (var cp in _config.Commands.Where(x => x is CopyCommand).Cast<CopyCommand>())
{
if (!File.Exists(cp.Item1)) continue;
if (!File.Exists(cp.From)) continue;

var wp = Path.Combine(workingFolder, cp.Item1);
var wp = Path.Combine(workingFolder, cp.From);
var wdp = Path.GetDirectoryName(wp);
Directory.CreateDirectory(wdp);
File.Copy(cp.Item1,wp);
File.Copy(cp.From,wp, true /*overwrite*/);
}

foreach (var command in _config.AddRunCommands.Where(x => x is AddCommand).Cast<AddCommand>())
foreach (var command in _config.Commands.Where(x => x is AddCommand).Cast<AddCommand>())
{
var wff = Path.Combine(workingFolder, command.Source);
if (File.Exists(wff) || Directory.Exists(wff))
Expand All @@ -168,24 +190,27 @@ private void RenderDockerfile(TemplateString workingFolder)
}

var dockerFile = Path.Combine(workingFolder, "Dockerfile");

string contents = !string.IsNullOrEmpty(_config.UseFile) ?
_config.UseFile.FromFile() :
ResolveOrBuildString();

string contents = null;
if (!string.IsNullOrEmpty(_config.UseFile))
{
contents = _config.UseFile.FromFile();
}
contents.ToFile(dockerFile);

if (!string.IsNullOrWhiteSpace(_config.DockerFileString))
{
contents = _config.DockerFileString;
}
_lastContents = contents;
}

if (string.IsNullOrEmpty(contents))
{
contents = _config.ToString();
}
private string ResolveOrBuildString()
{
return !string.IsNullOrWhiteSpace(_config.DockerFileString) ?
_config.DockerFileString :
_config.ToString();
}

contents.ToFile(dockerFile);
public string ToDockerfileString()
{
PrepareBuild();
return _lastContents;
}
}
}
14 changes: 14 additions & 0 deletions Ductus.FluentDocker/Fd.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,20 @@ public static ImageBuilder DefineImage(string image)
return new Builder().DefineImage(image);
}

/// <summary>
/// Creates a in-memory Dockerfile builder.
/// </summary>
/// <returns>A builder to build a Dockerfile</returns>
/// <remarks>
/// This builder won't build an Image, use <see cref="DefineImage(string)"/>
/// for that purpose. This is a builder that can produce a string representing
/// a docker file.
/// </remarks>
public static FileBuilder Dockerfile()
{
return new FileBuilder();
}

public static IEngineScope EngineScope(EngineScopeType scope, DockerUri host = null, ICertificatePaths certificates = null)
{
return new EngineScope(host, scope, certificates);
Expand Down
15 changes: 0 additions & 15 deletions Ductus.FluentDocker/Model/Builders/AddCommand.cs

This file was deleted.

21 changes: 21 additions & 0 deletions Ductus.FluentDocker/Model/Builders/FileBuilder/AddCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Ductus.FluentDocker.Model.Common;

namespace Ductus.FluentDocker.Model.Builders.FileBuilder
{
public sealed class AddCommand : ICommand
{
public AddCommand(TemplateString source, TemplateString destination)
{
Source = source;
Destination = destination;
}

public TemplateString Source { get; internal set; }
public TemplateString Destination { get; }

public override string ToString()
{
return $"ADD {Source} {Destination}";
}
}
}
19 changes: 19 additions & 0 deletions Ductus.FluentDocker/Model/Builders/FileBuilder/CmdCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace Ductus.FluentDocker.Model.Builders.FileBuilder
{
public sealed class CmdCommand : ICommand
{
public CmdCommand(string cmd, params string []args)
{
Cmd = cmd;
Arguments = null == args ? new string[0] : args;
}

public string Cmd { get; }
public string []Arguments { get; }

public override string ToString()
{
return $"CMD [{Cmd}{string.Join(",", Arguments)}]";
}
}
}
19 changes: 19 additions & 0 deletions Ductus.FluentDocker/Model/Builders/FileBuilder/CopyCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace Ductus.FluentDocker.Model.Builders.FileBuilder
{
public sealed class CopyCommand : ICommand
{
public CopyCommand(string from, string to)
{
From = from;
To = to;
}

public string From { get; }
public string To { get; }

public override string ToString()
{
return $"COPY {From} {To}";
}
}
}
19 changes: 19 additions & 0 deletions Ductus.FluentDocker/Model/Builders/FileBuilder/ExposeCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Ductus.FluentDocker.Model.Common;

namespace Ductus.FluentDocker.Model.Builders.FileBuilder
{
public sealed class ExposeCommand : ICommand
{
public ExposeCommand(params int []ports)
{
Ports = null == ports ? new int[0] : ports;
}

public int []Ports { get; }

public override string ToString()
{
return $"EXPOSE {string.Join(",", Ports)}";
}
}
}
17 changes: 17 additions & 0 deletions Ductus.FluentDocker/Model/Builders/FileBuilder/FromCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace Ductus.FluentDocker.Model.Builders.FileBuilder
{
public sealed class FromCommand : ICommand
{
public FromCommand(string @from)
{
From = @from;
}

public string From { get; }

public override string ToString()
{
return $"FROM {From}";
}
}
}
Loading