Repository: google/ko
Pull request #211 (2020-09-30 17:48:27)

Document `--platform` flag in README.md
Followup on #38 



codecov-commenter: # [Codecov](https://codecov.io/gh/google/ko/pull/211?src=pr&el=h1) Report
> Merging [#211](https://codecov.io/gh/google/ko/pull/211?src=pr&el=desc) into [master](https://codecov.io/gh/google/ko/commit/27676b12d84c9a20068ba76e99fa64f4bcf9dfdf?el=desc) will **not change** coverage.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/google/ko/pull/211/graphs/tree.svg?width=650&height=150&src=pr&token=UhqMbLrA3r)](https://codecov.io/gh/google/ko/pull/211?src=pr&el=tree)

```diff
@@           Coverage Diff           @@
##           master     #211   +/-   ##
=======================================
  Coverage   37.15%   37.15%           
=======================================
  Files          33       33           
  Lines        1405     1405           
=======================================
  Hits          522      522           
  Misses        793      793           
  Partials       90       90           
```



------

[Continue to review full report at Codecov](https://codecov.io/gh/google/ko/pull/211?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/google/ko/pull/211?src=pr&el=footer). Last update [27676b1...aa77632](https://codecov.io/gh/google/ko/pull/211?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

jonjohnsonjr: I have seen a lot of folks referencing the `--platform=amd64` syntax, which won't work (has been corrected in this PR). If people don't like having to `--platform=linux/amd64`, we should talk about that **now** before this implementation ossifies.

Patch:
diff --git a/.golangci.yaml b/.golangci.yaml
index 6747ceda..a241ffad 100644
--- a/.golangci.yaml
+++ b/.golangci.yaml
@@ -1,21 +1,6 @@
 run:
   timeout: 5m
 
-  build-tags:
-    - e2e
-    - performance
-    - probe
-    - preupgrade
-    - postupgrade
-    - postdowngrade
-    - istio
-
-  skip-dirs:
-    - pkg/client
-
-  skip-files:
-    - ".pb.go$"
-
 linters:
   enable:
     - asciicheck
@@ -32,10 +17,3 @@ issues:
     - path: test # Excludes /test, *_test.go etc.
       linters:
         - gosec
-        - unparam
-
-    # Allow source and sink receivers in conversion code for clarity.
-    - path: _conversion\.go
-      text: "ST1016:"
-      linters:
-        - stylecheck
diff --git a/README.md b/README.md
index 09d7b76e..125ad9ad 100644
--- a/README.md
+++ b/README.md
@@ -303,9 +303,10 @@ preserved (see `--preserve-import-paths`).
 
 ## With `kind`
 
-Likewise, you can use `ko` with `kind` to aid in rapid local iteration both
-locally and in small CI environments. To instruct `ko` to publish images into
-your `kind` cluster, the `KO_DOCKER_REPO` variable must be set to `kind.local`.
+Likewise, you can use `ko` with [kind](https://github.com/kubernetes-sigs/kind)
+to aid in rapid local iteration both locally and in small CI environments. To
+instruct `ko` to publish images into your `kind` cluster, the `KO_DOCKER_REPO`
+variable must be set to `kind.local`.
 
 This would look something like:
 
@@ -313,10 +314,14 @@ This would look something like:
 # Create a kind cluster
 kind create cluster
 
-# Deploy to minikube w/o registry.
-KO_DOCKER_REPO=kind.local ko apply -L -f config/
+# Deploy to kind w/o registry.
+KO_DOCKER_REPO=kind.local ko apply -f config/
 ```
 
+If you want to create a `kind` cluster with a non default name, you can set the
+`KIND_CLUSTER_NAME` variable to the respective name (which is also supported by
+[`kind` itself](https://github.com/kubernetes-sigs/kind/releases/tag/v0.8.0)).
+
 Like with `minikube` above, a caveat of this approach is that it will not work
 if your container is configured with `imagePullPolicy: Always` because despite
 having the image locally, a pull is performed to ensure we have the latest
@@ -426,6 +431,25 @@ kubectl logs kodata
 2018/07/19 23:38:29 Hello there
 ```
 
+## Multi-Platform Images
+
+If `ko` is invoked with `--platform=all`, for any image that it builds that is
+based on a multi-architecture image (e.g., the default
+`gcr.io/distroless/static:nonroot`, `busybox`, `alpine`, etc.), `ko` will
+attempt to build the Go binary using Go's cross-compilation support and produce
+a multi-architecture [image
+index](https://github.com/opencontainers/image-spec/blob/master/image-index.md)
+(aka "manifest list"), with support for each OS and architecture pair supported
+by the base image.
+
+If `ko` is invoked with `--platform=<some-OS>/<some-platform>` (e.g.,
+`--platform=linux/amd64` or `--platform=linux/arm64`), then it will attempt to
+build an image for that OS and architecture only, assuming the base image
+supports it.
+
+When `--platform` is not provided, `ko` builds an image with the OS and
+architecture based on the build environment's `GOOS` and `GOARCH`.
+
 ## Enable Autocompletion
 
 To generate an bash completion script, you can run:
diff --git a/pkg/build/gobuild.go b/pkg/build/gobuild.go
index bcabeeb6..7f6c33dc 100644
--- a/pkg/build/gobuild.go
+++ b/pkg/build/gobuild.go
@@ -425,7 +425,7 @@ func (g *gobuild) tarKoData(ref reference) (*bytes.Buffer, error) {
 	return buf, walkRecursive(tw, root, kodataRoot)
 }
 
-func (gb *gobuild) buildOne(ctx context.Context, s string, base v1.Image) (v1.Image, error) {
+func (g *gobuild) buildOne(ctx context.Context, s string, base v1.Image) (v1.Image, error) {
 	ref := newRef(s)
 
 	cf, err := base.ConfigFile()
@@ -438,7 +438,7 @@ func (gb *gobuild) buildOne(ctx context.Context, s string, base v1.Image) (v1.Im
 	}
 
 	// Do the build into a temporary file.
-	file, err := gb.build(ctx, ref.Path(), platform, gb.disableOptimizations)
+	file, err := g.build(ctx, ref.Path(), platform, g.disableOptimizations)
 	if err != nil {
 		return nil, err
 	}
@@ -446,7 +446,7 @@ func (gb *gobuild) buildOne(ctx context.Context, s string, base v1.Image) (v1.Im
 
 	var layers []mutate.Addendum
 	// Create a layer from the kodata directory under this import path.
-	dataLayerBuf, err := gb.tarKoData(ref)
+	dataLayerBuf, err := g.tarKoData(ref)
 	if err != nil {
 		return nil, err
 	}
@@ -514,8 +514,8 @@ func (gb *gobuild) buildOne(ctx context.Context, s string, base v1.Image) (v1.Im
 	}
 
 	empty := v1.Time{}
-	if gb.creationTime != empty {
-		return mutate.CreatedAt(image, gb.creationTime)
+	if g.creationTime != empty {
+		return mutate.CreatedAt(image, g.creationTime)
 	}
 	return image, nil
 }
@@ -542,9 +542,9 @@ func updatePath(cf *v1.ConfigFile) {
 }
 
 // Build implements build.Interface
-func (gb *gobuild) Build(ctx context.Context, s string) (Result, error) {
+func (g *gobuild) Build(ctx context.Context, s string) (Result, error) {
 	// Determine the appropriate base image for this import path.
-	base, err := gb.getBase(s)
+	base, err := g.getBase(s)
 	if err != nil {
 		return nil, err
 	}
@@ -561,20 +561,20 @@ func (gb *gobuild) Build(ctx context.Context, s string) (Result, error) {
 		if !ok {
 			return nil, fmt.Errorf("failed to interpret base as index: %v", base)
 		}
-		return gb.buildAll(ctx, s, base)
+		return g.buildAll(ctx, s, base)
 	case types.OCIManifestSchema1, types.DockerManifestSchema2:
 		base, ok := base.(v1.Image)
 		if !ok {
 			return nil, fmt.Errorf("failed to interpret base as image: %v", base)
 		}
-		return gb.buildOne(ctx, s, base)
+		return g.buildOne(ctx, s, base)
 	default:
 		return nil, fmt.Errorf("base image media type: %s", mt)
 	}
 }
 
 // TODO(#192): Do these in parallel?
-func (gb *gobuild) buildAll(ctx context.Context, s string, base v1.ImageIndex) (v1.ImageIndex, error) {
+func (g *gobuild) buildAll(ctx context.Context, s string, base v1.ImageIndex) (v1.ImageIndex, error) {
 	im, err := base.IndexManifest()
 	if err != nil {
 		return nil, err
@@ -592,7 +592,7 @@ func (gb *gobuild) buildAll(ctx context.Context, s string, base v1.ImageIndex) (
 		if err != nil {
 			return nil, err
 		}
-		img, err := gb.buildOne(ctx, s, base)
+		img, err := g.buildOne(ctx, s, base)
 		if err != nil {
 			return nil, err
 		}
diff --git a/pkg/commands/options/publish.go b/pkg/commands/options/publish.go
index 2e8b4253..21265231 100644
--- a/pkg/commands/options/publish.go
+++ b/pkg/commands/options/publish.go
@@ -15,7 +15,7 @@
 package options
 
 import (
-	"crypto/md5"
+	"crypto/md5" //nolint: gosec // No strong cryptography needed.
 	"encoding/hex"
 	"path/filepath"
 
@@ -64,7 +64,7 @@ func AddPublishArg(cmd *cobra.Command, po *PublishOptions) {
 }
 
 func packageWithMD5(importpath string) string {
-	hasher := md5.New()
+	hasher := md5.New() //nolint: gosec // No strong cryptography needed.
 	hasher.Write([]byte(importpath))
 	return filepath.Base(importpath) + "-" + hex.EncodeToString(hasher.Sum(nil))
 }
diff --git a/pkg/commands/resolver.go b/pkg/commands/resolver.go
index 579659ec..24d18961 100644
--- a/pkg/commands/resolver.go
+++ b/pkg/commands/resolver.go
@@ -160,6 +160,16 @@ func makePublisher(po *options.PublishOptions) (publish.Interface, error) {
 			}
 			publishers = append(publishers, dp)
 		}
+
+		// If not publishing, at least generate a digest to simulate
+		// publishing.
+		if len(publishers) == 0 {
+			publishers = append(publishers, nopPublisher{
+				repoName: repoName,
+				namer:    namer,
+			})
+		}
+
 		return publish.MultiPublisher(publishers...), nil
 	}()
 	if err != nil {
@@ -170,6 +180,23 @@ func makePublisher(po *options.PublishOptions) (publish.Interface, error) {
 	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
+}
+
+func (n nopPublisher) Publish(br build.Result, s string) (name.Reference, error) {
+	h, err := br.Digest()
+	if err != nil {
+		return nil, err
+	}
+	return name.NewDigest(fmt.Sprintf("%s/%s@%s", n.repoName, n.namer(s), h))
+}
+
+func (n nopPublisher) Close() error { return nil }
+
 // resolvedFuture represents a "future" for the bytes of a resolved file.
 type resolvedFuture chan []byte
 
diff --git a/pkg/commands/version.go b/pkg/commands/version.go
index 8416c398..4d0752c8 100644
--- a/pkg/commands/version.go
+++ b/pkg/commands/version.go
@@ -21,7 +21,7 @@ import (
 	"github.com/spf13/cobra"
 )
 
-// provided by govvv in compile-time
+// Version is provided by govvv at compile-time
 var Version string
 
 // addVersion augments our CLI surface with version.
diff --git a/pkg/publish/multi.go b/pkg/publish/multi.go
index f4c2f8e7..68240005 100644
--- a/pkg/publish/multi.go
+++ b/pkg/publish/multi.go
@@ -15,6 +15,8 @@
 package publish
 
 import (
+	"errors"
+
 	"github.com/google/go-containerregistry/pkg/name"
 	"github.com/google/ko/pkg/build"
 )
@@ -34,13 +36,16 @@ type multiPublisher struct {
 
 // Publish implements publish.Interface.
 func (p *multiPublisher) Publish(br build.Result, s string) (ref name.Reference, err error) {
+	if len(p.publishers) == 0 {
+		return nil, errors.New("MultiPublisher configured with zero publishers")
+	}
+
 	for _, pub := range p.publishers {
 		ref, err = pub.Publish(br, s)
 		if err != nil {
 			return
 		}
 	}
-
 	return
 }
 
diff --git a/pkg/resolve/selector.go b/pkg/resolve/selector.go
index 1c4fc352..0f2813e7 100644
--- a/pkg/resolve/selector.go
+++ b/pkg/resolve/selector.go
@@ -17,7 +17,7 @@ package resolve
 import (
 	"errors"
 
-	. "github.com/dprotaso/go-yit"
+	. "github.com/dprotaso/go-yit" //nolint: stylecheck // Allow this dot import.
 	"gopkg.in/yaml.v3"
 	"k8s.io/apimachinery/pkg/labels"
 )


Test patch:
diff --git a/cmd/ko/test/main.go b/cmd/ko/test/main.go
index a88f651e..70b2a343 100644
--- a/cmd/ko/test/main.go
+++ b/cmd/ko/test/main.go
@@ -37,10 +37,10 @@ func main() {
 	if err != nil {
 		log.Fatalf("Error reading %q: %v", file, err)
 	}
-	log.Printf(string(bytes))
+	log.Print(string(bytes))
 
 	// Cause the pod to "hang" to allow us to check for a readiness state.
-	sigs := make(chan os.Signal)
+	sigs := make(chan os.Signal, 1)
 	signal.Notify(sigs, syscall.SIGTERM)
 	<-sigs
 }
diff --git a/pkg/build/gobuild_test.go b/pkg/build/gobuild_test.go
index a241449c..2eff3dc8 100644
--- a/pkg/build/gobuild_test.go
+++ b/pkg/build/gobuild_test.go
@@ -397,7 +397,7 @@ func TestGoBuild(t *testing.T) {
 	}
 	importpath := "github.com/google/ko"
 
-	creationTime := v1.Time{time.Unix(5000, 0)}
+	creationTime := v1.Time{Time: time.Unix(5000, 0)}
 	ng, err := NewGo(
 		WithCreationTime(creationTime),
 		WithBaseImages(func(string) (Result, error) { return base, nil }),
@@ -451,7 +451,7 @@ func TestGoBuildIndex(t *testing.T) {
 	}
 	importpath := "github.com/google/ko"
 
-	creationTime := v1.Time{time.Unix(5000, 0)}
+	creationTime := v1.Time{Time: time.Unix(5000, 0)}
 	ng, err := NewGo(
 		WithCreationTime(creationTime),
 		WithBaseImages(func(string) (Result, error) { return base, nil }),
@@ -521,7 +521,7 @@ func TestNestedIndex(t *testing.T) {
 
 	nestedBase := mutate.AppendManifests(empty.Index, mutate.IndexAddendum{Add: base})
 
-	creationTime := v1.Time{time.Unix(5000, 0)}
+	creationTime := v1.Time{Time: time.Unix(5000, 0)}
 	ng, err := NewGo(
 		WithCreationTime(creationTime),
 		WithBaseImages(func(string) (Result, error) { return nestedBase, nil }),
diff --git a/pkg/publish/multi_test.go b/pkg/publish/multi_test.go
index 46ed2120..4112fa29 100644
--- a/pkg/publish/multi_test.go
+++ b/pkg/publish/multi_test.go
@@ -61,3 +61,15 @@ func TestMulti(t *testing.T) {
 		t.Errorf("Close() = %v", err)
 	}
 }
+
+func TestMulti_Zero(t *testing.T) {
+	img, err := random.Image(1024, 1)
+	if err != nil {
+		t.Fatalf("random.Image() = %v", err)
+	}
+
+	p := MultiPublisher() // No publishers.
+	if _, err := p.Publish(img, "foo"); err == nil {
+		t.Errorf("Publish() got nil error")
+	}
+}