github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/build/builder_mux.go (about)

     1  /*
     2  Copyright 2020 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 build
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"io"
    23  	"reflect"
    24  
    25  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants"
    26  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/graph"
    27  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/hooks"
    28  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/output/log"
    29  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/platform"
    30  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
    31  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/tag"
    32  )
    33  
    34  // BuilderMux encapsulates multiple build configs.
    35  type BuilderMux struct {
    36  	builders    []PipelineBuilder
    37  	byImageName map[string]PipelineBuilder
    38  	store       ArtifactStore
    39  	concurrency int
    40  }
    41  
    42  // Config represents an interface for getting all config pipelines.
    43  type Config interface {
    44  	GetPipelines() []latest.Pipeline
    45  	DefaultRepo() *string
    46  	MultiLevelRepo() *bool
    47  	GlobalConfig() string
    48  	BuildConcurrency() int
    49  }
    50  
    51  // NewBuilderMux returns an implementation of `build.BuilderMux`.
    52  func NewBuilderMux(cfg Config, store ArtifactStore, builder func(p latest.Pipeline) (PipelineBuilder, error)) (*BuilderMux, error) {
    53  	pipelines := cfg.GetPipelines()
    54  	m := make(map[string]PipelineBuilder)
    55  	var pbs []PipelineBuilder
    56  	for _, p := range pipelines {
    57  		b, err := builder(p)
    58  		if err != nil {
    59  			return nil, fmt.Errorf("creating builder: %w", err)
    60  		}
    61  		pbs = append(pbs, b)
    62  		for _, a := range p.Build.Artifacts {
    63  			m[a.ImageName] = b
    64  		}
    65  	}
    66  	concurrency := getConcurrency(pbs, cfg.BuildConcurrency())
    67  	return &BuilderMux{builders: pbs, byImageName: m, store: store, concurrency: concurrency}, nil
    68  }
    69  
    70  // Build executes the specific image builder for each artifact in the given artifact slice.
    71  func (b *BuilderMux) Build(ctx context.Context, out io.Writer, tags tag.ImageTags, resolver platform.Resolver, artifacts []*latest.Artifact) ([]graph.Artifact, error) {
    72  	m := make(map[PipelineBuilder]bool)
    73  	for _, a := range artifacts {
    74  		m[b.byImageName[a.ImageName]] = true
    75  	}
    76  
    77  	for builder := range m {
    78  		if err := builder.PreBuild(ctx, out); err != nil {
    79  			return nil, err
    80  		}
    81  	}
    82  
    83  	builderF := func(ctx context.Context, out io.Writer, artifact *latest.Artifact, tag string, platforms platform.Matcher) (string, error) {
    84  		p := b.byImageName[artifact.ImageName]
    85  		pl, err := filterBuildEnvSupportedPlatforms(p.SupportedPlatforms(), platforms)
    86  		if err != nil {
    87  			return "", err
    88  		}
    89  		platforms = pl
    90  
    91  		artifactBuilder := p.Build(ctx, out, artifact)
    92  		hooksOpts, err := hooks.NewBuildEnvOpts(artifact, tag, p.PushImages())
    93  		if err != nil {
    94  			return "", err
    95  		}
    96  		r := hooks.BuildRunner(artifact.LifecycleHooks, hooksOpts)
    97  		var built string
    98  		if err = r.RunPreHooks(ctx, out); err != nil {
    99  			return "", err
   100  		}
   101  		if built, err = artifactBuilder(ctx, out, artifact, tag, platforms); err != nil {
   102  			return "", err
   103  		}
   104  		if err = r.RunPostHooks(ctx, out); err != nil {
   105  			return "", err
   106  		}
   107  		return built, nil
   108  	}
   109  	ar, err := InOrder(ctx, out, tags, resolver, artifacts, builderF, b.concurrency, b.store)
   110  	if err != nil {
   111  		return nil, err
   112  	}
   113  
   114  	for builder := range m {
   115  		if errB := builder.PostBuild(ctx, out); errB != nil {
   116  			return nil, errB
   117  		}
   118  	}
   119  
   120  	return ar, nil
   121  }
   122  
   123  // Prune removes built images.
   124  func (b *BuilderMux) Prune(ctx context.Context, writer io.Writer) error {
   125  	for _, builder := range b.builders {
   126  		if err := builder.Prune(ctx, writer); err != nil {
   127  			return err
   128  		}
   129  	}
   130  	return nil
   131  }
   132  
   133  // filterBuildEnvSupportedPlatforms filters the target platforms to those supported by the selected build environment (local/googleCloudBuild/cluster).
   134  func filterBuildEnvSupportedPlatforms(supported platform.Matcher, target platform.Matcher) (platform.Matcher, error) {
   135  	if target.IsEmpty() {
   136  		return target, nil
   137  	}
   138  	pl := target.Intersect(supported)
   139  	if pl.IsEmpty() {
   140  		return platform.Matcher{}, fmt.Errorf("target build platforms %q not supported by current build environment. Supported platforms: %q", target, supported)
   141  	}
   142  	return pl, nil
   143  }
   144  
   145  func getConcurrency(pbs []PipelineBuilder, cliConcurrency int) int {
   146  	ctx := context.TODO()
   147  	if cliConcurrency >= 0 {
   148  		log.Entry(ctx).Infof("build concurrency set to cli concurrency %d", cliConcurrency)
   149  		return cliConcurrency
   150  	}
   151  	minConcurrency := -1
   152  	for i, b := range pbs {
   153  		concurrency := 1
   154  		if b.Concurrency() != nil {
   155  			concurrency = *b.Concurrency()
   156  		}
   157  		// set mux concurrency to be the minimum of all builders' concurrency. (concurrency = 0 means unlimited)
   158  		switch {
   159  		case minConcurrency < 0:
   160  			minConcurrency = concurrency
   161  			log.Entry(ctx).Infof("build concurrency first set to %d parsed from %s[%d]", minConcurrency, reflect.TypeOf(b).String(), i)
   162  		case concurrency > 0 && (minConcurrency == 0 || concurrency < minConcurrency):
   163  			minConcurrency = concurrency
   164  			log.Entry(ctx).Infof("build concurrency updated to %d parsed from %s[%d]", minConcurrency, reflect.TypeOf(b).String(), i)
   165  		default:
   166  			log.Entry(ctx).Infof("build concurrency value %d parsed from %s[%d] is ignored since it's not less than previously set value %d", concurrency, reflect.TypeOf(b).String(), i, minConcurrency)
   167  		}
   168  	}
   169  	if minConcurrency < 0 {
   170  		log.Entry(ctx).Infof("build concurrency set to default value of %d", minConcurrency)
   171  		// set default concurrency to 1 for local builder. For GCB and Cluster build the default value is 0
   172  		return constants.DefaultLocalConcurrency
   173  	}
   174  	log.Entry(ctx).Infof("final build concurrency value is %d", minConcurrency)
   175  	return minConcurrency
   176  }