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 }