Skip to content
Open
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
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,11 @@
<version>${jacoco.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<version>2.7.18</version>
</dependency>
</dependencies>

<distributionManagement>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,16 @@ public class CheckRepository {
TrailingSpacesCheck.class,
TruthyCheck.class,
QuotedStringsCheck.class,
IntValueInRangeCheck.class
IntValueInRangeCheck.class,
DurationInRangeCheck.class
);

private static final List<String> TEMPLATE_RULE_KEYS = Arrays.asList(
"ForbiddenKeyCheck",
"ForbiddenValueCheck",
"RequiredKeyCheck",
"IntValueInRangeCheck"
"IntValueInRangeCheck",
"DurationInRangeCheck"
);


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* Copyright (c) 2018-2023, Sylvain Baudoin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.sbaudoin.sonar.plugins.yaml.checks;

import com.github.sbaudoin.yamllint.LintScanner;
import org.sonar.check.Rule;
import org.sonar.check.RuleProperty;
import org.yaml.snakeyaml.tokens.ScalarToken;
import org.yaml.snakeyaml.tokens.Token;
import org.yaml.snakeyaml.tokens.ValueToken;
import org.springframework.boot.convert.DurationStyle;

import java.time.Duration;

/**
* Check to be used that the YAML file does not contain duration values out of the specified range
*/
@Rule(key = "DurationInRangeCheck")
public class DurationInRangeCheck extends ForbiddenCheck {
@RuleProperty(key = "minMillisValue", description = "Minimum value in milliseconds")
int minMillis;

@RuleProperty(key = "maxMillisValue", description = "Maximum value in milliseconds")
int maxMillis;

/**
* Takes the next token and, if it is a key that matches the {@code key-name} regex, analyzes its value against the
* {@code value} regex, possibly returning an issue if there is a match
*
* @param parser the scanner that holds the tokens
*/
@Override
protected void checkNextToken(LintScanner parser) {
// Accepted token type: remove it from stack
Token t = parser.getToken();
if (parser.peekToken() instanceof ValueToken) {
parser.getToken();
Token t3 = parser.peekToken();
if (t3 instanceof ScalarToken) {
String strVal = ((ScalarToken)t3).getValue();
try {
Duration dur = DurationStyle.SIMPLE.parse(strVal);
if (dur.toMillis() < minMillis || dur.toMillis() > maxMillis) {
// Report new error
addViolation(getMessage(dur), t);
}
}
catch(IllegalArgumentException | IllegalStateException e) {
addViolation("Parse error: Non-duration found for duration-in-range-check", t);
}
}
}
}

String getMessage(Duration dur) {
return "Duration out of range found, in milliseconds="
+ dur.toMillis() + ", Range: minMillis=" + minMillis + " maxMillis=" + maxMillis;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<p>Use this rule to control that the YAML documents for a specified key, only contain <strong>Duration</strong> values within a specified range.
The simple format used by Spring boots' DurationStyle is used to parse the String and convert into a value in milliseconds. Options are: ns, us, ms, s, m, h, d.
The range can be defined with a minimum and a maximum milliseconds value. The specific key can be defined with a regular expression
and the location of the key can be defined with two ancestor regular expressions.
</p>

<h2>Parameters</h2>
<dl>
<dt>key-name</dt>
<dd>Regular expression that matches keys for which the duration value must be in range. In order to match any key, set it to
<code>.*</code>. The start and end line markers <code>^</code> and <code>$</code> are implicit: this means that
setting <code>foo</code> is equivalent to <code>^foo$</code>.</dd>
<dt>included-ancestors</dt>
<dd>Regular expression that matches against an ancestor string made of joining the parent chain of the matching key.
The parents are separated by a colon (<code>:</code>) and always start with an implicit <code>&lt;root&gt;</code> parent.
So, the ancestor string for a connectionTimeout key could be: <code>&lt;root&gt;:resilience4j:circuitbreaker.*</code>.
If the includedAncestors regex matches the ancestor string of the key, the value is range checked.
The start and end line markers <code>^</code> and <code>$</code> are implicit, just like the key regex.
Leave empty for no ancestor matching.</dd>
<dt>excluded-ancestors</dt>
<dd>Regular expression that matches against the ancestor string of the matching key, just like above.
However, if this regex matches, the value is *not* range checked.
The start and end line markers <code>^</code> and <code>$</code> are implicit, just like the key regex.
Leave empty for no ancestor matching.</dd>
<dt>minMillis</dt>
<dd>Integer defining the minimum allowed value in milliseconds.</dd>
<dt>maxMillis</dt>
<dd>Integer defining the maximum allowed value in milliseconds.</dd>
</dl>

<h2>Examples</h2>


<p>With:</p>
<pre>
key-name = waitDurationInOpenState|maxWaitDurationInHalfOpenState
included-ancestors = &lt;root&gt;:resilience4j:circuitbreaker.*
excluded-ancestors =
minMillis = 2000
maxMillis = 60000
</pre>
<p>the following code snippet would <strong>PASS</strong>:</p>
<pre>
resilience4j:
circuitbreaker:
instances:
CosmosDbClientGood:
waitDurationInOpenState: 50s
maxWaitDurationInHalfOpenState: 10s
</pre>
<p>the following code snippet would <strong>FAIL</strong>:</p>
<pre>
resilience4j:
circuitbreaker:
instances:
CosmosDbClientBad:
waitDurationInOpenState: 1s # violation
maxWaitDurationInHalfOpenState: 5m # violation
</pre>


Current limitation: yaml list notation is not supported by ancestor matching.




Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"title": "YAML documents should for a specified key only have duration values in a given range",
"type": "CODE_SMELL",
"status": "ready",
"remediation": {
"func": "Constant\/Issue",
"constantCost": "2min"
},
"tags": [
"convention"
],
"defaultSeverity": "Critical"
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,15 @@ void testGetParsingErrorCheckClass() {

@Test
void testGetCheckClasses() {
assertEquals(28, CheckRepository.getCheckClasses().size());
assertEquals(29, CheckRepository.getCheckClasses().size());
assertTrue(CheckRepository.getCheckClasses().contains(ParsingErrorCheck.class));
}

@Test
void testGetTemplateRuleKeys() {
assertEquals(4, CheckRepository.getTemplateRuleKeys().size());
assertEquals(5, CheckRepository.getTemplateRuleKeys().size());
assertTrue(CheckRepository.getTemplateRuleKeys().contains("ForbiddenKeyCheck"));
assertTrue(CheckRepository.getTemplateRuleKeys().contains("IntValueInRangeCheck"));
assertTrue(CheckRepository.getTemplateRuleKeys().contains("DurationInRangeCheck"));
}
}
Loading