// SPDX-License-Identifier: AGPL-3.0-or-later package app import ( "fmt" "os" "os/signal" "strings" "syscall" "github.com/spf13/cobra" "bnema.dev/ermite-api/gen/ermite" "bnema.dev/ermite/internal/adapters/client" ) // jsonEvent is the stable JSON shape of one event: one object per line with // `ermite watch --json`. Fields not meaningful for a type are omitted. type jsonEvent struct { ID string `json:"id"` Time *string `json:"time"` Type string `json:"type"` Repo string `json:"repo,omitempty"` Repository *jsonRepo `json:"repository,omitempty"` RefUpdates []jsonRefUpdate `json:"ref_updates,omitempty"` // MergeRequest is set for the merge_request_* events, Number and Comment // for comment_added. MergeRequest *jsonMergeRequest `json:"merge_request,omitempty"` Number int64 `json:"number,omitempty"` Comment *jsonComment `json:"comment,omitempty"` // Mergeability is set for a merge_request_mergeability event. Mergeability *jsonMergeability `json:"mergeability,omitempty"` // State and FailureClass are set for a mirror_failed event. State string `json:"state,omitempty"` FailureClass string `json:"failure_class,omitempty"` // Auto is set for merge_request_merged: auto-merge made the merge. Auto bool `json:"auto,omitempty"` // Reason is set for merge_request_auto_merge_cancelled. Reason string `json:"reason,omitempty"` } type jsonRefUpdate struct { Ref string `json:"ref"` OldID string `json:"old_id"` NewID string `json:"new_id"` } // Event types in the output. const ( eventRepoCreated = "repo_created" eventRepoUpdated = "repo_updated" eventRepoDeleted = "repo_deleted" eventPushed = "pushed" eventGap = "gap" eventMROpened = "merge_request_opened" eventMRMerged = "merge_request_merged" eventMRClosed = "merge_request_closed" eventMRAutoEnabled = "merge_request_auto_merge_enabled" eventMRAutoCancelled = "merge_request_auto_merge_cancelled" eventCommentAdded = "comment_added" eventCommentEdited = "comment_edited" eventCommentDeleted = "comment_deleted" eventCommentResolved = "comment_resolved" eventMRMergeability = "merge_request_mergeability" eventMirrorFailed = "mirror_failed" eventMirrorRecovered = "mirror_recovered" ) // toJSONEvent maps an event; ok is false for kinds this client does not know, // which the contract says to ignore. func toJSONEvent(ev *ermite.Event, baseURL string) (jsonEvent, bool) { out := jsonEvent{ID: ev.GetId(), Time: jsonTime(ev.GetTime())} switch k := ev.GetKind().(type) { case *ermite.Event_RepoCreated: r := toJSONRepo(k.RepoCreated.GetRepo()) out.Type, out.Repo, out.Repository = eventRepoCreated, r.Name, &r case *ermite.Event_RepoUpdated: r := toJSONRepo(k.RepoUpdated.GetRepo()) out.Type, out.Repo, out.Repository = eventRepoUpdated, r.Name, &r case *ermite.Event_RepoDeleted: out.Type, out.Repo = eventRepoDeleted, k.RepoDeleted.GetRepo() case *ermite.Event_Pushed: out.Type, out.Repo = eventPushed, k.Pushed.GetRepo() out.RefUpdates = []jsonRefUpdate{} for _, u := range k.Pushed.GetRefUpdates() { out.RefUpdates = append(out.RefUpdates, jsonRefUpdate{Ref: u.GetRef(), OldID: u.GetOldId(), NewID: u.GetNewId()}) } case *ermite.Event_MergeRequestOpened: m := toJSONMergeRequest(k.MergeRequestOpened.GetMergeRequest(), baseURL) out.Type, out.Repo, out.MergeRequest = eventMROpened, m.Repo, &m case *ermite.Event_MergeRequestMerged: m := toJSONMergeRequest(k.MergeRequestMerged.GetMergeRequest(), baseURL) out.Type, out.Repo, out.MergeRequest = eventMRMerged, m.Repo, &m out.Auto = k.MergeRequestMerged.GetAuto() case *ermite.Event_MergeRequestAutoMergeEnabled: m := toJSONMergeRequest(k.MergeRequestAutoMergeEnabled.GetMergeRequest(), baseURL) out.Type, out.Repo, out.MergeRequest = eventMRAutoEnabled, m.Repo, &m case *ermite.Event_MergeRequestAutoMergeCancelled: m := toJSONMergeRequest(k.MergeRequestAutoMergeCancelled.GetMergeRequest(), baseURL) out.Type, out.Repo, out.MergeRequest = eventMRAutoCancelled, m.Repo, &m out.Reason = k.MergeRequestAutoMergeCancelled.GetReason() case *ermite.Event_MergeRequestClosed: m := toJSONMergeRequest(k.MergeRequestClosed.GetMergeRequest(), baseURL) out.Type, out.Repo, out.MergeRequest = eventMRClosed, m.Repo, &m case *ermite.Event_CommentAdded: c := toJSONComment(k.CommentAdded.GetComment()) out.Type, out.Repo, out.Number, out.Comment = eventCommentAdded, k.CommentAdded.GetRepo(), k.CommentAdded.GetNumber(), &c case *ermite.Event_CommentEdited: c := toJSONComment(k.CommentEdited.GetComment()) out.Type, out.Repo, out.Number, out.Comment = eventCommentEdited, k.CommentEdited.GetRepo(), k.CommentEdited.GetNumber(), &c case *ermite.Event_CommentDeleted: c := toJSONComment(k.CommentDeleted.GetComment()) out.Type, out.Repo, out.Number, out.Comment = eventCommentDeleted, k.CommentDeleted.GetRepo(), k.CommentDeleted.GetNumber(), &c case *ermite.Event_CommentResolved: c := toJSONComment(k.CommentResolved.GetComment()) out.Type, out.Repo, out.Number, out.Comment = eventCommentResolved, k.CommentResolved.GetRepo(), k.CommentResolved.GetNumber(), &c case *ermite.Event_MergeRequestMergeability: out.Type, out.Repo, out.Number = eventMRMergeability, k.MergeRequestMergeability.GetRepo(), k.MergeRequestMergeability.GetNumber() out.Mergeability = toJSONMergeability(k.MergeRequestMergeability.GetMergeability()) case *ermite.Event_MirrorFailed: out.Type, out.Repo = eventMirrorFailed, k.MirrorFailed.GetRepo() out.State, out.FailureClass = mirrorStateName(k.MirrorFailed.GetState()), k.MirrorFailed.GetFailureClass() case *ermite.Event_MirrorRecovered: out.Type, out.Repo = eventMirrorRecovered, k.MirrorRecovered.GetRepo() case *ermite.Event_Gap: out.Type = eventGap default: return jsonEvent{}, false } return out, true } // eventLine is the human line of an event: "