1
0
mirror of https://github.com/ko-build/ko.git synced 2025-02-07 19:30:23 +02:00
ko-build/pkg/resolve/resolve.go
2019-08-15 16:55:26 -04:00

168 lines
4.8 KiB
Go

// Copyright 2018 Google LLC 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.
package resolve
import (
"bytes"
"fmt"
"io"
"strings"
"sync"
"github.com/google/ko/pkg/build"
"github.com/google/ko/pkg/publish"
"golang.org/x/sync/errgroup"
yaml "gopkg.in/yaml.v2"
)
// ImageReferences resolves supported references to images within the input yaml
// to published image digests.
func ImageReferences(input []byte, strict bool, builder build.Interface, publisher publish.Interface) ([]byte, error) {
// First, walk the input objects and collect a list of supported references
refs := make(map[string]struct{})
// 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://github.com/go-yaml/yaml/blob/v2.2.1/yaml.go#L124
decoder := yaml.NewDecoder(bytes.NewBuffer(input))
for {
var obj interface{}
if err := decoder.Decode(&obj); err != nil {
if err == io.EOF {
break
}
return nil, err
}
// This simply returns the replaced object, which we discard during the gathering phase.
if _, err := replaceRecursive(obj, func(ref string) (string, error) {
strictRef := strings.HasPrefix(ref, "ko://")
if strict && !strictRef {
return ref, nil
}
tref := strings.TrimPrefix(ref, "ko://")
if builder.IsSupportedReference(tref) {
refs[tref] = struct{}{}
} else if strict && strictRef {
return "", fmt.Errorf("Found strict reference %q but %s is not a valid import path", ref, tref)
}
return ref, nil
}); err != nil {
return nil, err
}
}
// Next, perform parallel builds for each of the supported references.
var sm sync.Map
var errg errgroup.Group
for ref := range refs {
ref := ref
errg.Go(func() error {
img, err := builder.Build(ref)
if err != nil {
return err
}
digest, err := publisher.Publish(img, ref)
if err != nil {
return err
}
sm.Store(ref, digest.String())
return nil
})
}
if err := errg.Wait(); err != nil {
return nil, err
}
// Last, walk the inputs again and replace the supported references with their published images.
decoder = yaml.NewDecoder(bytes.NewBuffer(input))
buf := bytes.NewBuffer(nil)
encoder := yaml.NewEncoder(buf)
for {
var obj interface{}
if err := decoder.Decode(&obj); err != nil {
if err == io.EOF {
return buf.Bytes(), nil
}
return nil, err
}
// Recursively walk input, replacing supported reference with our computed digests.
obj2, err := replaceRecursive(obj, func(ref string) (string, error) {
if !builder.IsSupportedReference(ref) {
return ref, nil
}
ref = strings.TrimPrefix(ref, "ko://")
if val, ok := sm.Load(ref); ok {
return val.(string), nil
}
return "", fmt.Errorf("resolved reference to %q not found", ref)
})
if err != nil {
return nil, err
}
if err := encoder.Encode(obj2); err != nil {
return nil, err
}
}
}
type replaceString func(string) (string, error)
// replaceRecursive walks the provided untyped object recursively by switching
// on the type of the object at each level. It supports walking through the
// keys and values of maps, and the elements of an array. When a leaf of type
// string is encountered, this will call the provided replaceString function on
// it. This function does not support walking through struct types, but also
// should not need to as the input is expected to be the result of parsing yaml
// or json into an interface{}, which should only produce primitives, maps and
// arrays. This function will return a copy of the object rebuilt by the walk
// with the replacements made.
func replaceRecursive(obj interface{}, rs replaceString) (interface{}, error) {
switch typed := obj.(type) {
case map[interface{}]interface{}:
m2 := make(map[interface{}]interface{}, len(typed))
for k, v := range typed {
k2, err := replaceRecursive(k, rs)
if err != nil {
return nil, err
}
v2, err := replaceRecursive(v, rs)
if err != nil {
return nil, err
}
m2[k2] = v2
}
return m2, nil
case []interface{}:
a2 := make([]interface{}, len(typed))
for idx, v := range typed {
v2, err := replaceRecursive(v, rs)
if err != nil {
return nil, err
}
a2[idx] = v2
}
return a2, nil
case string:
// call our replaceString on this string leaf.
return rs(typed)
default:
// leave other leaves alone.
return typed, nil
}
}