Repository: google/ko
Pull request #339 (2021-04-14 20:28:04)

Build ko for Windows
Also explicitly build for macOS (darwin), I'm not sure how that was being done before.

codecov-io: # [Codecov](https://codecov.io/gh/google/ko/pull/339?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) Report
> Merging [#339](https://codecov.io/gh/google/ko/pull/339?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) (550a6f1) into [main](https://codecov.io/gh/google/ko/commit/d4987345562115ab5b8985301d1f89bf70c91966?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) (d498734) will **not change** coverage.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/google/ko/pull/339/graphs/tree.svg?width=650&height=150&src=pr&token=UhqMbLrA3r&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google)](https://codecov.io/gh/google/ko/pull/339?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google)

```diff
@@           Coverage Diff           @@
##             main     #339   +/-   ##
=======================================
  Coverage   38.24%   38.24%           
=======================================
  Files          33       33           
  Lines        1527     1527           
=======================================
  Hits          584      584           
  Misses        849      849           
  Partials       94       94           
```



------

[Continue to review full report at Codecov](https://codecov.io/gh/google/ko/pull/339?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/google/ko/pull/339?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google). Last update [d498734...550a6f1](https://codecov.io/gh/google/ko/pull/339?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google).


Patch:
diff --git a/.goreleaser.yml b/.goreleaser.yml
index b03c3833..0946f8e1 100644
--- a/.goreleaser.yml
+++ b/.goreleaser.yml
@@ -14,6 +14,10 @@ builds:
   - -trimpath
   ldflags:
   - "-s -w -X github.com/google/ko/pkg/commands.Version={{.Version}}"
+  goos:
+  - windows
+  - linux
+  - darwin
   goarch:
   - amd64
   - arm64
diff --git a/pkg/commands/apply.go b/pkg/commands/apply.go
index 1365bfdd..af9ad24b 100644
--- a/pkg/commands/apply.go
+++ b/pkg/commands/apply.go
@@ -15,8 +15,8 @@
 package commands
 
 import (
+	"errors"
 	"fmt"
-	"log"
 	"os"
 	"os/exec"
 
@@ -62,10 +62,9 @@ func addApply(topLevel *cobra.Command) {
   # Apply from stdin:
   cat config.yaml | ko apply -f -`,
 		Args: cobra.NoArgs,
-		Run: func(cmd *cobra.Command, args []string) {
+		RunE: func(cmd *cobra.Command, args []string) error {
 			if !isKubectlAvailable() {
-				log.Print("error: kubectl is not available. kubectl must be installed to use ko apply.")
-				return
+				return errors.New("error: kubectl is not available. kubectl must be installed to use ko apply")
 			}
 
 			// Cancel on signals.
@@ -73,11 +72,11 @@ func addApply(topLevel *cobra.Command) {
 
 			builder, err := makeBuilder(ctx, bo)
 			if err != nil {
-				log.Fatalf("error creating builder: %v", err)
+				return fmt.Errorf("error creating builder: %v", err)
 			}
 			publisher, err := makePublisher(po)
 			if err != nil {
-				log.Fatalf("error creating publisher: %v", err)
+				return fmt.Errorf("error creating publisher: %v", err)
 			}
 			defer publisher.Close()
 			// Create a set of ko-specific flags to ignore when passing through
@@ -110,7 +109,7 @@ func addApply(topLevel *cobra.Command) {
 			// Wire up kubectl stdin to resolveFilesToWriter.
 			stdin, err := kubectlCmd.StdinPipe()
 			if err != nil {
-				log.Fatalf("error piping to 'kubectl apply': %v", err)
+				return fmt.Errorf("error piping to 'kubectl apply': %v", err)
 			}
 
 			// Make sure builds are cancelled if kubectl apply fails.
@@ -138,9 +137,7 @@ func addApply(topLevel *cobra.Command) {
 				return nil
 			})
 
-			if err := g.Wait(); err != nil {
-				log.Fatal(err)
-			}
+			return g.Wait()
 		},
 	}
 	options.AddPublishArg(apply, po)
diff --git a/pkg/commands/create.go b/pkg/commands/create.go
index e91fb6a8..e29046bf 100644
--- a/pkg/commands/create.go
+++ b/pkg/commands/create.go
@@ -15,8 +15,8 @@
 package commands
 
 import (
+	"errors"
 	"fmt"
-	"log"
 	"os"
 	"os/exec"
 
@@ -62,10 +62,9 @@ func addCreate(topLevel *cobra.Command) {
   # Create from stdin:
   cat config.yaml | ko create -f -`,
 		Args: cobra.NoArgs,
-		Run: func(cmd *cobra.Command, args []string) {
+		RunE: func(cmd *cobra.Command, args []string) error {
 			if !isKubectlAvailable() {
-				log.Print("error: kubectl is not available. kubectl must be installed to use ko create.")
-				return
+				return errors.New("error: kubectl is not available. kubectl must be installed to use ko create")
 			}
 
 			// Cancel on signals.
@@ -73,11 +72,11 @@ func addCreate(topLevel *cobra.Command) {
 
 			builder, err := makeBuilder(ctx, bo)
 			if err != nil {
-				log.Fatalf("error creating builder: %v", err)
+				return fmt.Errorf("error creating builder: %v", err)
 			}
 			publisher, err := makePublisher(po)
 			if err != nil {
-				log.Fatalf("error creating publisher: %v", err)
+				return fmt.Errorf("error creating publisher: %v", err)
 			}
 			defer publisher.Close()
 			// Create a set of ko-specific flags to ignore when passing through
@@ -110,7 +109,7 @@ func addCreate(topLevel *cobra.Command) {
 			// Wire up kubectl stdin to resolveFilesToWriter.
 			stdin, err := kubectlCmd.StdinPipe()
 			if err != nil {
-				log.Fatalf("error piping to 'kubectl create': %v", err)
+				return fmt.Errorf("error piping to 'kubectl create': %v", err)
 			}
 
 			// Make sure builds are cancelled if kubectl create fails.
@@ -138,9 +137,7 @@ func addCreate(topLevel *cobra.Command) {
 				return nil
 			})
 
-			if err := g.Wait(); err != nil {
-				log.Fatal(err)
-			}
+			return g.Wait()
 		},
 	}
 	options.AddPublishArg(create, po)
diff --git a/pkg/commands/delete.go b/pkg/commands/delete.go
index 89f4f931..7f996e20 100644
--- a/pkg/commands/delete.go
+++ b/pkg/commands/delete.go
@@ -15,7 +15,7 @@
 package commands
 
 import (
-	"log"
+	"errors"
 	"os"
 	"os/exec"
 
@@ -23,15 +23,14 @@ import (
 )
 
 // runCmd is suitable for use with cobra.Command's Run field.
-type runCmd func(*cobra.Command, []string)
+type runCmd func(*cobra.Command, []string) error
 
 // passthru returns a runCmd that simply passes our CLI arguments
 // through to a binary named command.
 func passthru(command string) runCmd {
-	return func(_ *cobra.Command, _ []string) {
+	return func(_ *cobra.Command, _ []string) error {
 		if !isKubectlAvailable() {
-			log.Print("error: kubectl is not available. kubectl must be installed to use ko delete.")
-			return
+			return errors.New("error: kubectl is not available. kubectl must be installed to use ko delete")
 		}
 
 		// Cancel on signals.
@@ -49,9 +48,7 @@ func passthru(command string) runCmd {
 		cmd.Stdin = os.Stdin
 
 		// Run it.
-		if err := cmd.Run(); err != nil {
-			log.Fatalf("error executing %q command with args: %v; %v", command, os.Args[1:], err)
-		}
+		return cmd.Run()
 	}
 }
 
@@ -60,7 +57,7 @@ func addDelete(topLevel *cobra.Command) {
 	topLevel.AddCommand(&cobra.Command{
 		Use:   "delete",
 		Short: `See "kubectl help delete" for detailed usage.`,
-		Run:   passthru("kubectl"),
+		RunE:  passthru("kubectl"),
 		// We ignore unknown flags to avoid importing everything Go exposes
 		// from our commands.
 		FParseErrWhitelist: cobra.FParseErrWhitelist{
diff --git a/pkg/commands/publish.go b/pkg/commands/publish.go
index 236dbb94..370e46c0 100644
--- a/pkg/commands/publish.go
+++ b/pkg/commands/publish.go
@@ -16,7 +16,6 @@ package commands
 
 import (
 	"fmt"
-	"log"
 
 	"github.com/google/ko/pkg/commands/options"
 	"github.com/spf13/cobra"
@@ -57,24 +56,25 @@ func addPublish(topLevel *cobra.Command) {
   # This always preserves import paths.
   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) {
+		RunE: func(_ *cobra.Command, args []string) error {
 			ctx := createCancellableContext()
 			builder, err := makeBuilder(ctx, bo)
 			if err != nil {
-				log.Fatalf("error creating builder: %v", err)
+				return fmt.Errorf("error creating builder: %v", err)
 			}
 			publisher, err := makePublisher(po)
 			if err != nil {
-				log.Fatalf("error creating publisher: %v", err)
+				return fmt.Errorf("error creating publisher: %v", err)
 			}
 			defer publisher.Close()
 			images, err := publishImages(ctx, args, publisher, builder)
 			if err != nil {
-				log.Fatalf("failed to publish images: %v", err)
+				return fmt.Errorf("failed to publish images: %v", err)
 			}
 			for _, img := range images {
 				fmt.Println(img)
 			}
+			return nil
 		},
 	}
 	options.AddPublishArg(publish, po)
diff --git a/pkg/commands/resolve.go b/pkg/commands/resolve.go
index b53c18c6..cfc7cb3f 100644
--- a/pkg/commands/resolve.go
+++ b/pkg/commands/resolve.go
@@ -15,7 +15,7 @@
 package commands
 
 import (
-	"log"
+	"fmt"
 	"os"
 
 	"github.com/google/ko/pkg/commands/options"
@@ -54,20 +54,18 @@ func addResolve(topLevel *cobra.Command) {
   # This always preserves import paths.
   ko resolve --local -f config/`,
 		Args: cobra.NoArgs,
-		Run: func(cmd *cobra.Command, args []string) {
+		RunE: func(cmd *cobra.Command, args []string) error {
 			ctx := createCancellableContext()
 			builder, err := makeBuilder(ctx, bo)
 			if err != nil {
-				log.Fatalf("error creating builder: %v", err)
+				return fmt.Errorf("error creating builder: %v", err)
 			}
 			publisher, err := makePublisher(po)
 			if err != nil {
-				log.Fatalf("error creating publisher: %v", err)
+				return fmt.Errorf("error creating publisher: %v", err)
 			}
 			defer publisher.Close()
-			if err := resolveFilesToWriter(ctx, builder, publisher, fo, so, os.Stdout); err != nil {
-				log.Fatal(err)
-			}
+			return resolveFilesToWriter(ctx, builder, publisher, fo, so, os.Stdout)
 		},
 	}
 	options.AddPublishArg(resolve, po)
diff --git a/pkg/commands/run.go b/pkg/commands/run.go
index 87d85214..dd88d180 100644
--- a/pkg/commands/run.go
+++ b/pkg/commands/run.go
@@ -15,6 +15,8 @@
 package commands
 
 import (
+	"errors"
+	"fmt"
 	"log"
 	"os"
 	"os/exec"
@@ -46,7 +48,7 @@ func addRun(topLevel *cobra.Command) {
 
   # You can also supply args and flags to the command.
   ko run ./cmd/baz -- -v arg1 arg2 --yes`,
-		Run: func(cmd *cobra.Command, args []string) {
+		RunE: func(cmd *cobra.Command, args []string) error {
 			ctx := createCancellableContext()
 
 			// Args after -- are for kubectl, so only consider importPaths before it.
@@ -56,7 +58,7 @@ func addRun(topLevel *cobra.Command) {
 				importPaths = args[:cmd.Flags().ArgsLenAtDash()]
 			}
 			if len(importPaths) == 0 {
-				log.Fatalf("ko run: no importpaths listed")
+				return errors.New("ko run: no importpaths listed")
 			}
 
 			kubectlArgs := []string{}
@@ -67,24 +69,24 @@ func addRun(topLevel *cobra.Command) {
 
 			builder, err := makeBuilder(ctx, bo)
 			if err != nil {
-				log.Fatalf("error creating builder: %v", err)
+				return fmt.Errorf("error creating builder: %v", err)
 			}
 			publisher, err := makePublisher(po)
 			if err != nil {
-				log.Fatalf("error creating publisher: %v", err)
+				return fmt.Errorf("error creating publisher: %v", err)
 			}
 			defer publisher.Close()
 
 			if len(os.Args) < 3 {
-				log.Fatalf("usage: %s run <package>", os.Args[0])
+				return fmt.Errorf("usage: %s run <package>", os.Args[0])
 			}
 			ip := os.Args[2]
 			if strings.HasPrefix(ip, "-") {
-				log.Fatalf("expected first arg to be positional, got %q", ip)
+				return fmt.Errorf("expected first arg to be positional, got %q", ip)
 			}
 			imgs, err := publishImages(ctx, importPaths, publisher, builder)
 			if err != nil {
-				log.Fatalf("failed to publish images: %v", err)
+				return fmt.Errorf("failed to publish images: %v", err)
 			}
 
 			// Usually only one, but this is the simple way to access the
@@ -127,9 +129,10 @@ func addRun(topLevel *cobra.Command) {
 
 				// Run it.
 				if err := kubectlCmd.Run(); err != nil {
-					log.Fatalf("error executing \"kubectl run\": %v", err)
+					return err
 				}
 			}
+			return nil
 		},
 		// We ignore unknown flags to avoid importing everything Go exposes
 		// from our commands.
diff --git a/pkg/publish/kind.go b/pkg/publish/kind.go
index 48f7598c..f20303cb 100644
--- a/pkg/publish/kind.go
+++ b/pkg/publish/kind.go
@@ -46,7 +46,7 @@ func NewKindPublisher(namer Namer, tags []string) Interface {
 }
 
 // Publish implements publish.Interface.
-func (t *kindPublisher) Publish(_ context.Context, br build.Result, s string) (name.Reference, error) {
+func (t *kindPublisher) Publish(ctx context.Context, br build.Result, s string) (name.Reference, error) {
 	s = strings.TrimPrefix(s, build.StrictScheme)
 	// https://github.com/google/go-containerregistry/issues/212
 	s = strings.ToLower(s)
@@ -102,7 +102,7 @@ func (t *kindPublisher) Publish(_ context.Context, br build.Result, s string) (n
 	}
 
 	log.Printf("Loading %v", digestTag)
-	if err := kind.Write(digestTag, img); err != nil {
+	if err := kind.Write(ctx, digestTag, img); err != nil {
 		return nil, err
 	}
 	log.Printf("Loaded %v", digestTag)
@@ -114,7 +114,7 @@ func (t *kindPublisher) Publish(_ context.Context, br build.Result, s string) (n
 			return nil, err
 		}
 
-		if err := kind.Tag(digestTag, tag); err != nil {
+		if err := kind.Tag(ctx, digestTag, tag); err != nil {
 			return nil, err
 		}
 		log.Printf("Added tag %v", tagName)
diff --git a/pkg/publish/kind/write.go b/pkg/publish/kind/write.go
index 0e9bb064..179615e3 100644
--- a/pkg/publish/kind/write.go
+++ b/pkg/publish/kind/write.go
@@ -15,6 +15,7 @@
 package kind
 
 import (
+	"context"
 	"fmt"
 	"io"
 	"os"
@@ -43,9 +44,9 @@ var GetProvider = func() provider {
 }
 
 // Tag adds a tag to an already existent image.
-func Tag(src, dest name.Tag) error {
+func Tag(ctx context.Context, src, dest name.Tag) error {
 	return onEachNode(func(n nodes.Node) error {
-		cmd := n.Command("ctr", "--namespace=k8s.io", "images", "tag", "--force", src.String(), dest.String())
+		cmd := n.CommandContext(ctx, "ctr", "--namespace=k8s.io", "images", "tag", "--force", src.String(), dest.String())
 		if err := cmd.Run(); err != nil {
 			return fmt.Errorf("failed to tag image: %w", err)
 		}
@@ -54,7 +55,7 @@ func Tag(src, dest name.Tag) error {
 }
 
 // Write saves the image into the kind nodes as the given tag.
-func Write(tag name.Tag, img v1.Image) error {
+func Write(ctx context.Context, tag name.Tag, img v1.Image) error {
 	return onEachNode(func(n nodes.Node) error {
 		pr, pw := io.Pipe()
 
@@ -63,7 +64,7 @@ func Write(tag name.Tag, img v1.Image) error {
 			return pw.CloseWithError(tarball.Write(tag, img, pw))
 		})
 
-		cmd := n.Command("ctr", "--namespace=k8s.io", "images", "import", "-").SetStdin(pr)
+		cmd := n.CommandContext(ctx, "ctr", "--namespace=k8s.io", "images", "import", "-").SetStdin(pr)
 		if err := cmd.Run(); err != nil {
 			return fmt.Errorf("failed to load image to node %q: %w", n, err)
 		}


Test patch:
diff --git a/pkg/publish/kind/write_test.go b/pkg/publish/kind/write_test.go
index ec08e936..013e9f1e 100644
--- a/pkg/publish/kind/write_test.go
+++ b/pkg/publish/kind/write_test.go
@@ -30,6 +30,7 @@ import (
 )
 
 func TestWrite(t *testing.T) {
+	ctx := context.Background()
 	img, err := random.Image(1024, 1)
 	if err != nil {
 		t.Fatalf("random.Image() = %v", err)
@@ -46,7 +47,7 @@ func TestWrite(t *testing.T) {
 		return &fakeProvider{nodes: []nodes.Node{n1, n2}}
 	}
 
-	if err := Write(tag, img); err != nil {
+	if err := Write(ctx, tag, img); err != nil {
 		t.Fatalf("Write() = %v", err)
 	}
 
@@ -64,6 +65,7 @@ func TestWrite(t *testing.T) {
 }
 
 func TestTag(t *testing.T) {
+	ctx := context.Background()
 	oldTag, err := name.NewTag("kind.local/test:test")
 	if err != nil {
 		t.Fatalf("name.NewTag() = %v", err)
@@ -80,7 +82,7 @@ func TestTag(t *testing.T) {
 		return &fakeProvider{nodes: []nodes.Node{n1, n2}}
 	}
 
-	if err := Tag(oldTag, newTag); err != nil {
+	if err := Tag(ctx, oldTag, newTag); err != nil {
 		t.Fatalf("Tag() = %v", err)
 	}
 
@@ -98,6 +100,7 @@ func TestTag(t *testing.T) {
 }
 
 func TestFailWithNoNodes(t *testing.T) {
+	ctx := context.Background()
 	img, err := random.Image(1024, 1)
 	if err != nil {
 		panic(err)
@@ -117,15 +120,16 @@ func TestFailWithNoNodes(t *testing.T) {
 		return &fakeProvider{}
 	}
 
-	if err := Write(newTag, img); err == nil {
+	if err := Write(ctx, newTag, img); err == nil {
 		t.Fatal("Write() = nil, wanted an error")
 	}
-	if err := Tag(oldTag, newTag); err == nil {
+	if err := Tag(ctx, oldTag, newTag); err == nil {
 		t.Fatal("Tag() = nil, wanted an error")
 	}
 }
 
 func TestFailCommands(t *testing.T) {
+	ctx := context.Background()
 	img, err := random.Image(1024, 1)
 	if err != nil {
 		panic(err)
@@ -149,10 +153,10 @@ func TestFailCommands(t *testing.T) {
 		return &fakeProvider{nodes: []nodes.Node{n1, n2}}
 	}
 
-	if err := Write(newTag, img); !errors.Is(err, errTest) {
+	if err := Write(ctx, newTag, img); !errors.Is(err, errTest) {
 		t.Fatalf("Write() = %v, want %v", err, errTest)
 	}
-	if err := Tag(oldTag, newTag); !errors.Is(err, errTest) {
+	if err := Tag(ctx, oldTag, newTag); !errors.Is(err, errTest) {
 		t.Fatalf("Write() = %v, want %v", err, errTest)
 	}
 }
@@ -171,7 +175,7 @@ type fakeNode struct {
 	err  error
 }
 
-func (f *fakeNode) Command(cmd string, args ...string) exec.Cmd {
+func (f *fakeNode) CommandContext(ctx context.Context, cmd string, args ...string) exec.Cmd {
 	command := &fakeCmd{
 		cmd: strings.Join(append([]string{cmd}, args...), " "),
 		err: f.err,
@@ -185,10 +189,10 @@ func (f *fakeNode) String() string {
 }
 
 // The following functions are not used by our code at all.
-func (f *fakeNode) CommandContext(context.Context, string, ...string) exec.Cmd { return nil }
-func (f *fakeNode) Role() (string, error)                                      { return "", nil }
-func (f *fakeNode) IP() (ipv4 string, ipv6 string, err error)                  { return "", "", nil }
-func (f *fakeNode) SerialLogs(writer io.Writer) error                          { return nil }
+func (f *fakeNode) Command(string, ...string) exec.Cmd        { return nil }
+func (f *fakeNode) Role() (string, error)                     { return "", nil }
+func (f *fakeNode) IP() (ipv4 string, ipv6 string, err error) { return "", "", nil }
+func (f *fakeNode) SerialLogs(writer io.Writer) error         { return nil }
 
 type fakeCmd struct {
 	cmd   string