1
0
mirror of https://github.com/ko-build/ko.git synced 2025-02-01 19:14:40 +02:00

Implement "strict mode"

When ko is invoked in this mode, import paths must have the `ko://`
prefix. If a human marks an import path with `ko://` and ko can't
resolve the resulting import path, it fails. In "loose mode", such an
import path would be silently ignored and passed on to the resolved
YAML, often resulting in invalid image names (e.g., `image:
github.com/foo/bar`)

In loose mode, `ko://` prefixes are always ignored for
backward-compatibility.
This commit is contained in:
Jason Hall 2019-07-10 01:11:20 -04:00
parent 2d12e28795
commit 4342ceff74
14 changed files with 126 additions and 35 deletions

View File

@ -49,6 +49,7 @@ type gobuild struct {
build builder
disableOptimizations bool
mod *modInfo
strict bool
}
// Option is a functional option for NewGo.
@ -60,6 +61,7 @@ type gobuildOpener struct {
build builder
disableOptimizations bool
mod *modInfo
strict bool
}
func (gbo *gobuildOpener) Open() (Interface, error) {
@ -72,6 +74,7 @@ func (gbo *gobuildOpener) Open() (Interface, error) {
build: gbo.build,
disableOptimizations: gbo.disableOptimizations,
mod: gbo.mod,
strict: gbo.strict,
}, nil
}
@ -119,6 +122,10 @@ func NewGo(options ...Option) (Interface, error) {
// Only valid importpaths that provide commands (i.e., are "package main") are
// supported.
func (g *gobuild) IsSupportedReference(s string) bool {
if g.strict && !strings.HasPrefix(s, "ko://") {
return false
}
s = strings.TrimPrefix(s, "ko://")
p, err := g.importPackage(s)
if err != nil {
return false

View File

@ -39,7 +39,8 @@ func TestGoBuildIsSupportedRef(t *testing.T) {
// Supported import paths.
for _, importpath := range []string{
filepath.FromSlash("github.com/google/ko/cmd/ko"), // ko can build itself.
filepath.FromSlash("github.com/google/ko/cmd/ko"), // ko can build itself.
filepath.FromSlash("ko://github.com/google/ko/cmd/ko"), // ko:// prefix is ignored in loose mode.
} {
t.Run(importpath, func(t *testing.T) {
if !ng.IsSupportedReference(importpath) {
@ -61,19 +62,52 @@ func TestGoBuildIsSupportedRef(t *testing.T) {
}
}
func TestGoBuildIsSupportedReference_Strict(t *testing.T) {
base, err := random.Image(1024, 3)
if err != nil {
t.Fatalf("random.Image() = %v", err)
}
ng, err := NewGo(WithBaseImages(func(string) (v1.Image, error) { return base, nil }), WithStrict())
if err != nil {
t.Fatalf("NewGo() = %v", err)
}
// Supported import paths.
for _, importpath := range []string{
filepath.FromSlash("ko://github.com/google/ko/cmd/ko"), // ko can build itself.
} {
t.Run(importpath, func(t *testing.T) {
if !ng.IsSupportedReference(importpath) {
t.Errorf("IsSupportedReference(%q) = false, want true", importpath)
}
})
}
// Unsupported import paths.
for _, importpath := range []string{
filepath.FromSlash("github.com/google/ko/cmd/ko"), // In strict mode, without ko://, it's not supported.
filepath.FromSlash("github.com/google/ko/pkg/build"), // not a command.
filepath.FromSlash("github.com/google/ko/pkg/nonexistent"), // does not exist.
} {
t.Run(importpath, func(t *testing.T) {
if ng.IsSupportedReference(importpath) {
t.Errorf("IsSupportedReference(%v) = true, want false", importpath)
}
})
}
}
func TestGoBuildIsSupportedRefWithModules(t *testing.T) {
base, err := random.Image(1024, 3)
if err != nil {
t.Fatalf("random.Image() = %v", err)
}
mod := &modInfo{
Path: filepath.FromSlash("github.com/google/ko/cmd/ko/test"),
Dir: ".",
}
ng, err := NewGo(WithBaseImages(func(string) (v1.Image, error) { return base, nil }),
withModuleInfo(mod))
ng, err := NewGo(WithBaseImages(func(string) (v1.Image, error) { return base, nil }), withModuleInfo(mod))
if err != nil {
t.Fatalf("NewGo() = %v", err)
}

View File

@ -47,7 +47,6 @@ func WithDisabledOptimizations() Option {
// withBuilder is a functional option for overriding the way go binaries
// are built.
// This is exposed for testing.
func withBuilder(b builder) Option {
return func(gbo *gobuildOpener) error {
gbo.build = b
@ -64,3 +63,12 @@ func withModuleInfo(mi *modInfo) Option {
return nil
}
}
// WithStrict is a functional option for requiring that package references are
// explicitly noted.
func WithStrict() Option {
return func(g *gobuildOpener) error {
g.strict = true
return nil
}
}

View File

@ -34,6 +34,7 @@ func addApply(topLevel *cobra.Command) {
ta := &options.TagsOptions{}
do := &options.DebugOptions{}
so := &options.SelectorOptions{}
sto := &options.StrictOptions{}
apply := &cobra.Command{
Use: "apply -f FILENAME",
Short: "Apply the input files with image references resolved to built/pushed image digests.",
@ -63,7 +64,7 @@ func addApply(topLevel *cobra.Command) {
cat config.yaml | ko apply -f -`,
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
builder, err := makeBuilder(do)
builder, err := makeBuilder(do, sto)
if err != nil {
log.Fatalf("error creating builder: %v", err)
}
@ -116,7 +117,7 @@ func addApply(topLevel *cobra.Command) {
stdin.Write([]byte("---\n"))
}
// Once primed kick things off.
resolveFilesToWriter(builder, publisher, fo, so, stdin)
resolveFilesToWriter(builder, publisher, fo, so, sto, stdin)
}()
// Run it.
@ -131,6 +132,7 @@ func addApply(topLevel *cobra.Command) {
options.AddTagsArg(apply, ta)
options.AddDebugArg(apply, do)
options.AddSelectorArg(apply, so)
options.AddStrictArg(apply, sto)
// Collect the ko-specific apply flags before registering the kubectl global
// flags so that we can ignore them when passing kubectl global flags through

View File

@ -34,6 +34,7 @@ func addCreate(topLevel *cobra.Command) {
ta := &options.TagsOptions{}
do := &options.DebugOptions{}
so := &options.SelectorOptions{}
sto := &options.StrictOptions{}
create := &cobra.Command{
Use: "create -f FILENAME",
Short: "Create the input files with image references resolved to built/pushed image digests.",
@ -63,7 +64,7 @@ func addCreate(topLevel *cobra.Command) {
cat config.yaml | ko create -f -`,
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
builder, err := makeBuilder(do)
builder, err := makeBuilder(do, sto)
if err != nil {
log.Fatalf("error creating builder: %v", err)
}
@ -116,7 +117,7 @@ func addCreate(topLevel *cobra.Command) {
stdin.Write([]byte("---\n"))
}
// Once primed kick things off.
resolveFilesToWriter(builder, publisher, fo, so, stdin)
resolveFilesToWriter(builder, publisher, fo, so, sto, stdin)
}()
// Run it.
@ -131,6 +132,7 @@ func addCreate(topLevel *cobra.Command) {
options.AddTagsArg(create, ta)
options.AddDebugArg(create, do)
options.AddSelectorArg(create, so)
options.AddStrictArg(create, sto)
// Collect the ko-specific apply flags before registering the kubectl global
// flags so that we can ignore them when passing kubectl global flags through

View File

@ -21,13 +21,13 @@ import (
// LocalOptions represents options for the ko binary.
type LocalOptions struct {
// Local publishes images to a local docker daemon.
Local bool
Local bool
InsecureRegistry bool
}
func AddLocalArg(cmd *cobra.Command, lo *LocalOptions) {
cmd.Flags().BoolVarP(&lo.Local, "local", "L", lo.Local,
"Whether to publish images to a local docker daemon vs. a registry.")
cmd.Flags().BoolVar(&lo.InsecureRegistry, "insecure-registry", lo.InsecureRegistry,
cmd.Flags().BoolVar(&lo.InsecureRegistry, "insecure-registry", lo.InsecureRegistry,
"Whether to skip TLS verification on the registry")
}

View File

@ -0,0 +1,29 @@
// Copyright 2019 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 options
import (
"github.com/spf13/cobra"
)
// StrictOptions holds options to require strict references.
type StrictOptions struct {
Strict bool
}
func AddStrictArg(cmd *cobra.Command, so *StrictOptions) {
cmd.Flags().BoolVarP(&so.Strict, "strict", "S", so.Strict,
"If true, require package references to be explicitly noted")
}

View File

@ -60,7 +60,7 @@ func addPublish(topLevel *cobra.Command) {
ko publish --local github.com/foo/bar/cmd/baz github.com/foo/bar/cmd/blah`,
Args: cobra.MinimumNArgs(1),
Run: func(_ *cobra.Command, args []string) {
builder, err := makeBuilder(do)
builder, err := makeBuilder(do, &options.StrictOptions{})
if err != nil {
log.Fatalf("error creating builder: %v", err)
}

View File

@ -24,13 +24,13 @@ import (
// addResolve augments our CLI surface with resolve.
func addResolve(topLevel *cobra.Command) {
lo := &options.LocalOptions{}
no := &options.NameOptions{}
fo := &options.FilenameOptions{}
ta := &options.TagsOptions{}
do := &options.DebugOptions{}
so := &options.SelectorOptions{}
sto := &options.StrictOptions{}
resolve := &cobra.Command{
Use: "resolve -f FILENAME",
@ -58,7 +58,7 @@ func addResolve(topLevel *cobra.Command) {
ko resolve --local -f config/`,
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
builder, err := makeBuilder(do)
builder, err := makeBuilder(do, sto)
if err != nil {
log.Fatalf("error creating builder: %v", err)
}
@ -66,7 +66,7 @@ func addResolve(topLevel *cobra.Command) {
if err != nil {
log.Fatalf("error creating publisher: %v", err)
}
resolveFilesToWriter(builder, publisher, fo, so, os.Stdout)
resolveFilesToWriter(builder, publisher, fo, so, sto, os.Stdout)
},
}
options.AddLocalArg(resolve, lo)
@ -75,5 +75,6 @@ func addResolve(topLevel *cobra.Command) {
options.AddTagsArg(resolve, ta)
options.AddDebugArg(resolve, do)
options.AddSelectorArg(resolve, so)
options.AddStrictArg(resolve, sto)
topLevel.AddCommand(resolve)
}

View File

@ -32,7 +32,7 @@ import (
"github.com/mattmoor/dep-notify/pkg/graph"
)
func gobuildOptions(do *options.DebugOptions) ([]build.Option, error) {
func gobuildOptions(do *options.DebugOptions, so *options.StrictOptions) ([]build.Option, error) {
creationTime, err := getCreationTime()
if err != nil {
return nil, err
@ -46,11 +46,14 @@ func gobuildOptions(do *options.DebugOptions) ([]build.Option, error) {
if do.DisableOptimizations {
opts = append(opts, build.WithDisabledOptimizations())
}
if so.Strict {
opts = append(opts, build.WithStrict())
}
return opts, nil
}
func makeBuilder(do *options.DebugOptions) (*build.Caching, error) {
opt, err := gobuildOptions(do)
func makeBuilder(do *options.DebugOptions, so *options.StrictOptions) (*build.Caching, error) {
opt, err := gobuildOptions(do, so)
if err != nil {
log.Fatalf("error setting up builder options: %v", err)
}
@ -113,7 +116,7 @@ func makePublisher(no *options.NameOptions, lo *options.LocalOptions, ta *option
// resolvedFuture represents a "future" for the bytes of a resolved file.
type resolvedFuture chan []byte
func resolveFilesToWriter(builder *build.Caching, publisher publish.Interface, fo *options.FilenameOptions, so *options.SelectorOptions, out io.WriteCloser) {
func resolveFilesToWriter(builder *build.Caching, publisher publish.Interface, fo *options.FilenameOptions, so *options.SelectorOptions, sto *options.StrictOptions, out io.WriteCloser) {
defer out.Close()
// By having this as a channel, we can hook this up to a filesystem
@ -194,7 +197,7 @@ func resolveFilesToWriter(builder *build.Caching, publisher publish.Interface, f
recordingBuilder := &build.Recorder{
Builder: builder,
}
b, err := resolveFile(f, recordingBuilder, publisher, so)
b, err := resolveFile(f, recordingBuilder, publisher, so, sto)
if err != nil {
// Don't let build errors disrupt the watch.
lg := log.Fatalf
@ -239,7 +242,7 @@ func resolveFilesToWriter(builder *build.Caching, publisher publish.Interface, f
}
}
func resolveFile(f string, builder build.Interface, pub publish.Interface, so *options.SelectorOptions) (b []byte, err error) {
func resolveFile(f string, builder build.Interface, pub publish.Interface, so *options.SelectorOptions, sto *options.StrictOptions) (b []byte, err error) {
if f == "-" {
b, err = ioutil.ReadAll(os.Stdin)
} else {
@ -256,5 +259,5 @@ func resolveFile(f string, builder build.Interface, pub publish.Interface, so *o
}
}
return resolve.ImageReferences(b, builder, pub)
return resolve.ImageReferences(b, sto.Strict, builder, pub)
}

View File

@ -45,7 +45,7 @@ func addRun(topLevel *cobra.Command) {
# This supports relative import paths as well.
ko run foo --image=./cmd/baz`,
Run: func(cmd *cobra.Command, args []string) {
builder, err := makeBuilder(do)
builder, err := makeBuilder(do, &options.StrictOptions{})
if err != nil {
log.Fatalf("error creating builder: %v", err)
}

View File

@ -28,11 +28,11 @@ import (
// defalt is intentionally misspelled to avoid keyword collision (and drive Jon nuts).
type defalt struct {
base string
t http.RoundTripper
auth authn.Authenticator
namer Namer
tags []string
base string
t http.RoundTripper
auth authn.Authenticator
namer Namer
tags []string
insecure bool
}
@ -40,11 +40,11 @@ type defalt struct {
type Option func(*defaultOpener) error
type defaultOpener struct {
base string
t http.RoundTripper
auth authn.Authenticator
namer Namer
tags []string
base string
t http.RoundTripper
auth authn.Authenticator
namer Namer
tags []string
insecure bool
}

View File

@ -86,4 +86,4 @@ func Insecure(b bool) Option {
i.insecure = b
return nil
}
}
}

View File

@ -18,6 +18,7 @@ import (
"bytes"
"fmt"
"io"
"strings"
"sync"
"github.com/google/ko/pkg/build"
@ -28,7 +29,7 @@ import (
// ImageReferences resolves supported references to images within the input yaml
// to published image digests.
func ImageReferences(input []byte, builder build.Interface, publisher publish.Interface) ([]byte, error) {
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.
@ -46,7 +47,10 @@ func ImageReferences(input []byte, builder build.Interface, publisher publish.In
// This simply returns the replaced object, which we discard during the gathering phase.
if _, err := replaceRecursive(obj, func(ref string) (string, error) {
if builder.IsSupportedReference(ref) {
ref = strings.TrimPrefix(ref, "ko://")
refs[ref] = struct{}{}
} else if strict && strings.HasPrefix(ref, "ko://") {
return "", fmt.Errorf("Strict reference %q is not supported", ref)
}
return ref, nil
}); err != nil {
@ -93,6 +97,7 @@ func ImageReferences(input []byte, builder build.Interface, publisher publish.In
if !builder.IsSupportedReference(ref) {
return ref, nil
}
ref = strings.TrimPrefix(ref, "ko://")
if val, ok := sm.Load(ref); ok {
return val.(string), nil
}