github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/runner/v1/dev.go (about) 1 /* 2 Copyright 2019 The Skaffold Authors 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package v1 18 19 import ( 20 "context" 21 "fmt" 22 "io" 23 "strconv" 24 "time" 25 26 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants" 27 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/event" 28 eventV2 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/event/v2" 29 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/filemon" 30 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/graph" 31 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/instrumentation" 32 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/output" 33 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/output/log" 34 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/runner" 35 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" 36 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/sync" 37 timeutil "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util/time" 38 "github.com/GoogleContainerTools/skaffold/proto/v1" 39 ) 40 41 var ( 42 // For testing 43 fileSyncInProgress = event.FileSyncInProgress 44 fileSyncFailed = event.FileSyncFailed 45 fileSyncSucceeded = event.FileSyncSucceeded 46 ) 47 48 func (r *SkaffoldRunner) doDev(ctx context.Context, out io.Writer) error { 49 // never queue intents from user, even if they're not used 50 defer r.intents.Reset() 51 52 if r.changeSet.NeedsReload() { 53 return runner.ErrorConfigurationChanged 54 } 55 56 buildIntent, syncIntent, deployIntent := r.intents.GetIntents() 57 log.Entry(ctx).Tracef("dev intents: build %t, sync %t, deploy %t\n", buildIntent, syncIntent, deployIntent) 58 needsBuild := buildIntent && len(r.changeSet.NeedsRebuild()) > 0 59 needsSync := syncIntent && (len(r.changeSet.NeedsResync()) > 0 || needsBuild) 60 needsTest := len(r.changeSet.NeedsRetest()) > 0 61 needsDeploy := deployIntent && (r.changeSet.NeedsRedeploy() || needsBuild) 62 if !needsSync && !needsBuild && !needsTest && !needsDeploy { 63 return nil 64 } 65 log.Entry(ctx).Debugf(" devloop: build %t, sync %t, deploy %t\n", needsBuild, needsSync, needsDeploy) 66 67 r.deployer.GetLogger().Mute() 68 // if any action is going to be performed, reset the monitor's changed component tracker for debouncing 69 defer r.monitor.Reset() 70 defer r.listener.LogWatchToUser(out) 71 72 event.DevLoopInProgress(r.devIteration) 73 eventV2.InitializeState(r.runCtx) 74 eventV2.TaskInProgress(constants.DevLoop, "") 75 defer func() { r.devIteration++ }() 76 eventV2.LogMetaEvent() 77 ctx, endTrace := instrumentation.StartTrace(ctx, "doDev_DevLoopInProgress", map[string]string{ 78 "devIteration": strconv.Itoa(r.devIteration), 79 }) 80 81 meterUpdated := false 82 if needsSync { 83 childCtx, endTrace := instrumentation.StartTrace(ctx, "doDev_needsSync") 84 defer func() { 85 r.changeSet.ResetSync() 86 r.intents.ResetSync() 87 }() 88 instrumentation.AddDevIteration("sync") 89 meterUpdated = true 90 for _, s := range r.changeSet.NeedsResync() { 91 fileCount := len(s.Copy) + len(s.Delete) 92 output.Default.Fprintf(out, "Syncing %d files for %s\n", fileCount, s.Image) 93 fileSyncInProgress(fileCount, s.Image) 94 95 if err := r.deployer.GetSyncer().Sync(childCtx, out, s); err != nil { 96 log.Entry(ctx).Warn("Skipping deploy due to sync error:", err) 97 fileSyncFailed(fileCount, s.Image, err) 98 event.DevLoopFailedInPhase(r.devIteration, constants.Sync, err) 99 eventV2.TaskFailed(constants.DevLoop, err) 100 endTrace(instrumentation.TraceEndError(err)) 101 102 return nil 103 } 104 105 fileSyncSucceeded(fileCount, s.Image) 106 } 107 endTrace() 108 } 109 110 var bRes []graph.Artifact 111 if needsBuild { 112 childCtx, endTrace := instrumentation.StartTrace(ctx, "doDev_needsBuild") 113 event.ResetStateOnBuild() 114 defer func() { 115 r.changeSet.ResetBuild() 116 r.intents.ResetBuild() 117 }() 118 if !meterUpdated { 119 instrumentation.AddDevIteration("build") 120 meterUpdated = true 121 } 122 123 var err error 124 bRes, err = r.Build(childCtx, out, r.changeSet.NeedsRebuild()) 125 if err != nil { 126 log.Entry(ctx).Warn("Skipping test and deploy due to build error:", err) 127 event.DevLoopFailedInPhase(r.devIteration, constants.Build, err) 128 eventV2.TaskFailed(constants.DevLoop, err) 129 endTrace(instrumentation.TraceEndError(err)) 130 return nil 131 } 132 r.changeSet.Redeploy() 133 needsDeploy = deployIntent 134 endTrace() 135 } 136 137 // Trigger retest when there are newly rebuilt artifacts or untested previous artifacts; and it's not explicitly skipped 138 if (len(bRes) > 0 || needsTest) && r.runCtx.IsTestPhaseActive() { 139 childCtx, endTrace := instrumentation.StartTrace(ctx, "doDev_needsTest") 140 event.ResetStateOnTest() 141 defer func() { 142 r.changeSet.ResetTest() 143 }() 144 for _, a := range bRes { 145 delete(r.changeSet.NeedsRetest(), a.ImageName) 146 } 147 for _, a := range r.Builds { 148 if r.changeSet.NeedsRetest()[a.ImageName] { 149 bRes = append(bRes, a) 150 } 151 } 152 if err := r.Test(childCtx, out, bRes); err != nil { 153 if needsDeploy { 154 log.Entry(ctx).Warn("Skipping deploy due to test error:", err) 155 } 156 event.DevLoopFailedInPhase(r.devIteration, constants.Test, err) 157 eventV2.TaskFailed(constants.DevLoop, err) 158 endTrace(instrumentation.TraceEndError(err)) 159 return nil 160 } 161 endTrace() 162 } 163 164 if needsDeploy { 165 childCtx, endTrace := instrumentation.StartTrace(ctx, "doDev_needsDeploy") 166 event.ResetStateOnDeploy() 167 defer func() { 168 r.changeSet.ResetDeploy() 169 r.intents.ResetDeploy() 170 }() 171 172 log.Entry(ctx).Debug("stopping accessor") 173 r.deployer.GetAccessor().Stop() 174 175 log.Entry(ctx).Debug("stopping debugger") 176 r.deployer.GetDebugger().Stop() 177 178 if !meterUpdated { 179 instrumentation.AddDevIteration("deploy") 180 } 181 if err := r.Deploy(childCtx, out, r.Builds); err != nil { 182 log.Entry(ctx).Warn("Skipping deploy due to error:", err) 183 event.DevLoopFailedInPhase(r.devIteration, constants.Deploy, err) 184 eventV2.TaskFailed(constants.DevLoop, err) 185 endTrace(instrumentation.TraceEndError(err)) 186 return nil 187 } 188 189 if err := r.deployer.GetAccessor().Start(childCtx, out); err != nil { 190 log.Entry(ctx).Warnf("failed to start accessor: %v", err) 191 } 192 193 if err := r.deployer.GetDebugger().Start(childCtx); err != nil { 194 log.Entry(ctx).Warnf("failed to start debugger: %v", err) 195 } 196 197 endTrace() 198 } 199 event.DevLoopComplete(r.devIteration) 200 eventV2.TaskSucceeded(constants.DevLoop) 201 endTrace() 202 r.deployer.GetLogger().Unmute() 203 return nil 204 } 205 206 // Dev watches for changes and runs the skaffold build, test and deploy 207 // config until interrupted by the user. 208 func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*latest.Artifact) error { 209 event.DevLoopInProgress(r.devIteration) 210 eventV2.InitializeState(r.runCtx) 211 eventV2.TaskInProgress(constants.DevLoop, "") 212 defer func() { r.devIteration++ }() 213 eventV2.LogMetaEvent() 214 ctx, endTrace := instrumentation.StartTrace(ctx, "Dev", map[string]string{ 215 "devIteration": strconv.Itoa(r.devIteration), 216 }) 217 218 g := getTransposeGraph(artifacts) 219 // Watch artifacts 220 start := time.Now() 221 output.Default.Fprintln(out, "Listing files to watch...") 222 223 for i := range artifacts { 224 artifact := artifacts[i] 225 if !r.runCtx.Opts.IsTargetImage(artifact) { 226 continue 227 } 228 229 output.Default.Fprintf(out, " - %s\n", artifact.ImageName) 230 231 select { 232 case <-ctx.Done(): 233 return context.Canceled 234 default: 235 if err := r.monitor.Register( 236 func() ([]string, error) { 237 return r.sourceDependencies.TransitiveArtifactDependencies(ctx, artifact) 238 }, 239 func(e filemon.Events) { 240 s, err := sync.NewItem(ctx, artifact, e, r.Builds, r.runCtx, len(g[artifact.ImageName])) 241 switch { 242 case err != nil: 243 log.Entry(ctx).Warnf("error adding dirty artifact to changeset: %s", err.Error()) 244 case s != nil: 245 r.changeSet.AddResync(s) 246 default: 247 r.changeSet.AddRebuild(artifact) 248 } 249 }, 250 ); err != nil { 251 event.DevLoopFailedWithErrorCode(r.devIteration, proto.StatusCode_DEVINIT_REGISTER_BUILD_DEPS, err) 252 eventV2.TaskFailed(constants.DevLoop, err) 253 endTrace() 254 return fmt.Errorf("watching files for artifact %q: %w", artifact.ImageName, err) 255 } 256 } 257 } 258 259 // Watch test configuration 260 for i := range artifacts { 261 artifact := artifacts[i] 262 if err := r.monitor.Register( 263 func() ([]string, error) { return r.tester.TestDependencies(ctx, artifact) }, 264 func(filemon.Events) { r.changeSet.AddRetest(artifact) }, 265 ); err != nil { 266 event.DevLoopFailedWithErrorCode(r.devIteration, proto.StatusCode_DEVINIT_REGISTER_TEST_DEPS, err) 267 eventV2.TaskFailed(constants.DevLoop, err) 268 endTrace() 269 return fmt.Errorf("watching test files: %w", err) 270 } 271 } 272 273 // Watch deployment configuration 274 if err := r.monitor.Register( 275 r.deployer.Dependencies, 276 func(filemon.Events) { r.changeSet.Redeploy() }, 277 ); err != nil { 278 event.DevLoopFailedWithErrorCode(r.devIteration, proto.StatusCode_DEVINIT_REGISTER_DEPLOY_DEPS, err) 279 eventV2.TaskFailed(constants.DevLoop, err) 280 endTrace() 281 return fmt.Errorf("watching files for deployer: %w", err) 282 } 283 284 // Watch Skaffold configuration 285 if err := r.monitor.Register( 286 func() ([]string, error) { return []string{r.runCtx.ConfigurationFile()}, nil }, 287 func(filemon.Events) { r.changeSet.Reload() }, 288 ); err != nil { 289 event.DevLoopFailedWithErrorCode(r.devIteration, proto.StatusCode_DEVINIT_REGISTER_CONFIG_DEP, err) 290 eventV2.TaskFailed(constants.DevLoop, err) 291 endTrace() 292 return fmt.Errorf("watching skaffold configuration %q: %w", r.runCtx.ConfigurationFile(), err) 293 } 294 295 log.Entry(ctx).Infoln("List generated in", timeutil.Humanize(time.Since(start))) 296 297 // Init Sync State 298 if err := sync.Init(ctx, artifacts); err != nil { 299 event.DevLoopFailedWithErrorCode(r.devIteration, proto.StatusCode_SYNC_INIT_ERROR, err) 300 eventV2.TaskFailed(constants.DevLoop, err) 301 endTrace() 302 return fmt.Errorf("exiting dev mode because initializing sync state failed: %w", err) 303 } 304 305 // First build 306 bRes, err := r.Build(ctx, out, artifacts) 307 if err != nil { 308 event.DevLoopFailedInPhase(r.devIteration, constants.Build, err) 309 eventV2.TaskFailed(constants.DevLoop, err) 310 endTrace() 311 return fmt.Errorf("exiting dev mode because first build failed: %w", err) 312 } 313 // First test 314 if r.runCtx.IsTestPhaseActive() { 315 if err = r.Test(ctx, out, bRes); err != nil { 316 event.DevLoopFailedInPhase(r.devIteration, constants.Build, err) 317 eventV2.TaskFailed(constants.DevLoop, err) 318 endTrace() 319 return fmt.Errorf("exiting dev mode because test failed after first build: %w", err) 320 } 321 } 322 323 defer r.deployer.GetLogger().Stop() 324 defer r.deployer.GetDebugger().Stop() 325 326 // Logs should be retrieved up to just before the deploy 327 r.deployer.GetLogger().SetSince(time.Now()) 328 329 // First deploy 330 if err := r.Deploy(ctx, out, r.Builds); err != nil { 331 event.DevLoopFailedInPhase(r.devIteration, constants.Deploy, err) 332 eventV2.TaskFailed(constants.DevLoop, err) 333 endTrace() 334 return fmt.Errorf("exiting dev mode because first deploy failed: %w", err) 335 } 336 337 defer r.deployer.GetAccessor().Stop() 338 339 if err := r.deployer.GetAccessor().Start(ctx, out); err != nil { 340 log.Entry(ctx).Warn("Error starting resource accessor:", err) 341 } 342 if err := r.deployer.GetDebugger().Start(ctx); err != nil { 343 log.Entry(ctx).Warn("Error starting debug container notification:", err) 344 } 345 // Start printing the logs after deploy is finished 346 if err := r.deployer.GetLogger().Start(ctx, out); err != nil { 347 return fmt.Errorf("starting logger: %w", err) 348 } 349 350 output.Yellow.Fprintln(out, "Press Ctrl+C to exit") 351 352 event.DevLoopComplete(r.devIteration) 353 eventV2.TaskSucceeded(constants.DevLoop) 354 endTrace() 355 r.devIteration++ 356 return r.listener.WatchForChanges(ctx, out, func() error { 357 return r.doDev(ctx, out) 358 }) 359 } 360 361 // graph represents the artifact graph 362 type devGraph map[string][]*latest.Artifact 363 364 // getTransposeGraph builds the transpose of the graph represented by the artifacts slice, with edges directed from required artifact to the dependent artifact. 365 func getTransposeGraph(artifacts []*latest.Artifact) devGraph { 366 g := make(map[string][]*latest.Artifact) 367 for _, a := range artifacts { 368 for _, d := range a.Dependencies { 369 g[d.ImageName] = append(g[d.ImageName], a) 370 } 371 } 372 return g 373 }