mirror of
https://github.com/ko-build/ko.git
synced 2024-12-12 08:54:09 +02:00
ff61ea330c
This change more or less completely changes how `ko://` is handled internally to `ko`, but the user-facing changes should only be net-positive. `ko://` was previously stripped at the highest level, and the build logic was unaware, which had some undesirable diagnostic/functional implications that are collectively addressed in this change. With this change, the `ko://` prefix is preserved and passed to the build logic, which internally parses a new `reference` type (this was useful to have Go's type checker find all of the places that needed fixing). The main functional differences are: 1. If a reference is prefixed with `ko://` we will now fail fast in `IsSupportedReference` regardless of whether `--strict` is passed. 2. If a reference is prefixed with `ko://` it will bypass the prefix check, which allows the use of `ko://github.com/another/repo` that references a vendored binary package. For `2.` the absence of the module prefix causes the filtering logic Jon introduced to avoid the reference. This was critical for efficiency when `ko://` isn't around because we feed every string in the yaml through it, but when the user has explicitly decorated things it's the perfect thing to be sensitive to. Fixes: https://github.com/google/ko/issues/146 Fixes: https://github.com/google/ko/issues/152
319 lines
9.2 KiB
Go
319 lines
9.2 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"
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/google/go-cmp/cmp/cmpopts"
|
|
"github.com/google/go-containerregistry/pkg/name"
|
|
v1 "github.com/google/go-containerregistry/pkg/v1"
|
|
"github.com/google/go-containerregistry/pkg/v1/random"
|
|
"github.com/google/ko/pkg/build"
|
|
kotesting "github.com/google/ko/pkg/internal/testing"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
var (
|
|
fooRef = "github.com/awesomesauce/foo"
|
|
foo = mustRandom()
|
|
fooHash = mustDigest(foo)
|
|
barRef = "github.com/awesomesauce/bar"
|
|
bar = mustRandom()
|
|
barHash = mustDigest(bar)
|
|
bazRef = "github.com/awesomesauce/baz"
|
|
baz = mustRandom()
|
|
bazHash = mustDigest(baz)
|
|
testBuilder = kotesting.NewFixedBuild(map[string]v1.Image{
|
|
fooRef: foo,
|
|
barRef: bar,
|
|
bazRef: baz,
|
|
})
|
|
testHashes = map[string]v1.Hash{
|
|
fooRef: fooHash,
|
|
barRef: barHash,
|
|
bazRef: bazHash,
|
|
}
|
|
)
|
|
|
|
func TestYAMLArrays(t *testing.T) {
|
|
tests := []struct {
|
|
desc string
|
|
refs []string
|
|
hashes []v1.Hash
|
|
base name.Repository
|
|
}{{
|
|
desc: "singleton array",
|
|
refs: []string{fooRef},
|
|
hashes: []v1.Hash{fooHash},
|
|
base: mustRepository("gcr.io/mattmoor"),
|
|
}, {
|
|
desc: "singleton array (different base)",
|
|
refs: []string{fooRef},
|
|
hashes: []v1.Hash{fooHash},
|
|
base: mustRepository("gcr.io/jasonhall"),
|
|
}, {
|
|
desc: "two element array",
|
|
refs: []string{fooRef, barRef},
|
|
hashes: []v1.Hash{fooHash, barHash},
|
|
base: mustRepository("gcr.io/jonjohnson"),
|
|
}, {
|
|
desc: "empty array",
|
|
refs: []string{},
|
|
hashes: []v1.Hash{},
|
|
base: mustRepository("gcr.io/blah"),
|
|
}}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.desc, func(t *testing.T) {
|
|
inputStructured := test.refs
|
|
inputYAML, err := yaml.Marshal(inputStructured)
|
|
if err != nil {
|
|
t.Fatalf("yaml.Marshal(%v) = %v", inputStructured, err)
|
|
}
|
|
|
|
doc := strToYAML(t, string(inputYAML))
|
|
err = ImageReferences(context.Background(), []*yaml.Node{doc}, false, testBuilder, kotesting.NewFixedPublish(test.base, testHashes))
|
|
if err != nil {
|
|
t.Fatalf("ImageReferences(%v) = %v", string(inputYAML), err)
|
|
}
|
|
var outStructured []string
|
|
if err := doc.Decode(&outStructured); err != nil {
|
|
t.Errorf("doc.Decode(%v) = %v", yamlToStr(t, doc), err)
|
|
}
|
|
|
|
if want, got := len(inputStructured), len(outStructured); want != got {
|
|
t.Errorf("ImageReferences(%v) = %v, want %v", string(inputYAML), got, want)
|
|
}
|
|
|
|
var expectedStructured []string
|
|
for i, ref := range test.refs {
|
|
hash := test.hashes[i]
|
|
expectedStructured = append(expectedStructured,
|
|
kotesting.ComputeDigest(test.base, ref, hash))
|
|
}
|
|
|
|
if diff := cmp.Diff(expectedStructured, outStructured, cmpopts.EquateEmpty()); diff != "" {
|
|
t.Errorf("ImageReferences(%v); (-want +got) = %v", string(inputYAML), diff)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestYAMLMaps(t *testing.T) {
|
|
base := mustRepository("gcr.io/mattmoor")
|
|
tests := []struct {
|
|
desc string
|
|
input map[string]string
|
|
expected map[string]string
|
|
}{{
|
|
desc: "simple value",
|
|
input: map[string]string{"image": fooRef},
|
|
expected: map[string]string{"image": kotesting.ComputeDigest(base, fooRef, fooHash)},
|
|
}, {
|
|
desc: "simple key",
|
|
input: map[string]string{bazRef: "blah"},
|
|
expected: map[string]string{
|
|
kotesting.ComputeDigest(base, bazRef, bazHash): "blah",
|
|
},
|
|
}, {
|
|
desc: "key and value",
|
|
input: map[string]string{fooRef: barRef},
|
|
expected: map[string]string{
|
|
kotesting.ComputeDigest(base, fooRef, fooHash): kotesting.ComputeDigest(base, barRef, barHash),
|
|
},
|
|
}, {
|
|
desc: "empty map",
|
|
input: map[string]string{},
|
|
expected: map[string]string{},
|
|
}, {
|
|
desc: "multiple values",
|
|
input: map[string]string{
|
|
"arg1": fooRef,
|
|
"arg2": barRef,
|
|
},
|
|
expected: map[string]string{
|
|
"arg1": kotesting.ComputeDigest(base, fooRef, fooHash),
|
|
"arg2": kotesting.ComputeDigest(base, barRef, barHash),
|
|
},
|
|
}}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.desc, func(t *testing.T) {
|
|
inputStructured := test.input
|
|
inputYAML, err := yaml.Marshal(inputStructured)
|
|
if err != nil {
|
|
t.Fatalf("yaml.Marshal(%v) = %v", inputStructured, err)
|
|
}
|
|
|
|
doc := strToYAML(t, string(inputYAML))
|
|
err = ImageReferences(context.Background(), []*yaml.Node{doc}, false, testBuilder, kotesting.NewFixedPublish(base, testHashes))
|
|
if err != nil {
|
|
t.Fatalf("ImageReferences(%v) = %v", string(inputYAML), err)
|
|
}
|
|
var outStructured map[string]string
|
|
if err := doc.Decode(&outStructured); err != nil {
|
|
t.Errorf("doc.Decode(%v) = %v", yamlToStr(t, doc), err)
|
|
}
|
|
|
|
if want, got := len(inputStructured), len(outStructured); want != got {
|
|
t.Errorf("ImageReferences(%v) = %v, want %v", string(inputYAML), got, want)
|
|
}
|
|
|
|
if diff := cmp.Diff(test.expected, outStructured, cmpopts.EquateEmpty()); diff != "" {
|
|
t.Errorf("ImageReferences(%v); (-want +got) = %v", string(inputYAML), diff)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// object has public fields to avoid `yaml:"foo"` annotations.
|
|
type object struct {
|
|
S string
|
|
M map[string]object
|
|
A []object
|
|
P *object
|
|
}
|
|
|
|
func TestYAMLObject(t *testing.T) {
|
|
base := mustRepository("gcr.io/bazinga")
|
|
tests := []struct {
|
|
desc string
|
|
input *object
|
|
expected *object
|
|
}{{
|
|
desc: "empty object",
|
|
input: &object{},
|
|
expected: &object{},
|
|
}, {
|
|
desc: "string field",
|
|
input: &object{S: fooRef},
|
|
expected: &object{S: kotesting.ComputeDigest(base, fooRef, fooHash)},
|
|
}, {
|
|
desc: "map field",
|
|
input: &object{M: map[string]object{"blah": {S: fooRef}}},
|
|
expected: &object{M: map[string]object{"blah": {S: kotesting.ComputeDigest(base, fooRef, fooHash)}}},
|
|
}, {
|
|
desc: "array field",
|
|
input: &object{A: []object{{S: fooRef}}},
|
|
expected: &object{A: []object{{S: kotesting.ComputeDigest(base, fooRef, fooHash)}}},
|
|
}, {
|
|
desc: "pointer field",
|
|
input: &object{P: &object{S: fooRef}},
|
|
expected: &object{P: &object{S: kotesting.ComputeDigest(base, fooRef, fooHash)}},
|
|
}, {
|
|
desc: "deep field",
|
|
input: &object{M: map[string]object{"blah": {A: []object{{P: &object{S: fooRef}}}}}},
|
|
expected: &object{M: map[string]object{"blah": {A: []object{{P: &object{S: kotesting.ComputeDigest(base, fooRef, fooHash)}}}}}},
|
|
}}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.desc, func(t *testing.T) {
|
|
inputStructured := test.input
|
|
inputYAML, err := yaml.Marshal(inputStructured)
|
|
if err != nil {
|
|
t.Fatalf("yaml.Marshal(%v) = %v", inputStructured, err)
|
|
}
|
|
|
|
doc := strToYAML(t, string(inputYAML))
|
|
err = ImageReferences(context.Background(), []*yaml.Node{doc}, false, testBuilder, kotesting.NewFixedPublish(base, testHashes))
|
|
if err != nil {
|
|
t.Fatalf("ImageReferences(%v) = %v", string(inputYAML), err)
|
|
}
|
|
var outStructured *object
|
|
if err := doc.Decode(&outStructured); err != nil {
|
|
t.Errorf("doc.Decode(%v) = %v", yamlToStr(t, doc), err)
|
|
}
|
|
|
|
if diff := cmp.Diff(test.expected, outStructured, cmpopts.EquateEmpty()); diff != "" {
|
|
t.Errorf("ImageReferences(%v); (-want +got) = %v", string(inputYAML), diff)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestStrict(t *testing.T) {
|
|
refs := []string{
|
|
build.StrictScheme + fooRef,
|
|
build.StrictScheme + barRef,
|
|
}
|
|
buf := bytes.NewBuffer(nil)
|
|
encoder := yaml.NewEncoder(buf)
|
|
for _, input := range refs {
|
|
if err := encoder.Encode(input); err != nil {
|
|
t.Fatalf("Encode(%v) = %v", input, err)
|
|
}
|
|
}
|
|
base := mustRepository("gcr.io/multi-pass")
|
|
doc := strToYAML(t, string(buf.Bytes()))
|
|
|
|
err := ImageReferences(context.Background(), []*yaml.Node{doc}, true, testBuilder, kotesting.NewFixedPublish(base, testHashes))
|
|
if err != nil {
|
|
t.Fatalf("ImageReferences: %v", err)
|
|
}
|
|
t.Log(yamlToStr(t, doc))
|
|
}
|
|
|
|
func TestNoStrictKoPrefixRemains(t *testing.T) {
|
|
ref := build.StrictScheme + fooRef
|
|
|
|
buf := bytes.NewBuffer(nil)
|
|
encoder := yaml.NewEncoder(buf)
|
|
if err := encoder.Encode(ref); err != nil {
|
|
t.Fatalf("Encode(%v) = %v", ref, err)
|
|
}
|
|
|
|
base := mustRepository("gcr.io/multi-pass")
|
|
doc := strToYAML(t, string(buf.Bytes()))
|
|
|
|
noMatchBuilder := kotesting.NewFixedBuild(nil)
|
|
|
|
err := ImageReferences(context.Background(), []*yaml.Node{doc}, false, noMatchBuilder, kotesting.NewFixedPublish(base, testHashes))
|
|
if err != nil {
|
|
t.Fatalf("ImageReferences: %v", err)
|
|
}
|
|
if diff := cmp.Diff(ref, strings.TrimSpace(yamlToStr(t, doc))); diff != "" {
|
|
t.Errorf("expected the ko prefix to remain (-want,+got): %v", diff)
|
|
}
|
|
}
|
|
|
|
func mustRandom() v1.Image {
|
|
img, err := random.Image(1024, 5)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return img
|
|
}
|
|
|
|
func mustRepository(s string) name.Repository {
|
|
n, err := name.NewRepository(s)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return n
|
|
}
|
|
|
|
func mustDigest(img v1.Image) v1.Hash {
|
|
d, err := img.Digest()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return d
|
|
}
|