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  }