Skip to content

Commit

Permalink
Use working directory and build config dir
Browse files Browse the repository at this point in the history
Use the working directory from `BuildOptions` to load `.ko.yaml`.

Also, use the `dir` build config field to load package information,
instead of assuming that `go.mod` is in the current working directory.

This removes the `init()` function from `./pkg/commands/config.go`.

And avoids the global viper instance, which caused some Heisenbugs (and
associated hair loss).

Fixes: ko-build#422, ko-build#424
  • Loading branch information
halvards committed Aug 23, 2021
1 parent 040b7c7 commit 2dc2792
Show file tree
Hide file tree
Showing 7 changed files with 86 additions and 32 deletions.
73 changes: 41 additions & 32 deletions pkg/commands/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ import (
"golang.org/x/tools/go/packages"
)

const (
// configDefaultBaseImage is the default base image if not specified in .ko.yaml.
configDefaultBaseImage = "gcr.io/distroless/static:nonroot"
)

var (
defaultBaseImage string
baseImageOverrides map[string]string
Expand Down Expand Up @@ -168,8 +173,8 @@ func createCancellableContext() context.Context {
return ctx
}

func createBuildConfigs(baseDir string, configs []build.Config) map[string]build.Config {
buildConfigs = make(map[string]build.Config)
func createBuildConfigs(workingDirectory string, configs []build.Config) map[string]build.Config {
buildConfigsByImportPath := make(map[string]build.Config)
for i, config := range configs {
// Make sure to behave like GoReleaser by defaulting to the current
// directory in case the build or main field is not set, check
Expand All @@ -181,74 +186,78 @@ func createBuildConfigs(baseDir string, configs []build.Config) map[string]build
config.Main = "."
}

// To behave like GoReleaser, check whether the configured path points to a
// source file, and if so, just use the directory it is in
var path string
if fi, err := os.Stat(filepath.Join(baseDir, config.Dir, config.Main)); err == nil && fi.Mode().IsRegular() {
path = filepath.Dir(filepath.Join(config.Dir, config.Main))
// baseDir is the directory where `go list` will be run to look for package information
baseDir := filepath.Join(workingDirectory, config.Dir)

} else {
path = filepath.Join(config.Dir, config.Main)
// To behave like GoReleaser, check whether the configured `main` config value points to a
// source file, and if so, just use the directory it is in
path := config.Main
if fi, err := os.Stat(filepath.Join(baseDir, config.Main)); err == nil && fi.Mode().IsRegular() {
path = filepath.Dir(config.Main)
}

// By default, paths configured in the builds section are considered
// local import paths, therefore add a "./" equivalent as a prefix to
// the constructured import path
importPath := fmt.Sprint(".", string(filepath.Separator), path)
localImportPath := fmt.Sprint(".", string(filepath.Separator), path)

pkgs, err := packages.Load(&packages.Config{Mode: packages.NeedName, Dir: baseDir}, importPath)
pkgs, err := packages.Load(&packages.Config{Mode: packages.NeedName, Dir: baseDir}, localImportPath)
if err != nil {
log.Fatalf("'builds': entry #%d does not contain a usuable path (%s): %v", i, importPath, err)
log.Fatalf("'builds': entry #%d does not contain a valid local import path (%s) for directory (%s): %v", i, localImportPath, baseDir, err)
}

if len(pkgs) != 1 {
log.Fatalf("'builds': entry #%d results in %d local packages, only 1 is expected", i, len(pkgs))
}

importPath = pkgs[0].PkgPath
buildConfigs[importPath] = config
importPath := pkgs[0].PkgPath
buildConfigsByImportPath[importPath] = config
}

return buildConfigs
return buildConfigsByImportPath
}

func init() {
// loadConfig reads build configuration from defaults, environment variables, and the `.ko.yaml` config file.
func loadConfig(workingDirectory string) {
v := viper.New()
if workingDirectory == "" {
workingDirectory = "."
}
// If omitted, use this base image.
viper.SetDefault("defaultBaseImage", "gcr.io/distroless/static:nonroot")
viper.SetConfigName(".ko") // .yaml is implicit
viper.SetEnvPrefix("KO")
viper.AutomaticEnv()
v.SetDefault("defaultBaseImage", configDefaultBaseImage)
v.SetConfigName(".ko") // .yaml is implicit
v.SetEnvPrefix("KO")
v.AutomaticEnv()

if override := os.Getenv("KO_CONFIG_PATH"); override != "" {
viper.AddConfigPath(override)
v.AddConfigPath(override)
}

viper.AddConfigPath("./")
v.AddConfigPath(workingDirectory)

if err := viper.ReadInConfig(); err != nil {
if err := v.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
log.Fatalf("error reading config file: %v", err)
}
}

ref := viper.GetString("defaultBaseImage")
ref := v.GetString("defaultBaseImage")
if _, err := name.ParseReference(ref); err != nil {
log.Fatalf("'defaultBaseImage': error parsing %q as image reference: %v", ref, err)
}
defaultBaseImage = ref

baseImageOverrides = make(map[string]string)
overrides := viper.GetStringMapString("baseImageOverrides")
for k, v := range overrides {
if _, err := name.ParseReference(v); err != nil {
log.Fatalf("'baseImageOverrides': error parsing %q as image reference: %v", v, err)
overrides := v.GetStringMapString("baseImageOverrides")
for key, value := range overrides {
if _, err := name.ParseReference(value); err != nil {
log.Fatalf("'baseImageOverrides': error parsing %q as image reference: %v", value, err)
}
baseImageOverrides[k] = v
baseImageOverrides[key] = value
}

var builds []build.Config
if err := viper.UnmarshalKey("builds", &builds); err != nil {
if err := v.UnmarshalKey("builds", &builds); err != nil {
log.Fatalf("configuration section 'builds' cannot be parsed")
}
buildConfigs = createBuildConfigs(".", builds)
buildConfigs = createBuildConfigs(workingDirectory, builds)
}
31 changes: 31 additions & 0 deletions pkg/commands/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,37 @@ func TestOverrideDefaultBaseImageUsingBuildOption(t *testing.T) {
}
}

// TestDefaultBaseImage is a canary-type test for ensuring that config has been read when creating a builder.
func TestDefaultBaseImage(t *testing.T) {
_, err := NewBuilder(context.Background(), &options.BuildOptions{
WorkingDirectory: "testdata/config",
})
if err != nil {
t.Fatal(err)
}
wantDefaultBaseImage := "gcr.io/distroless/base:nonroot" // matches value in ./testdata/.ko.yaml
if defaultBaseImage != wantDefaultBaseImage {
t.Fatalf("wanted defaultBaseImage %s, got %s", wantDefaultBaseImage, defaultBaseImage)
}
}

func TestBuildConfigWithWorkingDirectoryAndDirAndMain(t *testing.T) {
_, err := NewBuilder(context.Background(), &options.BuildOptions{
WorkingDirectory: "testdata/paths",
})
if err != nil {
t.Fatalf("NewBuilder(): %+v", err)
}

if len(buildConfigs) != 1 {
t.Fatalf("expected 1 build config, got %d", len(buildConfigs))
}
expectedImportPath := "example.com/testapp/cmd/foo" // module from app/go.mod + `main` from .ko.yaml
if _, exists := buildConfigs[expectedImportPath]; !exists {
t.Fatalf("expected build config for import path [%s], got %+v", expectedImportPath, buildConfigs)
}
}

func TestCreateBuildConfigs(t *testing.T) {
compare := func(expected string, actual string) {
if expected != actual {
Expand Down
1 change: 1 addition & 0 deletions pkg/commands/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ func NewBuilder(ctx context.Context, bo *options.BuildOptions) (build.Interface,
}

func makeBuilder(ctx context.Context, bo *options.BuildOptions) (*build.Caching, error) {
loadConfig(bo.WorkingDirectory)
opt, err := gobuildOptions(bo)
if err != nil {
return nil, fmt.Errorf("error setting up builder options: %v", err)
Expand Down
1 change: 1 addition & 0 deletions pkg/commands/testdata/config/.ko.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
defaultBaseImage: gcr.io/distroless/base:nonroot
4 changes: 4 additions & 0 deletions pkg/commands/testdata/paths/.ko.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
builds:
- id: app-with-main-package-in-different-directory-to-go-mod-and-ko-yaml
dir: ./app
main: ./cmd/foo
5 changes: 5 additions & 0 deletions pkg/commands/testdata/paths/app/cmd/foo/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package main

func main() {
println("cmd/foo")
}
3 changes: 3 additions & 0 deletions pkg/commands/testdata/paths/app/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module example.com/testapp

go 1.15

0 comments on commit 2dc2792

Please sign in to comment.