Skip to content

Commit

Permalink
[FIXED JENKINS-42771] Allow + binary expressions in env values
Browse files Browse the repository at this point in the history
This worked previously because we didn't actually pay attention to
what was really in the value for an environment variable in the AST
model, only at runtime. And the runtime parsing worked differently, so
it did fine with `FOO = "A" + "B" + "C"`. Well, now so does the AST
model. Yay!
  • Loading branch information
abayer committed Mar 14, 2017
1 parent 6ead78c commit 577eb30
Show file tree
Hide file tree
Showing 6 changed files with 199 additions and 2 deletions.
Expand Up @@ -244,10 +244,22 @@ class ModelParser implements Parser {
r.variables[key] = parseArgument(exp.rightExpression, true)
return
} else if (exp.rightExpression instanceof MethodCallExpression) {
r.variables[key] = parseInternalFunctionCall((MethodCallExpression)exp.rightExpression)
r.variables[key] = parseInternalFunctionCall((MethodCallExpression) exp.rightExpression)
return
} else if (exp.rightExpression instanceof BinaryExpression) {
if (((BinaryExpression)exp.rightExpression).operation.type == Types.PLUS) {
// This is to support JENKINS-42771, allowing `FOO = "b" + "a" + "r"` sorts of syntax.
String envValue = envValueForStringConcat((BinaryExpression) exp.rightExpression)
if (envValue != null) {
r.variables[key] = ModelASTValue.fromConstant(envValue, exp.rightExpression)
}
return
} else {
errorCollector.error(new ModelASTKey(exp.rightExpression), Messages.ModelParser_InvalidEnvironmentOperation())
return
}
} else {
errorCollector.error(key, Messages.ModelParser_InvalidEnvironmentValue())
errorCollector.error(new ModelASTKey(exp.rightExpression), Messages.ModelParser_InvalidEnvironmentValue())
return
}
}
Expand All @@ -271,6 +283,71 @@ class ModelParser implements Parser {
return r;
}

/**
* This works functionally equivalent to the string used in {@link #parseArgument}, but without returning a
* {@link ModelASTValue}, returning just a string instead, and without handling {@link MapExpression} or
* {@link VariableExpression}, which is exclusively used for the `agent any` and `agent none` shortcuts regardless.
*
* @param e A non-null expression. If the expression is anything but a constant or a GString, an error will be thrown.
* @return The string value for that expression - in the case of {@link GStringExpression}, we explicitly remove the
* quotes from around it so that it's suitable for string concatenation. Returns null if an error was encountered.
*/
@CheckForNull
private String envStringFromArbitraryExpression(@Nonnull Expression e) {
StringBuilder builder = new StringBuilder()
if (e instanceof ConstantExpression) {
builder.append(e.value)
} else if (e instanceof GStringExpression) {
String rawSrc = getSourceText(e)
builder.append(rawSrc.substring(1, rawSrc.length() - 1))
} else {
errorCollector.error(new ModelASTKey(e), Messages.ModelParser_InvalidEnvironmentConcatValue())
return null
}
return builder.toString()
}

/**
* Traverses a {@link BinaryExpression} known to be a {@link Types#PLUS}, to concatenate its various subexpressions
* together as string values.
* @param exp A non-null binary expression
* @return The concatenated string equivalent of that binary expression, assuming no errors were encountered on the
* various subexpressions, in which case it will return null.
*/
@CheckForNull
private String envValueForStringConcat(@Nonnull BinaryExpression exp) {
StringBuilder builder = new StringBuilder()

if (exp.leftExpression instanceof BinaryExpression) {
if (((BinaryExpression)exp.leftExpression).operation.type == Types.PLUS) {
String nestedString = envValueForStringConcat((BinaryExpression) exp.leftExpression)
if (nestedString == null) {
return null
} else {
builder.append(nestedString)
}
} else {
errorCollector.error(new ModelASTKey(exp), Messages.ModelParser_InvalidEnvironmentOperation())
return
}
} else {
String leftExpString = envStringFromArbitraryExpression(exp.leftExpression)
if (leftExpString == null) {
return null
} else {
builder.append(leftExpString)
}
}
String thisString = envStringFromArbitraryExpression(exp.rightExpression)
if (thisString == null) {
return null
} else {
builder.append(thisString)
}

return builder.toString()
}

public @Nonnull ModelASTLibraries parseLibraries(Statement stmt) {
def r = new ModelASTLibraries(stmt);

Expand Down
Expand Up @@ -57,6 +57,8 @@ ModelParser.ExpectedWhen=Expected a when condition
ModelParser.InvalidAgent=Only "agent none", "agent any" or "agent '{'...}" are allowed.
ModelParser.InvalidBuildCondition=The 'post' section can only contain build condition names with code blocks. Valid condition names are {0}
ModelParser.InvalidEnvironmentIdentifier="{0}" is not a valid environment expression. Use "key = value" pairs with valid Java/shell keys.
ModelParser.InvalidEnvironmentOperation=Environment variable values can only be joined together with '+'s.
ModelParser.InvalidEnvironmentConcatValue=Environment variable values to be concatenated together must be strings.
ModelParser.InvalidEnvironmentValue=Environment variable values must either be strings or function calls.
ModelParser.InvalidInternalFunctionArg=Internal function call parameters must be strings.
ModelParser.InvalidSectionDefinition=Not a valid section definition: "{0}". Some extra configuration is required.
Expand Down
Expand Up @@ -56,6 +56,16 @@ public void simpleEnvironment() throws Exception {
.go();
}

@Issue("JENKINS-42771")
@Test
public void multiExpressionEnvironment() throws Exception {
expect("multiExpressionEnvironment")
.logContains("[Pipeline] { (foo)",
"FOO is BAR",
"_UNDERSCORE is VALID")
.go();
}

@Test
public void environmentInStage() throws Exception {
expect("environmentInStage")
Expand Down
Expand Up @@ -566,4 +566,13 @@ public void undefinedSectionReferencesCorrectly() throws Exception {
.go();
}

@Issue("JENKINS-42771")
@Test
public void invalidMultiExpressionEnvironment() throws Exception {
expectError("invalidMultiExpressionEnvironment")
.logContains(Messages.ModelParser_InvalidEnvironmentOperation(),
Messages.ModelParser_InvalidEnvironmentConcatValue())
.go();
}

}
@@ -0,0 +1,50 @@
/*
* The MIT License
*
* Copyright (c) 2017, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

pipeline {
environment {
FOO =
"B" -
"A" +
"R"
BAR = 1 + credentials("foo")
_UNDERSCORE = "VALID"
}

agent {
label "some-label"
}

stages {
stage("foo") {
steps {
sh 'echo "FOO is $FOO"'
sh 'echo "_UNDERSCORE is $_UNDERSCORE"'
}
}
}
}



@@ -0,0 +1,49 @@
/*
* The MIT License
*
* Copyright (c) 2017, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

pipeline {
environment {
FOO =
"B" +
"${'A'}" +
"R"
_UNDERSCORE = "VALID"
}

agent {
label "some-label"
}

stages {
stage("foo") {
steps {
sh 'echo "FOO is $FOO"'
sh 'echo "_UNDERSCORE is $_UNDERSCORE"'
}
}
}
}



0 comments on commit 577eb30

Please sign in to comment.