1
0
mirror of https://github.com/ko-build/ko.git synced 2025-04-20 11:29:06 +02:00
ko-build/pkg/commands/resolver.go

496 lines
14 KiB
Go
Raw Normal View History

// Copyright 2018 ko Build Authors All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
2019-03-14 14:23:47 -04:00
package commands
2019-03-14 14:23:47 -04:00
import (
"bytes"
2019-12-13 15:08:52 -08:00
"context"
"errors"
2019-03-14 14:23:47 -04:00
"fmt"
"io"
"os"
"path"
"strings"
2019-03-14 14:23:47 -04:00
"sync"
"github.com/google/go-containerregistry/pkg/name"
"golang.org/x/sync/errgroup"
"gopkg.in/yaml.v3"
"k8s.io/apimachinery/pkg/labels"
"github.com/google/ko/pkg/build"
"github.com/google/ko/pkg/commands/options"
"github.com/google/ko/pkg/publish"
"github.com/google/ko/pkg/resolve"
2019-03-14 14:23:47 -04:00
)
// ua returns the ko user agent.
2019-12-13 15:08:52 -08:00
func ua() string {
if v := version(); v != "" {
return "ko/" + v
}
return "ko"
}
func gobuildOptions(bo *options.BuildOptions) ([]build.Option, error) {
2019-03-14 14:23:47 -04:00
creationTime, err := getCreationTime()
if err != nil {
return nil, err
}
kodataCreationTime, err := getKoDataCreationTime()
if err != nil {
return nil, err
}
if len(bo.Platforms) == 0 && len(bo.DefaultPlatforms) > 0 {
bo.Platforms = bo.DefaultPlatforms
}
if len(bo.Platforms) == 0 {
envPlatform := "linux/amd64"
goos, goarch, goarm := os.Getenv("GOOS"), os.Getenv("GOARCH"), os.Getenv("GOARM")
// Default to linux/amd64 unless GOOS and GOARCH are set.
if goos != "" && goarch != "" {
envPlatform = path.Join(goos, goarch)
}
// Use GOARM for variant if it's set and GOARCH is arm.
if strings.Contains(goarch, "arm") && goarm != "" {
envPlatform = path.Join(envPlatform, "v"+goarm)
}
bo.Platforms = []string{envPlatform}
} else {
// Make sure these are all unset
for _, env := range []string{"GOOS", "GOARCH", "GOARM"} {
if s, ok := os.LookupEnv(env); ok {
return nil, fmt.Errorf("cannot use --platform or defaultPlatforms in .ko.yaml or env KO_DEFAULTPLATFORMS combined with %s=%q", env, s)
}
}
}
2019-03-14 14:23:47 -04:00
opts := []build.Option{
build.WithBaseImages(getBaseImage(bo)),
build.WithEnv(bo.Env),
build.WithFlags(bo.Flags),
build.WithLdflags(bo.Ldflags),
build.WithPlatforms(bo.Platforms...),
build.WithJobs(bo.ConcurrentBuilds),
2019-03-14 14:23:47 -04:00
}
if creationTime != nil {
opts = append(opts, build.WithCreationTime(*creationTime))
}
if kodataCreationTime != nil {
opts = append(opts, build.WithKoDataCreationTime(*kodataCreationTime))
}
if bo.DisableOptimizations {
opts = append(opts, build.WithDisabledOptimizations())
}
Add support for writing SBOMs when the `build.Result` is `oci.Signed*`. (#506) This adds functionality that enables the default publisher to publish SBOMs (and later signatures and attestations) when the `build.Result` is an `oci.SignedEntity`. This also changes the `gobuild` logic to start producing `oci.Signed*` as its `build.Result`s, so when executed we get an SBOM for each architecture image. For example, see the "Published SBOM" lines below: ```shell 2021/11/19 19:24:50 Using base gcr.io/distroless/static:nonroot for github.com/google/ko 2021/11/19 19:24:51 Building github.com/google/ko for linux/amd64 2021/11/19 19:24:52 Building github.com/google/ko for linux/arm64 2021/11/19 19:24:57 Publishing ghcr.io/mattmoor/ko:latest 2021/11/19 19:24:58 existing blob: sha256:c78c74e7bb4a511f7d31061fbf140d55d5549a62d33cdbdf0c57ffe43603bbeb 2021/11/19 19:24:58 existing blob: sha256:4aa59d0bf53d4190174fbbfa3e9b15fdab72e5a95077025abfa8435ccafa2920 2021/11/19 19:24:58 ghcr.io/mattmoor/ko:sha256-d2bc030f5ed083d5e6a30a7969c9a8e599511b8d7a6e20695bf5ea029b6e2c3f.sbom: digest: sha256:c67ec671aaa82902e619883a7ac7486e6f9af36653449e2eb030ba273fe5a022 size: 348 2021/11/19 19:24:58 Published SBOM ghcr.io/mattmoor/ko:sha256-d2bc030f5ed083d5e6a30a7969c9a8e599511b8d7a6e20695bf5ea029b6e2c3f.sbom 2021/11/19 19:24:58 existing blob: sha256:c78c74e7bb4a511f7d31061fbf140d55d5549a62d33cdbdf0c57ffe43603bbeb 2021/11/19 19:24:58 existing blob: sha256:4aa59d0bf53d4190174fbbfa3e9b15fdab72e5a95077025abfa8435ccafa2920 2021/11/19 19:24:59 ghcr.io/mattmoor/ko:sha256-b74c230f20efd94981e5fd823bacc23cbd71055a1b3b6a0893152b398c67743b.sbom: digest: sha256:c67ec671aaa82902e619883a7ac7486e6f9af36653449e2eb030ba273fe5a022 size: 348 2021/11/19 19:24:59 Published SBOM ghcr.io/mattmoor/ko:sha256-b74c230f20efd94981e5fd823bacc23cbd71055a1b3b6a0893152b398c67743b.sbom 2021/11/19 19:24:59 existing blob: sha256:3f7e3c6765a6abc682cd40ea256fbea5c1d4debbc07659efbc0dedc13eee0da6 2021/11/19 19:24:59 existing blob: sha256:250c06f7c38e52dc77e5c7586c3e40280dc7ff9bb9007c396e06d96736cf8542 2021/11/19 19:24:59 existing blob: sha256:e8614d09b7bebabd9d8a450f44e88a8807c98a438a2ddd63146865286b132d1b 2021/11/19 19:24:59 existing blob: sha256:7067b1bc6f9ce59f3a4ed2216946ebbb27a4f7a102f55d96c6af1dc90e77b510 2021/11/19 19:25:00 ghcr.io/mattmoor/ko@sha256:d2bc030f5ed083d5e6a30a7969c9a8e599511b8d7a6e20695bf5ea029b6e2c3f: digest: sha256:d2bc030f5ed083d5e6a30a7969c9a8e599511b8d7a6e20695bf5ea029b6e2c3f size: 751 2021/11/19 19:25:01 existing blob: sha256:250c06f7c38e52dc77e5c7586c3e40280dc7ff9bb9007c396e06d96736cf8542 2021/11/19 19:25:02 pushed blob: sha256:121c637d5c84562b51404a6f71c1f995ad059740293a3911a0dc33eb223e41a4 2021/11/19 19:25:02 pushed blob: sha256:859e03b7461b2a512159493ef1504d2859ed37c05ed1ef781ff98394ea4799b5 2021/11/19 19:25:02 pushed blob: sha256:d1b55c3db0f16b5056776c6d2c279efd16d28dbf1aae3eef1f3f9b7551d1f490 2021/11/19 19:25:03 ghcr.io/mattmoor/ko@sha256:b74c230f20efd94981e5fd823bacc23cbd71055a1b3b6a0893152b398c67743b: digest: sha256:b74c230f20efd94981e5fd823bacc23cbd71055a1b3b6a0893152b398c67743b size: 751 2021/11/19 19:25:03 ghcr.io/mattmoor/ko:latest: digest: sha256:e4466a7dd9be66c7c1b43a8ecc19247041ece232407a14e3d6ea3c51d2561a71 size: 529 2021/11/19 19:25:03 Published ghcr.io/mattmoor/ko@sha256:e4466a7dd9be66c7c1b43a8ecc19247041ece232407a14e3d6ea3c51d2561a71 ghcr.io/mattmoor/ko@sha256:e4466a7dd9be66c7c1b43a8ecc19247041ece232407a14e3d6ea3c51d2561a71 ``` The "SBOM" being attached in this change is the raw output of `go version -m`, which we will convert to one of the standard formats in a subsequent change.
2021-11-22 10:57:13 -08:00
switch bo.SBOM {
case "none":
opts = append(opts, build.WithDisabledSBOM())
case "go.version-m":
opts = append(opts, build.WithGoVersionSBOM())
case "cyclonedx":
opts = append(opts, build.WithCycloneDX())
default: // "spdx"
opts = append(opts, build.WithSPDX(version()))
Add support for writing SBOMs when the `build.Result` is `oci.Signed*`. (#506) This adds functionality that enables the default publisher to publish SBOMs (and later signatures and attestations) when the `build.Result` is an `oci.SignedEntity`. This also changes the `gobuild` logic to start producing `oci.Signed*` as its `build.Result`s, so when executed we get an SBOM for each architecture image. For example, see the "Published SBOM" lines below: ```shell 2021/11/19 19:24:50 Using base gcr.io/distroless/static:nonroot for github.com/google/ko 2021/11/19 19:24:51 Building github.com/google/ko for linux/amd64 2021/11/19 19:24:52 Building github.com/google/ko for linux/arm64 2021/11/19 19:24:57 Publishing ghcr.io/mattmoor/ko:latest 2021/11/19 19:24:58 existing blob: sha256:c78c74e7bb4a511f7d31061fbf140d55d5549a62d33cdbdf0c57ffe43603bbeb 2021/11/19 19:24:58 existing blob: sha256:4aa59d0bf53d4190174fbbfa3e9b15fdab72e5a95077025abfa8435ccafa2920 2021/11/19 19:24:58 ghcr.io/mattmoor/ko:sha256-d2bc030f5ed083d5e6a30a7969c9a8e599511b8d7a6e20695bf5ea029b6e2c3f.sbom: digest: sha256:c67ec671aaa82902e619883a7ac7486e6f9af36653449e2eb030ba273fe5a022 size: 348 2021/11/19 19:24:58 Published SBOM ghcr.io/mattmoor/ko:sha256-d2bc030f5ed083d5e6a30a7969c9a8e599511b8d7a6e20695bf5ea029b6e2c3f.sbom 2021/11/19 19:24:58 existing blob: sha256:c78c74e7bb4a511f7d31061fbf140d55d5549a62d33cdbdf0c57ffe43603bbeb 2021/11/19 19:24:58 existing blob: sha256:4aa59d0bf53d4190174fbbfa3e9b15fdab72e5a95077025abfa8435ccafa2920 2021/11/19 19:24:59 ghcr.io/mattmoor/ko:sha256-b74c230f20efd94981e5fd823bacc23cbd71055a1b3b6a0893152b398c67743b.sbom: digest: sha256:c67ec671aaa82902e619883a7ac7486e6f9af36653449e2eb030ba273fe5a022 size: 348 2021/11/19 19:24:59 Published SBOM ghcr.io/mattmoor/ko:sha256-b74c230f20efd94981e5fd823bacc23cbd71055a1b3b6a0893152b398c67743b.sbom 2021/11/19 19:24:59 existing blob: sha256:3f7e3c6765a6abc682cd40ea256fbea5c1d4debbc07659efbc0dedc13eee0da6 2021/11/19 19:24:59 existing blob: sha256:250c06f7c38e52dc77e5c7586c3e40280dc7ff9bb9007c396e06d96736cf8542 2021/11/19 19:24:59 existing blob: sha256:e8614d09b7bebabd9d8a450f44e88a8807c98a438a2ddd63146865286b132d1b 2021/11/19 19:24:59 existing blob: sha256:7067b1bc6f9ce59f3a4ed2216946ebbb27a4f7a102f55d96c6af1dc90e77b510 2021/11/19 19:25:00 ghcr.io/mattmoor/ko@sha256:d2bc030f5ed083d5e6a30a7969c9a8e599511b8d7a6e20695bf5ea029b6e2c3f: digest: sha256:d2bc030f5ed083d5e6a30a7969c9a8e599511b8d7a6e20695bf5ea029b6e2c3f size: 751 2021/11/19 19:25:01 existing blob: sha256:250c06f7c38e52dc77e5c7586c3e40280dc7ff9bb9007c396e06d96736cf8542 2021/11/19 19:25:02 pushed blob: sha256:121c637d5c84562b51404a6f71c1f995ad059740293a3911a0dc33eb223e41a4 2021/11/19 19:25:02 pushed blob: sha256:859e03b7461b2a512159493ef1504d2859ed37c05ed1ef781ff98394ea4799b5 2021/11/19 19:25:02 pushed blob: sha256:d1b55c3db0f16b5056776c6d2c279efd16d28dbf1aae3eef1f3f9b7551d1f490 2021/11/19 19:25:03 ghcr.io/mattmoor/ko@sha256:b74c230f20efd94981e5fd823bacc23cbd71055a1b3b6a0893152b398c67743b: digest: sha256:b74c230f20efd94981e5fd823bacc23cbd71055a1b3b6a0893152b398c67743b size: 751 2021/11/19 19:25:03 ghcr.io/mattmoor/ko:latest: digest: sha256:e4466a7dd9be66c7c1b43a8ecc19247041ece232407a14e3d6ea3c51d2561a71 size: 529 2021/11/19 19:25:03 Published ghcr.io/mattmoor/ko@sha256:e4466a7dd9be66c7c1b43a8ecc19247041ece232407a14e3d6ea3c51d2561a71 ghcr.io/mattmoor/ko@sha256:e4466a7dd9be66c7c1b43a8ecc19247041ece232407a14e3d6ea3c51d2561a71 ``` The "SBOM" being attached in this change is the raw output of `go version -m`, which we will convert to one of the standard formats in a subsequent change.
2021-11-22 10:57:13 -08:00
}
opts = append(opts, build.WithTrimpath(bo.Trimpath))
for _, lf := range bo.Labels {
parts := strings.SplitN(lf, "=", 2)
if len(parts) != 2 {
return nil, fmt.Errorf("invalid label flag: %s", lf)
}
opts = append(opts, build.WithLabel(parts[0], parts[1]))
}
if bo.BuildConfigs != nil {
opts = append(opts, build.WithConfig(bo.BuildConfigs))
}
if bo.SBOMDir != "" {
opts = append(opts, build.WithSBOMDir(bo.SBOMDir))
}
2019-03-14 14:23:47 -04:00
return opts, nil
}
// NewBuilder creates a ko builder
func NewBuilder(ctx context.Context, bo *options.BuildOptions) (build.Interface, error) {
return makeBuilder(ctx, bo)
}
func makeBuilder(ctx context.Context, bo *options.BuildOptions) (*build.Caching, error) {
if err := bo.LoadConfig(); err != nil {
return nil, err
}
opt, err := gobuildOptions(bo)
2019-03-14 14:23:47 -04:00
if err != nil {
return nil, fmt.Errorf("error setting up builder options: %w", err)
2019-03-14 14:23:47 -04:00
}
innerBuilder, err := build.NewGobuilds(ctx, bo.WorkingDirectory, bo.BuildConfigs, opt...)
2019-03-14 14:23:47 -04:00
if err != nil {
return nil, err
}
// tl;dr Wrap builder in a caching builder.
//
// The caching builder should on Build calls:
// - Check for a valid Build future
// - if a valid Build future exists at the time of the request,
// then block on it.
// - if it does not, then initiate and record a Build future.
//
// This will benefit the following key cases:
// 1. When the same import path is referenced across multiple yaml files
// we can elide subsequent builds by blocking on the same image future.
// 2. When an affected yaml file has multiple import paths (mostly unaffected)
// we can elide the builds of unchanged import paths.
return build.NewCaching(innerBuilder)
}
// NewPublisher creates a ko publisher
func NewPublisher(po *options.PublishOptions) (publish.Interface, error) {
return makePublisher(po)
}
func makePublisher(po *options.PublishOptions) (publish.Interface, error) {
// use each tag only once
po.Tags = unique(po.Tags)
2019-03-14 14:23:47 -04:00
// Create the publish.Interface that we will use to publish image references
// to either a docker daemon or a container image registry.
innerPublisher, err := func() (publish.Interface, error) {
repoName := po.DockerRepo
namer := options.MakeNamer(po)
// Default LocalDomain if unset.
if po.LocalDomain == "" {
po.LocalDomain = publish.LocalDomain
}
// If repoName is unset with --local, default it to the local domain.
if po.Local && repoName == "" {
repoName = po.LocalDomain
}
// When in doubt, if repoName is under the local domain, default to --local.
po.Local = po.Local || strings.HasPrefix(repoName, po.LocalDomain)
if po.Local {
// TODO(jonjohnsonjr): I'm assuming that nobody will
// use local with other publishers, but that might
// not be true.
po.LocalDomain = repoName
return publish.NewDaemon(namer, po.Tags,
publish.WithDockerClient(po.DockerClient),
publish.WithLocalDomain(po.LocalDomain),
)
2019-03-14 14:23:47 -04:00
}
if strings.HasPrefix(repoName, publish.KindDomain) {
return publish.NewKindPublisher(repoName, namer, po.Tags), nil
}
if repoName == "" && po.Push {
return nil, errors.New("KO_DOCKER_REPO environment variable is unset")
}
if _, err := name.NewRegistry(repoName); err != nil {
if _, err := name.NewRepository(repoName); err != nil {
return nil, fmt.Errorf("failed to parse %q as repository: %w", repoName, err)
}
2019-03-14 14:23:47 -04:00
}
publishers := []publish.Interface{}
if po.OCILayoutPath != "" {
lp, err := publish.NewLayout(po.OCILayoutPath)
if err != nil {
return nil, fmt.Errorf("failed to create LayoutPublisher for %q: %w", po.OCILayoutPath, err)
}
publishers = append(publishers, lp)
}
if po.TarballFile != "" {
tp := publish.NewTarball(po.TarballFile, repoName, namer, po.Tags)
publishers = append(publishers, tp)
}
userAgent := ua()
if po.UserAgent != "" {
userAgent = po.UserAgent
}
if po.Push {
dp, err := publish.NewDefault(repoName,
publish.WithUserAgent(userAgent),
publish.WithAuthFromKeychain(keychain),
publish.WithNamer(namer),
publish.WithTags(po.Tags),
publish.WithTagOnly(po.TagOnly),
publish.Insecure(po.InsecureRegistry),
2023-04-07 10:33:24 -07:00
publish.WithJobs(po.Jobs),
)
if err != nil {
return nil, err
}
publishers = append(publishers, dp)
}
// If not publishing, at least generate a digest to simulate
// publishing.
if len(publishers) == 0 {
// If one or more tags are specified, use the first tag in the list
var tag string
if len(po.Tags) >= 1 {
tag = po.Tags[0]
}
publishers = append(publishers, nopPublisher{
repoName: repoName,
namer: namer,
tag: tag,
tagOnly: po.TagOnly,
})
}
return publish.MultiPublisher(publishers...), nil
2019-03-14 14:23:47 -04:00
}()
if err != nil {
return nil, err
}
if po.ImageRefsFile != "" {
2022-10-18 13:47:23 -04:00
f, err := os.OpenFile(po.ImageRefsFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
return nil, err
}
innerPublisher, err = publish.NewRecorder(innerPublisher, f)
if err != nil {
return nil, err
}
}
2019-03-14 14:23:47 -04:00
// Wrap publisher in a memoizing publisher implementation.
return publish.NewCaching(innerPublisher)
}
// nopPublisher simulates publishing without actually publishing anything, to
// provide fallback behavior when the user configures no push destinations.
type nopPublisher struct {
repoName string
namer publish.Namer
tag string
tagOnly bool
}
func (n nopPublisher) Publish(_ context.Context, br build.Result, s string) (name.Reference, error) {
s = strings.TrimPrefix(s, build.StrictScheme)
nm := n.namer(n.repoName, s)
if n.tagOnly {
if n.tag == "" {
return nil, errors.New("must specify tag if requesting tag only")
}
return name.NewTag(fmt.Sprintf("%s:%s", nm, n.tag))
}
h, err := br.Digest()
if err != nil {
return nil, err
}
if n.tag == "" {
return name.NewDigest(fmt.Sprintf("%s@%s", nm, h))
}
return name.NewDigest(fmt.Sprintf("%s:%s@%s", nm, n.tag, h))
}
func (n nopPublisher) Close() error { return nil }
2019-03-14 14:23:47 -04:00
// resolvedFuture represents a "future" for the bytes of a resolved file.
type resolvedFuture chan []byte
func ResolveFilesToWriter(
2019-12-13 15:08:52 -08:00
ctx context.Context,
builder *build.Caching,
publisher publish.Interface,
fo *options.FilenameOptions,
so *options.SelectorOptions,
out io.WriteCloser) error {
2019-03-14 14:23:47 -04:00
defer out.Close()
// By having this as a channel, we can hook this up to a filesystem
// watcher and leave `fs` open to stream the names of yaml files
// affected by code changes (including the modification of existing or
// creation of new yaml files).
fs := options.EnumerateFiles(fo)
2019-03-14 14:23:47 -04:00
// This tracks filename -> []importpath
var sm sync.Map
// This tracks resolution errors and ensures we cancel other builds if an
// individual build fails.
errs, ctx := errgroup.WithContext(ctx)
2019-03-14 14:23:47 -04:00
var futures []resolvedFuture
for {
// Each iteration, if there is anything in the list of futures,
// listen to it in addition to the file enumerating channel.
// A nil channel is never available to receive on, so if nothing
// is available, this will result in us exclusively selecting
// on the file enumerating channel.
var bf resolvedFuture
if len(futures) > 0 {
bf = futures[0]
} else if fs == nil {
// There are no more files to enumerate and the futures
// have been drained, so quit.
break
}
select {
case file, ok := <-fs:
2019-03-14 14:23:47 -04:00
if !ok {
// a nil channel is never available to receive on.
// This allows us to drain the list of in-process
// futures without this case of the select winning
// each time.
fs = nil
break
}
// Make a new future to use to ship the bytes back and append
// it to the list of futures (see comment below about ordering).
ch := make(resolvedFuture)
futures = append(futures, ch)
// Kick off the resolution that will respond with its bytes on
// the future.
f := file // defensive copy
errs.Go(func() error {
2019-03-14 14:23:47 -04:00
defer close(ch)
// Record the builds we do via this builder.
recordingBuilder := &build.Recorder{
Builder: builder,
}
b, err := resolveFile(ctx, f, recordingBuilder, publisher, so)
2019-03-14 14:23:47 -04:00
if err != nil {
// This error is sometimes expected during watch mode, so this
// isn't fatal. Just print it and keep the watch open.
2022-03-03 14:58:34 -05:00
return fmt.Errorf("error processing import paths in %q: %w", f, err)
2019-03-14 14:23:47 -04:00
}
// Associate with this file the collection of binary import paths.
sm.Store(f, recordingBuilder.ImportPaths)
ch <- b
return nil
})
2019-03-14 14:23:47 -04:00
case b, ok := <-bf:
// Once the head channel returns something, dequeue it.
// We listen to the futures in order to be respectful of
// the kubectl apply ordering, which matters!
futures = futures[1:]
if ok {
// Write the next body and a trailing delimiter.
2023-03-18 20:37:44 +08:00
// We write the delimiter LAST so that when streamed to
2019-03-14 14:23:47 -04:00
// kubectl it knows that the resource is complete and may
// be applied.
out.Write(append(b, []byte("\n---\n")...))
}
}
}
// Make sure we exit with an error.
// See https://github.com/ko-build/ko/issues/84
return errs.Wait()
2019-03-14 14:23:47 -04:00
}
func resolveFile(
2019-11-10 01:23:09 +08:00
ctx context.Context,
f string,
builder build.Interface,
pub publish.Interface,
so *options.SelectorOptions) (b []byte, err error) {
var selector labels.Selector
if so.Selector != "" {
var err error
selector, err = labels.Parse(so.Selector)
if err != nil {
return nil, fmt.Errorf("unable to parse selector: %w", err)
}
}
2019-03-14 14:23:47 -04:00
if f == "-" {
b, err = io.ReadAll(os.Stdin)
2019-03-14 14:23:47 -04:00
} else {
b, err = os.ReadFile(f)
2019-03-14 14:23:47 -04:00
}
if err != nil {
return nil, err
}
var docNodes []*yaml.Node
// The loop is to support multi-document yaml files.
// This is handled by using a yaml.Decoder and reading objects until io.EOF, see:
// https://godoc.org/gopkg.in/yaml.v3#Decoder.Decode
decoder := yaml.NewDecoder(bytes.NewBuffer(b))
for {
var doc yaml.Node
if err := decoder.Decode(&doc); err != nil {
if errors.Is(err, io.EOF) {
break
}
return nil, err
}
if selector != nil {
if match, err := resolve.MatchesSelector(&doc, selector); err != nil {
return nil, fmt.Errorf("error evaluating selector: %w", err)
} else if !match {
continue
}
}
docNodes = append(docNodes, &doc)
}
if err := resolve.ImageReferences(ctx, docNodes, builder, pub); err != nil {
return nil, fmt.Errorf("error resolving image references: %w", err)
}
buf := &bytes.Buffer{}
e := yaml.NewEncoder(buf)
e.SetIndent(2)
for _, doc := range docNodes {
err := e.Encode(doc)
if err != nil {
return nil, fmt.Errorf("failed to encode output: %w", err)
}
}
e.Close()
return buf.Bytes(), nil
2019-03-14 14:23:47 -04:00
}
// create a set from the input slice
// preserving the order of unique elements
func unique(ss []string) []string {
var (
seen = make(map[string]struct{}, len(ss))
uniq = make([]string, 0, len(ss))
)
for _, s := range ss {
if _, ok := seen[s]; !ok {
seen[s] = struct{}{}
uniq = append(uniq, s)
}
}
return uniq
}