in pkg/source/common/k8s_audit/recorder/statusrecorder/recorder.go [45:136]
func recordChangeSetForLog(ctx context.Context, resourcePath string, log *types.AuditLogParserInput, prevStatus *model.K8sResourceContainingStatus, cs *history.ChangeSet, builder *history.Builder) (*model.K8sResourceContainingStatus, error) {
var resourceContainingStatus model.K8sResourceContainingStatus
err := log.ResourceBodyReader.ReadReflect("", &resourceContainingStatus)
if err != nil {
return prevStatus, err
}
if resourceContainingStatus.Status == nil || len(resourceContainingStatus.Status.Conditions) == 0 {
// This resource has no status field or no conditions in status field
return &resourceContainingStatus, nil
}
deletionStatus := manifestutil.ParseDeletionStatus(ctx, log.ResourceBodyReader, log.Operation)
isDeletionRequest := deletionStatus == manifestutil.DeletionStatusDeleted
for _, condition := range resourceContainingStatus.Status.Conditions {
lastTransitionTime, err := time.Parse(time.RFC3339, condition.LastTransitionTime)
if err != nil {
continue
}
conditionTime := lastTransitionTime
lastHeartbeatTime, err := time.Parse(time.RFC3339, condition.LastHeartbeatTime)
if err == nil && lastHeartbeatTime.Sub(conditionTime) > 0 {
conditionTime = lastHeartbeatTime
}
lastProbeTime, err := time.Parse(time.RFC3339, condition.LastProbeTime)
if err == nil && lastProbeTime.Sub(lastHeartbeatTime) > 0 {
conditionTime = lastProbeTime
}
// Ignore if the transition time was older than the last revision
statusPath := resourcepath.Status(resourcepath.FromK8sOperation(*log.Operation), condition.Type)
if log.Operation.SubResourceName != "" {
parentOp := model.KubernetesObjectOperation{
APIVersion: log.Operation.APIVersion,
PluralKind: log.Operation.PluralKind,
Namespace: log.Operation.Namespace,
Name: log.Operation.Name,
Verb: log.Operation.Verb,
}
statusPath = resourcepath.Status(resourcepath.FromK8sOperation(parentOp), condition.Type)
}
tb := builder.GetTimelineBuilder(statusPath.Path)
latest := tb.GetLatestRevision()
latestTime := time.Time{}
if latest != nil {
latestTime = latest.ChangeTime
} else {
creationTime := manifestutil.ParseCreationTime(log.ResourceBodyReader, time.Time{})
if err == nil && conditionTime.Sub(creationTime) != 0 {
cs.RecordRevision(statusPath, &history.StagingResourceRevision{
Verb: enum.RevisionVerbStatusUnknown,
Body: "# status is unknown but existence is inferred from the later log.",
Partial: false,
Inferred: true,
Requestor: "",
ChangeTime: creationTime,
State: enum.RevisionStateConditionUnknown,
})
}
}
prevCondition := lookUpConditionFromStatus(prevStatus, condition.Type)
latestUpdateTimeIsLaterThanPrevRevision := conditionTime.Sub(latestTime) > 0 // The previous revision is older than the timestamps written on the condition. This should be recorded as the change even if the reason and state is same to show the heart beat timing.
conditionUpdated := prevCondition != nil && (prevCondition.Status != condition.Status || prevCondition.Message != condition.Message || prevCondition.Reason != condition.Reason)
if latestUpdateTimeIsLaterThanPrevRevision || conditionUpdated {
conditionYaml, err := goyaml.Marshal(condition)
if err != nil {
continue
}
cs.RecordRevision(statusPath, &history.StagingResourceRevision{
Verb: conditionStateToRevisionVerb(condition.Status),
Body: string(conditionYaml),
Partial: false,
Requestor: "",
ChangeTime: conditionTime,
State: conditionStateToRevisionState(condition.Status),
})
}
if isDeletionRequest {
cs.RecordRevision(statusPath, &history.StagingResourceRevision{
Verb: enum.RevisionVerbDelete,
Body: "",
Partial: false,
Requestor: log.Requestor,
ChangeTime: log.Log.Timestamp(),
State: enum.RevisionStateDeleted,
})
}
}
if isDeletionRequest {
return nil, nil
}
return &resourceContainingStatus, nil
}