forked from GoogleContainerTools/skaffold
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This adds the `Build()` method for building artifacts using ko. It supports both publishing the resulting image to a registry, and sideloading it to the local Docker daemon. The `temporary.go` file contains the structs intended to be added to the schema. This implementation is still missing the following features: - integration test - dependencies (for file watching) - insecure registries - debug mode - support for `go` flags and environment variables (waiting on ko-build/ko#340) - actually plumbing the builder into the Skaffold CLI and API :-) Tracking: GoogleContainerTools#6041
- Loading branch information
Showing
10 changed files
with
613 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
/* | ||
Copyright 2021 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 ko | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"io" | ||
"strings" | ||
|
||
"github.com/google/go-containerregistry/pkg/name" | ||
"github.com/google/ko/pkg/build" | ||
"github.com/google/ko/pkg/publish" | ||
|
||
// latestV1 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest/v1" | ||
latestV1 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/ko/schema" | ||
) | ||
|
||
// Build an artifact using ko | ||
func (b *Builder) Build(ctx context.Context, out io.Writer, a *latestV1.Artifact, ref string) (string, error) { | ||
koBuilder, err := b.newKoBuilder(ctx, a) | ||
if err != nil { | ||
return "", fmt.Errorf("error creating ko builder: %w", err) | ||
} | ||
|
||
koPublisher, err := b.newKoPublisher(ref) | ||
if err != nil { | ||
return "", fmt.Errorf("error creating ko publisher: %w", err) | ||
} | ||
defer koPublisher.Close() | ||
|
||
imageRef, err := b.buildAndPublish(ctx, a.ImageName, koBuilder, koPublisher) | ||
if err != nil { | ||
return "", fmt.Errorf("could not build and publish ko image %q: %w", a.ImageName, err) | ||
} | ||
fmt.Fprintln(out, imageRef.Name()) | ||
|
||
return b.getImageIdentifier(ctx, imageRef, ref) | ||
} | ||
|
||
// buildAndPublish the image using the ko builder and publisher. | ||
func (b *Builder) buildAndPublish(ctx context.Context, imageName string, koBuilder build.Interface, koPublisher publish.Interface) (name.Reference, error) { | ||
importpath, err := getImportPath(imageName, koBuilder) | ||
if err != nil { | ||
return nil, fmt.Errorf("could not determine Go import path for ko image %q: %w", imageName, err) | ||
} | ||
imageMap, err := b.publishImages(ctx, []string{importpath}, koPublisher, koBuilder) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to publish ko image: %w", err) | ||
} | ||
imageRef, exists := imageMap[importpath] | ||
if !exists { | ||
return nil, fmt.Errorf("no built image found for Go import path %q build images: %+v", importpath, imageMap) | ||
} | ||
return imageRef, nil | ||
} | ||
|
||
// getImportPath determines the Go import path that ko should build. | ||
// | ||
// If the image name from the Skaffold config has the prefix `ko://`, then | ||
// treat the remainder of the string as the Go import path to build. This | ||
// matches current ko behavior for working with Kubernetes resource files, and | ||
// it will allow ko users to easily migrate to Skaffold without changing their | ||
// Kubernetes YAML files. See https://github.com/google/ko#yaml-changes. | ||
// | ||
// If the image name does _not_ start with `ko://`, determine the Go import | ||
// path of the image workspace directory. | ||
func getImportPath(imageName string, koBuilder build.Interface) (string, error) { | ||
if strings.HasPrefix(imageName, build.StrictScheme) { | ||
return imageName, nil | ||
} | ||
return koBuilder.QualifyImport(".") | ||
} | ||
|
||
// getImageIdentifier returns the image tag or digest for published images (`pushImages=true`), | ||
// or the image ID from the local Docker daemon for sideloaded images (`pushImages=false`). | ||
func (b *Builder) getImageIdentifier(ctx context.Context, imageRef name.Reference, ref string) (string, error) { | ||
if b.pushImages { | ||
return imageRef.Identifier(), nil | ||
} | ||
imageIdentifier, err := b.localDocker.ImageID(ctx, ref) | ||
if err != nil { | ||
return "", fmt.Errorf("could not get imageID from local Docker Daemon for image %s: %+v", ref, err) | ||
} | ||
return imageIdentifier, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
/* | ||
Copyright 2021 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 ko | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"strings" | ||
"testing" | ||
|
||
"github.com/docker/docker/client" | ||
"github.com/google/go-containerregistry/pkg/name" | ||
"github.com/google/ko/pkg/build" | ||
"github.com/google/ko/pkg/publish" | ||
|
||
// latestV1 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest/v1" | ||
latestV1 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/ko/schema" | ||
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker" | ||
"github.com/GoogleContainerTools/skaffold/testutil" | ||
) | ||
|
||
// koImportPath is the import path of this package, with the ko scheme prefix. | ||
const koImportPath = "ko://github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/ko" | ||
|
||
func TestBuildKoImages(t *testing.T) { | ||
tests := []struct { | ||
description string | ||
ref string | ||
imageID string | ||
pushImages bool | ||
importpath string | ||
imageNameFromConfig string | ||
workspace string | ||
}{ | ||
{ | ||
description: "simple image name in config and sideload image", | ||
ref: "gcr.io/project-id/test-app1:testTag", | ||
imageID: "imageID", | ||
pushImages: false, | ||
importpath: koImportPath, | ||
imageNameFromConfig: "test-app1", | ||
}, | ||
{ | ||
description: "ko import path used in image name config and sideload image", | ||
ref: "gcr.io/project-id/example.com/myapp:myTag", | ||
imageID: "imageID", | ||
pushImages: false, | ||
importpath: "ko://example.com/myapp", | ||
imageNameFromConfig: "ko://example.com/myapp", | ||
}, | ||
{ | ||
description: "simple image name in config and push image", | ||
ref: "gcr.io/project-id/test-app2:testTag", | ||
imageID: "testTag", | ||
pushImages: true, | ||
importpath: "ko://github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/ko", | ||
imageNameFromConfig: "test-app2", | ||
}, | ||
{ | ||
description: "ko import path used in image name config and push image", | ||
ref: "gcr.io/project-id/example.com/myapp:myTag", | ||
imageID: "myTag", | ||
pushImages: true, | ||
importpath: "ko://example.com/myapp", | ||
imageNameFromConfig: "ko://example.com/myapp", | ||
}, | ||
} | ||
for _, test := range tests { | ||
testutil.Run(t, test.description, func(t *testutil.T) { | ||
b := stubKoArtifactBuilder(test.ref, test.imageID, test.pushImages, test.importpath) | ||
|
||
artifact := &latestV1.Artifact{ | ||
ImageName: test.imageNameFromConfig, | ||
ArtifactType: latestV1.ArtifactType{ | ||
KoArtifact: &latestV1.KoArtifact{}, | ||
}, | ||
Workspace: test.workspace, | ||
Dependencies: []*latestV1.ArtifactDependency{}, | ||
} | ||
|
||
var outBuffer bytes.Buffer | ||
gotImageID, err := b.Build(context.TODO(), &outBuffer, artifact, test.ref) | ||
t.CheckNoError(err) | ||
if gotImageID != test.imageID { | ||
t.Errorf("got image ID %s, wanted %s", gotImageID, test.imageID) | ||
} | ||
imageNameOut := strings.TrimSuffix(outBuffer.String(), "\n") | ||
if imageNameOut != test.ref { | ||
t.Errorf("image name output was %q, wanted %q", imageNameOut, test.ref) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func stubKoArtifactBuilder(ref string, imageID string, pushImages bool, importpath string) *Builder { | ||
api := (&testutil.FakeAPIClient{}).Add(ref, imageID) | ||
localDocker := fakeLocalDockerDaemon(api) | ||
b := NewArtifactBuilder(localDocker, pushImages) | ||
|
||
// Fake implementation of the `publishImages` function. | ||
b.publishImages = func(_ context.Context, _ []string, _ publish.Interface, _ build.Interface) (map[string]name.Reference, error) { | ||
imageRef, err := name.ParseReference(ref) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return map[string]name.Reference{ | ||
importpath: imageRef, | ||
}, nil | ||
} | ||
return b | ||
} | ||
|
||
func fakeLocalDockerDaemon(api client.CommonAPIClient) docker.LocalDaemon { | ||
return docker.NewLocalDaemon(api, nil, false, nil) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
/* | ||
Copyright 2021 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 ko | ||
|
||
import ( | ||
"context" | ||
"strings" | ||
|
||
"github.com/google/ko/pkg/build" | ||
"github.com/google/ko/pkg/commands" | ||
"github.com/google/ko/pkg/commands/options" | ||
|
||
// latestV1 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest/v1" | ||
latestV1 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/ko/schema" | ||
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/version" | ||
) | ||
|
||
func (b *Builder) newKoBuilder(ctx context.Context, a *latestV1.Artifact) (build.Interface, error) { | ||
bo := buildOptions(a.KoArtifact.BaseImage, a.KoArtifact.Platforms, a.Workspace) | ||
return commands.NewBuilder(ctx, bo) | ||
} | ||
|
||
func buildOptions(baseImage string, platforms []string, workspace string) *options.BuildOptions { | ||
return &options.BuildOptions{ | ||
BaseImage: baseImage, | ||
ConcurrentBuilds: 1, | ||
Platform: strings.Join(platforms, ","), | ||
UserAgent: version.UserAgentWithClient(), | ||
WorkingDirectory: workspace, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
/* | ||
Copyright 2021 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 ko | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/version" | ||
"github.com/GoogleContainerTools/skaffold/testutil" | ||
) | ||
|
||
func TestBuildOptions(t *testing.T) { | ||
tests := []struct { | ||
description string | ||
baseImage string | ||
platforms []string | ||
wantPlatform string | ||
workspace string | ||
}{ | ||
{ | ||
description: "all zero value", | ||
}, | ||
{ | ||
description: "empty platforms", | ||
platforms: []string{}, | ||
}, | ||
{ | ||
description: "base image", | ||
baseImage: "gcr.io/distroless/static:nonroot", | ||
}, | ||
{ | ||
description: "multiple platforms", | ||
platforms: []string{"linux/amd64", "linux/arm64"}, | ||
wantPlatform: "linux/amd64,linux/arm64", | ||
}, | ||
{ | ||
description: "workspace", | ||
workspace: "my-app-subdirectory", | ||
}, | ||
} | ||
for _, test := range tests { | ||
testutil.Run(t, test.description, func(t *testutil.T) { | ||
bo := buildOptions(test.baseImage, test.platforms, test.workspace) | ||
if bo.BaseImage != test.baseImage { | ||
t.Errorf("wanted BaseImage (%q), got (%q)", test.baseImage, bo.BaseImage) | ||
} | ||
if bo.ConcurrentBuilds < 1 { | ||
t.Errorf("ConcurrentBuilds must always be >= 1 for the ko builder") | ||
} | ||
if bo.Platform != test.wantPlatform { | ||
t.Errorf("wanted platform (%q), got (%q)", test.wantPlatform, bo.Platform) | ||
} | ||
if bo.UserAgent != version.UserAgentWithClient() { | ||
t.Errorf("need user agent for fetching the base image") | ||
} | ||
if bo.WorkingDirectory != test.workspace { | ||
t.Errorf("wanted WorkingDirectory (%q), got (%q)", test.workspace, bo.WorkingDirectory) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.