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  }