github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/build/ko/build.go (about) 1 /* 2 Copyright 2021 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 ko 18 19 import ( 20 "context" 21 "fmt" 22 "io" 23 "path/filepath" 24 "strings" 25 26 "github.com/google/go-containerregistry/pkg/name" 27 "github.com/google/ko/pkg/build" 28 "github.com/google/ko/pkg/publish" 29 "golang.org/x/tools/go/packages" 30 31 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/platform" 32 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" 33 ) 34 35 // Build an artifact using ko, and either push it to an image registry, or 36 // sideload it to the local docker daemon. 37 // Build prints the image name to the out io.Writer and returns the image 38 // identifier. The image identifier is the tag or digest for pushed images, or 39 // the docker image ID for sideloaded images. 40 func (b *Builder) Build(ctx context.Context, out io.Writer, a *latest.Artifact, ref string, platforms platform.Matcher) (string, error) { 41 if b.pushImages && strings.HasPrefix(ref, build.StrictScheme) { 42 return "", fmt.Errorf("default repo must be set when using the 'ko://' prefix and pushing to a registry: %s, see https://skaffold.dev/docs/environment/image-registries/", a.ImageName) 43 } 44 koBuilder, err := b.newKoBuilder(ctx, a, platforms) 45 if err != nil { 46 return "", fmt.Errorf("error creating ko builder: %w", err) 47 } 48 49 koPublisher, err := b.newKoPublisher(ref) 50 if err != nil { 51 return "", fmt.Errorf("error creating ko publisher: %w", err) 52 } 53 defer koPublisher.Close() 54 55 imageRef, err := b.buildAndPublish(ctx, a, koBuilder, koPublisher) 56 if err != nil { 57 return "", fmt.Errorf("could not build and publish ko image %q: %w", a.ImageName, err) 58 } 59 60 return b.getImageIdentifier(ctx, imageRef, ref) 61 } 62 63 func (b *Builder) SupportedPlatforms() platform.Matcher { return platform.All } 64 65 // buildAndPublish the image using the ko builder and publisher. 66 func (b *Builder) buildAndPublish(ctx context.Context, a *latest.Artifact, koBuilder build.Interface, koPublisher publish.Interface) (name.Reference, error) { 67 importpath, err := getImportPath(a) 68 if err != nil { 69 return nil, fmt.Errorf("could not determine Go import path for ko image %q: %w", a.ImageName, err) 70 } 71 imageMap, err := b.publishImages(ctx, []string{importpath}, koPublisher, koBuilder) 72 if err != nil { 73 return nil, fmt.Errorf("failed to publish ko image: %w", err) 74 } 75 imageRef, exists := imageMap[importpath] 76 if !exists { 77 return nil, fmt.Errorf("no built image found for Go import path %q build images: %+v", importpath, imageMap) 78 } 79 return imageRef, nil 80 } 81 82 // getImportPath determines the Go import path that ko should build. 83 // The import path is returned with the `ko://` scheme prefix. 84 // 85 // If the image name from the Skaffold config has the prefix `ko://`, then 86 // treat the remainder of the string as the Go import path to build. This 87 // matches current ko behavior for working with Kubernetes resource files, and 88 // it will allow ko users to easily migrate to Skaffold without changing their 89 // Kubernetes YAML files. See https://github.com/google/ko#yaml-changes. 90 // 91 // If the image name does _not_ start with `ko://`, determine the Go import 92 // path of the image workspace directory. 93 func getImportPath(a *latest.Artifact) (string, error) { 94 if strings.HasPrefix(a.ImageName, build.StrictScheme) { 95 return a.ImageName, nil 96 } 97 baseDir := filepath.Join(a.Workspace, a.KoArtifact.Dir) 98 target := a.KoArtifact.Main 99 pkgConfig := &packages.Config{ 100 Mode: packages.NeedName, 101 Dir: baseDir, 102 } 103 pkgs, err := packages.Load(pkgConfig, target) 104 if err != nil { 105 return "", fmt.Errorf("could not determine import path from directory %q and target %q: %v", baseDir, target, err) 106 } 107 if len(pkgs) != 1 { 108 return "", fmt.Errorf("expected exactly one main package for directory %q and target %q, got %d: %v", baseDir, target, len(pkgs), err) 109 } 110 return build.StrictScheme + pkgs[0].PkgPath, nil 111 } 112 113 // getImageIdentifier returns the image tag or digest for published images (`pushImages=true`), 114 // or the image ID from the local Docker daemon for sideloaded images (`pushImages=false`). 115 func (b *Builder) getImageIdentifier(ctx context.Context, imageRef name.Reference, ref string) (string, error) { 116 if b.pushImages { 117 return imageRef.Identifier(), nil 118 } 119 imageIdentifier, err := b.localDocker.ImageID(ctx, ref) 120 if err != nil { 121 return "", fmt.Errorf("could not get imageID from local Docker Daemon for image %s: %+v", ref, err) 122 } 123 return imageIdentifier, nil 124 }