github.com/kyma-project/kyma-environment-broker@v0.0.1/internal/appinfo/runtime_info.go (about) 1 package appinfo 2 3 import ( 4 "fmt" 5 "net/http" 6 "time" 7 8 "github.com/kyma-project/kyma-environment-broker/internal" 9 "github.com/kyma-project/kyma-environment-broker/internal/broker" 10 "github.com/kyma-project/kyma-environment-broker/internal/httputil" 11 "github.com/kyma-project/kyma-environment-broker/internal/ptr" 12 "github.com/kyma-project/kyma-environment-broker/internal/storage/dberr" 13 "github.com/kyma-project/kyma-environment-broker/internal/storage/predicate" 14 ) 15 16 //go:generate mockery --name=InstanceFinder --output=automock --outpkg=automock --case=underscore 17 18 type ( 19 InstanceFinder interface { 20 FindAllJoinedWithOperations(prct ...predicate.Predicate) ([]internal.InstanceWithOperation, error) 21 } 22 23 LastOperationFinder interface { 24 GetLastOperation(instanceID string) (*internal.Operation, error) 25 } 26 27 ResponseWriter interface { 28 InternalServerError(rw http.ResponseWriter, r *http.Request, err error, context string) 29 } 30 ) 31 32 type RuntimeInfoHandler struct { 33 instanceFinder InstanceFinder 34 lastOperationFinder LastOperationFinder 35 respWriter ResponseWriter 36 plansConfig broker.PlansConfig 37 defaultSubaccountRegion string 38 } 39 40 func NewRuntimeInfoHandler(instanceFinder InstanceFinder, lastOpFinder LastOperationFinder, plansConfig broker.PlansConfig, region string, respWriter ResponseWriter) *RuntimeInfoHandler { 41 return &RuntimeInfoHandler{ 42 instanceFinder: instanceFinder, 43 lastOperationFinder: lastOpFinder, 44 respWriter: respWriter, 45 plansConfig: plansConfig, 46 defaultSubaccountRegion: region, 47 } 48 } 49 50 func (h *RuntimeInfoHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 51 allInstances, err := h.instanceFinder.FindAllJoinedWithOperations(predicate.SortAscByCreatedAt()) 52 if err != nil { 53 h.respWriter.InternalServerError(w, r, err, "while fetching all instances") 54 return 55 } 56 57 dto, err := h.mapToDTO(allInstances) 58 if err != nil { 59 h.respWriter.InternalServerError(w, r, err, "while mapping instance model to dto") 60 } 61 62 if err := httputil.JSONEncode(w, dto); err != nil { 63 h.respWriter.InternalServerError(w, r, err, "while encoding response to JSON") 64 return 65 } 66 } 67 68 func (h *RuntimeInfoHandler) mapToDTO(instances []internal.InstanceWithOperation) ([]*RuntimeDTO, error) { 69 items := make([]*RuntimeDTO, 0, len(instances)) 70 indexer := map[string]int{} 71 firstProvOpCreationTimePerInstance := map[string]time.Time{} 72 lastDeprovOpCreationTimesPerInstance := map[string]time.Time{} 73 74 for _, inst := range instances { 75 region := h.getRegionOrDefault(inst) 76 77 idx, found := indexer[inst.InstanceID] 78 if !found { 79 // Determine runtime modifiedAt timestamp based on the last operation of the runtime 80 lastOp, err := h.lastOperationFinder.GetLastOperation(inst.InstanceID) 81 if err != nil && !dberr.IsNotFound(err) { 82 return nil, fmt.Errorf("while getting last operation for instance %s: %w", inst.InstanceID, err) 83 } 84 updatedAt := inst.UpdatedAt 85 if lastOp != nil { 86 updatedAt = lastOp.UpdatedAt 87 } 88 items = append(items, &RuntimeDTO{ 89 RuntimeID: inst.RuntimeID, 90 SubAccountID: inst.SubAccountID, 91 SubAccountRegion: region, 92 ServiceInstanceID: inst.InstanceID, 93 GlobalAccountID: inst.GlobalAccountID, 94 ServiceClassID: inst.ServiceID, 95 ServiceClassName: svcNameOrDefault(inst), 96 ServicePlanID: inst.ServicePlanID, 97 ServicePlanName: h.planNameOrDefault(inst), 98 Status: StatusDTO{ 99 CreatedAt: getIfNotZero(inst.CreatedAt), 100 UpdatedAt: getIfNotZero(updatedAt), 101 DeletedAt: getIfNotZero(inst.DeletedAt), 102 }, 103 }) 104 idx = len(items) - 1 105 indexer[inst.InstanceID] = idx 106 } 107 108 // TODO: consider to merge the rows in sql query 109 opStatus := &OperationStatusDTO{ 110 State: inst.State.String, 111 Description: inst.Description.String, 112 } 113 switch internal.OperationType(inst.Type.String) { 114 case internal.OperationTypeProvision: 115 opCreationTime, exists := firstProvOpCreationTimePerInstance[inst.InstanceID] 116 if !exists { 117 firstProvOpCreationTimePerInstance[inst.InstanceID] = inst.OpCreatedAt 118 items[idx].Status.Provisioning = opStatus 119 } 120 if inst.OpCreatedAt.Before(opCreationTime) { 121 firstProvOpCreationTimePerInstance[inst.InstanceID] = inst.OpCreatedAt 122 items[idx].Status.Provisioning = opStatus 123 } 124 case internal.OperationTypeDeprovision: 125 if !inst.IsSuspensionOp { 126 opCreationTime, exists := lastDeprovOpCreationTimesPerInstance[inst.InstanceID] 127 if !exists { 128 lastDeprovOpCreationTimesPerInstance[inst.InstanceID] = inst.OpCreatedAt 129 items[idx].Status.Deprovisioning = opStatus 130 } 131 if inst.OpCreatedAt.After(opCreationTime) { 132 lastDeprovOpCreationTimesPerInstance[inst.InstanceID] = inst.OpCreatedAt 133 items[idx].Status.Deprovisioning = opStatus 134 } 135 } 136 } 137 } 138 139 return items, nil 140 } 141 142 func (h *RuntimeInfoHandler) getRegionOrDefault(inst internal.InstanceWithOperation) string { 143 if inst.Parameters.PlatformRegion == "" { 144 return h.defaultSubaccountRegion 145 } 146 return inst.Parameters.PlatformRegion 147 } 148 149 func svcNameOrDefault(inst internal.InstanceWithOperation) string { 150 if inst.ServiceName != "" { 151 return inst.ServiceName 152 } 153 return broker.KymaServiceName 154 } 155 156 func (h *RuntimeInfoHandler) planNameOrDefault(inst internal.InstanceWithOperation) string { 157 if inst.ServicePlanName != "" { 158 return inst.ServicePlanName 159 } 160 return broker.Plans(h.plansConfig, "", false, false, true, true)[inst.ServicePlanID].Name 161 } 162 163 func getIfNotZero(in time.Time) *time.Time { 164 if in.IsZero() { 165 return nil 166 } 167 return ptr.Time(in) 168 }