github.com/jfrog/frogbot/v2@v2.21.0/utils/outputwriter/outputwriter.go (about) 1 package outputwriter 2 3 import ( 4 "fmt" 5 "strings" 6 7 "github.com/jfrog/froggit-go/vcsclient" 8 "github.com/jfrog/froggit-go/vcsutils" 9 "github.com/jfrog/jfrog-client-go/utils/log" 10 ) 11 12 const ( 13 SecretsEmailCSS = `body { 14 font-family: Arial, sans-serif; 15 background-color: #f5f5f5; 16 } 17 table { 18 border-collapse: collapse; 19 width: 80%; 20 } 21 th, td { 22 padding: 10px; 23 border: 1px solid #ccc; 24 } 25 th { 26 background-color: #f2f2f2; 27 } 28 tr:nth-child(even) { 29 background-color: #f9f9f9; 30 } 31 tr:hover { 32 background-color: #f5f5f5; 33 } 34 .table-container { 35 max-width: 700px; 36 padding: 20px; 37 box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); 38 border-radius: 10px; 39 overflow: hidden; 40 background-color: #fff; 41 margin-top: 10px; 42 } 43 .ignore-comments { 44 margin-top: 10px; 45 margin-bottom: 5px; 46 border-radius: 5px; 47 }` 48 //#nosec G101 -- full secrets would not be hard coded 49 SecretsEmailHTMLTemplate = ` 50 <!DOCTYPE html> 51 <html> 52 <head> 53 <title>Frogbot Secret Detection</title> 54 <style> 55 %s 56 </style> 57 </head> 58 <body> 59 <div> 60 The following potential exposed secrets in your <a href="%s">%s</a> have been detected by <a href="https://docs.jfrog-applications.jfrog.io/jfrog-applications/frogbot">Frogbot</a> 61 <br/> 62 <table class="table-container"> 63 <thead> 64 <tr> 65 <th>FILE</th> 66 <th>LINE:COLUMN</th> 67 <th>SECRET</th> 68 </tr> 69 </thead> 70 <tbody> 71 %s 72 </tbody> 73 </table> 74 <div class="ignore-comments"> 75 <b>NOTE:</b> If you'd like Frogbot to ignore the lines with the potential secrets, add a comment that includes the <b>jfrog-ignore</b> keyword above the lines with the secrets. 76 </div> 77 </div> 78 </body> 79 </html>` 80 //#nosec G101 -- full secrets would not be hard coded 81 SecretsEmailTableRow = ` 82 <tr> 83 <td> %s </td> 84 <td> %d:%d </td> 85 <td> %s </td> 86 </tr>` 87 ) 88 89 // The OutputWriter interface allows Frogbot output to be written in an appropriate way for each git provider. 90 // Some git providers support markdown only partially, whereas others support it fully. 91 type OutputWriter interface { 92 // Options 93 SetJasOutputFlags(entitled, showCaColumn bool) 94 IsShowingCaColumn() bool 95 IsEntitledForJas() bool 96 SetAvoidExtraMessages(avoidExtraMessages bool) 97 AvoidExtraMessages() bool 98 SetPullRequestCommentTitle(pullRequestCommentTitle string) 99 PullRequestCommentTitle() string 100 SetHasInternetConnection(connected bool) 101 HasInternetConnection() bool 102 SizeLimit(comment bool) int 103 SetSizeLimit(client vcsclient.VcsClient) 104 // VCS info 105 VcsProvider() vcsutils.VcsProvider 106 SetVcsProvider(provider vcsutils.VcsProvider) 107 // Markdown interface 108 FormattedSeverity(severity, applicability string) string 109 Separator() string 110 MarkInCenter(content string) string 111 MarkAsDetails(summary string, subTitleDepth int, content string) string 112 MarkAsTitle(title string, subTitleDepth int) string 113 Image(source ImageSource) string 114 } 115 116 type MarkdownOutput struct { 117 pullRequestCommentTitle string 118 avoidExtraMessages bool 119 showCaColumn bool 120 entitledForJas bool 121 hasInternetConnection bool 122 descriptionSizeLimit int 123 commentSizeLimit int 124 vcsProvider vcsutils.VcsProvider 125 } 126 127 type CommentDecorator func(int, string) string 128 129 func (mo *MarkdownOutput) SetVcsProvider(provider vcsutils.VcsProvider) { 130 mo.vcsProvider = provider 131 } 132 133 func (mo *MarkdownOutput) VcsProvider() vcsutils.VcsProvider { 134 return mo.vcsProvider 135 } 136 137 func (mo *MarkdownOutput) SetAvoidExtraMessages(avoidExtraMessages bool) { 138 mo.avoidExtraMessages = avoidExtraMessages 139 } 140 141 func (mo *MarkdownOutput) AvoidExtraMessages() bool { 142 return mo.avoidExtraMessages 143 } 144 145 func (mo *MarkdownOutput) SetHasInternetConnection(connected bool) { 146 mo.hasInternetConnection = connected 147 } 148 149 func (mo *MarkdownOutput) HasInternetConnection() bool { 150 return mo.hasInternetConnection 151 } 152 153 func (mo *MarkdownOutput) SetJasOutputFlags(entitled, showCaColumn bool) { 154 mo.entitledForJas = entitled 155 mo.showCaColumn = showCaColumn 156 } 157 158 func (mo *MarkdownOutput) SetPullRequestCommentTitle(pullRequestCommentTitle string) { 159 mo.pullRequestCommentTitle = pullRequestCommentTitle 160 } 161 162 func (mo *MarkdownOutput) IsShowingCaColumn() bool { 163 return mo.showCaColumn 164 } 165 166 func (mo *MarkdownOutput) IsEntitledForJas() bool { 167 return mo.entitledForJas 168 } 169 170 func (mo *MarkdownOutput) PullRequestCommentTitle() string { 171 return mo.pullRequestCommentTitle 172 } 173 174 func (mo *MarkdownOutput) SizeLimit(comment bool) int { 175 if comment { 176 return mo.commentSizeLimit 177 } 178 return mo.descriptionSizeLimit 179 } 180 181 func (mo *MarkdownOutput) SetSizeLimit(client vcsclient.VcsClient) { 182 if client == nil { 183 return 184 } 185 mo.commentSizeLimit = client.GetPullRequestCommentSizeLimit() 186 mo.descriptionSizeLimit = client.GetPullRequestDetailsSizeLimit() 187 } 188 189 func GetMarkdownSizeLimit(client vcsclient.VcsClient) int { 190 limit := client.GetPullRequestCommentSizeLimit() 191 if client.GetPullRequestDetailsSizeLimit() < limit { 192 limit = client.GetPullRequestDetailsSizeLimit() 193 } 194 return limit 195 } 196 197 func GetCompatibleOutputWriter(provider vcsutils.VcsProvider) OutputWriter { 198 switch provider { 199 case vcsutils.BitbucketServer: 200 return &SimplifiedOutput{MarkdownOutput{vcsProvider: provider, hasInternetConnection: true}} 201 default: 202 return &StandardOutput{MarkdownOutput{vcsProvider: provider, hasInternetConnection: true}} 203 } 204 } 205 206 func MarkdownComment(text string) string { 207 return fmt.Sprintf("\n\n[comment]: <> (%s)\n", text) 208 } 209 210 func MarkAsBold(content string) string { 211 return fmt.Sprintf("**%s**", content) 212 } 213 214 func MarkAsQuote(content string) string { 215 return fmt.Sprintf("`%s`", content) 216 } 217 218 func MarkAsLink(content, link string) string { 219 return fmt.Sprintf("[%s](%s)", content, link) 220 } 221 222 func SectionDivider() string { 223 return "\n---" 224 } 225 226 func MarkAsCodeSnippet(snippet string) string { 227 return fmt.Sprintf("```\n%s\n```", snippet) 228 } 229 230 func WriteContent(builder *strings.Builder, contents ...string) { 231 for _, content := range contents { 232 fmt.Fprintf(builder, "\n%s", content) 233 } 234 } 235 236 func WriteNewLine(builder *strings.Builder) { 237 builder.WriteString("\n") 238 } 239 240 // ConvertContentToComments converts the given content to comments, and returns the comments as a list of strings. 241 // The content is split into comments based on the size limit of the output writer. 242 // The commentDecorators are applied to each comment. 243 func ConvertContentToComments(content []string, writer OutputWriter, commentDecorators ...CommentDecorator) (comments []string) { 244 commentBuilder := strings.Builder{} 245 for _, commentContent := range content { 246 if newContent, limitReached := getContentAndResetBuilderIfLimitReached(len(comments), commentContent, &commentBuilder, writer, commentDecorators...); limitReached { 247 comments = append(comments, newContent) 248 } 249 WriteContent(&commentBuilder, commentContent) 250 } 251 if commentBuilder.Len() > 0 || len(content) == 0 { 252 comments = append(comments, decorate(len(comments), commentBuilder.String(), commentDecorators...)) 253 } 254 return 255 } 256 257 func getContentAndResetBuilderIfLimitReached(commentCount int, newContent string, builder *strings.Builder, writer OutputWriter, commentDecorators ...CommentDecorator) (content string, reached bool) { 258 limit := writer.SizeLimit(commentCount != 0) 259 if limit == 0 { 260 // No limit 261 return 262 } 263 if builder.Len()+decoratorsSize(commentCount, commentDecorators...)+len(newContent) < limit { 264 return 265 } 266 // Limit reached - Add the current content as a comment to the list and reset the builder 267 log.Debug(fmt.Sprintf("Content size limit reached (%d), splitting. (total comments for content: %d)", limit, commentCount+1)) 268 content = builder.String() 269 builder.Reset() 270 return decorate(commentCount, content, commentDecorators...), true 271 } 272 273 func decorate(commentCount int, content string, commentDecorators ...CommentDecorator) string { 274 for _, decorator := range commentDecorators { 275 content = decorator(commentCount, content) 276 } 277 return content 278 } 279 280 func decoratorsSize(commentCount int, decorators ...CommentDecorator) (size int) { 281 for _, decorator := range decorators { 282 size += len(decorator(commentCount, "")) 283 } 284 return 285 }