github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/runner/v1/new.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  
    23  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/build"
    24  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/cache"
    25  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/deploy"
    26  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/deploy/label"
    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/log"
    33  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/platform"
    34  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/runner"
    35  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/runner/runcontext"
    36  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
    37  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/server"
    38  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/tag"
    39  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/test"
    40  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/trigger"
    41  )
    42  
    43  // NewForConfig returns a new SkaffoldRunner for a SkaffoldConfig
    44  func NewForConfig(ctx context.Context, runCtx *runcontext.RunContext) (*SkaffoldRunner, error) {
    45  	event.InitializeState(runCtx)
    46  	event.LogMetaEvent()
    47  	eventV2.InitializeState(runCtx)
    48  	eventV2.LogMetaEvent()
    49  	_, endTrace := instrumentation.StartTrace(context.Background(), "NewForConfig")
    50  	defer endTrace()
    51  
    52  	tagger, err := tag.NewTaggerMux(runCtx)
    53  	if err != nil {
    54  		endTrace(instrumentation.TraceEndError(err))
    55  		return nil, fmt.Errorf("creating tagger: %w", err)
    56  	}
    57  
    58  	store := build.NewArtifactStore()
    59  	g := graph.ToArtifactGraph(runCtx.Artifacts())
    60  	sourceDependencies := graph.NewSourceDependenciesCache(runCtx, store, g)
    61  
    62  	isLocalImage := func(imageName string) (bool, error) {
    63  		return isImageLocal(runCtx, imageName)
    64  	}
    65  
    66  	// Always add skaffold-specific labels, except during `skaffold render`
    67  	labeller := label.NewLabeller(runCtx.AddSkaffoldLabels(), runCtx.CustomLabels(), runCtx.GetRunID())
    68  	tester, err := getTester(ctx, runCtx, isLocalImage)
    69  	if err != nil {
    70  		endTrace(instrumentation.TraceEndError(err))
    71  		return nil, fmt.Errorf("creating tester: %w", err)
    72  	}
    73  
    74  	var deployer deploy.Deployer
    75  	deployer, err = runner.GetDeployer(ctx, runCtx, labeller)
    76  	if err != nil {
    77  		endTrace(instrumentation.TraceEndError(err))
    78  		return nil, fmt.Errorf("creating deployer: %w", err)
    79  	}
    80  	platforms, err := platform.NewResolver(ctx, runCtx.Pipelines.All(), runCtx.Opts.Platforms, runCtx.Mode(), runCtx.KubeContext)
    81  	if err != nil {
    82  		endTrace(instrumentation.TraceEndError(err))
    83  		return nil, fmt.Errorf("getting target platforms: %w", err)
    84  	}
    85  	// The Builder must be instantiated AFTER the Deployer, because the Deploy target influences
    86  	// the Cluster object on the RunContext, which in turn influences whether or not we will push images.
    87  	var builder build.Builder
    88  	builder, err = build.NewBuilderMux(runCtx, store, func(p latest.Pipeline) (build.PipelineBuilder, error) {
    89  		return runner.GetBuilder(ctx, runCtx, store, sourceDependencies, p)
    90  	})
    91  	if err != nil {
    92  		endTrace(instrumentation.TraceEndError(err))
    93  		return nil, fmt.Errorf("creating builder: %w", err)
    94  	}
    95  
    96  	depLister := func(ctx context.Context, artifact *latest.Artifact) ([]string, error) {
    97  		ctx, endTrace := instrumentation.StartTrace(ctx, "NewForConfig_depLister")
    98  		defer endTrace()
    99  
   100  		buildDependencies, err := sourceDependencies.SingleArtifactDependencies(ctx, artifact)
   101  		if err != nil {
   102  			endTrace(instrumentation.TraceEndError(err))
   103  			return nil, err
   104  		}
   105  
   106  		testDependencies, err := tester.TestDependencies(ctx, artifact)
   107  		if err != nil {
   108  			endTrace(instrumentation.TraceEndError(err))
   109  			return nil, err
   110  		}
   111  		return append(buildDependencies, testDependencies...), nil
   112  	}
   113  
   114  	artifactCache, err := cache.NewCache(ctx, runCtx, isLocalImage, depLister, g, store)
   115  	if err != nil {
   116  		endTrace(instrumentation.TraceEndError(err))
   117  		return nil, fmt.Errorf("initializing cache: %w", err)
   118  	}
   119  
   120  	builder, tester, deployer = runner.WithTimings(builder, tester, deployer, runCtx.CacheArtifacts())
   121  	if runCtx.Notification() {
   122  		deployer = runner.WithNotification(deployer)
   123  	}
   124  
   125  	monitor := filemon.NewMonitor()
   126  	intents, intentChan := setupIntents(runCtx)
   127  	rtrigger, err := trigger.NewTrigger(runCtx, intents.IsAnyAutoEnabled)
   128  	if err != nil {
   129  		endTrace(instrumentation.TraceEndError(err))
   130  		return nil, fmt.Errorf("creating watch trigger: %w", err)
   131  	}
   132  
   133  	rbuilder := runner.NewBuilder(builder, tagger, platforms, artifactCache, runCtx)
   134  	return &SkaffoldRunner{
   135  		Builder:            *rbuilder,
   136  		Pruner:             runner.Pruner{Builder: builder},
   137  		tester:             tester,
   138  		deployer:           deployer,
   139  		platforms:          platforms,
   140  		monitor:            monitor,
   141  		listener:           runner.NewSkaffoldListener(monitor, rtrigger, sourceDependencies, intentChan),
   142  		artifactStore:      store,
   143  		sourceDependencies: sourceDependencies,
   144  		labeller:           labeller,
   145  		cache:              artifactCache,
   146  		runCtx:             runCtx,
   147  		intents:            intents,
   148  		isLocalImage:       isLocalImage,
   149  	}, nil
   150  }
   151  
   152  func setupIntents(runCtx *runcontext.RunContext) (*runner.Intents, chan bool) {
   153  	intents := runner.NewIntents(runCtx.AutoBuild(), runCtx.AutoSync(), runCtx.AutoDeploy())
   154  
   155  	intentChan := make(chan bool, 1)
   156  	setupTrigger("build", intents.SetBuild, intents.SetAutoBuild, intents.GetAutoBuild, server.SetBuildCallback, server.SetAutoBuildCallback, intentChan)
   157  	setupTrigger("sync", intents.SetSync, intents.SetAutoSync, intents.GetAutoSync, server.SetSyncCallback, server.SetAutoSyncCallback, intentChan)
   158  	setupTrigger("deploy", intents.SetDeploy, intents.SetAutoDeploy, intents.GetAutoDeploy, server.SetDeployCallback, server.SetAutoDeployCallback, intentChan)
   159  	// Setup callback function to buildCallback since build is the start of the devloop.
   160  	setupTrigger("devloop", intents.SetDevloop, intents.SetAutoDevloop, intents.GetAutoDevloop, server.SetDevloopCallback, server.SetAutoDevloopCallback, intentChan)
   161  
   162  	return intents, intentChan
   163  }
   164  
   165  func setupTrigger(triggerName string, setIntent func(bool), setAutoTrigger func(bool), getAutoTrigger func() bool, singleTriggerCallback func(func()), autoTriggerCallback func(func(bool)), c chan<- bool) {
   166  	setIntent(getAutoTrigger())
   167  	// give the server a callback to set the intent value when a user request is received
   168  	singleTriggerCallback(func() {
   169  		if !getAutoTrigger() { // if auto trigger is disabled, we're in manual mode
   170  			log.Entry(context.TODO()).Debugf("%s intent received, calling back to runner", triggerName)
   171  			c <- true
   172  			setIntent(true)
   173  		}
   174  	})
   175  
   176  	// give the server a callback to update auto trigger value when a user request is received
   177  	autoTriggerCallback(func(val bool) {
   178  		log.Entry(context.TODO()).Debugf("%s auto trigger update to %t received, calling back to runner", triggerName, val)
   179  		// signal chan only when auto trigger is set to true
   180  		if val {
   181  			c <- true
   182  		}
   183  		setAutoTrigger(val)
   184  		setIntent(val)
   185  	})
   186  }
   187  
   188  func isImageLocal(runCtx *runcontext.RunContext, imageName string) (bool, error) {
   189  	pipeline, found := runCtx.PipelineForImage(imageName)
   190  	if !found {
   191  		pipeline = runCtx.DefaultPipeline()
   192  	}
   193  	if pipeline.Build.GoogleCloudBuild != nil || pipeline.Build.Cluster != nil {
   194  		return false, nil
   195  	}
   196  
   197  	// if we're deploying to local Docker, all images must be local
   198  	if pipeline.Deploy.DockerDeploy != nil {
   199  		return true, nil
   200  	}
   201  
   202  	cl := runCtx.GetCluster()
   203  	var pushImages bool
   204  
   205  	switch {
   206  	case runCtx.Opts.PushImages.Value() != nil:
   207  		log.Entry(context.TODO()).Debugf("push value set via skaffold build --push flag, --push=%t", *runCtx.Opts.PushImages.Value())
   208  		pushImages = *runCtx.Opts.PushImages.Value()
   209  	case pipeline.Build.LocalBuild.Push == nil:
   210  		pushImages = cl.PushImages
   211  		log.Entry(context.TODO()).Debugf("push value not present in isImageLocal(), defaulting to %t because cluster.PushImages is %t", pushImages, cl.PushImages)
   212  	default:
   213  		pushImages = *pipeline.Build.LocalBuild.Push
   214  	}
   215  	return !pushImages, nil
   216  }
   217  
   218  func getTester(ctx context.Context, cfg test.Config, isLocalImage func(imageName string) (bool, error)) (test.Tester, error) {
   219  	tester, err := test.NewTester(ctx, cfg, isLocalImage)
   220  	if err != nil {
   221  		return nil, err
   222  	}
   223  
   224  	return tester, nil
   225  }