github.com/GoogleContainerTools/skaffold/v2@v2.13.2/docs-v2/design_proposals/ko-builder.md (about) 1 # ko builder 2 3 * Author(s): Halvard Skogsrud (@halvards) 4 * Design Shepherd: \<skaffold-core-team-member\> 5 * Date: 2021-03-29 6 * Status: Draft 7 8 ## Objectives 9 10 Fast, standardized, reproducible, configuration-less, Docker-less, and 11 secure-by-default container image builds for Go apps. 12 13 ## Background 14 15 [ko](https://github.com/google/ko) is a container image builder for Go. It's 16 [fast](https://cloud.google.com/blog/topics/developers-practitioners/ship-your-go-applications-faster-cloud-run-ko), 17 doesn't use a `Dockerfile` or rely on the Docker daemon, and uses 18 [distroless](https://github.com/GoogleContainerTools/distroless) base images by 19 default. ko is to Go apps what 20 [Jib](https://github.com/GoogleContainerTools/jib) is to JVM-based apps 21 (approximately). 22 23 The [Knative](https://knative.dev/) and [Tekton](https://tekton.dev/) open 24 source projects use ko. 25 26 ## Proposal 27 28 This proposal adds a new `ko` builder to Skaffold, based on the `ko build` 29 command (a.k.a. `ko publish`). The integration does _not_ include other ko 30 functionality related to 31 [rendering](https://github.com/google/ko#ko-resolve) manifests, 32 [deploying](https://github.com/google/ko#ko-apply) to Kubernetes clusters, and 33 [file watching](https://github.com/google/ko/blob/f7df8106196518df5c6c35432843421e33990329/pkg/commands/resolver.go#L240). 34 35 Compared to ... 36 37 - [the Cloud Native buildpacks builder](https://skaffold.dev/docs/builders/buildpacks/), 38 the ko builder is 39 [fast](https://cloud.google.com/blog/topics/developers-practitioners/ship-your-go-applications-faster-cloud-run-ko), 40 doesn't require Docker, and uses a default base image that has a small attack 41 surface ([distroless](https://github.com/GoogleContainerTools/distroless)). 42 43 - [the Docker builder](https://skaffold.dev/docs/builders/docker/), 44 the ko builder standardizes builds, avoiding artisanal 45 [snowflake](https://martinfowler.com/bliki/SnowflakeServer.html) 46 `Dockerfile`s. It also doesn't require the Docker daemon, so builds can 47 run in security-constrained environments. 48 49 - [the Kaniko builder](https://skaffold.dev/docs/builders/docker/#dockerfile-in-cluster-with-kaniko), 50 the ko builder doesn't need a Kubernetes cluster, and avoids the 51 previously-mentioned artisanal `Dockerfile`s. 52 53 - [the Bazel builder](https://skaffold.dev/docs/builders/bazel/), 54 the ko builder doesn't require users to adopt Bazel. However, users who 55 already use Bazel for their Go app should use the Bazel builder. 56 57 - [the custom builder](https://skaffold.dev/docs/builders/custom/), 58 the ko builder is portable: 59 60 1. The Skaffold config can be shared with other developers and ops teams, 61 and used in CI/CD pipelines, without requiring users to install 62 additional tools such as Docker Engine or 63 [crane](https://github.com/google/go-containerregistry/blob/main/cmd/crane/README.md) 64 (or even ko, depending on how the builder is implemented). This eases the 65 path to adoption of Skaffold and reduces friction for users, both for 66 local development, and for anyone using Skaffold in CI/CD pipelines. 67 68 2. The ko builder doesn't require running custom shell scripts. This means 69 more standardized builds, a desirable trait for enterprise users. 70 71 The ko builder supports and enhances these Skaffold 72 [features](https://skaffold.dev/docs/): 73 74 - _fast local workflow_: building with ko is 75 [fast](https://cloud.google.com/blog/topics/developers-practitioners/ship-your-go-applications-faster-cloud-run-ko). 76 77 - _share with other developers_: no additional tools are required to 78 `skaffold run` with the ko builder, not even Docker. Though if we don't embed 79 ko in Skaffold, ko will be a tool that all developers in a team would have to 80 install. 81 82 - works great as a _CI/CD building block_: when using the ko builder, pipeline 83 steps can run using the default Skaffold container image, without 84 installing additional tools or keeping toolchain versions in sync across 85 local development and CI/CD. 86 87 ## Background: ko image names and Go import paths 88 89 Ko uses Go import paths to build images. The 90 [`ko build`](https://github.com/google/ko#build-an-image) command is similar 91 to `go build` and takes a positional argument, which can be either a local 92 file path or a Go import path. If the argument is a local file path (as per 93 [`go/build.IsLocalImport()`](https://pkg.go.dev/go/build#IsLocalImport)) 94 then, ko resolves the local file path to a Go import path (see 95 [`github.com/google/ko/pkg/build`](https://github.com/google/ko/blob/ab4d264103bd4931c6721d52bfc9d1a2e79c81d1/pkg/build/gobuild.go#L261)). 96 97 The import path must be of the package than contains the `main()` function. 98 For instance, to build Skaffold using ko, from the repository root directory: 99 100 ```sh 101 ko build ./cmd/skaffold 102 ``` 103 104 or 105 106 ```sh 107 ko build github.com/GoogleContainerTools/skaffold/cmd/skaffold 108 ``` 109 110 When the ko CLI is used to 111 [populate the image name in templated Kubernetes resource files](https://github.com/google/ko#kubernetes-integration), 112 only the Go import path option can be used, and the import path must be 113 prefixed by the `ko://` scheme, e.g., 114 `ko://github.com/GoogleContainerTools/skaffold/cmd/skaffold`. 115 116 Ko determines the image name from the container image registry (provided by the 117 `KO_DOCKER_REPO` environment variable) and the Go import path. The Go import 118 path is appended in one of these ways: 119 120 - The last path segment (e.g., `skaffold`), followed by a hyphen and a MD5 121 hash. This is the default behavior of the `ko build` command. 122 123 - The last path segment (e.g., `skaffold`) only, if `ko build` is invoked 124 with the `-B` or `--base-import-paths` flag. 125 126 - The full import path, lowercased (e.g., 127 `github.com/googlecontainertools/skaffold/cmd/skaffold`), if `ko build` is 128 invoked with the `-P` or `--preserve-import-paths` flag. This is the option 129 used by projects such as Knative (see the 130 [`release.sh` script](https://github.com/knative/serving/blob/v0.24.0/vendor/knative.dev/hack/release.sh#L98)) 131 and Tekton 132 (see the pipeline in 133 [publish.yaml](https://github.com/tektoncd/pipeline/blob/v0.25.0/tekton/publish.yaml#L137)). 134 135 - No import path (just `KO_DOCKER_REPO`), if `ko build` is invoked with the 136 `--bare` flag. 137 138 ## Supporting existing Skaffold users 139 140 The Skaffold ko builder follows the existing Skaffold image naming logic. This 141 means that the image naming behavior doesn't change for existing Skaffold users 142 who migrate from other builders to the ko builder. 143 144 The ko builder achieves this by using ko's 145 [`Bare`](https://github.com/google/ko/blob/ab4d264103bd4931c6721d52bfc9d1a2e79c81d1/pkg/commands/options/publish.go#L60) 146 naming option. 147 148 By using this option, the image name is not tied to the Go import path. If the 149 Skaffold 150 [default repo](https://skaffold.dev/docs/environment/image-registries/) value 151 is `gcr.io/k8s-skaffold` and the value of the `image` field in `skaffold.yaml` 152 is `skaffold`, the resulting image name will be `gcr.io/k8s-skaffold/skaffold`. 153 154 It is still necessary to resolve the Go import path for the underlying ko 155 implementation. To do so, the ko builder determines the import path based on 156 the value of the `main` config field. The `main` config field refers to the 157 location of a main package and corresponds to a `go build` pattern, e.g., 158 `go build ./cmd/skaffold`. Using the `main` field results in deterministic 159 behavior even in cases where there are multiple main packages in different 160 directories. 161 162 If `main` is a relative path (and it will be most of the time), it is 163 relative to the current 164 [`context`](https://skaffold.dev/docs/references/yaml/#build-artifacts-context) 165 (a.k.a. 166 [`Workspace`](https://github.com/GoogleContainerTools/skaffold/blob/v1.27.0/pkg/skaffold/schema/latest/config.go#L832)) 167 directory. 168 169 For example, to build Skaffold itself, with `package main` in the 170 `./cmd/skaffold/` subdirectory, the config would be as follows: 171 172 ```yaml 173 apiVersion: skaffold/v2beta26 174 kind: Config 175 build: 176 artifacts: 177 - image: skaffold 178 context: . 179 ko: 180 main: ./cmd/skaffold 181 ``` 182 183 Users can specify a `main` value using a pattern with the `...` wildcard, such 184 as `./...`. In this case, ko locates the main package. If there are multiple 185 main packages, 186 [ko fails](https://github.com/google/ko/blob/780c2812926cd706423e2ba65aeb1beb842c04af/pkg/build/gobuild.go#L270). 187 188 Implementation note: The value of `main` will be the input when invoking 189 [`QualifyImport()`](https://github.com/GoogleContainerTools/skaffold/blob/953594000be68fa8fad0ec4636ab03f8153a1c08/pkg/skaffold/build/ko/build.go#L93). 190 191 If the Go sources and the `go.mod` file are in a subdirectory of the `context` 192 directory, users can use the `dir` config field to specify the path where the 193 ko builder runs the `go` tool. 194 195 ## Supporting existing ko users 196 197 To support existing ko users moving to Skaffold, the ko builder also supports 198 `image` names in `skaffold.yaml` that use the Go import path, prefixed by the 199 `ko://` scheme. Examples of such image references in Kubernetes manifest files 200 can be seen in projects such as 201 [Knative](https://github.com/knative/serving/blob/main/config/core/deployments/activator.yaml#L41) 202 and 203 [Tekton](https://github.com/tektoncd/pipeline/blob/v0.25.0/config/controller.yaml#L66). 204 205 In the case of `ko://`-prefixed image names, the Skaffold ko builder 206 constructs the image name by: 207 208 1. Removing the `ko://` scheme prefix. 209 2. Transforming the import path to a valid image name using the function 210 [`SanitizeImageName()`](https://github.com/GoogleContainerTools/skaffold/blob/v1.27.0/pkg/skaffold/docker/reference.go#L83) 211 (from the package 212 `github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker`). 213 3. Combining the Skaffold default repo with the transformed import path as per 214 existing Skaffold image naming logic. 215 216 This will result in image names that match those produced by the `ko` CLI when 217 using the `-P` or `--preserve-import-paths` flag. For example, if the Skaffold 218 default repo is `gcr.io/k8s-skaffold` and the `image` name in `skaffold.yaml` 219 is `ko://github.com/GoogleContainerTools/skaffold/cmd/skaffold`, the resulting 220 image name will be 221 `gcr.io/k8s-skaffold/github.com/googlecontainertools/skaffold/cmd/skaffold`. 222 223 Real-world examples of image names that follow this naming convention can be 224 found in the Tekton and Knative release manifests. For instance, view the 225 images in the Knative Serving release YAMLs: 226 227 ```sh 228 curl -sL https://github.com/knative/serving/releases/download/v0.24.0/serving-core.yaml | grep 'image: ' 229 ``` 230 231 If the `image` field in `skaffold.yaml` starts with the `ko://` scheme prefix, 232 the Skaffold ko builder uses the Go import path that follows the prefix. For 233 example, to build Skaffold itself, with `package main` in the `./cmd/skaffold/` 234 subdirectory, the config would be as follows: 235 236 ```yaml 237 apiVersion: skaffold/v2beta26 238 kind: Config 239 build: 240 artifacts: 241 - image: ko://github.com/GoogleContainerTools/skaffold/cmd/skaffold 242 context: . 243 ko: {} 244 ``` 245 246 The `main` field is ignored if the `image` field starts with the `ko://` 247 scheme prefix. 248 249 ## Debugging ko images using Skaffold 250 251 Ko provides the 252 [`DisableOptimizations`](https://github.com/google/ko/blob/780c2812926cd706423e2ba65aeb1beb842c04af/pkg/commands/options/build.go#L34) 253 build option to 254 [set `gcflags` to disable optimizations and inlining](https://github.com/google/ko/blob/335c1ac8a6fdcc5eb0bb26579e4b44b4c62a9565/pkg/build/gobuild.go#L709-L712). 255 The ko builder sets `DisableOptimizations` to `true` when the Skaffold 256 `runMode` is `Debug`. 257 258 Skaffold can 259 [recognize Go-based container images](https://skaffold.dev/docs/workflows/debug/#go-runtime-go-protocols-dlv) 260 built by ko by the presence of the 261 [`KO_DATA_PATH` environment variable](https://github.com/google/ko/blob/9a256a4b1920ed5fd5fbf77d987fddbfb9733c40/pkg/build/gobuild.go#L803-L810). 262 This allows Skaffold to transform Pod specifications to enable remote 263 debugging. 264 265 The ko builder implementation work will add `KO_DATA_PATH` to 266 [the set of environment variables used to detect Go-based applications](https://github.com/GoogleContainerTools/skaffold/blob/c75c55133e709b2fee906eb158be13c7ccfa72cd/pkg/skaffold/debug/transform_go.go#L67-L72) 267 and updating the associated unit tests and 268 [documentation](https://github.com/GoogleContainerTools/skaffold/blob/01a833614efd780ddece99198aa7fdcf3f355706/docs-v1/content/en/docs/workflows/debug.md#L103). 269 270 ## Design 271 272 Adding the ko builder requires making config changes to the Skaffold schema. 273 274 1. Add a `KoArtifact` type: 275 276 ```go 277 // KoArtifact builds images using [ko](https://github.com/google/ko). 278 type KoArtifact struct { 279 // Asmflags are assembler flags passed to the builder. 280 Asmflags []string `yaml:"asmflags,omitempty"` 281 282 // BaseImage overrides the default ko base image. 283 // Corresponds to, and overrides, the `defaultBaseImage` in `.ko.yaml`. 284 BaseImage string `yaml:"fromImage,omitempty"` 285 286 // Dependencies are the file dependencies that Skaffold should watch for both 287 // rebuilding and file syncing for this artifact. 288 Dependencies *KoDependencies `yaml:"dependencies,omitempty"` 289 290 // Dir is the directory where the `go` tool will be run. 291 // The value is a directory path relative to the `context` directory. 292 // If empty, the `go` tool will run in the `context` directory. 293 // Examples: `live-at-head`, `compat-go114` 294 Dir string `yaml:"dir,omitempty"` 295 296 // Env are environment variables, in the `key=value` form, passed to the build. 297 // These environment variables are only used at build time. 298 // They are _not_ set in the resulting container image. 299 // For example: `["GOPRIVATE=source.developers.google.com", "GOCACHE=/workspace/.gocache"]`. 300 Env []string `yaml:"env,omitempty"` 301 302 // Flags are additional build flags passed to the builder. 303 // For example: `["-trimpath", "-v"]`. 304 Flags []string `yaml:"flags,omitempty"` 305 306 // Gcflags are Go compiler flags passed to the builder. 307 // For example: `["-m"]`. 308 Gcflags []string `yaml:"gcflags,omitempty"` 309 310 // Labels are key-value string pairs to add to the image config. 311 // For example: `{"org.opencontainers.image.source":"https://github.com/GoogleContainerTools/skaffold"}`. 312 Labels map[string]string `yaml:"labels,omitempty"` 313 314 // Ldflags are linker flags passed to the builder. 315 // For example: `["-buildid=", "-s", "-w"]`. 316 Ldflags []string `yaml:"ldflags,omitempty"` 317 318 // Main is the location of the main package. It is the pattern passed to `go build`. 319 // If main is specified as a relative path, it is relative to the `context` directory. 320 // If main is empty, the ko builder uses a default value of `.`. 321 // If main is a pattern with wildcards, such as `./...`, 322 // the expansion must contain only one main package, otherwise ko fails. 323 // Main is ignored if the `ImageName` starts with `ko://`. 324 // Example: `./cmd/foo` 325 Main string `yaml:"main,omitempty"` 326 327 // Platforms is the list of platforms to build images for. Each platform 328 // is of the format `os[/arch[/variant]]`, e.g., `linux/amd64`. 329 // By default, the ko builder builds for `all` platforms supported by the 330 // base image. 331 Platforms []string `yaml:"platforms,omitempty"` 332 333 // SourceDateEpoch is the `created` time of the container image. 334 // Specify as the number of seconds since January 1st 1970, 00:00 UTC. 335 // You can override this value by setting the `SOURCE_DATE_EPOCH` 336 // environment variable. 337 SourceDateEpoch uint64 `yaml:"sourceDateEpoch,omitempty"` 338 } 339 ``` 340 341 2. Add a `KoArtifact` field to the `ArtifactType` struct: 342 343 ```go 344 type ArtifactType struct { 345 [...] 346 // KoArtifact builds images using [ko](https://github.com/google/ko). 347 KoArtifact *KoArtifact `yaml:"ko,omitempty" yamltags:"oneOf=artifact"` 348 } 349 ``` 350 351 3. Define `KoDependencies`: 352 353 ```go 354 // KoDependencies is used to specify dependencies for an artifact built by ko. 355 type KoDependencies struct { 356 // Paths should be set to the file dependencies for this artifact, 357 // so that the Skaffold file watcher knows when to rebuild and perform file synchronization. 358 // Defaults to ["**/*.go"]. 359 Paths []string `yaml:"paths,omitempty" yamltags:"oneOf=dependency"` 360 361 // Ignore specifies the paths that should be ignored by Skaffold's file watcher. 362 // If a file exists in both `paths` and in `ignore`, it will be ignored, 363 // and will be excluded from both rebuilds and file synchronization. 364 Ignore []string `yaml:"ignore,omitempty"` 365 } 366 ``` 367 368 4. Add `KO` to the `BuilderType` enum in `proto/enums/enums.proto`: 369 370 ```proto 371 enum BuilderType { 372 // Could not determine builder type 373 UNKNOWN_BUILDER_TYPE = 0; 374 // JIB Builder 375 JIB = 1; 376 // Bazel Builder 377 BAZEL = 2; 378 // Buildpacks Builder 379 BUILDPACKS = 3; 380 // Custom Builder 381 CUSTOM = 4; 382 // Kaniko Builder 383 KANIKO = 5; 384 // Docker Builder 385 DOCKER = 6; 386 // Ko Builder 387 KO = 7; 388 } 389 ``` 390 391 ### Builder config schema 392 393 Example basic config, this will be sufficient for many users: 394 395 ```yaml 396 apiVersion: skaffold/v2beta26 397 kind: Config 398 build: 399 artifacts: 400 - image: skaffold-example-ko 401 ko: {} 402 ``` 403 404 The value of the `image` field is the Go import path of the app entry point, 405 [prefixed by `ko://`](https://github.com/google/ko/pull/58). 406 407 A more comprehensive example config: 408 409 ```yaml 410 apiVersion: skaffold/v2beta26 411 kind: Config 412 build: 413 artifacts: 414 - image: skaffold-example-ko-comprehensive 415 ko: 416 fromImage: gcr.io/distroless/base:nonroot 417 dependencies: 418 paths: 419 - go.mod 420 - "**.go" 421 dir: '.' 422 env: 423 - GOPRIVATE=source.developers.google.com 424 flags: 425 - -trimpath 426 - -v 427 gcflags: 428 - -m 429 labels: 430 foo: bar 431 baz: frob 432 ldflags: 433 - -buildid= 434 - -s 435 - -w 436 main: ./cmd/foo 437 platforms: 438 - linux/amd64 439 - linux/arm64 440 ``` 441 442 ko requires setting a 443 [`KO_DOCKER_REPO`](https://github.com/google/ko#choose-destination) 444 environment variable to specify where container images are pushed. The Skaffold 445 [default repo](https://skaffold.dev/docs/environment/image-registries/) 446 maps directly to this value. 447 448 ### Resolved questions 449 450 1. Should Skaffold embed ko as a Go module, or shell out? 451 452 __Resolved:__ Embed as a Go module 453 454 Benefits of embedding: 455 456 - Skaffold can pin the ko version it supports in its `go.mod` file. Users 457 wouldn't raise bugs/issues for incompatible version pairings of Skaffold 458 and ko. 459 460 - Reduce toolchain maintenance toil for users. Skaffold users wouldn't need 461 to synchronize ko versions used by different team members or in their CI 462 build, since the Skaffold version determines the ko version. 463 464 - Portability. Skaffold+ko users only need one tool for their container 465 image building needs: the `skaffold` binary. (Plus the Go distribution, 466 of course.) The current `gcr.io/k8s-skaffold/skaffold` container image 467 could serve as a build and deploy image for CI/CD pipeline steps. 468 469 Embedding ko would require some level of documented behavioural stability 470 guarantees for the most ko interfaces that Skaffold would use, such as 471 [`build.Interface`](https://github.com/google/ko/blob/82cabb40bae577ce3bc016e5939fd85889538e8b/pkg/build/build.go#L24) 472 and 473 [`publish.Interface`](https://github.com/google/ko/blob/82cabb40bae577ce3bc016e5939fd85889538e8b/pkg/publish/publish.go#L24), 474 or others? 475 476 Benefits of shelling out: 477 478 - It's an established pattern used by other Skaffold builders. 479 480 - It would allow Skaffold to support a range of ko versions. On the other 481 hand, these versions would need to be tracked and documented. 482 483 - No need to resolve dependency version differences between Skaffold and 484 ko. 485 486 - If a new ko version provided a significant bug fix, there would be no 487 need to release a new version of Skaffold for this fix. 488 489 Shelling out to ko would require some stability guarantees for the 490 `ko build` subcommand. 491 492 Suggest embedding as a Go module. 493 494 2. Should Skaffold use base image settings from 495 [`.ko.yaml`](https://github.com/google/ko#configuration) if the ko builder 496 definition in `skaffold.yaml` doesn't specify a base image? 497 498 __Resolved:__ Yes, to simplify adoption of Skaffold for existing ko users. 499 500 3. If a config value is set both as an environment variable, and as a config 501 value, which takes precedence? E.g., `ko.sourceDateEpoch` vs 502 `SOURCE_DATE_EPOCH`. 503 504 __Resolved:__ Follow existing Skaffold patterns. 505 506 4. Should the ko builder have a config option for 507 [`SOURCE_DATE_EPOCH`](https://reproducible-builds.org/specs/source-date-epoch/), 508 or should users specify the value via an environment variable? 509 510 __Resolved__: Specify via the reproducible builds spec environment variable 511 `SOURCE_DATE_EPOCH`, see 512 <https://github.com/google/ko#why-are-my-images-all-created-in-1970> and 513 <https://reproducible-builds.org/docs/source-date-epoch/>. 514 515 5. Should we default dependency paths to `["go.mod", "**.go"]` instead of 516 `["."]`.? 517 518 The former is a useful default for many (most?) Go apps, and it's used 519 in the `custom` example. The latter is the default for some other builders. 520 521 __Resolved__: Default to `["**/*.go"]`, see 522 [#6617](https://github.com/GoogleContainerTools/skaffold/pull/6617#discussion_r719804744). 523 524 6. Add a Google Cloud Build (`gcb`) support for the ko builder? 525 526 By embedding ko as a module, there is no need for a ko-specific Skaffold 527 builder image. 528 529 __Resolved__: Add remote builder support. 530 531 7. File sync support: Should we limit this to 532 [ko static assets](https://github.com/google/ko#static-assets) only? 533 534 This is the only way to include additional files in a container image 535 built by ko. 536 537 __Resolved__: Implement file sync (for the Beta stage). 538 539 ### Open questions 540 541 1. Should the ko builder be the default for `skaffold init`, instead of 542 buildpacks, for Go apps, when there's no Dockerfile and no Bazel workspace 543 file? 544 545 Suggest yes, to make Skaffold a compelling choice for Go developers. 546 547 If no, we can still consider configuring the ko builder if `skaffold init` 548 finds a `.ko.yaml` configuration file. 549 550 __Not Yet Resolved__ 551 552 ## Approach 553 554 Implement the ko builder as a series of small PRs that can be merged one by one. 555 The PRs should not surface any new user-visible behavior until the feature is 556 ready. 557 558 This approach has a lower risk than implementing the entire feature on a 559 separate branch before merging all at once. 560 561 The steps roughly outlined: 562 563 1. Add dependency on the `github.com/google/ko` module. 564 565 2. Implement the core ko build and publish logic, including unit tests in the 566 package `github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/ko`. 567 568 Support this implementation with "dummy" schema definitions for types such 569 as `KoArtifact` in a separate "temporary" package. This package only exists 570 until the definitions are merged into the latest `v1` schema, and it allows 571 for evolution of the types until they are committed to the schema. 572 573 3. Add integration test for the ko builder to the `integration` package, and 574 an example app + config to a new `integration/examples/ko` directory. 575 576 To avoid failures in schema unit tests from the new example in the 577 integration directory, add an `if` statement to `TestParseExamples` in the 578 package `github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema` that 579 skips directories called `ko`. 580 581 4. Add the ko builder schema types (`KoArtifact` and `KoDependency`) to the 582 latest unreleased schema. 583 584 For the `ArtifactType` struct, add a `KoArtifact` field, but set its 585 YAML flag key to `"-"` 586 (full syntax `` `yaml:"-,omitempty" yamltags:"oneOf=artifact"` ``). 587 588 Do _not_ yet add the `KO = 7;` entry to the `BuilderType` enum in 589 `proto/enums/enums.proto`. 590 591 5. Plumb the code in the package `pkg/skaffold/build/ko` into the other parts 592 of the Skaffold codebase that interacts with the config schema. 593 594 E.g., a `case` statement for `a.KoArtifact` in the `newPerArtifactBuilder` 595 function in `pkg/skaffold/build/local` (file `types.go`). 596 597 6. When we are ready to add the ko builder as an alpha feature to an upcoming 598 Skaffold release, set the `KoArtifact` YAML flag key to `ko` and add `KO` 599 to the `BuilderType` enum. 600 601 ## Implementation plan 602 603 1. [Done] Define integration points in the ko codebase that allows ko to be 604 used from Skaffold without duplicating existing ko CLI code. 605 606 In the package `github.com/google/ko/pkg/commands`: 607 608 [`resolver.go`](https://github.com/google/ko/blob/ee23538378722e060a2f7c7800f226e0b82e09e7/pkg/commands/resolver.go#L110) 609 ```go 610 // NewBuilder creates a ko builder 611 func NewBuilder(ctx context.Context, bo *options.BuildOptions) (build.Interface, error) 612 ``` 613 614 [`resolver.go`](https://github.com/google/ko/blob/ee23538378722e060a2f7c7800f226e0b82e09e7/pkg/commands/resolver.go#L146) 615 ```go 616 // NewPublisher creates a ko publisher 617 func NewPublisher(po *options.PublishOptions) (publish.Interface, error) 618 ``` 619 620 [`publisher.go`](https://github.com/google/ko/blob/ee23538378722e060a2f7c7800f226e0b82e09e7/pkg/commands/publisher.go#L28) 621 ```go 622 // PublishImages publishes images 623 func PublishImages(ctx context.Context, importpaths []string, pub publish.Interface, b build.Interface) (map[string]name.Reference, error) 624 ``` 625 626 Add build and publish options to support Skaffold config propagating to 627 ko. In the package `github.com/google/ko/pkg/commands/options`: 628 629 [`build.go`](https://github.com/google/ko/blob/ee23538378722e060a2f7c7800f226e0b82e09e7/pkg/commands/options/build.go#L25) 630 ```go 631 type BuildOptions struct { 632 // BaseImage enables setting the default base image programmatically. 633 // If non-empty, this takes precedence over the value in `.ko.yaml`. 634 BaseImage string 635 636 // WorkingDirectory allows for setting the working directory for invocations of the `go` tool. 637 // Empty string means the current working directory. 638 WorkingDirectory string 639 640 // UserAgent enables overriding the default value of the `User-Agent` HTTP 641 // request header used when retrieving the base image. 642 UserAgent string 643 644 [...] 645 } 646 ``` 647 648 [`publish.go`](https://github.com/google/ko/blob/ee23538378722e060a2f7c7800f226e0b82e09e7/pkg/commands/options/publish.go#L29) 649 ```go 650 type PublishOptions struct { 651 // DockerRepo configures the destination image repository. 652 // In normal ko usage, this is populated with the value of $KO_DOCKER_REPO. 653 DockerRepo string 654 655 // LocalDomain overrides the default domain for images loaded into the local Docker daemon. 656 // Use with Local=true. 657 LocalDomain string 658 659 // UserAgent enables overriding the default value of the `User-Agent` HTTP 660 // request header used when pushing the built image to an image registry. 661 UserAgent string 662 663 [...] 664 } 665 ``` 666 667 2. Add ko builder with support for existing ko config options. Provide 668 this as an Alpha feature in an upcoming Skaffold release. 669 670 Config options supported, all are optional: 671 672 - `fromImage`, to override the default distroless base image 673 - `dependencies`, for Skaffold file watching 674 - `dir`, if Go sources are not in the `context` directory 675 - `env`, to support ko CLI users who currently set environment variables 676 such as `GOFLAGS` when running ko. 677 - `labels`, e.g., to 678 [link an image to a Git repository](https://github.com/opencontainers/image-spec/blob/main/annotations.md#pre-defined-annotation-keys) 679 - `platforms` 680 - `sourceDateEpoch` 681 - `flags`, e.g., `-v`, `-trimpath` 682 - `ldflags`, e.g., `-s` 683 684 Example `skaffold.yaml` supported at this stage: 685 686 ```yaml 687 apiVersion: skaffold/v2beta26 688 kind: Config 689 build: 690 artifacts: 691 - image: skaffold-ko 692 ko: 693 fromImage: gcr.io/distroless/base:nonroot 694 dependencies: 695 paths: 696 - go.mod 697 - "**.go" 698 dir: '.' 699 env: 700 - GOPRIVATE=source.developers.google.com 701 labels: 702 foo: bar 703 baz: frob 704 ldflags: 705 - -s 706 main: ./cmd/foo 707 platforms: 708 - linux/amd64 709 - linux/arm64 710 ``` 711 712 3. Implement Skaffold config support for additional ko config options not 713 currently supported by ko: 714 715 - `asmflags` 716 - `gcflags` 717 718 Provide this as a feature in an upcoming Skaffold release. 719 720 ## Release plan 721 722 The ko builder will go through the release stages Alpha -> Beta -> Stable. 723 724 The following features will be released at each stage: 725 726 **Alpha** 727 728 - Support for the Skaffold lifecycle subcommands that build images: 729 - `build` 730 - `debug` 731 - `dev` 732 - `run` 733 - Images built using the ko builder are automatically detected as Go images 734 for debugging support. 735 - File watch support for `dev` mode. 736 - Local builder only (no `gcb` or `cluster` builder at this stage). 737 - Multi-platform images support, including support for 738 [`all`](https://github.com/google/ko#multi-platform-images), which builds 739 images for all platforms supported by the base image). 740 - Image names following standard Skaffold naming, for existing Skaffold 741 users. 742 - Support for `ko://`-prefixed image names, for existing ko users. 743 744 **Beta** 745 746 - File sync support. 747 - Remote builders support. 748 - `skaffold init` support, behind a `--enableKoInit` flag. 749 750 **Stable** 751 752 - Cloud Code integration 753 754 ## Integration test plan 755 756 Please describe what new test cases you are going to consider. 757 758 1. Unit and integration tests for ko builder, similar to other builders. 759 760 The integration tests should be written to catch situations such as where 761 changes to ko interfaces break the Skaffold ko builder. 762 763 2. Test that the ko flag 764 [`--disable-optimization`](https://github.com/google/ko/blob/f7df8106196518df5c6c35432843421e33990329/pkg/commands/options/build.go#L34) 765 is added for debugging. 766 767 3. Add basic and comprehensive ko examples to the `integration/examples` 768 directory.