github.com/GoogleContainerTools/skaffold/v2@v2.13.2/integration/build_test.go (about) 1 /* 2 Copyright 2019 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 "context" 21 "os" 22 "os/exec" 23 "strings" 24 "testing" 25 "time" 26 27 "4d63.com/tz" 28 "github.com/google/go-cmp/cmp" 29 "github.com/google/go-cmp/cmp/cmpopts" 30 v1 "github.com/opencontainers/image-spec/specs-go/v1" 31 32 "github.com/GoogleContainerTools/skaffold/v2/cmd/skaffold/app/flags" 33 "github.com/GoogleContainerTools/skaffold/v2/integration/skaffold" 34 "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/build/jib" 35 "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/docker" 36 kubectx "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/kubernetes/context" 37 "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/runner/runcontext" 38 "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/util" 39 "github.com/GoogleContainerTools/skaffold/v2/testutil" 40 ) 41 42 const imageName = "us-central1-docker.pkg.dev/k8s-skaffold/testing/simple-build:" 43 44 func TestBuild(t *testing.T) { 45 tests := []struct { 46 description string 47 dir string 48 args []string 49 expectImage string 50 setup func(t *testing.T, workdir string) 51 }{ 52 { 53 description: "docker build", 54 dir: "testdata/build", 55 }, 56 { 57 description: "git tagger", 58 dir: "testdata/tagPolicy", 59 args: []string{"-p", "gitCommit"}, 60 setup: setupGitRepo, 61 expectImage: imageName + "v1", 62 }, 63 { 64 description: "sha256 tagger", 65 dir: "testdata/tagPolicy", 66 args: []string{"-p", "sha256"}, 67 expectImage: imageName + "latest", 68 }, 69 { 70 description: "dateTime tagger", 71 dir: "testdata/tagPolicy", 72 args: []string{"-p", "dateTime"}, 73 // around midnight this test might fail, if the tests above run slowly 74 expectImage: imageName + nowInChicago(), 75 }, 76 { 77 description: "envTemplate tagger", 78 dir: "testdata/tagPolicy", 79 args: []string{"-p", "envTemplate"}, 80 expectImage: imageName + "tag", 81 }, 82 { 83 description: "custom", 84 dir: "examples/custom", 85 }, 86 { 87 description: "--tag arg", 88 dir: "testdata/tagPolicy", 89 args: []string{"-p", "args", "-t", "foo"}, 90 expectImage: imageName + "foo", 91 }, 92 { 93 description: "envTemplate command tagger", 94 dir: "testdata/tagPolicy", 95 args: []string{"-p", "envTemplateCmd"}, 96 expectImage: imageName + "1.0.0", 97 }, 98 { 99 description: "envTemplate default tagger", 100 dir: "testdata/tagPolicy", 101 args: []string{"-p", "envTemplateDefault"}, 102 expectImage: imageName + "bar", 103 }, 104 } 105 for _, test := range tests { 106 t.Run(test.description, func(t *testing.T) { 107 MarkIntegrationTest(t, CanRunWithoutGcp) 108 if test.setup != nil { 109 test.setup(t, test.dir) 110 } 111 112 // Run without artifact caching 113 skaffold.Build(append(test.args, "--cache-artifacts=false")...).InDir(test.dir).RunOrFail(t) 114 115 // Run with artifact caching 116 skaffold.Build(append(test.args, "--cache-artifacts=true")...).InDir(test.dir).RunOrFail(t) 117 118 // Run a second time with artifact caching 119 out := skaffold.Build(append(test.args, "--cache-artifacts=true")...).InDir(test.dir).RunOrFailOutput(t) 120 if strings.Contains(string(out), "Not found. Building") { 121 t.Errorf("images were expected to be found in cache: %s", out) 122 } 123 checkImageExists(t, test.expectImage) 124 }) 125 } 126 } 127 128 func TestBuildWithWithPlatform(t *testing.T) { 129 tests := []struct { 130 description string 131 dir string 132 args []string 133 image string 134 expectedPlatforms []v1.Platform 135 }{ 136 { 137 description: "docker build linux/amd64", 138 dir: "testdata/build/docker-with-platform-amd", 139 args: []string{"--platform", "linux/amd64"}, 140 expectedPlatforms: []v1.Platform{{OS: "linux", Architecture: "amd64"}}, 141 }, 142 { 143 description: "docker build linux/arm64", 144 dir: "testdata/build/docker-with-platform-arm", 145 args: []string{"--platform", "linux/arm64"}, 146 expectedPlatforms: []v1.Platform{{OS: "linux", Architecture: "arm64"}}, 147 }, 148 } 149 150 for _, test := range tests { 151 t.Run(test.description, func(t *testing.T) { 152 MarkIntegrationTest(t, CanRunWithoutGcp) 153 tmpfile := testutil.TempFile(t, "", []byte{}) 154 args := append(test.args, "--file-output", tmpfile) 155 skaffold.Build(args...).InDir(test.dir).RunOrFail(t) 156 bytes, err := os.ReadFile(tmpfile) 157 failNowIfError(t, err) 158 buildArtifacts, err := flags.ParseBuildOutput(bytes) 159 failNowIfError(t, err) 160 checkLocalImagePlatforms(t, buildArtifacts.Builds[0].Tag, test.expectedPlatforms) 161 }) 162 } 163 } 164 165 func TestBuildWithMultiPlatforms(t *testing.T) { 166 tests := []struct { 167 description string 168 dir string 169 args []string 170 image string 171 expectedPlatforms []v1.Platform 172 }{ 173 { 174 description: "build cross platform images with gcb", 175 dir: "testdata/build/gcb-with-platform", 176 args: []string{"--platform", "linux/arm64,linux/amd64"}, 177 expectedPlatforms: []v1.Platform{{OS: "linux", Architecture: "arm64"}, {OS: "linux", Architecture: "amd64"}}, 178 }, 179 } 180 181 for _, test := range tests { 182 t.Run(test.description, func(t *testing.T) { 183 MarkIntegrationTest(t, NeedsGcp) 184 tmpfile := testutil.TempFile(t, "", []byte{}) 185 args := append(test.args, "--file-output", tmpfile) 186 skaffold.Build(args...).InDir(test.dir).RunOrFail(t) 187 bytes, err := os.ReadFile(tmpfile) 188 failNowIfError(t, err) 189 buildArtifacts, err := flags.ParseBuildOutput(bytes) 190 failNowIfError(t, err) 191 checkRemoteImagePlatforms(t, buildArtifacts.Builds[0].Tag, test.expectedPlatforms) 192 }) 193 } 194 } 195 196 // TestExpectedBuildFailures verifies that `skaffold build` fails in expected ways 197 func TestExpectedBuildFailures(t *testing.T) { 198 if !jib.JVMFound(context.Background()) { 199 t.Fatal("test requires Java VM") 200 } 201 202 tests := []struct { 203 description string 204 dir string 205 args []string 206 expected string 207 }{ 208 { 209 description: "jib is too old", 210 dir: "testdata/jib", 211 args: []string{"-p", "old-jib"}, 212 expected: "Could not find goal '_skaffold-fail-if-jib-out-of-date' in plugin com.google.cloud.tools:jib-maven-plugin:1.3.0", 213 // test string will need to be updated for the jib.requiredVersion error text when moving to Jib > 1.4.0 214 }, 215 } 216 for _, test := range tests { 217 t.Run(test.description, func(t *testing.T) { 218 MarkIntegrationTest(t, NeedsGcp) 219 if out, err := skaffold.Build(test.args...).InDir(test.dir).RunWithCombinedOutput(t); err == nil { 220 t.Fatal("expected build to fail") 221 } else if !strings.Contains(string(out), test.expected) { 222 t.Log("build output: ", string(out)) 223 t.Fatalf("build failed but for wrong reason") 224 } 225 }) 226 } 227 } 228 229 func checkLocalImagePlatforms(t *testing.T, image string, expected []v1.Platform) { 230 if expected == nil { 231 return 232 } 233 t.Helper() 234 235 cfg, err := kubectx.CurrentConfig() 236 failNowIfError(t, err) 237 238 client, err := docker.NewAPIClient(context.Background(), &runcontext.RunContext{ 239 KubeContext: cfg.CurrentContext, 240 }) 241 failNowIfError(t, err) 242 inspect, _, err := client.ImageInspectWithRaw(context.Background(), image) 243 failNowIfError(t, err) 244 245 actual := []v1.Platform{{Architecture: inspect.Architecture, OS: inspect.Os}} 246 checkPlatformsEqual(t, actual, expected) 247 } 248 249 func checkRemoteImagePlatforms(t *testing.T, image string, expected []v1.Platform) { 250 if expected == nil { 251 return 252 } 253 t.Helper() 254 actual, err := docker.GetPlatforms(image) 255 if err != nil { 256 t.Error(err) 257 } 258 checkPlatformsEqual(t, actual, expected) 259 } 260 261 func checkPlatformsEqual(t *testing.T, actual, expected []v1.Platform) { 262 platLess := func(a, b v1.Platform) bool { 263 return a.OS < b.OS || (a.OS == b.OS && a.Architecture < b.Architecture) 264 } 265 if diff := cmp.Diff(expected, actual, cmpopts.SortSlices(platLess)); diff != "" { 266 t.Fatalf("Platforms differ (-got,+want):\n%s", diff) 267 } 268 } 269 270 // checkImageExists asserts that the given image is present 271 func checkImageExists(t *testing.T, image string) { 272 t.Helper() 273 274 if image == "" { 275 return 276 } 277 278 cfg, err := kubectx.CurrentConfig() 279 failNowIfError(t, err) 280 281 // TODO: use the proper RunContext 282 client, err := docker.NewAPIClient(context.Background(), &runcontext.RunContext{ 283 KubeContext: cfg.CurrentContext, 284 }) 285 failNowIfError(t, err) 286 287 ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(5*time.Second)) 288 defer cancel() 289 if !client.ImageExists(ctx, image) { 290 t.Errorf("expected image '%s' not present", image) 291 } 292 } 293 294 // setupGitRepo sets up a clean repo with tag v1 295 func setupGitRepo(t *testing.T, dir string) { 296 t.Cleanup(func() { os.RemoveAll(dir + "/.git") }) 297 298 gitArgs := [][]string{ 299 {"init"}, 300 {"config", "user.email", "john@doe.org"}, 301 {"config", "user.name", "John Doe"}, 302 {"add", "."}, 303 {"commit", "-m", "Initial commit"}, 304 {"tag", "v1"}, 305 } 306 307 for _, args := range gitArgs { 308 cmd := exec.Command("git", args...) 309 cmd.Dir = dir 310 if buf, err := util.RunCmdOut(context.Background(), cmd); err != nil { 311 t.Logf(string(buf)) 312 t.Fatal(err) 313 } 314 } 315 } 316 317 // nowInChicago returns the dateTime string as generated by the dateTime tagger 318 func nowInChicago() string { 319 loc, _ := tz.LoadLocation("America/Chicago") 320 return time.Now().In(loc).Format("2006-01-02") 321 } 322 323 type Fataler interface { 324 Fatal(args ...interface{}) 325 Helper() 326 } 327 328 func failNowIfError(t Fataler, err error) { 329 t.Helper() 330 if err != nil { 331 t.Fatal(err) 332 } 333 }