github.com/grahambrereton-form3/tilt@v0.10.18/internal/hud/webview/convert.go (about) 1 package webview 2 3 import ( 4 "os" 5 "path/filepath" 6 "sort" 7 "strings" 8 9 "github.com/windmilleng/tilt/internal/cloud/cloudurl" 10 "github.com/windmilleng/tilt/internal/dockercompose" 11 "github.com/windmilleng/tilt/internal/feature" 12 "github.com/windmilleng/tilt/internal/ospath" 13 "github.com/windmilleng/tilt/internal/store" 14 "github.com/windmilleng/tilt/pkg/model" 15 16 proto_webview "github.com/windmilleng/tilt/pkg/webview" 17 ) 18 19 func StateToProtoView(s store.EngineState) (*proto_webview.View, error) { 20 ret := &proto_webview.View{} 21 22 rpv, err := tiltfileResourceProtoView(s) 23 if err != nil { 24 return nil, err 25 } 26 ret.Resources = append(ret.Resources, rpv) 27 28 for _, name := range s.ManifestDefinitionOrder { 29 mt, ok := s.ManifestTargets[name] 30 if !ok { 31 continue 32 } 33 34 ms := mt.State 35 36 var absWatchDirs []string 37 var absWatchPaths []string 38 for _, p := range mt.Manifest.LocalPaths() { 39 fi, err := os.Stat(p) 40 if err == nil && !fi.IsDir() { 41 absWatchPaths = append(absWatchPaths, p) 42 } else { 43 absWatchDirs = append(absWatchDirs, p) 44 } 45 } 46 absWatchPaths = append(absWatchPaths, s.TiltfilePath) 47 relWatchDirs := ospath.TryAsCwdChildren(absWatchDirs) 48 relWatchPaths := ospath.TryAsCwdChildren(absWatchPaths) 49 50 var pendingBuildEdits []string 51 for _, status := range ms.BuildStatuses { 52 for f := range status.PendingFileChanges { 53 pendingBuildEdits = append(pendingBuildEdits, f) 54 } 55 } 56 57 pendingBuildEdits = ospath.FileListDisplayNames(absWatchDirs, pendingBuildEdits) 58 59 buildHistory := append([]model.BuildRecord{}, ms.BuildHistory...) 60 for i, build := range buildHistory { 61 build.Edits = ospath.FileListDisplayNames(absWatchDirs, build.Edits) 62 buildHistory[i] = build 63 } 64 65 currentBuild := ms.CurrentBuild 66 currentBuild.Edits = ospath.FileListDisplayNames(absWatchDirs, ms.CurrentBuild.Edits) 67 68 // Sort the strings to make the outputs deterministic. 69 sort.Strings(pendingBuildEdits) 70 71 endpoints := store.ManifestTargetEndpoints(mt) 72 73 podID := ms.MostRecentPod().PodID 74 75 var facets []model.Facet 76 if s.Features[feature.Facets] { 77 facets = mt.Facets(s.Secrets) 78 } 79 80 bh, err := ToProtoBuildRecords(buildHistory) 81 if err != nil { 82 return nil, err 83 } 84 lastDeploy, err := timeToProto(ms.LastSuccessfulDeployTime) 85 if err != nil { 86 return nil, err 87 } 88 cb, err := ToProtoBuildRecord(currentBuild) 89 if err != nil { 90 return nil, err 91 } 92 93 // NOTE(nick): Right now, the UX is designed to show the output exactly one 94 // pod. A better UI might summarize the pods in other ways (e.g., show the 95 // "most interesting" pod that's crash looping, or show logs from all pods 96 // at once). 97 hasPendingChanges, pendingBuildSince := ms.HasPendingChanges() 98 pbs, err := timeToProto(pendingBuildSince) 99 if err != nil { 100 return nil, err 101 } 102 103 r := &proto_webview.Resource{ 104 Name: name.String(), 105 DirectoriesWatched: relWatchDirs, 106 PathsWatched: relWatchPaths, 107 LastDeployTime: lastDeploy, 108 BuildHistory: bh, 109 PendingBuildEdits: pendingBuildEdits, 110 PendingBuildSince: pbs, 111 PendingBuildReason: int32(ms.NextBuildReason()), 112 CurrentBuild: cb, 113 Endpoints: endpoints, 114 PodID: podID.String(), 115 ShowBuildStatus: len(mt.Manifest.ImageTargets) > 0 || mt.Manifest.IsDC(), 116 CombinedLog: ms.CombinedLog.String(), 117 CrashLog: ms.CrashLog.String(), 118 TriggerMode: int32(mt.Manifest.TriggerMode), 119 HasPendingChanges: hasPendingChanges, 120 Facets: model.FacetsToProto(facets), 121 Queued: s.ManifestInTriggerQueue(name), 122 } 123 124 riv, err := protoPopulateResourceInfoView(mt, r) 125 if err != nil { 126 return nil, err 127 } 128 r.RuntimeStatus = string(runtimeStatus(riv)) 129 130 ret.Resources = append(ret.Resources, r) 131 } 132 133 ret.Log = s.Log.String() 134 ret.NeedsAnalyticsNudge = NeedsNudge(s) 135 ret.RunningTiltBuild = &proto_webview.TiltBuild{ 136 Version: s.TiltBuildInfo.Version, 137 CommitSHA: s.TiltBuildInfo.CommitSHA, 138 Dev: s.TiltBuildInfo.Dev, 139 Date: s.TiltBuildInfo.Date, 140 } 141 ret.LatestTiltBuild = &proto_webview.TiltBuild{ 142 Version: s.LatestTiltBuild.Version, 143 CommitSHA: s.LatestTiltBuild.CommitSHA, 144 Dev: s.LatestTiltBuild.Dev, 145 Date: s.LatestTiltBuild.Date, 146 } 147 ret.FeatureFlags = make(map[string]bool) 148 for k, v := range s.Features { 149 ret.FeatureFlags[k] = v 150 } 151 ret.TiltCloudUsername = s.TiltCloudUsername 152 ret.TiltCloudSchemeHost = cloudurl.URL(s.CloudAddress).String() 153 ret.TiltCloudTeamID = s.TeamName 154 if s.FatalError != nil { 155 ret.FatalError = s.FatalError.Error() 156 } 157 158 return ret, nil 159 } 160 161 func tiltfileResourceView(s store.EngineState) Resource { 162 ltfb := s.TiltfileState.LastBuild() 163 ctfb := s.TiltfileState.CurrentBuild 164 if !ctfb.Empty() { 165 ltfb.Log = ctfb.Log 166 } 167 168 ltfb.Edits = ospath.FileListDisplayNames([]string{filepath.Dir(s.TiltfilePath)}, ltfb.Edits) 169 170 tr := Resource{ 171 Name: store.TiltfileManifestName, 172 IsTiltfile: true, 173 CurrentBuild: ToWebViewBuildRecord(ctfb), 174 BuildHistory: []BuildRecord{ 175 ToWebViewBuildRecord(ltfb), 176 }, 177 CombinedLog: s.TiltfileState.CombinedLog, 178 RuntimeStatus: RuntimeStatusOK, 179 } 180 if !ctfb.Empty() { 181 tr.PendingBuildSince = ctfb.StartTime 182 } else { 183 tr.LastDeployTime = ltfb.FinishTime 184 } 185 return tr 186 } 187 188 func tiltfileResourceProtoView(s store.EngineState) (*proto_webview.Resource, error) { 189 ltfb := s.TiltfileState.LastBuild() 190 ctfb := s.TiltfileState.CurrentBuild 191 if !ctfb.Empty() { 192 ltfb.Log = ctfb.Log 193 } 194 195 ltfb.Edits = ospath.FileListDisplayNames([]string{filepath.Dir(s.TiltfilePath)}, ltfb.Edits) 196 197 pctfb, err := ToProtoBuildRecord(ctfb) 198 if err != nil { 199 return nil, err 200 } 201 pltfb, err := ToProtoBuildRecord(ltfb) 202 if err != nil { 203 return nil, err 204 } 205 tr := &proto_webview.Resource{ 206 Name: store.TiltfileManifestName.String(), 207 IsTiltfile: true, 208 CurrentBuild: pctfb, 209 BuildHistory: []*proto_webview.BuildRecord{ 210 pltfb, 211 }, 212 CombinedLog: s.TiltfileState.CombinedLog.String(), 213 RuntimeStatus: string(RuntimeStatusOK), 214 } 215 start, err := timeToProto(ctfb.StartTime) 216 if err != nil { 217 return nil, err 218 } 219 finish, err := timeToProto(ltfb.FinishTime) 220 if err != nil { 221 return nil, err 222 } 223 if !ctfb.Empty() { 224 tr.PendingBuildSince = start 225 } else { 226 tr.LastDeployTime = finish 227 } 228 return tr, nil 229 } 230 231 func populateResourceInfoView(mt *store.ManifestTarget, r *Resource) ResourceInfoView { 232 if mt.Manifest.IsUnresourcedYAMLManifest() { 233 r.YAMLResourceInfo = &YAMLResourceInfo{ 234 K8sResources: mt.Manifest.K8sTarget().DisplayNames, 235 } 236 return r.YAMLResourceInfo 237 } 238 239 if mt.Manifest.IsDC() { 240 dc := mt.Manifest.DockerComposeTarget() 241 dcState := mt.State.DCRuntimeState() 242 info := NewDCResourceInfo(dc.ConfigPaths, dcState.Status, dcState.ContainerID, dcState.Log(), dcState.StartTime) 243 r.DCResourceInfo = &info 244 return r.DCResourceInfo 245 } 246 if mt.Manifest.IsLocal() { 247 r.LocalResourceInfo = &LocalResourceInfo{} 248 return r.LocalResourceInfo 249 } 250 if mt.Manifest.IsK8s() { 251 kState := mt.State.K8sRuntimeState() 252 pod := kState.MostRecentPod() 253 r.K8sResourceInfo = &K8sResourceInfo{ 254 PodName: pod.PodID.String(), 255 PodCreationTime: pod.StartedAt, 256 PodUpdateStartTime: pod.UpdateStartTime, 257 PodStatus: pod.Status, 258 PodStatusMessage: strings.Join(pod.StatusMessages, "\n"), 259 AllContainersReady: pod.AllContainersReady(), 260 PodRestarts: pod.VisibleContainerRestarts(), 261 PodLog: pod.Log(), 262 } 263 return r.K8sResourceInfo 264 } 265 266 panic("Unrecognized manifest type (not one of: k8s, DC, local)") 267 } 268 269 func protoPopulateResourceInfoView(mt *store.ManifestTarget, r *proto_webview.Resource) (ResourceInfoView, error) { 270 if mt.Manifest.IsUnresourcedYAMLManifest() { 271 r.YamlResourceInfo = &proto_webview.YAMLResourceInfo{ 272 K8SResources: mt.Manifest.K8sTarget().DisplayNames, 273 } 274 riv := &YAMLResourceInfo{ 275 K8sResources: mt.Manifest.K8sTarget().DisplayNames, 276 } 277 return riv, nil 278 } 279 280 if mt.Manifest.IsDC() { 281 dc := mt.Manifest.DockerComposeTarget() 282 dcState := mt.State.DCRuntimeState() 283 info, err := NewProtoDCResourceInfo(dc.ConfigPaths, dcState.Status, dcState.ContainerID, dcState.Log(), dcState.StartTime) 284 if err != nil { 285 return nil, err 286 } 287 riv := NewDCResourceInfo(dc.ConfigPaths, dcState.Status, dcState.ContainerID, dcState.Log(), dcState.StartTime) 288 r.DcResourceInfo = info 289 return riv, nil 290 } 291 if mt.Manifest.IsLocal() { 292 r.LocalResourceInfo = &proto_webview.LocalResourceInfo{} 293 return &LocalResourceInfo{}, nil 294 } 295 if mt.Manifest.IsK8s() { 296 kState := mt.State.K8sRuntimeState() 297 pod := kState.MostRecentPod() 298 r.K8SResourceInfo = &proto_webview.K8SResourceInfo{ 299 PodName: pod.PodID.String(), 300 PodCreationTime: pod.StartedAt.String(), 301 PodUpdateStartTime: pod.UpdateStartTime.String(), 302 PodStatus: pod.Status, 303 PodStatusMessage: strings.Join(pod.StatusMessages, "\n"), 304 AllContainersReady: pod.AllContainersReady(), 305 PodRestarts: int32(pod.VisibleContainerRestarts()), 306 PodLog: pod.Log().String(), 307 } 308 return &K8sResourceInfo{ 309 PodName: pod.PodID.String(), 310 PodCreationTime: pod.StartedAt, 311 PodUpdateStartTime: pod.UpdateStartTime, 312 PodStatus: pod.Status, 313 PodStatusMessage: strings.Join(pod.StatusMessages, "\n"), 314 AllContainersReady: pod.AllContainersReady(), 315 PodRestarts: pod.VisibleContainerRestarts(), 316 PodLog: pod.Log(), 317 }, nil 318 } 319 320 panic("Unrecognized manifest type (not one of: k8s, DC, local)") 321 } 322 323 func runtimeStatus(res ResourceInfoView) RuntimeStatus { 324 _, isLocal := res.(*LocalResourceInfo) 325 if isLocal { 326 return RuntimeStatusNotApplicable 327 } 328 // if we have no images to build, we have no runtime status monitoring. 329 _, isYAML := res.(*YAMLResourceInfo) 330 if isYAML { 331 return RuntimeStatusNotApplicable 332 } 333 334 result, ok := runtimeStatusMap[res.Status()] 335 if !ok { 336 return RuntimeStatusError 337 } 338 339 return result 340 } 341 342 var runtimeStatusMap = map[string]RuntimeStatus{ 343 "Running": RuntimeStatusOK, 344 "ContainerCreating": RuntimeStatusPending, 345 "Pending": RuntimeStatusPending, 346 "PodInitializing": RuntimeStatusPending, 347 "Error": RuntimeStatusError, 348 "CrashLoopBackOff": RuntimeStatusError, 349 "ErrImagePull": RuntimeStatusError, 350 "ImagePullBackOff": RuntimeStatusError, 351 "RunContainerError": RuntimeStatusError, 352 "StartError": RuntimeStatusError, 353 string(dockercompose.StatusInProg): RuntimeStatusPending, 354 string(dockercompose.StatusUp): RuntimeStatusOK, 355 string(dockercompose.StatusDown): RuntimeStatusError, 356 "Completed": RuntimeStatusOK, 357 358 // If the runtime status hasn't shown up yet, we assume it's pending. 359 "": RuntimeStatusPending, 360 }