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  }