github.com/mongodb/grip@v0.0.0-20240213223901-f906268d82b9/send/github_status.go (about)

     1  package send
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"log"
     7  	"os"
     8  	"time"
     9  
    10  	"github.com/google/go-github/v53/github"
    11  	"github.com/mongodb/grip/message"
    12  	"github.com/pkg/errors"
    13  	"go.opentelemetry.io/otel/attribute"
    14  	"go.opentelemetry.io/otel/codes"
    15  	"go.opentelemetry.io/otel/trace"
    16  )
    17  
    18  type githubStatusMessageLogger struct {
    19  	opts *GithubOptions
    20  	ref  string
    21  
    22  	gh githubClient
    23  	*Base
    24  }
    25  
    26  func (s *githubStatusMessageLogger) Send(m message.Composer) {
    27  	if s.Level().ShouldLog(m) {
    28  		var status *github.RepoStatus
    29  		owner := ""
    30  		repo := ""
    31  		ref := ""
    32  
    33  		switch v := m.Raw().(type) {
    34  		case *message.GithubStatus:
    35  			status = githubStatusMessagePayloadToRepoStatus(v)
    36  			if v != nil {
    37  				owner = v.Owner
    38  				repo = v.Repo
    39  				ref = v.Ref
    40  			}
    41  		case message.GithubStatus:
    42  			status = githubStatusMessagePayloadToRepoStatus(&v)
    43  			owner = v.Owner
    44  			repo = v.Repo
    45  			ref = v.Ref
    46  
    47  		case *message.Fields:
    48  			status = s.githubMessageFieldsToStatus(v)
    49  			owner, repo, ref = githubMessageFieldsToRepo(v)
    50  		case message.Fields:
    51  			status = s.githubMessageFieldsToStatus(&v)
    52  			owner, repo, ref = githubMessageFieldsToRepo(&v)
    53  		}
    54  		if len(owner) == 0 {
    55  			owner = s.opts.Account
    56  		}
    57  		if len(repo) == 0 {
    58  			owner = s.opts.Repo
    59  		}
    60  		if len(ref) == 0 {
    61  			owner = s.ref
    62  		}
    63  		if status == nil {
    64  			s.ErrorHandler()(errors.New("composer cannot be converted to GitHub status"), m)
    65  			return
    66  		}
    67  
    68  		ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
    69  		defer cancel()
    70  
    71  		ctx, span := tracer.Start(ctx, "CreateStatus", trace.WithAttributes(
    72  			attribute.String(githubEndpointAttribute, "CreateStatus"),
    73  			attribute.String(githubOwnerAttribute, owner),
    74  			attribute.String(githubRepoAttribute, repo),
    75  			attribute.String(githubRefAttribute, ref),
    76  		))
    77  		defer span.End()
    78  
    79  		if _, resp, err := s.gh.CreateStatus(ctx, owner, repo, ref, status); err != nil {
    80  			s.ErrorHandler()(errors.Wrap(err, "sending GitHub create status request"), m)
    81  
    82  			span.RecordError(err)
    83  			span.SetStatus(codes.Error, "sending status")
    84  		} else if err = handleHTTPResponseError(resp.Response); err != nil {
    85  			s.ErrorHandler()(errors.Wrap(err, "creating GitHub status"), m)
    86  
    87  			span.RecordError(err)
    88  			span.SetStatus(codes.Error, "sending status")
    89  		}
    90  	}
    91  }
    92  
    93  func (s *githubStatusMessageLogger) Flush(_ context.Context) error { return nil }
    94  
    95  // NewGithubStatusLogger returns a Sender to send payloads to the Github Status
    96  // API. Statuses will be attached to the given ref.
    97  func NewGithubStatusLogger(name string, opts *GithubOptions, ref string) (Sender, error) {
    98  	opts.populate()
    99  	s := &githubStatusMessageLogger{
   100  		Base: NewBase(name),
   101  		gh:   &githubClientImpl{},
   102  		ref:  ref,
   103  	}
   104  
   105  	s.gh.Init(opts.Token, opts.MaxAttempts, opts.MinDelay)
   106  
   107  	fallback := log.New(os.Stdout, "", log.LstdFlags)
   108  	if err := s.SetErrorHandler(ErrorHandlerFromLogger(fallback)); err != nil {
   109  		return nil, err
   110  	}
   111  
   112  	if err := s.SetFormatter(MakePlainFormatter()); err != nil {
   113  		return nil, err
   114  	}
   115  
   116  	s.reset = func() {
   117  		fallback.SetPrefix(fmt.Sprintf("[%s] [%s/%s] ", s.Name(), opts.Account, opts.Repo))
   118  	}
   119  
   120  	s.SetName(name)
   121  
   122  	return s, nil
   123  }
   124  
   125  func (s *githubStatusMessageLogger) githubMessageFieldsToStatus(m *message.Fields) *github.RepoStatus {
   126  	if m == nil {
   127  		return nil
   128  	}
   129  
   130  	state, ok := getStringPtrFromField((*m)["state"])
   131  	if !ok {
   132  		return nil
   133  	}
   134  	context, ok := getStringPtrFromField((*m)["context"])
   135  	if !ok {
   136  		return nil
   137  	}
   138  	URL, ok := getStringPtrFromField((*m)["URL"])
   139  	if !ok {
   140  		return nil
   141  	}
   142  	var description *string
   143  	if description != nil {
   144  		description, ok = getStringPtrFromField((*m)["description"])
   145  		if description != nil && len(*description) == 0 {
   146  			description = nil
   147  		}
   148  		if !ok {
   149  			return nil
   150  		}
   151  	}
   152  
   153  	status := &github.RepoStatus{
   154  		State:       state,
   155  		Context:     context,
   156  		TargetURL:   URL,
   157  		Description: description,
   158  	}
   159  
   160  	return status
   161  }
   162  
   163  func getStringPtrFromField(i interface{}) (*string, bool) {
   164  	if ret, ok := i.(string); ok {
   165  		return &ret, true
   166  	}
   167  	if ret, ok := i.(*string); ok {
   168  		return ret, ok
   169  	}
   170  
   171  	return nil, false
   172  }
   173  func githubStatusMessagePayloadToRepoStatus(c *message.GithubStatus) *github.RepoStatus {
   174  	if c == nil {
   175  		return nil
   176  	}
   177  
   178  	s := &github.RepoStatus{
   179  		Context: github.String(c.Context),
   180  		State:   github.String(string(c.State)),
   181  	}
   182  	if len(c.URL) > 0 {
   183  		s.TargetURL = github.String(c.URL)
   184  	}
   185  	if len(c.Description) > 0 {
   186  		s.Description = github.String(c.Description)
   187  	}
   188  
   189  	return s
   190  }
   191  
   192  func githubMessageFieldsToRepo(m *message.Fields) (string, string, string) {
   193  	if m == nil {
   194  		return "", "", ""
   195  	}
   196  
   197  	owner, ok := getStringPtrFromField((*m)["owner"])
   198  	if !ok {
   199  		owner = github.String("")
   200  	}
   201  	repo, ok := getStringPtrFromField((*m)["repo"])
   202  	if !ok {
   203  		repo = github.String("")
   204  	}
   205  	ref, ok := getStringPtrFromField((*m)["ref"])
   206  	if !ok {
   207  		ref = github.String("")
   208  	}
   209  
   210  	return *owner, *repo, *ref
   211  }