Skip to content

Commit 64e26f8

Browse files
DENA-671: comments validation for properties with bytes (#32)
* Extracted method for reporting the human-readable comment * Implemented handling comments for byte values * Added more tests * Added handling of retention.bytes * Updated docs * Use binary prefixes * Update rules/msk_topic_config_comments.md Co-authored-by: Matt Hughes <[email protected]> * Added test for invalid value for retention bytes * Minor code update Co-authored-by: Matt Hughes <[email protected]> * Minor code update --------- Co-authored-by: Matt Hughes <[email protected]>
1 parent 2769335 commit 64e26f8

3 files changed

+298
-22
lines changed

rules/msk_topic_config_comments.go

+132-17
Original file line numberDiff line numberDiff line change
@@ -86,14 +86,14 @@ func (r *MSKTopicConfigCommentsRule) validateTopicConfigComments(runner tflint.R
8686
return nil
8787
}
8888

89-
type configTimeValueCommentInfo struct {
89+
type configValueCommentInfo struct {
9090
key string
9191
infiniteValue string
9292
baseComment string
9393
issueWhenInvalid bool
9494
}
9595

96-
var configTimeValueCommentInfos = []configTimeValueCommentInfo{
96+
var configTimeValueCommentInfos = []configValueCommentInfo{
9797
{
9898
key: retentionTimeAttr,
9999
infiniteValue: "-1",
@@ -114,6 +114,21 @@ var configTimeValueCommentInfos = []configTimeValueCommentInfo{
114114
},
115115
}
116116

117+
var configByteValueCommentInfos = []configValueCommentInfo{
118+
{
119+
key: "max.message.bytes",
120+
infiniteValue: "",
121+
baseComment: "allow for a batch of records maximum",
122+
issueWhenInvalid: true,
123+
},
124+
{
125+
key: "retention.bytes",
126+
infiniteValue: "-1",
127+
baseComment: "keep on each partition",
128+
issueWhenInvalid: true,
129+
},
130+
}
131+
117132
func (r *MSKTopicConfigCommentsRule) validateConfigValuesInComments(
118133
runner tflint.Runner,
119134
configKeyToPairMap map[string]hcl.KeyValuePair,
@@ -123,16 +138,22 @@ func (r *MSKTopicConfigCommentsRule) validateConfigValuesInComments(
123138
return err
124139
}
125140
}
141+
for _, configValueInfo := range configByteValueCommentInfos {
142+
if err := r.validateByteConfigValue(runner, configKeyToPairMap, configValueInfo); err != nil {
143+
return err
144+
}
145+
}
126146

127147
return nil
128148
}
129149

130150
func (r *MSKTopicConfigCommentsRule) validateTimeConfigValue(
131151
runner tflint.Runner,
132152
configKeyToPairMap map[string]hcl.KeyValuePair,
133-
configValueInfo configTimeValueCommentInfo,
153+
configValueInfo configValueCommentInfo,
134154
) error {
135-
timePair, hasConfig := configKeyToPairMap[configValueInfo.key]
155+
key := configValueInfo.key
156+
timePair, hasConfig := configKeyToPairMap[key]
136157
if !hasConfig {
137158
return nil
138159
}
@@ -145,39 +166,70 @@ func (r *MSKTopicConfigCommentsRule) validateTimeConfigValue(
145166
return nil
146167
}
147168

148-
comment, err := r.getExistingComment(runner, timePair)
169+
return r.reportHumanReadableComment(runner, timePair, key, msg)
170+
}
171+
172+
func (r *MSKTopicConfigCommentsRule) validateByteConfigValue(
173+
runner tflint.Runner,
174+
configKeyToPairMap map[string]hcl.KeyValuePair,
175+
configValueInfo configValueCommentInfo,
176+
) error {
177+
key := configValueInfo.key
178+
dataPair, hasConfig := configKeyToPairMap[key]
179+
if !hasConfig {
180+
return nil
181+
}
182+
183+
msg, err := r.buildDataSizeComment(runner, dataPair, configValueInfo)
184+
if err != nil {
185+
return err
186+
}
187+
if msg == "" {
188+
return nil
189+
}
190+
191+
return r.reportHumanReadableComment(runner, dataPair, key, msg)
192+
}
193+
194+
func (r *MSKTopicConfigCommentsRule) reportHumanReadableComment(
195+
runner tflint.Runner,
196+
keyValuePair hcl.KeyValuePair,
197+
key string,
198+
commentMsg string,
199+
) error {
200+
comment, err := r.getExistingComment(runner, keyValuePair)
149201
if err != nil {
150202
return err
151203
}
152204

153205
if comment == nil {
154206
err := runner.EmitIssueWithFix(
155207
r,
156-
fmt.Sprintf("%s must have a comment with the human readable value: adding it ...", configValueInfo.key),
157-
timePair.Key.Range(),
208+
fmt.Sprintf("%s must have a comment with the human readable value: adding it ...", key),
209+
keyValuePair.Key.Range(),
158210
func(f tflint.Fixer) error {
159-
return f.InsertTextAfter(timePair.Value.Range(), msg)
211+
return f.InsertTextAfter(keyValuePair.Value.Range(), commentMsg)
160212
},
161213
)
162214
if err != nil {
163-
return fmt.Errorf("emitting issue: no comment for time value: %w", err)
215+
return fmt.Errorf("emitting issue: no comment for human readable value: %w", err)
164216
}
165217
return nil
166218
}
167219

168220
commentTxt := strings.TrimSpace(string(comment.Bytes))
169-
if commentTxt != msg {
221+
if commentTxt != commentMsg {
170222
issueMsg := fmt.Sprintf(
171223
"%s value doesn't correspond to the human readable value in the comment: fixing it ...",
172-
configValueInfo.key,
224+
key,
173225
)
174226
err := runner.EmitIssueWithFix(r, issueMsg, comment.Range,
175227
func(f tflint.Fixer) error {
176-
return f.ReplaceText(comment.Range, msg+"\n")
228+
return f.ReplaceText(comment.Range, commentMsg+"\n")
177229
},
178230
)
179231
if err != nil {
180-
return fmt.Errorf("emitting issue: wrong comment for time value: %w", err)
232+
return fmt.Errorf("emitting issue: wrong comment for human readable value: %w", err)
181233
}
182234
}
183235
return nil
@@ -243,7 +295,7 @@ func isNotComment(token hclsyntax.Token) bool {
243295
func (r *MSKTopicConfigCommentsRule) buildDurationComment(
244296
runner tflint.Runner,
245297
timePair hcl.KeyValuePair,
246-
configValueInfo configTimeValueCommentInfo,
298+
configValueInfo configValueCommentInfo,
247299
) (string, error) {
248300
var timeVal string
249301
diags := gohcl.DecodeExpression(timePair.Value, nil, &timeVal)
@@ -271,10 +323,73 @@ func (r *MSKTopicConfigCommentsRule) buildDurationComment(
271323
return "", nil
272324
}
273325

274-
baseComment := configValueInfo.baseComment
326+
return buildCommentForMillis(timeMillis, configValueInfo.baseComment), nil
327+
}
328+
329+
func (r *MSKTopicConfigCommentsRule) buildDataSizeComment(
330+
runner tflint.Runner,
331+
dataPair hcl.KeyValuePair,
332+
configValueInfo configValueCommentInfo,
333+
) (string, error) {
334+
var dataVal string
335+
diags := gohcl.DecodeExpression(dataPair.Value, nil, &dataVal)
336+
if diags.HasErrors() {
337+
return "", diags
338+
}
339+
340+
if dataVal == configValueInfo.infiniteValue {
341+
return fmt.Sprintf("# %s unlimited data", configValueInfo.baseComment), nil
342+
}
275343

276-
msg := buildCommentForMillis(timeMillis, baseComment)
277-
return msg, nil
344+
byteVal, err := strconv.Atoi(dataVal)
345+
if err != nil {
346+
if configValueInfo.issueWhenInvalid {
347+
issueMsg := fmt.Sprintf(
348+
"%s must have a valid integer value expressed in bytes",
349+
configValueInfo.key,
350+
)
351+
err := runner.EmitIssue(r, issueMsg, dataPair.Value.Range())
352+
if err != nil {
353+
return "", fmt.Errorf("emitting issue: invalid data value: %w", err)
354+
}
355+
}
356+
357+
return "", nil
358+
}
359+
360+
return buildCommentForBytes(byteVal, configValueInfo.baseComment), nil
361+
}
362+
363+
func buildCommentForBytes(bytes int, baseComment string) string {
364+
byteUnits, unit := determineByteUnits(bytes)
365+
366+
byteUnitsStr := strconv.FormatFloat(byteUnits, 'f', -1, 64)
367+
return fmt.Sprintf("# %s %s%s", baseComment, byteUnitsStr, unit)
368+
}
369+
370+
const (
371+
bytesInOneKiB = 1024
372+
bytesInOneMiB = 1024 * bytesInOneKiB
373+
bytesInOneGiB = 1024 * bytesInOneMiB
374+
)
375+
376+
func determineByteUnits(bytes int) (float64, string) {
377+
floatBytes := float64(bytes)
378+
gbs := round(floatBytes / bytesInOneGiB)
379+
if gbs >= 1 {
380+
return gbs, "GiB"
381+
}
382+
383+
mbs := round(floatBytes / bytesInOneMiB)
384+
if mbs >= 1 {
385+
return mbs, "MiB"
386+
}
387+
388+
kbs := round(floatBytes / bytesInOneKiB)
389+
if kbs >= 1 {
390+
return kbs, "KiB"
391+
}
392+
return floatBytes, "B"
278393
}
279394

280395
func buildCommentForMillis(timeMillis int, baseComment string) string {

rules/msk_topic_config_comments.md

+6-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Requirements
44

5-
Topic configurations expressed in milliseconds must have comments explaining the property and including the human-readable value.
5+
Topic configurations expressed in milliseconds and bytes must have comments explaining the property and including the human-readable value.
66
The comments can be placed after the property definition on the same line or on the line before the definition.
77

88
For computing the human-readable values it considers the following:
@@ -13,7 +13,8 @@ It currently checks the properties:
1313
- retention.ms: explanation must start with `keep data`
1414
- local.retention.ms: explanation must start with `keep data in primary storage`
1515
- max.compaction.lag.ms: explanation must start with `allow not compacted keys maximum`
16-
16+
- retention.bytes: explanation must start with `keep on each partition`
17+
- max.message.bytes: explanation must start with `allow for a batch of records maximum`
1718
## Example
1819

1920
### Good example
@@ -28,7 +29,9 @@ resource "kafka_topic" "good_topic" {
2829
"cleanup.policy" = "delete"
2930
# keep data in primary storage for 1 day
3031
"local.retention.ms" = "86400000"
31-
"retention.ms" = "2592000000" # keep data for 1 month
32+
"retention.ms" = "2592000000" # keep data for 1 month
33+
"max.message.bytes" = "3145728" # allow for a batch of records maximum 3MiB
34+
"retention.bytes" = "1610612736" # keep on each partition 1.5GiB
3235
"compression.type" = "zstd"
3336
}
3437
}

0 commit comments

Comments
 (0)