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 }