Skip to content

Commit 08f6a86

Browse files
committed
test(ko): Simple integration test for ko builder
Programmatic integration test to build and push a container image using the ko builder. CLI-type end-to-end integration tests will require Skaffold config unmarshalling support for the ko builder. Tracking: #6041
1 parent 33dbba4 commit 08f6a86

File tree

9 files changed

+311
-2
lines changed

9 files changed

+311
-2
lines changed

integration/examples/ko/README.md

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
### Example: ko builder
2+
3+
This is an example demonstrating building a Go app with the
4+
[ko](https://github.com/google/ko) builder.
5+
6+
The included [Cloud Build](https://cloud.google.com/build/docs) configuration
7+
file shows how users can set up a simple pipeline using `skaffold build` and
8+
`skaffold deploy`, without having to create a custom builder image.
+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Copyright 2021 The Skaffold Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
# Demonstrate Skaffold build using ko builder and deploy to GKE.
16+
17+
options:
18+
dynamic_substitutions: true
19+
env:
20+
- 'KUBECONFIG=/workspace/.kubeconfig'
21+
- 'SKAFFOLD_DEFAULT_REPO=$_IMAGE_REPO'
22+
- 'SKAFFOLD_DETECT_MINIKUBE=false'
23+
- 'SKAFFOLD_INTERACTIVE=false'
24+
- 'SKAFFOLD_TIMESTAMPS=true'
25+
- 'SKAFFOLD_UPDATE_CHECK=false'
26+
- 'SKAFFOLD_VERBOSITY=info'
27+
28+
steps:
29+
- id: creds
30+
name: $_GCLOUD_IMAGE
31+
entrypoint: gcloud
32+
args:
33+
- container
34+
- clusters
35+
- get-credentials
36+
- $_GKE_CLUSTER_NAME
37+
- --project=$_GKE_CLUSTER_PROJECT_ID
38+
- --zone=$_GKE_CLUSTER_ZONE
39+
40+
- id: build
41+
name: $_SKAFFOLD_IMAGE
42+
entrypoint: skaffold
43+
args:
44+
- build
45+
- --file-output=artifacts.json
46+
47+
- id: deploy
48+
name: $_SKAFFOLD_IMAGE
49+
entrypoint: skaffold
50+
args:
51+
- deploy
52+
- --build-artifacts=artifacts.json
53+
- --status-check=true
54+
55+
substitutions:
56+
_GKE_CLUSTER_NAME: skaffold-ko
57+
_GKE_CLUSTER_PROJECT_ID: $PROJECT_ID
58+
_GKE_CLUSTER_ZONE: us-central1-f
59+
_IMAGE_REPO: gcr.io/${PROJECT_ID}
60+
_GCLOUD_IMAGE: gcr.io/k8s-skaffold/skaffold
61+
_SKAFFOLD_IMAGE: gcr.io/k8s-skaffold/skaffold
62+
63+
timeout: 1200s

integration/examples/ko/go.mod

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright 2021 The Skaffold Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
module github.com/GoogleContainerTools/skaffold/examples/ko
16+
17+
go 1.13
+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Copyright 2021 The Skaffold Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
apiVersion: v1
16+
kind: Service
17+
metadata:
18+
name: ko
19+
spec:
20+
ports:
21+
- name: http
22+
port: 80
23+
targetPort: 8080
24+
selector:
25+
app: ko
26+
type: ClusterIP
27+
---
28+
apiVersion: apps/v1
29+
kind: Deployment
30+
metadata:
31+
name: ko
32+
spec:
33+
selector:
34+
matchLabels:
35+
app: ko
36+
template:
37+
metadata:
38+
labels:
39+
app: ko
40+
spec:
41+
containers:
42+
- image: skaffold-ko
43+
name: ko
44+
ports:
45+
- containerPort: 8080

integration/examples/ko/main.go

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
Copyright 2021 The Skaffold Authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package main
18+
19+
import (
20+
"fmt"
21+
"log"
22+
"net/http"
23+
)
24+
25+
func main() {
26+
http.HandleFunc("/", hello)
27+
28+
log.Println("Listening on port 8080")
29+
http.ListenAndServe(":8080", nil)
30+
}
31+
32+
func hello(w http.ResponseWriter, r *http.Request) {
33+
fmt.Fprintf(w, "Hello, World!")
34+
}

integration/examples/ko/skaffold.yaml

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Copyright 2021 The Skaffold Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
apiVersion: skaffold/v2beta25
16+
kind: Config
17+
build:
18+
artifacts:
19+
- image: skaffold-ko
20+
ko: {}

integration/ko_test.go

+117
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/*
2+
Copyright 2021 The Skaffold Authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package integration
18+
19+
import (
20+
"bytes"
21+
"context"
22+
"fmt"
23+
"io/ioutil"
24+
stdlog "log"
25+
"net/http/httptest"
26+
"path/filepath"
27+
"runtime"
28+
"strings"
29+
"testing"
30+
31+
"github.com/google/go-cmp/cmp"
32+
"github.com/google/go-containerregistry/pkg/crane"
33+
"github.com/google/go-containerregistry/pkg/registry"
34+
"github.com/google/go-containerregistry/pkg/v1/random"
35+
36+
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/ko"
37+
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/config"
38+
latestV1 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest/v1"
39+
)
40+
41+
func TestBuildAndPushKoImageProgrammatically(t *testing.T) {
42+
MarkIntegrationTest(t, CanRunWithoutGcp)
43+
44+
// Start a local registry server.
45+
// This registry hosts the base image, and it is the target registry for the built image.
46+
baseimageNamespace := "baseimage"
47+
registryServer, err := registryServerWithImage(baseimageNamespace)
48+
if err != nil {
49+
t.Fatalf("could not create test registry server: %v", err)
50+
}
51+
defer registryServer.Close()
52+
registryAddr := registryServer.Listener.Addr().String()
53+
baseImage := fmt.Sprintf("%s/%s", registryAddr, baseimageNamespace)
54+
55+
// Get the directory of the basic ko sample app from the `examples` directory.
56+
exampleAppDir, err := koExampleAppDir()
57+
if err != nil {
58+
t.Fatalf("could not get ko example app dir: %+v", err)
59+
}
60+
61+
// Build the artifact
62+
b := ko.NewArtifactBuilder(nil, true, config.RunModes.Build, nil)
63+
var imageFullNameBuffer bytes.Buffer
64+
artifact := &latestV1.Artifact{
65+
ArtifactType: latestV1.ArtifactType{
66+
KoArtifact: &latestV1.KoArtifact{
67+
BaseImage: baseImage,
68+
},
69+
},
70+
Workspace: exampleAppDir,
71+
}
72+
imageName := fmt.Sprintf("%s/%s", registryAddr, "skaffold-ko")
73+
digest, err := b.Build(context.Background(), &imageFullNameBuffer, artifact, imageName)
74+
if err != nil {
75+
t.Fatalf("b.Build(): %+v", err)
76+
}
77+
78+
wantImageFullName := fmt.Sprintf("%s@%s", imageName, digest)
79+
gotImageFullName := strings.TrimSuffix(imageFullNameBuffer.String(), "\n")
80+
if diff := cmp.Diff(wantImageFullName, gotImageFullName); diff != "" {
81+
t.Errorf("image name mismatch (-want +got):\n%s", diff)
82+
}
83+
}
84+
85+
// registryServerWithImage starts a local registry and pushes a random image.
86+
// Use this to speed up tests, by not having to reach out to a real registry.
87+
// The registry uses a NOP logger to avoid spamming test logs.
88+
// Remember to call `defer Close()` on the returned `httptest.Server`.
89+
func registryServerWithImage(namespace string) (*httptest.Server, error) {
90+
nopLog := stdlog.New(ioutil.Discard, "", 0)
91+
r := registry.New(registry.Logger(nopLog))
92+
s := httptest.NewServer(r)
93+
imageName := fmt.Sprintf("%s/%s", s.Listener.Addr().String(), namespace)
94+
image, err := random.Image(1024, 1)
95+
if err != nil {
96+
return nil, fmt.Errorf("random.Image(): %+v", err)
97+
}
98+
err = crane.Push(image, imageName)
99+
if err != nil {
100+
return nil, fmt.Errorf("crane.Push(): %+v", err)
101+
}
102+
return s, nil
103+
}
104+
105+
// koExampleAppDir returns the directory path of the basic ko builder sample app.
106+
func koExampleAppDir() (string, error) {
107+
_, filename, _, ok := runtime.Caller(0)
108+
if !ok {
109+
return "", fmt.Errorf("could not get current filename")
110+
}
111+
basepath := filepath.Dir(filename)
112+
exampleDir, err := filepath.Abs(filepath.Join(basepath, "examples", "ko"))
113+
if err != nil {
114+
return "", fmt.Errorf("could not get absolute path of example from basepath %q: %w", basepath, err)
115+
}
116+
return exampleDir, nil
117+
}

pkg/skaffold/build/ko/publisher.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,11 @@ import (
2929
)
3030

3131
func (b *Builder) newKoPublisher(ref string) (publish.Interface, error) {
32-
po, err := publishOptions(ref, b.pushImages, b.localDocker.RawClient(), b.insecureRegistries)
32+
var dockerClient daemon.Client
33+
if b.localDocker != nil {
34+
dockerClient = b.localDocker.RawClient()
35+
}
36+
po, err := publishOptions(ref, b.pushImages, dockerClient, b.insecureRegistries)
3337
if err != nil {
3438
return nil, err
3539
}

pkg/skaffold/schema/validation/samples_test.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ const (
3838
)
3939

4040
var (
41-
ignoredExamples = []string{"docker-deploy", "react-reload-docker"}
41+
// TODO(halvards): Remove ko exmples from ignoredExamples once unmarshalling of ko config is enabled.
42+
ignoredExamples = []string{"docker-deploy", "ko", "react-reload-docker"}
4243
ignoredSamples = []string{"structureTest.yaml", "build.sh", "globalConfig.yaml", "Dockerfile.app", "Dockerfile.base"}
4344
)
4445

0 commit comments

Comments
 (0)