github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/hud/view_test.go (about)

     1  package hud
     2  
     3  import (
     4  	"path/filepath"
     5  	"sync"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/google/uuid"
    10  	"github.com/stretchr/testify/assert"
    11  	"github.com/stretchr/testify/require"
    12  	v1 "k8s.io/api/core/v1"
    13  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    14  
    15  	"github.com/tilt-dev/tilt/internal/hud/view"
    16  	"github.com/tilt-dev/tilt/internal/k8s"
    17  	"github.com/tilt-dev/tilt/internal/k8s/testyaml"
    18  	"github.com/tilt-dev/tilt/internal/store"
    19  	"github.com/tilt-dev/tilt/internal/store/k8sconv"
    20  	"github.com/tilt-dev/tilt/internal/testutils/manifestbuilder"
    21  	"github.com/tilt-dev/tilt/internal/testutils/tempdir"
    22  	"github.com/tilt-dev/tilt/pkg/apis"
    23  	"github.com/tilt-dev/tilt/pkg/apis/core/v1alpha1"
    24  	"github.com/tilt-dev/tilt/pkg/model"
    25  )
    26  
    27  func TestStateToViewRelativeEditPaths(t *testing.T) {
    28  	f := tempdir.NewTempDirFixture(t)
    29  	m := model.Manifest{
    30  		Name: "foo",
    31  	}.WithDeployTarget(model.K8sTarget{}).WithImageTarget(model.ImageTarget{}.
    32  		WithDockerImage(v1alpha1.DockerImageSpec{Context: f.JoinPath("a", "b", "c")}))
    33  
    34  	state := newState([]model.Manifest{m})
    35  	ms := state.ManifestTargets[m.Name].State
    36  	ms.CurrentBuilds["buildcontrol"] = model.BuildRecord{
    37  		Edits: []string{
    38  			f.JoinPath("a", "b", "c", "foo"),
    39  			f.JoinPath("a", "b", "c", "d", "e"),
    40  		},
    41  	}
    42  	ms.BuildHistory = []model.BuildRecord{
    43  		{
    44  			Edits: []string{
    45  				f.JoinPath("a", "b", "c", "foo"),
    46  				f.JoinPath("a", "b", "c", "d", "e"),
    47  			},
    48  		},
    49  	}
    50  	ms.MutableBuildStatus(m.ImageTargets[0].ID()).PendingFileChanges =
    51  		map[string]time.Time{
    52  			f.JoinPath("a", "b", "c", "foo"):    time.Now(),
    53  			f.JoinPath("a", "b", "c", "d", "e"): time.Now(),
    54  		}
    55  	v := StateToTerminalView(*state, &sync.RWMutex{})
    56  
    57  	require.Len(t, v.Resources, 2)
    58  
    59  	r, _ := v.Resource(m.Name)
    60  	assert.Equal(t, []string{"foo", filepath.Join("d", "e")}, r.LastBuild().Edits)
    61  	assert.Equal(t, []string{"foo", filepath.Join("d", "e")}, r.CurrentBuild.Edits)
    62  	assert.Equal(t, []string{filepath.Join("d", "e"), "foo"}, r.PendingBuildEdits) // these are sorted for deterministic ordering
    63  }
    64  
    65  func TestStateToTerminalViewPortForwards(t *testing.T) {
    66  	m := model.Manifest{
    67  		Name: "foo",
    68  	}.WithDeployTarget(model.K8sTarget{
    69  		KubernetesApplySpec: v1alpha1.KubernetesApplySpec{
    70  			PortForwardTemplateSpec: &v1alpha1.PortForwardTemplateSpec{
    71  				Forwards: []v1alpha1.Forward{
    72  					{LocalPort: 8000, ContainerPort: 5000},
    73  					{LocalPort: 7000, ContainerPort: 5001},
    74  				},
    75  			},
    76  		},
    77  	})
    78  	state := newState([]model.Manifest{m})
    79  	v := StateToTerminalView(*state, &sync.RWMutex{})
    80  	res, _ := v.Resource(m.Name)
    81  	assert.Equal(t,
    82  		[]string{"http://localhost:8000/", "http://localhost:7000/"},
    83  		res.Endpoints)
    84  }
    85  
    86  func TestStateToWebViewLinksAndPortForwards(t *testing.T) {
    87  	m := model.Manifest{
    88  		Name: "foo",
    89  	}.WithDeployTarget(model.K8sTarget{
    90  		KubernetesApplySpec: v1alpha1.KubernetesApplySpec{
    91  			PortForwardTemplateSpec: &v1alpha1.PortForwardTemplateSpec{
    92  				Forwards: []v1alpha1.Forward{
    93  					{LocalPort: 8000, ContainerPort: 5000},
    94  					{LocalPort: 8001, ContainerPort: 5001, Name: "debugger"},
    95  				},
    96  			},
    97  		},
    98  		Links: []model.Link{
    99  			model.MustNewLink("www.apple.edu", "apple"),
   100  			model.MustNewLink("www.zombo.com", "zombo"),
   101  		},
   102  	})
   103  	state := newState([]model.Manifest{m})
   104  	v := StateToTerminalView(*state, &sync.RWMutex{})
   105  	res, _ := v.Resource(m.Name)
   106  	assert.Equal(t,
   107  		[]string{"www.apple.edu", "www.zombo.com", "http://localhost:8000/", "http://localhost:8001/"},
   108  		res.Endpoints)
   109  }
   110  func TestStateToTerminalViewLocalResourceLinks(t *testing.T) {
   111  	m := model.Manifest{
   112  		Name: "foo",
   113  	}.WithDeployTarget(model.LocalTarget{
   114  		Links: []model.Link{
   115  			model.MustNewLink("www.apple.edu", "apple"),
   116  			model.MustNewLink("www.zombo.com", "zombo"),
   117  		},
   118  	})
   119  	state := newState([]model.Manifest{m})
   120  	v := StateToTerminalView(*state, &sync.RWMutex{})
   121  	res, _ := v.Resource(m.Name)
   122  	assert.Equal(t,
   123  		[]string{"www.apple.edu", "www.zombo.com"},
   124  		res.Endpoints)
   125  }
   126  
   127  func TestRuntimeStateNonWorkload(t *testing.T) {
   128  	f := tempdir.NewTempDirFixture(t)
   129  
   130  	m := manifestbuilder.New(f, model.UnresourcedYAMLManifestName).
   131  		WithK8sYAML(testyaml.SecretYaml).
   132  		Build()
   133  	state := newState([]model.Manifest{m})
   134  	runtimeState := state.ManifestTargets[m.Name].State.K8sRuntimeState()
   135  	assert.Equal(t, v1alpha1.RuntimeStatusPending, runtimeState.RuntimeStatus())
   136  
   137  	runtimeState.HasEverDeployedSuccessfully = true
   138  
   139  	assert.Equal(t, v1alpha1.RuntimeStatusOK, runtimeState.RuntimeStatus())
   140  }
   141  
   142  func TestRuntimeStateJob(t *testing.T) {
   143  	for _, tc := range []struct {
   144  		phase                 v1.PodPhase
   145  		expectedRuntimeStatus v1alpha1.RuntimeStatus
   146  	}{
   147  		{v1.PodRunning, v1alpha1.RuntimeStatusPending},
   148  		{v1.PodSucceeded, v1alpha1.RuntimeStatusOK},
   149  		{v1.PodFailed, v1alpha1.RuntimeStatusError},
   150  	} {
   151  		t.Run(string(tc.phase), func(t *testing.T) {
   152  			f := tempdir.NewTempDirFixture(t)
   153  
   154  			m := manifestbuilder.New(f, "foo").
   155  				WithK8sYAML(testyaml.JobYAML).
   156  				WithK8sPodReadiness(model.PodReadinessSucceeded).
   157  				Build()
   158  			state := newState([]model.Manifest{m})
   159  			runtimeState := state.ManifestTargets[m.Name].State.K8sRuntimeState()
   160  			assert.Equal(t, v1alpha1.RuntimeStatusPending, runtimeState.RuntimeStatus())
   161  
   162  			runtimeState.HasEverDeployedSuccessfully = true
   163  
   164  			pod := v1alpha1.Pod{
   165  				Name:      "pod",
   166  				CreatedAt: apis.Now(),
   167  				Phase:     string(tc.phase),
   168  			}
   169  			runtimeState.FilteredPods = []v1alpha1.Pod{pod}
   170  
   171  			assert.Equal(t, tc.expectedRuntimeStatus, runtimeState.RuntimeStatus())
   172  		})
   173  	}
   174  }
   175  
   176  func TestRuntimeStateJobCompleteMissingPods(t *testing.T) {
   177  	f := tempdir.NewTempDirFixture(t)
   178  
   179  	m := manifestbuilder.New(f, "foo").
   180  		WithK8sYAML(testyaml.JobYAML).
   181  		WithK8sPodReadiness(model.PodReadinessSucceeded).
   182  		Build()
   183  	state := newState([]model.Manifest{m})
   184  	runtimeState := state.ManifestTargets[m.Name].State.K8sRuntimeState()
   185  	runtimeState.HasEverDeployedSuccessfully = true
   186  	assert.Equal(t, v1alpha1.RuntimeStatusPending, runtimeState.RuntimeStatus())
   187  
   188  	// N.B. there are no pods but a condition attached
   189  	runtimeState.Conditions = []metav1.Condition{
   190  		{
   191  			Type:   v1alpha1.ApplyConditionJobComplete,
   192  			Status: metav1.ConditionTrue,
   193  		},
   194  	}
   195  	assert.Equal(t, v1alpha1.RuntimeStatusOK, runtimeState.RuntimeStatus())
   196  }
   197  
   198  func TestStateToTerminalViewUnresourcedYAMLManifest(t *testing.T) {
   199  	m := k8sManifest(t, model.UnresourcedYAMLManifestName, testyaml.SanchoYAML)
   200  	state := newState([]model.Manifest{m})
   201  	krs := state.ManifestTargets[m.Name].State.K8sRuntimeState()
   202  	krs.ApplyFilter = yamlToApplyFilter(t, testyaml.SanchoYAML)
   203  	state.ManifestTargets[m.Name].State.RuntimeState = krs
   204  
   205  	v := StateToTerminalView(*state, &sync.RWMutex{})
   206  
   207  	assert.Equal(t, 2, len(v.Resources))
   208  
   209  	r, _ := v.Resource(m.Name)
   210  	assert.Equal(t, nil, r.LastBuild().Error)
   211  
   212  	expectedInfo := view.YAMLResourceInfo{
   213  		K8sDisplayNames: []string{"sancho:deployment"},
   214  	}
   215  	assert.Equal(t, expectedInfo, r.ResourceInfo)
   216  }
   217  
   218  func TestStateToTerminalViewNonWorkloadYAMLManifest(t *testing.T) {
   219  	m := k8sManifest(t, "foo", testyaml.SecretYaml)
   220  	state := newState([]model.Manifest{m})
   221  	krs := state.ManifestTargets[m.Name].State.K8sRuntimeState()
   222  	krs.ApplyFilter = yamlToApplyFilter(t, testyaml.SecretYaml)
   223  	state.ManifestTargets[m.Name].State.RuntimeState = krs
   224  
   225  	v := StateToTerminalView(*state, &sync.RWMutex{})
   226  
   227  	assert.Equal(t, 2, len(v.Resources))
   228  
   229  	r, _ := v.Resource(m.Name)
   230  	assert.Equal(t, nil, r.LastBuild().Error)
   231  
   232  	expectedInfo := view.YAMLResourceInfo{
   233  		K8sDisplayNames: []string{"mysecret:secret"},
   234  	}
   235  	assert.Equal(t, expectedInfo, r.ResourceInfo)
   236  }
   237  
   238  func newState(manifests []model.Manifest) *store.EngineState {
   239  	ret := store.NewState()
   240  	for _, m := range manifests {
   241  		ret.ManifestTargets[m.Name] = store.NewManifestTarget(m)
   242  		ret.ManifestDefinitionOrder = append(ret.ManifestDefinitionOrder, m.Name)
   243  	}
   244  
   245  	return ret
   246  }
   247  
   248  func yamlToApplyFilter(t testing.TB, yaml string) *k8sconv.KubernetesApplyFilter {
   249  	t.Helper()
   250  	entities, err := k8s.ParseYAMLFromString(yaml)
   251  	require.NoError(t, err, "Failed to parse YAML")
   252  	for i := range entities {
   253  		entities[i].SetUID(uuid.New().String())
   254  	}
   255  	yaml, err = k8s.SerializeSpecYAML(entities)
   256  	require.NoError(t, err, "Failed to re-serialize YAML")
   257  	applyFilter, err := k8sconv.NewKubernetesApplyFilter(yaml)
   258  	require.NoError(t, err, "Failed to create KubernetesApplyFilter")
   259  	require.NotNil(t, applyFilter, "ApplyFilter was nil")
   260  	return applyFilter
   261  }
   262  
   263  func k8sManifest(t testing.TB, name model.ManifestName, yaml string) model.Manifest {
   264  	t.Helper()
   265  	kt, err := k8s.NewTargetForYAML(name.TargetName(), yaml, nil)
   266  	require.NoError(t, err, "Failed to create Kubernetes deploy target")
   267  	return model.Manifest{Name: name}.WithDeployTarget(kt)
   268  }