github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/initializer/build/resolve.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  	"fmt"
    21  	"path/filepath"
    22  	"regexp"
    23  	"sort"
    24  	"strings"
    25  
    26  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/initializer/errors"
    27  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/initializer/prompt"
    28  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util/stringslice"
    29  )
    30  
    31  // For each image parsed from all k8s manifests, prompt the user for the builder that builds the referenced image
    32  func (d *defaultBuildInitializer) resolveBuilderImages() error {
    33  	// If nothing to choose, don't bother prompting
    34  	if len(d.builders) == 0 {
    35  		return nil
    36  	}
    37  
    38  	// if there's only one builder config, no need to prompt
    39  	if len(d.builders) == 1 {
    40  		if len(d.unresolvedImages) == 0 {
    41  			// no image was parsed from k8s manifests, so we create an image name
    42  			d.generatedArtifactInfos = append(d.generatedArtifactInfos, getGeneratedArtifactInfo(d.builders[0]))
    43  			return nil
    44  		}
    45  		// we already have the image, just use it and return
    46  		if len(d.unresolvedImages) == 1 {
    47  			d.artifactInfos = append(d.artifactInfos, ArtifactInfo{
    48  				Builder:   d.builders[0],
    49  				ImageName: d.unresolvedImages[0],
    50  			})
    51  			return nil
    52  		}
    53  	}
    54  
    55  	if d.force {
    56  		return d.resolveBuilderImagesForcefully()
    57  	}
    58  
    59  	return d.resolveBuilderImagesInteractively()
    60  }
    61  
    62  func (d *defaultBuildInitializer) resolveBuilderImagesForcefully() error {
    63  	// In the case of 1 image and multiple builders, respects the ordering Docker > Jib > Bazel > Buildpacks
    64  	if len(d.unresolvedImages) == 1 {
    65  		image := d.unresolvedImages[0]
    66  		choice := d.builders[0]
    67  		for _, builder := range d.builders {
    68  			if builderRank(builder) < builderRank(choice) {
    69  				choice = builder
    70  			}
    71  		}
    72  
    73  		d.artifactInfos = append(d.artifactInfos, ArtifactInfo{Builder: choice, ImageName: image})
    74  		d.unresolvedImages = []string{}
    75  		return nil
    76  	}
    77  
    78  	return errors.BuilderImageAmbiguitiesErr{}
    79  }
    80  
    81  func builderRank(builder InitBuilder) int {
    82  	a := builder.ArtifactType("")
    83  	switch {
    84  	case a.DockerArtifact != nil:
    85  		return 1
    86  	case a.JibArtifact != nil:
    87  		return 2
    88  	case a.BazelArtifact != nil:
    89  		return 3
    90  	case a.BuildpackArtifact != nil:
    91  		return 4
    92  	}
    93  
    94  	return 5
    95  }
    96  
    97  func (d *defaultBuildInitializer) resolveBuilderImagesInteractively() error {
    98  	// Build map from choice string to builder config struct
    99  	choices := make([]string, len(d.builders))
   100  	choiceMap := make(map[string]InitBuilder, len(d.builders))
   101  	for i, buildConfig := range d.builders {
   102  		choice := buildConfig.Describe()
   103  		choices[i] = choice
   104  		choiceMap[choice] = buildConfig
   105  	}
   106  	sort.Strings(choices)
   107  
   108  	// For each choice, use prompt string to pair builder config with k8s image
   109  	for {
   110  		if len(d.unresolvedImages) == 0 {
   111  			break
   112  		}
   113  
   114  		image := d.unresolvedImages[0]
   115  		choice, err := prompt.BuildConfigFunc(image, append(choices, NoBuilder))
   116  		if err != nil {
   117  			return err
   118  		}
   119  
   120  		if choice != NoBuilder {
   121  			d.artifactInfos = append(d.artifactInfos, ArtifactInfo{Builder: choiceMap[choice], ImageName: image})
   122  			choices = stringslice.Remove(choices, choice)
   123  		}
   124  		d.unresolvedImages = stringslice.Remove(d.unresolvedImages, image)
   125  	}
   126  	if len(choices) > 0 {
   127  		chosen, err := prompt.ChooseBuildersFunc(choices)
   128  		if err != nil {
   129  			return err
   130  		}
   131  
   132  		for _, choice := range chosen {
   133  			d.generatedArtifactInfos = append(d.generatedArtifactInfos, getGeneratedArtifactInfo(choiceMap[choice]))
   134  		}
   135  	}
   136  	return nil
   137  }
   138  
   139  func getGeneratedArtifactInfo(b InitBuilder) GeneratedArtifactInfo {
   140  	path := b.Path()
   141  	var imageName string
   142  	// if the builder is in a nested directory, use that as the image name AND the path to write the manifest
   143  	// otherwise, use the builder as the image name itself, and the current directory to write the manifest
   144  	if filepath.Dir(path) != "." {
   145  		imageName = strings.ToLower(filepath.Dir(path))
   146  		path = imageName
   147  	} else {
   148  		imageName = fmt.Sprintf("%s-image", strings.ToLower(path))
   149  		path = "."
   150  	}
   151  	return GeneratedArtifactInfo{
   152  		ArtifactInfo: ArtifactInfo{
   153  			Builder:   b,
   154  			ImageName: sanitizeImageName(imageName),
   155  		},
   156  		ManifestPath: filepath.Join(path, "deployment.yaml"),
   157  	}
   158  }
   159  
   160  func sanitizeImageName(imageName string) string {
   161  	// Replace unsupported characters with `_`
   162  	sanitized := regexp.MustCompile(`[^a-zA-Z0-9-_]`).ReplaceAllString(imageName, `-`)
   163  
   164  	// Truncate to 128 characters
   165  	if len(sanitized) > 128 {
   166  		return sanitized[0:128]
   167  	}
   168  
   169  	return sanitized
   170  }