Skip to content

Commit

Permalink
feat(ko): Init support for the ko builder
Browse files Browse the repository at this point in the history
Adds the ko builder as an option when running `skaffold init` in a
directory containing Go code.

The ko builder will show up as an option for directories containing a
`go.mod` file.

The feature can be disabled by adding the `--XXenableKoInit=false` flag.

Tracking: GoogleContainerTools#7131
  • Loading branch information
halvards committed Jul 21, 2022
1 parent 12d9a22 commit c0aeab2
Show file tree
Hide file tree
Showing 13 changed files with 196 additions and 61 deletions.
5 changes: 4 additions & 1 deletion cmd/skaffold/app/cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ var (
analyze bool
enableJibInit bool
enableJibGradleInit bool
enableKoInit bool
enableBuildpacksInit bool
enableNewInitFormat bool
enableManifestGeneration bool
Expand All @@ -65,6 +66,7 @@ func NewCmdInit() *cobra.Command {
{Value: &enableNewInitFormat, Name: "XXenableNewInitFormat", DefValue: false, Usage: "", Hidden: true, IsEnum: true},
{Value: &enableJibInit, Name: "XXenableJibInit", DefValue: true, Usage: "", Hidden: true, IsEnum: true},
{Value: &enableJibGradleInit, Name: "XXenableJibGradleInit", DefValue: false, Usage: "", Hidden: true, IsEnum: true},
{Value: &enableKoInit, Name: "XXenableKoInit", DefValue: true, Usage: "", Hidden: true, IsEnum: true},
{Value: &enableBuildpacksInit, Name: "XXenableBuildpacksInit", DefValue: true, Usage: "", Hidden: true, IsEnum: true},
{Value: &buildpacksBuilder, Name: "XXdefaultBuildpacksBuilder", DefValue: "gcr.io/buildpacks/builder:v1", Usage: "", Hidden: true},
{Value: &enableManifestGeneration, Name: "generate-manifests", DefValue: false, Usage: "Allows skaffold to try and generate basic kubernetes resources to get your project started", IsEnum: true},
Expand All @@ -85,8 +87,9 @@ func doInit(ctx context.Context, out io.Writer) error {
Analyze: analyze,
EnableJibInit: enableJibInit,
EnableJibGradleInit: enableJibGradleInit,
EnableKoInit: enableKoInit,
EnableBuildpacksInit: enableBuildpacksInit,
EnableNewInitFormat: enableNewInitFormat || enableBuildpacksInit || enableJibInit,
EnableNewInitFormat: enableNewInitFormat || enableBuildpacksInit || enableJibInit || enableKoInit,
EnableManifestGeneration: enableManifestGeneration,
Opts: opts,
MaxFileSize: maxFileSize,
Expand Down
38 changes: 36 additions & 2 deletions cmd/skaffold/app/cmd/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ func TestFlagsToConfigVersion(t *testing.T) {
Analyze: false,
EnableJibInit: true,
EnableJibGradleInit: false,
EnableKoInit: true,
EnableBuildpacksInit: true,
EnableNewInitFormat: true,
BuildpacksBuilder: "gcr.io/buildpacks/builder:v1",
Expand All @@ -77,6 +78,7 @@ func TestFlagsToConfigVersion(t *testing.T) {
"--analyze",
"--XXenableJibInit",
"--XXenableJibGradleInit",
"--XXenableKoInit=false",
"--XXenableBuildpacksInit",
"--XXenableNewInitFormat",
"--XXdefaultBuildpacksBuilder", "buildpacks/builder",
Expand All @@ -91,6 +93,7 @@ func TestFlagsToConfigVersion(t *testing.T) {
Analyze: true,
EnableJibInit: true,
EnableJibGradleInit: true,
EnableKoInit: false,
EnableBuildpacksInit: true,
EnableNewInitFormat: true,
BuildpacksBuilder: "buildpacks/builder",
Expand All @@ -104,6 +107,8 @@ func TestFlagsToConfigVersion(t *testing.T) {
args: []string{
"init",
"--XXenableJibInit",
"--XXenableKoInit=false",
"--XXenableBuildpacksInit=false",
},
expectedConfig: config.Config{
ComposeFile: "",
Expand All @@ -114,7 +119,33 @@ func TestFlagsToConfigVersion(t *testing.T) {
Force: false,
Analyze: false,
EnableJibInit: true,
EnableBuildpacksInit: true,
EnableKoInit: false,
EnableBuildpacksInit: false,
EnableNewInitFormat: true,
BuildpacksBuilder: "gcr.io/buildpacks/builder:v1",
Opts: opts,
MaxFileSize: maxFileSize,
},
},
{
name: "enableKoInit implies enableNewInitFormat",
args: []string{
"init",
"--XXenableJibInit=false",
"--XXenableKoInit",
"--XXenableBuildpacksInit=false",
},
expectedConfig: config.Config{
ComposeFile: "",
CliArtifacts: []string{},
CliKubernetesManifests: []string{},
SkipBuild: false,
SkipDeploy: false,
Force: false,
Analyze: false,
EnableJibInit: false,
EnableKoInit: true,
EnableBuildpacksInit: false,
EnableNewInitFormat: true,
BuildpacksBuilder: "gcr.io/buildpacks/builder:v1",
Opts: opts,
Expand All @@ -125,6 +156,8 @@ func TestFlagsToConfigVersion(t *testing.T) {
name: "enableBuildpackInit implies enableNewInitFormat",
args: []string{
"init",
"--XXenableJibInit=false",
"--XXenableKoInit=false",
"--XXenableBuildpacksInit",
},
expectedConfig: config.Config{
Expand All @@ -135,7 +168,8 @@ func TestFlagsToConfigVersion(t *testing.T) {
SkipDeploy: false,
Force: false,
Analyze: false,
EnableJibInit: true,
EnableJibInit: false,
EnableKoInit: false,
EnableBuildpacksInit: true,
EnableNewInitFormat: true,
BuildpacksBuilder: "gcr.io/buildpacks/builder:v1",
Expand Down
28 changes: 4 additions & 24 deletions docs-v2/content/en/docs/pipeline-stages/builders/ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,30 +254,10 @@ Useful tips for existing `ko` users:
specify these fields in `.ko.yaml`, you do not need to repeat them in
`skaffold.yaml`.

- Future Skaffold releases will include support for generating `skaffold.yaml`
files by examining an existing code base. For now, you can generate a starter
`skaffold.yaml` file by searching your existing manifests for image
references starting with `ko://` by using this snippet:

```shell
cat << EOF > skaffold.yaml
apiVersion: skaffold/v2beta26
kind: Config
build:
artifacts:
EOF
grep -rho "ko://$(go list -m)[^\"]*" ./config/ | sort | uniq | xargs -Iimg echo -e " - image: img\n ko: {}" >> skaffold.yaml
cat << EOF >> skaffold.yaml
local:
concurrency: 0
deploy:
kubectl:
manifests:
- ./config/**
EOF
```

Replace `./config/` with the path to your Kubernetes manifest files.
- You can generate `skaffold.yaml` files by examining an existing code base,
using the command
[`skaffold init`]({{< relref "/docs/pipeline-stages/init" >}}).
Select the Ko builder for your images when prompted.

### `ko` commands and workflows in Skaffold

Expand Down
5 changes: 3 additions & 2 deletions docs-v2/content/en/docs/pipeline-stages/init.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ and [deploy](#deploy-config-initialization) config.

1. [Docker]({{<relref "/docs/pipeline-stages/builders/docker">}})
2. [Jib]({{<relref "/docs/pipeline-stages/builders/jib">}})
2. [Buildpacks]({{<relref "/docs/pipeline-stages/builders/buildpacks">}})
3. [Ko]({{<relref "/docs/pipeline-stages/builders/ko">}})
4. [Buildpacks]({{<relref "/docs/pipeline-stages/builders/buildpacks">}})

`skaffold init` walks your project directory and looks for any build configuration files such as `Dockerfile`,
`build.gradle/pom.xml`, `package.json`, `requirements.txt` or `go.mod`. `init` skips files that are larger
Expand Down Expand Up @@ -108,7 +109,7 @@ If bringing a project to skaffold that has no kubernetes manifests yet, it may b
## `--force` Flag
`skaffold init` allows for use of a `--force` flag, which removes the prompts from vanilla `skaffold init`, and allows skaffold to make a best effort attempt to automatically generate a config for your project.

In a situation where one image is detected, but multiple possible builders are detected, skaffold will choose a builder as follows: Docker > Jib > Bazel > Buildpacks.
In a situation where one image is detected, but multiple possible builders are detected, skaffold will choose a builder as follows: Docker > Jib > Ko > Bazel > Buildpacks.

*Note: This feature is still under development, and doesn't currently support use cases such as multiple images in a project.*

Expand Down
28 changes: 4 additions & 24 deletions docs/content/en/docs/pipeline-stages/builders/ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,30 +254,10 @@ Useful tips for existing `ko` users:
specify these fields in `.ko.yaml`, you do not need to repeat them in
`skaffold.yaml`.

- Future Skaffold releases will include support for generating `skaffold.yaml`
files by examining an existing code base. For now, you can generate a starter
`skaffold.yaml` file by searching your existing manifests for image
references starting with `ko://` by using this snippet:

```shell
cat << EOF > skaffold.yaml
apiVersion: skaffold/v2beta26
kind: Config
build:
artifacts:
EOF
grep -rho "ko://$(go list -m)[^\"]*" ./config/ | sort | uniq | xargs -Iimg echo -e " - image: img\n ko: {}" >> skaffold.yaml
cat << EOF >> skaffold.yaml
local:
concurrency: 0
deploy:
kubectl:
manifests:
- ./config/**
EOF
```

Replace `./config/` with the path to your Kubernetes manifest files.
- You can generate `skaffold.yaml` files by examining an existing code base,
using the command
[`skaffold init`]({{< relref "/docs/pipeline-stages/init" >}}).
Select the Ko builder for your images when prompted.

### `ko` commands and workflows in Skaffold

Expand Down
5 changes: 3 additions & 2 deletions docs/content/en/docs/pipeline-stages/init.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ and [deploy](#deploy-config-initialization) config.

1. [Docker]({{<relref "/docs/pipeline-stages/builders/docker">}})
2. [Jib]({{<relref "/docs/pipeline-stages/builders/jib">}})
2. [Buildpacks]({{<relref "/docs/pipeline-stages/builders/buildpacks">}})
3. [Ko]({{<relref "/docs/pipeline-stages/builders/ko">}})
4. [Buildpacks]({{<relref "/docs/pipeline-stages/builders/buildpacks">}})

`skaffold init` walks your project directory and looks for any build configuration files such as `Dockerfile`,
`build.gradle/pom.xml`, `package.json`, `requirements.txt` or `go.mod`. `init` skips files that are larger
Expand Down Expand Up @@ -108,7 +109,7 @@ If bringing a project to skaffold that has no kubernetes manifests yet, it may b
## `--force` Flag
`skaffold init` allows for use of a `--force` flag, which removes the prompts from vanilla `skaffold init`, and allows skaffold to make a best effort attempt to automatically generate a config for your project.

In a situation where one image is detected, but multiple possible builders are detected, skaffold will choose a builder as follows: Docker > Jib > Bazel > Buildpacks.
In a situation where one image is detected, but multiple possible builders are detected, skaffold will choose a builder as follows: Docker > Jib > Ko > Bazel > Buildpacks.

*Note: This feature is still under development, and doesn't currently support use cases such as multiple images in a project.*

Expand Down
70 changes: 70 additions & 0 deletions pkg/skaffold/build/ko/init/init.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
Copyright 2022 The Skaffold Authors
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 init

import (
"fmt"
"path/filepath"

"github.com/GoogleContainerTools/skaffold/pkg/skaffold/initializer/build"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
)

// ArtifactConfig holds information about a Ko project
type ArtifactConfig struct {
File string `json:"path,omitempty"`
}

var _ build.InitBuilder = &ArtifactConfig{}

// Validate checks if the file is a Go module file.
func Validate(path string) bool {
return filepath.Base(path) == "go.mod"
}

// Name returns the name of the builder.
func (c ArtifactConfig) Name() string {
return "Ko"
}

// Describe returns the initBuilder's string representation.
// This representation is used when prompting the user to choose a builder.
func (c ArtifactConfig) Describe() string {
return fmt.Sprintf("%s (%s)", c.Name(), c.File)
}

// ArtifactType returns a definition of the artifact to be built.
func (c ArtifactConfig) ArtifactType(_ string) latest.ArtifactType {
return latest.ArtifactType{
KoArtifact: &latest.KoArtifact{
Dependencies: &latest.KoDependencies{
Paths: []string{"**/*.go", "go.*"},
},
},
}
}

// ConfiguredImage returns the target image configured by the builder, or empty string if no image is configured.
func (c ArtifactConfig) ConfiguredImage() string {
// Target image is not configured in Ko.
return ""
}

// Path returns the path to the build definition.
func (c ArtifactConfig) Path() string {
return c.File
}
52 changes: 52 additions & 0 deletions pkg/skaffold/build/ko/init/init_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
Copyright 2022 The Skaffold Authors
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 init

import (
"testing"

"github.com/GoogleContainerTools/skaffold/testutil"
)

func TestValidate(t *testing.T) {
tests := []struct {
description string
path string
expect bool
}{
{
description: "go.mod in current directory",
path: "go.mod",
expect: true,
},
{
description: "go.mod in subdirectory",
path: "foo/go.mod",
expect: true,
},
{
description: "not go.mod",
path: "Gopkg.toml",
expect: false,
},
}
for _, test := range tests {
testutil.Run(t, test.description, func(t *testutil.T) {
t.CheckDeepEqual(test.expect, Validate(test.path))
})
}
}
1 change: 1 addition & 0 deletions pkg/skaffold/initializer/analyze/analyze.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ func NewAnalyzer(c config.Config) *ProjectAnalysis {
findBuilders: !c.SkipBuild,
enableJibInit: c.EnableJibInit,
enableJibGradleInit: c.EnableJibGradleInit,
enableKoInit: c.EnableKoInit,
enableBuildpacksInit: c.EnableBuildpacksInit,
buildpacksBuilder: c.BuildpacksBuilder,
},
Expand Down
10 changes: 10 additions & 0 deletions pkg/skaffold/initializer/analyze/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (

"github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/buildpacks"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/jib"
koinit "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/ko/init"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/initializer/build"
)
Expand All @@ -31,6 +32,7 @@ type builderAnalyzer struct {
directoryAnalyzer
enableJibInit bool
enableJibGradleInit bool
enableKoInit bool
enableBuildpacksInit bool
findBuilders bool
buildpacksBuilder string
Expand Down Expand Up @@ -86,6 +88,14 @@ func (a *builderAnalyzer) detectBuilders(ctx context.Context, path string, detec
}
}

if a.enableKoInit {
if koinit.Validate(path) {
results = append(results, koinit.ArtifactConfig{
File: path,
})
}
}

// TODO: Remove backwards compatibility if statement (not entire block)
if a.enableBuildpacksInit {
// Check for buildpacks
Expand Down
Loading

0 comments on commit c0aeab2

Please sign in to comment.