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 support for WORKDIR instruction #90

Merged
merged 1 commit into from
Dec 21, 2020
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
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ public void BuildAllConstructs()
.ShellInstruction("cmd")
.StopSignalInstruction("1")
.UserInstruction("test")
.VolumeInstruction("path");
.VolumeInstruction("path")
.WorkdirInstruction("path");

string expectedOutput =
"ADD src dst" + Environment.NewLine +
Expand All @@ -68,7 +69,8 @@ public void BuildAllConstructs()
"SHELL [\"cmd\"]" + Environment.NewLine +
"STOPSIGNAL 1" + Environment.NewLine +
"USER test" + Environment.NewLine +
"VOLUME [\"path\"]" + Environment.NewLine;
"VOLUME [\"path\"]" + Environment.NewLine +
"WORKDIR path" + Environment.NewLine;

Assert.Equal(expectedOutput, builder.Dockerfile.ToString());
Assert.Equal(expectedOutput, builder.ToString());
Expand Down
156 changes: 156 additions & 0 deletions src/DockerfileModel/DockerfileModel.Tests/WorkdirInstructionTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
using System;
using System.Collections.Generic;
using System.Linq;
using DockerfileModel.Tokens;
using Sprache;
using Xunit;

using static DockerfileModel.Tests.TokenValidator;

namespace DockerfileModel.Tests
{
public class WorkdirInstructionTests
{
[Theory]
[MemberData(nameof(ParseTestInput))]
public void Parse(WorkdirInstructionParseTestScenario scenario)
{
if (scenario.ParseExceptionPosition is null)
{
WorkdirInstruction result = WorkdirInstruction.Parse(scenario.Text, scenario.EscapeChar);
Assert.Equal(scenario.Text, result.ToString());
Assert.Collection(result.Tokens, scenario.TokenValidators);
scenario.Validate?.Invoke(result);
}
else
{
ParseException exception = Assert.Throws<ParseException>(
() => WorkdirInstruction.Parse(scenario.Text, scenario.EscapeChar));
Assert.Equal(scenario.ParseExceptionPosition.Line, exception.Position.Line);
Assert.Equal(scenario.ParseExceptionPosition.Column, exception.Position.Column);
}
}

[Theory]
[MemberData(nameof(CreateTestInput))]
public void Create(CreateTestScenario scenario)
{
WorkdirInstruction result = new WorkdirInstruction(scenario.Path);
Assert.Collection(result.Tokens, scenario.TokenValidators);
scenario.Validate?.Invoke(result);
}

[Fact]
public void Path()
{
WorkdirInstruction result = new WorkdirInstruction("/test");
Assert.Equal("/test", result.Path);
Assert.Equal("/test", result.PathToken.Value);
Assert.Equal("WORKDIR /test", result.ToString());

result.Path = "/test2";
Assert.Equal("/test2", result.Path);
Assert.Equal("/test2", result.PathToken.Value);
Assert.Equal("WORKDIR /test2", result.ToString());

result.PathToken.Value = "/test3";
Assert.Equal("/test3", result.Path);
Assert.Equal("/test3", result.PathToken.Value);
Assert.Equal("WORKDIR /test3", result.ToString());

result.PathToken = new LiteralToken("/test4");
Assert.Equal("/test4", result.Path);
Assert.Equal("/test4", result.PathToken.Value);
Assert.Equal("WORKDIR /test4", result.ToString());

Assert.Throws<ArgumentNullException>(() => result.Path = null);
Assert.Throws<ArgumentException>(() => result.Path = "");
Assert.Throws<ArgumentNullException>(() => result.PathToken = null);
}

[Fact]
public void PathWithVariables()
{
WorkdirInstruction result = new WorkdirInstruction("$var");
TestHelper.TestVariablesWithLiteral(() => result.PathToken, "var", canContainVariables: true);
}

public static IEnumerable<object[]> ParseTestInput()
{
WorkdirInstructionParseTestScenario[] testInputs = new WorkdirInstructionParseTestScenario[]
{
new WorkdirInstructionParseTestScenario
{
Text = "WORKDIR /test",
TokenValidators = new Action<Token>[]
{
token => ValidateKeyword(token, "WORKDIR"),
token => ValidateWhitespace(token, " "),
token => ValidateLiteral(token, "/test")
},
Validate = result =>
{
Assert.Empty(result.Comments);
Assert.Equal("WORKDIR", result.InstructionName);
Assert.Equal("/test", result.Path);
}
},
new WorkdirInstructionParseTestScenario
{
Text = "WORKDIR $TEST",
TokenValidators = new Action<Token>[]
{
token => ValidateKeyword(token, "WORKDIR"),
token => ValidateWhitespace(token, " "),
token => ValidateAggregate<LiteralToken>(token, "$TEST",
token => ValidateAggregate<VariableRefToken>(token, "$TEST",
token => ValidateString(token, "TEST")))
}
},
new WorkdirInstructionParseTestScenario
{
Text = "WORKDIR`\n /test",
EscapeChar = '`',
TokenValidators = new Action<Token>[]
{
token => ValidateKeyword(token, "WORKDIR"),
token => ValidateLineContinuation(token, '`', "\n"),
token => ValidateWhitespace(token, " "),
token => ValidateLiteral(token, "/test")
}
}
};

return testInputs.Select(input => new object[] { input });
}

public static IEnumerable<object[]> CreateTestInput()
{
CreateTestScenario[] testInputs = new CreateTestScenario[]
{
new CreateTestScenario
{
Path = "/test",
TokenValidators = new Action<Token>[]
{
token => ValidateKeyword(token, "WORKDIR"),
token => ValidateWhitespace(token, " "),
token => ValidateLiteral(token, "/test")
}
}
};

return testInputs.Select(input => new object[] { input });
}

public class WorkdirInstructionParseTestScenario : ParseTestScenario<WorkdirInstruction>
{
public char EscapeChar { get; set; }
}

public class CreateTestScenario : TestScenario<WorkdirInstruction>
{
public string Path { get; set; }
}
}
}
6 changes: 6 additions & 0 deletions src/DockerfileModel/DockerfileModel/DockerfileBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,12 @@ public DockerfileBuilder VolumeInstruction(IEnumerable<string> paths) =>
public DockerfileBuilder VolumeInstruction(Action<TokenBuilder> configureBuilder) =>
ParseTokens(configureBuilder, DockerfileModel.VolumeInstruction.Parse);

public DockerfileBuilder WorkdirInstruction(string path) =>
AddConstruct(new WorkdirInstruction(path, EscapeChar));

public DockerfileBuilder WorkdirInstruction(Action<TokenBuilder> configureBuilder) =>
ParseTokens(configureBuilder, DockerfileModel.WorkdirInstruction.Parse);

private DockerfileBuilder ParseTokens(Action<TokenBuilder> configureBuilder, Func<string, DockerfileConstruct> parseConstruct)
{
TokenBuilder builder = new TokenBuilder
Expand Down
2 changes: 1 addition & 1 deletion src/DockerfileModel/DockerfileModel/Instruction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public abstract class Instruction : DockerfileConstruct, ICommentable
{ "STOPSIGNAL", StopSignalInstruction.Parse },
{ "USER", UserInstruction.Parse },
{ "VOLUME", VolumeInstruction.Parse },
{ "WORKDIR", GenericInstruction.Parse },
{ "WORKDIR", WorkdirInstruction.Parse },
};

protected Instruction(IEnumerable<Token> tokens) : base(tokens)
Expand Down
60 changes: 60 additions & 0 deletions src/DockerfileModel/DockerfileModel/WorkdirInstruction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using System.Collections.Generic;
using System.Linq;
using DockerfileModel.Tokens;
using Sprache;
using Validation;
using static DockerfileModel.ParseHelper;

namespace DockerfileModel
{
public class WorkdirInstruction : Instruction
{
public WorkdirInstruction(string path, char escapeChar = Dockerfile.DefaultEscapeChar)
: this(GetTokens(path, escapeChar))
{
}

private WorkdirInstruction(IEnumerable<Token> tokens) : base(tokens)
{
}

public string Path
{
get => PathToken.Value;
set
{
Requires.NotNullOrEmpty(value, nameof(value));
PathToken.Value = value;
}
}

public LiteralToken PathToken
{
get => Tokens.OfType<LiteralToken>().First();
set
{
Requires.NotNull(value, nameof(value));
SetToken(PathToken, value);
}
}

public static WorkdirInstruction Parse(string text, char escapeChar = Dockerfile.DefaultEscapeChar) =>
new WorkdirInstruction(GetTokens(text, GetInnerParser(escapeChar)));

public static Parser<WorkdirInstruction> GetParser(char escapeChar = Dockerfile.DefaultEscapeChar) =>
from tokens in GetInnerParser(escapeChar)
select new WorkdirInstruction(tokens);

internal static Parser<IEnumerable<Token>> GetInnerParser(char escapeChar) =>
Instruction("WORKDIR", escapeChar, GetArgsParser(escapeChar));

private static IEnumerable<Token> GetTokens(string path, char escapeChar)
{
Requires.NotNullOrEmpty(path, nameof(path));
return GetTokens($"WORKDIR {path}", GetInnerParser(escapeChar));
}

private static Parser<IEnumerable<Token>> GetArgsParser(char escapeChar) =>
ArgTokens(LiteralWithVariables(escapeChar, whitespaceMode: WhitespaceMode.Allowed).AsEnumerable(), escapeChar);
}
}