github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/testutils/manifestbuilder/manifestbuilder.go (about) 1 package manifestbuilder 2 3 import ( 4 "fmt" 5 "testing" 6 7 "k8s.io/apimachinery/pkg/labels" 8 9 "github.com/stretchr/testify/require" 10 11 "github.com/tilt-dev/tilt/internal/controllers/apis/cmdimage" 12 "github.com/tilt-dev/tilt/internal/controllers/apis/dockerimage" 13 "github.com/tilt-dev/tilt/internal/controllers/apis/liveupdate" 14 "github.com/tilt-dev/tilt/internal/k8s" 15 "github.com/tilt-dev/tilt/pkg/apis/core/v1alpha1" 16 "github.com/tilt-dev/tilt/pkg/model" 17 ) 18 19 // Builds Manifest objects for testing. 20 // 21 // To create well-formed manifests, we want to make sure that: 22 // - The relationships between targets are internally consistent 23 // (e.g., if there's an ImageTarget and a K8sTarget in the manifest, then 24 // the K8sTarget should depend on the ImageTarget). 25 // - Any filepaths in the manifest are scoped to the 26 // test directory (e.g., we're not trying to watch random directories 27 // outside the test environment). 28 29 type ManifestBuilder struct { 30 f Fixture 31 name model.ManifestName 32 33 k8sPodReadiness model.PodReadinessMode 34 k8sYAML string 35 k8sPodSelectors []labels.Set 36 k8sImageLocators []v1alpha1.KubernetesImageLocator 37 dcConfigPaths []string 38 localCmd string 39 localServeCmd string 40 localDeps []string 41 localAllowParallel bool 42 resourceDeps []string 43 triggerMode model.TriggerMode 44 45 iTargets []model.ImageTarget 46 } 47 48 func New(f Fixture, name model.ManifestName) ManifestBuilder { 49 k8sPodReadiness := model.PodReadinessWait 50 51 // TODO(nick): A better solution would be to identify whether this 52 // is a workload based on the YAML. 53 if name == model.UnresourcedYAMLManifestName { 54 k8sPodReadiness = model.PodReadinessIgnore 55 } 56 57 return ManifestBuilder{ 58 f: f, 59 name: name, 60 k8sPodReadiness: k8sPodReadiness, 61 } 62 } 63 64 func (b ManifestBuilder) WithNamedJSONPathImageLocator(name, path string) ManifestBuilder { 65 b.k8sImageLocators = append(b.k8sImageLocators, v1alpha1.KubernetesImageLocator{ 66 ObjectSelector: k8s.MustNameSelector(name).ToSpec(), 67 Path: path, 68 }) 69 return b 70 } 71 72 func (b ManifestBuilder) WithK8sPodReadiness(pr model.PodReadinessMode) ManifestBuilder { 73 b.k8sPodReadiness = pr 74 return b 75 } 76 77 func (b ManifestBuilder) WithK8sYAML(yaml string) ManifestBuilder { 78 b.k8sYAML = yaml 79 return b 80 } 81 82 func (b ManifestBuilder) WithK8sPodSelectors(podSelectors []labels.Set) ManifestBuilder { 83 b.k8sPodSelectors = podSelectors 84 return b 85 } 86 87 func (b ManifestBuilder) WithDockerCompose() ManifestBuilder { 88 b.dcConfigPaths = []string{b.f.JoinPath("docker-compose.yml")} 89 return b 90 } 91 92 func (b ManifestBuilder) WithLocalResource(cmd string, deps []string) ManifestBuilder { 93 b.localCmd = cmd 94 b.localDeps = deps 95 return b 96 } 97 98 func (b ManifestBuilder) WithLocalServeCmd(cmd string) ManifestBuilder { 99 b.localServeCmd = cmd 100 return b 101 } 102 103 func (b ManifestBuilder) WithLocalAllowParallel(v bool) ManifestBuilder { 104 b.localAllowParallel = v 105 return b 106 } 107 108 func (b ManifestBuilder) WithTriggerMode(tm model.TriggerMode) ManifestBuilder { 109 b.triggerMode = tm 110 return b 111 } 112 113 func (b ManifestBuilder) WithImageTarget(iTarg model.ImageTarget) ManifestBuilder { 114 b.iTargets = append(b.iTargets, iTarg) 115 return b 116 } 117 118 func (b ManifestBuilder) WithImageTargets(iTargs ...model.ImageTarget) ManifestBuilder { 119 b.iTargets = append(b.iTargets, iTargs...) 120 return b 121 } 122 123 func (b ManifestBuilder) WithLiveUpdate(lu v1alpha1.LiveUpdateSpec) ManifestBuilder { 124 return b.WithLiveUpdateAtIndex(lu, 0) 125 } 126 127 func (b ManifestBuilder) WithLiveUpdateAtIndex(lu v1alpha1.LiveUpdateSpec, index int) ManifestBuilder { 128 if len(b.iTargets) <= index { 129 b.f.T().Fatalf("WithLiveUpdateAtIndex: index %d out of range -- (manifestBuilder has %d image targets)", index, len(b.iTargets)) 130 } 131 132 iTarg := b.iTargets[index] 133 iTarg.LiveUpdateSpec = lu 134 if !liveupdate.IsEmptySpec(lu) { 135 iTarg.LiveUpdateName = liveupdate.GetName(b.name, iTarg.ID()) 136 } 137 b.iTargets[index] = iTarg 138 return b 139 } 140 141 func (b ManifestBuilder) WithResourceDeps(deps ...string) ManifestBuilder { 142 b.resourceDeps = deps 143 return b 144 } 145 146 func (b ManifestBuilder) Build() model.Manifest { 147 var m model.Manifest 148 149 // Adjust images to use their API server object names, when appropriate. 150 // Currently, 151 for index, iTarget := range b.iTargets { 152 if iTarget.IsDockerBuild() { 153 iTarget.DockerImageName = dockerimage.GetName(b.name, iTarget.ID()) 154 } else if iTarget.IsCustomBuild() { 155 iTarget.CmdImageName = cmdimage.GetName(b.name, iTarget.ID()) 156 } 157 158 if len(b.dcConfigPaths) != 0 { 159 if iTarget.IsDockerBuild() { 160 dbi := iTarget.DockerBuildInfo() 161 dbi.DockerImageSpec.Cluster = v1alpha1.ClusterNameDocker 162 iTarget.BuildDetails = dbi 163 } else if iTarget.IsCustomBuild() { 164 cbi := iTarget.CustomBuildInfo() 165 cbi.CmdImageSpec.Cluster = v1alpha1.ClusterNameDocker 166 iTarget.BuildDetails = cbi 167 } 168 } 169 170 if liveupdate.IsEmptySpec(iTarget.LiveUpdateSpec) { 171 iTarget.LiveUpdateReconciler = false 172 } else { 173 iTarget.LiveUpdateReconciler = true 174 } 175 b.iTargets[index] = iTarget 176 } 177 178 var rds []model.ManifestName 179 for _, dep := range b.resourceDeps { 180 rds = append(rds, model.ManifestName(dep)) 181 } 182 183 if b.k8sYAML != "" { 184 k8sTarget := k8s.MustTarget(model.TargetName(b.name), b.k8sYAML) 185 k8sTarget.KubernetesApplySpec.KubernetesDiscoveryTemplateSpec = &v1alpha1.KubernetesDiscoveryTemplateSpec{ 186 ExtraSelectors: k8s.SetsAsLabelSelectors(b.k8sPodSelectors), 187 } 188 k8sTarget.ImageLocators = append(k8sTarget.ImageLocators, b.k8sImageLocators...) 189 k8sTarget.PodReadinessMode = b.k8sPodReadiness 190 191 m = assembleK8s( 192 model.Manifest{Name: b.name, ResourceDependencies: rds}, 193 k8sTarget, 194 b.iTargets...) 195 } else if len(b.dcConfigPaths) > 0 { 196 m = assembleDC( 197 model.Manifest{Name: b.name, ResourceDependencies: rds}, 198 model.DockerComposeTarget{ 199 Spec: v1alpha1.DockerComposeServiceSpec{ 200 Service: string(b.name), 201 Project: v1alpha1.DockerComposeProject{ 202 ConfigPaths: b.dcConfigPaths, 203 }, 204 }, 205 Name: model.TargetName(b.name), 206 }, 207 b.iTargets...) 208 } else if b.localCmd != "" || b.localServeCmd != "" { 209 updateCmd := model.ToHostCmd(b.localCmd) 210 updateCmd.Dir = b.f.Path() 211 212 serveCmd := model.ToHostCmd(b.localServeCmd) 213 serveCmd.Dir = b.f.Path() 214 215 lt := model.NewLocalTarget( 216 model.TargetName(b.name), 217 updateCmd, 218 serveCmd, 219 b.localDeps). 220 WithAllowParallel(b.localAllowParallel) 221 m = model.Manifest{Name: b.name, ResourceDependencies: rds}.WithDeployTarget(lt) 222 223 m = m.WithDisableSource(&v1alpha1.DisableSource{ 224 ConfigMap: &v1alpha1.ConfigMapDisableSource{ 225 Name: fmt.Sprintf("%s-disable", b.name), 226 Key: "isDisabled", 227 }, 228 }) 229 } else { 230 b.f.T().Fatalf("No deploy target specified: %s", b.name) 231 return model.Manifest{} 232 } 233 m = m.WithTriggerMode(b.triggerMode) 234 235 err := m.InferImageProperties() 236 require.NoError(b.f.T(), err) 237 238 err = m.InferLiveUpdateSelectors() 239 require.NoError(b.f.T(), err) 240 return m 241 } 242 243 type Fixture interface { 244 T() testing.TB 245 Path() string 246 JoinPath(ps ...string) string 247 MkdirAll(p string) 248 }