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  }