GitOps for k8s
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

event.go 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. package event
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "sort"
  6. "strings"
  7. "time"
  8. "github.com/pkg/errors"
  9. "github.com/fluxcd/flux/resource"
  10. "github.com/fluxcd/flux/update"
  11. )
  12. // These are all the types of events.
  13. const (
  14. EventCommit = "commit"
  15. EventSync = "sync"
  16. EventRelease = "release"
  17. EventAutoRelease = "autorelease"
  18. EventAutomate = "automate"
  19. EventDeautomate = "deautomate"
  20. EventLock = "lock"
  21. EventUnlock = "unlock"
  22. EventUpdatePolicy = "update_policy"
  23. // This is used to label e.g., commits that we _don't_ consider an event in themselves.
  24. NoneOfTheAbove = "other"
  25. LogLevelDebug = "debug"
  26. LogLevelInfo = "info"
  27. LogLevelWarn = "warn"
  28. LogLevelError = "error"
  29. )
  30. type EventID int64
  31. type Event struct {
  32. // ID is a UUID for this event. Will be auto-set when saving if blank.
  33. ID EventID `json:"id"`
  34. // Identifiers of workloads affected by this event.
  35. // TODO: rename to WorkloadIDs after adding versioning.
  36. ServiceIDs []resource.ID `json:"serviceIDs"`
  37. // Type is the type of event, usually "release" for now, but could be other
  38. // things later
  39. Type string `json:"type"`
  40. // StartedAt is the time the event began.
  41. StartedAt time.Time `json:"startedAt"`
  42. // EndedAt is the time the event ended. For instantaneous events, this will
  43. // be the same as StartedAt.
  44. EndedAt time.Time `json:"endedAt"`
  45. // LogLevel for this event. Used to indicate how important it is.
  46. // `debug|info|warn|error`
  47. LogLevel string `json:"logLevel"`
  48. // Message is a pre-formatted string for errors and other stuff. Included for
  49. // backwards-compatibility, and is now somewhat unnecessary. Should only be
  50. // used if metadata is empty.
  51. Message string `json:"message,omitempty"`
  52. // Metadata is Event.Type-specific metadata. If an event has no metadata,
  53. // this will be nil.
  54. Metadata EventMetadata `json:"metadata,omitempty"`
  55. }
  56. type EventWriter interface {
  57. // LogEvent records a message in the history.
  58. LogEvent(Event) error
  59. }
  60. func (e Event) WorkloadIDStrings() []string {
  61. var strWorkloadIDs []string
  62. for _, workloadID := range e.ServiceIDs {
  63. strWorkloadIDs = append(strWorkloadIDs, workloadID.String())
  64. }
  65. sort.Strings(strWorkloadIDs)
  66. return strWorkloadIDs
  67. }
  68. func (e Event) String() string {
  69. if e.Message != "" {
  70. return e.Message
  71. }
  72. strWorkloadIDs := e.WorkloadIDStrings()
  73. switch e.Type {
  74. case EventRelease:
  75. metadata := e.Metadata.(*ReleaseEventMetadata)
  76. strImageIDs := metadata.Result.ChangedImages()
  77. if len(strImageIDs) == 0 {
  78. strImageIDs = []string{"no image changes"}
  79. }
  80. if metadata.Spec.Type == "" || metadata.Spec.Type == ReleaseImageSpecType {
  81. for _, spec := range metadata.Spec.ReleaseImageSpec.ServiceSpecs {
  82. if spec == update.ResourceSpecAll {
  83. strWorkloadIDs = []string{"all workloads"}
  84. break
  85. }
  86. }
  87. }
  88. if len(strWorkloadIDs) == 0 {
  89. strWorkloadIDs = []string{"no workloads"}
  90. }
  91. var user string
  92. if metadata.Cause.User != "" {
  93. user = fmt.Sprintf(", by %s", metadata.Cause.User)
  94. }
  95. var msg string
  96. if metadata.Cause.Message != "" {
  97. msg = fmt.Sprintf(", with message %q", metadata.Cause.Message)
  98. }
  99. return fmt.Sprintf(
  100. "Released: %s to %s%s%s",
  101. strings.Join(strImageIDs, ", "),
  102. strings.Join(strWorkloadIDs, ", "),
  103. user,
  104. msg,
  105. )
  106. case EventAutoRelease:
  107. metadata := e.Metadata.(*AutoReleaseEventMetadata)
  108. strImageIDs := metadata.Result.ChangedImages()
  109. if len(strImageIDs) == 0 {
  110. strImageIDs = []string{"no image changes"}
  111. }
  112. return fmt.Sprintf(
  113. "Automated release of %s",
  114. strings.Join(strImageIDs, ", "),
  115. )
  116. case EventCommit:
  117. metadata := e.Metadata.(*CommitEventMetadata)
  118. svcStr := "<no changes>"
  119. if len(strWorkloadIDs) > 0 {
  120. svcStr = strings.Join(strWorkloadIDs, ", ")
  121. }
  122. return fmt.Sprintf("Commit: %s, %s", shortRevision(metadata.Revision), svcStr)
  123. case EventSync:
  124. metadata := e.Metadata.(*SyncEventMetadata)
  125. revStr := "<no revision>"
  126. if 0 < len(metadata.Commits) && len(metadata.Commits) <= 2 {
  127. revStr = shortRevision(metadata.Commits[0].Revision)
  128. } else if len(metadata.Commits) > 2 {
  129. revStr = fmt.Sprintf(
  130. "%s..%s",
  131. shortRevision(metadata.Commits[len(metadata.Commits)-1].Revision),
  132. shortRevision(metadata.Commits[0].Revision),
  133. )
  134. }
  135. svcStr := "no workloads changed"
  136. if len(strWorkloadIDs) > 0 {
  137. svcStr = strings.Join(strWorkloadIDs, ", ")
  138. }
  139. return fmt.Sprintf("Sync: %s, %s", revStr, svcStr)
  140. case EventAutomate:
  141. return fmt.Sprintf("Automated: %s", strings.Join(strWorkloadIDs, ", "))
  142. case EventDeautomate:
  143. return fmt.Sprintf("Deautomated: %s", strings.Join(strWorkloadIDs, ", "))
  144. case EventLock:
  145. return fmt.Sprintf("Locked: %s", strings.Join(strWorkloadIDs, ", "))
  146. case EventUnlock:
  147. return fmt.Sprintf("Unlocked: %s", strings.Join(strWorkloadIDs, ", "))
  148. case EventUpdatePolicy:
  149. return fmt.Sprintf("Updated policies: %s", strings.Join(strWorkloadIDs, ", "))
  150. default:
  151. return fmt.Sprintf("Unknown event: %s", e.Type)
  152. }
  153. }
  154. func shortRevision(rev string) string {
  155. if len(rev) <= 7 {
  156. return rev
  157. }
  158. return rev[:7]
  159. }
  160. // CommitEventMetadata is the metadata for when new git commits are created
  161. type CommitEventMetadata struct {
  162. Revision string `json:"revision,omitempty"`
  163. Spec *update.Spec `json:"spec"`
  164. Result update.Result `json:"result,omitempty"`
  165. }
  166. func (c CommitEventMetadata) ShortRevision() string {
  167. return shortRevision(c.Revision)
  168. }
  169. // Commit represents the commit information in a sync event. We could
  170. // use git.Commit, but that would lead to an import cycle, and may
  171. // anyway represent coupling (of an internal API to serialised data)
  172. // that we don't want.
  173. type Commit struct {
  174. Revision string `json:"revision"`
  175. Message string `json:"message"`
  176. }
  177. type ResourceError struct {
  178. ID resource.ID
  179. Path string
  180. Error string
  181. }
  182. // SyncEventMetadata is the metadata for when new a commit is synced to the cluster
  183. type SyncEventMetadata struct {
  184. // for parsing old events; Commits is now used in preference
  185. Revs []string `json:"revisions,omitempty"`
  186. Commits []Commit `json:"commits,omitempty"`
  187. // Which "kinds" of commit this includes; release, autoreleases,
  188. // policy changes, and "other" (meaning things we didn't commit
  189. // ourselves)
  190. Includes map[string]bool `json:"includes,omitempty"`
  191. // Per-resource errors
  192. Errors []ResourceError `json:"errors,omitempty"`
  193. // `true` if we have no record of having synced before
  194. InitialSync bool `json:"initialSync,omitempty"`
  195. }
  196. // Account for old events, which used the revisions field rather than commits
  197. func (ev *SyncEventMetadata) UnmarshalJSON(b []byte) error {
  198. type data SyncEventMetadata
  199. err := json.Unmarshal(b, (*data)(ev))
  200. if err != nil {
  201. return err
  202. }
  203. if ev.Commits == nil {
  204. ev.Commits = make([]Commit, len(ev.Revs))
  205. for i, rev := range ev.Revs {
  206. ev.Commits[i].Revision = rev
  207. }
  208. }
  209. return nil
  210. }
  211. type ReleaseEventCommon struct {
  212. Revision string // the revision which has the changes for the release
  213. Result update.Result `json:"result"`
  214. // Message of the error if there was one.
  215. Error string `json:"error,omitempty"`
  216. }
  217. const (
  218. // ReleaseImageSpecType is a type of release spec when there are update.Images
  219. ReleaseImageSpecType = "releaseImageSpecType"
  220. // ReleaseContainersSpecType is a type of release spec when there are update.Containers
  221. ReleaseContainersSpecType = "releaseContainersSpecType"
  222. )
  223. // ReleaseSpec is a spec for images and containers release
  224. type ReleaseSpec struct {
  225. // Type is ReleaseImageSpecType or ReleaseContainersSpecType
  226. // if empty (for previous version), then use ReleaseImageSpecType
  227. Type string
  228. ReleaseImageSpec *update.ReleaseImageSpec
  229. ReleaseContainersSpec *update.ReleaseContainersSpec
  230. }
  231. // IsKindExecute reports whether the release spec s has ReleaseImageSpec or ReleaseImageSpec with Kind execute
  232. // or error if s has invalid Type
  233. func (s ReleaseSpec) IsKindExecute() (bool, error) {
  234. switch s.Type {
  235. case ReleaseImageSpecType:
  236. if s.ReleaseImageSpec != nil && s.ReleaseImageSpec.Kind == update.ReleaseKindExecute {
  237. return true, nil
  238. }
  239. case ReleaseContainersSpecType:
  240. if s.ReleaseContainersSpec != nil && s.ReleaseContainersSpec.Kind == update.ReleaseKindExecute {
  241. return true, nil
  242. }
  243. default:
  244. return false, errors.Errorf("unknown release spec type %s", s.Type)
  245. }
  246. return false, nil
  247. }
  248. // UnmarshalJSON for old version of spec (update.ReleaseImageSpec) where Type is empty
  249. func (s *ReleaseSpec) UnmarshalJSON(b []byte) error {
  250. type T ReleaseSpec
  251. t := (*T)(s)
  252. if err := json.Unmarshal(b, t); err != nil {
  253. return err
  254. }
  255. switch t.Type {
  256. case "":
  257. r := &update.ReleaseImageSpec{}
  258. if err := json.Unmarshal(b, r); err != nil {
  259. return err
  260. }
  261. s.Type = ReleaseImageSpecType
  262. s.ReleaseImageSpec = r
  263. case ReleaseImageSpecType, ReleaseContainersSpecType:
  264. // all good
  265. default:
  266. return errors.New("unknown ReleaseSpec type")
  267. }
  268. return nil
  269. }
  270. // ReleaseEventMetadata is the metadata for when workloads(s) are released
  271. type ReleaseEventMetadata struct {
  272. ReleaseEventCommon
  273. Spec ReleaseSpec `json:"spec"`
  274. Cause update.Cause `json:"cause"`
  275. }
  276. // AutoReleaseEventMetadata is for when workloads(s) are released
  277. // automatically because there's a new image or images
  278. type AutoReleaseEventMetadata struct {
  279. ReleaseEventCommon
  280. Spec update.Automated `json:"spec"`
  281. }
  282. type UnknownEventMetadata map[string]interface{}
  283. func (e *Event) UnmarshalJSON(in []byte) error {
  284. type alias Event
  285. var wireEvent struct {
  286. *alias
  287. MetadataBytes json.RawMessage `json:"metadata,omitempty"`
  288. }
  289. wireEvent.alias = (*alias)(e)
  290. // Now unmarshall custom wireEvent with RawMessage
  291. if err := json.Unmarshal(in, &wireEvent); err != nil {
  292. return err
  293. }
  294. if wireEvent.Type == "" {
  295. return errors.New("Event type is empty")
  296. }
  297. // The cases correspond to kinds of event that we care about
  298. // processing e.g., for notifications.
  299. switch wireEvent.Type {
  300. case EventRelease:
  301. var metadata ReleaseEventMetadata
  302. if err := json.Unmarshal(wireEvent.MetadataBytes, &metadata); err != nil {
  303. return err
  304. }
  305. e.Metadata = &metadata
  306. break
  307. case EventAutoRelease:
  308. var metadata AutoReleaseEventMetadata
  309. if err := json.Unmarshal(wireEvent.MetadataBytes, &metadata); err != nil {
  310. return err
  311. }
  312. e.Metadata = &metadata
  313. break
  314. case EventCommit:
  315. var metadata CommitEventMetadata
  316. if err := json.Unmarshal(wireEvent.MetadataBytes, &metadata); err != nil {
  317. return err
  318. }
  319. e.Metadata = &metadata
  320. break
  321. case EventSync:
  322. var metadata SyncEventMetadata
  323. if err := json.Unmarshal(wireEvent.MetadataBytes, &metadata); err != nil {
  324. return err
  325. }
  326. e.Metadata = &metadata
  327. break
  328. default:
  329. if len(wireEvent.MetadataBytes) > 0 {
  330. var metadata UnknownEventMetadata
  331. if err := json.Unmarshal(wireEvent.MetadataBytes, &metadata); err != nil {
  332. return err
  333. }
  334. e.Metadata = metadata
  335. }
  336. }
  337. // By default, leave the Event Metadata as map[string]interface{}
  338. return nil
  339. }
  340. // EventMetadata is a type safety trick used to make sure that Metadata field
  341. // of Event is always a pointer, so that consumers can cast without being
  342. // concerned about encountering a value type instead. It works by virtue of the
  343. // fact that the method is only defined for pointer receivers; the actual
  344. // method chosen is entirely arbitary.
  345. type EventMetadata interface {
  346. Type() string
  347. }
  348. func (cem *CommitEventMetadata) Type() string {
  349. return EventCommit
  350. }
  351. func (cem *SyncEventMetadata) Type() string {
  352. return EventSync
  353. }
  354. func (rem *ReleaseEventMetadata) Type() string {
  355. return EventRelease
  356. }
  357. func (rem *AutoReleaseEventMetadata) Type() string {
  358. return EventAutoRelease
  359. }
  360. // Special exception from pointer receiver rule, as UnknownEventMetadata is a
  361. // type alias for a map
  362. func (uem UnknownEventMetadata) Type() string {
  363. return "unknown"
  364. }