Skip to content

Commit

Permalink
feat: Add KO_GO_BIN env var
Browse files Browse the repository at this point in the history
Closes: #926
Signed-off-by: Michael Gasch <15986659+embano1@users.noreply.github.com>
  • Loading branch information
embano1 committed Jan 19, 2023
1 parent 18d3a82 commit c512f73
Show file tree
Hide file tree
Showing 4 changed files with 205 additions and 7 deletions.
18 changes: 14 additions & 4 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@

## Basic Configuration

Aside from `KO_DOCKER_REPO`, you can configure `ko`'s behavior using a
`.ko.yaml` file. The location of this file can be overridden with
`KO_CONFIG_PATH`.
Aside from certain environment variables (see [below](#environment-variables-advanced)) like `KO_DOCKER_REPO`, you can
configure `ko`'s behavior using a `.ko.yaml` file. The location of this file can be overridden with `KO_CONFIG_PATH`.

### Overriding Base Images

Expand Down Expand Up @@ -83,7 +82,6 @@ The `ldflags` default value is `[]`.
only the `env`, `flags` and `ldflags` fields are currently supported. Also, the
templating support is currently limited to using environment variables only.


### Setting default platforms

By default, `ko` builds images based on the platform it runs on. If your target platform differs from your build platform you can specify the build platform:
Expand All @@ -106,6 +104,18 @@ You can also use the `KO_DEFAULTPLATFORMS` environment variable to set the defau
KO_DEFAULTPLATFORMS=linux/arm64,linux/amd64
```

### Environment Variables (advanced)

For ease of use, backward compatibility and advanced use cases, `ko` supports the following environment variables to
influence the build process.

| Variable | Default Value | Description |
|------------------|--------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `KO_DOCKER_REPO` | (not set) | Container repository where to push images built with `ko` (required) |
| `KO_DATA_PATH` | `/var/run/ko` (`C:\var\run\ko` on Windows) | By convention, any contents of a directory named `<importpath>/kodata/` will be bundled into the image, and the path where it's available in the image will be identified by the environment variable `KO_DATA_PATH` (optional). |
| `KO_GO_PATH` | `go` | `go` binary to use for builds, relative or absolute path, otherwise looked up via $PATH (optional) |
| `KO_CONFIG_PATH` | `./ko.yaml` | Path to `ko` configuration file (optional) |
| `KOCACHE` | (not set) | This tells `ko` to store a local mapping between the `go build` inputs to the image layer that they produce, so `go build` can be skipped entirely if the layer is already present in the image registry (optional). |

## Naming Images

Expand Down
7 changes: 6 additions & 1 deletion pkg/build/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,12 @@ func (c *layerCache) readBuildToDiff(file string) (buildIDToDiffID, error) {
}

func getBuildID(ctx context.Context, file string) (string, error) {
cmd := exec.CommandContext(ctx, "go", "tool", "buildid", file)
gobin := defaultGoBin
if env := os.Getenv(goBinPathEnv); env != "" {
gobin = env
}

cmd := exec.CommandContext(ctx, gobin, "tool", "buildid", file)
var output bytes.Buffer
cmd.Stderr = &output
cmd.Stdout = &output
Expand Down
17 changes: 15 additions & 2 deletions pkg/build/gobuild.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ import (

const (
defaultAppFilename = "ko-app"

defaultGoBin = "go" // defaults to first go binary found in PATH
goBinPathEnv = "KO_GO_PATH" // env lookup for optional relative or full go binary path
)

// GetBase takes an importpath and returns a base image reference and base image (or index).
Expand Down Expand Up @@ -285,7 +288,12 @@ func build(ctx context.Context, ip string, dir string, platform v1.Platform, con

args = append(args, "-o", file)
args = append(args, ip)
cmd := exec.CommandContext(ctx, "go", args...)

gobin := defaultGoBin
if bin := os.Getenv(goBinPathEnv); bin != "" {
gobin = bin
}
cmd := exec.CommandContext(ctx, gobin, args...)
cmd.Dir = dir
cmd.Env = env

Expand All @@ -305,10 +313,15 @@ func build(ctx context.Context, ip string, dir string, platform v1.Platform, con
}

func goversionm(ctx context.Context, file string, appPath string, appFileName string, se oci.SignedEntity, dir string) ([]byte, types.MediaType, error) {
gobin := defaultGoBin
if env := os.Getenv(goBinPathEnv); env != "" {
gobin = env
}

switch se.(type) {
case oci.SignedImage:
sbom := bytes.NewBuffer(nil)
cmd := exec.CommandContext(ctx, "go", "version", "-m", file)
cmd := exec.CommandContext(ctx, gobin, "version", "-m", file)
cmd.Stdout = sbom
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
Expand Down
170 changes: 170 additions & 0 deletions pkg/commands/deps.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
// Copyright 2021 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 commands

import (
"archive/tar"
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path"
"path/filepath"

"github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1/mutate"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/google/ko/internal/sbom"
"github.com/sigstore/cosign/v2/pkg/oci/signed"
"github.com/spf13/cobra"
)

const (
defaultGoBin = "go" // defaults to first go binary found in PATH
goBinPathEnv = "KO_GO_PATH" // env lookup for optional relative or full go binary path
)

// addDeps augments our CLI surface with deps.
func addDeps(topLevel *cobra.Command) {
var sbomType string
deps := &cobra.Command{
Use: "deps IMAGE",
Short: "Print Go module dependency information about the ko-built binary in the image",
Long: `This sub-command finds and extracts the executable binary in the image, assuming it was built by ko, and prints information about the Go module dependencies of that executable, as reported by "go version -m".
If the image was not built using ko, or if it was built without embedding dependency information, this command will fail.`,
Example: `
# Fetch and extract Go dependency information from an image:
ko deps docker.io/my-user/my-image:v3`,
Args: cobra.ExactArgs(1),
Deprecated: "SBOMs are generated and uploaded by default; this command will be removed in a future release.",
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()

switch sbomType {
case "cyclonedx", "spdx", "go.version-m":
default:
return fmt.Errorf("invalid sbom type %q: must be spdx, cyclonedx or go.version-m", sbomType)
}

ref, err := name.ParseReference(args[0])
if err != nil {
return err
}

img, err := remote.Image(ref,
remote.WithContext(ctx),
remote.WithAuthFromKeychain(keychain),
remote.WithUserAgent(ua()))
if err != nil {
return err
}

cfg, err := img.ConfigFile()
if err != nil {
return err
}
ep := cfg.Config.Entrypoint
if len(ep) != 1 {
return fmt.Errorf("unexpected entrypoint: %s", ep)
}
bin := ep[0]

rc := mutate.Extract(img)
defer rc.Close()
tr := tar.NewReader(rc)
for {
// Stop reading if the context is cancelled.
select {
case <-ctx.Done():
return ctx.Err()
default:
// keep reading.
}
h, err := tr.Next()
if errors.Is(err, io.EOF) {
return fmt.Errorf("no ko-built executable named %q found", bin)
}
if err != nil {
return err
}

if h.Typeflag != tar.TypeReg {
continue
}
if h.Name != bin {
continue
}

tmp, err := ioutil.TempFile("", filepath.Base(filepath.Clean(h.Name)))
if err != nil {
return err
}
n := tmp.Name()
defer os.RemoveAll(n) // best effort: remove tmp file afterwards.
defer tmp.Close() // close it first.
// io.LimitReader to appease gosec...
if _, err := io.Copy(tmp, io.LimitReader(tr, h.Size)); err != nil {
return err
}
if err := os.Chmod(n, os.FileMode(h.Mode)); err != nil {
return err
}

gobin := defaultGoBin
if env := os.Getenv(goBinPathEnv); env != "" {
gobin = env
}
cmd := exec.CommandContext(ctx, gobin, "version", "-m", n)
var buf bytes.Buffer
cmd.Stdout = &buf
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return err
}
// In order to get deterministics SBOMs replace
// our randomized file name with the path the
// app will get inside of the container.
mod := bytes.Replace(buf.Bytes(),
[]byte(n),
[]byte(path.Join("/ko-app", filepath.Base(filepath.Clean(h.Name)))),
1)
switch sbomType {
case "spdx":
b, err := sbom.GenerateImageSPDX(Version, mod, signed.Image(img))
if err != nil {
return err
}
io.Copy(os.Stdout, bytes.NewReader(b))
case "cyclonedx":
b, err := sbom.GenerateImageCycloneDX(mod)
if err != nil {
return err
}
io.Copy(os.Stdout, bytes.NewReader(b))
case "go.version-m":
io.Copy(os.Stdout, bytes.NewReader(mod))
}
return nil
}
// unreachable
},
}
deps.Flags().StringVar(&sbomType, "sbom", "spdx", "Format for SBOM output (supports: spdx, cyclonedx, go.version-m).")
topLevel.AddCommand(deps)
}

0 comments on commit c512f73

Please sign in to comment.